adamdottv 9 месяцев назад
Родитель
Сommit
01b6bf5bb7

+ 19 - 13
internal/db/files.sql.go

@@ -15,13 +15,11 @@ INSERT INTO files (
     session_id,
     path,
     content,
-    version,
-    created_at,
-    updated_at
+    version
 ) VALUES (
-    ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+    ?, ?, ?, ?, ?
 )
-RETURNING id, session_id, path, content, version, created_at, updated_at
+RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
 `
 
 type CreateFileParams struct {
@@ -47,6 +45,7 @@ func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (File, e
 		&i.Path,
 		&i.Content,
 		&i.Version,
+		&i.IsNew,
 		&i.CreatedAt,
 		&i.UpdatedAt,
 	)
@@ -74,7 +73,7 @@ func (q *Queries) DeleteSessionFiles(ctx context.Context, sessionID string) erro
 }
 
 const getFile = `-- name: GetFile :one
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
 FROM files
 WHERE id = ? LIMIT 1
 `
@@ -88,6 +87,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
 		&i.Path,
 		&i.Content,
 		&i.Version,
+		&i.IsNew,
 		&i.CreatedAt,
 		&i.UpdatedAt,
 	)
@@ -95,7 +95,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
 }
 
 const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
 FROM files
 WHERE path = ? AND session_id = ?
 ORDER BY created_at DESC
@@ -116,6 +116,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
 		&i.Path,
 		&i.Content,
 		&i.Version,
+		&i.IsNew,
 		&i.CreatedAt,
 		&i.UpdatedAt,
 	)
@@ -123,7 +124,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
 }
 
 const listFilesByPath = `-- name: ListFilesByPath :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
 FROM files
 WHERE path = ?
 ORDER BY created_at DESC
@@ -144,6 +145,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
 			&i.Path,
 			&i.Content,
 			&i.Version,
+			&i.IsNew,
 			&i.CreatedAt,
 			&i.UpdatedAt,
 		); err != nil {
@@ -161,7 +163,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
 }
 
 const listFilesBySession = `-- name: ListFilesBySession :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
 FROM files
 WHERE session_id = ?
 ORDER BY created_at ASC
@@ -182,6 +184,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
 			&i.Path,
 			&i.Content,
 			&i.Version,
+			&i.IsNew,
 			&i.CreatedAt,
 			&i.UpdatedAt,
 		); err != nil {
@@ -199,7 +202,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
 }
 
 const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many
-SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at
+SELECT f.id, f.session_id, f.path, f.content, f.version, f.is_new, f.created_at, f.updated_at
 FROM files f
 INNER JOIN (
     SELECT path, MAX(created_at) as max_created_at
@@ -225,6 +228,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
 			&i.Path,
 			&i.Content,
 			&i.Version,
+			&i.IsNew,
 			&i.CreatedAt,
 			&i.UpdatedAt,
 		); err != nil {
@@ -242,7 +246,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
 }
 
 const listNewFiles = `-- name: ListNewFiles :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
 FROM files
 WHERE is_new = 1
 ORDER BY created_at DESC
