Skip to content

Commit 53c0e5e

Browse files
authored
Merge pull request #126 from sudocode-ai/acp
support claude auto-compaction
2 parents d71b992 + e972dd1 commit 53c0e5e

File tree

20 files changed

+2177
-523
lines changed

20 files changed

+2177
-523
lines changed

frontend/src/components/executions/AgentTrajectory.tsx

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
ToolCall,
2020
AgentThought,
2121
ToolCallContentItem,
22+
SessionNotification,
2223
} from '@/hooks/useSessionUpdateStream'
2324
import type { PermissionRequest as PermissionRequestType } from '@/types/permissions'
2425
import { TodoTracker } from './TodoTracker'
@@ -67,6 +68,11 @@ export interface AgentTrajectoryProps {
6768
*/
6869
permissionRequests?: PermissionRequestType[]
6970

71+
/**
72+
* Array of session notifications (compaction events, etc.)
73+
*/
74+
sessionNotifications?: SessionNotification[]
75+
7076
/**
7177
* Callback when user responds to a permission request
7278
*/
@@ -116,6 +122,12 @@ type TrajectoryItem =
116122
index?: number
117123
data: PermissionRequestType
118124
}
125+
| {
126+
type: 'notification'
127+
timestamp: number
128+
index?: number
129+
data: SessionNotification
130+
}
119131

120132
/**
121133
* Format tool arguments for compact display
@@ -412,6 +424,7 @@ export function AgentTrajectory({
412424
toolCalls,
413425
thoughts = [],
414426
permissionRequests = [],
427+
sessionNotifications = [],
415428
onPermissionRespond,
416429
onSkipAllPermissions,
417430
isSkippingAllPermissions = false,
@@ -481,6 +494,16 @@ export function AgentTrajectory({
481494
})
482495
})
483496

497+
// Add session notifications (compaction events, etc.)
498+
sessionNotifications.forEach((notification) => {
499+
items.push({
500+
type: 'notification',
501+
timestamp: notification.timestamp.getTime(),
502+
index: notification.index,
503+
data: notification,
504+
})
505+
})
506+
484507
// Sort by index (primary) for stable ordering during streaming,
485508
// falling back to timestamp for items without indices
486509
return items.sort((a, b) => {
@@ -496,7 +519,7 @@ export function AgentTrajectory({
496519
// Fallback to timestamp for items without indices
497520
return a.timestamp - b.timestamp
498521
})
499-
}, [messages, toolCalls, thoughts, permissionRequests, hideSystemMessages])
522+
}, [messages, toolCalls, thoughts, permissionRequests, sessionNotifications, hideSystemMessages])
500523

501524
if (trajectory.length === 0) {
502525
return null
@@ -526,6 +549,8 @@ export function AgentTrajectory({
526549
autoFocus={false}
527550
/>
528551
)
552+
} else if (item.type === 'notification') {
553+
return <NotificationItem key={`notif-${item.data.id}-${idx}`} notification={item.data} />
529554
} else {
530555
return <ToolCallItem key={`tool-${item.data.id}-${idx}`} toolCall={item.data} />
531556
}
@@ -740,6 +765,41 @@ function ThoughtItem({ thought }: { thought: AgentThought }) {
740765
)
741766
}
742767

768+
/**
769+
* NotificationItem - Terminal-style notification rendering (compaction, etc.)
770+
*/
771+
function NotificationItem({ notification }: { notification: SessionNotification }) {
772+
const { notificationType, data } = notification
773+
774+
// Convert snake_case to Title Case for display
775+
let label = notificationType
776+
.split('_')
777+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
778+
.join(' ')
779+
780+
return (
781+
<div className="group">
782+
<div className="flex items-start gap-2">
783+
<div className="min-w-0 flex-1 py-0.5">
784+
<div className="flex items-center gap-2">
785+
<span className="text-xs font-medium text-muted-foreground">{label}</span>
786+
</div>
787+
{/* Show data payload for unknown notification types */}
788+
{Object.keys(data).length > 0 && (
789+
<div className="mt-0.5 text-xs text-muted-foreground/60">
790+
{Object.entries(data).map(([key, value]) => (
791+
<span key={key} className="mr-2">
792+
{key}: {typeof value === 'number' ? value.toLocaleString() : String(value)}
793+
</span>
794+
))}
795+
</div>
796+
)}
797+
</div>
798+
</div>
799+
</div>
800+
)
801+
}
802+
743803
/**
744804
* ToolCallItem - Terminal-style tool call rendering
745805
*/

