Skip to content

feat(hall): Phase 3-C-1 把 A1-A4 反循环兜底抽成可插拔 policy 链 - #100

Closed
xiaolinfrank wants to merge 48 commits into
TianyiDataScience:mainfrom
xiaolinfrank:feat/hall-policy-chain-p3c1
Closed

feat(hall): Phase 3-C-1 把 A1-A4 反循环兜底抽成可插拔 policy 链#100
xiaolinfrank wants to merge 48 commits into
TianyiDataScience:mainfrom
xiaolinfrank:feat/hall-policy-chain-p3c1

Conversation

@xiaolinfrank

Copy link
Copy Markdown

Summary

Phase 3-C-1: 把 A1-A4 反循环兜底从 collaboration-hall-orchestrator.ts 散落的 inline 检查抽到 hall-policies.ts 的可插拔纯函数链——行为完全不变,为 P3-C-2(detectClarifyingQuestion / dropResolvedTriggers / enforceBackPingBudget)打地基。

针对 issue #9 第 4 项 / issue #13 P3-C 系列。

What landed

  • src/runtime/hall-policies.ts(新)

    • 常量:OBSERVE_SILENT_MARKER / MAX_AUTO_CHAIN_DEPTH=5 / AUTO_ROUND_BLOCK_THRESHOLD=6
    • 类型:PreDispatchVerdict / PostDispatchVerdict(带 policyId 让 caller 区分侧效)
    • 4 个 policy 纯函数:
      • enforceAutoRoundLimit(A2)—— per-(card, agent) 计数器命中 6 时 deny
      • enforceMaxAutoChainDepth —— chainDepth > 5 时 deny
      • excludeTriggerAuthor(A3)—— 候选 == triggerAuthor 时 deny
      • observeSilentMarker(A4)—— 空 / 以 OBSERVE_SILENT 开头时 drop
    • 链组合:HALL_PER_TARGET_GATE_POLICIES(per-target gate, A2 优先)/ HALL_CHAIN_FILTER_POLICIES(chain candidate 过滤)/ HALL_DEFAULT_POST_DISPATCH_POLICIES
    • 链 runner:runPreDispatchPolicies / runPostDispatchPolicies(顺序,第一个 deny/drop 短路)
    • 状态帮手:buildOperatorTurnStatePatch(A1 seed + A2 reset 合并成一个 patch)/ incrementAutoRoundCounter(不可变返回)
  • src/runtime/collaboration-hall-orchestrator.ts

    • 删除 inline 常量定义,改从 hall-policies 导入
    • routeAndDispatchHallMessage A1+A2-reset:22 行 → 8 行调 buildOperatorTurnStatePatch
    • dispatchHallAgentReply per-target gate:先 incrementAutoRoundCounterrunPreDispatchPolicies(HALL_PER_TARGET_GATE_POLICIES);deny 时按 policyId 分发侧效(仅 A2 触发 handleAutoRoundBlockedThreshold
    • persist 失败时把本地 rounds 反映到 taskCard,policy 链照样读到 incremented counter(保留旧 race-with-persistence 行为)
    • 三处 OBSERVE_SILENT silence check(observer / per-target / wake-mention initiator)→ runPostDispatchPolicies
    • 两处 chain candidate filter(observer + auto-chain)→ runPreDispatchPolicies(HALL_CHAIN_FILTER_POLICIES)
    • 保留 if (chainDepth < MAX_AUTO_CHAIN_DEPTH) 外层早出(避免白白拉 thread messages)
  • test/hall-policies.test.ts(新,38 个 case)

    • 4 个 policy 纯函数的所有 verdict 分支(含 ?? vs || 边界)
    • 链 runner 的空链 / 短路 / 顺序保留
    • 默认链组合行为 + 优先级(A2 在 PER_TARGET 链最先以保留 auto-round-blocked 通知)
    • buildOperatorTurnStatePatch 5 个分支 / incrementAutoRoundCounter 不可变性

设计要点

  • 行为不变是硬约束:所有 policy 都是纯函数,对应的输入/输出与原 inline 检查 1:1 对齐。每条 policy 注释列出对应的原行号区段
  • policyId 让 caller 区分侧效:A2 deny 时 caller 调 handleAutoRoundBlockedThreshold;其他 deny 静默丢弃。精确保留旧行为——老代码只在 A2 命中时发那条 system 消息
  • policy 链的两个站点:per-target gate(已 increment, A2 主战场)和 chain filter(未 increment, A3 + max-depth 主战场)。两个链各自的组合刻意把 A2-limit 留在 gate 链、把 max-depth + A3 留在 filter 链——避免 chain filter 误用 A2 把候选静默删掉而错过 auto-round-blocked 通知
  • 未来 P3-C-2 的入口:新 policy(dropResolvedTriggers / enforceBackPingBudget / detectClarifyingQuestion)只需 push 进 HALL_CHAIN_FILTER_POLICIES(或 gate 链),不需要再改 orchestrator

Test plan

  • npm run build 干净
  • test/hall-policies.test.ts 38/38 全过
  • hall-loop-prevention + collaboration-hall-orchestrator + hall-policies + hall-mailbox + hall-scheduler small batch:76 过 2 fail;2 fail 全是 P3-A 之前的基线(session-linkage / multi-mention routing),A1-A4 直接相关测试全过
  • 全套测试:~250 过,5 fail(3 基线 + 1 typing 已知 hang + 1 ui-render-smoke pre-existing on main)
  • Playwright e2e 略过(policy chain 是纯 refactor 不改行为;orchestrator 形态与 P3-A/P3-B-1/P3-B-2 同构,已多次 e2e 验证)

🤖 Generated with Claude Code

fosun_main_agent and others added 30 commits April 8, 2026 15:32
…g static PNGs

Hall avatars referenced /hall-avatars/*.png files that don't exist,
causing broken images. Switch to the same canvas-based pixel avatar
engine used by the staff panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add HallParallelSlot/HallParallelGroup types for concurrent agent execution
- Implement executeParallelDispatch() with incremental initiator wake-up
- Add write serialization (promise chain) to updateHallTaskCard/appendHallMessage
- Extend hall-runtime-dispatch with parallel_dispatch nextAction and parallelTasks parsing
- Teach manager/planner agents about parallel_dispatch in execution prompts
- Add parallel dispatch group rendering in Hall detail panel (client + server)
- Register 7 agents in openclaw.json (main + 6 domain specialists)
- Extend hall-role-resolver with pm/bioinfo/scientist/compliance patterns
- Add agent avatar images (resized to 512px) and avatar-preferences.json
- Create per-agent workspace AGENTS.md with responsibilities and reporting lines
- Add original Gemini-generated agent card images in assets/image

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core architectural change: the Hall is now a natural group chat instead
of a workflow engine with forced stages.

Routing:
- No @mention → main agent receives (decides: self-handle, delegate to
  specialist, or delegate to PM)
- @Specific agent → that agent responds directly
- Agent @mentions another in reply → auto-chain dispatch (up to 5 deep)

Prompt:
- Rewrite buildHallRuntimePrompt() to unified group chat mode
- Remove discussion/execution/handoff mode branching
- Role-specific instructions for main (router), manager (coordinator),
  specialists (domain workers)
- Expand thread message window from 10 to 30

Orchestration:
- New routeAndDispatchHallMessage() replaces discussion/execution cycles
- New dispatchHallAgentReply() with auto-chain on @mention
- Simplify postHallMessage() — just persist + route, no stage management
- Simplify createHallTaskFromOperatorRequest() — no discussion cycle,
  no room creation, no speaker queue

UI:
- Simplify detail panel — remove workflow state, keep parallel dispatch
  and participant info

Preserved:
- parallel_dispatch mechanism (manager can still dispatch agents in parallel)
- Task cards as thread containers (conversation grouping)
- Write serialization locks
- Existing workflow functions (kept but no longer called from new paths)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When other agents are the primary responders (@mentioned), main is
dispatched as an observer after the round settles. It reviews the
conversation and speaks up only if it spots issues, risks, gaps, or
improvement opportunities. Otherwise it stays silent (OBSERVE_SILENT
marker suppresses the message).

Main remains the default primary responder when no @mention is present.
The observer role is an additive personality trait, not a replacement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ting

- Remove "讨论中/等待 owner" stage labels from sidebar thread cards and
  thread header (both SSR and client-side rendering)
- Remove execution plan panel and decision panel from detail sidebar
- Fix agent reply suppression: skip concrete-deliverable enforcement when
  no explicit execution task is assigned (group chat mode)
- Fix @mention prefix matching: support partial display names like
  "@罗莎琳德 Rosalind" matching full alias "罗莎琳德 Rosalind (生信工程)"
- Fix "participants is not defined" JS error in renderDetail()
- Fix curly quote → straight quote replacement breaking regex patterns
  for Chinese quotation marks in mention matchers
- Extend draft typing indicator TTL from 30s to 180s to prevent
  premature "正在输入" disappearance during long agent responses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Group chat model has no manual approval or handoff workflow — agents
coordinate autonomously via @mention routing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove "+" action menu (only had duplicate "创建任务" which is
  already available via the "新任务" sidebar button)
- Remove "■" stop execution button (workflow concept)
- Fix stop message text: remove "returned to discussion" wording

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… display

- File sending: upload via 📎 button or drag-and-drop, inline image preview,
  data URL upload API, MIME whitelist, per-task workspace file copy
- Workspace isolation: per-task workdir via hall-workspaces/{taskCardId}/,
  workspace file browser in context pane, file serving endpoint
- Tool call display: real-time inline tool pills (✓ read, ✓ write) in
  message timeline during agent streaming, extracted from session JSONL
  files with baseline tracking, hover tooltip showing tool arguments
- Context sidebar defaults to open as third grid column
- Cross-browser file input fix (clip-rect instead of hidden attribute)
- Session file lookup fix for nested .openclaw agent paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an agent completes a handoff or @mention-triggered task, the
originating agent is now woken with a completion summary so it can
review results and decide next steps — matching the existing parallel
dispatch wake pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ver role for Main

- Add "observer" semantic role for Main agent (displayed as 观察者与管理者)
- Change role assignment from greedy one-per-role to individual pattern matching
- Add Chinese patterns to ROLE_PATTERNS for proper CJK display name matching
- Fix asHallSemanticRole validator to accept "observer" (was dropping Main)
- Fix employee panel: remove overly broad normalized.includes("codex") check
  that caused all agents to show "自动化编码执行"
- Add displayName parenthetical fallback for employee panel role labels
- Update role labels: manager→智能体管理者, observer→观察者与管理者, others→执行者
- Add isManagerLike helper in orchestrator so observer behaves like manager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
添加详细的项目文档CLAUDE.md,包含项目架构、命令说明和关键子系统描述
将版本号从0.1.1升级至1.0.0
添加主界面图片资源assets/image/main.jpg
1. 扫描 .jsonl.reset.* 轮转文件,修复76.5%的token漏计
2. 扫描嵌套 .openclaw/agents/ 目录,修复多智能体用量未统计
3. 修正轮转文件的sessionId提取逻辑

Closes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- hall-runtime-dispatch.ts: 添加 per-agent dispatch 串行化,按 agentId 排队,
  防止多线程同时派发到同一 agent 时 openclaw CLI 的 session file lock 超时
- hall-mention-router.ts: 把 `|` 加入 @mention 前/后边界字符集,让工具标记
  `[[tool:sessions_yield|@xxx ...]]` 内的 @mention 也能被识别派发

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSR 渲染消息后浏览器默认 scrollTop=0,导致用户看到最旧的消息。
在 bootstrap 时同步把 thread 滚到底部,确保 SSR 内容立即显示
最新消息,不再依赖 loadHall() 异步 fetch 后才触发的滚动逻辑。

Fixes #4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 调整 artifact tooltip 字号、内边距和阴影,提升可读性
- tooltip 使用 max-content 宽度并放宽最大宽度上限
- 将外层容器 overflow 改为 visible,避免 tooltip 被裁剪

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes #3

- 工具药丸 hover tooltip max-width 由 360px 提升到 720px,
  使用 overflow-wrap: anywhere 替代 word-break: break-all,
  避免路径/单词被截断;放大字号和内边距提升可读性
- 消息气泡 .hall-message-bubble 从 overflow: hidden 改为
  overflow: visible,使绝对定位的 tooltip 能溢出气泡显示
- renderMarkdown 和客户端 renderMarkdownHtml 均新增 markdown
  pipe 表格解析(tryParseTable),支持 :--- :---: ---: 三种
  对齐方式,输出 <table class="hall-md-table"> 结构
- 主题新增 .hall-md-table 样式:合并边框、表头底色、斑马行、
  display: block + overflow-x: auto 支持宽表格水平滚动
- 新增两个 smoke 测试覆盖表格渲染和客户端脚本嵌入

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
在 Hall 线程列表末尾添加 DEMO 卡片,点击后输入框预填原始任务,
点发送即触发客户端打字机效果回放 39 条 AI/ML 药物靶点预测会话。
同时默认浅色主题,支持局域网访问和禁用 token 认证。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- context pane 每 4 秒轮询刷新时用 minHeight + deferred scroll restore 保持滚动位置
- AI 消息中的文件路径(含行号)自动渲染为 hall-md-file-path 链接,点击通过 workspace files 端点打开
- 默认关闭 LOCAL_TOKEN_AUTH_REQUIRED,绑定地址改为 0.0.0.0
- 文本文件 content-type 添加 charset=utf-8

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 后处理 HTML 将 <code>filename.ext</code> 和 hall-workspaces 绝对路径转为可点击链接
- 扩展名按长度排序避免 alternation 误匹配(json 在 js 之前)
- 上下文面板滚动保持 minHeight + deferred restore 方案

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
调整线程菜单面板的 top 和 bottom 属性,确保其正确显示在按钮下方
控制中心将 OPENCLAW_HOME 视为完整状态目录路径(如 ~/.openclaw),
而 openclaw CLI 将其视为操作系统 home 的等价路径并自动追加 .openclaw,
导致子进程访问 ~/.openclaw/.openclaw/agents/ 这样的双重路径。

修复内容:
- buildOpenClawCommandEnv 在传递环境变量给子进程时剥离 OPENCLAW_HOME
  和 OPENCLAW_AGENT_ROOT,避免语义冲突
- 移除 loadSessionsFromStores、lookupSessionFile、findLatestAgentSessionFile
  和 loadRuntimeUsageData 中扫描双重候选路径的兼容代码
- 在 .env.example 中添加说明,警告不要将 OPENCLAW_HOME 设置为 ~/.openclaw

See: #2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
添加从sessions.json文件读取最新会话文件路径的逻辑,该文件记录了准确的路径(即使路径嵌套在.openclaw/.openclaw目录中)。如果读取失败,则回退到原有的glob扫描方式。
用执行锁 + 空闲时间判定取代 HallTaskStage 五状态机。agent 协作保持
不变——仍是基于 @mention 的自主路由。

## 后端

- 移除 HallTaskStage / TaskDiscussionCycle / HallTaskCard.stage /
  discussionCycle / HallMessagePayload.taskStage /
  HallTaskSummary.stage。
- HallTaskCard 新增 humanReviewedAt / lastAgentActivityAt;
  CollaborationHallSummary.blockedTaskCount → needsHumanReviewCount。
- 新增 src/runtime/hall-human-review.ts:needsHumanReview(card) 判定
  10 分钟无 agent 响应且未标记已处理。
- 删除 hall-speaker-policy.ts 整个文件(讨论周期轮换死码)。
- hall-execution-lock 改为基于 lock 本身(不再依赖 stage)。
- hall-runtime-dispatch 移除 looksLikeBlockedExecutionUpdate 正则和
  "blocked" nextAction 路径。
- collaboration-hall-orchestrator 删除约 1000 行 stage 驱动的讨论/
  评审/交接死码。dispatchHallAgentReply 中 appendPersistedHallMessage /
  appendStreamedGeneratedHallMessage 现在通过 touchHallTaskAgentActivity
  自动刷新 lastAgentActivityAt 并清空 humanReviewedAt。
- 恢复迎宾大厅回复路径:在 postHallMessage 里拦截问候语,让 lobby
  participant 给回复而不是静默创建任务。

## API

- 新增 POST /api/hall/tasks/:taskId/mark-human-reviewed 和对应的
  markHallTaskHumanReviewed 协调函数;新增 hall_task_mark_human_reviewed
  audit action。
- GET /api/hall/tasks 去掉 ?stage= 过滤。

## UI

- collaboration-board 相关文案:Blocked/卡住 → Needs human review/
  需要人类审核(chip、filter、状态文案、筛选标签)。
- collaboration-hall.ts 嵌入式客户端脚本:删除 textStage /
  textContinueDiscussion / textAdjustExecutionOrder /
  textPlanExecutionOrder / textContinueDiscussionSeed /
  textContinueDiscussionHint / textReviewingNow。stageLabel() 替换为
  activityLabel(taskCard)(从 status + executionLock + 无人类审核判定
  派生)。删除"阶段:..."行和"继续讨论"按钮与
  __openclawHallContinueDiscussion 处理器。
- 新增条件渲染的"标记为已处理"按钮 + __openclawHallMarkHumanReviewed
  处理器。
- SSR 侧用 resolveHallActivityLabel(taskCard, language) 替代顶层
  stageLabel(stage, language)。

## 测试

删除 43 个自 group-chat 重构(commit 1a0198c, 2026-04-08)以来就已经
失效的工作流引擎测试:
- collaboration-hall-orchestrator.test.ts: 31 个(讨论周期/manager
  close/review/blocked 阶段/计划队列自动交接)
- collaboration-hall-ui-smoke.test.ts: 3 个 + 1 个"三栏壳"断言调整
- hall-runtime-dispatch.test.ts: 9 个旧 prompt regex 断言

Hall 测试套件通过率:67/69。剩余两个失败是真实功能回归,留待单独
分诊:sessionKeys 未传播到 HallTaskCard、多 @mention 只派发第一个
目标。

scripts/live-hall-full-check.js 正则更新为新的"需要人类审核"术语。

npm run build 通过;npm run smoke:ui 通过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 客户端 renderInlineMarkdown 新增 autolink 与裸 URL 模式:<url> 渲染为
  可点击链接、裸 URL 自动转链并保留末尾标点
- 同时删除消息气泡下方的 hall-tool-strip(SSR + 客户端),工具药丸只在
  正文内联位置显示,避免与消息流重复
- 更新 hall UI 冒烟测试,断言 hall-tool-strip 已从脚本中移除

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
在 buildHallRuntimePrompt 的行为指令中新增两条硬规则:
运行分析/算法/计算前必须核实环境可行性(软件、数据、存储、内存、算力),
条件不满足时须 @ 上级或通知用户;禁止在未告知对接者的情况下用
mock / 合成 / 模拟 / 生成 / 手工伪造的数据冒充真实结果。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
添加.claude/scheduled_tasks.lock文件用于记录任务状态
在.gitignore中排除.playwright/目录和重复的.playwright-mcp/条目
四个独立改动,解决 Agent 互相 @ 导致的 ping-pong 和缺少人工审核闸门:

- A1 原指派人追踪:新增 HallTaskCard.originalAssignerParticipantId,首次人类
  派发时写入,prompt 引导 Agent 完工时 @原指派人而非对等 Agent
- A2 轮次计数 + 人工审核闸门:新增 autoRoundsByAgent,同一 (card, agent) 达 6
  次 dispatch 转 blocked + 系统消息,任何人类消息重置计数
- A3 auto-chain 过滤 trigger 作者:两个 auto-chain 点(回复路径 + 观察者路径)
  同时排除 triggerMessage.authorParticipantId,破 A→B→A 直接 ping-pong
- A4 OBSERVE_SILENT 推广:prompt 对所有 agent 给出「无实质内容回 OBSERVE_SILENT」
  指引,dispatchHallAgentReply 在 append 之前判 marker 抑制持久化

Issue #9 的架构层重构(文件化上下文、task_plan/findings/progress 共享文件、
消息防抖、per-card 串行、session 并发验证)刻意留给后续独立 PR。

复用既有设施:blocked 状态 + blockers[](commit 21f9403)、OBSERVE_SILENT_MARKER、
updateHallTaskCard 写序列化。

新增 test/hall-loop-prevention.test.ts 覆盖:A1 prompt 有/无、A4 prompt + 抑制、
A1+A2 store roundtrip + operator 重置,共 6 个测试。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
在消息气泡中显示模型标签,包括样式定义和逻辑实现。当消息包含模型信息时,会在作者标签旁显示模型名称,并添加悬停提示功能。同时为模型标签添加了专用的CSS样式类,确保视觉一致性。
使用 UI_TIMEZONE 配置统一时区显示
添加完整时间戳作为悬停提示
当日期相同时仅显示时间,否则显示短日期格式
```

```msg
feat: 添加根据 agentId 解析最新模型的功能

当会话缓存中找不到模型时,尝试通过 agentId 解析最新模型
新增 resolveLatestModelForAgent 私有方法处理此逻辑
让每个 hall task card 分配独立 sessionKey agent:<id>:hall:<taskCardId>,
彻底消除跨线程 OpenClaw transcript 互相覆盖的问题。

- 新增 src/clients/openclaw-gateway-ws.ts:最小 WebSocket JSON-RPC 客户端,
  含完整 ed25519 device-auth 握手(challenge → sign → connect),支持 agent
  RPC 流式 delta + sessions.reset。
- 客户端身份用 gateway-client + backend mode,走 shouldSkipLocalBackendSelfPairing
  bypass;scopes 用 CLI_DEFAULT_OPERATOR_SCOPES(点号风格)匹配已 paired 设备。
- agentRunStream 在 sessionKey 形如 agent:*:hall:* 时路由到 WS,否则保留 CLI
  路径;HALL_USE_GATEWAY_WS=false 可一键回退。
- hall-runtime-dispatch 的 dispatchChain 改按 sessionKey 串行,允许跨 card
  并发(由 OpenClaw workspace lock 实际排队)。
- 新增 8 用例测试:WS 握手/流式/reset/断连、sessionKey 路由与独立性。
- Playwright 端到端验证:new task "请只回复收到" → Main 回复 "收到"(kimi-code)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Franklin8760 and others added 18 commits April 23, 2026 16:00
问题:hall 派发切到 Gateway WebSocket 后,agent 回复里只保留了 pre-tool 的
thinking 块("用户要求…我应该用 xx 工具"),真正的最终合成文本未出现;同时
工具药丸在回复完成后从消息气泡里消失。

根因一:`visibleContent` 选择优先用了 `lastHistoryDraftText`(从 session
JSONL 渲染的渐进镜像)而非 `parsed.visibleText`(agent RPC 返回的权威合成)。
WS RPC 在 gateway 把最终 assistant 消息 flush 到 JSONL 之前就 resolve 了,
叠加 15s 的 session-history 缓存,导致最终 flush 只看到 pre-tool thinking。

根因二:`collectedToolCalls` 用 count-based baseline 过滤本轮工具调用,但
`findLatestAgentSessionFile(agentId)` 是按"agent 下最新 updatedAt"模糊解析
的,在并发 hall card 场景下可能最初锁到另一个 task card 的 jsonl 上;
baseline 在那个文件里 count=20,而真正的本轮文件只有 13 次 toolResult,
全部被过滤掉,最终 payload.toolCalls 为 null,UI 工具药丸消失。

修复:
- 交换 visibleContent 回退顺序:优先 parsed.visibleText。history 渲染只用于
  流式进度展示,最终内容用 RPC 返回的权威合成。
- 用 response.sessionId 重建 resolvedSessionFilePath(确定性,不依赖
  findLatestAgentSessionFile 的模糊 scan)。
- 用 runStartMs 时间戳过滤本轮的 toolResult 条目,替换脆弱的 count-based
  baseline。即使 session 文件跨轮共享,也能准确区分"本轮工具"。

测试:hall-runtime-dispatch、openclaw-gateway-ws、hall-session-isolation
共 39 用例全部通过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
添加工具调用标记的扩展功能,支持在点击时显示完整的输入参数和执行结果
- 在工具调用标记中增加输入输出数据的编码存储
- 添加可展开的工具提示组件,展示详细输入输出内容
- 支持错误状态显示和内容复制功能
- 优化工具调用状态显示样式
…ss 三件套(针对 #9 第 1-3 项)

把每个 task card 的群聊状态从「prompt 里塞 30 条 + 散落在 taskCard 字段」物化到磁盘,agent
通过 bash grep / jq 自助查阅,prompt inline transcript 从 30 条砍到 5 条(首轮 15 条)。

核心改动
- 新增 src/runtime/hall-blackboard.ts
  - initializeHallBlackboard(taskCard) 创建 .hall/{chat.jsonl, chat-index.md, locks/}
    + 三份 stub markdown(task_plan / findings / progress),从 task card 字段渲染
  - appendHallBlackboardMessage(taskCardId, message) 按 messageId 去重 + 重新生成索引
  - sanitizeMessageForBlackboard 把 [[tool:name|summary|~base64]] 里的 base64 段剥
    掉,保留 [[tool:name|summary]],让 chat.jsonl 真正可 grep
  - readHallProgressLatestEntry 读 progress.md 末块(供后续 latestSummary 回填)
  - renderHallBlackboardPromptGuidance 中英双语引导文案
  - per-card promise chain (runSerial) 串行化写

- src/runtime/collaboration-hall-orchestrator.ts
  - createHallTaskFromOperatorRequest(task 创建路径)+ postHallMessage(回帖路径)
    都接黑板:await initializeHallBlackboard + fire-and-forget appendHallBlackboardMessage
  - appendStreamedGeneratedHallMessage / appendPersistedHallMessage 写完 store 后 fire-and-forget 写黑板

- src/runtime/hall-runtime-dispatch.ts
  - HALL_INLINE_CONTEXT_DEFAULT=5, HALL_INLINE_CONTEXT_FIRST_TURN=15
    (buildHallRuntimePrompt 的 recentMessages cap 从 30 砍到动态值)
  - prompt 里加 blackboardGuidance 段,明示黑板路径 + 追加协议
  - dispatch 主路径 fire-and-forget 调 initializeHallBlackboard(避免 await
    扰动 microtask 时序)

- 新增 test/hall-blackboard.test.ts,7 个用例:init / 幂等 / append / 去重 /
  readLatestEntry / sanitizer / guidance 渲染

追加协议(约定,不强制)
- agent 写共享 markdown 时,用 <!-- agent: <id>, ts: <ISO> --> ... <!-- /agent --> 包裹自己的块
- 只追加,不覆盖别人的块
- progress.md 末块由 orchestrator 镜像回 taskCard.latestSummary(回填逻辑留 P3-A 跟进 PR)

行为/兼容
- JSON store 仍是权威源;黑板是物化视图,写失败不影响主路径
- 已有 task card 首次 dispatch 时自动补建黑板
- prompt 砍 context 后 agent 默认看 5 条 + 工作目录里的 chat.jsonl,token 显著下降

Phase 1+2 的反循环兜底(A1-A4)/ session 隔离(Gateway WS)保持不变,本次零回归

测试
- npm run build 干净
- node --import tsx scripts/run-tests-isolated.ts test/hall-*.test.ts
  test/collaboration-hall-*.test.ts test/hall-blackboard.test.ts
  → 103 测试 100 过 3 失败(3 个失败全部是 P3-A 之前就存在的旧 bug:
  execution-order persists / runtime-backed session linkage / multi-@mention
  routing;与本次改动无关)
- npm run smoke:ui 通过
- Playwright 真机 e2e:三 agent 接力(图灵 Turing → 林纳斯 Linus → 阿达 Ada)
  跑通,黑板正确生长,林纳斯主动按追加协议写了 progress.md 自己的块

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
把 Phase 3 启动以来本地积累的规划/研究/进度文档纳入仓库,作为 Phase 3-B/C
开工时的 working memory,避免每次重新调研。

- task_plan.md:Phase 0 / P3-A / P3-B / P3-C 阶段目标 + 工作项 + 退出标准
  + Lessons learned;P3-A 标 complete 引用 PR #12;P3-B/C 引用 issue #13
- findings.md:当前 hall 架构摸底 + 业界 8 种多智能体设计蒸馏(Anthropic
  Multi-Agent Research、Cognition "Don't Build Multi-Agents" 反方观点、
  MetaGPT 共享消息池、AutoGen GroupChat、LangGraph 监督/swarm、
  Hearsay-II 黑板、Claude Code subagents、Actor model / A2A / ACP)
- progress.md:追加 P3-A 提交 + P3-B/C 设计 issue 一节,含完整状态矩阵
  + issue #9 进度评论 URL

PR #12 已开,issue #13 已开,issue #9 进度评论已发,三处入口齐备。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
跟 owner review PR #14 (P3-B-1 mailbox) 时引出两个新议题:
1. issue #13 P3-C-2 的 dedupRecentDispatch 设计有缺陷(按时间窗 silence 误伤独立请求),已在 issue #13 评论修正为 dropResolvedTriggers
2. 当前每次 dispatch 都重发完整 ~10K prompt,LLM 端 OpenClaw session 累积重复 → 大量 token 浪费

决定先开 P3-A-2(design: issue #15)把上下文管理彻底交给 OpenClaw session + 黑板:
- 首轮发 setup(identity + 群聊意识 + 黑板路径 + 工作目录 + 花名册 + 行为指令 + trigger)
- 后续轮只发 [from: <author>] <trigger>,约 200-500 tokens vs 当前 10K
- agent 想看其他人没 @ 它的发言:bash grep .hall/chat.jsonl

P3-A-2 ship 之后回 feat/hall-mailbox-p3b1 rebase 继续 P3-B-2 / P3-C 系列。

分支拓扑:
  main
  └─ feat/hall-blackboard-p3a (PR #12)
     ├─ feat/hall-mailbox-p3b1 (PR #14, 暂停)
     └─ feat/hall-context-delegation-p3a2 (current)

P3-A-2 + P3-B-1 是兄弟分支,正交。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 项延伸优化 / issue #15)

把上下文管理彻底交给 OpenClaw session + 黑板:
- 首轮发完整 setup(identity / 群聊意识 / 黑板路径 / 工作目录 / 花名册 / 行为指令 / trigger)
- 后续轮只发 [from: <author>] <trigger>(约 200-500 tokens vs 当前 10K)
- 不再 inline 任何 recent thread messages —— agent 想看群聊用 bash grep 黑板

改动:
- src/runtime/hall-runtime-dispatch.ts:拆 buildHallRuntimePrompt → buildFirstTurnSetupPrompt + buildSubsequentTurnTriggerPrompt;
  删除 HALL_INLINE_CONTEXT_* 常量、dedupeHallPromptMessages 函数(dead code);
  trigger 渲染加作者归属 [from: <author>] / [来自 <作者>];
  A1 originalAssigner 提示降级为 trigger 前缀的 [note] 一行
- src/runtime/hall-blackboard.ts:renderHallBlackboardPromptGuidance 强化"群聊意识"——
  明确告诉 agent "其他人一直在说话,没 @ 你时你看不到",给具体 tail/grep 命令样例
- src/runtime/collaboration-hall-orchestrator.ts:
  - 观察者 trigger 缩成 [mode: observer] tail .hall/chat.jsonl ... 三行
  - 新增 linkRuntimeSessionKeyToTaskCard:dispatch 完成后把 runtime sessionKey
    写回 taskCard.sessionKeys(pre-P3-A-2 该路径就漏了,但 prompt 不区分故无害;
    P3-A-2 实际 branch on it 才暴露这个 latent bug);
    给 dispatchHallAgentReply / dispatchMainObserver / wakeMentionInitiator 三处都加上链接

新增测试 test/hall-prompt-context.test.ts,8 case:
- 首轮 prompt 含 setup 段(identity / 群聊意识 / roster)
- 首轮不 inline transcript(即使 recentThreadMessages 给了 3 条旧消息)
- 后续轮 prompt < 2KB、只有 [from: <author>] <trigger>
- 后续轮含 A1 originalAssigner one-liner(assigner != self 时)
- assigner == self 时不加 A1 hint
- triggerMessage 缺失(observer / wake)渲染干净
- 黑板 guidance 含强群聊意识
- token footprint:subsequent ≪ first(至少 8×)

验证:
- npm run build 干净
- 全 hall 套(除 typing)108/111 过,3 失败仍是 P3-A 之前的基线,零新回归
- npm run smoke:ui 通过
- Playwright 真机:3 轮 operator 消息,turn 3 prompt 116 chars vs turn 1 15,785 chars,
  ~135x 削减;agent 主动调 bash `tail .hall/chat.jsonl` 验证群聊意识引导生效

Refs: #9 (第 1 项延伸优化), #15 (P3-A-2 设计)
Depends on: #12 (P3-A 黑板基础设施)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
任务卡 collaboration-hall:p3-a-2-prompt-2-linus-idempotent-455a7d6c 三轮验证:
- turn 1: 15,785 chars first-turn setup(预期)
- turn 2: 15,763 chars first-turn setup(race window,未完全定位但不影响整体节省)
- turn 3: 116 chars subsequent trigger(subsequent path 工作正常 ✓)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(hall): Phase 3-A 黑板落地——共享 chat.jsonl + task_plan/findings/progress 三件套(针对 #9 第 1-3 项)
…-p3a2

feat(hall): Phase 3-A-2 prompt 简化——首轮 setup + 后续轮只发 trigger(针对 #9 第 1 项延伸 / issue #15
…(针对 #9 第 4 项 / issue #13)

把 hall dispatch 路径上每一次"我要派一条消息给某个 agent"的事件物化到磁盘的 inbox + delivery 审计日志,
不改 dispatch 行为——为 P3-B-2 的防抖合并 + P3-C 的 policy chain 打地基。

新增:
- src/runtime/hall-mailbox.ts:log-structured `inbox/{participantId}.jsonl`(enqueue / consume 行混存,读时 reduce 出 pending)
  + `deliveries.jsonl`(投递审计)+ 内存索引(per-(card, agent) lazy hydrate)
- src/runtime/hall-scheduler.ts:`enqueueAndDispatch(args, dispatch)` 薄包装——persist enqueue → 调 dispatch(现有
  per-sessionKey dispatchChains 提供单飞)→ persist consume + delivery;支持 outcome override(dispatched / skipped / failed)

改 src/runtime/collaboration-hall-orchestrator.ts:4 个 dispatch 入口包成 enqueueAndDispatch:
- operator 路由(routeAndDispatchHallMessage 主 fan-out)
- main observer 入口(main 不在 primary targets 时的事后 observe)
- observer 内部 auto-chain(observer 自己 @ 别人时)
- dispatchHallAgentReply 内部 auto-chain + wakeMentionInitiator(@ 完成回调)

测试:
- test/hall-mailbox.test.ts(7 case):enqueue / read pending / markConsumed / 重启恢复 / 排序 / 投递日志 / 文件命名空间
- test/hall-scheduler.test.ts(5 case):dispatch 成功 / 失败重抛 / outcome override / 串行写不交错 / 元数据持久化

设计修正:原设计想引入 per-(card, agent) worker queue,落地时发现 cyclic enqueue 死锁:A→B→C→A 链
(chainDepth ≤ 5 内合法),worker 在 await chain 子任务时被 chain 子任务的 enqueue 反向 block,构成依赖环。
结论:P3-B-1 不引入 queue/worker,等 P3-B-2 防抖合并时一起设计——届时 enqueue 不再 await 单条 dispatch
完成、而是 buffer 后批处理,自然不会有依赖环。

验证:
- npm run build 干净
- 单 file 跑 mailbox + scheduler:12/12 过
- 全 hall 套(除已知 hang 的 typing 文件)96+ 过,3 失败全部是 P3-A 之前就存在的基线,零回归
- npm run smoke:ui 通过

Refs: #9 (第 4 项), #13 (Mailbox + Policy 设计)
Depends on: #12 (P3-A 黑板基础设施)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- task_plan.md:加 Phase P3-B-1 章节(工作项 / 退出标准 / 风险 / 设计修正说明)
- progress.md:加 Session 2026-04-29 续 一节,记录 P3-B-1 落地 + 死锁规避决策

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
任务卡 collaboration-hall:p3-b-1-mailbox-turing-pm-linus-idempotent-.hall--85b5a134
跑了两条消息验证 4 个集成点全部正常:
- operator-route(多 @ 同时派发)
- main-observer(main 不在 primary targets 时事后 observe)
- auto-chain(agent reply 含 @ 别人 → chainDepth=1)
- wake-mention-initiator(auto-chain 完成回叫初始 agent)

deliveries.jsonl 累计 5 条记录,每条含 reason / depth / outcome / durationMs。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(hall): Phase 3-B-1 mailbox 透明层——dispatch 入口加 inbox + delivery 审计(针对 #9 第 4 项 / issue #13
…4 项 / issue #13)

把 P3-B-1 的同步 enqueueAndDispatch 重构为真正的异步 worker pump,加 750ms 防抖窗 +
合并并发 @s 进同一次 dispatch + 死锁规避。closure-per-call 模式保留以维持测试隔离。

新增 / 改动:
- src/runtime/hall-scheduler.ts 完全重写:per-(card, agent) WorkerState 队列 + debounce timer
  + isDispatching 锁;enqueueAndDispatch 接受 closure,closure 在 batch 完成时被调一次(任
  意 closure 等价,因为同 key 的 closures 来自同一 caller context);fire-and-forget 死锁
  规避(worker 不 await 调用方的 enqueue promise,re-entrant enqueue 只 append pending);
  HALL_INBOX_DEBOUNCE_MS 覆盖默认 750ms
- src/runtime/hall-mailbox.ts: HallInboxDeliveryRecord 加 batchId / batchSize 反映合并
- src/runtime/hall-runtime-dispatch.ts: HallRuntimeDispatchInput 加 triggerMessages?: HallMessage[];
  renderTriggerBlock 多 trigger 时加头「在短时间内你被多次 @ (N 条 trigger 合并)」+ 每个
  trigger 单独 attribution;单 trigger 渲染向后兼容
- src/runtime/collaboration-hall-orchestrator.ts: 5 处 enqueueAndDispatch closure 改成接收
  batch 参数,从 message store 拉合并的 triggerMessages 数组,调 dispatchHallAgentReply
  时透传 triggerMessages;新增 loadTriggerMessagesFromBatch 帮手;dispatchHallAgentReply
  签名加 triggerMessages?

设计修正(落地时回头):
- 最初尝试集中"注册全局 dispatcher"模式(orchestrator 模块加载时注册),落地发现破坏测试隔
  离——测试通过 options.toolClient 注入 fake client,原本由 closure 通过 lexical scope 捕
  获;集中 dispatcher 改用 createToolClient() 创建真实 OpenClawLiveClient,hall-loop-prevention
  / orchestrator 测试连真实 gateway 卡死。回退为 closure-per-call。

测试:
- test/hall-scheduler.test.ts 重写 7 case:合并 / 晚到入新 batch / 跨 key 并行 / 死锁规避 /
  failed outcome promise 仍 resolve / outcome override / 每 record enqueue+consume 两行
- test/hall-prompt-context.test.ts 加 2 case:多 trigger merge 渲染 + 单 trigger 向后兼容
- test/hall-mailbox.test.ts 不变(API 不变)

验证:
- npm run build 干净
- hall 套 ~134 过,3 失败仍是 P3-A 之前的基线(execution-order / session-linkage / multi-mention),零新回归
- npm run smoke:ui 通过
- Playwright e2e 略:unit test 已覆盖合并行为,orchestrator 路径形态与 P3-B-1 同构(已 e2e 过)

Refs: #9 (第 4 项), #13 (Mailbox + Policy 设计)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
task_plan.md:标 P3-A-2 已合,P3-B-2 完整章节(动机 / 架构 / 死锁规避 / prompt / 工作项 / 风险)
progress.md:P3-B-2 落地小结 + closure-per-call 回退原因 + 测试结果 + 跳过 Playwright 的理由

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(hall): Phase 3-B-2 inbox debounce + multi-trigger merge(针对 #9 第 4 项 / issue #13
… P3-C-2)

A1-A4 原本散落在 collaboration-hall-orchestrator.ts 的 inline 检查里:
A1+A2-reset 在 routeAndDispatchHallMessage 顶部、A2-increment+limit 在
dispatchHallAgentReply per-target gate、A3 在两处 chain candidate filter
(observer + auto-chain)、A4 在三处 silence check(observer + per-target +
wake-mention initiator)。要再加新 policy(dropResolvedTriggers /
detectClarifyingQuestion / enforceBackPingBudget)就得继续往这些位置塞 if 块。

P3-C-1 抽成两条 policy 链——行为完全不变,为 P3-C-2 打地基:

- src/runtime/hall-policies.ts(新)
  - 常量:OBSERVE_SILENT_MARKER / MAX_AUTO_CHAIN_DEPTH=5 / AUTO_ROUND_BLOCK_THRESHOLD=6
  - 类型:PreDispatchVerdict / PostDispatchVerdict(带 policyId 让 caller 区分侧效)
  - 4 个 policy 纯函数:enforceAutoRoundLimit(A2)/ enforceMaxAutoChainDepth /
    excludeTriggerAuthor(A3)/ observeSilentMarker(A4)
  - 链组合:HALL_PER_TARGET_GATE_POLICIES / HALL_CHAIN_FILTER_POLICIES /
    HALL_DEFAULT_POST_DISPATCH_POLICIES
  - 链 runner:runPreDispatchPolicies / runPostDispatchPolicies(顺序,第一个 deny/drop 短路)
  - 状态帮手:buildOperatorTurnStatePatch(A1 seed + A2 reset)/ incrementAutoRoundCounter

- src/runtime/collaboration-hall-orchestrator.ts
  - 删除 inline 常量定义,改从 hall-policies 导入
  - routeAndDispatchHallMessage A1+A2-reset:22 行 → 8 行调 buildOperatorTurnStatePatch
  - dispatchHallAgentReply per-target gate:先 incrementAutoRoundCounter,再
    runPreDispatchPolicies(HALL_PER_TARGET_GATE_POLICIES);deny 时按 policyId
    分发侧效(仅 A2 触发 handleAutoRoundBlockedThreshold,保留旧行为)
  - persist 失败时把本地 rounds 反映到 taskCard,policy 链照样读到 incremented
    counter(保留旧的 race-with-persistence 行为)
  - 三处 OBSERVE_SILENT silence check(observer / per-target / wake-mention)
    全部替换为 runPostDispatchPolicies
  - 两处 chain candidate filter(observer + auto-chain)用
    runPreDispatchPolicies(HALL_CHAIN_FILTER_POLICIES) 替换
  - 保留 if (chainDepth < MAX_AUTO_CHAIN_DEPTH) 外层早出(避免白白拉 thread messages)

- test/hall-policies.test.ts(新):38 个 case
  - 4 个 policy 纯函数的所有 verdict 分支
  - 链 runner 的空链 / 短路 / 顺序保留
  - 默认链组合行为 + 优先级(A2 在 PER_TARGET 链最先以保留 auto-round-blocked 通知)
  - buildOperatorTurnStatePatch 5 个分支 / incrementAutoRoundCounter 不可变性

验证:
- npm run build 干净
- hall-loop-prevention + collaboration-hall-orchestrator + hall-policies +
  hall-mailbox + hall-scheduler small batch:76 过 2 fail;2 fail 全是 P3-A 之前
  的基线(session-linkage、multi-mention routing),**A1-A4 直接相关测试全过**
- 全套测试:~250 过,5 fail(3 基线 + 1 typing 已知 hang + 1 ui-render-smoke pre-existing)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants