feat(task): 资产覆盖度功能可按任务开关
创建任务时新增「资产覆盖度功能」开关(默认开)。关闭后: - 不计算/展示测试覆盖度,总览卡片隐藏、覆盖度 API 返回 enabled:false - 态势图仅展示资产不显示测试进度(B1:抹平 tested、去已测统计与图例) - 不再自动累积 task_scope(source=auto) - planner/mainagent 的 add_task_scope、list_untested_assets 从工具列表剔除 - graph_overview 丢弃 coverage 度量块、保留 scope 字段(防上下文污染) company 关联(task_scope kind=company)与此开关无关,永不受影响: insertTaskCompanies 不 gate,TaskCompanyIDs 照常读回,scope 字段照常喂给 agent。 DB:tasks 加 coverage_enabled 列(schema.sql + ALTER ADD COLUMN IF NOT EXISTS 补旧库,默认 true 保持存量任务原行为)。 Claude-Session: https://claude.ai/code/session_01Jm87Gr5fq5aso6SfLyguhx
This commit is contained in:
1 parent
5126ac4592
commit
eb3f278711
18 files changed
+179
-43
No files matched your search
@@ -341,6 +341,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
||||
parent_ref TEXT,
|
||||
timeout_seconds INTEGER NOT NULL DEFAULT 0,
|
||||
plan_heartbeat_seconds INTEGER NOT NULL DEFAULT 300,
|
||||
coverage_enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
first_run_at TIMESTAMPTZ,
|
||||
deadline_at TIMESTAMPTZ,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
@@ -357,6 +358,10 @@ CREATE TRIGGER trg_tasks_upd BEFORE UPDATE ON tasks
|
||||
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS plan_heartbeat_seconds INTEGER NOT NULL DEFAULT 300;
|
||||
-- 并发上限挂起态;补旧库。true=因并发上限排队、等待空位自动启动。
|
||||
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS queued BOOLEAN NOT NULL DEFAULT false;
|
||||
-- 资产覆盖度功能开关;补旧库。true(默认)=计算/展示测试覆盖度、自动累积测试范围、
|
||||
-- 给 agent 开放 add_task_scope/list_untested_assets;false=全部关闭(见 task_scope.go)。
|
||||
-- 存量任务默认 true 保持原行为;company 关联(task_scope kind=company)不受此开关影响。
|
||||
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS coverage_enabled BOOLEAN NOT NULL DEFAULT true;
|
||||
-- queued_at makes admission FIFO reflect the actual enqueue order rather than the
|
||||
-- task creation order. queue_mode distinguishes first bootstrap from resuming an
|
||||
-- exploration that already owns goals/history.
|
||||
|
||||
@@ -248,6 +248,7 @@ type CoverageByType struct {
|
||||
// NOT a precise metric. Denominator = assets matching any active task_scope row;
|
||||
// Tested = those anchored to at least one fact node in the exploration.
|
||||
type Coverage struct {
|
||||
Enabled bool `json:"enabled"` // 资产覆盖度功能是否开启;false 时其余字段为零值
|
||||
ScopeRows int `json:"scope_rows"` // 0 → 范围未锚定
|
||||
Denominator int `json:"denominator"` // 范围内资产数
|
||||
Tested int `json:"tested"` // 已测(约)
|
||||
@@ -255,6 +256,20 @@ type Coverage struct {
|
||||
ByType []CoverageByType `json:"by_type"` // 按资产类型的 总数/已测
|
||||
}
|
||||
|
||||
// CoverageEnabled reports whether a task has the asset-coverage feature turned on
|
||||
// (tasks.coverage_enabled). Missing row / error → true (fail open to the default),
|
||||
// so unknown/legacy tasks keep the historical behavior. taskID<=0 → true.
|
||||
func (s *AssetStore) CoverageEnabled(taskID int64) bool {
|
||||
if taskID <= 0 {
|
||||
return true
|
||||
}
|
||||
var enabled bool
|
||||
if err := s.db.QueryRow(`SELECT COALESCE(coverage_enabled,true) FROM tasks WHERE id=$1`, taskID).Scan(&enabled); err != nil {
|
||||
return true
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
// task_scope→assets match predicate, reused by the count / by-type / untested queries. $1=taskID.
|
||||
const covTargetCTE = `
|
||||
target AS (
|
||||
|
||||
+15
-5
@@ -40,6 +40,11 @@ type Task struct {
|
||||
// planner 心跳触发间隔(秒):距上轮 plan 结束/任务开始满该值且期间无触发 → 触发一轮。
|
||||
// 下限=默认=300(5min),低于一律抬到 300(在 CreateTask 归一)。见 docs/planner-trigger-impl-plan.md
|
||||
PlanHeartbeatSeconds int `json:"plan_heartbeat_seconds"`
|
||||
// CoverageEnabled 是「资产覆盖度功能」总开关(默认 true)。false 时:不计算/不展示测试
|
||||
// 覆盖度、不自动累积 task_scope(source=auto)、不给 agent 开放 add_task_scope/
|
||||
// list_untested_assets、态势里不注入 coverage 块(scope 字段仍保留)。company 关联
|
||||
// (task_scope kind=company)与此开关无关,永不受影响。见 db/task_scope.go。
|
||||
CoverageEnabled bool `json:"coverage_enabled"`
|
||||
}
|
||||
|
||||
// TaskDeleteResult reports optional related-data cleanup performed in the same
|
||||
@@ -137,6 +142,9 @@ type TaskCreateOptions struct {
|
||||
LLMProfileIDs []int64
|
||||
TimeoutSeconds int
|
||||
PlanHeartbeatSeconds int
|
||||
// CoverageEnabled 是「资产覆盖度功能」开关;nil=默认开(true),让不关心该开关的创建
|
||||
// 路径(编排 spawn、老 API)沿用原行为。仅 web 创建任务时可显式传 false 关闭。
|
||||
CoverageEnabled *bool
|
||||
}
|
||||
|
||||
// CreateTaskWithOptions creates an exploration, task, direct source relations,
|
||||
@@ -178,6 +186,7 @@ VALUES ($1, 'fact', $2, 0, 'origin', 'system')`, expID, string(originPayload));
|
||||
opts.TimeoutSeconds = 0
|
||||
}
|
||||
opts.PlanHeartbeatSeconds = normalizeHeartbeat(opts.PlanHeartbeatSeconds)
|
||||
coverageEnabled := opts.CoverageEnabled == nil || *opts.CoverageEnabled
|
||||
var active *int64
|
||||
if len(opts.LLMProfileIDs) > 0 {
|
||||
id := opts.LLMProfileIDs[0]
|
||||
@@ -190,11 +199,12 @@ VALUES ($1, 'fact', $2, 0, 'origin', 'system')`, expID, string(originPayload));
|
||||
SourceTaskIDs: append([]int64(nil), opts.SourceTaskIDs...),
|
||||
CompanyIDs: append([]int64(nil), opts.CompanyIDs...),
|
||||
TimeoutSeconds: opts.TimeoutSeconds, PlanHeartbeatSeconds: opts.PlanHeartbeatSeconds,
|
||||
CoverageEnabled: coverageEnabled,
|
||||
}
|
||||
if err := tx.QueryRow(`
|
||||
INSERT INTO tasks(description, goal, exploration_id, llm_profile_id, active_llm_profile_id, timeout_seconds, plan_heartbeat_seconds)
|
||||
VALUES ($1,$2,$3,$4,$4,$5,$6)
|
||||
RETURNING id, status, paused, created_at`, description, goal, expID, active, opts.TimeoutSeconds, opts.PlanHeartbeatSeconds).Scan(&t.ID, &t.Status, &t.Paused, &t.CreatedAt); err != nil {
|
||||
INSERT INTO tasks(description, goal, exploration_id, llm_profile_id, active_llm_profile_id, timeout_seconds, plan_heartbeat_seconds, coverage_enabled)
|
||||
VALUES ($1,$2,$3,$4,$4,$5,$6,$7)
|
||||
RETURNING id, status, paused, created_at`, description, goal, expID, active, opts.TimeoutSeconds, opts.PlanHeartbeatSeconds, coverageEnabled).Scan(&t.ID, &t.Status, &t.Paused, &t.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := insertTaskRelations(tx, t.ID, opts.SourceTaskIDs); err != nil {
|
||||
@@ -274,11 +284,11 @@ func insertTaskLLMProfiles(tx *sql.Tx, taskID int64, profileIDs []int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const taskCols = `id, description, goal, exploration_id, status, paused, queued, queued_at, COALESCE(queue_mode,''), llm_profile_id, active_llm_profile_id, COALESCE(parent_ref,''), created_at, completed_at, COALESCE(timeout_seconds,0), COALESCE(plan_heartbeat_seconds,300), first_run_at, deadline_at`
|
||||
const taskCols = `id, description, goal, exploration_id, status, paused, queued, queued_at, COALESCE(queue_mode,''), llm_profile_id, active_llm_profile_id, COALESCE(parent_ref,''), created_at, completed_at, COALESCE(timeout_seconds,0), COALESCE(plan_heartbeat_seconds,300), COALESCE(coverage_enabled,true), first_run_at, deadline_at`
|
||||
|
||||
func scanTask(sc interface{ Scan(...any) error }) (*Task, error) {
|
||||
var t Task
|
||||
if err := sc.Scan(&t.ID, &t.Description, &t.Goal, &t.ExplorationID, &t.Status, &t.Paused, &t.Queued, &t.QueuedAt, &t.QueueMode, &t.LLMProfileID, &t.ActiveLLMProfileID, &t.ParentRef, &t.CreatedAt, &t.CompletedAt, &t.TimeoutSeconds, &t.PlanHeartbeatSeconds, &t.FirstRunAt, &t.DeadlineAt); err != nil {
|
||||
if err := sc.Scan(&t.ID, &t.Description, &t.Goal, &t.ExplorationID, &t.Status, &t.Paused, &t.Queued, &t.QueuedAt, &t.QueueMode, &t.LLMProfileID, &t.ActiveLLMProfileID, &t.ParentRef, &t.CreatedAt, &t.CompletedAt, &t.TimeoutSeconds, &t.PlanHeartbeatSeconds, &t.CoverageEnabled, &t.FirstRunAt, &t.DeadlineAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
|
||||
Reference in new issue
Block a user