Files
HomeAgent/internal/sdk/status.go
JianFeeeee fa54738c6a fix(scheduler): 安全点重新求值中断队列 + 抢占/背压计数修正 + 停机补终态
对照 docs/zh/input-scheduler-design.md 原文修四处(前两处是真缺陷,后两处是
观测面与设计承诺不一致),均配回归用例:

1. §4.3/§5.2「临界区结束后的第一个安全点重新求值」此前**没有实现**:
   全仓唯一的武装点是 registerInterrupt,凡被拦成「入队」的中断只能等当前任务
   自然结束。可达症状:WebUI 终止按钮连按两次,第二次落在 2s 抢占冷却窗内 →
   入队 → 再也不会被求值。修:runTaskSteps 的安全点先 rearmPending()——
   判据与 registerInterrupt 完全同一套(canPreempt + 冷却 + 临界区闸门)。

2. PreemptsByLevel 的语义是「进入 immediate 槽的次数」,但计数发生在
   setImmediateLocked 之前:immediate 是单槽,同一安全点前到达的两条同级中断里
   被降级的那条也被计成抢占。修:setImmediateLocked 只在真占住槽时返回 true,
   计数随之为真;同时把「降级入队」的责任收归调用方,消除同一任务被入队两次的
   隐患(实测该隐患会让中断任务执行两次、Executed 虚高)。

3. 状态面 Preempted 此前拿 Stats.Suspended 顶替,与 preempts_by_level 自相矛盾。
   修:Preempted = Σ PreemptsByLevel[1..4]。

4. §4.4/Q4「满时阻塞发送方 + 计数并打日志」只做了阻塞:pumpInbox 满时直接返回,
   一个字都不计。修:新增 Stats.Backpressure(+DTO 字段) 与只报一次的状态翻转日志;
   同时显式处理 enqueue 返回值(静默丢弃会让同步调用方永久挂起)。

另:Stop() 停机前排空待办——给从未运行与已挂起的、带 ResponseCh 的任务补
skipped 终态,否则 cli/clawhubadapter 这类无超时同步注入方永久挂起(§7 I5、§11.3 X4)。
emitResponse 的 ResponseCh 写入改为非阻塞 + 告警,避免一行写错就卡死调度器 goroutine。

验证:go build/vet 干净;go test -count=1 ./internal/agent/... ./internal/plugin/...
./internal/sdk/... ./cmd/... 全绿;go test -race ./internal/agent/core/ ./internal/sdk/ 干净。
新增 scheduler_rearm_test.go 六个用例(冷却期满重新求值/同级降级不计数/Preempted 求和/
停机补终态/背压计数与翻转/pumpInbox 满计数)。
2026-09-14 10:39:33 +08:00

