Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
37407ec
Filter XML tool call tags from SDK stream chunks
brandonkachen Oct 7, 2025
51b2614
Bump SDK version to 0.4.0 - Add XML filtering feature
brandonkachen Oct 7, 2025
b84c552
feat: initial cli based on opentui
brandonkachen Oct 2, 2025
1bfb75a
feat(cli): introduce a robust MultilineInput to enable reliable multi…
brandonkachen Oct 2, 2025
ebd2404
feat(cli): added robust multiline input
brandonkachen Oct 2, 2025
77ef0f3
feat(cli): migrate Markdown rendering to remark/unified; remove legac…
brandonkachen Oct 2, 2025
fc5346d
feat: mockup of todo list
brandonkachen Oct 3, 2025
f6e2268
refactor(cli): streamline multiline input keyboard handling and remov…
brandonkachen Oct 7, 2025
af258c8
fix(opentui): update OpenTUI submodule to fix macOS Terminal color su…
brandonkachen Oct 8, 2025
5ba63a1
chore: update OpenTUI submodule with macOS Terminal color fix
brandonkachen Oct 8, 2025
b69f610
chore: update OpenTUI submodule to ignore workspace symlinks
brandonkachen Oct 8, 2025
36f0a6f
chore: update OpenTUI submodule with package metadata
brandonkachen Oct 8, 2025
5aabca1
feat(cli): add -p flag for prompt automation and subagent chunk logging
brandonkachen Oct 8, 2025
ed0fae1
fix: remove log
brandonkachen Oct 8, 2025
747afed
feat: connecting/connected indicator
brandonkachen Oct 8, 2025
cb06995
fix: show tool calls
brandonkachen Oct 8, 2025
a678771
fix: pasting works
brandonkachen Oct 8, 2025
91a39f6
chore: update OpenTUI submodule to remove Bun dependency
brandonkachen Oct 8, 2025
b6e0d2c
chore: update OpenTUI submodule with tsconfig include field
brandonkachen Oct 8, 2025
84bce7f
fix: ui tweaks + added abort signal + convo state
brandonkachen Oct 8, 2025
e11cd11
refactor: moved stuff out of chat.tsx into their own components
brandonkachen Oct 8, 2025
e587542
fix(cli): resolve all ESLint warnings and remove unused code
brandonkachen Oct 9, 2025
4acc6b5
refactor: move components into folders
brandonkachen Oct 9, 2025
3c6a4a9
feat(cli): add Zed IDE scroll optimization
brandonkachen Oct 9, 2025
0c2bcf9
fix: subagent/tool call structure
brandonkachen Oct 9, 2025
c242d53
chore: track opentui submodule dirty state
brandonkachen Oct 10, 2025
9a1eca7
Fix OpenTUI text node rendering in branch item toggle
brandonkachen Oct 10, 2025
61a8db0
Fix tool toggle rendering by wrapping content in text element
brandonkachen Oct 10, 2025
2d460b6
feat(cli): add automatic system theme detection
brandonkachen Oct 10, 2025
7fcbb1c
Fix CLI toggle rendering and update knowledge
brandonkachen Oct 10, 2025
46ec44c
Add slash command and agent mention autocomplete system
brandonkachen Oct 10, 2025
7b79b72
Improve markdown rendering stability and scroll anchoring
brandonkachen Oct 10, 2025
2bdc005
Add scroll-to-element on branch toggle
brandonkachen Oct 10, 2025
9070f23
Improve type safety for ContentBlock state management
brandonkachen Oct 10, 2025
3de62f7
Fix scroll behavior for programmatic updates
brandonkachen Oct 10, 2025
3adc129
Document new autocomplete and rendering features
brandonkachen Oct 10, 2025
cb3f626
Remove Zed IDE-specific scroll acceleration
brandonkachen Oct 10, 2025
b5185ff
Update opentui submodule to codebuff/custom branch
brandonkachen Oct 10, 2025
ba90b62
Configure opentui submodule to track codebuff/custom branch
brandonkachen Oct 10, 2025
25cdb29
Filter XML tool call tags from SDK stream chunks
brandonkachen Oct 7, 2025
08704f1
Resolve merge conflicts by correcting agentId handling in subagent
brandonkachen Oct 10, 2025
736c6e2
Add postinstall script for automatic submodule setup
brandonkachen Oct 10, 2025
f968342
Replace opentui workspace dependencies with file: dependencies
brandonkachen Oct 10, 2025
45e6d7b
feat(cli): ensure SDK is built as part of prebuild to keep CLI and SD…
brandonkachen Oct 10, 2025
715da4a
Refactor streaming architecture to fix agent content attribution
brandonkachen Oct 12, 2025
7a57d3a
Add IDE theme detection and live synchronization
brandonkachen Oct 12, 2025
6dfb8e4
Add inertial scroll acceleration with IDE-specific tuning
brandonkachen Oct 12, 2025
899024c
Update OpenTUI submodule to include directional scroll API
brandonkachen Oct 12, 2025
1de13df
Fix ESLint import ordering and remove unused code
brandonkachen Oct 12, 2025
60d0c7e
Add dynamic color palette generation for shimmer effects
brandonkachen Oct 12, 2025
63659de
Show terminal command in run_terminal_command preview
brandonkachen Oct 12, 2025
ca1a5bc
Improve cursor visibility with block-style character highlighting
brandonkachen Oct 12, 2025
3ccaffe
Fix useMemo dependency array in multiline input
brandonkachen Oct 12, 2025
0e0b931
feat(multiline-input): make newline handling more predictable and sho…
brandonkachen Oct 12, 2025
e1ff9a0
Refactor agent/tool branch rendering with improved UI and code
brandonkachen Oct 13, 2025
c59c226
feat: updated suggestion menu
brandonkachen Oct 13, 2025
5f0dd80
fix: turn agent/tool orange when active
brandonkachen Oct 13, 2025
cd6f459
fix: hide end_turn tool output
brandonkachen Oct 13, 2025
2fb6f6b
fix: harden sdk stream filtering
brandonkachen Oct 14, 2025
99fc7bc
fix(cli): show agent prompts in previews
brandonkachen Oct 14, 2025
b4b9a99
feat(cli): surface agent prompts in expanded view
brandonkachen Oct 14, 2025
9d6bfe8
refactor(cli): embed agent prompt in details panel
brandonkachen Oct 14, 2025
e04f057
chore(cli): add spacing between prompt and response sections
brandonkachen Oct 14, 2025
4c4d1b2
Update bun.lock
brandonkachen Oct 14, 2025
a802691
Fix CI frozen lockfile failure
brandonkachen Oct 14, 2025
aff0d3b
feat: implement nested agent display with parent-child tracking
brandonkachen Oct 14, 2025
a2a0e48
fix(cli): verify agent block exists before nesting tool calls
brandonkachen Oct 14, 2025
9014964
refactor: only include agentId in tool calls for subagents
brandonkachen Oct 14, 2025
a2212c5
Clean up knowledge.md and add state management improvements
brandonkachen Oct 14, 2025
04c43eb
refactor: replace OpenTUI git submodule with TypeScript symlink script
brandonkachen Oct 14, 2025
c6d3fd3
fix(cli): add @types/react-reconciler to resolve typecheck errors
brandonkachen Oct 15, 2025
3819fc9
feat(agents): implement proper agent nesting with parentAgentId propa…
brandonkachen Oct 15, 2025
99fe2c2
fix(cli): improve main agent text streaming with buffered tool call
brandonkachen Oct 15, 2025
5db2beb
Update package.json
brandonkachen Oct 15, 2025
d152a74
Merge origin/main into brandon/tui
brandonkachen Oct 15, 2025
4b93d15
Remove --watch flag from dev script
brandonkachen Oct 15, 2025
d6d10ec
switch to zustand
brandonkachen Oct 15, 2025
448c531
Refactor: Modernize Zustand store implementation
brandonkachen Oct 15, 2025
a90dcb0
Refactor: Extract shared useConnectionStatus hook and fix test
brandonkachen Oct 15, 2025
26c8d23
Fix subagent streaming test expectations
brandonkachen Oct 15, 2025
fe2c82d
fix(cli): add missing immer dependency for zustand
brandonkachen Oct 15, 2025
3b2def4
Update ci.yml
brandonkachen Oct 15, 2025
42cb2e5
fix: immer-related crash, plus try removing `@opentui/core-darwin-arm64`
brandonkachen Oct 15, 2025
4457528
Fix subagent streaming and parent tracking for nested agents
brandonkachen Oct 15, 2025
0cdd977
chore(logging): remove verbose subagent_start debug in executeSubagent
brandonkachen Oct 15, 2025
c710a2d
refactor(backend): decouple tool execution from WebSocket layer
brandonkachen Oct 15, 2025
4312053
refactor(cli): extract findGitRoot to dedicated git utility file
brandonkachen Oct 15, 2025
16c58ea
chore(repo): add npm-app/src/__tests__/data/ to .gitignore
brandonkachen Oct 15, 2025
0a90e9b
Update changelog for version 0.4.2
brandonkachen Oct 15, 2025
5833d37
Simplify ToolName re-export
brandonkachen Oct 15, 2025
db07ecc
Revert "refactor(backend): decouple tool execution from WebSocket layer"
brandonkachen Oct 15, 2025
157f52e
refactor(backend): simplify parentAgentId handling with switch statement
brandonkachen Oct 15, 2025
e3c2f5a
Merge branch 'main' into brandon/tui
brandonkachen Oct 15, 2025
f0aa4d5
Update bun.lock
brandonkachen Oct 15, 2025
cfa8c64
fix: typecheck
brandonkachen Oct 15, 2025
123a4ee
Refine parent agent handling for tool events
brandonkachen Oct 15, 2025
e141933
chore(cli): move setup-cli-symlinks into cli/scripts and update posti…
brandonkachen Oct 15, 2025
8a248d9
docs: reorganize CLI Misc Notes section into structured categories
brandonkachen Oct 15, 2025
45d6d23
Merge remote-tracking branch 'origin/main' into brandon/tui
brandonkachen Oct 15, 2025
438c053
Merge remote-tracking branch 'origin/main' into brandon/tui
brandonkachen Oct 15, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dist-env
tsconfig.tsbuildinfo
.manicode
__mock-projects__
npm-app/src/__tests__/data/
.aider*
.codebuff*
**.log
Expand Down
30 changes: 9 additions & 21 deletions backend/src/__tests__/subagent-streaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,32 +173,20 @@ describe('Subagent Streaming', () => {
await result

// Verify that subagent streaming messages were sent
expect(mockWriteToClient).toHaveBeenCalledTimes(4)
expect(mockWriteToClient).toHaveBeenCalledTimes(2)

// First streaming chunk is a labled divider
expect(mockWriteToClient).toHaveBeenNthCalledWith(1, {
type: 'subagent_start',
agentId: 'thinker',
displayName: 'Thinker',
onlyChild: true,
})
// First call is subagent_start
expect(mockWriteToClient).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ type: 'subagent_start' }),
)

