feat(cli,ohos): 补齐两处能力缺口——CLI /memory context|tools、鸿蒙 camerasue 录像回传

核对「CLI 与 WebUI 插件能力对齐」时发现两个此前遗漏的缺口,一并补齐。

1) CLI /memory 缺 context 与 tools(internal/plugins/cli/plugin.go)
   WebUI 有 GET /api/v1/memory/context 与 /api/v1/memory/tools,CLI 只有
   query/graph/text。IndexerAPI 本就对内部插件开放(BuildContext/
   FormatContext/GetToolDefinitions/BuildToolPrompt),不是 SDK 缺口。
   补上后与 WebUI 同源:context 打印「实际会注入什么上下文」,
   tools 打印工具定义 + 工具提示词。
   waiter 同步接上两条远端路由与 help 文案。

2) 鸿蒙 camerasue 录像(BridgeCaps/BridgeRouter/DeviceBridge.ets)
   此前 `camerasue <N秒>` 直接返回「暂不支持录像回传」。查 SDK 后发现
   cameraPicker 本身就有 PickerMediaType.VIDEO 与 PickerProfile.videoDuration
   —— 录像完全可行,只是回传通道没接。
   现改为:VIDEO 模式取回 mp4,经 DeviceBridge.sendDataChunked 按
   cmd_data_start/分块/cmd_data_end 回传(与 GUI/CLI 录像路径一致),
   网关聚合后落盘成文件、agent 拿路径;照片仍走小体积 base64 内联。
   上限 64MB、时长 1~300s,超限明确报错而不是把 WS/上下文撑爆。

   依赖方向处理:BridgeCaps 需要「往本请求回传字节」,但 DeviceBridge 为取
   CapResult 已 import BridgeCaps,反向 import 会成环。改为 BridgeRouter
   注入 DataChunkSender 回调(它同时持有 deviceBridge 与 reqId),
   BridgeCaps 不碰 socket。新增 CapResult.chunked 标记「结果已由能力分块
   发完」,DeviceBridge 据此不再回 cmd_result,避免网关把已完成请求与后续
   分块错配。

验证:hvigor assembleHap BUILD SUCCESSFUL(ArkTS 编译通过,改动文件零告警);
make check-client-versions 一致;go vet 干净;全量 go test ./internal/... ./cmd/... 零失败。
This commit is contained in:
JianFeeeee
2026-09-18 10:04:53 +08:00
parent 9de3b365a6
commit 1f5af1dccf
5 changed files with 145 additions and 12 deletions

View File

@ -356,6 +356,8 @@ func (p *Plugin) cmdHelp(conn net.Conn) {
/memory query <关键词> 查询图记忆
/memory graph 导出整张图记忆快照
/memory text [n] 最近 n 条文本记忆事件 + 统计
/memory context [q] 组装后的记忆上下文(会注入什么)
/memory tools 记忆工具定义 + 工具提示词
/knowledge 列出知识库
/knowledge delete <name> 删除一条知识
/knowledge stats 知识库统计
@ -747,8 +749,42 @@ func (p *Plugin) cmdMemory(conn net.Conn, parts []string, s *sdk.PluginSDK) {
"events": events,
"stats": tm.Stats(),
})
case "context":
// 与 WebUI GET /api/v1/memory/context 同源:都走 IndexerAPI。
// 打印“输进去会拼成什么上下文”,是排查召回质量最直接的一眼。
idx := s.Indexer()
if idx == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "indexer not available"})
return
}
q := ""
if len(parts) >= 3 {
q = strings.Join(parts[2:], " ")
}
injected, err := idx.BuildContext(q)
if err != nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
return
}
writeJSONContent(conn, map[string]interface{}{
"context": idx.FormatContext(injected),
"summary": injected.Summary,
"entities": injected.Entities,
"token_estimate": injected.TokenEstimate,
})
case "tools":
// 与 WebUI GET /api/v1/memory/tools 同源。
idx := s.Indexer()
if idx == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "indexer not available"})
return
}
writeJSONContent(conn, map[string]interface{}{
"tools": idx.GetToolDefinitions(),
"tool_prompt": idx.BuildToolPrompt(),
})
default:
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /memory query <关键词> | /memory graph | /memory text [n]"})
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /memory query <关键词> | /memory graph | /memory text [n] | /memory context [q] | /memory tools"})
}
}