mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
refactor(terminal): 内核开终端/命令历史权威视图,WebUI 与 CLI 都改接内核
按「内核开,两个插件接」重构终端与命令历史的数据归属。
背景:此前 WebUI 与 CLI 各订 EventToolCall/EventTerminalOutput 攅一份状态,
同一件事两份推导,还各自踩过同一个坑——工具 result 是 Go 的 map 文本
(map[cols:80 ... id:term_2 ...]),断言成 map[string]interface{} 永远失败,
terminal_create 的 id 回填不生效,/terminals 因此恒空(WebUI 也一样)。
实测确认:WebUI 自己的 /api/v1/terminals 与 /api/v1/cmd/history 同样是空的。
内核开(权威唯一真相):
- internal/agent/core/terminal_registry.go:TerminalRegistry 归并两类事件——
EventToolCall(terminal_create/close、cmd_run,id/command 从 args 或 Go map
文本回填)与 EventTerminalOutput(agentcli 生命周期 + 输出,含 64KB 缓冲上限、
100 条命令历史、50 个终端上限)。
- internal/sdk/terminal.go:新增 TerminalAPI(ListTerminals/CmdHistory)与
TerminalStatus/CmdExecStatus DTO。**不塞进 KernelStatus**:那是全量快照,
前端每 3 秒轮询 /kernel,背上每终端最多 64KB 输出会让轮询成本爆炸;
终端输出是按需拉取的明细,另开接口。
- Agent 订阅自己的事件总线(subscribeTerminalRegistry),且**只根 agent 建**
(驻留子共用同一总线,每个子都建会 N+1 份重复记账)。
- SDKConfig/Registry/bootstrap 接线:pluginReg.SetTerminalAPI(agent)。
生产者补全(agentcli):终端无输出时 ticker 不发事件,内核就无从知道终端
存在。新增 emitTermState,在 handleCreate/handleClose/readLoop 退出(超时/
进程结束/读取错误/stopCh)显式上报 running 状态,并给输出事件补 command 字段。
handleClose 改为接收 *sdk.PluginSDK 以便上报。
两个插件接(消费方):
- WebUI:删掉本地 termStates/cmdHistory/subscribeTerminalStream/handleToolEvent
及不再使用的 getStr;/terminals 与 /cmd/history 直接读 s.Terminal()。
- CLI:删掉上一轮刚加的 subscribeToolEvents 与 cliTermState/cliCmdExec;
/terminals 与 /cmd/history 直接读 s.Terminal()。两条路(local/remote)都通。
测试:新增 terminal_registry_test.go,锁死 Go map 文本解析(旧缺陷根因)、
生命周期、CLI 直调路径(无 EventToolCall 仅凭 output 事件建条目)、历史与
终端数量上限。全量 go test ./internal/... ./cmd/... 通过。
This commit is contained in:
@ -176,6 +176,10 @@ type Agent struct {
|
||||
noMergeMarkers map[string]int
|
||||
noMergeMu sync.Mutex
|
||||
|
||||
// TerminalRegistry 是终端会话与命令历史的权威视图(“内核开,两个插件接”)。
|
||||
// 内核订阅自己的事件总线归并而来;WebUI/CLI 经 KernelStatus 读取。
|
||||
terminalReg *TerminalRegistry
|
||||
|
||||
// 输入去重:防 webui/GUI 断线重连导致的消息重放
|
||||
// key=source+"|"+content, value=上次接收时间;短窗口内同内容丢弃
|
||||
lastInput map[string]time.Time
|
||||
@ -377,6 +381,13 @@ func New(cfg AgentConfig) *Agent {
|
||||
lastInput: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
// 终端权威注册表只归**根 agent**(无 ParentID)。驻留子共用同一事件总线,
|
||||
// 若每个子都建一份并订阅,一次工具调用会被 N+1 份重复记账;而终端本就是
|
||||
// 内核级设备,不属于任何单个驻留子。
|
||||
if cfg.ParentID == "" {
|
||||
a.terminalReg = NewTerminalRegistry()
|
||||
}
|
||||
|
||||
// 输入路由:inputch 是可分配资源,划给某个 agent 后输入**只**流向那个 agent
|
||||
// (设计 §4.1「路由发生在进内核之前」)。io 层不认识 agent,所以在这里把路由器
|
||||
// 注入进去:插件注入输入时先问它,被别的 agent 接管就不再进本内核队列。
|
||||
@ -396,11 +407,28 @@ func (a *Agent) Start() {
|
||||
go a.archiveLoop()
|
||||
go a.mergeLoop()
|
||||
go a.reviewLoop()
|
||||
a.subscribeTerminalRegistry()
|
||||
a.reembedStaleMedia()
|
||||
a.migrateLegacyGraphMedia()
|
||||
log.Printf("[agent] %s started, waiting for IO interrupts", a.id)
|
||||
}
|
||||
|
||||
// subscribeTerminalRegistry 让内核的终端/命令历史权威视图归并事件流。
|
||||
//
|
||||
// 内核自己发 EventToolCall(agent 路径),agentcli 发 EventTerminalOutput
|
||||
// (含生命周期事件)。两者都进这份唯一真相,WebUI/CLI 不再各自推导。
|
||||
func (a *Agent) subscribeTerminalRegistry() {
|
||||
if a.eventBus == nil || a.terminalReg == nil {
|
||||
return
|
||||
}
|
||||
a.eventBus.Subscribe(events.EventToolCall, func(ev *events.Event) {
|
||||
a.terminalReg.OnToolCall(ev.Payload)
|
||||
})
|
||||
a.eventBus.Subscribe(events.EventTerminalOutput, func(ev *events.Event) {
|
||||
a.terminalReg.OnTerminalOutput(ev.Payload)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Agent) Stop() {
|
||||
// 父退出**必须**销毁全部驻留子(设计 §10 硬约束:子不得比父活得久、不留孤儿)。
|
||||
a.StopResidents()
|
||||
|
||||
Reference in New Issue
Block a user