@swarmclawai/swarmclaw 0.6.0 → 0.6.2
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 +15 -2
- package/bin/server-cmd.js +1 -0
- package/package.json +2 -1
- package/src/app/api/canvas/[sessionId]/route.ts +31 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +10 -136
- package/src/app/api/connectors/[id]/route.ts +1 -0
- package/src/app/api/connectors/route.ts +2 -1
- package/src/app/api/files/open/route.ts +43 -0
- package/src/app/api/search/route.ts +9 -7
- package/src/app/api/sessions/[id]/messages/route.ts +70 -2
- package/src/app/api/sessions/[id]/route.ts +4 -0
- package/src/app/api/tasks/metrics/route.ts +101 -0
- package/src/app/api/tasks/route.ts +17 -2
- package/src/app/api/tts/route.ts +3 -2
- package/src/app/api/tts/stream/route.ts +3 -2
- package/src/app/api/uploads/[filename]/route.ts +19 -34
- package/src/app/api/uploads/route.ts +94 -0
- package/src/app/globals.css +5 -0
- package/src/cli/index.js +16 -1
- package/src/cli/spec.js +26 -0
- package/src/components/agents/agent-card.tsx +3 -3
- package/src/components/agents/agent-chat-list.tsx +29 -6
- package/src/components/agents/agent-sheet.tsx +66 -4
- package/src/components/agents/inspector-panel.tsx +81 -6
- package/src/components/agents/openclaw-skills-panel.tsx +32 -3
- package/src/components/agents/personality-builder.tsx +42 -14
- package/src/components/agents/soul-library-picker.tsx +89 -0
- package/src/components/canvas/canvas-panel.tsx +96 -0
- package/src/components/chat/activity-moment.tsx +8 -4
- package/src/components/chat/chat-area.tsx +46 -22
- package/src/components/chat/chat-header.tsx +455 -286
- package/src/components/chat/chat-preview-panel.tsx +1 -2
- package/src/components/chat/delegation-banner.tsx +371 -0
- package/src/components/chat/file-path-chip.tsx +23 -2
- package/src/components/chat/heartbeat-history-panel.tsx +269 -0
- package/src/components/chat/message-bubble.tsx +315 -25
- package/src/components/chat/message-list.tsx +180 -7
- package/src/components/chat/streaming-bubble.tsx +68 -1
- package/src/components/chat/tool-call-bubble.tsx +45 -3
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/chatroom-list.tsx +8 -1
- package/src/components/chatrooms/chatroom-message.tsx +8 -3
- package/src/components/chatrooms/chatroom-view.tsx +3 -3
- package/src/components/connectors/connector-list.tsx +168 -90
- package/src/components/connectors/connector-sheet.tsx +68 -16
- package/src/components/home/home-view.tsx +1 -1
- package/src/components/input/chat-input.tsx +28 -2
- package/src/components/layout/app-layout.tsx +19 -2
- package/src/components/projects/project-detail.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +260 -127
- package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/chatroom-picker-list.tsx +61 -0
- package/src/components/shared/connector-platform-icon.tsx +51 -4
- package/src/components/shared/icon-button.tsx +16 -2
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +1 -1
- package/src/components/shared/search-dialog.tsx +17 -10
- package/src/components/shared/settings/section-embedding.tsx +48 -13
- package/src/components/shared/settings/section-orchestrator.tsx +46 -15
- package/src/components/shared/settings/section-storage.tsx +206 -0
- package/src/components/shared/settings/section-user-preferences.tsx +18 -0
- package/src/components/shared/settings/section-voice.tsx +42 -21
- package/src/components/shared/settings/section-web-search.tsx +30 -6
- package/src/components/shared/settings/settings-page.tsx +3 -1
- package/src/components/shared/settings/storage-browser.tsx +259 -0
- package/src/components/tasks/task-card.tsx +14 -1
- package/src/components/tasks/task-sheet.tsx +328 -3
- package/src/components/usage/metrics-dashboard.tsx +90 -6
- package/src/hooks/use-continuous-speech.ts +10 -4
- package/src/hooks/use-voice-conversation.ts +53 -10
- package/src/hooks/use-ws.ts +4 -2
- package/src/lib/providers/anthropic.ts +13 -7
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/providers/openai.ts +13 -7
- package/src/lib/server/chat-execution.ts +51 -11
- package/src/lib/server/chatroom-helpers.ts +146 -0
- package/src/lib/server/connectors/manager.ts +218 -7
- package/src/lib/server/heartbeat-service.ts +8 -1
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/memory-consolidation.ts +15 -2
- package/src/lib/server/memory-db.ts +134 -6
- package/src/lib/server/mime.ts +51 -0
- package/src/lib/server/openclaw-gateway.ts +2 -2
- package/src/lib/server/orchestrator-lg.ts +2 -0
- package/src/lib/server/orchestrator.ts +5 -2
- package/src/lib/server/playwright-proxy.mjs +2 -3
- package/src/lib/server/prompt-runtime-context.ts +53 -0
- package/src/lib/server/queue.ts +52 -7
- package/src/lib/server/session-tools/canvas.ts +67 -0
- package/src/lib/server/session-tools/connector.ts +83 -9
- package/src/lib/server/session-tools/crud.ts +21 -0
- package/src/lib/server/session-tools/delegate.ts +68 -4
- package/src/lib/server/session-tools/git.ts +71 -0
- package/src/lib/server/session-tools/http.ts +57 -0
- package/src/lib/server/session-tools/index.ts +8 -0
- package/src/lib/server/session-tools/memory.ts +1 -0
- package/src/lib/server/session-tools/search-providers.ts +16 -8
- package/src/lib/server/session-tools/subagent.ts +106 -0
- package/src/lib/server/session-tools/web.ts +115 -4
- package/src/lib/server/stream-agent-chat.ts +32 -10
- package/src/lib/server/task-mention.ts +41 -0
- package/src/lib/sessions.ts +10 -0
- package/src/lib/soul-library.ts +103 -0
- package/src/lib/task-dedupe.ts +26 -0
- package/src/lib/tool-definitions.ts +2 -0
- package/src/lib/tts.ts +2 -2
- package/src/stores/use-app-store.ts +5 -1
- package/src/stores/use-chat-store.ts +65 -2
- package/src/types/index.ts +32 -2
|
@@ -46,13 +46,12 @@ export function ChatPreviewPanel({ content, onClose }: Props) {
|
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
48
|
<div
|
|
49
|
-
className="flex flex-col border-l border-white/[0.06] bg-bg shrink-0"
|
|
49
|
+
className="relative flex flex-col border-l border-white/[0.06] bg-bg shrink-0"
|
|
50
50
|
style={{ width, minWidth: 300, maxWidth: '50%', animation: 'fade-in 0.25s ease' }}
|
|
51
51
|
>
|
|
52
52
|
{/* Resize handle */}
|
|
53
53
|
<div
|
|
54
54
|
className="absolute left-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent-bright/20 transition-colors z-10"
|
|
55
|
-
style={{ position: 'relative', width: 4, minWidth: 4 }}
|
|
56
55
|
onMouseDown={handleMouseDown}
|
|
57
56
|
/>
|
|
58
57
|
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
7
|
+
|
|
8
|
+
type DelegationStatus = 'delegating' | 'checking' | 'completed' | 'failed'
|
|
9
|
+
|
|
10
|
+
interface DelegationBannerProps {
|
|
11
|
+
agentName: string
|
|
12
|
+
agentAvatarSeed: string | null
|
|
13
|
+
taskPreview: string
|
|
14
|
+
taskId: string | null
|
|
15
|
+
status: DelegationStatus
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STATUS_CONFIG: Record<DelegationStatus, { color: string; bg: string; border: string }> = {
|
|
19
|
+
delegating: { color: '#818CF8', bg: 'rgba(99,102,241,0.06)', border: 'rgba(99,102,241,0.12)' },
|
|
20
|
+
checking: { color: '#818CF8', bg: 'rgba(99,102,241,0.06)', border: 'rgba(99,102,241,0.12)' },
|
|
21
|
+
completed: { color: '#34D399', bg: 'rgba(52,211,153,0.06)', border: 'rgba(52,211,153,0.12)' },
|
|
22
|
+
failed: { color: '#F43F5E', bg: 'rgba(244,63,94,0.06)', border: 'rgba(244,63,94,0.12)' },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function StatusIcon({ status, color }: { status: DelegationStatus; color: string }) {
|
|
26
|
+
switch (status) {
|
|
27
|
+
case 'delegating':
|
|
28
|
+
return (
|
|
29
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" className="shrink-0">
|
|
30
|
+
<path d="M5 12h14" />
|
|
31
|
+
<path d="M12 5l7 7-7 7" />
|
|
32
|
+
</svg>
|
|
33
|
+
)
|
|
34
|
+
case 'checking':
|
|
35
|
+
return (
|
|
36
|
+
<span className="w-3.5 h-3.5 shrink-0 rounded-full border-2 animate-spin" style={{ borderColor: color, borderTopColor: 'transparent' }} />
|
|
37
|
+
)
|
|
38
|
+
case 'completed':
|
|
39
|
+
return (
|
|
40
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" className="shrink-0">
|
|
41
|
+
<polyline points="20 6 9 17 4 12" />
|
|
42
|
+
</svg>
|
|
43
|
+
)
|
|
44
|
+
case 'failed':
|
|
45
|
+
return (
|
|
46
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" className="shrink-0">
|
|
47
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
48
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
49
|
+
</svg>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function statusText(status: DelegationStatus, name: string): string {
|
|
55
|
+
switch (status) {
|
|
56
|
+
case 'delegating': return `Delegated to ${name}`
|
|
57
|
+
case 'checking': return `Checking on ${name}...`
|
|
58
|
+
case 'completed': return `${name} completed`
|
|
59
|
+
case 'failed': return `${name} failed`
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function DelegationBanner({ agentName, agentAvatarSeed, taskPreview, taskId, status }: DelegationBannerProps) {
|
|
64
|
+
const cfg = STATUS_CONFIG[status]
|
|
65
|
+
|
|
66
|
+
const handleTaskClick = () => {
|
|
67
|
+
if (!taskId) return
|
|
68
|
+
const store = useAppStore.getState()
|
|
69
|
+
store.loadTasks(true).then(() => {
|
|
70
|
+
store.setTaskSheetViewOnly(true)
|
|
71
|
+
store.setEditingTaskId(taskId)
|
|
72
|
+
store.setTaskSheetOpen(true)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className="rounded-[12px] px-3.5 py-2.5 flex items-center gap-2.5"
|
|
79
|
+
style={{
|
|
80
|
+
background: cfg.bg,
|
|
81
|
+
border: `1px solid ${cfg.border}`,
|
|
82
|
+
animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1)',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<div className="shrink-0" style={{ animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both' }}>
|
|
86
|
+
<AgentAvatar seed={agentAvatarSeed} name={agentName} size={24} />
|
|
87
|
+
</div>
|
|
88
|
+
<StatusIcon status={status} color={cfg.color} />
|
|
89
|
+
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
|
|
90
|
+
<span className="text-[12px] font-600" style={{ color: cfg.color }}>
|
|
91
|
+
{statusText(status, agentName)}
|
|
92
|
+
</span>
|
|
93
|
+
{taskPreview && (
|
|
94
|
+
<span className="text-[11px] text-text-3 truncate">{taskPreview}</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
{taskId && (
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
onClick={handleTaskClick}
|
|
101
|
+
className="shrink-0 text-[10px] font-600 px-2 py-1 rounded-[6px] cursor-pointer border-none transition-colors"
|
|
102
|
+
style={{
|
|
103
|
+
color: cfg.color,
|
|
104
|
+
background: `${cfg.color}15`,
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
View Task
|
|
108
|
+
</button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ---------- Task Completion Card ---------- */
|
|
115
|
+
|
|
116
|
+
export interface TaskCompletionInfo {
|
|
117
|
+
status: 'completed' | 'failed'
|
|
118
|
+
taskTitle: string
|
|
119
|
+
taskId: string | null
|
|
120
|
+
/** The agent that executed the task (present on delegated results) */
|
|
121
|
+
executorName: string | null
|
|
122
|
+
workingDir: string | null
|
|
123
|
+
resumeInfo: string | null
|
|
124
|
+
resultBody: string
|
|
125
|
+
imageUrl?: string
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const TASK_COMPLETION_RE = /^(?:Delegated )?[Tt]ask (completed|failed): \*\*\[([^\]]+)\]\(#task:([^)]+)\)\*\*(?:\s*\(by ([^)]+)\))?/
|
|
129
|
+
|
|
130
|
+
export function parseTaskCompletion(text: string): TaskCompletionInfo | null {
|
|
131
|
+
const m = text.match(TASK_COMPLETION_RE)
|
|
132
|
+
if (!m) return null
|
|
133
|
+
const status = m[1] as 'completed' | 'failed'
|
|
134
|
+
const taskTitle = m[2]
|
|
135
|
+
const taskId = m[3] || null
|
|
136
|
+
const executorName = m[4] || null
|
|
137
|
+
|
|
138
|
+
// Parse the body sections (separated by double newlines)
|
|
139
|
+
const bodyStart = text.indexOf('\n\n')
|
|
140
|
+
const sections = bodyStart === -1 ? [] : text.slice(bodyStart + 2).split('\n\n')
|
|
141
|
+
|
|
142
|
+
let workingDir: string | null = null
|
|
143
|
+
let resumeInfo: string | null = null
|
|
144
|
+
const resultParts: string[] = []
|
|
145
|
+
|
|
146
|
+
for (const section of sections) {
|
|
147
|
+
if (section.startsWith('Working directory: ')) {
|
|
148
|
+
workingDir = section.replace('Working directory: ', '').replace(/^`|`$/g, '')
|
|
149
|
+
} else if (/^(Claude session|Codex thread|OpenCode session|CLI session):/.test(section)) {
|
|
150
|
+
resumeInfo = section
|
|
151
|
+
} else if (section.trim()) {
|
|
152
|
+
resultParts.push(section)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { status, taskTitle, taskId, executorName, workingDir, resumeInfo, resultBody: resultParts.join('\n\n') }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function TaskCompletionCard({ info }: { info: TaskCompletionInfo }) {
|
|
160
|
+
const isSuccess = info.status === 'completed'
|
|
161
|
+
const [expanded, setExpanded] = useState(false)
|
|
162
|
+
|
|
163
|
+
const handleTaskClick = () => {
|
|
164
|
+
if (!info.taskId) return
|
|
165
|
+
const store = useAppStore.getState()
|
|
166
|
+
store.loadTasks(true).then(() => {
|
|
167
|
+
store.setTaskSheetViewOnly(true)
|
|
168
|
+
store.setEditingTaskId(info.taskId!)
|
|
169
|
+
store.setTaskSheetOpen(true)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Truncate result for preview
|
|
174
|
+
const resultPreview = info.resultBody.length > 200 ? info.resultBody.slice(0, 200) + '...' : info.resultBody
|
|
175
|
+
const hasLongResult = info.resultBody.length > 200
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div
|
|
179
|
+
className="rounded-[14px] overflow-hidden"
|
|
180
|
+
style={{
|
|
181
|
+
background: isSuccess ? 'rgba(52,211,153,0.04)' : 'rgba(244,63,94,0.04)',
|
|
182
|
+
border: `1px solid ${isSuccess ? 'rgba(52,211,153,0.15)' : 'rgba(244,63,94,0.15)'}`,
|
|
183
|
+
animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1)',
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
{/* Status header bar */}
|
|
187
|
+
<div
|
|
188
|
+
className="flex items-center gap-2.5 px-4 py-2.5"
|
|
189
|
+
style={{
|
|
190
|
+
background: isSuccess ? 'rgba(52,211,153,0.06)' : 'rgba(244,63,94,0.06)',
|
|
191
|
+
borderBottom: `1px solid ${isSuccess ? 'rgba(52,211,153,0.08)' : 'rgba(244,63,94,0.08)'}`,
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
{/* Status icon */}
|
|
195
|
+
{isSuccess ? (
|
|
196
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" className="shrink-0">
|
|
197
|
+
<circle cx="12" cy="12" r="10" stroke="#34D399" strokeWidth="2" />
|
|
198
|
+
<polyline points="8 12 11 15 16 9" stroke="#34D399" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
199
|
+
</svg>
|
|
200
|
+
) : (
|
|
201
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" className="shrink-0">
|
|
202
|
+
<circle cx="12" cy="12" r="10" stroke="#F43F5E" strokeWidth="2" />
|
|
203
|
+
<line x1="15" y1="9" x2="9" y2="15" stroke="#F43F5E" strokeWidth="2.5" strokeLinecap="round" />
|
|
204
|
+
<line x1="9" y1="9" x2="15" y2="15" stroke="#F43F5E" strokeWidth="2.5" strokeLinecap="round" />
|
|
205
|
+
</svg>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
|
|
209
|
+
<span className={`text-[12px] font-700 ${isSuccess ? 'text-emerald-400' : 'text-rose-400'}`}>
|
|
210
|
+
{info.executorName
|
|
211
|
+
? `${info.executorName} — task ${info.status}`
|
|
212
|
+
: `Task ${info.status}`
|
|
213
|
+
}
|
|
214
|
+
</span>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{info.taskId && (
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
onClick={handleTaskClick}
|
|
221
|
+
className="shrink-0 text-[10px] font-600 px-2.5 py-1 rounded-[6px] cursor-pointer border-none transition-colors"
|
|
222
|
+
style={{
|
|
223
|
+
color: isSuccess ? '#34D399' : '#F43F5E',
|
|
224
|
+
background: isSuccess ? 'rgba(52,211,153,0.1)' : 'rgba(244,63,94,0.1)',
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
View Task
|
|
228
|
+
</button>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* Body content */}
|
|
233
|
+
<div className="px-4 py-3 flex flex-col gap-2.5">
|
|
234
|
+
{/* Task title */}
|
|
235
|
+
<div className="flex items-start gap-2">
|
|
236
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0 mt-0.5 text-text-3/50">
|
|
237
|
+
<path d="M9 11l3 3L22 4" />
|
|
238
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
|
239
|
+
</svg>
|
|
240
|
+
{info.taskId ? (
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
onClick={handleTaskClick}
|
|
244
|
+
className={`text-[13px] font-600 ${isSuccess ? 'text-emerald-300 hover:text-emerald-200' : 'text-rose-300 hover:text-rose-200'} cursor-pointer bg-transparent border-none p-0 font-inherit text-left underline decoration-current/30 hover:decoration-current/60 transition-colors`}
|
|
245
|
+
>
|
|
246
|
+
{info.taskTitle}
|
|
247
|
+
</button>
|
|
248
|
+
) : (
|
|
249
|
+
<span className="text-[13px] font-600 text-text-2">{info.taskTitle}</span>
|
|
250
|
+
)}
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
{/* Working directory */}
|
|
254
|
+
{info.workingDir && (
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0 text-text-3/40">
|
|
257
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
258
|
+
</svg>
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
onClick={() => { api('POST', '/files/open', { path: info.workingDir }).catch(() => {}) }}
|
|
262
|
+
className="text-[11px] text-text-3/60 hover:text-text-3 font-mono truncate bg-transparent border-none p-0 cursor-pointer transition-colors"
|
|
263
|
+
title="Open folder"
|
|
264
|
+
>
|
|
265
|
+
{info.workingDir}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
|
|
270
|
+
{/* Resume info */}
|
|
271
|
+
{info.resumeInfo && (
|
|
272
|
+
<div className="flex items-center gap-2">
|
|
273
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0 text-text-3/40">
|
|
274
|
+
<polyline points="4 17 10 11 4 5" />
|
|
275
|
+
<line x1="12" y1="19" x2="20" y2="19" />
|
|
276
|
+
</svg>
|
|
277
|
+
<span className="text-[11px] text-text-3/60 font-mono truncate">{info.resumeInfo}</span>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
|
|
281
|
+
{/* Image artifact */}
|
|
282
|
+
{info.imageUrl && (
|
|
283
|
+
<div className="mt-0.5">
|
|
284
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
285
|
+
<img
|
|
286
|
+
src={info.imageUrl}
|
|
287
|
+
alt="Task result"
|
|
288
|
+
loading="lazy"
|
|
289
|
+
className="max-w-full rounded-[10px] border border-white/[0.06]"
|
|
290
|
+
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
|
|
295
|
+
{/* Result body */}
|
|
296
|
+
{info.resultBody && (
|
|
297
|
+
<div className="mt-0.5">
|
|
298
|
+
<div className="rounded-[10px] bg-white/[0.02] border border-white/[0.04] px-3 py-2.5">
|
|
299
|
+
<pre className="text-[12px] leading-[1.6] text-text-3/80 whitespace-pre-wrap break-words m-0 font-mono">
|
|
300
|
+
{expanded ? info.resultBody : resultPreview}
|
|
301
|
+
</pre>
|
|
302
|
+
{hasLongResult && (
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
onClick={() => setExpanded((v) => !v)}
|
|
306
|
+
className="mt-2 text-[11px] font-600 text-text-3/50 hover:text-text-3 bg-transparent border-none cursor-pointer p-0 transition-colors"
|
|
307
|
+
>
|
|
308
|
+
{expanded ? 'Show less' : 'Show full result'}
|
|
309
|
+
</button>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* ---------- Delegation Source Banner ---------- */
|
|
320
|
+
|
|
321
|
+
interface DelegationSourceBannerProps {
|
|
322
|
+
delegatorName: string
|
|
323
|
+
delegatorAvatarSeed: string | null
|
|
324
|
+
taskTitle: string
|
|
325
|
+
taskId: string | null
|
|
326
|
+
description: string
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function DelegationSourceBanner({ delegatorName, delegatorAvatarSeed, taskTitle, taskId, description }: DelegationSourceBannerProps) {
|
|
330
|
+
const handleTaskClick = () => {
|
|
331
|
+
if (!taskId) return
|
|
332
|
+
const store = useAppStore.getState()
|
|
333
|
+
store.loadTasks(true).then(() => {
|
|
334
|
+
store.setTaskSheetViewOnly(true)
|
|
335
|
+
store.setEditingTaskId(taskId)
|
|
336
|
+
store.setTaskSheetOpen(true)
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div
|
|
342
|
+
className="rounded-[12px] px-3.5 py-2.5 flex items-start gap-2.5 bg-indigo-500/[0.05] border border-indigo-500/[0.12]"
|
|
343
|
+
style={{ animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
344
|
+
>
|
|
345
|
+
<div className="shrink-0 mt-0.5" style={{ animation: 'delegation-handoff-in 0.45s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both' }}>
|
|
346
|
+
<AgentAvatar seed={delegatorAvatarSeed} name={delegatorName} size={24} />
|
|
347
|
+
</div>
|
|
348
|
+
<div className="flex flex-col gap-1 min-w-0 flex-1">
|
|
349
|
+
<span className="text-[12px] font-600 text-indigo-400">
|
|
350
|
+
Delegated by {delegatorName}
|
|
351
|
+
</span>
|
|
352
|
+
{taskTitle && (
|
|
353
|
+
taskId ? (
|
|
354
|
+
<button
|
|
355
|
+
type="button"
|
|
356
|
+
onClick={handleTaskClick}
|
|
357
|
+
className="text-[12px] text-indigo-300 hover:text-indigo-200 underline cursor-pointer bg-transparent border-none p-0 font-inherit text-left truncate"
|
|
358
|
+
>
|
|
359
|
+
{taskTitle}
|
|
360
|
+
</button>
|
|
361
|
+
) : (
|
|
362
|
+
<span className="text-[12px] text-text-2 truncate">{taskTitle}</span>
|
|
363
|
+
)
|
|
364
|
+
)}
|
|
365
|
+
{description && (
|
|
366
|
+
<span className="text-[11px] text-text-3 line-clamp-2">{description}</span>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
)
|
|
371
|
+
}
|
|
@@ -49,13 +49,34 @@ export function FilePathChip({ filePath }: { filePath: string }) {
|
|
|
49
49
|
? serverState.framework.charAt(0).toUpperCase() + serverState.framework.slice(1)
|
|
50
50
|
: null
|
|
51
51
|
|
|
52
|
+
const isDir = !PREVIEWABLE_EXT.test(filePath) && !filePath.includes('.')
|
|
53
|
+
|
|
54
|
+
const handleReveal = () => {
|
|
55
|
+
api('POST', '/files/open', { path: filePath }).catch(() => { /* best-effort */ })
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
return (
|
|
53
59
|
<span className="inline-flex items-center gap-1.5 px-2 py-1 rounded-[8px] bg-white/[0.06] border border-white/[0.08] font-mono text-[13px]">
|
|
54
60
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/50 shrink-0">
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
{isDir ? (
|
|
62
|
+
<><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /></>
|
|
63
|
+
) : (
|
|
64
|
+
<><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /></>
|
|
65
|
+
)}
|
|
57
66
|
</svg>
|
|
58
67
|
<span className="text-sky-400">{filePath}</span>
|
|
68
|
+
<button
|
|
69
|
+
onClick={handleReveal}
|
|
70
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[4px] bg-white/[0.06] hover:bg-white/[0.10] text-[10px] font-600 text-text-3 hover:text-text-2 border-none transition-colors cursor-pointer"
|
|
71
|
+
title={isDir ? 'Open folder' : 'Reveal in file manager'}
|
|
72
|
+
>
|
|
73
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
74
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
75
|
+
<polyline points="15 3 21 3 21 9" />
|
|
76
|
+
<line x1="10" y1="14" x2="21" y2="3" />
|
|
77
|
+
</svg>
|
|
78
|
+
{isDir ? 'Open' : 'Reveal'}
|
|
79
|
+
</button>
|
|
59
80
|
{canPreview && !serverState.running && (
|
|
60
81
|
<a
|
|
61
82
|
href={serveUrl}
|