// Check first streaming chunk
// Second call is subagent_finish
expect(mockWriteToClient).toHaveBeenNthCalledWith(
2,
'Thinking about the problem...',
expect.objectContaining({ type: 'subagent_finish' }),
)

// Check second streaming chunk
expect(mockWriteToClient).toHaveBeenNthCalledWith(3, 'Found a solution!')

// Last streaming chunk is a labeled divider
expect(mockWriteToClient).toHaveBeenNthCalledWith(4, {
type: 'subagent_finish',
agentId: 'thinker',
displayName: 'Thinker',
onlyChild: true,
})
return
})

it('should include correct agentId and agentType in streaming messages', async () => {
Expand Down
11 changes: 10 additions & 1 deletion backend/src/client-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,15 @@ export function sendSubagentChunkWs(
ws: WebSocket
} & ParamsOf<SendSubagentChunkFn>,
): ReturnType<SendSubagentChunkFn> {
const { ws, userInputId, agentId, agentType, chunk, prompt } = params
const {
ws,
userInputId,
agentId,
agentType,
chunk,
prompt,
forwardToPrompt = true,
} = params
return sendActionWs({
ws,
action: {
Expand All @@ -217,6 +225,7 @@ export function sendSubagentChunkWs(
agentType,
chunk,
prompt,
forwardToPrompt,
},
})
}
Expand Down
18 changes: 18 additions & 0 deletions backend/src/llm-apis/vercel-ai-sdk/ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ import type {
import type { LanguageModel } from 'ai'
import type { z } from 'zod/v4'

export type StreamChunk =
| {
type: 'text'
text: string
agentId?: string
}
| {
type: 'reasoning'
text: string
}
| { type: 'error'; message: string }
// TODO: We'll want to add all our models here!
const modelToAiSDKModel = (model: Model): LanguageModel => {
if (
Expand All @@ -56,6 +67,9 @@ export async function* promptAiSdkStream(
params: ParamsOf<PromptAiSdkStreamFn>,
): ReturnType<PromptAiSdkStreamFn> {
const { logger } = params
const agentChunkMetadata =
params.agentId != null ? { agentId: params.agentId } : undefined

if (
!checkLiveUserInput({ ...params, clientSessionId: params.clientSessionId })
) {
Expand Down Expand Up @@ -91,6 +105,7 @@ export async function* promptAiSdkStream(
yield {
type: 'text',
text: flushed,
...(agentChunkMetadata ?? {}),
}
}
}
Expand Down Expand Up @@ -143,6 +158,7 @@ export async function* promptAiSdkStream(
yield {
type: 'text',
text: chunk.text,
...(agentChunkMetadata ?? {}),
}
}
continue
Expand All @@ -154,6 +170,7 @@ export async function* promptAiSdkStream(
yield {
type: 'text',
text: stopSequenceResult.text,
...(agentChunkMetadata ?? {}),
}
}
}
Expand All @@ -164,6 +181,7 @@ export async function* promptAiSdkStream(
yield {
type: 'text',
text: flushed,
...(agentChunkMetadata ?? {}),
}
}

Expand Down
14 changes: 12 additions & 2 deletions backend/src/run-agent-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
FinishAgentRunFn,
StartAgentRunFn,
} from '@codebuff/common/types/contracts/database'
import type { SendActionFn } from '@codebuff/common/types/contracts/client'
import type { Logger } from '@codebuff/common/types/contracts/logger'
import type { ParamsExcluding } from '@codebuff/common/types/function-params'
import type { Message } from '@codebuff/common/types/messages/codebuff-message'
Expand All @@ -56,6 +57,7 @@ export const runAgentStep = async (
clientSessionId: string
fingerprintId: string
onResponseChunk: (chunk: string | PrintModeEvent) => void
sendAction: SendActionFn

agentType: AgentTemplateType
fileContext: ProjectFileContext
Expand Down Expand Up @@ -105,13 +107,15 @@ export const runAgentStep = async (
fingerprintId,
clientSessionId,
onResponseChunk,
sendAction,
fileContext,
agentType,
localAgentTemplates,
prompt,
spawnParams,
system,
logger,
promptAiSdkStream,
} = params
let agentState = params.agentState

Expand Down Expand Up @@ -234,8 +238,11 @@ export const runAgentStep = async (
const { model } = agentTemplate

const { getStream } = getAgentStreamFromTemplate({
...params,
agentId: agentState.agentId,
clientSessionId,
fingerprintId,
userInputId,
userId,
agentId: agentState.parentId ? agentState.agentId : undefined,
template: agentTemplate,
onCostCalculated: async (credits: number) => {
try {
Expand All @@ -254,6 +261,9 @@ export const runAgentStep = async (
)
}
},
sendAction,
promptAiSdkStream,
logger,
includeCacheControl: supportsCacheControl(agentTemplate.model),
})

Expand Down
70 changes: 69 additions & 1 deletion backend/src/run-programmatic-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export async function runProgrammaticStep(
agentType: string
chunk: string
prompt?: string
forwardToPrompt?: boolean
}) => {
sendAction({
action: {
Expand Down Expand Up @@ -275,15 +276,18 @@ export async function runProgrammaticStep(
role: 'assistant' as const,
content: toolCallString,
})
state.sendSubagentChunk({
// Optional call handles both top-level and nested agents
state.sendSubagentChunk?.({
userInputId,
agentId: state.agentState.agentId,
agentType: state.agentState.agentType!,
chunk: toolCallString,
forwardToPrompt: !state.agentState.parentId,
})
}

// Execute the tool synchronously and get the result immediately
// Wrap onResponseChunk to add parentAgentId to nested agent events
await executeToolCall({
...params,
toolName: toolCall.toolName,
Expand All @@ -299,6 +303,70 @@ export async function runProgrammaticStep(
autoInsertEndStepParam: true,
excludeToolFromMessageHistory,
fromHandleSteps: true,
onResponseChunk: (chunk: string | PrintModeEvent) => {
if (typeof chunk === 'string') {
onResponseChunk(chunk)
return
}

// Only add parentAgentId if this programmatic agent has a parent (i.e., it's nested)
// This ensures we don't add parentAgentId to top-level spawns
if (state.agentState.parentId) {
const parentAgentId = state.agentState.agentId

switch (chunk.type) {
case 'subagent_start':
case 'subagent_finish':
if (!chunk.parentAgentId) {
logger.debug(
{
eventType: chunk.type,
agentId: chunk.agentId,
parentId: parentAgentId,
},
`run-programmatic-step: Adding parentAgentId to ${chunk.type} event`,
)
onResponseChunk({
...chunk,
parentAgentId,
})
return
}
break
case 'tool_call':
case 'tool_result': {
if (!chunk.parentAgentId) {
const debugPayload =
chunk.type === 'tool_call'
? {
eventType: chunk.type,
agentId: chunk.agentId,
parentId: parentAgentId,
}
: {
eventType: chunk.type,
parentId: parentAgentId,
}
logger.debug(
debugPayload,
`run-programmatic-step: Adding parentAgentId to ${chunk.type} event`,
)
onResponseChunk({
...chunk,
parentAgentId,
})
return
}
break
}
default:
break
}
}

// For other events or top-level spawns, send as-is
onResponseChunk(chunk)
},
})

// TODO: Remove messages from state and always use agentState.messageHistory.
Expand Down
15 changes: 10 additions & 5 deletions backend/src/tools/handlers/tool/spawn-agent-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,15 @@ export async function executeSubagent(
const { onResponseChunk, agentTemplate, parentAgentState, isOnlyChild } =
withDefaults

onResponseChunk({
type: 'subagent_start',
agentId: agentTemplate.id,
const startEvent = {
type: 'subagent_start' as const,
agentId: withDefaults.agentState.agentId,
agentType: agentTemplate.id,
displayName: agentTemplate.displayName,
onlyChild: isOnlyChild,
})
parentAgentId: parentAgentState.agentId,
}
onResponseChunk(startEvent)

// Import loopAgentSteps dynamically to avoid circular dependency
const { loopAgentSteps } = await import('../../../run-agent-step')
Expand All @@ -340,9 +343,11 @@ export async function executeSubagent(

onResponseChunk({
type: 'subagent_finish',
agentId: agentTemplate.id,
agentId: result.agentState.agentId,
agentType: agentTemplate.id,
displayName: agentTemplate.displayName,
onlyChild: isOnlyChild,
parentAgentId: parentAgentState.agentId,
})

if (result.agentState.runId) {
Expand Down
Loading