- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留 - 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
92 lines
3.1 KiB
Go
92 lines
3.1 KiB
Go
package orchestration
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/orchestration/audit"
|
||
)
|
||
|
||
// AuditMode 选择子代理上下文为 subagent_ask 上游 WS 路由裁剪成什么形状。它与
|
||
// audit.ContextAuditor 的表面(Full/Tail/Keyword)一一对应,使网关能按客户端所请求
|
||
// 的窗口精确取用。
|
||
type AuditMode string
|
||
|
||
const (
|
||
// AuditFull 返回整段会话上下文。
|
||
AuditFull AuditMode = "full"
|
||
// AuditTail 返回上下文最后 n 条消息。
|
||
AuditTail AuditMode = "tail"
|
||
// AuditKeyword 返回首个关键词命中处附近的窗口。
|
||
AuditKeyword AuditMode = "keyword"
|
||
)
|
||
|
||
// AuditResult 是审计一个子代理会话上下文的只读结果。Messages 承载被选中的窗口;
|
||
// Line 与 Match 仅在关键词查询时填充。它是 ws.subagent_event 应答的线上载荷。
|
||
type AuditResult struct {
|
||
AgentID string `json:"agentId"`
|
||
Status Status `json:"status"`
|
||
Goal string `json:"goal,omitempty"`
|
||
Mode AuditMode `json:"mode"`
|
||
Messages []model.Message `json:"messages,omitempty"`
|
||
Line int `json:"line,omitempty"`
|
||
Match string `json:"match,omitempty"`
|
||
}
|
||
|
||
// AuditContext 返回子代理 id 的会话上下文只读视图,供 subagent_ask WS 路由使用。
|
||
// mode 选择窗口:AuditFull 返回整段上下文,AuditTail 返回最后 n 条消息,
|
||
// AuditKeyword 返回首个 query 命中处附近的窗口。未知 id 包装 ErrAgentNotFound,
|
||
// 关键词查询为空则包装 audit.ErrQueryEmpty,两者都以 %w 包装,便于调用方用
|
||
// errors.Is 归类。
|
||
func (s *Scheduler) AuditContext(ctx context.Context, id string, mode AuditMode, n int, query string) (*AuditResult, error) {
|
||
if err := ctx.Err(); err != nil {
|
||
return nil, fmt.Errorf("orchestration: audit %q: %w", id, err)
|
||
}
|
||
s.mu.Lock()
|
||
sub := s.agents[id]
|
||
s.mu.Unlock()
|
||
if sub == nil {
|
||
return nil, fmt.Errorf("orchestration: audit %q: %w", id, ErrAgentNotFound)
|
||
}
|
||
|
||
a := audit.NewContextAuditor(sub.Context)
|
||
res := &AuditResult{AgentID: sub.ID, Status: sub.Status(), Goal: sub.Goal, Mode: mode}
|
||
switch mode {
|
||
case AuditFull:
|
||
res.Messages = a.Full()
|
||
case AuditTail:
|
||
res.Messages = a.Tail(n)
|
||
case AuditKeyword:
|
||
loc, err := a.Keyword(ctx, query)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("orchestration: audit %q: %w", id, err)
|
||
}
|
||
if loc == nil {
|
||
res.Messages = []model.Message{}
|
||
break
|
||
}
|
||
res.Line = loc.Line
|
||
res.Messages = loc.Surrounding
|
||
res.Match = messageText(loc.Surrounding[len(loc.Surrounding)-1])
|
||
default:
|
||
return nil, fmt.Errorf("orchestration: audit %q: unknown mode %q", id, mode)
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
// messageText 拼接一条消息的各个文本分段。它与 audit 子包自己的同名辅助函数保持一致,
|
||
// 使结果载荷能给出人类可读的命中文本,而无需导出跨包的文本提取器。
|
||
func messageText(m model.Message) string {
|
||
var text string
|
||
for _, c := range m.Content {
|
||
if c.Type == model.ContentTypeText && c.Text != "" {
|
||
if text != "" {
|
||
text += " "
|
||
}
|
||
text += c.Text
|
||
}
|
||
}
|
||
return text
|
||
}
|