frontend/src/components/executions/ClaudeCodeConfigForm.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export interface ClaudeCodeConfig {
2323
dangerouslySkipPermissions?: boolean
2424
restrictToWorkDir?: boolean
2525
permissionMode?: 'default' | 'plan' | 'bypassPermissions'
26+
compaction?: {
27+
enabled: boolean
28+
contextTokenThreshold?: number
29+
}
2630
}
2731

2832
interface ClaudeCodeConfigFormProps {
@@ -234,6 +238,58 @@ export function ClaudeCodeConfigForm({ config, onChange }: ClaudeCodeConfigFormP
234238
</SelectContent>
235239
</Select>
236240
</div>
241+
242+
{/* Auto-Compaction */}
243+
<div className="space-y-3 pt-2">
244+
<div className="flex items-center justify-between">
245+
<div className="space-y-0.5">
246+
<Label htmlFor="claude-compaction" className="text-xs font-medium">
247+
Auto-Compaction
248+
</Label>
249+
<p className="text-[10px] text-muted-foreground">
250+
Automatically compact context when token limit is reached
251+
</p>
252+
</div>
253+
<Switch
254+
id="claude-compaction"
255+
checked={config.compaction?.enabled ?? false}
256+
onCheckedChange={(checked: boolean) =>
257+
updateConfig({
258+
compaction: { ...config.compaction, enabled: checked },
259+
})
260+
}
261+
/>
262+
</div>
263+
264+
{config.compaction?.enabled && (
265+
<div className="space-y-2 pl-4 border-l-2 border-muted">
266+
<Label htmlFor="claude-compaction-threshold" className="text-xs">
267+
Token Threshold
268+
</Label>
269+
<input
270+
id="claude-compaction-threshold"
271+
type="number"
272+
min="10000"
273+
max="500000"
274+
step="10000"
275+
className="flex h-8 w-full rounded-md border border-input bg-background px-3 py-1 text-xs"
276+
value={config.compaction?.contextTokenThreshold ?? 100000}
277+
onChange={(e) =>
278+
updateConfig({
279+
compaction: {
280+
enabled: true,
281+
contextTokenThreshold: e.target.value ? parseInt(e.target.value) : undefined,
282+
},
283+
})
284+
}
285+
placeholder="100000"
286+
/>
287+
<p className="text-[10px] text-muted-foreground">
288+
Compact when tokens exceed this threshold (default: 100,000)
289+
</p>
290+
</div>
291+
)}
292+
</div>
237293
</CollapsibleContent>
238294
</Collapsible>
239295
</div>

frontend/src/components/executions/ExecutionMonitor.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,9 @@ export function ExecutionMonitor({
552552
const permissionRequests = wsStream.permissionRequests
553553
const markPermissionResponded = wsStream.markPermissionResponded
554554

555+
// Get session notifications from WebSocket stream (compaction events, etc.)
556+
const sessionNotifications = wsStream.sessionNotifications
557+
555558
// Handle permission response - calls API and updates local state
556559
const handlePermissionRespond = async (requestId: string, optionId: string) => {
557560
try {
@@ -750,12 +753,13 @@ export function ExecutionMonitor({
750753
)}
751754

752755
{/* Agent Trajectory */}
753-
{(messageCount > 0 || toolCallCount > 0 || permissionRequests.length > 0) && (
756+
{(messageCount > 0 || toolCallCount > 0 || permissionRequests.length > 0 || sessionNotifications.length > 0) && (
754757
<AgentTrajectory
755758
messages={messages}
756759
toolCalls={toolCalls}
757760
thoughts={thoughts}
758761
permissionRequests={permissionRequests}
762+
sessionNotifications={sessionNotifications}
759763
onPermissionRespond={handlePermissionRespond}
760764
onSkipAllPermissions={
761765
onSkipAllPermissionsComplete ? handleSkipAllPermissions : undefined
@@ -832,12 +836,13 @@ export function ExecutionMonitor({
832836
)}
833837

834838
{/* Agent Trajectory - unified messages and tool calls */}
835-
{(messageCount > 0 || toolCallCount > 0 || permissionRequests.length > 0) && (
839+
{(messageCount > 0 || toolCallCount > 0 || permissionRequests.length > 0 || sessionNotifications.length > 0) && (
836840
<AgentTrajectory
837841
messages={messages}
838842
toolCalls={toolCalls}
839843
thoughts={thoughts}
840844
permissionRequests={permissionRequests}
845+
sessionNotifications={sessionNotifications}
841846
onPermissionRespond={handlePermissionRespond}
842847
onSkipAllPermissions={
843848
onSkipAllPermissionsComplete ? handleSkipAllPermissions : undefined

0 commit comments

Comments
 (0)