@@ -263,6 +267,7 @@ func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) {
 			&i.Path,
 			&i.Content,
 			&i.Version,
+			&i.IsNew,
 			&i.CreatedAt,
 			&i.UpdatedAt,
 		); err != nil {
@@ -284,9 +289,9 @@ UPDATE files
 SET
     content = ?,
     version = ?,
-    updated_at = strftime('%s', 'now')
+    updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = ?
-RETURNING id, session_id, path, content, version, created_at, updated_at
+RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
 `
 
 type UpdateFileParams struct {
@@ -304,6 +309,7 @@ func (q *Queries) UpdateFile(ctx context.Context, arg UpdateFileParams) (File, e
 		&i.Path,
 		&i.Content,
 		&i.Version,
+		&i.IsNew,
 		&i.CreatedAt,
 		&i.UpdatedAt,
 	)

+ 8 - 9
internal/db/logs.sql.go

@@ -17,27 +17,24 @@ INSERT INTO logs (
     timestamp,
     level,
     message,
-    attributes,
-    created_at
+    attributes
 ) VALUES (
     ?,
     ?,
     ?,
     ?,
     ?,
-    ?,
     ?
-) RETURNING id, session_id, timestamp, level, message, attributes, created_at
+) RETURNING id, session_id, timestamp, level, message, attributes, created_at, updated_at
 `
 
 type CreateLogParams struct {
 	ID         string         `json:"id"`
 	SessionID  sql.NullString `json:"session_id"`
-	Timestamp  int64          `json:"timestamp"`
+	Timestamp  string         `json:"timestamp"`
 	Level      string         `json:"level"`
 	Message    string         `json:"message"`
 	Attributes sql.NullString `json:"attributes"`
-	CreatedAt  int64          `json:"created_at"`
 }
 
 func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, error) {
@@ -48,7 +45,6 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
 		arg.Level,
 		arg.Message,
 		arg.Attributes,
-		arg.CreatedAt,
 	)
 	var i Log
 	err := row.Scan(
@@ -59,12 +55,13 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
 		&i.Message,
 		&i.Attributes,
 		&i.CreatedAt,
+		&i.UpdatedAt,
 	)
 	return i, err
 }
 
 const listAllLogs = `-- name: ListAllLogs :many
-SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs
+SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
 ORDER BY timestamp DESC
 LIMIT ?
 `
@@ -86,6 +83,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
 			&i.Message,
 			&i.Attributes,
 			&i.CreatedAt,
+			&i.UpdatedAt,
 		); err != nil {
 			return nil, err
 		}
@@ -101,7 +99,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
 }
 
 const listLogsBySession = `-- name: ListLogsBySession :many
-SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs
+SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
 WHERE session_id = ?
 ORDER BY timestamp ASC
 `
@@ -123,6 +121,7 @@ func (q *Queries) ListLogsBySession(ctx context.Context, sessionID sql.NullStrin
 			&i.Message,
 			&i.Attributes,
 			&i.CreatedAt,
+			&i.UpdatedAt,
 		); err != nil {
 			return nil, err
 		}

+ 7 - 9
internal/db/messages.sql.go

@@ -16,11 +16,9 @@ INSERT INTO messages (
     session_id,
     role,
     parts,
-    model,
-    created_at,
-    updated_at
+    model
 ) VALUES (
-    ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+    ?, ?, ?, ?, ?
 )
 RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at
 `
@@ -145,7 +143,7 @@ ORDER BY created_at ASC
 
 type ListMessagesBySessionAfterParams struct {
 	SessionID string `json:"session_id"`
-	CreatedAt int64  `json:"created_at"`
+	CreatedAt string `json:"created_at"`
 }
 
 func (q *Queries) ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error) {
@@ -185,14 +183,14 @@ UPDATE messages
 SET
     parts = ?,
     finished_at = ?,
-    updated_at = strftime('%s', 'now')
+    updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = ?
 `
 
 type UpdateMessageParams struct {
-	Parts      string        `json:"parts"`
-	FinishedAt sql.NullInt64 `json:"finished_at"`
-	ID         string        `json:"id"`
+	Parts      string         `json:"parts"`
+	FinishedAt sql.NullString `json:"finished_at"`
+	ID         string         `json:"id"`
 }
 
 func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error {

+ 0 - 11
internal/db/migrations/20250502063010_add_summary_to_sessions.sql

@@ -1,11 +0,0 @@
--- +goose Up
--- +goose StatementBegin
-ALTER TABLE sessions ADD COLUMN summary TEXT;
-ALTER TABLE sessions ADD COLUMN summarized_at INTEGER;
--- +goose StatementEnd
-
--- +goose Down
--- +goose StatementBegin
-ALTER TABLE sessions DROP COLUMN summarized_at;
-ALTER TABLE sessions DROP COLUMN summary;
--- +goose StatementEnd

+ 0 - 16
internal/db/migrations/20250508122310_create_logs_table.sql

@@ -1,16 +0,0 @@
--- +goose Up
-CREATE TABLE logs (
-    id TEXT PRIMARY KEY,
-    session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
-    timestamp INTEGER NOT NULL,
-    level TEXT NOT NULL,
-    message TEXT NOT NULL,
-    attributes TEXT,
-    created_at INTEGER NOT NULL
-);
-
-CREATE INDEX logs_session_id_idx ON logs(session_id);
-CREATE INDEX logs_timestamp_idx ON logs(timestamp);
-
--- +goose Down
-DROP TABLE logs;

+ 40 - 13
internal/db/migrations/20250424200609_initial.sql → internal/db/migrations/20250513000000_initial.sql

@@ -6,17 +6,19 @@ CREATE TABLE IF NOT EXISTS sessions (
     parent_session_id TEXT,
     title TEXT NOT NULL,
     message_count INTEGER NOT NULL DEFAULT 0 CHECK (message_count >= 0),
-    prompt_tokens  INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0),
-    completion_tokens  INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens>= 0),
+    prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0),
+    completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens >= 0),
     cost REAL NOT NULL DEFAULT 0.0 CHECK (cost >= 0.0),
