You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
+46
-2
@@ -71,6 +71,14 @@ type Relay struct {
|
||||
log *slog.Logger
|
||||
cfg RelayConfig
|
||||
|
||||
// 回合完成检测:被动解析 session/update + session/prompt 响应的 stopReason。
|
||||
// 由 supervisor.Spawn 通过 SetTracker 注入;可为 nil(部分测试)。
|
||||
tracker *TurnTracker
|
||||
|
||||
// 成本核算:被动解析 agent→client 消息中的 token/cost 用量并累加 + 预算判定。
|
||||
// 由 supervisor.Spawn 通过 SetUsageSink 注入;可为 nil(部分测试 / 未配置)。
|
||||
usageSink usageSink
|
||||
|
||||
// 终止控制
|
||||
closed atomic.Bool
|
||||
done chan struct{}
|
||||
@@ -106,6 +114,17 @@ func NewRelay(sessionID uuid.UUID, dec *Decoder, enc *Encoder,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTracker 注入回合追踪器。由 supervisor.Spawn 在 NewRelay 之后调用。
|
||||
func (r *Relay) SetTracker(t *TurnTracker) { r.tracker = t }
|
||||
|
||||
// SetUsageSink 注入成本核算 sink。由 supervisor.Spawn 在 NewRelay 之后调用;
|
||||
// 可为 nil(部分测试 / 未配置 model 价格时由 supervisor 自行决定是否注入)。
|
||||
func (r *Relay) SetUsageSink(s usageSink) { r.usageSink = s }
|
||||
|
||||
// Tracker 返回回合追踪器(可能为 nil)。session_service / handler 在发送
|
||||
// session/prompt 前用它登记回合 id。
|
||||
func (r *Relay) Tracker() *TurnTracker { return r.tracker }
|
||||
|
||||
// Subscribe 注册一个 WS tab 订阅。
|
||||
func (r *Relay) Subscribe(userID uuid.UUID) *Subscriber {
|
||||
sub := &Subscriber{
|
||||
@@ -228,6 +247,12 @@ func (r *Relay) reader(ctx context.Context, sess handlers.SessionContext) {
|
||||
envelope := r.persistAndEnvelope(ctx, msg, "out")
|
||||
if envelope != nil {
|
||||
r.fanout(envelope)
|
||||
// 成本核算:在事件落库后用持久化的 event id 喂给 usageSink。usageSink
|
||||
// 内部仅在真实可计费回合时累加,并在预算突破时以 detached goroutine 触发
|
||||
// kill(绝不阻塞 reader)。
|
||||
if r.usageSink != nil {
|
||||
r.usageSink.Observe(ctx, msg, envelope.ID)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -238,10 +263,23 @@ func (r *Relay) reader(ctx context.Context, sess handlers.SessionContext) {
|
||||
} else {
|
||||
r.log.Warn("acp.relay.orphan_response", "session_id", r.sessionID, "id", id)
|
||||
}
|
||||
} else if sid, ok := msg.IDString(); ok && r.tracker.IsTrackedPrompt(sid) {
|
||||
// 字符串-id 的 session/prompt 响应:回合完成检测。
|
||||
// 仅当 id 匹配当前开放回合时拦截;其它字符串 id 仍走既有 fanout(上方已 fanout)。
|
||||
if msg.Error != nil {
|
||||
// JSON-RPC error 响应:本回合失败,标 aborted 并发 session_idle(reason=error),
|
||||
// 而非误记为 completed(stopReason 空)。agent 仍存活,可接受下一条 prompt。
|
||||
r.tracker.Abort(ctx, "error")
|
||||
} else {
|
||||
r.tracker.OnPromptResponse(ctx, sid, ParseStopReason(msg.Result))
|
||||
}
|
||||
}
|
||||
// String IDs go to clients via fanout (handled above); no server action.
|
||||
// 其它 String IDs go to clients via fanout (handled above); no server action.
|
||||
case msg.IsNotification():
|
||||
// Already persisted + fanout'd. No reply.
|
||||
// Already persisted + fanout'd. No reply. 回合检测:累加 update 计数。
|
||||
if msg.Method == "session/update" {
|
||||
r.tracker.OnUpdate(ctx)
|
||||
}
|
||||
case msg.IsRequest():
|
||||
go r.handleAgentRequest(ctx, sess, msg)
|
||||
}
|
||||
@@ -272,6 +310,12 @@ func (r *Relay) handleAgentRequest(ctx context.Context, sess handlers.SessionCon
|
||||
case "fs/write_text_file":
|
||||
out := r.fsHandler.Write.Handle(ctx, sess, req.Params)
|
||||
resp = r.fsResultToResponse(req.ID, out)
|
||||
case "fs/list":
|
||||
out := r.fsHandler.List.Handle(ctx, sess, req.Params)
|
||||
resp = r.fsResultToResponse(req.ID, out)
|
||||
case "fs/grep":
|
||||
out := r.fsHandler.Grep.Handle(ctx, sess, req.Params)
|
||||
resp = r.fsResultToResponse(req.ID, out)
|
||||
case "session/request_permission":
|
||||
out := r.permHandler.Handle(ctx, sess, string(req.ID), req.Params)
|
||||
resp = r.fsResultToResponse(req.ID, out)
|
||||
|
||||
Reference in New Issue
Block a user