|
@@ -1,6 +1,7 @@
|
|
|
package services
|
|
package services
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "context"
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"fwh-toolbox-server/repositories"
|
|
"fwh-toolbox-server/repositories"
|
|
@@ -330,8 +331,11 @@ func (m *ACPManager) ResolvePermission(userId string, optionId string) error {
|
|
|
|
|
|
|
|
// ============ 消息发送 ============
|
|
// ============ 消息发送 ============
|
|
|
|
|
|
|
|
-// Prompt 发送消息并流式读取响应
|
|
|
|
|
-func (m *ACPManager) Prompt(sess *ACPSession, message string, eventCh chan<- AIChatEvent) {
|
|
|
|
|
|
|
+// Prompt 发送消息并流式读取响应。
|
|
|
|
|
+// ctx 由上层(HTTP 处理器)在连接断开时取消:一旦取消,立即通知 Agent 取消本轮
|
|
|
|
|
+// (Client.Cancel)、唤醒 acp 层阻塞的事件发送(Client.AbortEvents),并停止向
|
|
|
|
|
+// eventCh 转发,避免 goroutine 因客户端断开而永久阻塞。
|
|
|
|
|
+func (m *ACPManager) Prompt(ctx context.Context, sess *ACPSession, message string, eventCh chan<- AIChatEvent) {
|
|
|
sess.Mutex.Lock()
|
|
sess.Mutex.Lock()
|
|
|
defer sess.Mutex.Unlock()
|
|
defer sess.Mutex.Unlock()
|
|
|
|
|
|
|
@@ -353,19 +357,37 @@ func (m *ACPManager) Prompt(sess *ACPSession, message string, eventCh chan<- AIC
|
|
|
acpEvents := make(chan acp.Event, 50)
|
|
acpEvents := make(chan acp.Event, 50)
|
|
|
go sess.Client.Prompt(sess.SessionId, contextMessage, acpEvents)
|
|
go sess.Client.Prompt(sess.SessionId, contextMessage, acpEvents)
|
|
|
|
|
|
|
|
|
|
+ // 监听上层取消:断开时通知 Agent 取消并解除 acp 层发送阻塞
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ <-ctx.Done()
|
|
|
|
|
+ sess.Client.Cancel(sess.SessionId)
|
|
|
|
|
+ sess.Client.AbortEvents()
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ // 转发单条事件,支持在上层取消时中断,避免阻塞在满通道上
|
|
|
|
|
+ send := func(ev AIChatEvent) bool {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case eventCh <- ev:
|
|
|
|
|
+ return true
|
|
|
|
|
+ case <-ctx.Done():
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 转换 acp.Event → AIChatEvent
|
|
// 转换 acp.Event → AIChatEvent
|
|
|
for event := range acpEvents {
|
|
for event := range acpEvents {
|
|
|
|
|
+ var ok bool
|
|
|
switch event.Type {
|
|
switch event.Type {
|
|
|
case "text":
|
|
case "text":
|
|
|
- eventCh <- AIChatEvent{Type: "text", Content: event.Content}
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{Type: "text", Content: event.Content})
|
|
|
case "tool_call":
|
|
case "tool_call":
|
|
|
- eventCh <- AIChatEvent{
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{
|
|
|
Type: "tool_call",
|
|
Type: "tool_call",
|
|
|
ToolCallId: event.ToolCallId,
|
|
ToolCallId: event.ToolCallId,
|
|
|
ToolName: event.ToolName,
|
|
ToolName: event.ToolName,
|
|
|
Description: acp.DescribeTool(event.ToolName),
|
|
Description: acp.DescribeTool(event.ToolName),
|
|
|
Status: acp.NormalizeStatus(event.ToolStatus),
|
|
Status: acp.NormalizeStatus(event.ToolStatus),
|
|
|
- }
|
|
|
|
|
|
|
+ })
|
|
|
case "tool_update":
|
|
case "tool_update":
|
|
|
// update 一般只变更状态;若本次携带了新 title 才重新映射简述
|
|
// update 一般只变更状态;若本次携带了新 title 才重新映射简述
|
|
|
ev := AIChatEvent{
|
|
ev := AIChatEvent{
|
|
@@ -377,15 +399,24 @@ func (m *ACPManager) Prompt(sess *ACPSession, message string, eventCh chan<- AIC
|
|
|
ev.ToolName = event.ToolName
|
|
ev.ToolName = event.ToolName
|
|
|
ev.Description = acp.DescribeTool(event.ToolName)
|
|
ev.Description = acp.DescribeTool(event.ToolName)
|
|
|
}
|
|
}
|
|
|
- eventCh <- ev
|
|
|
|
|
|
|
+ ok = send(ev)
|
|
|
case "permission":
|
|
case "permission":
|
|
|
- eventCh <- AIChatEvent{Type: "permission", Content: toJSON(event.Permission)}
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{Type: "permission", Content: toJSON(event.Permission)})
|
|
|
case "metadata":
|
|
case "metadata":
|
|
|
- eventCh <- AIChatEvent{Type: "metadata", Content: event.Content}
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{Type: "metadata", Content: event.Content})
|
|
|
case "done":
|
|
case "done":
|
|
|
- eventCh <- AIChatEvent{Type: "done", SessionId: event.SessionId, StopReason: event.StopReason}
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{Type: "done", SessionId: event.SessionId, StopReason: event.StopReason})
|
|
|
case "error":
|
|
case "error":
|
|
|
- eventCh <- AIChatEvent{Type: "error", Content: event.Content}
|
|
|
|
|
|
|
+ ok = send(AIChatEvent{Type: "error", Content: event.Content})
|
|
|
|
|
+ default:
|
|
|
|
|
+ ok = true
|
|
|
|
|
+ }
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ // 上层已取消:继续排空 acpEvents 直至 acp 层因 abort/取消关闭它,
|
|
|
|
|
+ // 但不再向 eventCh 转发,防止阻塞。
|
|
|
|
|
+ for range acpEvents {
|
|
|
|
|
+ }
|
|
|
|
|
+ break
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|