feat: reconcile SQLite tables and columns at startup

This commit is contained in:
Ed1s0nZ committed 2026-10-10 20:45:39 +08:00
1 parent 8cc575b6ae
commit 7f8dab4ec9
18 files changed
+1669 -1836

No files matched your search

+2 -29
View File
@@ -63,38 +63,11 @@ func NewHITLManager(db *database.DB, logger *zap.Logger) *HITLManager {
}
}
// EnsureSchema performs historical recovery after NewDB has checked all tables.
func (m *HITLManager) EnsureSchema() error {
if _, err := m.db.Exec(`
CREATE TABLE IF NOT EXISTS hitl_interrupts (
id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL,
message_id TEXT,
mode TEXT NOT NULL,
tool_name TEXT NOT NULL,
tool_call_id TEXT,
payload TEXT,
status TEXT NOT NULL,
reviewer TEXT NOT NULL DEFAULT 'human',
decision TEXT,
decision_comment TEXT,
created_at DATETIME NOT NULL,
decided_at DATETIME
);`); err != nil {
if err := m.backfillHitlReviewers(); err != nil {
return err
}
_, err := m.db.Exec(`
CREATE TABLE IF NOT EXISTS hitl_conversation_configs (
conversation_id TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 0,
mode TEXT NOT NULL DEFAULT 'off',
sensitive_tools TEXT NOT NULL DEFAULT '[]',
timeout_seconds INTEGER NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL
);`)
if err != nil {
return err
}
m.migrateHitlSchemaColumns()
// On startup, cancel all orphaned pending interrupts from previous process.
// Their in-memory channels are gone, so they can never be resolved.
+4 -6
View File
@@ -37,12 +37,10 @@ func normalizeHitlDecidedBy(v string) string {
}
}
func (m *HITLManager) migrateHitlSchemaColumns() {
_, _ = m.db.Exec(`ALTER TABLE hitl_interrupts ADD COLUMN decided_by TEXT NOT NULL DEFAULT 'human'`)
_, _ = m.db.Exec(`ALTER TABLE hitl_interrupts ADD COLUMN reviewer TEXT NOT NULL DEFAULT 'human'`)
_, _ = m.db.Exec(`UPDATE hitl_interrupts SET reviewer='audit_agent'
WHERE COALESCE(decided_by, '') IN ('audit_agent', 'agent', 'ai')`)
_, _ = m.db.Exec(`ALTER TABLE hitl_conversation_configs ADD COLUMN reviewer TEXT NOT NULL DEFAULT 'human'`)
func (m *HITLManager) backfillHitlReviewers() error {
_, err := m.db.Exec(`UPDATE hitl_interrupts SET reviewer='audit_agent'
WHERE COALESCE(decided_by, '') IN ('audit_agent', 'agent', 'ai')`)
return err
}
func hitlInterruptRowToMap(
+1 -26
View File
@@ -582,25 +582,6 @@ func countUnread(items []NotificationSummaryItem) int {
return total
}
func createNotificationReadTableIfNeeded(db *database.DB) error {
if db == nil {
return fmt.Errorf("db is nil")
}
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS notification_reads_by_user (
user_id TEXT NOT NULL,
event_id TEXT NOT NULL,
read_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(user_id, event_id)
);
`)
if err != nil {
return err
}
_, idxErr := db.Exec(`CREATE INDEX IF NOT EXISTS idx_notification_reads_user_read_at ON notification_reads_by_user(user_id, read_at DESC);`)
return idxErr
}
func pruneNotificationReads(db *database.DB, userID string, maxRows int) error {
if db == nil {
return fmt.Errorf("db is nil")
@@ -648,7 +629,7 @@ func normalizeMarkableEventID(id string) (string, bool) {
// MarkRead 按事件 ID 标记已读
func (h *NotificationHandler) MarkRead(c *gin.Context) {
if err := createNotificationReadTableIfNeeded(h.db); err != nil {
if h.db == nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to prepare notification read table"})
return
}
@@ -713,12 +694,6 @@ func (h *NotificationHandler) GetSummary(c *gin.Context) {
return
}
if err := createNotificationReadTableIfNeeded(h.db); err != nil {
h.logger.Warn("初始化通知已读表失败", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initialize notification read table"})
return
}
english := requestWantsEnglish(c)
sinceMs := normalizeSinceMs(parseSinceMs(c.Query("since")))
limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "50")))