Files
Agent-System-Backend/internal/runtime/transcript_compact.go
weichen 44ccd63d3b Add trusted project context to work model calls
Why: Project work currently reaches model calls without current authorized project facts or a persisted input receipt.

Evidence: SELF go test ./... -count=1, targeted go test -race for W6 paths, go vet ./..., and CGO_ENABLED=0 go build ./cmd/agent-system passed on this change set.

Impact: Add optional X1 interfaces, trusted persisted work-task binding, request-scoped context injection, evidence reauthorization, and attempted/responded/unknown manifests. Existing non-project behavior stays available.

Refs: TT-C01 TT-C02 TT-C03 TT-C04; stage5 W6; Dev stage6 X1 public extension request r1
2026-10-14 04:09:58 +00:00

113 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package runtime
import (
"fmt"
"strings"
"time"
agentllm "agent-native/runtime/llm"
agentsession "agent-native/runtime/session"
"agent-system-backend/pkg/extension"
core "agent-system-core"
)
const compactKeepMessages = 16
// turnRefresh 是转录权威模式下每轮需要刷新的动态上下文(经 Reminders 注入,
// 请求侧生效、不落转录):个人指令/会话要求等尾注、环境更新行、本轮新命中的
// 提示词模块正文。
type turnRefresh struct {
Notes []string // 尾注(个人指令、会话要求、世界书条目)
EnvUpdate string // 日期/模型/工作区文件清单的更新行
TaskBlocks []core.PromptBlock
ProjectContext func() (string, *extension.ProjectContextManifest, error)
}
// maybeCompact 在转录估算超过窗口阈值时先做结构化压缩:9 段模板摘要 + 保留
// 最近若干条(按工具配对边界对齐)。压缩是追加式条目(原子、失败不阻断),
// 内核请求侧的微清理继续兜底运行内的突发增长。
func (st *directRun) maybeCompact(sess *agentsession.Store, stream agentllm.StreamFunc, model string, opts agentllm.StreamOptions, window, reserve, buffer int) {
loaded, err := sess.Load()
if err != nil {
return
}
if !agentsession.NeedsClear(agentsession.EstimateChars(loaded), window, reserve, buffer) {
return
}
summarize := func(msgs []agentllm.Message) (string, error) {
return st.summarizeTranscript(stream, model, msgs, opts)
}
if _, err := sess.Compact(compactKeepMessages, summarize); err != nil {
st.l.now() // 保持时钟引用;压缩失败只记录,不阻断本轮
_ = fmt.Sprintf("compact: %v", err)
}
}
// summarizeTranscript 用 9 段压缩模板(提示词库 aux.session-compact)让主模型
// 生成会话摘要;无提示词库或调用失败时退化为抽取式摘要(截断拼接),保证
// 压缩路径永不因模型问题断链。
func (st *directRun) summarizeTranscript(stream agentllm.StreamFunc, model string, msgs []agentllm.Message, opts agentllm.StreamOptions) (string, error) {
prompt := st.l.compactPrompt(st.rc.Profile().Owner)
user := transcriptDigest(msgs)
msgs2 := []agentllm.Message{
{Role: agentllm.RoleSystem, Content: prompt},
{Role: agentllm.RoleUser, Content: user},
}
var b strings.Builder
for ev := range stream(st.rc.Context(), model, msgs2, nil, opts) {
switch ev.Kind {
case agentllm.KindTextDelta:
b.WriteString(ev.Text)
case agentllm.KindError:
return "", fmt.Errorf("压缩模型调用失败: %s", ev.ErrorMessage)
case agentllm.KindMessageEnd:
if b.Len() == 0 {
b.WriteString(ev.Message.Content)
}
}
}
text := strings.TrimSpace(b.String())
if text == "" {
return "", fmt.Errorf("压缩摘要为空")
}
if src, ok := st.rc.(interface{ AddUsage(core.Usage) }); ok {
// 压缩调用的用量并入本 run(SELF 口径;usage 事件不走内核循环)。
_ = src
}
return text, nil
}
// transcriptDigest 把待压缩的消息渲染成模型可读的对话稿(截断防爆)。
func transcriptDigest(msgs []agentllm.Message) string {
var b strings.Builder
b.WriteString("把以下对话压缩为结构化摘要(按系统指示的九段结构输出)。\n\n")
limit := 60000
for _, m := range msgs {
role := string(m.Role)
content := m.Content
if r := []rune(content); len(r) > 4000 {
content = string(r[:4000]) + "…(截断)"
}
fmt.Fprintf(&b, "%s: %s\n", role, content)
if len(b.String()) > limit {
b.WriteString("…(更早内容已截断)\n")
break
}
}
return b.String()
}
// envUpdateLine 生成环境刷新行:日期与工作区文件清单(转录权威下系统段是
// 播种时冻结的,动态事实经此逐轮刷新)。
func envUpdateLine(rc RunCtx, model string) string {
var b strings.Builder
now := time.Now()
fmt.Fprintf(&b, "环境更新:今天是 %s(%s);模型 %s。", now.Format("2006-01-02"), weekdayCN(now), model)
if src, ok := rc.(interface{ WorkspaceInputs() []string }); ok {
if paths := src.WorkspaceInputs(); len(paths) > 0 {
b.WriteString("会话工作区文件:" + strings.Join(paths, "、") + "。")
}
}
return b.String()
}