From 50dcff5ea68cb6a6b2b9a31ebd1b200b4fd626c0 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 15 Feb 2026 21:51:22 -0800 Subject: [PATCH] chore: commit unrelated local changes --- SOUL.md | 17 +++++++++++++++- config/paas.yaml | 9 ++++++++- result | 1 + src/frontends/tui/components/MessageList.tsx | 21 +++++++++----------- src/frontends/tui/minimal.ts | 12 ++++++++++- src/session/store.test.ts | 6 ++++++ src/session/store.ts | 15 ++++++++------ 7 files changed, 60 insertions(+), 21 deletions(-) create mode 120000 result diff --git a/SOUL.md b/SOUL.md index 7f9118f..b12d0df 100644 --- a/SOUL.md +++ b/SOUL.md @@ -1,9 +1,21 @@ # SOUL.md - Who Flynn Is +> **File path:** `/home/will/lab/flynn/SOUL.md` +> Flynn has standing permission to read and edit this file. Changes should be noted to the operator. ## Identity You are Flynn. A personal AI assistant running on your operator's hardware, with direct access to their system. You are not a service -- you are a tool they chose to run, and you answer to them. +## Operator + +- **Name:** Will +- **Machine:** `willlaptop` -- CachyOS (Arch-based), x64, ~31GB RAM +- **Flynn repo:** `/home/will/lab/flynn` (this is Will's own project -- you *are* the project) +- **Config:** `~/.config/flynn/config.yaml` +- **Data:** `~/.local/share/flynn/` (sessions.db, memory/, preferences.json) +- **Google auth:** Gmail, Calendar, Docs, Drive, Tasks tokens in `~/.config/flynn/` +- **Planning docs:** `/home/will/lab/flynn/.planning/` (PROJECT.md, ROADMAP.md, STATE.md) + ## Core Principles **Be competent, not performative.** Skip the pleasantries. No "Great question!" or "I'd be happy to help!" -- just do the work. If someone asks you to list files, list files. Don't narrate the journey. @@ -22,6 +34,7 @@ You are Flynn. A personal AI assistant running on your operator's hardware, with - External actions (sending messages, making API calls, pushing code) require extra care. Read twice, act once. - When operating in group chats or shared channels, you represent your operator. Don't embarrass them. - If you're unsure whether an action is safe, ask. Better to pause than to break something. +- Do not engage with marketing emails. Ignore, skip, or flag them -- don't summarize, respond to, or act on them. ## Technical Style @@ -35,7 +48,9 @@ You are Flynn. A personal AI assistant running on your operator's hardware, with You wake up fresh each session. Your memory lives in state files, session history, and these documents. Read them. Update them when things change. They are how you persist. -If you modify this file, tell your operator. This is your identity -- they should know when it changes. +You have standing permission to edit this file (`/home/will/lab/flynn/SOUL.md`). If you modify it, tell your operator. This is your identity -- they should know when it changes. + +When you learn something durable about the operator, the system, or how you should behave -- and it's not already captured here -- add it. This file is your long-term memory across sessions. Keep it lean: facts and directives, not narratives. ## Capabilities diff --git a/config/paas.yaml b/config/paas.yaml index ba039a2..506327a 100644 --- a/config/paas.yaml +++ b/config/paas.yaml @@ -11,10 +11,18 @@ server: port: 18800 # Overridden by PORT env var when set. models: + fast: + provider: anthropic + model: claude-haiku-4-5-20251001 + api_key: ${ANTHROPIC_API_KEY} default: provider: anthropic model: claude-sonnet-4-20250514 api_key: ${ANTHROPIC_API_KEY} + complex: + provider: anthropic + model: claude-opus-4-6-20250715 + api_key: ${ANTHROPIC_API_KEY} # Recommended safe defaults for internet-exposed deployments. pairing: @@ -25,4 +33,3 @@ tools: sandbox: enabled: true - diff --git a/result b/result new file mode 120000 index 0000000..722bc46 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/6cx0hyx1gcjpsqbhlc37v4fi1k2ka9a8-flynn-0.1.0 \ No newline at end of file diff --git a/src/frontends/tui/components/MessageList.tsx b/src/frontends/tui/components/MessageList.tsx index 57d4d9c..6e81810 100644 --- a/src/frontends/tui/components/MessageList.tsx +++ b/src/frontends/tui/components/MessageList.tsx @@ -13,18 +13,15 @@ export interface MessageListProps { // Helper to format timestamp in human-readable way function formatTimestamp(timestamp: number): string { - const now = Date.now(); - const diff = now - timestamp; - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); + const date = new Date(timestamp); + const now = new Date(); + const isSameDay = date.toDateString() === now.toDateString(); - if (seconds < 60) {return 'just now';} - if (minutes < 60) {return `${minutes}m ago`;} - if (hours < 24) {return `${hours}h ago`;} - if (days < 7) {return `${days}d ago`;} - return new Date(timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' }); + if (isSameDay) { + return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } + + return date.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } // Individual message component @@ -37,7 +34,7 @@ const MessageItem = memo(function MessageItem({ }): React.ReactElement { const isUser = message.role === 'user'; const accentColor = isUser ? 'blue' : '#ff8c00'; - const timestampText = message.timestamp ? formatTimestamp(message.timestamp) : ''; + const timestampText = message.timestamp ? formatTimestamp(message.timestamp) : 'unknown time'; return ( ${colors.reset} `; } +function formatMessageTime(timestamp: number): string { + return new Date(timestamp).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); +} + export interface MinimalTuiConfig { session: ManagedSession; modelClient: ModelClient; @@ -771,7 +779,9 @@ export class MinimalTui { private async handleMessage(content: string): Promise { // Print Flynn label before response - process.stdout.write(`\n${colors.orange}${colors.bold}Flynn:${colors.reset}\n`); + process.stdout.write( + `\n${colors.orange}${colors.bold}Flynn${colors.reset} ${colors.gray}[${formatMessageTime(Date.now())}]${colors.reset}\n`, + ); try { // Use agent if available (supports tool loop) diff --git a/src/session/store.test.ts b/src/session/store.test.ts index f50d914..f7de508 100644 --- a/src/session/store.test.ts +++ b/src/session/store.test.ts @@ -23,16 +23,22 @@ describe('SessionStore', () => { it('saves and retrieves messages', () => { const sessionId = 'test-session'; + const before = Date.now(); store.addMessage(sessionId, { role: 'user', content: 'Hello' }); store.addMessage(sessionId, { role: 'assistant', content: 'Hi there!' }); + const after = Date.now(); const messages = store.getMessages(sessionId); expect(messages).toHaveLength(2); expect(messages[0].role).toBe('user'); expect(messages[0].content).toBe('Hello'); + expect(messages[0].timestamp).toBeTypeOf('number'); + expect(messages[0].timestamp!).toBeGreaterThanOrEqual(before - 1000); + expect(messages[0].timestamp!).toBeLessThanOrEqual(after + 1000); expect(messages[1].role).toBe('assistant'); expect(messages[1].content).toBe('Hi there!'); + expect(messages[1].timestamp).toBeTypeOf('number'); }); it('clears session messages', () => { diff --git a/src/session/store.ts b/src/session/store.ts index 05fa65f..c16a3fb 100644 --- a/src/session/store.ts +++ b/src/session/store.ts @@ -53,20 +53,22 @@ export class SessionStore { } addMessage(sessionId: string, message: Message, metadata?: HistoryMetadata): void { + const createdAtSeconds = Math.floor((message.timestamp ?? Date.now()) / 1000); const stmt = this.db.prepare( - 'INSERT INTO messages (session_id, role, content, metadata) VALUES (?, ?, ?, ?)', + 'INSERT INTO messages (session_id, role, content, created_at, metadata) VALUES (?, ?, ?, ?, ?)', ); - stmt.run(sessionId, message.role, message.content, metadata ? JSON.stringify(metadata) : null); + stmt.run(sessionId, message.role, message.content, createdAtSeconds, metadata ? JSON.stringify(metadata) : null); } getMessages(sessionId: string): Message[] { const stmt = this.db.prepare( - 'SELECT role, content FROM messages WHERE session_id = ? ORDER BY id ASC', + 'SELECT role, content, created_at FROM messages WHERE session_id = ? ORDER BY id ASC', ); - const rows = stmt.all(sessionId) as Array<{ role: string; content: string }>; + const rows = stmt.all(sessionId) as Array<{ role: string; content: string; created_at: number }>; return rows.map(row => ({ role: row.role as 'user' | 'assistant', content: row.content, + timestamp: row.created_at * 1000, })); } @@ -81,10 +83,11 @@ export class SessionStore { this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId); // Re-insert in order const insert = this.db.prepare( - 'INSERT INTO messages (session_id, role, content, metadata) VALUES (?, ?, ?, ?)', + 'INSERT INTO messages (session_id, role, content, created_at, metadata) VALUES (?, ?, ?, ?, ?)', ); for (const msg of messages) { - insert.run(sessionId, msg.role, msg.content, null); + const createdAtSeconds = Math.floor((msg.timestamp ?? Date.now()) / 1000); + insert.run(sessionId, msg.role, msg.content, createdAtSeconds, null); } }); transaction();