acp.go 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504
  1. // Package acp 实现 ACP (Agent Client Protocol) 客户端
  2. // 基于 ACP v1 规范,通过 stdin/stdout ND-JSON 与 Agent 子进程通讯
  3. package acp
  4. import (
  5. "bufio"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "os"
  10. "os/exec"
  11. "os/user"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "sync/atomic"
  17. "syscall"
  18. "time"
  19. "fwh-toolbox-server/utils/filestore"
  20. "go.uber.org/zap"
  21. )
  22. // ============ JSON-RPC 2.0 基础类型 ============
  23. // Request JSON-RPC 请求
  24. type Request struct {
  25. JSONRPC string `json:"jsonrpc"`
  26. ID int64 `json:"id"`
  27. Method string `json:"method"`
  28. Params any `json:"params,omitempty"`
  29. }
  30. // Response JSON-RPC 响应
  31. type Response struct {
  32. JSONRPC string `json:"jsonrpc"`
  33. ID *int64 `json:"id,omitempty"`
  34. Method string `json:"method,omitempty"`
  35. Result json.RawMessage `json:"result,omitempty"`
  36. Error *RPCError `json:"error,omitempty"`
  37. Params json.RawMessage `json:"params,omitempty"`
  38. }
  39. // RPCError JSON-RPC 错误
  40. type RPCError struct {
  41. Code int `json:"code"`
  42. Message string `json:"message"`
  43. Data string `json:"data,omitempty"`
  44. }
  45. // FullMessage 返回包含 data 的完整错误信息
  46. func (e *RPCError) FullMessage() string {
  47. if e.Data != "" {
  48. return fmt.Sprintf("%s: %s", e.Message, e.Data)
  49. }
  50. return e.Message
  51. }
  52. // ============ ACP 协议类型 ============
  53. // InitializeParams 初始化请求参数
  54. type InitializeParams struct {
  55. ProtocolVersion int `json:"protocolVersion"`
  56. ClientInfo ClientInfo `json:"clientInfo"`
  57. ClientCapabilities ClientCapabilities `json:"clientCapabilities"`
  58. }
  59. // ClientInfo 客户端信息
  60. type ClientInfo struct {
  61. Name string `json:"name"`
  62. Version string `json:"version"`
  63. }
  64. // ClientCapabilities 客户端能力声明
  65. type ClientCapabilities struct {
  66. FS *FSCapabilities `json:"fs,omitempty"`
  67. Terminal bool `json:"terminal,omitempty"`
  68. }
  69. // FSCapabilities 文件系统能力
  70. type FSCapabilities struct {
  71. ReadTextFile bool `json:"readTextFile"`
  72. WriteTextFile bool `json:"writeTextFile"`
  73. }
  74. // NewSessionParams 创建会话参数
  75. type NewSessionParams struct {
  76. CWD string `json:"cwd"`
  77. MCPServers []any `json:"mcpServers"`
  78. }
  79. // NewSessionResult 创建会话响应
  80. type NewSessionResult struct {
  81. SessionId string `json:"sessionId"`
  82. Modes *SessionModes `json:"modes,omitempty"`
  83. Models *SessionModels `json:"models,omitempty"`
  84. }
  85. // SessionModes 会话模式
  86. type SessionModes struct {
  87. CurrentModeId string `json:"currentModeId"`
  88. AvailableModes []SessionMode `json:"availableModes"`
  89. }
  90. // SessionMode 单个模式
  91. type SessionMode struct {
  92. Id string `json:"id"`
  93. Name string `json:"name"`
  94. Description string `json:"description"`
  95. Meta *SessionMeta `json:"_meta,omitempty"`
  96. }
  97. // SessionMeta 模式元数据
  98. type SessionMeta struct {
  99. WelcomeMessage string `json:"welcomeMessage"`
  100. }
  101. // SessionModels 会话可用模型
  102. type SessionModels struct {
  103. CurrentModelId string `json:"currentModelId"`
  104. AvailableModels []SessionModel `json:"availableModels"`
  105. }
  106. // SessionModel 单个模型
  107. type SessionModel struct {
  108. ModelId string `json:"modelId"`
  109. Name string `json:"name"`
  110. Description string `json:"description"`
  111. }
  112. // PromptParams 发送消息参数
  113. type PromptParams struct {
  114. SessionId string `json:"sessionId"`
  115. Prompt []ContentBlock `json:"prompt"`
  116. }
  117. // ContentBlock 内容块
  118. type ContentBlock struct {
  119. Type string `json:"type"`
  120. Text string `json:"text"`
  121. }
  122. // PromptResult prompt 响应
  123. type PromptResult struct {
  124. StopReason string `json:"stopReason"`
  125. }
  126. // CancelParams 取消参数
  127. type CancelParams struct {
  128. SessionId string `json:"sessionId"`
  129. }
  130. // ============ Session Update 事件类型 ============
  131. // SessionUpdate 会话更新通知
  132. type SessionUpdate struct {
  133. SessionId string `json:"sessionId"`
  134. Update json.RawMessage `json:"update"`
  135. }
  136. // UpdateBase 更新基础类型
  137. type UpdateBase struct {
  138. SessionUpdate string `json:"sessionUpdate"`
  139. }
  140. // AgentMessageChunk Agent 文本流式输出
  141. type AgentMessageChunk struct {
  142. SessionUpdate string `json:"sessionUpdate"` // "agent_message_chunk"
  143. MessageId string `json:"messageId,omitempty"`
  144. Content ContentBlock `json:"content"`
  145. }
  146. // ToolCall 工具调用通知
  147. type ToolCall struct {
  148. SessionUpdate string `json:"sessionUpdate"` // "tool_call"
  149. ToolCallId string `json:"toolCallId"`
  150. Title string `json:"title"`
  151. Status string `json:"status"` // pending, running, completed, failed
  152. }
  153. // ToolCallUpdate 工具调用状态更新
  154. type ToolCallUpdate struct {
  155. SessionUpdate string `json:"sessionUpdate"` // "tool_call_update"
  156. ToolCallId string `json:"toolCallId"`
  157. Title string `json:"title,omitempty"`
  158. Status string `json:"status"`
  159. }
  160. // ============ 客户端事件(对外输出) ============
  161. // Event 输出给上层的事件
  162. type Event struct {
  163. Type string // "text", "tool_call", "tool_update", "done", "error", "permission"
  164. Content string
  165. ToolCallId string // 工具调用唯一ID(用于前端按 id 关联状态更新)
  166. ToolName string
  167. ToolStatus string
  168. SessionId string
  169. StopReason string
  170. // 权限请求相关
  171. Permission *PermissionEvent `json:"permission,omitempty"`
  172. }
  173. // PermissionEvent 权限请求事件(推给前端)
  174. type PermissionEvent struct {
  175. RequestId string `json:"requestId"` // 用于前端回传
  176. Title string `json:"title"` // 权限标题
  177. Options []PermissionOption `json:"options"` // 可选选项
  178. }
  179. // PermissionOption 权限选项
  180. type PermissionOption struct {
  181. OptionId string `json:"optionId"`
  182. Name string `json:"name"`
  183. Kind string `json:"kind"` // allow_once, allow_always, deny
  184. }
  185. // ============ ACP Client 实现 ============
  186. // PermissionWaitTimeout 前端授权回传的最长等待时长。
  187. // 超时后默认按 deny 处理,避免读循环因前端断开而永久阻塞。
  188. const PermissionWaitTimeout = 5 * time.Minute
  189. // pendingResponse 等待中的请求:读循环收到匹配 id 的响应后通过 ch 投递
  190. type pendingResponse struct {
  191. ch chan Response
  192. }
  193. // notifySink 通知回调:读循环把 notification 派发给当前阶段的接收者。
  194. // 返回值无意义,仅用于让不同阶段(Prompt / LoadSession / 默认)自定义处理。
  195. type notifySink func(method string, params json.RawMessage)
  196. // Client ACP 客户端(管理单个 Agent 进程)
  197. //
  198. // 并发模型:构造时启动唯一的 readLoop goroutine,负责读取 stdout 的每一行并派发:
  199. // - 响应(有 id、无 method)→ 按 id 投递到 pending map 中等待的 channel;
  200. // - Agent→Client 反向请求(有 id、有 method)→ 交给 handleAgentRequest(在独立
  201. // goroutine 中处理,避免权限等待阻塞读循环);
  202. // - 通知(无 id、有 method)→ 交给当前 notifySink。
  203. //
  204. // 所有对 stdin 的写入通过 writeMu 串行化;Cancel 与权限响应因此不会互相死锁。
  205. type Client struct {
  206. cmd *exec.Cmd
  207. stdin io.WriteCloser
  208. scanner *bufio.Scanner
  209. sessionId string
  210. requestId int64
  211. writeMu sync.Mutex // 保护 stdin 写入
  212. // 请求分发:id -> 等待响应的 channel
  213. pendingMu sync.Mutex
  214. pending map[int64]*pendingResponse
  215. // 通知分发:读循环把 notification 交给当前 sink
  216. sinkMu sync.Mutex
  217. sink notifySink
  218. eventCh chan<- Event // 当前 prompt 的事件通道(用于推送 permission 事件),受 sinkMu 保护
  219. abortCh chan struct{} // 当前 prompt 的中止信号(上层停止消费时用于解除发送阻塞),受 sinkMu 保护
  220. emitWg sync.WaitGroup // 统计在途 emit,endTurn 借此在 close(eventCh) 前等待发送退出
  221. // 权限请求等待机制
  222. permissionCh chan string // 接收前端选择的 optionId
  223. // 进程存活标记:由 readLoop / waitProcess 单点置位,IsAlive 读取
  224. dead atomic.Bool
  225. doneCh chan struct{} // 进程退出/读循环结束时关闭
  226. // 文件存储:会话身份(用于定位 MinIO 中的文件)
  227. UserId string
  228. // 文本累积(仅用于在 turn 结束时打一条完整回复日志)。
  229. // 由 sink(readLoop goroutine)写入、Prompt goroutine 读取,故用 textMu 保护。
  230. textMu sync.Mutex
  231. textBuffer string
  232. // 最近一次上下文用量与耗时(供首屏展示,读取需加 metaMu)
  233. metaMu sync.Mutex
  234. lastContextUsage float64
  235. lastTurnDurationMs int64
  236. }
  237. // ClientConfig 客户端配置
  238. type ClientConfig struct {
  239. Command string // Agent 命令(如 "kiro-cli")
  240. Args []string // 命令参数(如 ["acp"])
  241. WorkingDir string // 工作目录
  242. Env []string // 额外环境变量(如 "HOME=/home/user")
  243. RunAsUser string // 降权运行的目标系统用户名,为空则不降权
  244. }
  245. // ApplyRunAsUser 让 cmd 以指定系统用户身份运行(降权)。
  246. // runAsUser 为空时不做任何处理(沿用当前进程用户)。
  247. // 该函数会:
  248. // 1. 解析目标用户的 uid/gid,并设置 cmd.SysProcAttr.Credential;
  249. // 2. 附带解析该用户的补充组(supplementary groups);
  250. // 3. 修正 HOME/USER/LOGNAME 环境变量为目标用户的值,避免 kiro-cli
  251. // 仍读取 root 的家目录导致配置/凭证路径错误。
  252. //
  253. // 注意:设置 Credential 需要当前进程具备相应权限(通常为 root)。
  254. func ApplyRunAsUser(cmd *exec.Cmd, runAsUser string) error {
  255. runAsUser = strings.TrimSpace(runAsUser)
  256. if runAsUser == "" {
  257. return nil
  258. }
  259. u, err := user.Lookup(runAsUser)
  260. if err != nil {
  261. return fmt.Errorf("查找降权用户 %q 失败: %w", runAsUser, err)
  262. }
  263. uid, err := strconv.ParseUint(u.Uid, 10, 32)
  264. if err != nil {
  265. return fmt.Errorf("解析用户 %q 的 uid(%s) 失败: %w", runAsUser, u.Uid, err)
  266. }
  267. gid, err := strconv.ParseUint(u.Gid, 10, 32)
  268. if err != nil {
  269. return fmt.Errorf("解析用户 %q 的 gid(%s) 失败: %w", runAsUser, u.Gid, err)
  270. }
  271. // 解析补充组(失败不致命,仅记录告警)
  272. var groups []uint32
  273. if gidStrs, gErr := u.GroupIds(); gErr == nil {
  274. for _, gs := range gidStrs {
  275. if g, pErr := strconv.ParseUint(gs, 10, 32); pErr == nil {
  276. groups = append(groups, uint32(g))
  277. }
  278. }
  279. } else {
  280. zap.L().Warn("[ACP] 读取降权用户补充组失败", zap.String("user", runAsUser), zap.Error(gErr))
  281. }
  282. if cmd.SysProcAttr == nil {
  283. cmd.SysProcAttr = &syscall.SysProcAttr{}
  284. }
  285. cmd.SysProcAttr.Credential = &syscall.Credential{
  286. Uid: uint32(uid),
  287. Gid: uint32(gid),
  288. Groups: groups,
  289. }
  290. // 修正环境变量,确保子进程使用目标用户的身份。
  291. // USER/LOGNAME 强制设为目标用户;HOME 尊重上层(配置)已传入的值,
  292. // 仅在未提供时才回退到用户家目录。
  293. if cmd.Env == nil {
  294. cmd.Env = os.Environ()
  295. }
  296. cmd.Env = overrideEnv(cmd.Env, map[string]string{
  297. "USER": u.Username,
  298. "LOGNAME": u.Username,
  299. })
  300. cmd.Env = fillEnvIfAbsent(cmd.Env, "HOME", u.HomeDir)
  301. effectiveHome := u.HomeDir
  302. for _, e := range cmd.Env {
  303. if strings.HasPrefix(e, "HOME=") {
  304. effectiveHome = strings.TrimPrefix(e, "HOME=")
  305. break
  306. }
  307. }
  308. zap.L().Info("[ACP] 子进程降权运行",
  309. zap.String("user", u.Username),
  310. zap.String("uid", u.Uid),
  311. zap.String("gid", u.Gid),
  312. zap.String("home", effectiveHome),
  313. )
  314. return nil
  315. }
  316. // fillEnvIfAbsent 仅当 env 中不存在 key 时才追加 key=value,否则原样返回。
  317. func fillEnvIfAbsent(env []string, key, value string) []string {
  318. prefix := key + "="
  319. for _, e := range env {
  320. if strings.HasPrefix(e, prefix) {
  321. return env
  322. }
  323. }
  324. return append(env, prefix+value)
  325. }
  326. // overrideEnv 用 kv 覆盖 env 中已存在的同名变量,不存在则追加。
  327. func overrideEnv(env []string, kv map[string]string) []string {
  328. out := make([]string, 0, len(env)+len(kv))
  329. seen := make(map[string]bool, len(kv))
  330. for _, e := range env {
  331. key := e
  332. if idx := strings.IndexByte(e, '='); idx >= 0 {
  333. key = e[:idx]
  334. }
  335. if v, ok := kv[key]; ok {
  336. out = append(out, key+"="+v)
  337. seen[key] = true
  338. } else {
  339. out = append(out, e)
  340. }
  341. }
  342. for k, v := range kv {
  343. if !seen[k] {
  344. out = append(out, k+"="+v)
  345. }
  346. }
  347. return out
  348. }
  349. // NewClient 创建并启动 ACP Client
  350. func NewClient(cfg ClientConfig) (*Client, error) {
  351. cmd := exec.Command(cfg.Command, cfg.Args...)
  352. // 确保工作目录是绝对路径
  353. absDir, err := filepath.Abs(cfg.WorkingDir)
  354. if err != nil {
  355. absDir = cfg.WorkingDir
  356. }
  357. cmd.Dir = absDir
  358. // 设置环境变量
  359. cmd.Env = append(os.Environ(), cfg.Env...)
  360. // 若配置了降权用户,则以该用户身份拉起子进程
  361. if err := ApplyRunAsUser(cmd, cfg.RunAsUser); err != nil {
  362. return nil, err
  363. }
  364. stdin, err := cmd.StdinPipe()
  365. if err != nil {
  366. return nil, fmt.Errorf("创建 stdin pipe 失败: %w", err)
  367. }
  368. stdout, err := cmd.StdoutPipe()
  369. if err != nil {
  370. return nil, fmt.Errorf("创建 stdout pipe 失败: %w", err)
  371. }
  372. // 捕获 stderr
  373. stderrPipe, _ := cmd.StderrPipe()
  374. go func() {
  375. if stderrPipe != nil {
  376. s := bufio.NewScanner(stderrPipe)
  377. for s.Scan() {
  378. zap.L().Warn("[ACP] agent stderr", zap.String("line", s.Text()))
  379. }
  380. }
  381. }()
  382. if err := cmd.Start(); err != nil {
  383. return nil, fmt.Errorf("启动 Agent 进程失败: %w", err)
  384. }
  385. scanner := bufio.NewScanner(stdout)
  386. scanner.Buffer(make([]byte, 0, 2*1024*1024), 2*1024*1024) // 2MB buffer
  387. c := &Client{
  388. cmd: cmd,
  389. stdin: stdin,
  390. scanner: scanner,
  391. pending: make(map[int64]*pendingResponse),
  392. permissionCh: make(chan string, 1),
  393. doneCh: make(chan struct{}),
  394. }
  395. // 监听子进程退出,单点置位 dead 标记(供 IsAlive 可靠判活)
  396. go c.waitProcess()
  397. // 启动唯一的读循环,统一分发响应/请求/通知
  398. go c.readLoop()
  399. return c, nil
  400. }
  401. // waitProcess 阻塞等待子进程退出,退出后标记 dead。
  402. // 这是回收子进程、置位存活标记的唯一入口,因此 IsAlive 不再依赖
  403. // cmd.ProcessState(只有调用 Wait 后才会被填充)的偶然状态。
  404. func (c *Client) waitProcess() {
  405. _ = c.cmd.Wait()
  406. c.markDead()
  407. }
  408. // markDead 标记进程已退出,并关闭 doneCh(幂等)。
  409. func (c *Client) markDead() {
  410. if c.dead.CompareAndSwap(false, true) {
  411. close(c.doneCh)
  412. }
  413. }
  414. // ============ 读循环与分发 ============
  415. // readLoop 是唯一读取 stdout 的 goroutine。它把每行消息按类型派发:
  416. // - 响应(有 id、无 method)→ 投递到 pending map 中等待的 channel;
  417. // - 反向请求(有 id、有 method)→ 在独立 goroutine 中处理(权限等待不阻塞读循环);
  418. // - 通知(无 id、有 method)→ 交给当前 notifySink。
  419. //
  420. // scanner 结束(EOF / 进程退出)时标记 dead,并唤醒所有等待中的请求。
  421. func (c *Client) readLoop() {
  422. defer func() {
  423. c.markDead()
  424. c.failAllPending()
  425. }()
  426. for c.scanner.Scan() {
  427. line := c.scanner.Text()
  428. if line == "" {
  429. continue
  430. }
  431. zap.L().Debug("[ACP] <<< 收到", zap.String("raw", line[:min(len(line), 500)]))
  432. var msg struct {
  433. JSONRPC string `json:"jsonrpc"`
  434. ID json.RawMessage `json:"id,omitempty"`
  435. Method string `json:"method,omitempty"`
  436. Result json.RawMessage `json:"result,omitempty"`
  437. Error *RPCError `json:"error,omitempty"`
  438. Params json.RawMessage `json:"params,omitempty"`
  439. }
  440. if err := json.Unmarshal([]byte(line), &msg); err != nil {
  441. zap.L().Warn("[ACP] JSON解析失败,跳过", zap.String("line", line[:min(len(line), 100)]))
  442. continue
  443. }
  444. hasId := len(msg.ID) > 0 && string(msg.ID) != "null"
  445. switch {
  446. case hasId && msg.Method == "":
  447. // 响应:按 id 派发给等待者
  448. var id int64
  449. if err := json.Unmarshal(msg.ID, &id); err != nil {
  450. zap.L().Warn("[ACP] 响应 id 解析失败", zap.String("id", string(msg.ID)))
  451. continue
  452. }
  453. c.deliverResponse(id, Response{
  454. JSONRPC: msg.JSONRPC,
  455. ID: &id,
  456. Result: msg.Result,
  457. Error: msg.Error,
  458. })
  459. case hasId && msg.Method != "":
  460. // Agent→Client 反向请求:独立 goroutine 处理,避免权限等待阻塞读循环
  461. id := append(json.RawMessage(nil), msg.ID...)
  462. params := append(json.RawMessage(nil), msg.Params...)
  463. method := msg.Method
  464. go c.handleAgentRequest(id, method, params)
  465. case !hasId && msg.Method != "":
  466. // 通知:交给当前 sink
  467. c.dispatchNotification(msg.Method, msg.Params)
  468. default:
  469. zap.L().Debug("[ACP] 未处理的消息", zap.String("line", line[:min(len(line), 200)]))
  470. }
  471. }
  472. }
  473. // registerPending 注册一个等待响应的请求,返回接收 channel。
  474. func (c *Client) registerPending(id int64) chan Response {
  475. ch := make(chan Response, 1)
  476. c.pendingMu.Lock()
  477. c.pending[id] = &pendingResponse{ch: ch}
  478. c.pendingMu.Unlock()
  479. return ch
  480. }
  481. // unregisterPending 移除等待项(请求完成或超时后调用)。
  482. func (c *Client) unregisterPending(id int64) {
  483. c.pendingMu.Lock()
  484. delete(c.pending, id)
  485. c.pendingMu.Unlock()
  486. }
  487. // deliverResponse 把响应投递给等待者;无人等待则忽略。
  488. func (c *Client) deliverResponse(id int64, resp Response) {
  489. c.pendingMu.Lock()
  490. p, ok := c.pending[id]
  491. if ok {
  492. delete(c.pending, id)
  493. }
  494. c.pendingMu.Unlock()
  495. if ok {
  496. p.ch <- resp
  497. } else {
  498. zap.L().Debug("[ACP] 收到无等待者的响应", zap.Int64("id", id))
  499. }
  500. }
  501. // failAllPending 进程退出时唤醒所有等待中的请求,返回错误响应。
  502. func (c *Client) failAllPending() {
  503. c.pendingMu.Lock()
  504. pend := c.pending
  505. c.pending = make(map[int64]*pendingResponse)
  506. c.pendingMu.Unlock()
  507. for id, p := range pend {
  508. p.ch <- Response{ID: &id, Error: &RPCError{Code: -1, Message: "Agent 进程已断开"}}
  509. }
  510. }
  511. // setSink 设置当前通知接收者,返回旧 sink(便于恢复)。
  512. func (c *Client) setSink(s notifySink) notifySink {
  513. c.sinkMu.Lock()
  514. old := c.sink
  515. c.sink = s
  516. c.sinkMu.Unlock()
  517. return old
  518. }
  519. // beginTurn 在一次 prompt 开始时设置事件通道与中止信号(受 sinkMu 保护),
  520. // 返回本轮的 abort channel 供上层(如断连时)主动中止。
  521. func (c *Client) beginTurn(ch chan<- Event) chan struct{} {
  522. abort := make(chan struct{})
  523. c.sinkMu.Lock()
  524. c.eventCh = ch
  525. c.abortCh = abort
  526. c.sinkMu.Unlock()
  527. return abort
  528. }
  529. // endTurn 在一次 prompt 结束时清理,并保证调用方随后可安全 close(eventCh):
  530. // 1. 关闭 abort,唤醒可能阻塞在发送上的 emit;
  531. // 2. 等待所有在途 emit(emitWg)退出;
  532. // 3. 在锁内清空 eventCh/abortCh。
  533. //
  534. // 注意:emit 不持 sinkMu 阻塞发送,故这里关闭 abort 不会与 emit 形成锁环。
  535. func (c *Client) endTurn() {
  536. c.sinkMu.Lock()
  537. abort := c.abortCh
  538. c.sinkMu.Unlock()
  539. if abort != nil {
  540. safeClose(abort)
  541. }
  542. c.emitWg.Wait()
  543. c.sinkMu.Lock()
  544. c.eventCh = nil
  545. c.abortCh = nil
  546. c.sinkMu.Unlock()
  547. }
  548. // abortTurn 主动中止当前 turn 的事件发送(上层连接断开时调用)。
  549. // 仅关闭 abort 信号,唤醒阻塞的 emit;turn 的最终结束仍由 Prompt 收到响应/取消驱动。
  550. func (c *Client) abortTurn() {
  551. c.sinkMu.Lock()
  552. abort := c.abortCh
  553. c.sinkMu.Unlock()
  554. if abort != nil {
  555. safeClose(abort)
  556. }
  557. }
  558. // AbortEvents 供上层在连接断开时调用:唤醒所有阻塞在事件发送上的 goroutine,
  559. // 使当前 turn 的事件流尽快收敛(配合 Cancel 通知 Agent 停止本轮)。
  560. func (c *Client) AbortEvents() {
  561. c.abortTurn()
  562. }
  563. // safeClose 幂等关闭一个 struct{} channel。
  564. func safeClose(ch chan struct{}) {
  565. defer func() { _ = recover() }()
  566. close(ch)
  567. }
  568. // emit 向当前 turn 的事件通道发送一个事件,返回是否成功送达。
  569. // 发送不持有 sinkMu(避免与 endTurn 关闭 abort 形成锁环),改用 emitWg 标记在途状态,
  570. // 使 endTurn 能在 close(eventCh) 前等待所有发送退出,杜绝向已关闭通道发送。
  571. // 发送同时监听 abort(上层停止消费)与 doneCh(进程退出),任一触发即放弃发送。
  572. func (c *Client) emit(ev Event) bool {
  573. c.emitWg.Add(1)
  574. defer c.emitWg.Done()
  575. c.sinkMu.Lock()
  576. ch := c.eventCh
  577. abort := c.abortCh
  578. c.sinkMu.Unlock()
  579. if ch == nil {
  580. return false
  581. }
  582. select {
  583. case ch <- ev:
  584. return true
  585. case <-abort:
  586. return false
  587. case <-c.doneCh:
  588. return false
  589. }
  590. }
  591. // pushPermissionEvent 把权限事件推给当前 eventCh;返回是否成功(无活跃通道时返回 false)。
  592. func (c *Client) pushPermissionEvent(ev Event) bool {
  593. return c.emit(ev)
  594. }
  595. // dispatchNotification 把通知交给当前 sink;无 sink 时走默认处理(仅缓存 metadata)。
  596. func (c *Client) dispatchNotification(method string, params json.RawMessage) {
  597. c.sinkMu.Lock()
  598. s := c.sink
  599. c.sinkMu.Unlock()
  600. if s != nil {
  601. s(method, params)
  602. return
  603. }
  604. c.defaultNotify(method, params)
  605. }
  606. // defaultNotify 无活跃 sink 时的兜底:仅捕获 metadata 用量,其余忽略。
  607. func (c *Client) defaultNotify(method string, params json.RawMessage) {
  608. if method == "_kiro.dev/metadata" {
  609. var data struct {
  610. ContextUsagePercentage float64 `json:"contextUsagePercentage"`
  611. TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
  612. }
  613. if err := json.Unmarshal(params, &data); err == nil {
  614. c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
  615. }
  616. }
  617. }
  618. // ResolvePermission 前端回传权限选择结果
  619. func (c *Client) ResolvePermission(optionId string) {
  620. select {
  621. case c.permissionCh <- optionId:
  622. default:
  623. // 没有等待中的权限请求,忽略
  624. }
  625. }
  626. // appendText 累积本轮 AI 文本(sink/readLoop goroutine 调用)。
  627. func (c *Client) appendText(s string) {
  628. c.textMu.Lock()
  629. c.textBuffer += s
  630. c.textMu.Unlock()
  631. }
  632. // takeText 取出并清空累积文本(Prompt goroutine 在 turn 结束时调用)。
  633. func (c *Client) takeText() string {
  634. c.textMu.Lock()
  635. s := c.textBuffer
  636. c.textBuffer = ""
  637. c.textMu.Unlock()
  638. return s
  639. }
  640. // resetText 清空累积文本(turn 开始前调用,避免跨 turn 串味)。
  641. func (c *Client) resetText() {
  642. c.textMu.Lock()
  643. c.textBuffer = ""
  644. c.textMu.Unlock()
  645. }
  646. // setMeta 更新最近的上下文用量与耗时(值 >0 时才覆盖)
  647. func (c *Client) setMeta(usage float64, turnMs int64) {
  648. c.metaMu.Lock()
  649. defer c.metaMu.Unlock()
  650. if usage > 0 {
  651. c.lastContextUsage = usage
  652. }
  653. if turnMs > 0 {
  654. c.lastTurnDurationMs = turnMs
  655. }
  656. }
  657. // GetMeta 返回最近的上下文用量(百分比)与上一轮耗时(毫秒)
  658. func (c *Client) GetMeta() (float64, int64) {
  659. c.metaMu.Lock()
  660. defer c.metaMu.Unlock()
  661. return c.lastContextUsage, c.lastTurnDurationMs
  662. }
  663. // Initialize 执行初始化握手
  664. func (c *Client) Initialize() error {
  665. params := InitializeParams{
  666. ProtocolVersion: 1,
  667. ClientInfo: ClientInfo{
  668. Name: "fwh-toolbox",
  669. Version: "1.0.0",
  670. },
  671. ClientCapabilities: ClientCapabilities{
  672. FS: &FSCapabilities{
  673. ReadTextFile: true,
  674. WriteTextFile: true,
  675. },
  676. Terminal: false, // 不声明终端能力,强制 Agent 使用 writeTextFile
  677. },
  678. }
  679. _, err := c.sendAndWait("initialize", params)
  680. return err
  681. }
  682. // NewSession 创建新会话,返回 sessionId、welcomeMessage、实际生效的 agentId 和 modelId
  683. func (c *Client) NewSession(cwd string) (sessionId string, welcomeMsg string, agentId string, modelId string, err error) {
  684. params := NewSessionParams{
  685. CWD: cwd,
  686. MCPServers: []any{},
  687. }
  688. result, err := c.sendAndWait("session/new", params)
  689. if err != nil {
  690. return "", "", "", "", err
  691. }
  692. var res NewSessionResult
  693. if err := json.Unmarshal(result, &res); err != nil {
  694. return "", "", "", "", fmt.Errorf("解析 session/new 响应失败: %w", err)
  695. }
  696. zap.L().Info("session/new", zap.String("result", string(result)))
  697. c.sessionId = res.SessionId
  698. // 提取当前 mode 的 welcomeMessage 和实际生效的 agentId
  699. if res.Modes != nil {
  700. agentId = res.Modes.CurrentModeId
  701. for _, mode := range res.Modes.AvailableModes {
  702. if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
  703. welcomeMsg = mode.Meta.WelcomeMessage
  704. break
  705. }
  706. }
  707. }
  708. // 提取实际生效的 modelId
  709. if res.Models != nil {
  710. modelId = res.Models.CurrentModelId
  711. }
  712. // session/new 之后 Kiro 紧跟发送的 commands/available、metadata 等通知,
  713. // 由读循环交给默认 sink 处理:其中 metadata 的初始上下文用量会被 setMeta 缓存。
  714. // 不再需要单独的 drainInitialMeta 阻塞读取。
  715. return res.SessionId, welcomeMsg, agentId, modelId, nil
  716. }
  717. // ReplayMessage 回放的单条消息
  718. type ReplayMessage struct {
  719. Role string `json:"role"` // "user" 或 "assistant"
  720. Content string `json:"content"`
  721. MessageId string `json:"messageId,omitempty"`
  722. }
  723. // LoadSession 加载已有会话(恢复历史对话上下文),返回回放的消息列表
  724. func (c *Client) LoadSession(sessionId string, cwd string) (string, string, string, string, []ReplayMessage, error) {
  725. params := map[string]any{
  726. "sessionId": sessionId,
  727. "cwd": cwd,
  728. "mcpServers": []any{},
  729. }
  730. // 回放消息在 session/load 响应之前以通知形式到达。用一个临时 sink 收集它们,
  731. // 由读循环在等待响应期间调用(sendAndWaitWithSink 会在返回前恢复原 sink)。
  732. var replayMessages []ReplayMessage
  733. currentMsg := &ReplayMessage{}
  734. sink := func(method string, p json.RawMessage) {
  735. if method != "session/update" && method != "_kiro.dev/session/update" {
  736. // 非回放通知(metadata 等)走默认处理,避免丢失用量
  737. c.defaultNotify(method, p)
  738. return
  739. }
  740. var notif SessionUpdate
  741. if err := json.Unmarshal(p, &notif); err != nil {
  742. return
  743. }
  744. var base UpdateBase
  745. if err := json.Unmarshal(notif.Update, &base); err != nil {
  746. return
  747. }
  748. switch base.SessionUpdate {
  749. case "user_message_chunk":
  750. if currentMsg.Content != "" {
  751. replayMessages = append(replayMessages, *currentMsg)
  752. }
  753. var chunk AgentMessageChunk
  754. json.Unmarshal(notif.Update, &chunk)
  755. currentMsg = &ReplayMessage{Role: "user", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  756. case "agent_message_chunk":
  757. var chunk AgentMessageChunk
  758. json.Unmarshal(notif.Update, &chunk)
  759. if currentMsg.Role != "assistant" || (chunk.MessageId != "" && chunk.MessageId != currentMsg.MessageId) {
  760. if currentMsg.Content != "" {
  761. replayMessages = append(replayMessages, *currentMsg)
  762. }
  763. currentMsg = &ReplayMessage{Role: "assistant", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  764. } else {
  765. currentMsg.Content += chunk.Content.Text
  766. }
  767. case "agent_thought_chunk", "tool_call", "tool_call_update", "tool_call_chunk":
  768. // 思考内容与工具调用回放忽略
  769. default:
  770. // 其他类型忽略
  771. }
  772. }
  773. result, err := c.sendAndWaitWithSink("session/load", params, sink)
  774. if err != nil {
  775. return "", "", "", "", nil, fmt.Errorf("session/load 错误: %w", err)
  776. }
  777. c.sessionId = sessionId
  778. // 追加最后一条未关闭的消息
  779. if currentMsg.Content != "" {
  780. replayMessages = append(replayMessages, *currentMsg)
  781. }
  782. // 解析响应中的 welcomeMessage、实际生效的 agentId 和 modelId
  783. var welcomeMsg string
  784. var agentId string
  785. var modelId string
  786. if len(result) > 0 {
  787. var res NewSessionResult
  788. if err := json.Unmarshal(result, &res); err == nil {
  789. if res.Modes != nil {
  790. agentId = res.Modes.CurrentModeId
  791. for _, mode := range res.Modes.AvailableModes {
  792. if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
  793. welcomeMsg = mode.Meta.WelcomeMessage
  794. break
  795. }
  796. }
  797. }
  798. if res.Models != nil {
  799. modelId = res.Models.CurrentModelId
  800. }
  801. }
  802. }
  803. // session/load 之后的初始用量 metadata 通知由默认 sink 捕获,无需单独处理。
  804. return sessionId, welcomeMsg, agentId, modelId, replayMessages, nil
  805. }
  806. // Prompt 发送消息并流式读取事件
  807. // eventCh 会在 turn 结束或出错时关闭
  808. func (c *Client) Prompt(sessionId string, message string, eventCh chan<- Event) {
  809. // 开始一个 turn:登记事件通道与 abort 信号。退出时 endTurn 会关闭 abort、
  810. // 等待所有在途 emit 结束,再清空通道引用,之后才 close(eventCh)——
  811. // 保证权限/流式 goroutine 不会向已关闭的通道发送。
  812. c.beginTurn(eventCh)
  813. defer func() {
  814. c.endTurn()
  815. close(eventCh)
  816. }()
  817. params := PromptParams{
  818. SessionId: sessionId,
  819. Prompt: []ContentBlock{
  820. {Type: "text", Text: message},
  821. },
  822. }
  823. // 注册等待 prompt 响应
  824. promptId := atomic.AddInt64(&c.requestId, 1)
  825. respCh := c.registerPending(promptId)
  826. // 清空上一轮可能残留的累积文本,避免跨 turn 串味
  827. c.resetText()
  828. // 安装 sink:turn 期间的 session/update 与扩展通知都转成 Event 推给 eventCh。
  829. // 反向请求(requestPermission)由读循环单独派发,不经过 sink。
  830. old := c.setSink(func(method string, p json.RawMessage) {
  831. switch method {
  832. case "session/update", "_kiro.dev/session/update":
  833. c.handleSessionUpdate(p)
  834. default:
  835. c.handleExtensionNotification(method, p)
  836. }
  837. })
  838. defer c.setSink(old)
  839. if err := c.writeRequest(promptId, "session/prompt", params); err != nil {
  840. c.unregisterPending(promptId)
  841. c.emit(Event{Type: "error", Content: fmt.Sprintf("发送 prompt 失败: %v", err)})
  842. return
  843. }
  844. zap.L().Debug("[ACP] prompt 已发送", zap.Int64("promptId", promptId), zap.String("sessionId", sessionId))
  845. // 等待 prompt 响应(turn 结束)或进程退出
  846. select {
  847. case resp := <-respCh:
  848. if full := c.takeText(); full != "" {
  849. zap.L().Info("[ACP] AI 回复完整内容", zap.String("text", full))
  850. }
  851. if resp.Error != nil {
  852. c.emit(Event{Type: "error", Content: resp.Error.FullMessage()})
  853. return
  854. }
  855. var result PromptResult
  856. if err := json.Unmarshal(resp.Result, &result); err == nil {
  857. c.emit(Event{Type: "done", SessionId: sessionId, StopReason: result.StopReason})
  858. }
  859. case <-c.doneCh:
  860. c.unregisterPending(promptId)
  861. c.emit(Event{Type: "error", Content: "Agent 进程已断开"})
  862. }
  863. }
  864. // Cancel 取消当前 prompt turn(ACP 协议:Notification, method="session/cancel", params={sessionId})
  865. func (c *Client) Cancel(sessionId string) {
  866. c.sendNotification("session/cancel", CancelParams{SessionId: sessionId})
  867. }
  868. // GetSessionId 获取当前会话 ID
  869. func (c *Client) GetSessionId() string {
  870. return c.sessionId
  871. }
  872. // Close 关闭 ACP Client,终止 Agent 进程
  873. func (c *Client) Close() {
  874. // 关闭 stdin 通知 Agent 退出
  875. c.stdin.Close()
  876. // 终止进程(回收由 waitProcess 中的 cmd.Wait 负责,此处不再重复 Wait)
  877. if c.cmd.Process != nil {
  878. c.cmd.Process.Kill()
  879. }
  880. // 解除可能阻塞在 permissionCh 上的 goroutine
  881. select {
  882. case c.permissionCh <- "deny":
  883. default:
  884. }
  885. }
  886. // IsAlive 检查 Agent 进程是否存活。
  887. // 依赖 waitProcess/readLoop 单点设置的 dead 标记,而非 cmd.ProcessState
  888. // (后者只有在 Wait 之后才被填充,子进程自行崩溃时会误判为存活)。
  889. func (c *Client) IsAlive() bool {
  890. return !c.dead.Load()
  891. }
  892. // ============ 内部方法 ============
  893. // sendRequest 发送请求(不等待响应),返回 request ID。
  894. // 注意:调用方若需要等待响应,应使用 sendAndWait;直接使用本方法的场景
  895. // (如 Prompt)需自行通过 registerPending/读循环获取响应。
  896. func (c *Client) sendRequest(method string, params any) int64 {
  897. id := atomic.AddInt64(&c.requestId, 1)
  898. if err := c.writeRequest(id, method, params); err != nil {
  899. zap.L().Error("[ACP] 发送请求失败", zap.String("method", method), zap.Error(err))
  900. }
  901. return id
  902. }
  903. // writeRequest 序列化并写入一个带 id 的请求,写入通过 writeMu 串行化。
  904. func (c *Client) writeRequest(id int64, method string, params any) error {
  905. req := Request{
  906. JSONRPC: "2.0",
  907. ID: id,
  908. Method: method,
  909. Params: params,
  910. }
  911. data, err := json.Marshal(req)
  912. if err != nil {
  913. return fmt.Errorf("序列化请求失败: %w", err)
  914. }
  915. zap.L().Debug("[ACP] >>> 发送", zap.String("raw", string(data)))
  916. return c.writeLine(data)
  917. }
  918. // writeLine 追加换行并串行写入 stdin。
  919. func (c *Client) writeLine(data []byte) error {
  920. c.writeMu.Lock()
  921. defer c.writeMu.Unlock()
  922. data = append(data, '\n')
  923. _, err := c.stdin.Write(data)
  924. return err
  925. }
  926. // sendNotification 发送 JSON-RPC Notification(不带 id,不期望响应)
  927. func (c *Client) sendNotification(method string, params any) {
  928. notif := struct {
  929. JSONRPC string `json:"jsonrpc"`
  930. Method string `json:"method"`
  931. Params any `json:"params,omitempty"`
  932. }{
  933. JSONRPC: "2.0",
  934. Method: method,
  935. Params: params,
  936. }
  937. data, err := json.Marshal(notif)
  938. if err != nil {
  939. zap.L().Error("[ACP] 序列化通知失败", zap.Error(err))
  940. return
  941. }
  942. zap.L().Debug("[ACP] >>> 发送(notification)", zap.String("raw", string(data)))
  943. if err := c.writeLine(data); err != nil {
  944. zap.L().Error("[ACP] 写入 stdin 失败(通知)", zap.String("method", method), zap.Error(err))
  945. }
  946. }
  947. // sendAndWait 发送请求并等待对应 ID 的响应(通过读循环分发)。
  948. // 若传入非 nil sink,在等待期间临时接管通知(用于捕获 init/load 阶段的通知),
  949. // 返回前恢复原 sink。
  950. func (c *Client) sendAndWait(method string, params any) (json.RawMessage, error) {
  951. return c.sendAndWaitWithSink(method, params, nil)
  952. }
  953. func (c *Client) sendAndWaitWithSink(method string, params any, sink notifySink) (json.RawMessage, error) {
  954. id := atomic.AddInt64(&c.requestId, 1)
  955. ch := c.registerPending(id)
  956. if sink != nil {
  957. old := c.setSink(sink)
  958. defer c.setSink(old)
  959. }
  960. if err := c.writeRequest(id, method, params); err != nil {
  961. c.unregisterPending(id)
  962. return nil, err
  963. }
  964. select {
  965. case resp := <-ch:
  966. if resp.Error != nil {
  967. return nil, fmt.Errorf("ACP 错误 [%d]: %s", resp.Error.Code, resp.Error.FullMessage())
  968. }
  969. return resp.Result, nil
  970. case <-c.doneCh:
  971. c.unregisterPending(id)
  972. return nil, fmt.Errorf("未收到 %s 的响应(进程已退出)", method)
  973. }
  974. }
  975. // handleSessionUpdate 解析并分发 session/update 通知
  976. func (c *Client) handleSessionUpdate(params json.RawMessage) {
  977. var notif SessionUpdate
  978. if err := json.Unmarshal(params, &notif); err != nil {
  979. return
  980. }
  981. // 解析 update 的类型
  982. var base UpdateBase
  983. if err := json.Unmarshal(notif.Update, &base); err != nil {
  984. return
  985. }
  986. // zap.L().Debug("[ACP] session/update", zap.String("type", base.SessionUpdate))
  987. switch base.SessionUpdate {
  988. case "agent_message_chunk":
  989. var chunk AgentMessageChunk
  990. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  991. c.appendText(chunk.Content.Text)
  992. c.emit(Event{Type: "text", Content: chunk.Content.Text})
  993. }
  994. case "agent_thought_chunk":
  995. var chunk AgentMessageChunk
  996. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  997. c.appendText(chunk.Content.Text)
  998. c.emit(Event{Type: "text", Content: chunk.Content.Text})
  999. }
  1000. case "tool_call":
  1001. var tc ToolCall
  1002. if err := json.Unmarshal(notif.Update, &tc); err == nil {
  1003. zap.L().Debug("[ACP] → tool_call", zap.String("title", tc.Title), zap.String("status", tc.Status), zap.String("id", tc.ToolCallId))
  1004. c.emit(Event{Type: "tool_call", ToolCallId: tc.ToolCallId, ToolName: tc.Title, ToolStatus: tc.Status})
  1005. }
  1006. case "tool_call_update":
  1007. var tcu ToolCallUpdate
  1008. if err := json.Unmarshal(notif.Update, &tcu); err == nil {
  1009. zap.L().Debug("[ACP] → tool_call_update", zap.String("title", tcu.Title), zap.String("status", tcu.Status), zap.String("id", tcu.ToolCallId))
  1010. c.emit(Event{Type: "tool_update", ToolCallId: tcu.ToolCallId, ToolName: tcu.Title, ToolStatus: tcu.Status})
  1011. }
  1012. case "tool_call_chunk":
  1013. // 工具调用的流式参数输出(Kiro 扩展)
  1014. zap.L().Debug("[ACP] → tool_call_chunk (ignored)")
  1015. case "plan":
  1016. zap.L().Debug("[ACP] → plan update")
  1017. default:
  1018. zap.L().Debug("[ACP] → 未知 session/update 类型", zap.String("type", base.SessionUpdate))
  1019. }
  1020. }
  1021. // handleExtensionNotification 处理 Kiro 扩展通知
  1022. func (c *Client) handleExtensionNotification(method string, params json.RawMessage) {
  1023. switch method {
  1024. case "_kiro.dev/commands/available":
  1025. // 可用命令和工具列表
  1026. var data struct {
  1027. SessionId string `json:"sessionId"`
  1028. Commands []struct {
  1029. Name string `json:"name"`
  1030. Description string `json:"description"`
  1031. } `json:"commands"`
  1032. Tools []struct {
  1033. Name string `json:"name"`
  1034. Description string `json:"description"`
  1035. Source string `json:"source"`
  1036. } `json:"tools"`
  1037. }
  1038. if err := json.Unmarshal(params, &data); err == nil {
  1039. zap.L().Debug("[ACP] 可用命令/工具更新",
  1040. zap.Int("commands", len(data.Commands)),
  1041. zap.Int("tools", len(data.Tools)),
  1042. )
  1043. // 推送工具列表给前端(可选)
  1044. if len(data.Tools) > 0 {
  1045. toolNames := make([]string, 0, len(data.Tools))
  1046. for _, t := range data.Tools {
  1047. toolNames = append(toolNames, t.Name)
  1048. }
  1049. c.emit(Event{Type: "tools_available", Content: toJSONStr(toolNames)})
  1050. }
  1051. }
  1052. case "_kiro.dev/metadata":
  1053. // 上下文使用量和耗时
  1054. var data struct {
  1055. SessionId string `json:"sessionId"`
  1056. ContextUsagePercentage float64 `json:"contextUsagePercentage"`
  1057. TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
  1058. }
  1059. if err := json.Unmarshal(params, &data); err == nil {
  1060. zap.L().Debug("[ACP] metadata",
  1061. zap.Float64("contextUsage%", data.ContextUsagePercentage),
  1062. zap.Int64("turnMs", data.TurnDurationMs),
  1063. )
  1064. // 缓存最近用量/耗时,供首屏读取
  1065. c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
  1066. // 上下文用量每条通知都会带(turnDurationMs 仅 turn 结束那条才有),
  1067. // 因此总是转发用量,耗时有值时才附带
  1068. content := map[string]any{
  1069. "contextUsagePercent": data.ContextUsagePercentage,
  1070. }
  1071. if data.TurnDurationMs > 0 {
  1072. content["turnDurationMs"] = data.TurnDurationMs
  1073. }
  1074. c.emit(Event{
  1075. Type: "metadata",
  1076. Content: toJSONStr(content),
  1077. })
  1078. }
  1079. case "_kiro.dev/subagent/list_update":
  1080. // 子代理列表更新
  1081. zap.L().Debug("[ACP] 子代理列表更新")
  1082. case "_kiro.dev/mcp/server_initialized":
  1083. // MCP Server 初始化完成
  1084. var data struct {
  1085. ServerName string `json:"serverName"`
  1086. }
  1087. json.Unmarshal(params, &data)
  1088. zap.L().Info("[ACP] MCP Server 初始化完成", zap.String("server", data.ServerName))
  1089. case "_kiro.dev/compaction/status":
  1090. // 上下文压缩状态
  1091. zap.L().Debug("[ACP] 上下文压缩中")
  1092. case "_kiro.dev/clear/status":
  1093. // 会话清除状态
  1094. zap.L().Debug("[ACP] 会话清除中")
  1095. default:
  1096. zap.L().Debug("[ACP] 未处理的扩展通知", zap.String("method", method))
  1097. }
  1098. }
  1099. // toJSONStr 序列化为 JSON 字符串
  1100. func toJSONStr(v any) string {
  1101. data, _ := json.Marshal(v)
  1102. return string(data)
  1103. }
  1104. // applyLineWindow 按 ACP readTextFile 语义对文本做行窗口截取。
  1105. // - line 为 1 基起始行号(nil 或 <=0 视为从第 1 行开始);
  1106. // - limit 为返回的最大行数(nil 或 <=0 视为不限制,返回到文件末尾)。
  1107. //
  1108. // 保留行间换行;起始行越界时返回空串。用于避免把整份大文件回传给 Agent。
  1109. func applyLineWindow(content string, line, limit *int) string {
  1110. start := 1
  1111. if line != nil && *line > 0 {
  1112. start = *line
  1113. }
  1114. // 无窗口需求:整文件返回
  1115. if start <= 1 && (limit == nil || *limit <= 0) {
  1116. return content
  1117. }
  1118. lines := strings.Split(content, "\n")
  1119. if start > len(lines) {
  1120. return ""
  1121. }
  1122. from := start - 1 // 转 0 基
  1123. to := len(lines)
  1124. if limit != nil && *limit > 0 {
  1125. if end := from + *limit; end < to {
  1126. to = end
  1127. }
  1128. }
  1129. return strings.Join(lines[from:to], "\n")
  1130. }
  1131. // handleAgentRequest 处理 Agent→Client 的反向请求。
  1132. // 该方法由读循环在独立 goroutine 中调用,因此权限等待不会阻塞读循环;
  1133. // stdin 写入通过 writeLine(writeMu)串行化,不与 Cancel 争锁。
  1134. func (c *Client) handleAgentRequest(rawId json.RawMessage, method string, rawParams json.RawMessage) {
  1135. zap.L().Debug("[ACP] 处理反向请求", zap.String("method", method), zap.String("id", string(rawId)), zap.String("params", string(rawParams[:min(len(rawParams), 200)])))
  1136. var respData []byte
  1137. switch method {
  1138. case "readTextFile":
  1139. // 读取文件:先从 FileStore 查找(MinIO),再从本地文件系统
  1140. var params struct {
  1141. Path string `json:"path"`
  1142. Line *int `json:"line,omitempty"`
  1143. Limit *int `json:"limit,omitempty"`
  1144. }
  1145. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  1146. var content string
  1147. var readErr error
  1148. // 优先从 FileStore(MinIO)读取:先按路径解析出 object key
  1149. key, keyErr := filestore.FindKeyByPath("upload", c.UserId, c.sessionId, params.Path)
  1150. if keyErr == nil && key != "" {
  1151. content, readErr = filestore.ReadFile(key)
  1152. zap.L().Debug("[ACP] readTextFile 从 MinIO 读取", zap.String("path", params.Path), zap.String("key", key))
  1153. } else {
  1154. // 回退到本地文件系统
  1155. data, err := os.ReadFile(params.Path)
  1156. if err == nil {
  1157. content = string(data)
  1158. zap.L().Debug("[ACP] readTextFile 从本地读取", zap.String("path", params.Path))
  1159. } else {
  1160. readErr = err
  1161. }
  1162. }
  1163. if readErr == nil {
  1164. windowed := applyLineWindow(content, params.Line, params.Limit)
  1165. result, _ := json.Marshal(map[string]string{"content": windowed})
  1166. respData, _ = json.Marshal(map[string]any{
  1167. "jsonrpc": "2.0",
  1168. "id": rawId,
  1169. "result": json.RawMessage(result),
  1170. })
  1171. }
  1172. }
  1173. if respData == nil {
  1174. respData, _ = json.Marshal(map[string]any{
  1175. "jsonrpc": "2.0",
  1176. "id": rawId,
  1177. "error": map[string]any{"code": -1, "message": "file not found"},
  1178. })
  1179. }
  1180. case "writeTextFile":
  1181. // 写入文件到 MinIO(不写本地)
  1182. var params struct {
  1183. Path string `json:"path"`
  1184. Content string `json:"content"`
  1185. }
  1186. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  1187. record, err := filestore.UploadFile(c.UserId, c.sessionId, filepath.Base(params.Path), params.Content)
  1188. if err == nil {
  1189. respData, _ = json.Marshal(map[string]any{
  1190. "jsonrpc": "2.0",
  1191. "id": rawId,
  1192. "result": map[string]any{},
  1193. })
  1194. _ = record // 文件下载通过前端标记检测实现,不再推送事件
  1195. } else {
  1196. zap.L().Error("[ACP] writeTextFile MinIO上传失败", zap.Error(err))
  1197. }
  1198. }
  1199. if respData == nil {
  1200. respData, _ = json.Marshal(map[string]any{
  1201. "jsonrpc": "2.0",
  1202. "id": rawId,
  1203. "error": map[string]any{"code": -1, "message": "write failed"},
  1204. })
  1205. }
  1206. case "requestPermission", "session/request_permission":
  1207. // 解析权限请求参数
  1208. var params struct {
  1209. ToolCall *struct {
  1210. Title string `json:"title"`
  1211. } `json:"toolCall"`
  1212. Options []struct {
  1213. OptionId string `json:"optionId"`
  1214. Name string `json:"name"`
  1215. Kind string `json:"kind"`
  1216. } `json:"options"`
  1217. }
  1218. json.Unmarshal(rawParams, &params)
  1219. title := ""
  1220. if params.ToolCall != nil {
  1221. title = params.ToolCall.Title
  1222. }
  1223. // 当前策略:所有权限请求都推给前端授权。
  1224. options := make([]PermissionOption, 0, len(params.Options))
  1225. for _, opt := range params.Options {
  1226. options = append(options, PermissionOption{
  1227. OptionId: opt.OptionId,
  1228. Name: opt.Name,
  1229. Kind: opt.Kind,
  1230. })
  1231. }
  1232. requestId := fmt.Sprintf("perm_%s", string(rawId))
  1233. pushed := c.pushPermissionEvent(Event{
  1234. Type: "permission",
  1235. Permission: &PermissionEvent{
  1236. RequestId: requestId,
  1237. Title: title,
  1238. Options: options,
  1239. },
  1240. })
  1241. if pushed {
  1242. // 丢弃可能残留的旧授权结果,避免消费到上一次请求/Close 写入的值
  1243. select {
  1244. case <-c.permissionCh:
  1245. default:
  1246. }
  1247. // 有界等待前端回复:超时 / 进程退出 时按 deny 处理,避免永久阻塞。
  1248. // 本 goroutine 阻塞不影响读循环(读循环在另一 goroutine)。
  1249. selectedOptionId := c.selectDenyOption(params.Options)
  1250. timer := time.NewTimer(PermissionWaitTimeout)
  1251. select {
  1252. case selectedOptionId = <-c.permissionCh:
  1253. case <-timer.C:
  1254. zap.L().Warn("[ACP] 权限等待超时,默认拒绝", zap.String("title", title))
  1255. case <-c.doneCh:
  1256. zap.L().Warn("[ACP] 权限等待期间进程已退出", zap.String("title", title))
  1257. }
  1258. timer.Stop()
  1259. respData, _ = json.Marshal(map[string]any{
  1260. "jsonrpc": "2.0",
  1261. "id": rawId,
  1262. "result": map[string]any{
  1263. "outcome": map[string]any{
  1264. "outcome": "selected",
  1265. "optionId": selectedOptionId,
  1266. },
  1267. },
  1268. })
  1269. } else {
  1270. // 无事件通道(非 prompt 阶段):自动允许
  1271. respData, _ = json.Marshal(map[string]any{
  1272. "jsonrpc": "2.0",
  1273. "id": rawId,
  1274. "result": map[string]any{
  1275. "outcome": map[string]any{
  1276. "outcome": "selected",
  1277. "optionId": "allow_once",
  1278. },
  1279. },
  1280. })
  1281. }
  1282. default:
  1283. // 未知请求返回错误
  1284. respData, _ = json.Marshal(map[string]any{
  1285. "jsonrpc": "2.0",
  1286. "id": rawId,
  1287. "error": map[string]any{"code": -32601, "message": "method not supported"},
  1288. })
  1289. }
  1290. if respData != nil {
  1291. zap.L().Debug("[ACP] 发送反向响应", zap.String("method", method), zap.String("resp", string(respData[:min(len(respData), 200)])))
  1292. if err := c.writeLine(respData); err != nil {
  1293. zap.L().Error("[ACP] 写入反向响应失败", zap.String("method", method), zap.Error(err))
  1294. }
  1295. } else {
  1296. zap.L().Warn("[ACP] 反向请求无响应数据", zap.String("method", method))
  1297. }
  1298. }
  1299. // selectDenyOption 从可选项中挑选一个 deny 语义的 optionId 作为超时/退出时的默认值。
  1300. // 找不到显式 deny 选项时回退到字面量 "deny"。
  1301. func (c *Client) selectDenyOption(options []struct {
  1302. OptionId string `json:"optionId"`
  1303. Name string `json:"name"`
  1304. Kind string `json:"kind"`
  1305. }) string {
  1306. for _, opt := range options {
  1307. if strings.Contains(strings.ToLower(opt.Kind), "deny") || strings.Contains(strings.ToLower(opt.OptionId), "deny") {
  1308. return opt.OptionId
  1309. }
  1310. }
  1311. return "deny"
  1312. }