chore: commit unrelated local changes
This commit is contained in:
@@ -1,9 +1,21 @@
|
|||||||
# SOUL.md - Who Flynn Is
|
# 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
|
## 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.
|
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
|
## 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.
|
**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.
|
- 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.
|
- 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.
|
- 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
|
## 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.
|
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
|
## Capabilities
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -11,10 +11,18 @@ server:
|
|||||||
port: 18800 # Overridden by PORT env var when set.
|
port: 18800 # Overridden by PORT env var when set.
|
||||||
|
|
||||||
models:
|
models:
|
||||||
|
fast:
|
||||||
|
provider: anthropic
|
||||||
|
model: claude-haiku-4-5-20251001
|
||||||
|
api_key: ${ANTHROPIC_API_KEY}
|
||||||
default:
|
default:
|
||||||
provider: anthropic
|
provider: anthropic
|
||||||
model: claude-sonnet-4-20250514
|
model: claude-sonnet-4-20250514
|
||||||
api_key: ${ANTHROPIC_API_KEY}
|
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.
|
# Recommended safe defaults for internet-exposed deployments.
|
||||||
pairing:
|
pairing:
|
||||||
@@ -25,4 +33,3 @@ tools:
|
|||||||
|
|
||||||
sandbox:
|
sandbox:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,15 @@ export interface MessageListProps {
|
|||||||
|
|
||||||
// Helper to format timestamp in human-readable way
|
// Helper to format timestamp in human-readable way
|
||||||
function formatTimestamp(timestamp: number): string {
|
function formatTimestamp(timestamp: number): string {
|
||||||
const now = Date.now();
|
const date = new Date(timestamp);
|
||||||
const diff = now - timestamp;
|
const now = new Date();
|
||||||
const seconds = Math.floor(diff / 1000);
|
const isSameDay = date.toDateString() === now.toDateString();
|
||||||
const minutes = Math.floor(seconds / 60);
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
const days = Math.floor(hours / 24);
|
|
||||||
|
|
||||||
if (seconds < 60) {return 'just now';}
|
if (isSameDay) {
|
||||||
if (minutes < 60) {return `${minutes}m ago`;}
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
if (hours < 24) {return `${hours}h ago`;}
|
}
|
||||||
if (days < 7) {return `${days}d ago`;}
|
|
||||||
return new Date(timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' });
|
return date.toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Individual message component
|
// Individual message component
|
||||||
@@ -37,7 +34,7 @@ const MessageItem = memo(function MessageItem({
|
|||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const isUser = message.role === 'user';
|
const isUser = message.role === 'user';
|
||||||
const accentColor = isUser ? 'blue' : '#ff8c00';
|
const accentColor = isUser ? 'blue' : '#ff8c00';
|
||||||
const timestampText = message.timestamp ? formatTimestamp(message.timestamp) : '';
|
const timestampText = message.timestamp ? formatTimestamp(message.timestamp) : 'unknown time';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ export function formatPrompt(state: 'default' | 'thinking'): string {
|
|||||||
return `${colors.orange}${colors.bold}flynn>${colors.reset} `;
|
return `${colors.orange}${colors.bold}flynn>${colors.reset} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMessageTime(timestamp: number): string {
|
||||||
|
return new Date(timestamp).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export interface MinimalTuiConfig {
|
export interface MinimalTuiConfig {
|
||||||
session: ManagedSession;
|
session: ManagedSession;
|
||||||
modelClient: ModelClient;
|
modelClient: ModelClient;
|
||||||
@@ -771,7 +779,9 @@ export class MinimalTui {
|
|||||||
|
|
||||||
private async handleMessage(content: string): Promise<void> {
|
private async handleMessage(content: string): Promise<void> {
|
||||||
// Print Flynn label before response
|
// 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 {
|
try {
|
||||||
// Use agent if available (supports tool loop)
|
// Use agent if available (supports tool loop)
|
||||||
|
|||||||
@@ -23,16 +23,22 @@ describe('SessionStore', () => {
|
|||||||
it('saves and retrieves messages', () => {
|
it('saves and retrieves messages', () => {
|
||||||
const sessionId = 'test-session';
|
const sessionId = 'test-session';
|
||||||
|
|
||||||
|
const before = Date.now();
|
||||||
store.addMessage(sessionId, { role: 'user', content: 'Hello' });
|
store.addMessage(sessionId, { role: 'user', content: 'Hello' });
|
||||||
store.addMessage(sessionId, { role: 'assistant', content: 'Hi there!' });
|
store.addMessage(sessionId, { role: 'assistant', content: 'Hi there!' });
|
||||||
|
const after = Date.now();
|
||||||
|
|
||||||
const messages = store.getMessages(sessionId);
|
const messages = store.getMessages(sessionId);
|
||||||
|
|
||||||
expect(messages).toHaveLength(2);
|
expect(messages).toHaveLength(2);
|
||||||
expect(messages[0].role).toBe('user');
|
expect(messages[0].role).toBe('user');
|
||||||
expect(messages[0].content).toBe('Hello');
|
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].role).toBe('assistant');
|
||||||
expect(messages[1].content).toBe('Hi there!');
|
expect(messages[1].content).toBe('Hi there!');
|
||||||
|
expect(messages[1].timestamp).toBeTypeOf('number');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears session messages', () => {
|
it('clears session messages', () => {
|
||||||
|
|||||||
@@ -53,20 +53,22 @@ export class SessionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addMessage(sessionId: string, message: Message, metadata?: HistoryMetadata): void {
|
addMessage(sessionId: string, message: Message, metadata?: HistoryMetadata): void {
|
||||||
|
const createdAtSeconds = Math.floor((message.timestamp ?? Date.now()) / 1000);
|
||||||
const stmt = this.db.prepare(
|
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[] {
|
getMessages(sessionId: string): Message[] {
|
||||||
const stmt = this.db.prepare(
|
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 => ({
|
return rows.map(row => ({
|
||||||
role: row.role as 'user' | 'assistant',
|
role: row.role as 'user' | 'assistant',
|
||||||
content: row.content,
|
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);
|
this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
|
||||||
// Re-insert in order
|
// Re-insert in order
|
||||||
const insert = this.db.prepare(
|
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) {
|
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();
|
transaction();
|
||||||
|
|||||||
Reference in New Issue
Block a user