展开 PLAN_DISPATCH_v1.0.md 的 D4。基线:main(v0.9.1+,已合入四支柱首块)。 先核查的结论(已对当前树验证):D4 里"advisor 建议可点"那一半 0.9.1 已经做了;真正没做的是 多 agent 前端消费。本计划据此收窄。
src/advisor/types.ts:26SuggestedAction { label, kind: approve|cancel|open|serialize|look|dispatch, arg };6 个 detector(detectors.ts)都产出带 action 的Suggestion。src/web/island.ts:699–759渲染 advisor 卡 真按钮(onclick),advisorPrefill()(676–697)把动作 prefill 进 composer、绝不自动执行;✕ dismiss →POST /api/advisor/dismiss。- SSE
advisor_suggestions(server.ts:289)+GET /api/advisor/latest;两个假告警也已修。
→ "可点 + prefill + dismiss 学习闭环"这条 review 生死线已经合上,不用重做。
- 后端已发通用
agent_session_update(server.ts:230,覆盖所有 observer)+ 兼容用的claude_session_update(232–241)。 - 但前端只消费 claude:
island.ts:1115只有case 'claude_session_update';lisa-client.ts:428同样只听 claude。→ codex/opencode/aider/git/shell 的 session 状态变化,UI 完全看不到(尽管它们都在 hub 里、都发了事件)。 GET /api/agents/sessions(server.ts:678)已能列全部 agent(初始加载用),但前端没用它建多-agent 列表。
- advisor 的
cancel/dispatch/approve目前都是 prefill→用户发→Lisa 调工具,没有/api/signal_agent之类直接执行端点。这是有意的安全默认(nothing auto-runs);是否要加"一键直接 cancel"是产品取舍,不是 bug。
目标:让 island(和主 chat)把所有 agent(不只 Claude)的 session 状态显示出来 —— 兑现"看你整支 agent 舰队"。 非目标:不重做已完成的 advisor 可点;不默认加"自动执行"动作端点(保持 prefill 安全默认,直接端点列为可选 D4b)。
- 统一消费
agent_session_update:在island.ts的 SSE switch(~1063–1130)与lisa-client.ts(~428)各加case 'agent_session_update'。agent_session_update已覆盖 claude-code,所以以它为准,claude_session_update仅作旧客户端兼容(前端可不再依赖)。 - 一个纯 reducer(可测):把"收到一条 session 更新 → 更新本地 roster"抽成纯函数,绕开前端难单测的问题:
前端只做
// 放进一个可被 vm/单测加载的纯模块(或 island 内联但导出给测试) export interface RosterEntry { agent: string; sessionId: string; project: string; state: string; lastMtime: number; } export function mergeAgentSession(roster: RosterEntry[], u: RosterEntry, now: number, windowMs = 30*60_000): RosterEntry[] // - upsert by (agent+sessionId);按 lastMtime 排序;丢弃超出活动窗口的;去掉 done/idle 过老的
roster = mergeAgentSession(roster, ev, Date.now())+ 渲染。 - 渲染:island 现有 Claude 监控区扩成"多 agent roster":每行
agent 图标 + project + 状态点 + 最近活动(activity.lastTools/lastCommandName)。agent 种类用小徽标(claude/codex/opencode/aider/git/shell)。初始加载走GET /api/agents/sessions。 - 复用现有状态点/CSS;不新增后端(后端已发齐)。
- 仅在用户明确想要"一键直接执行"时做。
POST /api/agent/signal { id, action: "cancel" }→ 复用signal_agent的 ledger-gated kill(只杀 LISA 自己派的)。 - 必须走 0.9.1 的 web 鉴权(loopback 或
LISA_WEB_TOKEN),并带确认。默认仍 prefill;direct 作为 opt-in。 - approve/dispatch 的直接执行风险更高(代码执行),不做,保持 prefill。
D4a(核心)
- island + lisa-client 各加
agent_session_update消费,经mergeAgentSession维护 roster。 - island 显示非-Claude(codex/git/shell…)session 的状态 + 活动(手动起一个 git observer 会话即可见)。
-
mergeAgentSession纯函数有单测(upsert/排序/窗口剔除)。 - 内联 JS 仍通过现有
vm.Script语法校验测试(island/lisa-html snapshot)。
D4b(可选,需用户拍板)
-
/api/agent/signal仅 loopback/token 可达;只 cancel ledger 内的 pid;有测试。 - island cancel 按钮可选"直接执行"(默认仍 prefill)。
- 抽纯 reducer(
mergeAgentSession)承载所有逻辑 → 单测覆盖;前端只是"调 reducer + 写 DOM"。 - 内联
<script>继续靠现有lisa-html/island 的vm.Script解析测试防语法炸。 - D4b 端点:后端单测(鉴权 + 只 cancel 自己的 pid)。
- 多 agent roster 只显示 hub 已有的结构化 activity(无 prompt/reply/内容)——隐私层不变。
- D4b 直接端点严守 0.9.1 web 鉴权 + ledger 限制(signal_agent 既有保证)。
- 前端可测性低 → 用纯 reducer 缓解。
- roster 太吵(很多 session)→ 活动窗口 + 上限 + 折叠;沿用 island 既有的 cap。
- direct-action(D4b)是攻击面扩张 → 默认不开,prefill 优先。
D4 的"可点"半边 0.9.1 已合;剩下的是让前端真正消费
agent_session_update,把"看你所有 agent"从后端事实变成 UI 事实 —— 用一个纯 reducer 把前端逻辑做成可测的。