-    updated_at INTEGER NOT NULL,  -- Unix timestamp in milliseconds
-    created_at INTEGER NOT NULL   -- Unix timestamp in milliseconds
+    summary TEXT,
+    summarized_at TEXT,
+    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
 );
 
 CREATE TRIGGER IF NOT EXISTS update_sessions_updated_at
 AFTER UPDATE ON sessions
 BEGIN
-UPDATE sessions SET updated_at = strftime('%s', 'now')
+UPDATE sessions SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = new.id;
 END;
 
@@ -27,8 +29,9 @@ CREATE TABLE IF NOT EXISTS files (
     path TEXT NOT NULL,
     content TEXT NOT NULL,
     version TEXT NOT NULL,
-    created_at INTEGER NOT NULL,  -- Unix timestamp in milliseconds
-    updated_at INTEGER NOT NULL,  -- Unix timestamp in milliseconds
+    is_new INTEGER DEFAULT 0,
+    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
     FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE,
     UNIQUE(path, session_id, version)
 );
@@ -39,7 +42,7 @@ CREATE INDEX IF NOT EXISTS idx_files_path ON files (path);
 CREATE TRIGGER IF NOT EXISTS update_files_updated_at
 AFTER UPDATE ON files
 BEGIN
-UPDATE files SET updated_at = strftime('%s', 'now')
+UPDATE files SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = new.id;
 END;
 
@@ -50,9 +53,9 @@ CREATE TABLE IF NOT EXISTS messages (
     role TEXT NOT NULL,
     parts TEXT NOT NULL default '[]',
     model TEXT,
-    created_at INTEGER NOT NULL,  -- Unix timestamp in milliseconds
-    updated_at INTEGER NOT NULL,  -- Unix timestamp in milliseconds
-    finished_at INTEGER,  -- Unix timestamp in milliseconds
+    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+    finished_at TEXT,
     FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE
 );
 
@@ -61,7 +64,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages (session_id);
 CREATE TRIGGER IF NOT EXISTS update_messages_updated_at
 AFTER UPDATE ON messages
 BEGIN
-UPDATE messages SET updated_at = strftime('%s', 'now')
+UPDATE messages SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = new.id;
 END;
 
@@ -81,6 +84,28 @@ UPDATE sessions SET
 WHERE id = old.session_id;
 END;
 
+-- Logs
+CREATE TABLE IF NOT EXISTS logs (
+    id TEXT PRIMARY KEY,
+    session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
+    timestamp TEXT NOT NULL,
+    level TEXT NOT NULL,
+    message TEXT NOT NULL,
+    attributes TEXT,
+    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
+);
+
+CREATE INDEX logs_session_id_idx ON logs(session_id);
+CREATE INDEX logs_timestamp_idx ON logs(timestamp);
+
+CREATE TRIGGER IF NOT EXISTS update_logs_updated_at
+AFTER UPDATE ON logs
+BEGIN
+UPDATE logs SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
+WHERE id = new.id;
+END;
+
 -- +goose StatementEnd
 
 -- +goose Down
