Add OAuth provider runtime and providers UI

This commit is contained in:
lpf
2026-03-11 15:47:49 +08:00
parent d9872c3da7
commit 1c0e463d07
52 changed files with 9772 additions and 901 deletions

View File

@@ -22,6 +22,7 @@ import (
type plannedTask struct {
Index int
Total int
Content string
ResourceKeys []string
}
@@ -82,6 +83,7 @@ func (al *AgentLoop) planSessionTasks(ctx context.Context, msg bus.InboundMessag
}
out = append(out, plannedTask{
Index: i + 1,
Total: 0,
Content: content,
ResourceKeys: scheduling.DeriveResourceKeys(content),
})
@@ -93,6 +95,9 @@ func (al *AgentLoop) planSessionTasks(ctx context.Context, msg bus.InboundMessag
out[0].Content = base
out[0].ResourceKeys = scheduling.DeriveResourceKeys(base)
}
for i := range out {
out[i].Total = len(out)
}
return out
}
@@ -254,6 +259,7 @@ func splitPlannedSegments(content string) []string {
func (al *AgentLoop) runPlannedTasks(ctx context.Context, msg bus.InboundMessage, tasks []plannedTask) (string, error) {
results := make([]plannedTaskResult, len(tasks))
enrichedContent := al.enrichPlannedTaskContents(ctx, tasks)
var wg sync.WaitGroup
var progressMu sync.Mutex
completed := 0
@@ -265,7 +271,8 @@ func (al *AgentLoop) runPlannedTasks(ctx context.Context, msg bus.InboundMessage
go func(index int, t plannedTask) {
defer wg.Done()
subMsg := msg
subMsg.Content = al.enrichTaskContentWithMemoryAndEKG(ctx, t)
enriched := enrichedContent[index]
subMsg.Content = enriched.content
subMsg.Metadata = cloneMetadata(msg.Metadata)
if subMsg.Metadata == nil {
subMsg.Metadata = map[string]string{}
@@ -273,6 +280,15 @@ func (al *AgentLoop) runPlannedTasks(ctx context.Context, msg bus.InboundMessage
subMsg.Metadata["resource_keys"] = strings.Join(t.ResourceKeys, ",")
subMsg.Metadata["planned_task_index"] = fmt.Sprintf("%d", t.Index)
subMsg.Metadata["planned_task_total"] = fmt.Sprintf("%d", len(tasks))
if enriched.extraChars > 0 {
subMsg.Metadata["context_extra_chars"] = fmt.Sprintf("%d", enriched.extraChars)
}
if enriched.ekgChars > 0 {
subMsg.Metadata["context_ekg_chars"] = fmt.Sprintf("%d", enriched.ekgChars)
}
if enriched.memoryChars > 0 {
subMsg.Metadata["context_memory_chars"] = fmt.Sprintf("%d", enriched.memoryChars)
}
out, err := al.processMessage(ctx, subMsg)
res := plannedTaskResult{Index: index, Task: t, Output: strings.TrimSpace(out), Err: err}
if err != nil {
@@ -434,35 +450,70 @@ func summarizePlannedTaskProgressBody(body string, maxLines, maxChars int) strin
return joined
}
func (al *AgentLoop) enrichTaskContentWithMemoryAndEKG(ctx context.Context, task plannedTask) string {
base := strings.TrimSpace(task.Content)
if base == "" {
return base
}
hints := make([]string, 0, 2)
if mem := al.memoryHintForTask(ctx, task); mem != "" {
hints = append(hints, "Memory:\n"+mem)
}
if risk := al.ekgHintForTask(task); risk != "" {
hints = append(hints, "EKG:\n"+risk)
}
if len(hints) == 0 {
return base
}
return strings.TrimSpace(
"Task Context (use it as constraints, avoid repeating known failures):\n" +
strings.Join(hints, "\n\n") +
"\n\nTask:\n" + base,
)
type taskPromptHints struct {
ekg string
memory string
}
func (al *AgentLoop) memoryHintForTask(ctx context.Context, task plannedTask) string {
type plannedTaskPrompt struct {
content string
extraChars int
ekgChars int
memoryChars int
}
func (al *AgentLoop) enrichPlannedTaskContents(ctx context.Context, tasks []plannedTask) []plannedTaskPrompt {
out := make([]plannedTaskPrompt, len(tasks))
seen := make(map[string]struct{}, len(tasks)*2)
remainingBudget := plannedTaskContextBudget(len(tasks))
for i, task := range tasks {
hints := al.collectTaskPromptHints(ctx, task)
if len(tasks) > 1 {
dedupeTaskPromptHints(&hints, seen)
applyPromptBudget(&hints, &remainingBudget)
}
prompt := buildPlannedTaskPrompt(task.Content, hints)
if len(tasks) > 1 && prompt.extraChars > 0 {
remainingBudget -= prompt.extraChars
if remainingBudget < 0 {
remainingBudget = 0
}
}
out[i] = prompt
}
return out
}
func (al *AgentLoop) enrichTaskContentWithMemoryAndEKG(ctx context.Context, task plannedTask) string {
return buildPlannedTaskPrompt(task.Content, al.collectTaskPromptHints(ctx, task)).content
}
func (al *AgentLoop) collectTaskPromptHints(ctx context.Context, task plannedTask) taskPromptHints {
hints := taskPromptHints{}
if risk := al.ekgHintForTask(task); risk != "" {
hints.ekg = risk
hints.memory = al.memoryHintForTask(ctx, task, true)
return hints
}
hints.memory = al.memoryHintForTask(ctx, task, false)
return hints
}
func (al *AgentLoop) memoryHintForTask(ctx context.Context, task plannedTask, hasEKG bool) string {
if al == nil || al.tools == nil {
return ""
}
maxResults := 1
maxChars := 360
if task.Total > 1 {
maxChars = 220
}
if hasEKG {
maxChars = 160
}
args := map[string]interface{}{
"query": task.Content,
"maxResults": 2,
"maxResults": maxResults,
}
if ns := memoryNamespaceFromContext(ctx); ns != "main" {
args["namespace"] = ns
@@ -475,7 +526,7 @@ func (al *AgentLoop) memoryHintForTask(ctx context.Context, task plannedTask) st
if txt == "" || strings.HasPrefix(strings.ToLower(txt), "no memory found") {
return ""
}
return truncate(txt, 1200)
return compactMemoryHint(txt, maxChars)
}
func (al *AgentLoop) ekgHintForTask(task plannedTask) string {
@@ -499,20 +550,27 @@ func (al *AgentLoop) ekgHintForTask(task plannedTask) string {
if !advice.ShouldEscalate {
return ""
}
reasons := strings.Join(advice.Reason, ", ")
if strings.TrimSpace(reasons) == "" {
reasons = "repeated error signature"
parts := []string{
fmt.Sprintf("repeat_errsig=%s", truncate(errSig, 72)),
fmt.Sprintf("backoff=%ds", advice.RetryBackoffSec),
}
return fmt.Sprintf("Related repeated error signature detected (%s). Suggested retry backoff: %ds. Last error: %s",
errSig, advice.RetryBackoffSec, truncate(strings.TrimSpace(evt.Log), 240))
if evt.Preview != "" {
parts = append(parts, "related_task="+truncate(strings.TrimSpace(evt.Preview), 96))
}
if len(advice.Reason) > 0 {
parts = append(parts, "reason="+truncate(strings.Join(advice.Reason, "+"), 64))
}
return strings.Join(parts, "; ")
}
type taskAuditErrorEvent struct {
TaskID string
Source string
Channel string
Log string
Preview string
TaskID string
Source string
Channel string
Log string
Preview string
MatchScore int
MatchRatio float64
}
func (al *AgentLoop) findRecentRelatedErrorEvent(taskContent string) (taskAuditErrorEvent, bool) {
@@ -529,6 +587,7 @@ func (al *AgentLoop) findRecentRelatedErrorEvent(taskContent string) (taskAuditE
}
var best taskAuditErrorEvent
bestScore := 0
bestRatio := 0.0
s := bufio.NewScanner(f)
for s.Scan() {
@@ -548,17 +607,25 @@ func (al *AgentLoop) findRecentRelatedErrorEvent(taskContent string) (taskAuditE
continue
}
preview := strings.TrimSpace(fmt.Sprintf("%v", row["input_preview"]))
score := overlapScore(kw, tokenizeTaskText(preview))
if score < 1 || score < bestScore {
previewKW := tokenizeTaskText(preview)
score := overlapScore(kw, previewKW)
ratio := overlapRatio(kw, previewKW, score)
if !isStrongTaskMatch(score, ratio) {
continue
}
if score < bestScore || (score == bestScore && ratio < bestRatio) {
continue
}
bestScore = score
bestRatio = ratio
best = taskAuditErrorEvent{
TaskID: strings.TrimSpace(fmt.Sprintf("%v", row["task_id"])),
Source: strings.TrimSpace(fmt.Sprintf("%v", row["source"])),
Channel: strings.TrimSpace(fmt.Sprintf("%v", row["channel"])),
Log: logText,
Preview: preview,
TaskID: strings.TrimSpace(fmt.Sprintf("%v", row["task_id"])),
Source: strings.TrimSpace(fmt.Sprintf("%v", row["source"])),
Channel: strings.TrimSpace(fmt.Sprintf("%v", row["channel"])),
Log: logText,
Preview: preview,
MatchScore: score,
MatchRatio: ratio,
}
}
if bestScore == 0 || strings.TrimSpace(best.TaskID) == "" {
@@ -597,6 +664,162 @@ func overlapScore(a, b []string) int {
return score
}
func overlapRatio(a, b []string, score int) float64 {
if score <= 0 || len(a) == 0 || len(b) == 0 {
return 0
}
shorter := len(a)
if len(b) < shorter {
shorter = len(b)
}
if shorter <= 0 {
return 0
}
return float64(score) / float64(shorter)
}
func isStrongTaskMatch(score int, ratio float64) bool {
if score >= 4 {
return true
}
if score < 2 {
return false
}
return ratio >= 0.35
}
func compactMemoryHint(raw string, maxChars int) string {
raw = strings.ReplaceAll(raw, "\r\n", "\n")
lines := strings.Split(raw, "\n")
parts := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
lower := strings.ToLower(line)
if strings.HasPrefix(lower, "found ") {
continue
}
if strings.HasPrefix(lower, "source: ") {
line = strings.TrimSpace(strings.TrimPrefix(line, "Source: "))
if line != "" {
parts = append(parts, "src="+line)
}
continue
}
parts = append(parts, line)
if len(parts) >= 2 {
break
}
}
if len(parts) == 0 {
return ""
}
return truncate(strings.Join(parts, " | "), maxChars)
}
func renderTaskPromptWithHints(taskContent string, hints taskPromptHints) string {
return buildPlannedTaskPrompt(taskContent, hints).content
}
func buildPlannedTaskPrompt(taskContent string, hints taskPromptHints) plannedTaskPrompt {
base := strings.TrimSpace(taskContent)
if base == "" {
return plannedTaskPrompt{}
}
if hints.ekg == "" && hints.memory == "" {
return plannedTaskPrompt{content: base}
}
lines := make([]string, 0, 4)
lines = append(lines, "Task Context:")
if hints.ekg != "" {
lines = append(lines, "EKG: "+hints.ekg)
}
if hints.memory != "" {
lines = append(lines, "Memory: "+hints.memory)
}
lines = append(lines, "Task:", base)
content := strings.Join(lines, "\n")
return plannedTaskPrompt{
content: content,
extraChars: maxInt(len(content)-len(base), 0),
ekgChars: len(hints.ekg),
memoryChars: len(hints.memory),
}
}
func plannedTaskContextBudget(taskCount int) int {
if taskCount <= 1 {
return 1 << 30
}
return 320
}
func applyPromptBudget(hints *taskPromptHints, remaining *int) {
if hints == nil || remaining == nil {
return
}
if *remaining <= 0 {
hints.ekg = ""
hints.memory = ""
return
}
needed := estimateHintChars(*hints)
if needed <= *remaining {
return
}
hints.memory = ""
needed = estimateHintChars(*hints)
if needed <= *remaining {
return
}
hints.ekg = ""
}
func estimateHintChars(hints taskPromptHints) int {
total := 0
if hints.ekg != "" {
total += len("Task Context:\nEKG: \nTask:\n") + len(hints.ekg)
}
if hints.memory != "" {
total += len("Memory: \n") + len(hints.memory)
if hints.ekg == "" {
total += len("Task Context:\nTask:\n")
}
}
return total
}
func dedupeTaskPromptHints(hints *taskPromptHints, seen map[string]struct{}) {
if hints == nil || seen == nil {
return
}
if hints.ekg != "" {
key := "ekg:" + hints.ekg
if _, ok := seen[key]; ok {
hints.ekg = ""
} else {
seen[key] = struct{}{}
}
}
if hints.memory != "" {
key := "memory:" + hints.memory
if _, ok := seen[key]; ok {
hints.memory = ""
} else {
seen[key] = struct{}{}
}
}
}
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
func cloneMetadata(m map[string]string) map[string]string {
if len(m) == 0 {
return nil