178 lines
6.4 KiB
Go
Raw 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 sdk
// StatusAPI provides a snapshot of the kernel runtime status.
type StatusAPI interface {
GetKernelStatus() *KernelStatus
}
// KernelStatus is the aggregated runtime snapshot of all kernel subsystems.
type KernelStatus struct {
Uptime string `json:"uptime"`
StartTime string `json:"start_time"`
// Build 是内核自身的版本与构建信息。
//
// 为何需要:此前 WebUI 展示的“版本”取自 `sdk.SDKVersion`,那是 **SDK 仓
// meta.Version 的硬编码值**;而 `-ldflags` 注入的是内核的
// `internal/meta.Version`——两条链完全不相交。于是:
// - 构建时注入的真实版本号、commit、构建时间全部丢失;
// - 两仲版本号碰巧相等时看不出问题,一旦不等就报错版本;
// - 无法回答“现网跑的是哪个 commit 构出来的”。
Build BuildStatus `json:"build"`
AgentID string `json:"agent_id"`
Plugins []PluginInfo `json:"plugins"`
Tools []ToolDef `json:"tools"`
Channels []ChannelInfo `json:"channels"`
Memory MemoryStatus `json:"memory"`
Knowledge KnowledgeStatus `json:"knowledge"`
Documents DocumentStatus `json:"documents"`
TextMemory TextMemoryStatus `json:"text_memory"`
Social SocialStatus `json:"social"`
LLM LLMStatus `json:"llm"`
Context ContextStatus `json:"context"`
Runtime RuntimeStatus `json:"runtime"`
// ONNX 报告统一多模态向量空间(ONNX 模型)是否**真的在用**。
//
// 为何单列:内核的向量能力是三层降级(统一多模态空间 → 词嵌入 → TF-IDF),
// 只报「向量可用/不可用」分不清「ONNX 模型已加载」与「退回了纯文本路径」。
// 模型缺失 / 运行时缺失 / provider 打开失败时这里是 enabled=false + reason。
ONNX ONNXStatus `json:"onnx"`
Tracker TrackerStatus `json:"tracker"`
// Scheduler 是输入调度器的运行时快照(可观测性,设计文档 §11 O1/O2)。
// M2 起输入不再直接排队在 channel 上,而是经 readyQueue/pendingInterrupts/
// suspendStack 三集合按优先级调度;这里把这些状态暴露出来。
Scheduler SchedulerStatus `json:"scheduler"`
}
// SchedulerStatus 是调度器的原子快照 DTO。
type SchedulerStatus struct {
// Running 是当前执行的任务(空表示空闲)。
Running *SchedulerTask `json:"running,omitempty"`
// ReadyQueueDepth / PendingInterrupts / SuspendStack 是三个集合的深度。
ReadyQueueDepth int `json:"ready_queue_depth"`
PendingInterrupts int `json:"pending_interrupts"`
SuspendStack int `json:"suspend_stack"`
MaxSuspendDepth int `json:"max_suspend_depth"`
Enqueued uint64 `json:"enqueued"`
Executed uint64 `json:"executed"`
Rejected uint64 `json:"rejected"`
Suspended uint64 `json:"suspended"`
Resumed uint64 `json:"resumed"`
// Preempted = Σ PreemptsByLevel[1..4],即「真正抢占成功」的次数。
// 它与 Suspended 不等价(受害者可能先自行结束),因此不是 Suspended 的别名。
Preempted uint64 `json:"preempted"`
// Backpressure 是就绪队列满、输入被挡回 channel 的次数(暂时不收,不是丢弃)。
Backpressure uint64 `json:"backpressure"`
}
// SchedulerTask 是任务的最小标识(不暴露帧内容)。
type SchedulerTask struct {
ID uint64 `json:"id"`
Level int `json:"level"`
Kind string `json:"kind"`
}
// ONNXStatus 是统一多模态向量空间(ONNX 模型)的启用状态与身份。
type ONNXStatus struct {
// Enabled 是 provider 真正打开且元数据合法(不是「配置里写了 provider」)。
Enabled bool `json:"enabled"`
// Provider 是配置指定的 provider 名(如 chineseclip / qwen3vl / http)。
Provider string `json:"provider,omitempty"`
Dim int `json:"dim,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Modalities []string `json:"modalities,omitempty"`
// Reason 是未启用时的原因(未配置 / 打开失败的具体错误 / 其它)。
Reason string `json:"reason,omitempty"`
}
type PluginInfo struct {
Name string `json:"name"`
Loaded bool `json:"loaded"`
}
type ChannelInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Ready bool `json:"ready"`
}
type MemoryStatus struct {
Available bool `json:"available"`
EntityCount int `json:"entity_count"`
RelationCount int `json:"relation_count"`
EntityTypes int `json:"entity_types"`
}
type KnowledgeStatus struct {
Available bool `json:"available"`
ItemCount int `json:"item_count"`
Items []string `json:"items,omitempty"`
}
type DocumentStatus struct {
Available bool `json:"available"`
DocCount int `json:"doc_count"`
VectorCount int `json:"vector_count"`
}
type TextMemoryStatus struct {
Available bool `json:"available"`
FileCount int `json:"file_count"`
}
type SocialStatus struct {
Available bool `json:"available"`
PersonCount int `json:"person_count"`
}
type LLMStatus struct {
Available bool `json:"available"`
Provider string `json:"provider,omitempty"`
Sources int `json:"sources,omitempty"`
}
type ContextStatus struct {
EventCount int `json:"event_count,omitempty"`
}
type RuntimeStatus struct {
Goroutines int `json:"goroutines"`
MemoryMB int64 `json:"memory_mb"`
GoVersion string `json:"go_version"`
}
// BuildStatus 是内核二进制的构建身份,全部来自 `internal/meta`
// (由 -ldflags 在构建时注入,未注入时为源码默认值/unknown)。
type BuildStatus struct {
// Version 是内核语义版本(如 1.0.0)。
Version string `json:"version"`
// Commit 是构建时的 Git 短 hash;未注入为 unknown。
Commit string `json:"commit"`
// BuildTime 是 UTC 构建时间;未注入为 unknown。
BuildTime string `json:"build_time"`
// SDKCompatible 是本内核可兼容的最高 SDK 版本。
// 插件用旧 SDK 编译时靠它判断能不能加载。
SDKCompatible string `json:"sdk_compatible"`
// KernelName 是内核名(HomeAgent)。
KernelName string `json:"kernel_name"`
// SourceURL 是本次构建对应的源码地址。AGPL-3.0 §13 要求:向使用者提供网络
// 服务时,要给他们拿到 Corresponding Source 的机会——WebUI 状态页会把它渲染成
// 可见链接,所以**修改后对外部署的分支必须把这个值改指向自己的源码仓库**。
SourceURL string `json:"source_url,omitempty"`
}
type TrackerStatus struct {
Available bool `json:"available"`
Dir string `json:"dir,omitempty"`
}