Phase 0.1/1/3/6: 核心生产问题修复

Healthcheck 隔离 (Phase 0.1):
- 新增 internal/sdk/selftest.go: VirtualInstance 完全隔离自检空间
- PluginSDK.Selftest()/SelftestReset() 暴露隔离实例 (含 mutex)
- LLM 自检只读白名单 isSafeReadonlyTool 防写类工具污染生产
- 单测验证: healthcheck 后生产实例内容不变 + 无残留
- 存量清理: 删除 gotest/luatest 残留目录

GraphDB 去重 (Phase 1):
- migrateRelationUnique: 启动自动重建 relations 表加 UNIQUE 约束并去重
- Commit 改为存在性检查, 重复三元组仅刷新 confidence 不重复插入
- 3 个 dedup 单测全绿

配置时长解析 (Phase 3):
- parseDurationExtended 支持 2d/1w/3h 等人类可读单位
- GetDuration 全局生效, 防 2d 静默回退 30m

Agentcli 通知风暴治理 (Phase 6):
- 语义通知: 累积 notify_bytes(2KB) 或间隔 notify_interval(2s) 触发
- 生命周期即时通知: 启动/进程退出/EOF 立即通知
- 可配置 settings, 保留通知机制保证 agent 感知终端存在
- 运维止血: 已杀掉幽灵 PID 3716282 (bash git sparse clone 运行 16h)

Plan.md: 新增设计意图备忘(插件即App/分层记忆), 更新各 Phase 进度
This commit is contained in:
root
2026-08-12 13:51:40 +08:00
parent daa216529d
commit 80646aedad
12 changed files with 962 additions and 39 deletions

View File

@ -7,6 +7,7 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@ -695,13 +696,44 @@ func (r *ConfigRegistry) GetDuration(key string, defaultVal time.Duration) time.
if err != nil {
return defaultVal
}
d, err := time.ParseDuration(v)
d, err := parseDurationExtended(v)
if err != nil {
return defaultVal
}
return d
}
// durationUnitRe 匹配 `\d+[dhw]`(天/小时/周)这类 Go time.ParseDuration 不支持的天气单位。
var durationUnitRe = regexp.MustCompile(`(\d+)\s*([dhw])`)
// parseDurationExtended 解析人类可读时长,支持 Go 原生单位(ns/us/ms/s/m/h,
// 及复合如 "1h30m")加上 d(天)与 w(周)。返回实例化 duration,失败返回 error。
func parseDurationExtended(s string) (time.Duration, error) {
s = strings.TrimSpace(s)
if s == "" {
return 0, fmt.Errorf("empty duration")
}
// 先展开 d/w,再交给 time.ParseDuration 处理剩余(含 m/h/s 组合)。
expanded := durationUnitRe.ReplaceAllStringFunc(s, func(m string) string {
parts := durationUnitRe.FindStringSubmatch(m)
n, _ := strconv.Atoi(parts[1])
switch parts[2] {
case "d":
return fmt.Sprintf("%dh", n*24)
case "w":
return fmt.Sprintf("%dh", n*24*7)
case "h":
return m
}
return m
})
d, err := time.ParseDuration(expanded)
if err != nil {
return 0, err
}
return d, nil
}
func (r *ConfigRegistry) GetBool(key string, defaultVal bool) bool {
r.mu.RLock()
defer r.mu.RUnlock()