fix: show real project runtime agents
This commit is contained in:
@@ -11,12 +11,19 @@ type Snapshot struct {
|
||||
type SourceMap map[string]SourceEvidence
|
||||
|
||||
type Thread struct {
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Source SourceEvidence `json:"source"`
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
CWD string `json:"cwd"`
|
||||
Title string `json:"title"`
|
||||
AgentNickname string `json:"agentNickname,omitempty"`
|
||||
AgentRole string `json:"agentRole,omitempty"`
|
||||
AgentPath string `json:"agentPath,omitempty"`
|
||||
ThreadSource string `json:"threadSource,omitempty"`
|
||||
Preview string `json:"preview,omitempty"`
|
||||
Source SourceEvidence `json:"source"`
|
||||
}
|
||||
|
||||
type SpawnEdge struct {
|
||||
|
||||
@@ -121,7 +121,20 @@ func readThreads(db *sql.DB, sourcePath string, sources SourceMap) ([]Thread, er
|
||||
sources["threads"] = tableSource("sqlite_schema_drift", sourcePath, "threads table is missing required id column.")
|
||||
return []Thread{}, nil
|
||||
}
|
||||
query := `SELECT ` + textColumn(columns, "id") + `, ` + textColumn(columns, "role") + `, ` + textColumn(columns, "status") + `, ` + textColumn(columns, "created_at") + `, ` + textColumn(columns, "updated_at") + ` FROM threads ORDER BY ` + orderBy(columns, "created_at", "id")
|
||||
query := `SELECT ` +
|
||||
textColumn(columns, "id") + `, ` +
|
||||
firstTextColumn(columns, "agent_role", "role") + `, ` +
|
||||
textColumn(columns, "status") + `, ` +
|
||||
firstTextColumn(columns, "created_at_ms", "created_at") + `, ` +
|
||||
firstTextColumn(columns, "updated_at_ms", "updated_at") + `, ` +
|
||||
textColumn(columns, "cwd") + `, ` +
|
||||
textColumn(columns, "title") + `, ` +
|
||||
textColumn(columns, "agent_nickname") + `, ` +
|
||||
textColumn(columns, "agent_role") + `, ` +
|
||||
textColumn(columns, "agent_path") + `, ` +
|
||||
textColumn(columns, "thread_source") + `, ` +
|
||||
textColumn(columns, "preview") +
|
||||
` FROM threads ORDER BY ` + orderBy(columns, "created_at_ms", "created_at", "id")
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
if isMissingTable(err) {
|
||||
@@ -133,7 +146,20 @@ func readThreads(db *sql.DB, sourcePath string, sources SourceMap) ([]Thread, er
|
||||
var threads []Thread
|
||||
for rows.Next() {
|
||||
var item Thread
|
||||
if err := rows.Scan(&item.ID, &item.Role, &item.Status, &item.CreatedAt, &item.UpdatedAt); err != nil {
|
||||
if err := rows.Scan(
|
||||
&item.ID,
|
||||
&item.Role,
|
||||
&item.Status,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.CWD,
|
||||
&item.Title,
|
||||
&item.AgentNickname,
|
||||
&item.AgentRole,
|
||||
&item.AgentPath,
|
||||
&item.ThreadSource,
|
||||
&item.Preview,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Source = SourceEvidence{Kind: "sqlite_table", Path: sourcePath, Confidence: "high"}
|
||||
@@ -152,11 +178,16 @@ func readSpawnEdges(db *sql.DB, sourcePath string, sources SourceMap) ([]SpawnEd
|
||||
sources["thread_spawn_edges"] = tableSource("sqlite_missing_table", sourcePath, "thread_spawn_edges table was not found.")
|
||||
return []SpawnEdge{}, nil
|
||||
}
|
||||
if !columns["from_thread_id"] || !columns["to_thread_id"] {
|
||||
if !(columns["from_thread_id"] && columns["to_thread_id"]) && !(columns["parent_thread_id"] && columns["child_thread_id"]) {
|
||||
sources["thread_spawn_edges"] = tableSource("sqlite_schema_drift", sourcePath, "thread_spawn_edges table is missing required endpoint columns.")
|
||||
return []SpawnEdge{}, nil
|
||||
}
|
||||
query := `SELECT ` + textColumn(columns, "from_thread_id") + `, ` + textColumn(columns, "to_thread_id") + `, ` + textColumn(columns, "reason") + `, ` + textColumn(columns, "created_at") + ` FROM thread_spawn_edges ORDER BY ` + orderBy(columns, "created_at", "from_thread_id", "to_thread_id")
|
||||
query := `SELECT ` +
|
||||
firstTextColumn(columns, "from_thread_id", "parent_thread_id") + `, ` +
|
||||
firstTextColumn(columns, "to_thread_id", "child_thread_id") + `, ` +
|
||||
firstTextColumn(columns, "reason", "status") + `, ` +
|
||||
textColumn(columns, "created_at") +
|
||||
` FROM thread_spawn_edges ORDER BY ` + orderBy(columns, "created_at", "from_thread_id", "parent_thread_id", "to_thread_id", "child_thread_id")
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
if isMissingTable(err) {
|
||||
@@ -191,7 +222,12 @@ func readGoals(db *sql.DB, sourcePath string, sources SourceMap) ([]Goal, error)
|
||||
sources["thread_goals"] = tableSource("sqlite_schema_drift", sourcePath, "thread_goals table is missing required thread_id column.")
|
||||
return []Goal{}, nil
|
||||
}
|
||||
query := `SELECT ` + textColumn(columns, "thread_id") + `, ` + textColumn(columns, "goal") + `, ` + textColumn(columns, "status") + `, ` + textColumn(columns, "updated_at") + ` FROM thread_goals ORDER BY ` + orderBy(columns, "updated_at", "thread_id")
|
||||
query := `SELECT ` +
|
||||
textColumn(columns, "thread_id") + `, ` +
|
||||
firstTextColumn(columns, "goal", "objective") + `, ` +
|
||||
textColumn(columns, "status") + `, ` +
|
||||
firstTextColumn(columns, "updated_at_ms", "updated_at") +
|
||||
` FROM thread_goals ORDER BY ` + orderBy(columns, "updated_at_ms", "updated_at", "thread_id")
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
if isMissingTable(err) {
|
||||
@@ -244,6 +280,15 @@ func textColumn(columns map[string]bool, name string) string {
|
||||
return "COALESCE(CAST(" + name + " AS TEXT), '')"
|
||||
}
|
||||
|
||||
func firstTextColumn(columns map[string]bool, names ...string) string {
|
||||
for _, name := range names {
|
||||
if columns[name] {
|
||||
return textColumn(columns, name)
|
||||
}
|
||||
}
|
||||
return "''"
|
||||
}
|
||||
|
||||
func orderBy(columns map[string]bool, names ...string) string {
|
||||
var order []string
|
||||
for _, name := range names {
|
||||
|
||||
@@ -66,6 +66,100 @@ func TestStoreReadsThreadsEdgesAndGoalsFromReadonlySQLite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreReadsCurrentCodexRuntimeSchema(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
stateDB := openWritableSQLite(t, filepath.Join(root, "state_5.sqlite"))
|
||||
defer stateDB.Close()
|
||||
execSQL(t, stateDB, `CREATE TABLE threads (
|
||||
id TEXT PRIMARY KEY,
|
||||
rollout_path TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
model_provider TEXT NOT NULL,
|
||||
cwd TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
sandbox_policy TEXT NOT NULL,
|
||||
approval_mode TEXT NOT NULL,
|
||||
tokens_used INTEGER NOT NULL DEFAULT 0,
|
||||
has_user_event INTEGER NOT NULL DEFAULT 0,
|
||||
archived INTEGER NOT NULL DEFAULT 0,
|
||||
archived_at INTEGER,
|
||||
git_sha TEXT,
|
||||
git_branch TEXT,
|
||||
git_origin_url TEXT,
|
||||
cli_version TEXT NOT NULL DEFAULT '',
|
||||
first_user_message TEXT NOT NULL DEFAULT '',
|
||||
agent_nickname TEXT,
|
||||
agent_role TEXT,
|
||||
memory_mode TEXT NOT NULL DEFAULT 'enabled',
|
||||
model TEXT,
|
||||
reasoning_effort TEXT,
|
||||
agent_path TEXT,
|
||||
created_at_ms INTEGER,
|
||||
updated_at_ms INTEGER,
|
||||
thread_source TEXT,
|
||||
preview TEXT NOT NULL DEFAULT ''
|
||||
)`)
|
||||
execSQL(t, stateDB, `CREATE TABLE thread_spawn_edges (
|
||||
parent_thread_id TEXT NOT NULL,
|
||||
child_thread_id TEXT NOT NULL PRIMARY KEY,
|
||||
status TEXT NOT NULL
|
||||
)`)
|
||||
execSQL(t, stateDB, `INSERT INTO threads (
|
||||
id, rollout_path, created_at, updated_at, source, model_provider, cwd, title, sandbox_policy, approval_mode,
|
||||
cli_version, agent_nickname, agent_role, agent_path, created_at_ms, updated_at_ms, thread_source, preview
|
||||
) VALUES
|
||||
('thread-a', '/tmp/a.jsonl', 1, 2, 'codex', 'openai', '/repo/a', '主线程', 'workspace-write', 'on-request',
|
||||
'0.0.1', '主控', '智能体编排者', '/agents/orchestrator.toml', 1000, 2000, 'cli', '监管项目流程'),
|
||||
('thread-b', '/tmp/b.jsonl', 3, 4, 'codex', 'openai', '/repo/b', '审查线程', 'workspace-write', 'on-request',
|
||||
'0.0.1', '审查员', '代码审查员', '/agents/reviewer.toml', 3000, 4000, 'cli', '审查实现')`)
|
||||
execSQL(t, stateDB, `INSERT INTO thread_spawn_edges (parent_thread_id, child_thread_id, status) VALUES
|
||||
('thread-a', 'thread-b', 'spawned')`)
|
||||
|
||||
goalsDB := openWritableSQLite(t, filepath.Join(root, "goals_1.sqlite"))
|
||||
defer goalsDB.Close()
|
||||
execSQL(t, goalsDB, `CREATE TABLE thread_goals (
|
||||
thread_id TEXT PRIMARY KEY NOT NULL,
|
||||
goal_id TEXT NOT NULL,
|
||||
objective TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
token_budget INTEGER,
|
||||
tokens_used INTEGER NOT NULL DEFAULT 0,
|
||||
time_used_seconds INTEGER NOT NULL DEFAULT 0,
|
||||
created_at_ms INTEGER NOT NULL,
|
||||
updated_at_ms INTEGER NOT NULL
|
||||
)`)
|
||||
execSQL(t, goalsDB, `INSERT INTO thread_goals (
|
||||
thread_id, goal_id, objective, status, token_budget, tokens_used, time_used_seconds, created_at_ms, updated_at_ms
|
||||
) VALUES
|
||||
('thread-a', 'goal-a', '管理当前项目的智能体流程', 'active', 10000, 1200, 60, 1000, 2000)`)
|
||||
|
||||
snapshot, err := Store{CodexHome: root}.Snapshot()
|
||||
if err != nil {
|
||||
t.Fatalf("Snapshot returned error: %v", err)
|
||||
}
|
||||
if len(snapshot.Threads) != 2 {
|
||||
t.Fatalf("threads = %#v", snapshot.Threads)
|
||||
}
|
||||
first := snapshot.Threads[0]
|
||||
if first.ID != "thread-a" || first.CWD != "/repo/a" || first.Title != "主线程" {
|
||||
t.Fatalf("unexpected first thread identity: %#v", first)
|
||||
}
|
||||
if first.AgentNickname != "主控" || first.AgentRole != "智能体编排者" || first.Role != "智能体编排者" {
|
||||
t.Fatalf("unexpected first thread agent fields: %#v", first)
|
||||
}
|
||||
if first.AgentPath != "/agents/orchestrator.toml" || first.Preview != "监管项目流程" || first.CreatedAt != "1000" || first.UpdatedAt != "2000" {
|
||||
t.Fatalf("unexpected first thread metadata: %#v", first)
|
||||
}
|
||||
if len(snapshot.SpawnEdges) != 1 || snapshot.SpawnEdges[0].FromThreadID != "thread-a" || snapshot.SpawnEdges[0].ToThreadID != "thread-b" || snapshot.SpawnEdges[0].Reason != "spawned" {
|
||||
t.Fatalf("unexpected current-schema edges: %#v", snapshot.SpawnEdges)
|
||||
}
|
||||
if len(snapshot.Goals) != 1 || snapshot.Goals[0].ThreadID != "thread-a" || snapshot.Goals[0].Goal != "管理当前项目的智能体流程" || snapshot.Goals[0].Status != "active" || snapshot.Goals[0].UpdatedAt != "2000" {
|
||||
t.Fatalf("unexpected current-schema goals: %#v", snapshot.Goals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreMarksStateMissingWhenOnlyGoalsSQLiteExists(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
createRuntimeSQLite(t, root)
|
||||
|
||||
Reference in New Issue
Block a user