@axplusb/kepler 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +52 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +142 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +159 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +52 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +182 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/claude-reader.ts +594 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +98 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { ToolCallBadge } from './tool-call-badge'
|
|
5
|
+
import { CompactionCard } from './compaction-card'
|
|
6
|
+
import { AssistantMarkdown } from './assistant-markdown'
|
|
7
|
+
import { UserToolResult } from './user-tool-result'
|
|
8
|
+
import { formatCost, formatTokens, formatDurationMs } from '@/lib/decode'
|
|
9
|
+
import type { ReplayTurn, CompactionEvent } from '@/types/claude'
|
|
10
|
+
import { Badge } from '@/components/ui/badge'
|
|
11
|
+
import { Button } from '@/components/ui/button'
|
|
12
|
+
import { cn } from '@/lib/utils'
|
|
13
|
+
import { ChevronDown, ChevronUp, Brain, Clock, Coins } from 'lucide-react'
|
|
14
|
+
|
|
15
|
+
/** Show “Show more” when assistant text exceeds this length (markdown; avoid slicing mid-block). */
|
|
16
|
+
const ASSISTANT_COLLAPSE_THRESHOLD = 900
|
|
17
|
+
|
|
18
|
+
interface TurnCardProps {
|
|
19
|
+
turn: ReplayTurn
|
|
20
|
+
turnNumber: number
|
|
21
|
+
compactionBefore?: CompactionEvent
|
|
22
|
+
toolResults: Map<string, { content: string; is_error: boolean }>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function TokenBreakdown({ turn }: { turn: ReplayTurn }) {
|
|
26
|
+
if (!turn.usage) return null
|
|
27
|
+
const u = turn.usage
|
|
28
|
+
const items = [
|
|
29
|
+
u.input_tokens ? { label: 'In', value: u.input_tokens, color: 'var(--viz-sky)' } : null,
|
|
30
|
+
u.output_tokens ? { label: 'Out', value: u.output_tokens, color: '#d97706' } : null,
|
|
31
|
+
u.cache_creation_input_tokens ? { label: 'cW', value: u.cache_creation_input_tokens, color: '#a78bfa' } : null,
|
|
32
|
+
u.cache_read_input_tokens ? { label: 'cR', value: u.cache_read_input_tokens, color: '#34d399' } : null,
|
|
33
|
+
].filter(Boolean) as { label: string; value: number; color: string }[]
|
|
34
|
+
|
|
35
|
+
if (items.length === 0) return null
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex items-center gap-1 flex-wrap">
|
|
39
|
+
<Coins className="w-3 h-3 text-muted-foreground/50 shrink-0" />
|
|
40
|
+
{items.map(({ label, value, color }) => (
|
|
41
|
+
<span
|
|
42
|
+
key={label}
|
|
43
|
+
className="text-[11px] font-mono px-1.5 py-0.5 rounded border border-border/50 bg-muted/50"
|
|
44
|
+
style={{ color }}
|
|
45
|
+
>
|
|
46
|
+
{label}:{formatTokens(value)}
|
|
47
|
+
</span>
|
|
48
|
+
))}
|
|
49
|
+
{turn.estimated_cost ? (
|
|
50
|
+
<span className="text-[11px] font-mono text-[#d97706] px-1 py-0.5">
|
|
51
|
+
{formatCost(turn.estimated_cost)}
|
|
52
|
+
</span>
|
|
53
|
+
) : null}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function UserTurnCard({ turn, compactionBefore, toolResults }: TurnCardProps) {
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
{compactionBefore && <CompactionCard event={compactionBefore} />}
|
|
62
|
+
|
|
63
|
+
<div className="mb-5 flex flex-col items-end gap-1.5">
|
|
64
|
+
{/* Timestamp label */}
|
|
65
|
+
<span className="text-[11px] text-muted-foreground/40 pr-1">
|
|
66
|
+
{new Date(turn.timestamp).toLocaleTimeString()}
|
|
67
|
+
</span>
|
|
68
|
+
|
|
69
|
+
{/* User bubble (right-aligned) */}
|
|
70
|
+
{turn.text && (
|
|
71
|
+
<div className="max-w-[85%] bg-primary/10 border border-primary/20 rounded-2xl rounded-tr-sm px-4 py-3">
|
|
72
|
+
<p className="text-sm text-foreground/90 whitespace-pre-wrap leading-relaxed">
|
|
73
|
+
{turn.text}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* Tool results (user feedback) */}
|
|
79
|
+
{turn.tool_results && turn.tool_results.length > 0 && (
|
|
80
|
+
<div className="flex w-full max-w-[90%] flex-col gap-2">
|
|
81
|
+
{turn.tool_results.map(r => (
|
|
82
|
+
<UserToolResult key={r.tool_use_id} content={r.content} isError={r.is_error} />
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function AssistantTurnCard({ turn, turnNumber, toolResults }: TurnCardProps) {
|
|
92
|
+
const [thinkingOpen, setThinkingOpen] = useState(false)
|
|
93
|
+
const [expanded, setExpanded] = useState(false)
|
|
94
|
+
|
|
95
|
+
const modelShort = turn.model?.includes('opus-4-6') ? 'Opus 4.6'
|
|
96
|
+
: turn.model?.includes('opus-4-5') ? 'Opus 4.5'
|
|
97
|
+
: turn.model?.includes('opus-4') ? 'Opus 4'
|
|
98
|
+
: turn.model?.includes('sonnet-4-6') ? 'Sonnet 4.6'
|
|
99
|
+
: turn.model?.includes('sonnet-4-5') ? 'Sonnet 4.5'
|
|
100
|
+
: turn.model?.includes('sonnet') ? 'Sonnet'
|
|
101
|
+
: turn.model?.includes('haiku') ? 'Haiku'
|
|
102
|
+
: turn.model ?? 'Claude'
|
|
103
|
+
|
|
104
|
+
const textToShow = turn.text ?? ''
|
|
105
|
+
const needsExpandToggle = textToShow.length > ASSISTANT_COLLAPSE_THRESHOLD
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div className="mb-6 flex flex-col gap-1.5">
|
|
109
|
+
{/* Header row */}
|
|
110
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
111
|
+
<div className="flex items-center gap-1.5">
|
|
112
|
+
<div className="w-6 h-6 rounded-full bg-primary/20 border border-primary/30 flex items-center justify-center shrink-0">
|
|
113
|
+
<span className="text-[10px] font-bold text-primary">C</span>
|
|
114
|
+
</div>
|
|
115
|
+
<span className="text-xs font-semibold text-primary/80">Claude</span>
|
|
116
|
+
</div>
|
|
117
|
+
<Badge variant="outline" className="text-[11px] px-1.5 py-0 h-5 font-mono">
|
|
118
|
+
{modelShort}
|
|
119
|
+
</Badge>
|
|
120
|
+
<span className="text-[11px] text-muted-foreground/30">#{turnNumber}</span>
|
|
121
|
+
{turn.turn_duration_ms && (
|
|
122
|
+
<span className="text-[11px] text-muted-foreground/40 flex items-center gap-1">
|
|
123
|
+
<Clock className="w-3 h-3" />
|
|
124
|
+
{formatDurationMs(turn.turn_duration_ms)}
|
|
125
|
+
</span>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Thinking block */}
|
|
130
|
+
{turn.has_thinking && (
|
|
131
|
+
<div className="ml-8">
|
|
132
|
+
<Button
|
|
133
|
+
type="button"
|
|
134
|
+
variant="ghost"
|
|
135
|
+
size="sm"
|
|
136
|
+
className="h-auto gap-1.5 px-2 py-1.5 text-xs font-medium text-indigo-700 hover:bg-indigo-500/10 hover:text-indigo-900 dark:text-indigo-400/90 dark:hover:text-indigo-300"
|
|
137
|
+
onClick={() => setThinkingOpen(o => !o)}
|
|
138
|
+
>
|
|
139
|
+
<Brain className="h-3.5 w-3.5 shrink-0" />
|
|
140
|
+
Extended thinking
|
|
141
|
+
<ChevronDown
|
|
142
|
+
className={cn('h-3.5 w-3.5 shrink-0 transition-transform duration-200', thinkingOpen && 'rotate-180')}
|
|
143
|
+
/>
|
|
144
|
+
</Button>
|
|
145
|
+
{thinkingOpen && turn.thinking_text && (
|
|
146
|
+
<div className="mt-1 bg-indigo-50 border border-indigo-200/80 rounded-xl px-4 py-3 dark:bg-indigo-950/20 dark:border-indigo-800/25">
|
|
147
|
+
<pre className="text-xs text-indigo-950/90 whitespace-pre-wrap max-h-56 overflow-auto leading-relaxed dark:text-indigo-200/50">
|
|
148
|
+
{turn.thinking_text.slice(0, 3000)}
|
|
149
|
+
{turn.thinking_text.length > 3000 && (
|
|
150
|
+
<span className="text-indigo-400/40"> …[{(turn.thinking_text.length - 3000).toLocaleString()} more chars]</span>
|
|
151
|
+
)}
|
|
152
|
+
</pre>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{/* Tool calls */}
|
|
159
|
+
{turn.tool_calls && turn.tool_calls.length > 0 && (
|
|
160
|
+
<div className="ml-8 space-y-1">
|
|
161
|
+
{turn.tool_calls.map(tc => (
|
|
162
|
+
<ToolCallBadge
|
|
163
|
+
key={tc.id}
|
|
164
|
+
tool={tc}
|
|
165
|
+
result={toolResults.get(tc.id)}
|
|
166
|
+
/>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* Response text bubble */}
|
|
172
|
+
{textToShow && (
|
|
173
|
+
<div className="ml-8">
|
|
174
|
+
<div className="rounded-2xl rounded-tl-sm border border-border/60 bg-card px-4 py-3">
|
|
175
|
+
<div
|
|
176
|
+
className={cn(
|
|
177
|
+
'relative',
|
|
178
|
+
needsExpandToggle && !expanded && 'max-h-112 overflow-hidden'
|
|
179
|
+
)}
|
|
180
|
+
>
|
|
181
|
+
<AssistantMarkdown content={textToShow} />
|
|
182
|
+
{needsExpandToggle && !expanded && (
|
|
183
|
+
<div
|
|
184
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-linear-to-t from-card to-transparent"
|
|
185
|
+
aria-hidden
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
{needsExpandToggle && (
|
|
190
|
+
<Button
|
|
191
|
+
type="button"
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="sm"
|
|
194
|
+
className="mt-2 h-7 gap-1 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
195
|
+
onClick={() => setExpanded(e => !e)}
|
|
196
|
+
>
|
|
197
|
+
{expanded ? (
|
|
198
|
+
<>
|
|
199
|
+
<ChevronUp className="h-3 w-3" /> Show less
|
|
200
|
+
</>
|
|
201
|
+
) : (
|
|
202
|
+
<>
|
|
203
|
+
<ChevronDown className="h-3 w-3" /> Show full response
|
|
204
|
+
</>
|
|
205
|
+
)}
|
|
206
|
+
</Button>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{/* Token breakdown */}
|
|
213
|
+
{turn.usage && (
|
|
214
|
+
<div className="ml-8 mt-0.5">
|
|
215
|
+
<TokenBreakdown turn={turn} />
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { AlertCircle, CheckCircle2, FileEdit, FileText, Info } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
export type ParsedToolResult =
|
|
7
|
+
| { kind: 'file_updated'; path: string; note?: string }
|
|
8
|
+
| { kind: 'file_written'; path: string; note?: string }
|
|
9
|
+
| { kind: 'file_read'; path: string; note?: string }
|
|
10
|
+
| { kind: 'plain'; text: string }
|
|
11
|
+
|
|
12
|
+
function restAfterAction(s: string, idx: number, needleLen: number): string | undefined {
|
|
13
|
+
let rest = s.slice(idx + needleLen).trim()
|
|
14
|
+
rest = rest.replace(/^\.\s*/, '').trim()
|
|
15
|
+
if (rest.startsWith('(') && rest.endsWith(')')) {
|
|
16
|
+
rest = rest.slice(1, -1).trim()
|
|
17
|
+
}
|
|
18
|
+
return rest || undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Best-effort parse of common Orca / tool sandbox result strings.
|
|
23
|
+
*/
|
|
24
|
+
export function parseToolResultMessage(raw: string): ParsedToolResult {
|
|
25
|
+
const s = raw.trim()
|
|
26
|
+
if (!s) return { kind: 'plain', text: raw }
|
|
27
|
+
|
|
28
|
+
const prefix = 'The file '
|
|
29
|
+
|
|
30
|
+
const needles: Array<{ needle: string; kind: 'file_updated' | 'file_written' | 'file_read' }> = [
|
|
31
|
+
{ needle: ' has been updated successfully', kind: 'file_updated' },
|
|
32
|
+
{ needle: ' has been written successfully', kind: 'file_written' },
|
|
33
|
+
{ needle: ' has been written.', kind: 'file_written' },
|
|
34
|
+
{ needle: ' was read successfully', kind: 'file_read' },
|
|
35
|
+
{ needle: ' has been read.', kind: 'file_read' },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
if (s.startsWith(prefix)) {
|
|
39
|
+
for (const { needle, kind } of needles) {
|
|
40
|
+
const i = s.indexOf(needle)
|
|
41
|
+
if (i <= prefix.length) continue
|
|
42
|
+
const path = s.slice(prefix.length, i).trim()
|
|
43
|
+
if (!path) continue
|
|
44
|
+
const note = restAfterAction(s, i, needle.length)
|
|
45
|
+
if (kind === 'file_updated') return { kind: 'file_updated', path, note }
|
|
46
|
+
if (kind === 'file_written') return { kind: 'file_written', path, note }
|
|
47
|
+
return { kind: 'file_read', path, note }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { kind: 'plain', text: s }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatPathForUi(path: string, max = 100): string {
|
|
55
|
+
if (path.length <= max) return path
|
|
56
|
+
const parts = path.split('/')
|
|
57
|
+
if (parts.length <= 2) return path.slice(0, max - 1) + '…'
|
|
58
|
+
const file = parts[parts.length - 1] ?? path
|
|
59
|
+
const start = parts.slice(0, 2).join('/')
|
|
60
|
+
return `${start}/…/${file}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface Props {
|
|
64
|
+
content: string
|
|
65
|
+
isError: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function UserToolResult({ content, isError }: Props) {
|
|
69
|
+
const parsed = parseToolResultMessage(content)
|
|
70
|
+
|
|
71
|
+
if (isError) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="flex gap-2.5 rounded-xl border border-red-500/25 bg-red-950/25 px-3 py-2.5 text-left">
|
|
74
|
+
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-400" />
|
|
75
|
+
<div className="min-w-0 flex-1">
|
|
76
|
+
<p className="text-[11px] font-semibold uppercase tracking-wide text-red-400/90">Tool error</p>
|
|
77
|
+
<pre className="mt-1 whitespace-pre-wrap wrap-break-word font-mono text-[12px] leading-relaxed text-red-200/90">
|
|
78
|
+
{content}
|
|
79
|
+
</pre>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (parsed.kind === 'file_updated') {
|
|
86
|
+
return (
|
|
87
|
+
<div className="flex gap-2.5 rounded-xl border border-emerald-500/20 bg-emerald-500/6 px-3 py-2.5 text-left">
|
|
88
|
+
<FileEdit className="mt-0.5 h-4 w-4 shrink-0 text-emerald-500" />
|
|
89
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
90
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
91
|
+
<span className="text-[13px] font-medium text-foreground">File updated</span>
|
|
92
|
+
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500/90" aria-hidden />
|
|
93
|
+
</div>
|
|
94
|
+
<p
|
|
95
|
+
className="break-all font-mono text-[12px] leading-snug text-foreground/85"
|
|
96
|
+
title={parsed.path}
|
|
97
|
+
>
|
|
98
|
+
{formatPathForUi(parsed.path)}
|
|
99
|
+
</p>
|
|
100
|
+
{parsed.note ? (
|
|
101
|
+
<p className="flex gap-1.5 text-[11px] leading-relaxed text-muted-foreground">
|
|
102
|
+
<Info className="mt-0.5 h-3 w-3 shrink-0 text-muted-foreground/50" />
|
|
103
|
+
<span>{parsed.note}</span>
|
|
104
|
+
</p>
|
|
105
|
+
) : null}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (parsed.kind === 'file_written') {
|
|
112
|
+
return (
|
|
113
|
+
<div className="flex gap-2.5 rounded-xl border border-blue-700/20 bg-blue-700/[0.07] dark:border-sky-500/20 dark:bg-sky-500/6 px-3 py-2.5 text-left">
|
|
114
|
+
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-blue-700 dark:text-sky-500" />
|
|
115
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
116
|
+
<span className="text-[13px] font-medium text-foreground">File written</span>
|
|
117
|
+
<p className="break-all font-mono text-[12px] leading-snug text-foreground/85" title={parsed.path}>
|
|
118
|
+
{formatPathForUi(parsed.path)}
|
|
119
|
+
</p>
|
|
120
|
+
{parsed.note ? <p className="text-[11px] text-muted-foreground">{parsed.note}</p> : null}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (parsed.kind === 'file_read') {
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex gap-2.5 rounded-xl border border-border/60 bg-muted/30 px-3 py-2.5 text-left">
|
|
129
|
+
<FileText className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
130
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
131
|
+
<span className="text-[13px] font-medium text-foreground/90">File read</span>
|
|
132
|
+
<p className="break-all font-mono text-[12px] leading-snug text-muted-foreground" title={parsed.path}>
|
|
133
|
+
{formatPathForUi(parsed.path)}
|
|
134
|
+
</p>
|
|
135
|
+
{parsed.note ? <p className="text-[11px] text-muted-foreground/80">{parsed.note}</p> : null}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const text = parsed.text
|
|
142
|
+
const long = text.length > 320
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={cn(
|
|
147
|
+
'rounded-xl border border-border/50 bg-muted/35 px-3 py-2.5 text-left',
|
|
148
|
+
'text-[13px] leading-relaxed text-foreground/85'
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground/70">Result</p>
|
|
152
|
+
<p className={cn('mt-1.5 whitespace-pre-wrap wrap-break-word', long && 'line-clamp-6')}>{text}</p>
|
|
153
|
+
{long && (
|
|
154
|
+
<p className="mt-2 text-[11px] text-muted-foreground/50">… {text.length.toLocaleString()} characters total</p>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
interface BadgeProps {
|
|
4
|
+
has_compaction?: boolean
|
|
5
|
+
uses_task_agent?: boolean
|
|
6
|
+
uses_mcp?: boolean
|
|
7
|
+
uses_web_search?: boolean
|
|
8
|
+
uses_web_fetch?: boolean
|
|
9
|
+
has_thinking?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SessionBadges({
|
|
13
|
+
has_compaction,
|
|
14
|
+
uses_task_agent,
|
|
15
|
+
uses_mcp,
|
|
16
|
+
uses_web_search,
|
|
17
|
+
uses_web_fetch,
|
|
18
|
+
has_thinking,
|
|
19
|
+
}: BadgeProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-wrap gap-1">
|
|
22
|
+
{has_compaction && (
|
|
23
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[12px] font-medium bg-amber-500/20 text-amber-400 border border-amber-500/30">
|
|
24
|
+
⚡ compacted
|
|
25
|
+
</span>
|
|
26
|
+
)}
|
|
27
|
+
{uses_task_agent && (
|
|
28
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[12px] font-medium bg-purple-500/20 text-purple-400 border border-purple-500/30">
|
|
29
|
+
🤖 agent
|
|
30
|
+
</span>
|
|
31
|
+
)}
|
|
32
|
+
{uses_mcp && (
|
|
33
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[12px] font-medium bg-blue-500/20 text-blue-700 dark:text-blue-400 border border-blue-500/30">
|
|
34
|
+
🔌 mcp
|
|
35
|
+
</span>
|
|
36
|
+
)}
|
|
37
|
+
{(uses_web_search || uses_web_fetch) && (
|
|
38
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[12px] font-medium bg-green-500/20 text-green-400 border border-green-500/30">
|
|
39
|
+
🔍 web
|
|
40
|
+
</span>
|
|
41
|
+
)}
|
|
42
|
+
{has_thinking && (
|
|
43
|
+
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[12px] font-medium bg-indigo-500/20 text-indigo-400 border border-indigo-500/30">
|
|
44
|
+
🧠 thinking
|
|
45
|
+
</span>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|