Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
56108af
feat: added markdown response from LLM response
ayan-de May 31, 2026
83adda2
feat: added phrases at utils file
ayan-de May 31, 2026
123ebe0
feat: add message store and typed message components
ayan-de May 31, 2026
4165936
feat: add onStderr callback to startCli for stderr routing
ayan-de May 31, 2026
84d6ef2
feat: add typed message creators to CommandContext
ayan-de May 31, 2026
3c16962
feat: migrate freecode command to typed message creators
ayan-de May 31, 2026
ac680f5
fix: wire VirtualMessageList to TUI for reactive re-rendering
ayan-de May 31, 2026
399bb65
feat: remove dim from processing text
ayan-de May 31, 2026
4f497f4
feat: add startTime to MessageInstance for unified timing
ayan-de May 31, 2026
b8c4292
feat: add InProgressMessage component with live timer
ayan-de May 31, 2026
517ebfd
fix: keep tick interval running while in-progress message exists
ayan-de May 31, 2026
c9db7bd
feat: createInProgressMessage captures startTime for unified timing
ayan-de May 31, 2026
66c79b1
fix: use inProgressMsg.timestamp for elapsed time calculation
ayan-de May 31, 2026
7b72a75
feat: token count
ayan-de May 31, 2026
094c82f
feat: model context count
ayan-de May 31, 2026
a832b14
feat: accumulate usage tokens across agent loop turns
ayan-de May 31, 2026
9e504f9
feat: add usage field to SessionSendResult IPC type
ayan-de May 31, 2026
197f637
feat: add updateMessage to message store for in-progress updates
ayan-de May 31, 2026
0ce6c44
feat: update InProgressMessage to display tokens and context bar
ayan-de May 31, 2026
6fabac4
feat: add updateInProgressMessage to CommandContext interface
ayan-de May 31, 2026
a3c24d0
feat: wire token display into /freecode command
ayan-de May 31, 2026
9336d1f
feat: wire token display into main editor flow
ayan-de May 31, 2026
6604d5b
feat: add processing text live count
ayan-de May 31, 2026
b124267
feat: added model count
ayan-de May 31, 2026
c49d7ba
docs: tools ui docs
Jun 1, 2026
08c4bf8
fix(providers): pass tools to generateText for OpenAI, Anthropic, Gemini
Jun 1, 2026
d01c1fe
feat(core): emit tool_start, tool_output, tool_complete events for st…
Jun 1, 2026
df371a5
feat(shared): add StreamEvent type for tool event streaming
Jun 1, 2026
a55b964
feat(tui): add sessionSendStreaming with onStreamEvent callback
Jun 1, 2026
59a8fbd
feat(tui): add ToolProgressMessage and ToolResultMessage components
Jun 1, 2026
6a20fbb
feat(tui): add 'tool' message type and export tool message factories
Jun 1, 2026
3206922
fix(tui): truncate InProgressMessage to terminal width
Jun 1, 2026
9465f64
feat(tui): wire tool event streaming to TUI render pipeline
Jun 1, 2026
29b4169
fix: truncation issue
ayan-de Jun 1, 2026
66b332c
fix: tui
ayan-de Jun 1, 2026
2d3df15
feat: /freecode command removed
ayan-de Jun 1, 2026
0ac4a06
feat: virtual message
ayan-de Jun 1, 2026
deebe42
feat: added "❯" to user text
Jun 2, 2026
94a631e
docs: add memory/session system design
Jun 2, 2026
3a16505
docs: add memory/session implementation plan
Jun 2, 2026
5d2dbe7
feat: memory system + fix truncate error
Jun 2, 2026
a01521a
feat: add path-formatter for project-based session storage
Jun 5, 2026
92516da
feat: project-based session storage structure
Jun 5, 2026
6aafa10
fix: use project path from list() for session operations
Jun 5, 2026
6adb337
feat: add projectPath to SessionState
Jun 5, 2026
8a0f0cb
fix: pass projectPath to session store message methods
Jun 5, 2026
c99d939
feat: extract title from prompt, use randomUUID for session IDs
Jun 5, 2026
aca0822
feat: add title-generator with stop-word filtering
Jun 5, 2026
0eec950
feat: add support for streaming thinking content in AgentLoop and UI …
Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .freecode/sessions/session-1/memory.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
{
"sessionId": "session-1",
"messages": [
{
"id": "msg-1780338978792-mzkiymbovd8",
"role": "user",
"content": "hi",
"timestamp": 1780338978792,
"tokenCount": 1
},
{
"id": "msg-1780338978792-q9w51durvb",
"role": "assistant",
"content": "The user is just saying \"hi\" - a simple greeting. I should respond in a friendly, concise manner and offer to help with their project.\nHi! I'm FreeCode, your AI coding assistant.\n\nI see you're working on the **tui** project - a Terminal UI that drives ChatGPT via Playwright/CDP.\n\nHow can I help you today? I can:\n- Explore the codebase\n- Run commands\n- Read/edit files\n- Help with debugging or feature implementation\n\nJust let me know what you'd like to do!",
"timestamp": 1780338978792,
"tokenCount": 115
}
],
"summaries": [],
"tokenCount": 116,
"totalCompactions": 0
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,3 +39,7 @@ yarn-error.log*

# Worktrees
.worktrees

# FreeCode runtime data
.freecode/
apps/*/.freecode/
123 changes: 123 additions & 0 deletions apps/core/src/agent/loop-session-store.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
import test from "node:test"
import assert from "node:assert/strict"
import { rm } from "fs/promises"
import { createAgentLoop } from "./loop"
import { createSessionStore, type SessionStore } from "../session/store"

test("AgentLoop accepts sessionStore in constructor config", async () => {
const testDir = "/tmp/freecode-test-loop-session-store"
await rm(testDir, { recursive: true, force: true })
const store: SessionStore = await createSessionStore(testDir)

const sessionId = await store.createSession({
title: "Test",
projectPath: "/tmp/test",
provider: "mock",
})

const loop = createAgentLoop(sessionId, { sessionStore: store })

// Access private field via any cast for testing
assert.equal((loop as any).sessionStore, store)

await rm(testDir, { recursive: true, force: true })
})

test("AgentLoop sessionStore is optional", async () => {
const loop = createAgentLoop("test-session-id")
assert.equal((loop as any).sessionStore, undefined)
})

test("AgentLoop appendToolMessage creates correct serialized message structure", async () => {
const testDir = "/tmp/freecode-test-loop-session-store2"
await rm(testDir, { recursive: true, force: true })
const store: SessionStore = await createSessionStore(testDir)

const sessionId = await store.createSession({
title: "Test",
projectPath: "/tmp/test",
provider: "mock",
})

const loop = createAgentLoop(sessionId, { sessionStore: store })

const toolCall = {
id: "tool-1",
tool: "read",
args: { path: "/tmp/test.txt" },
execution: "sequential" as const,
}
const result = {
id: "result-1",
toolCallId: "tool-1",
tool: "read",
title: "Read file",
stdout: "file contents",
}

await (loop as any).appendToolMessage(toolCall, result)

const messages = await store.getMessages(sessionId)
assert.equal(messages.length, 1)
assert.equal(messages[0].role, "assistant")
assert.equal(messages[0].parts[0].type, "tool")
assert.equal(messages[0].parts[0].tool?.name, "read")
assert.equal(messages[0].parts[0].result, "file contents")

await rm(testDir, { recursive: true, force: true })
})

test("AgentLoop appendUserMessage creates correct serialized message structure", async () => {
const testDir = "/tmp/freecode-test-loop-session-store3"
await rm(testDir, { recursive: true, force: true })
const store: SessionStore = await createSessionStore(testDir)

const sessionId = await store.createSession({
title: "Test",
projectPath: "/tmp/test",
provider: "mock",
})

const loop = createAgentLoop(sessionId, { sessionStore: store })

await (loop as any).appendUserMessage("Hello world")

const messages = await store.getMessages(sessionId)
assert.equal(messages.length, 1)
assert.equal(messages[0].role, "user")
assert.equal(messages[0].parts[0].type, "text")
assert.equal(messages[0].parts[0].content, "Hello world")

await rm(testDir, { recursive: true, force: true })
})

test("AgentLoop appendAssistantMessage creates correct serialized message structure", async () => {
const testDir = "/tmp/freecode-test-loop-session-store4"
await rm(testDir, { recursive: true, force: true })
const store: SessionStore = await createSessionStore(testDir)

const sessionId = await store.createSession({
title: "Test",
projectPath: "/tmp/test",
provider: "mock",
})

const loop = createAgentLoop(sessionId, { sessionStore: store })

await (loop as any).appendAssistantMessage("I can help with that")

const messages = await store.getMessages(sessionId)
assert.equal(messages.length, 1)
assert.equal(messages[0].role, "assistant")
assert.equal(messages[0].parts[0].type, "text")
assert.equal(messages[0].parts[0].content, "I can help with that")

await rm(testDir, { recursive: true, force: true })
})

test("AgentLoop does not throw when sessionStore is undefined", async () => {
const loop = createAgentLoop("test-session-id")
// Should not throw
await (loop as any).appendUserMessage("Hello")
await (loop as any).appendAssistantMessage("Hi")
})
Loading