@@ -88,11 +113,13 @@ END;
 DROP TRIGGER IF EXISTS update_sessions_updated_at;
 DROP TRIGGER IF EXISTS update_messages_updated_at;
 DROP TRIGGER IF EXISTS update_files_updated_at;
+DROP TRIGGER IF EXISTS update_logs_updated_at;
 
 DROP TRIGGER IF EXISTS update_session_message_count_on_delete;
 DROP TRIGGER IF EXISTS update_session_message_count_on_insert;
 
-DROP TABLE IF EXISTS sessions;
+DROP TABLE IF EXISTS logs;
 DROP TABLE IF EXISTS messages;
 DROP TABLE IF EXISTS files;
+DROP TABLE IF EXISTS sessions;
 -- +goose StatementEnd

+ 17 - 15
internal/db/models.go

@@ -9,23 +9,25 @@ import (
 )
 
 type File struct {
-	ID        string `json:"id"`
-	SessionID string `json:"session_id"`
-	Path      string `json:"path"`
-	Content   string `json:"content"`
-	Version   string `json:"version"`
-	CreatedAt int64  `json:"created_at"`
-	UpdatedAt int64  `json:"updated_at"`
+	ID        string        `json:"id"`
+	SessionID string        `json:"session_id"`
+	Path      string        `json:"path"`
+	Content   string        `json:"content"`
+	Version   string        `json:"version"`
+	IsNew     sql.NullInt64 `json:"is_new"`
+	CreatedAt string        `json:"created_at"`
+	UpdatedAt string        `json:"updated_at"`
 }
 
 type Log struct {
 	ID         string         `json:"id"`
 	SessionID  sql.NullString `json:"session_id"`
-	Timestamp  int64          `json:"timestamp"`
+	Timestamp  string         `json:"timestamp"`
 	Level      string         `json:"level"`
 	Message    string         `json:"message"`
 	Attributes sql.NullString `json:"attributes"`
-	CreatedAt  int64          `json:"created_at"`
+	CreatedAt  string         `json:"created_at"`
+	UpdatedAt  string         `json:"updated_at"`
 }
 
 type Message struct {
@@ -34,9 +36,9 @@ type Message struct {
 	Role       string         `json:"role"`
 	Parts      string         `json:"parts"`
 	Model      sql.NullString `json:"model"`
-	CreatedAt  int64          `json:"created_at"`
-	UpdatedAt  int64          `json:"updated_at"`
-	FinishedAt sql.NullInt64  `json:"finished_at"`
+	CreatedAt  string         `json:"created_at"`
+	UpdatedAt  string         `json:"updated_at"`
+	FinishedAt sql.NullString `json:"finished_at"`
 }
 
 type Session struct {
@@ -47,8 +49,8 @@ type Session struct {
 	PromptTokens     int64          `json:"prompt_tokens"`
 	CompletionTokens int64          `json:"completion_tokens"`
 	Cost             float64        `json:"cost"`
-	UpdatedAt        int64          `json:"updated_at"`
-	CreatedAt        int64          `json:"created_at"`
 	Summary          sql.NullString `json:"summary"`
-	SummarizedAt     sql.NullInt64  `json:"summarized_at"`
+	SummarizedAt     sql.NullString `json:"summarized_at"`
+	UpdatedAt        string         `json:"updated_at"`
+	CreatedAt        string         `json:"created_at"`
 }

+ 16 - 20
internal/db/sessions.sql.go

@@ -20,9 +20,7 @@ INSERT INTO sessions (
     completion_tokens,
     cost,
     summary,
-    summarized_at,
-    updated_at,
-    created_at
+    summarized_at
 ) VALUES (
     ?,
     ?,
@@ -32,10 +30,8 @@ INSERT INTO sessions (
     ?,
     ?,
     ?,
-    ?,
-    strftime('%s', 'now'),
-    strftime('%s', 'now')
-) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+    ?
+) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
 `
 
 type CreateSessionParams struct {
@@ -47,7 +43,7 @@ type CreateSessionParams struct {
 	CompletionTokens int64          `json:"completion_tokens"`
 	Cost             float64        `json:"cost"`
 	Summary          sql.NullString `json:"summary"`
-	SummarizedAt     sql.NullInt64  `json:"summarized_at"`
+	SummarizedAt     sql.NullString `json:"summarized_at"`
 }
 
 func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
@@ -71,10 +67,10 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
 		&i.PromptTokens,
 		&i.CompletionTokens,
 		&i.Cost,
-		&i.UpdatedAt,
-		&i.CreatedAt,
 		&i.Summary,
 		&i.SummarizedAt,
+		&i.UpdatedAt,
+		&i.CreatedAt,
 	)
 	return i, err
 }
@@ -90,7 +86,7 @@ func (q *Queries) DeleteSession(ctx context.Context, id string) error {
 }
 
 const getSessionByID = `-- name: GetSessionByID :one
-SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
 FROM sessions
 WHERE id = ? LIMIT 1
 `
@@ -106,16 +102,16 @@ func (q *Queries) GetSessionByID(ctx context.Context, id string) (Session, error
 		&i.PromptTokens,
 		&i.CompletionTokens,
 		&i.Cost,
-		&i.UpdatedAt,
-		&i.CreatedAt,
 		&i.Summary,
 		&i.SummarizedAt,
+		&i.UpdatedAt,
+		&i.CreatedAt,
 	)
 	return i, err
 }
 
 const listSessions = `-- name: ListSessions :many
-SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
 FROM sessions
 WHERE parent_session_id is NULL
 ORDER BY created_at DESC
@@ -138,10 +134,10 @@ func (q *Queries) ListSessions(ctx context.Context) ([]Session, error) {
 			&i.PromptTokens,
 			&i.CompletionTokens,
 			&i.Cost,
-			&i.UpdatedAt,
-			&i.CreatedAt,
 			&i.Summary,
 			&i.SummarizedAt,
+			&i.UpdatedAt,
+			&i.CreatedAt,
 		); err != nil {
 			return nil, err
 		}
@@ -166,7 +162,7 @@ SET
     summary = ?,
     summarized_at = ?
 WHERE id = ?
-RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
 `
 
 type UpdateSessionParams struct {
@@ -175,7 +171,7 @@ type UpdateSessionParams struct {
 	CompletionTokens int64          `json:"completion_tokens"`
 	Cost             float64        `json:"cost"`
 	Summary          sql.NullString `json:"summary"`
-	SummarizedAt     sql.NullInt64  `json:"summarized_at"`
+	SummarizedAt     sql.NullString `json:"summarized_at"`
 	ID               string         `json:"id"`
 }
 
@@ -198,10 +194,10 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
 		&i.PromptTokens,
 		&i.CompletionTokens,
 		&i.Cost,
-		&i.UpdatedAt,
-		&i.CreatedAt,
 		&i.Summary,
 		&i.SummarizedAt,
+		&i.UpdatedAt,
+		&i.CreatedAt,
 	)
 	return i, err
 }

+ 3 - 5
internal/db/sql/files.sql

@@ -28,11 +28,9 @@ INSERT INTO files (
     session_id,
     path,
     content,
-    version,
-    created_at,
-    updated_at
+    version
 ) VALUES (
-    ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+    ?, ?, ?, ?, ?
 )
 RETURNING *;
 
@@ -41,7 +39,7 @@ UPDATE files
 SET
     content = ?,
     version = ?,
-    updated_at = strftime('%s', 'now')
+    updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = ?
 RETURNING *;
 

+ 1 - 3
internal/db/sql/logs.sql

@@ -5,15 +5,13 @@ INSERT INTO logs (
     timestamp,
     level,
     message,
-    attributes,
-    created_at
+    attributes
 ) VALUES (
     ?,
     ?,
     ?,
     ?,
     ?,
-    ?,
     ?
 ) RETURNING *;
 

+ 3 - 5
internal/db/sql/messages.sql

@@ -21,11 +21,9 @@ INSERT INTO messages (
     session_id,
     role,
     parts,
-    model,
-    created_at,
-    updated_at
+    model
 ) VALUES (
-    ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+    ?, ?, ?, ?, ?
 )
 RETURNING *;
 
@@ -34,7 +32,7 @@ UPDATE messages
 SET
     parts = ?,
     finished_at = ?,
-    updated_at = strftime('%s', 'now')
+    updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
 WHERE id = ?;
 
 

+ 2 - 6
internal/db/sql/sessions.sql

@@ -8,9 +8,7 @@ INSERT INTO sessions (
     completion_tokens,
     cost,
     summary,
-    summarized_at,
-    updated_at,
-    created_at
+    summarized_at
 ) VALUES (
     ?,
     ?,
@@ -20,9 +18,7 @@ INSERT INTO sessions (
     ?,
     ?,
     ?,
-    ?,
-    strftime('%s', 'now'),
-    strftime('%s', 'now')
+    ?
 ) RETURNING *;
 
 -- name: GetSessionByID :one

+ 22 - 3
internal/history/history.go

@@ -113,7 +113,13 @@ func (s *service) CreateVersion(ctx context.Context, sessionID, path, content st
 			if b.Version == InitialVersion && a.Version != InitialVersion {
 				return -1
 			}
-			return int(b.CreatedAt - a.CreatedAt) // Fallback to timestamp
+			// Compare timestamps as strings (ISO format sorts correctly)
+			if b.CreatedAt > a.CreatedAt {
+				return 1
+			} else if a.CreatedAt > b.CreatedAt {
+				return -1
+			}
+			return 0 // Equal timestamps
 		})
 
 		latestFile := files[0]
@@ -362,14 +368,27 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[File] {
 }
 
 func (s *service) fromDBItem(item db.File) File {
+	// Parse timestamps from ISO strings
+	createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+	if err != nil {
+		slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+		createdAt = time.Now() // Fallback
+	}
+
+	updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+	if err != nil {
+		slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+		updatedAt = time.Now() // Fallback
+	}
+
 	return File{
 		ID:        item.ID,
 		SessionID: item.SessionID,
 		Path:      item.Path,
 		Content:   item.Content,
 		Version:   item.Version,
-		CreatedAt: time.UnixMilli(item.CreatedAt * 1000),
-		UpdatedAt: time.UnixMilli(item.UpdatedAt * 1000),
+		CreatedAt: createdAt,
+		UpdatedAt: updatedAt,
 	}
 }
 

+ 1 - 1
internal/llm/agent/agent.go

@@ -209,7 +209,7 @@ func (a *agent) prepareMessageHistory(ctx context.Context, sessionID string) (se
 	var sessionMessages []message.Message
 	if currentSession.Summary != "" && !currentSession.SummarizedAt.IsZero() {
 		// If summary exists, only fetch messages after the summarization timestamp
-		sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt.UnixMilli())
+		sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt)
 		if err != nil {
 			return currentSession, nil, fmt.Errorf("failed to list messages after summary: %w", err)
 		}

+ 18 - 5
internal/logging/logging.go

@@ -86,11 +86,10 @@ func (s *service) Create(ctx context.Context, timestamp time.Time, level, messag
 	dbLog, err := s.db.CreateLog(ctx, db.CreateLogParams{
 		ID:         uuid.New().String(),
 		SessionID:  sql.NullString{String: sessionID, Valid: sessionID != ""},
-		Timestamp:  timestamp.UnixMilli(),
+		Timestamp:  timestamp.UTC().Format(time.RFC3339Nano),
 		Level:      level,
 		Message:    message,
 		Attributes: attributesJSON,
-		CreatedAt:  time.Now().UnixMilli(),
 	})
 
 	if err != nil {
@@ -135,10 +134,24 @@ func (s *service) fromDBItem(item db.Log) Log {
 	log := Log{
 		ID:        item.ID,
 		SessionID: item.SessionID.String,
-		Timestamp: time.UnixMilli(item.Timestamp),
 		Level:     item.Level,
 		Message:   item.Message,
-		CreatedAt: time.UnixMilli(item.CreatedAt),
+	}
+
+	// Parse timestamp from ISO string
+	timestamp, err := time.Parse(time.RFC3339Nano, item.Timestamp)
+	if err == nil {
+		log.Timestamp = timestamp
+	} else {
+		log.Timestamp = time.Now() // Fallback
+	}
+
+	// Parse created_at from ISO string
+	createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+	if err == nil {
+		log.CreatedAt = createdAt
+	} else {
+		log.CreatedAt = time.Now() // Fallback
 	}
 
 	if item.Attributes.Valid && item.Attributes.String != "" {
@@ -195,7 +208,7 @@ func (sw *slogWriter) Write(p []byte) (n int, err error) {
 					parsedTime, timeErr = time.Parse(time.RFC3339, value)
 					if timeErr != nil {
 						slog.Error("Failed to parse time in slog writer", "value", value, "error", timeErr)
-						timestamp = time.Now()
+						timestamp = time.Now().UTC()
 						hasTimestamp = true
 						continue
 					}

+ 24 - 11
internal/message/message.go

@@ -45,7 +45,7 @@ type Service interface {
 	Update(ctx context.Context, message Message) (Message, error)
 	Get(ctx context.Context, id string) (Message, error)
 	List(ctx context.Context, sessionID string) ([]Message, error)
-	ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error)
+	ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error)
 	Delete(ctx context.Context, id string) error
 	DeleteSessionMessages(ctx context.Context, sessionID string) error
 }
@@ -134,12 +134,12 @@ func (s *service) Update(ctx context.Context, message Message) (Message, error)
 		return Message{}, fmt.Errorf("failed to marshal message parts for update: %w", err)
 	}
 
-	var dbFinishedAt sql.NullInt64
+	var dbFinishedAt sql.NullString
 	finishPart := message.FinishPart()
 	if finishPart != nil && !finishPart.Time.IsZero() {
-		dbFinishedAt = sql.NullInt64{
-			Int64: finishPart.Time.UnixMilli(),
-			Valid: true,
+		dbFinishedAt = sql.NullString{
+			String: finishPart.Time.UTC().Format(time.RFC3339Nano),
+			Valid:  true,
 		}
 	}
 
@@ -199,13 +199,13 @@ func (s *service) List(ctx context.Context, sessionID string) ([]Message, error)
 	return messages, nil
 }
 
-func (s *service) ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) {
+func (s *service) ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
 	dbMessages, err := s.db.ListMessagesBySessionAfter(ctx, db.ListMessagesBySessionAfterParams{
 		SessionID: sessionID,
-		CreatedAt: timestampMillis,
+		CreatedAt: timestamp.Format(time.RFC3339Nano),
 	})
 	if err != nil {
 		return nil, fmt.Errorf("db.ListMessagesBySessionAfter: %w", err)
@@ -294,14 +294,27 @@ func (s *service) fromDBItem(item db.Message) (Message, error) {
 		return Message{}, fmt.Errorf("unmarshallParts for message ID %s: %w. Raw parts: %s", item.ID, err, item.Parts)
 	}
 
+	// Parse timestamps from ISO strings
+	createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+	if err != nil {
+		slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+		createdAt = time.Now() // Fallback
+	}
+
+	updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+	if err != nil {
+		slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+		updatedAt = time.Now() // Fallback
+	}
+
 	msg := Message{
 		ID:        item.ID,
 		SessionID: item.SessionID,
 		Role:      MessageRole(item.Role),
 		Parts:     parts,
 		Model:     models.ModelID(item.Model.String),
-		CreatedAt: time.UnixMilli(item.CreatedAt),
-		UpdatedAt: time.UnixMilli(item.UpdatedAt),
+		CreatedAt: createdAt,
+		UpdatedAt: updatedAt,
 	}
 
 	return msg, nil
@@ -323,8 +336,8 @@ func List(ctx context.Context, sessionID string) ([]Message, error) {
 	return GetService().List(ctx, sessionID)
 }
 
-func ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) {
-	return GetService().ListAfter(ctx, sessionID, timestampMillis)
+func ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
+	return GetService().ListAfter(ctx, sessionID, timestamp)
 }
 
 func Delete(ctx context.Context, id string) error {

+ 10 - 8
internal/session/session.go

@@ -153,10 +153,6 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
 	if session.ID == "" {
 		return Session{}, fmt.Errorf("cannot update session with empty ID")
 	}
-	var summarizedAt sql.NullInt64
-	if !session.SummarizedAt.IsZero() {
-		summarizedAt = sql.NullInt64{Int64: session.SummarizedAt.UnixMilli(), Valid: true}
-	}
 
 	params := db.UpdateSessionParams{
 		ID:               session.ID,
@@ -165,7 +161,7 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
 		CompletionTokens: session.CompletionTokens,
 		Cost:             session.Cost,
 		Summary:          sql.NullString{String: session.Summary, Valid: session.Summary != ""},
-		SummarizedAt:     summarizedAt,
+		SummarizedAt:     sql.NullString{String: session.SummarizedAt.UTC().Format(time.RFC3339Nano), Valid: !session.SummarizedAt.IsZero()},
 	}
 	dbSession, err := s.db.UpdateSession(ctx, params)
 	if err != nil {
@@ -206,9 +202,15 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[Session] {
 func (s *service) fromDBItem(item db.Session) Session {
 	var summarizedAt time.Time
 	if item.SummarizedAt.Valid {
-		summarizedAt = time.UnixMilli(item.SummarizedAt.Int64)
+		parsedTime, err := time.Parse(time.RFC3339Nano, item.SummarizedAt.String)
+		if err == nil {
+			summarizedAt = parsedTime
+		}
 	}
 
+	createdAt, _ := time.Parse(time.RFC3339Nano, item.CreatedAt)
+	updatedAt, _ := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+
 	return Session{
 		ID:               item.ID,
 		ParentSessionID:  item.ParentSessionID.String,
@@ -219,8 +221,8 @@ func (s *service) fromDBItem(item db.Session) Session {
 		Cost:             item.Cost,
 		Summary:          item.Summary.String,
 		SummarizedAt:     summarizedAt,
-		CreatedAt:        time.UnixMilli(item.CreatedAt),
-		UpdatedAt:        time.UnixMilli(item.UpdatedAt),
+		CreatedAt:        createdAt,
+		UpdatedAt:        updatedAt,
 	}
 }
 

+ 1 - 1
internal/tui/components/chat/message.go

@@ -605,7 +605,7 @@ func renderToolMessage(
 		return toolMsg
 	}
 
-	params := renderToolParams(width-2-lipgloss.Width(toolNameText), toolCall)
+	params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
 	responseContent := ""
 	if response != nil {
 		responseContent = renderToolResponse(toolCall, *response, width-2)

+ 1 - 2
internal/tui/components/logs/table.go

@@ -159,8 +159,7 @@ func (i *tableCmp) updateRows() {
 	rows := make([]table.Row, 0, len(i.logs))
 
 	for _, log := range i.logs {
-		// Format timestamp as time
-		timeStr := log.Timestamp.Format("15:04:05")
+		timeStr := log.Timestamp.Local().Format("15:04:05")
 
 		// Include ID as hidden first column for selection
 		row := table.Row{