acp.go 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569
  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. // envHasKey 判断 env("KEY=VALUE" 形式的切片)中是否已存在指定 key。
  327. func envHasKey(env []string, key string) bool {
  328. prefix := key + "="
  329. for _, e := range env {
  330. if strings.HasPrefix(e, prefix) {
  331. return true
  332. }
  333. }
  334. return false
  335. }
  336. // ensureDirWritableByRunAsUser 确保日志目录可被降权用户写入。
  337. // 主服务常以 root 运行,创建的目录属主为 root;子进程降权到 runAsUser 后会写不进去,
  338. // 导致 kiro-cli 日志被静默丢弃。这里将目录(含其直接父目录)chown 到 runAsUser 的 uid/gid。
  339. // runAsUser 为空表示不降权(与主进程同用户),无需调整。所有失败仅记录告警,不致命。
  340. func ensureDirWritableByRunAsUser(dir string, runAsUser string) {
  341. runAsUser = strings.TrimSpace(runAsUser)
  342. if runAsUser == "" {
  343. return
  344. }
  345. u, err := user.Lookup(runAsUser)
  346. if err != nil {
  347. zap.L().Warn("[ACP] 查找降权用户失败,日志目录属主未调整", zap.String("user", runAsUser), zap.Error(err))
  348. return
  349. }
  350. uid, err1 := strconv.Atoi(u.Uid)
  351. gid, err2 := strconv.Atoi(u.Gid)
  352. if err1 != nil || err2 != nil {
  353. zap.L().Warn("[ACP] 解析降权用户 uid/gid 失败,日志目录属主未调整",
  354. zap.String("user", runAsUser), zap.String("uid", u.Uid), zap.String("gid", u.Gid))
  355. return
  356. }
  357. // 同时调整父目录(如 /tmp/kiro-log)与目标目录(如 /tmp/kiro-log/logs)。
  358. for _, d := range []string{filepath.Dir(dir), dir} {
  359. if err := os.Chown(d, uid, gid); err != nil {
  360. zap.L().Warn("[ACP] 调整日志目录属主失败(降权用户可能无法写入日志)",
  361. zap.String("dir", d), zap.String("user", runAsUser), zap.Error(err))
  362. }
  363. }
  364. }
  365. // overrideEnv 用 kv 覆盖 env 中已存在的同名变量,不存在则追加。
  366. func overrideEnv(env []string, kv map[string]string) []string {
  367. out := make([]string, 0, len(env)+len(kv))
  368. seen := make(map[string]bool, len(kv))
  369. for _, e := range env {
  370. key := e
  371. if idx := strings.IndexByte(e, '='); idx >= 0 {
  372. key = e[:idx]
  373. }
  374. if v, ok := kv[key]; ok {
  375. out = append(out, key+"="+v)
  376. seen[key] = true
  377. } else {
  378. out = append(out, e)
  379. }
  380. }
  381. for k, v := range kv {
  382. if !seen[k] {
  383. out = append(out, k+"="+v)
  384. }
  385. }
  386. return out
  387. }
  388. // NewClient 创建并启动 ACP Client
  389. func NewClient(cfg ClientConfig) (*Client, error) {
  390. cmd := exec.Command(cfg.Command, cfg.Args...)
  391. // 确保工作目录是绝对路径
  392. absDir, err := filepath.Abs(cfg.WorkingDir)
  393. if err != nil {
  394. absDir = cfg.WorkingDir
  395. }
  396. cmd.Dir = absDir
  397. // 设置环境变量
  398. cmd.Env = append(os.Environ(), cfg.Env...)
  399. // A 方案:为每次启动的子进程分配带启动时间戳的独立日志文件,避免覆盖历史日志。
  400. // 仅在开启了 kiro-cli debug 日志(env 含 KIRO_LOG_LEVEL)、且调用方未显式指定
  401. // KIRO_CHAT_LOG_FILE 时生效。KIRO_CHAT_LOG_FILE 是 kiro-cli acp 官方支持的日志路径变量。
  402. if envHasKey(cmd.Env, "KIRO_LOG_LEVEL") && !envHasKey(cmd.Env, "KIRO_CHAT_LOG_FILE") {
  403. logDir := "/tmp/kiro-log/logs"
  404. if err := os.MkdirAll(logDir, 0o777); err != nil {
  405. zap.L().Warn("[ACP] 创建日志目录失败,沿用 kiro-cli 默认日志路径",
  406. zap.String("dir", logDir), zap.Error(err))
  407. } else {
  408. // 主服务常以 root 运行,创建出的目录默认属主为 root;而子进程会被
  409. // ApplyRunAsUser 降权到 cfg.RunAsUser,若目录不可写日志将被静默丢弃。
  410. // 因此这里把目录属主/权限调整到降权用户可写。
  411. ensureDirWritableByRunAsUser(logDir, cfg.RunAsUser)
  412. logPath := filepath.Join(logDir, fmt.Sprintf("kiro-chat-%s.log", time.Now().Format("20060102T150405.000")))
  413. cmd.Env = append(cmd.Env, "KIRO_CHAT_LOG_FILE="+logPath)
  414. // 顺带关闭 ANSI 颜色码,便于后续直接阅读/grep 日志。
  415. if !envHasKey(cmd.Env, "KIRO_LOG_NO_COLOR") {
  416. cmd.Env = append(cmd.Env, "KIRO_LOG_NO_COLOR=1")
  417. }
  418. zap.L().Info("[ACP] 本次子进程日志文件", zap.String("logFile", logPath))
  419. }
  420. }
  421. // 若配置了降权用户,则以该用户身份拉起子进程
  422. if err := ApplyRunAsUser(cmd, cfg.RunAsUser); err != nil {
  423. return nil, err
  424. }
  425. stdin, err := cmd.StdinPipe()
  426. if err != nil {
  427. return nil, fmt.Errorf("创建 stdin pipe 失败: %w", err)
  428. }
  429. stdout, err := cmd.StdoutPipe()
  430. if err != nil {
  431. return nil, fmt.Errorf("创建 stdout pipe 失败: %w", err)
  432. }
  433. // 捕获 stderr
  434. stderrPipe, _ := cmd.StderrPipe()
  435. go func() {
  436. if stderrPipe != nil {
  437. s := bufio.NewScanner(stderrPipe)
  438. for s.Scan() {
  439. zap.L().Warn("[ACP] agent stderr", zap.String("line", s.Text()))
  440. }
  441. }
  442. }()
  443. if err := cmd.Start(); err != nil {
  444. return nil, fmt.Errorf("启动 Agent 进程失败: %w", err)
  445. }
  446. scanner := bufio.NewScanner(stdout)
  447. scanner.Buffer(make([]byte, 0, 2*1024*1024), 2*1024*1024) // 2MB buffer
  448. c := &Client{
  449. cmd: cmd,
  450. stdin: stdin,
  451. scanner: scanner,
  452. pending: make(map[int64]*pendingResponse),
  453. permissionCh: make(chan string, 1),
  454. doneCh: make(chan struct{}),
  455. }
  456. // 监听子进程退出,单点置位 dead 标记(供 IsAlive 可靠判活)
  457. go c.waitProcess()
  458. // 启动唯一的读循环,统一分发响应/请求/通知
  459. go c.readLoop()
  460. return c, nil
  461. }
  462. // waitProcess 阻塞等待子进程退出,退出后标记 dead。
  463. // 这是回收子进程、置位存活标记的唯一入口,因此 IsAlive 不再依赖
  464. // cmd.ProcessState(只有调用 Wait 后才会被填充)的偶然状态。
  465. func (c *Client) waitProcess() {
  466. _ = c.cmd.Wait()
  467. c.markDead()
  468. }
  469. // markDead 标记进程已退出,并关闭 doneCh(幂等)。
  470. func (c *Client) markDead() {
  471. if c.dead.CompareAndSwap(false, true) {
  472. close(c.doneCh)
  473. }
  474. }
  475. // ============ 读循环与分发 ============
  476. // readLoop 是唯一读取 stdout 的 goroutine。它把每行消息按类型派发:
  477. // - 响应(有 id、无 method)→ 投递到 pending map 中等待的 channel;
  478. // - 反向请求(有 id、有 method)→ 在独立 goroutine 中处理(权限等待不阻塞读循环);
  479. // - 通知(无 id、有 method)→ 交给当前 notifySink。
  480. //
  481. // scanner 结束(EOF / 进程退出)时标记 dead,并唤醒所有等待中的请求。
  482. func (c *Client) readLoop() {
  483. defer func() {
  484. c.markDead()
  485. c.failAllPending()
  486. }()
  487. for c.scanner.Scan() {
  488. line := c.scanner.Text()
  489. if line == "" {
  490. continue
  491. }
  492. zap.L().Debug("[ACP] <<< 收到", zap.String("raw", line[:min(len(line), 500)]))
  493. var msg struct {
  494. JSONRPC string `json:"jsonrpc"`
  495. ID json.RawMessage `json:"id,omitempty"`
  496. Method string `json:"method,omitempty"`
  497. Result json.RawMessage `json:"result,omitempty"`
  498. Error *RPCError `json:"error,omitempty"`
  499. Params json.RawMessage `json:"params,omitempty"`
  500. }
  501. if err := json.Unmarshal([]byte(line), &msg); err != nil {
  502. zap.L().Warn("[ACP] JSON解析失败,跳过", zap.String("line", line[:min(len(line), 100)]))
  503. continue
  504. }
  505. hasId := len(msg.ID) > 0 && string(msg.ID) != "null"
  506. switch {
  507. case hasId && msg.Method == "":
  508. // 响应:按 id 派发给等待者
  509. var id int64
  510. if err := json.Unmarshal(msg.ID, &id); err != nil {
  511. zap.L().Warn("[ACP] 响应 id 解析失败", zap.String("id", string(msg.ID)))
  512. continue
  513. }
  514. c.deliverResponse(id, Response{
  515. JSONRPC: msg.JSONRPC,
  516. ID: &id,
  517. Result: msg.Result,
  518. Error: msg.Error,
  519. })
  520. case hasId && msg.Method != "":
  521. // Agent→Client 反向请求:独立 goroutine 处理,避免权限等待阻塞读循环
  522. id := append(json.RawMessage(nil), msg.ID...)
  523. params := append(json.RawMessage(nil), msg.Params...)
  524. method := msg.Method
  525. go c.handleAgentRequest(id, method, params)
  526. case !hasId && msg.Method != "":
  527. // 通知:交给当前 sink
  528. c.dispatchNotification(msg.Method, msg.Params)
  529. default:
  530. zap.L().Debug("[ACP] 未处理的消息", zap.String("line", line[:min(len(line), 200)]))
  531. }
  532. }
  533. }
  534. // registerPending 注册一个等待响应的请求,返回接收 channel。
  535. func (c *Client) registerPending(id int64) chan Response {
  536. ch := make(chan Response, 1)
  537. c.pendingMu.Lock()
  538. c.pending[id] = &pendingResponse{ch: ch}
  539. c.pendingMu.Unlock()
  540. return ch
  541. }
  542. // unregisterPending 移除等待项(请求完成或超时后调用)。
  543. func (c *Client) unregisterPending(id int64) {
  544. c.pendingMu.Lock()
  545. delete(c.pending, id)
  546. c.pendingMu.Unlock()
  547. }
  548. // deliverResponse 把响应投递给等待者;无人等待则忽略。
  549. func (c *Client) deliverResponse(id int64, resp Response) {
  550. c.pendingMu.Lock()
  551. p, ok := c.pending[id]
  552. if ok {
  553. delete(c.pending, id)
  554. }
  555. c.pendingMu.Unlock()
  556. if ok {
  557. p.ch <- resp
  558. } else {
  559. zap.L().Debug("[ACP] 收到无等待者的响应", zap.Int64("id", id))
  560. }
  561. }
  562. // failAllPending 进程退出时唤醒所有等待中的请求,返回错误响应。
  563. func (c *Client) failAllPending() {
  564. c.pendingMu.Lock()
  565. pend := c.pending
  566. c.pending = make(map[int64]*pendingResponse)
  567. c.pendingMu.Unlock()
  568. for id, p := range pend {
  569. p.ch <- Response{ID: &id, Error: &RPCError{Code: -1, Message: "Agent 进程已断开"}}
  570. }
  571. }
  572. // setSink 设置当前通知接收者,返回旧 sink(便于恢复)。
  573. func (c *Client) setSink(s notifySink) notifySink {
  574. c.sinkMu.Lock()
  575. old := c.sink
  576. c.sink = s
  577. c.sinkMu.Unlock()
  578. return old
  579. }
  580. // beginTurn 在一次 prompt 开始时设置事件通道与中止信号(受 sinkMu 保护),
  581. // 返回本轮的 abort channel 供上层(如断连时)主动中止。
  582. func (c *Client) beginTurn(ch chan<- Event) chan struct{} {
  583. abort := make(chan struct{})
  584. c.sinkMu.Lock()
  585. c.eventCh = ch
  586. c.abortCh = abort
  587. c.sinkMu.Unlock()
  588. return abort
  589. }
  590. // endTurn 在一次 prompt 结束时清理,并保证调用方随后可安全 close(eventCh):
  591. // 1. 关闭 abort,唤醒可能阻塞在发送上的 emit;
  592. // 2. 等待所有在途 emit(emitWg)退出;
  593. // 3. 在锁内清空 eventCh/abortCh。
  594. //
  595. // 注意:emit 不持 sinkMu 阻塞发送,故这里关闭 abort 不会与 emit 形成锁环。
  596. func (c *Client) endTurn() {
  597. c.sinkMu.Lock()
  598. abort := c.abortCh
  599. c.sinkMu.Unlock()
  600. if abort != nil {
  601. safeClose(abort)
  602. }
  603. c.emitWg.Wait()
  604. c.sinkMu.Lock()
  605. c.eventCh = nil
  606. c.abortCh = nil
  607. c.sinkMu.Unlock()
  608. }
  609. // abortTurn 主动中止当前 turn 的事件发送(上层连接断开时调用)。
  610. // 仅关闭 abort 信号,唤醒阻塞的 emit;turn 的最终结束仍由 Prompt 收到响应/取消驱动。
  611. func (c *Client) abortTurn() {
  612. c.sinkMu.Lock()
  613. abort := c.abortCh
  614. c.sinkMu.Unlock()
  615. if abort != nil {
  616. safeClose(abort)
  617. }
  618. }
  619. // AbortEvents 供上层在连接断开时调用:唤醒所有阻塞在事件发送上的 goroutine,
  620. // 使当前 turn 的事件流尽快收敛(配合 Cancel 通知 Agent 停止本轮)。
  621. func (c *Client) AbortEvents() {
  622. c.abortTurn()
  623. }
  624. // safeClose 幂等关闭一个 struct{} channel。
  625. func safeClose(ch chan struct{}) {
  626. defer func() { _ = recover() }()
  627. close(ch)
  628. }
  629. // emit 向当前 turn 的事件通道发送一个事件,返回是否成功送达。
  630. // 发送不持有 sinkMu(避免与 endTurn 关闭 abort 形成锁环),改用 emitWg 标记在途状态,
  631. // 使 endTurn 能在 close(eventCh) 前等待所有发送退出,杜绝向已关闭通道发送。
  632. // 发送同时监听 abort(上层停止消费)与 doneCh(进程退出),任一触发即放弃发送。
  633. func (c *Client) emit(ev Event) bool {
  634. c.emitWg.Add(1)
  635. defer c.emitWg.Done()
  636. c.sinkMu.Lock()
  637. ch := c.eventCh
  638. abort := c.abortCh
  639. c.sinkMu.Unlock()
  640. if ch == nil {
  641. return false
  642. }
  643. select {
  644. case ch <- ev:
  645. return true
  646. case <-abort:
  647. return false
  648. case <-c.doneCh:
  649. return false
  650. }
  651. }
  652. // pushPermissionEvent 把权限事件推给当前 eventCh;返回是否成功(无活跃通道时返回 false)。
  653. func (c *Client) pushPermissionEvent(ev Event) bool {
  654. return c.emit(ev)
  655. }
  656. // dispatchNotification 把通知交给当前 sink;无 sink 时走默认处理(仅缓存 metadata)。
  657. func (c *Client) dispatchNotification(method string, params json.RawMessage) {
  658. c.sinkMu.Lock()
  659. s := c.sink
  660. c.sinkMu.Unlock()
  661. if s != nil {
  662. s(method, params)
  663. return
  664. }
  665. c.defaultNotify(method, params)
  666. }
  667. // defaultNotify 无活跃 sink 时的兜底:仅捕获 metadata 用量,其余忽略。
  668. func (c *Client) defaultNotify(method string, params json.RawMessage) {
  669. if method == "_kiro.dev/metadata" {
  670. var data struct {
  671. ContextUsagePercentage float64 `json:"contextUsagePercentage"`
  672. TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
  673. }
  674. if err := json.Unmarshal(params, &data); err == nil {
  675. c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
  676. }
  677. }
  678. }
  679. // ResolvePermission 前端回传权限选择结果
  680. func (c *Client) ResolvePermission(optionId string) {
  681. select {
  682. case c.permissionCh <- optionId:
  683. default:
  684. // 没有等待中的权限请求,忽略
  685. }
  686. }
  687. // appendText 累积本轮 AI 文本(sink/readLoop goroutine 调用)。
  688. func (c *Client) appendText(s string) {
  689. c.textMu.Lock()
  690. c.textBuffer += s
  691. c.textMu.Unlock()
  692. }
  693. // takeText 取出并清空累积文本(Prompt goroutine 在 turn 结束时调用)。
  694. func (c *Client) takeText() string {
  695. c.textMu.Lock()
  696. s := c.textBuffer
  697. c.textBuffer = ""
  698. c.textMu.Unlock()
  699. return s
  700. }
  701. // resetText 清空累积文本(turn 开始前调用,避免跨 turn 串味)。
  702. func (c *Client) resetText() {
  703. c.textMu.Lock()
  704. c.textBuffer = ""
  705. c.textMu.Unlock()
  706. }
  707. // setMeta 更新最近的上下文用量与耗时(值 >0 时才覆盖)
  708. func (c *Client) setMeta(usage float64, turnMs int64) {
  709. c.metaMu.Lock()
  710. defer c.metaMu.Unlock()
  711. if usage > 0 {
  712. c.lastContextUsage = usage
  713. }
  714. if turnMs > 0 {
  715. c.lastTurnDurationMs = turnMs
  716. }
  717. }
  718. // GetMeta 返回最近的上下文用量(百分比)与上一轮耗时(毫秒)
  719. func (c *Client) GetMeta() (float64, int64) {
  720. c.metaMu.Lock()
  721. defer c.metaMu.Unlock()
  722. return c.lastContextUsage, c.lastTurnDurationMs
  723. }
  724. // Initialize 执行初始化握手
  725. func (c *Client) Initialize() error {
  726. params := InitializeParams{
  727. ProtocolVersion: 1,
  728. ClientInfo: ClientInfo{
  729. Name: "fwh-toolbox",
  730. Version: "1.0.0",
  731. },
  732. ClientCapabilities: ClientCapabilities{
  733. FS: &FSCapabilities{
  734. ReadTextFile: true,
  735. WriteTextFile: true,
  736. },
  737. Terminal: false, // 不声明终端能力,强制 Agent 使用 writeTextFile
  738. },
  739. }
  740. _, err := c.sendAndWait("initialize", params)
  741. return err
  742. }
  743. // NewSession 创建新会话,返回 sessionId、welcomeMessage、实际生效的 agentId 和 modelId
  744. func (c *Client) NewSession(cwd string) (sessionId string, welcomeMsg string, agentId string, modelId string, err error) {
  745. params := NewSessionParams{
  746. CWD: cwd,
  747. MCPServers: []any{},
  748. }
  749. result, err := c.sendAndWait("session/new", params)
  750. if err != nil {
  751. return "", "", "", "", err
  752. }
  753. var res NewSessionResult
  754. if err := json.Unmarshal(result, &res); err != nil {
  755. return "", "", "", "", fmt.Errorf("解析 session/new 响应失败: %w", err)
  756. }
  757. zap.L().Info("session/new", zap.String("result", string(result)))
  758. c.sessionId = res.SessionId
  759. // 提取当前 mode 的 welcomeMessage 和实际生效的 agentId
  760. if res.Modes != nil {
  761. agentId = res.Modes.CurrentModeId
  762. for _, mode := range res.Modes.AvailableModes {
  763. if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
  764. welcomeMsg = mode.Meta.WelcomeMessage
  765. break
  766. }
  767. }
  768. }
  769. // 提取实际生效的 modelId
  770. if res.Models != nil {
  771. modelId = res.Models.CurrentModelId
  772. }
  773. // session/new 之后 Kiro 紧跟发送的 commands/available、metadata 等通知,
  774. // 由读循环交给默认 sink 处理:其中 metadata 的初始上下文用量会被 setMeta 缓存。
  775. // 不再需要单独的 drainInitialMeta 阻塞读取。
  776. return res.SessionId, welcomeMsg, agentId, modelId, nil
  777. }
  778. // ReplayMessage 回放的单条消息
  779. type ReplayMessage struct {
  780. Role string `json:"role"` // "user" 或 "assistant"
  781. Content string `json:"content"`
  782. MessageId string `json:"messageId,omitempty"`
  783. }
  784. // LoadSession 加载已有会话(恢复历史对话上下文),返回回放的消息列表
  785. func (c *Client) LoadSession(sessionId string, cwd string) (string, string, string, string, []ReplayMessage, error) {
  786. params := map[string]any{
  787. "sessionId": sessionId,
  788. "cwd": cwd,
  789. "mcpServers": []any{},
  790. }
  791. // 回放消息在 session/load 响应之前以通知形式到达。用一个临时 sink 收集它们,
  792. // 由读循环在等待响应期间调用(sendAndWaitWithSink 会在返回前恢复原 sink)。
  793. var replayMessages []ReplayMessage
  794. currentMsg := &ReplayMessage{}
  795. sink := func(method string, p json.RawMessage) {
  796. if method != "session/update" && method != "_kiro.dev/session/update" {
  797. // 非回放通知(metadata 等)走默认处理,避免丢失用量
  798. c.defaultNotify(method, p)
  799. return
  800. }
  801. var notif SessionUpdate
  802. if err := json.Unmarshal(p, &notif); err != nil {
  803. return
  804. }
  805. var base UpdateBase
  806. if err := json.Unmarshal(notif.Update, &base); err != nil {
  807. return
  808. }
  809. switch base.SessionUpdate {
  810. case "user_message_chunk":
  811. if currentMsg.Content != "" {
  812. replayMessages = append(replayMessages, *currentMsg)
  813. }
  814. var chunk AgentMessageChunk
  815. json.Unmarshal(notif.Update, &chunk)
  816. currentMsg = &ReplayMessage{Role: "user", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  817. case "agent_message_chunk":
  818. var chunk AgentMessageChunk
  819. json.Unmarshal(notif.Update, &chunk)
  820. if currentMsg.Role != "assistant" || (chunk.MessageId != "" && chunk.MessageId != currentMsg.MessageId) {
  821. if currentMsg.Content != "" {
  822. replayMessages = append(replayMessages, *currentMsg)
  823. }
  824. currentMsg = &ReplayMessage{Role: "assistant", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  825. } else {
  826. currentMsg.Content += chunk.Content.Text
  827. }
  828. case "agent_thought_chunk", "tool_call", "tool_call_update", "tool_call_chunk":
  829. // 思考内容与工具调用回放忽略
  830. default:
  831. // 其他类型忽略
  832. }
  833. }
  834. result, err := c.sendAndWaitWithSink("session/load", params, sink)
  835. if err != nil {
  836. return "", "", "", "", nil, fmt.Errorf("session/load 错误: %w", err)
  837. }
  838. c.sessionId = sessionId
  839. // 追加最后一条未关闭的消息
  840. if currentMsg.Content != "" {
  841. replayMessages = append(replayMessages, *currentMsg)
  842. }
  843. // 解析响应中的 welcomeMessage、实际生效的 agentId 和 modelId
  844. var welcomeMsg string
  845. var agentId string
  846. var modelId string
  847. if len(result) > 0 {
  848. var res NewSessionResult
  849. if err := json.Unmarshal(result, &res); err == nil {
  850. if res.Modes != nil {
  851. agentId = res.Modes.CurrentModeId
  852. for _, mode := range res.Modes.AvailableModes {
  853. if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
  854. welcomeMsg = mode.Meta.WelcomeMessage
  855. break
  856. }
  857. }
  858. }
  859. if res.Models != nil {
  860. modelId = res.Models.CurrentModelId
  861. }
  862. }
  863. }
  864. // session/load 之后的初始用量 metadata 通知由默认 sink 捕获,无需单独处理。
  865. return sessionId, welcomeMsg, agentId, modelId, replayMessages, nil
  866. }
  867. // Prompt 发送消息并流式读取事件
  868. // eventCh 会在 turn 结束或出错时关闭
  869. func (c *Client) Prompt(sessionId string, message string, eventCh chan<- Event) {
  870. // 开始一个 turn:登记事件通道与 abort 信号。退出时 endTurn 会关闭 abort、
  871. // 等待所有在途 emit 结束,再清空通道引用,之后才 close(eventCh)——
  872. // 保证权限/流式 goroutine 不会向已关闭的通道发送。
  873. c.beginTurn(eventCh)
  874. defer func() {
  875. c.endTurn()
  876. close(eventCh)
  877. }()
  878. params := PromptParams{
  879. SessionId: sessionId,
  880. Prompt: []ContentBlock{
  881. {Type: "text", Text: message},
  882. },
  883. }
  884. // 注册等待 prompt 响应
  885. promptId := atomic.AddInt64(&c.requestId, 1)
  886. respCh := c.registerPending(promptId)
  887. // 清空上一轮可能残留的累积文本,避免跨 turn 串味
  888. c.resetText()
  889. // 安装 sink:turn 期间的 session/update 与扩展通知都转成 Event 推给 eventCh。
  890. // 反向请求(requestPermission)由读循环单独派发,不经过 sink。
  891. old := c.setSink(func(method string, p json.RawMessage) {
  892. switch method {
  893. case "session/update", "_kiro.dev/session/update":
  894. c.handleSessionUpdate(p)
  895. default:
  896. c.handleExtensionNotification(method, p)
  897. }
  898. })
  899. defer c.setSink(old)
  900. if err := c.writeRequest(promptId, "session/prompt", params); err != nil {
  901. c.unregisterPending(promptId)
  902. c.emit(Event{Type: "error", Content: fmt.Sprintf("发送 prompt 失败: %v", err)})
  903. return
  904. }
  905. zap.L().Debug("[ACP] prompt 已发送", zap.Int64("promptId", promptId), zap.String("sessionId", sessionId))
  906. // 等待 prompt 响应(turn 结束)或进程退出
  907. select {
  908. case resp := <-respCh:
  909. if full := c.takeText(); full != "" {
  910. zap.L().Info("[ACP] AI 回复完整内容", zap.String("text", full))
  911. }
  912. if resp.Error != nil {
  913. c.emit(Event{Type: "error", Content: resp.Error.FullMessage()})
  914. return
  915. }
  916. var result PromptResult
  917. if err := json.Unmarshal(resp.Result, &result); err == nil {
  918. c.emit(Event{Type: "done", SessionId: sessionId, StopReason: result.StopReason})
  919. }
  920. case <-c.doneCh:
  921. c.unregisterPending(promptId)
  922. c.emit(Event{Type: "error", Content: "Agent 进程已断开"})
  923. }
  924. }
  925. // Cancel 取消当前 prompt turn(ACP 协议:Notification, method="session/cancel", params={sessionId})
  926. func (c *Client) Cancel(sessionId string) {
  927. c.sendNotification("session/cancel", CancelParams{SessionId: sessionId})
  928. }
  929. // GetSessionId 获取当前会话 ID
  930. func (c *Client) GetSessionId() string {
  931. return c.sessionId
  932. }
  933. // Close 关闭 ACP Client,终止 Agent 进程
  934. func (c *Client) Close() {
  935. // 关闭 stdin 通知 Agent 退出
  936. c.stdin.Close()
  937. // 终止进程(回收由 waitProcess 中的 cmd.Wait 负责,此处不再重复 Wait)
  938. if c.cmd.Process != nil {
  939. c.cmd.Process.Kill()
  940. }
  941. // 解除可能阻塞在 permissionCh 上的 goroutine
  942. select {
  943. case c.permissionCh <- "deny":
  944. default:
  945. }
  946. }
  947. // IsAlive 检查 Agent 进程是否存活。
  948. // 依赖 waitProcess/readLoop 单点设置的 dead 标记,而非 cmd.ProcessState
  949. // (后者只有在 Wait 之后才被填充,子进程自行崩溃时会误判为存活)。
  950. func (c *Client) IsAlive() bool {
  951. return !c.dead.Load()
  952. }
  953. // ============ 内部方法 ============
  954. // sendRequest 发送请求(不等待响应),返回 request ID。
  955. // 注意:调用方若需要等待响应,应使用 sendAndWait;直接使用本方法的场景
  956. // (如 Prompt)需自行通过 registerPending/读循环获取响应。
  957. func (c *Client) sendRequest(method string, params any) int64 {
  958. id := atomic.AddInt64(&c.requestId, 1)
  959. if err := c.writeRequest(id, method, params); err != nil {
  960. zap.L().Error("[ACP] 发送请求失败", zap.String("method", method), zap.Error(err))
  961. }
  962. return id
  963. }
  964. // writeRequest 序列化并写入一个带 id 的请求,写入通过 writeMu 串行化。
  965. func (c *Client) writeRequest(id int64, method string, params any) error {
  966. req := Request{
  967. JSONRPC: "2.0",
  968. ID: id,
  969. Method: method,
  970. Params: params,
  971. }
  972. data, err := json.Marshal(req)
  973. if err != nil {
  974. return fmt.Errorf("序列化请求失败: %w", err)
  975. }
  976. zap.L().Debug("[ACP] >>> 发送", zap.String("raw", string(data)))
  977. return c.writeLine(data)
  978. }
  979. // writeLine 追加换行并串行写入 stdin。
  980. func (c *Client) writeLine(data []byte) error {
  981. c.writeMu.Lock()
  982. defer c.writeMu.Unlock()
  983. data = append(data, '\n')
  984. _, err := c.stdin.Write(data)
  985. return err
  986. }
  987. // sendNotification 发送 JSON-RPC Notification(不带 id,不期望响应)
  988. func (c *Client) sendNotification(method string, params any) {
  989. notif := struct {
  990. JSONRPC string `json:"jsonrpc"`
  991. Method string `json:"method"`
  992. Params any `json:"params,omitempty"`
  993. }{
  994. JSONRPC: "2.0",
  995. Method: method,
  996. Params: params,
  997. }
  998. data, err := json.Marshal(notif)
  999. if err != nil {
  1000. zap.L().Error("[ACP] 序列化通知失败", zap.Error(err))
  1001. return
  1002. }
  1003. zap.L().Debug("[ACP] >>> 发送(notification)", zap.String("raw", string(data)))
  1004. if err := c.writeLine(data); err != nil {
  1005. zap.L().Error("[ACP] 写入 stdin 失败(通知)", zap.String("method", method), zap.Error(err))
  1006. }
  1007. }
  1008. // sendAndWait 发送请求并等待对应 ID 的响应(通过读循环分发)。
  1009. // 若传入非 nil sink,在等待期间临时接管通知(用于捕获 init/load 阶段的通知),
  1010. // 返回前恢复原 sink。
  1011. func (c *Client) sendAndWait(method string, params any) (json.RawMessage, error) {
  1012. return c.sendAndWaitWithSink(method, params, nil)
  1013. }
  1014. func (c *Client) sendAndWaitWithSink(method string, params any, sink notifySink) (json.RawMessage, error) {
  1015. id := atomic.AddInt64(&c.requestId, 1)
  1016. ch := c.registerPending(id)
  1017. if sink != nil {
  1018. old := c.setSink(sink)
  1019. defer c.setSink(old)
  1020. }
  1021. if err := c.writeRequest(id, method, params); err != nil {
  1022. c.unregisterPending(id)
  1023. return nil, err
  1024. }
  1025. select {
  1026. case resp := <-ch:
  1027. if resp.Error != nil {
  1028. return nil, fmt.Errorf("ACP 错误 [%d]: %s", resp.Error.Code, resp.Error.FullMessage())
  1029. }
  1030. return resp.Result, nil
  1031. case <-c.doneCh:
  1032. c.unregisterPending(id)
  1033. return nil, fmt.Errorf("未收到 %s 的响应(进程已退出)", method)
  1034. }
  1035. }
  1036. // handleSessionUpdate 解析并分发 session/update 通知
  1037. func (c *Client) handleSessionUpdate(params json.RawMessage) {
  1038. var notif SessionUpdate
  1039. if err := json.Unmarshal(params, &notif); err != nil {
  1040. return
  1041. }
  1042. // 解析 update 的类型
  1043. var base UpdateBase
  1044. if err := json.Unmarshal(notif.Update, &base); err != nil {
  1045. return
  1046. }
  1047. // zap.L().Debug("[ACP] session/update", zap.String("type", base.SessionUpdate))
  1048. switch base.SessionUpdate {
  1049. case "agent_message_chunk":
  1050. var chunk AgentMessageChunk
  1051. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  1052. c.appendText(chunk.Content.Text)
  1053. c.emit(Event{Type: "text", Content: chunk.Content.Text})
  1054. }
  1055. case "agent_thought_chunk":
  1056. var chunk AgentMessageChunk
  1057. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  1058. c.appendText(chunk.Content.Text)
  1059. c.emit(Event{Type: "text", Content: chunk.Content.Text})
  1060. }
  1061. case "tool_call":
  1062. var tc ToolCall
  1063. if err := json.Unmarshal(notif.Update, &tc); err == nil {
  1064. zap.L().Debug("[ACP] → tool_call", zap.String("title", tc.Title), zap.String("status", tc.Status), zap.String("id", tc.ToolCallId))
  1065. c.emit(Event{Type: "tool_call", ToolCallId: tc.ToolCallId, ToolName: tc.Title, ToolStatus: tc.Status})
  1066. }
  1067. case "tool_call_update":
  1068. var tcu ToolCallUpdate
  1069. if err := json.Unmarshal(notif.Update, &tcu); err == nil {
  1070. zap.L().Debug("[ACP] → tool_call_update", zap.String("title", tcu.Title), zap.String("status", tcu.Status), zap.String("id", tcu.ToolCallId))
  1071. c.emit(Event{Type: "tool_update", ToolCallId: tcu.ToolCallId, ToolName: tcu.Title, ToolStatus: tcu.Status})
  1072. }
  1073. case "tool_call_chunk":
  1074. // 工具调用的流式参数输出(Kiro 扩展)
  1075. zap.L().Debug("[ACP] → tool_call_chunk (ignored)")
  1076. case "plan":
  1077. zap.L().Debug("[ACP] → plan update")
  1078. default:
  1079. zap.L().Debug("[ACP] → 未知 session/update 类型", zap.String("type", base.SessionUpdate))
  1080. }
  1081. }
  1082. // handleExtensionNotification 处理 Kiro 扩展通知
  1083. func (c *Client) handleExtensionNotification(method string, params json.RawMessage) {
  1084. switch method {
  1085. case "_kiro.dev/commands/available":
  1086. // 可用命令和工具列表
  1087. var data struct {
  1088. SessionId string `json:"sessionId"`
  1089. Commands []struct {
  1090. Name string `json:"name"`
  1091. Description string `json:"description"`
  1092. } `json:"commands"`
  1093. Tools []struct {
  1094. Name string `json:"name"`
  1095. Description string `json:"description"`
  1096. Source string `json:"source"`
  1097. } `json:"tools"`
  1098. }
  1099. if err := json.Unmarshal(params, &data); err == nil {
  1100. zap.L().Debug("[ACP] 可用命令/工具更新",
  1101. zap.Int("commands", len(data.Commands)),
  1102. zap.Int("tools", len(data.Tools)),
  1103. )
  1104. // 推送工具列表给前端(可选)
  1105. if len(data.Tools) > 0 {
  1106. toolNames := make([]string, 0, len(data.Tools))
  1107. for _, t := range data.Tools {
  1108. toolNames = append(toolNames, t.Name)
  1109. }
  1110. c.emit(Event{Type: "tools_available", Content: toJSONStr(toolNames)})
  1111. }
  1112. }
  1113. case "_kiro.dev/metadata":
  1114. // 上下文使用量和耗时
  1115. var data struct {
  1116. SessionId string `json:"sessionId"`
  1117. ContextUsagePercentage float64 `json:"contextUsagePercentage"`
  1118. TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
  1119. }
  1120. if err := json.Unmarshal(params, &data); err == nil {
  1121. zap.L().Debug("[ACP] metadata",
  1122. zap.Float64("contextUsage%", data.ContextUsagePercentage),
  1123. zap.Int64("turnMs", data.TurnDurationMs),
  1124. )
  1125. // 缓存最近用量/耗时,供首屏读取
  1126. c.setMeta(data.ContextUsagePercentage, data.TurnDurationMs)
  1127. // 上下文用量每条通知都会带(turnDurationMs 仅 turn 结束那条才有),
  1128. // 因此总是转发用量,耗时有值时才附带
  1129. content := map[string]any{
  1130. "contextUsagePercent": data.ContextUsagePercentage,
  1131. }
  1132. if data.TurnDurationMs > 0 {
  1133. content["turnDurationMs"] = data.TurnDurationMs
  1134. }
  1135. c.emit(Event{
  1136. Type: "metadata",
  1137. Content: toJSONStr(content),
  1138. })
  1139. }
  1140. case "_kiro.dev/subagent/list_update":
  1141. // 子代理列表更新
  1142. zap.L().Debug("[ACP] 子代理列表更新")
  1143. case "_kiro.dev/mcp/server_initialized":
  1144. // MCP Server 初始化完成
  1145. var data struct {
  1146. ServerName string `json:"serverName"`
  1147. }
  1148. json.Unmarshal(params, &data)
  1149. zap.L().Info("[ACP] MCP Server 初始化完成", zap.String("server", data.ServerName))
  1150. case "_kiro.dev/compaction/status":
  1151. // 上下文压缩状态
  1152. zap.L().Debug("[ACP] 上下文压缩中")
  1153. case "_kiro.dev/clear/status":
  1154. // 会话清除状态
  1155. zap.L().Debug("[ACP] 会话清除中")
  1156. default:
  1157. zap.L().Debug("[ACP] 未处理的扩展通知", zap.String("method", method))
  1158. }
  1159. }
  1160. // toJSONStr 序列化为 JSON 字符串
  1161. func toJSONStr(v any) string {
  1162. data, _ := json.Marshal(v)
  1163. return string(data)
  1164. }
  1165. // applyLineWindow 按 ACP readTextFile 语义对文本做行窗口截取。
  1166. // - line 为 1 基起始行号(nil 或 <=0 视为从第 1 行开始);
  1167. // - limit 为返回的最大行数(nil 或 <=0 视为不限制,返回到文件末尾)。
  1168. //
  1169. // 保留行间换行;起始行越界时返回空串。用于避免把整份大文件回传给 Agent。
  1170. func applyLineWindow(content string, line, limit *int) string {
  1171. start := 1
  1172. if line != nil && *line > 0 {
  1173. start = *line
  1174. }
  1175. // 无窗口需求:整文件返回
  1176. if start <= 1 && (limit == nil || *limit <= 0) {
  1177. return content
  1178. }
  1179. lines := strings.Split(content, "\n")
  1180. if start > len(lines) {
  1181. return ""
  1182. }
  1183. from := start - 1 // 转 0 基
  1184. to := len(lines)
  1185. if limit != nil && *limit > 0 {
  1186. if end := from + *limit; end < to {
  1187. to = end
  1188. }
  1189. }
  1190. return strings.Join(lines[from:to], "\n")
  1191. }
  1192. // handleAgentRequest 处理 Agent→Client 的反向请求。
  1193. // 该方法由读循环在独立 goroutine 中调用,因此权限等待不会阻塞读循环;
  1194. // stdin 写入通过 writeLine(writeMu)串行化,不与 Cancel 争锁。
  1195. func (c *Client) handleAgentRequest(rawId json.RawMessage, method string, rawParams json.RawMessage) {
  1196. zap.L().Debug("[ACP] 处理反向请求", zap.String("method", method), zap.String("id", string(rawId)), zap.String("params", string(rawParams[:min(len(rawParams), 200)])))
  1197. var respData []byte
  1198. switch method {
  1199. case "readTextFile":
  1200. // 读取文件:先从 FileStore 查找(MinIO),再从本地文件系统
  1201. var params struct {
  1202. Path string `json:"path"`
  1203. Line *int `json:"line,omitempty"`
  1204. Limit *int `json:"limit,omitempty"`
  1205. }
  1206. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  1207. var content string
  1208. var readErr error
  1209. // 优先从 FileStore(MinIO)读取:先按路径解析出 object key
  1210. key, keyErr := filestore.FindKeyByPath("upload", c.UserId, c.sessionId, params.Path)
  1211. if keyErr == nil && key != "" {
  1212. content, readErr = filestore.ReadFile(key)
  1213. zap.L().Debug("[ACP] readTextFile 从 MinIO 读取", zap.String("path", params.Path), zap.String("key", key))
  1214. } else {
  1215. // 回退到本地文件系统
  1216. data, err := os.ReadFile(params.Path)
  1217. if err == nil {
  1218. content = string(data)
  1219. zap.L().Debug("[ACP] readTextFile 从本地读取", zap.String("path", params.Path))
  1220. } else {
  1221. readErr = err
  1222. }
  1223. }
  1224. if readErr == nil {
  1225. windowed := applyLineWindow(content, params.Line, params.Limit)
  1226. result, _ := json.Marshal(map[string]string{"content": windowed})
  1227. respData, _ = json.Marshal(map[string]any{
  1228. "jsonrpc": "2.0",
  1229. "id": rawId,
  1230. "result": json.RawMessage(result),
  1231. })
  1232. }
  1233. }
  1234. if respData == nil {
  1235. respData, _ = json.Marshal(map[string]any{
  1236. "jsonrpc": "2.0",
  1237. "id": rawId,
  1238. "error": map[string]any{"code": -1, "message": "file not found"},
  1239. })
  1240. }
  1241. case "writeTextFile":
  1242. // 写入文件到 MinIO(不写本地)
  1243. var params struct {
  1244. Path string `json:"path"`
  1245. Content string `json:"content"`
  1246. }
  1247. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  1248. record, err := filestore.UploadFile(c.UserId, c.sessionId, filepath.Base(params.Path), params.Content)
  1249. if err == nil {
  1250. respData, _ = json.Marshal(map[string]any{
  1251. "jsonrpc": "2.0",
  1252. "id": rawId,
  1253. "result": map[string]any{},
  1254. })
  1255. _ = record // 文件下载通过前端标记检测实现,不再推送事件
  1256. } else {
  1257. zap.L().Error("[ACP] writeTextFile MinIO上传失败", zap.Error(err))
  1258. }
  1259. }
  1260. if respData == nil {
  1261. respData, _ = json.Marshal(map[string]any{
  1262. "jsonrpc": "2.0",
  1263. "id": rawId,
  1264. "error": map[string]any{"code": -1, "message": "write failed"},
  1265. })
  1266. }
  1267. case "requestPermission", "session/request_permission":
  1268. // 解析权限请求参数
  1269. var params struct {
  1270. ToolCall *struct {
  1271. Title string `json:"title"`
  1272. } `json:"toolCall"`
  1273. Options []struct {
  1274. OptionId string `json:"optionId"`
  1275. Name string `json:"name"`
  1276. Kind string `json:"kind"`
  1277. } `json:"options"`
  1278. }
  1279. json.Unmarshal(rawParams, &params)
  1280. title := ""
  1281. if params.ToolCall != nil {
  1282. title = params.ToolCall.Title
  1283. }
  1284. // 当前策略:所有权限请求都推给前端授权。
  1285. options := make([]PermissionOption, 0, len(params.Options))
  1286. for _, opt := range params.Options {
  1287. options = append(options, PermissionOption{
  1288. OptionId: opt.OptionId,
  1289. Name: opt.Name,
  1290. Kind: opt.Kind,
  1291. })
  1292. }
  1293. requestId := fmt.Sprintf("perm_%s", string(rawId))
  1294. pushed := c.pushPermissionEvent(Event{
  1295. Type: "permission",
  1296. Permission: &PermissionEvent{
  1297. RequestId: requestId,
  1298. Title: title,
  1299. Options: options,
  1300. },
  1301. })
  1302. if pushed {
  1303. // 丢弃可能残留的旧授权结果,避免消费到上一次请求/Close 写入的值
  1304. select {
  1305. case <-c.permissionCh:
  1306. default:
  1307. }
  1308. // 有界等待前端回复:超时 / 进程退出 时按 deny 处理,避免永久阻塞。
  1309. // 本 goroutine 阻塞不影响读循环(读循环在另一 goroutine)。
  1310. selectedOptionId := c.selectDenyOption(params.Options)
  1311. timer := time.NewTimer(PermissionWaitTimeout)
  1312. select {
  1313. case selectedOptionId = <-c.permissionCh:
  1314. case <-timer.C:
  1315. zap.L().Warn("[ACP] 权限等待超时,默认拒绝", zap.String("title", title))
  1316. case <-c.doneCh:
  1317. zap.L().Warn("[ACP] 权限等待期间进程已退出", zap.String("title", title))
  1318. }
  1319. timer.Stop()
  1320. respData, _ = json.Marshal(map[string]any{
  1321. "jsonrpc": "2.0",
  1322. "id": rawId,
  1323. "result": map[string]any{
  1324. "outcome": map[string]any{
  1325. "outcome": "selected",
  1326. "optionId": selectedOptionId,
  1327. },
  1328. },
  1329. })
  1330. } else {
  1331. // 无事件通道(非 prompt 阶段):自动允许
  1332. respData, _ = json.Marshal(map[string]any{
  1333. "jsonrpc": "2.0",
  1334. "id": rawId,
  1335. "result": map[string]any{
  1336. "outcome": map[string]any{
  1337. "outcome": "selected",
  1338. "optionId": "allow_once",
  1339. },
  1340. },
  1341. })
  1342. }
  1343. default:
  1344. // 未知请求返回错误
  1345. respData, _ = json.Marshal(map[string]any{
  1346. "jsonrpc": "2.0",
  1347. "id": rawId,
  1348. "error": map[string]any{"code": -32601, "message": "method not supported"},
  1349. })
  1350. }
  1351. if respData != nil {
  1352. zap.L().Debug("[ACP] 发送反向响应", zap.String("method", method), zap.String("resp", string(respData[:min(len(respData), 200)])))
  1353. if err := c.writeLine(respData); err != nil {
  1354. zap.L().Error("[ACP] 写入反向响应失败", zap.String("method", method), zap.Error(err))
  1355. }
  1356. } else {
  1357. zap.L().Warn("[ACP] 反向请求无响应数据", zap.String("method", method))
  1358. }
  1359. }
  1360. // selectDenyOption 从可选项中挑选一个 deny 语义的 optionId 作为超时/退出时的默认值。
  1361. // 找不到显式 deny 选项时回退到字面量 "deny"。
  1362. func (c *Client) selectDenyOption(options []struct {
  1363. OptionId string `json:"optionId"`
  1364. Name string `json:"name"`
  1365. Kind string `json:"kind"`
  1366. }) string {
  1367. for _, opt := range options {
  1368. if strings.Contains(strings.ToLower(opt.Kind), "deny") || strings.Contains(strings.ToLower(opt.OptionId), "deny") {
  1369. return opt.OptionId
  1370. }
  1371. }
  1372. return "deny"
  1373. }