@swarmclawai/swarmclaw 0.6.7 → 0.7.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 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
import { setStoredAccessKey } from '@/lib/api-client'
|
|
5
5
|
import { fetchWithTimeout } from '@/lib/fetch-timeout'
|
|
6
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
6
7
|
|
|
7
8
|
interface AccessKeyGateProps {
|
|
8
9
|
onAuthenticated: () => void
|
|
@@ -42,7 +43,8 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
42
43
|
|
|
43
44
|
const handleCopyKey = async () => {
|
|
44
45
|
try {
|
|
45
|
-
await
|
|
46
|
+
const copiedKey = await copyTextToClipboard(generatedKey)
|
|
47
|
+
if (!copiedKey) return
|
|
46
48
|
setCopied(true)
|
|
47
49
|
setTimeout(() => setCopied(false), 2000)
|
|
48
50
|
} catch {
|
|
@@ -121,12 +123,9 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
121
123
|
/>
|
|
122
124
|
</div>
|
|
123
125
|
|
|
124
|
-
<div
|
|
125
|
-
className="relative max-w-[440px] w-full text-center"
|
|
126
|
-
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
127
|
-
>
|
|
126
|
+
<div className="relative max-w-[440px] w-full text-center">
|
|
128
127
|
{/* Lock / Key icon */}
|
|
129
|
-
<div className="flex justify-center mb-6">
|
|
128
|
+
<div className="flex justify-center mb-6" style={{ animation: 'spring-in 0.6s var(--ease-spring)' }}>
|
|
130
129
|
<div className="relative w-12 h-12 flex items-center justify-center">
|
|
131
130
|
<svg
|
|
132
131
|
width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
@@ -151,15 +150,17 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
151
150
|
{firstTime ? (
|
|
152
151
|
/* ── First-time setup: show the generated key ── */
|
|
153
152
|
<>
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
154
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
155
|
+
Your Access Key
|
|
156
|
+
</h1>
|
|
157
|
+
<p className="text-[14px] text-text-2 mb-8">
|
|
158
|
+
This key was generated for your server. Copy it somewhere safe — you'll need it to connect from other devices.
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
160
161
|
|
|
161
162
|
{/* Key display */}
|
|
162
|
-
<div className="mb-3">
|
|
163
|
+
<div className="mb-3" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both' }}>
|
|
163
164
|
<div
|
|
164
165
|
className="inline-flex items-center gap-3 px-5 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface
|
|
165
166
|
cursor-pointer hover:border-accent-bright/20 transition-all duration-200"
|
|
@@ -185,7 +186,7 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
185
186
|
</div>
|
|
186
187
|
</div>
|
|
187
188
|
|
|
188
|
-
<div className="relative h-5 mb-8">
|
|
189
|
+
<div className="relative h-5 mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
189
190
|
<p
|
|
190
191
|
className="absolute inset-x-0 text-[12px] transition-all duration-300"
|
|
191
192
|
style={{
|
|
@@ -207,56 +208,64 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
207
208
|
</p>
|
|
208
209
|
</div>
|
|
209
210
|
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
|
|
212
|
+
<button
|
|
213
|
+
onClick={handleClaimKey}
|
|
214
|
+
disabled={loading}
|
|
215
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
216
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
217
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
218
|
+
>
|
|
219
|
+
{loading ? 'Connecting...' : 'Continue'}
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
219
222
|
</>
|
|
220
223
|
) : (
|
|
221
224
|
/* ── Returning user: enter key ── */
|
|
222
225
|
<>
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
227
|
+
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
228
|
+
Connect
|
|
229
|
+
</h1>
|
|
230
|
+
<p className="text-[14px] text-text-2 mb-2">
|
|
231
|
+
Enter the access key to connect to this server.
|
|
232
|
+
</p>
|
|
233
|
+
<p className="text-[12px] text-text-3 mb-8">
|
|
234
|
+
You can find it in <code className="text-text-2">.env.local</code> in the project root.
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
232
237
|
|
|
233
238
|
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-4">
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both', width: '100%', display: 'flex', justifyContent: 'center' }}>
|
|
240
|
+
<input
|
|
241
|
+
type="password"
|
|
242
|
+
value={key}
|
|
243
|
+
onChange={(e) => { setKey(e.target.value); setError('') }}
|
|
244
|
+
placeholder="Access key"
|
|
245
|
+
autoFocus
|
|
246
|
+
autoComplete="off"
|
|
247
|
+
className="w-full max-w-[320px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
|
|
248
|
+
text-text text-[16px] text-center font-mono outline-none
|
|
249
|
+
transition-all duration-200 placeholder:text-text-3/70
|
|
250
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
246
253
|
|
|
247
254
|
{error && (
|
|
248
|
-
<p className="text-[13px] text-red-400">{error}</p>
|
|
255
|
+
<p className="text-[13px] text-red-400" style={{ animation: 'ai-shake 0.5s' }}>{error}</p>
|
|
249
256
|
)}
|
|
250
257
|
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
259
|
+
<button
|
|
260
|
+
type="submit"
|
|
261
|
+
disabled={!key.trim() || loading}
|
|
262
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
263
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
264
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
265
|
+
>
|
|
266
|
+
{loading ? 'Connecting...' : 'Connect'}
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
260
269
|
</form>
|
|
261
270
|
</>
|
|
262
271
|
)}
|
|
@@ -39,11 +39,10 @@ export function UserPicker() {
|
|
|
39
39
|
}} />
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
|
-
<div className="relative max-w-[420px] w-full text-center"
|
|
43
|
-
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
42
|
+
<div className="relative max-w-[420px] w-full text-center">
|
|
44
43
|
|
|
45
44
|
{/* Sparkle icon */}
|
|
46
|
-
<div className="flex justify-center mb-6">
|
|
45
|
+
<div className="flex justify-center mb-6" style={{ animation: 'spring-in 0.6s var(--ease-spring)' }}>
|
|
47
46
|
<div className="relative w-12 h-12">
|
|
48
47
|
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
|
|
49
48
|
style={{ animation: 'sparkle-spin 8s linear infinite' }}>
|
|
@@ -54,29 +53,33 @@ export function UserPicker() {
|
|
|
54
53
|
</div>
|
|
55
54
|
</div>
|
|
56
55
|
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.1s both' }}>
|
|
57
|
+
<h1 className="font-display text-[42px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
58
|
+
Welcome
|
|
59
|
+
</h1>
|
|
60
|
+
<p className="text-[15px] text-text-2 mb-10">
|
|
61
|
+
What should we call you?
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
63
64
|
|
|
64
65
|
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-5">
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.2s both', width: '100%', display: 'flex', justifyContent: 'center' }}>
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
value={name}
|
|
70
|
+
onChange={(e) => setName(e.target.value)}
|
|
71
|
+
placeholder="Your name"
|
|
72
|
+
autoFocus
|
|
73
|
+
className="w-full max-w-[280px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
|
|
74
|
+
text-text text-[18px] text-center font-display font-600 outline-none
|
|
75
|
+
transition-all duration-200 placeholder:text-text-3/70
|
|
76
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
77
|
+
style={{ fontFamily: 'inherit' }}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
77
80
|
|
|
78
81
|
{/* Avatar picker */}
|
|
79
|
-
<div className="flex flex-col items-center gap-3">
|
|
82
|
+
<div className="flex flex-col items-center gap-3" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
|
|
80
83
|
<AgentAvatar seed={avatarSeed || null} name={name || '?'} size={64} />
|
|
81
84
|
<div className="flex items-center gap-2">
|
|
82
85
|
<input
|
|
@@ -99,16 +102,18 @@ export function UserPicker() {
|
|
|
99
102
|
</div>
|
|
100
103
|
</div>
|
|
101
104
|
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
<div style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}>
|
|
106
|
+
<button
|
|
107
|
+
type="submit"
|
|
108
|
+
disabled={!name.trim()}
|
|
109
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
110
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
111
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
112
|
+
style={{ fontFamily: 'inherit' }}
|
|
113
|
+
>
|
|
114
|
+
Get Started
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
112
117
|
</form>
|
|
113
118
|
</div>
|
|
114
119
|
</div>
|
|
@@ -7,6 +7,7 @@ const NOTABLE_TOOLS: Record<string, { label: string; color: string; icon: 'brain
|
|
|
7
7
|
memory_tool: { label: 'Committed to memory', color: '#A855F7', icon: 'brain' },
|
|
8
8
|
manage_tasks: { label: 'Created a task', color: '#EC4899', icon: 'clipboard' },
|
|
9
9
|
manage_schedules: { label: 'Scheduled something', color: '#EC4899', icon: 'clipboard' },
|
|
10
|
+
schedule_wake: { label: 'Set a reminder', color: '#F59E0B', icon: 'clipboard' },
|
|
10
11
|
manage_agents: { label: 'Created an agent', color: '#EC4899', icon: 'clipboard' },
|
|
11
12
|
delegate_to_claude_code: { label: 'Delegated to Claude Code', color: '#38BDF8', icon: 'delegate' },
|
|
12
13
|
delegate_to_codex_cli: { label: 'Delegated to Codex', color: '#38BDF8', icon: 'delegate' },
|
|
@@ -24,6 +25,7 @@ function extractSnippet(toolName: string, toolInput: string): string | null {
|
|
|
24
25
|
if ((toolName === 'memory' || toolName === 'memory_tool') && parsed.key) return parsed.key
|
|
25
26
|
if (toolName === 'manage_tasks' && parsed.title) return parsed.title
|
|
26
27
|
if (toolName === 'manage_schedules' && parsed.name) return parsed.name
|
|
28
|
+
if (toolName === 'schedule_wake' && parsed.message) return parsed.message
|
|
27
29
|
if (toolName === 'manage_agents' && parsed.name) return parsed.name
|
|
28
30
|
if (toolName === 'delegate_to_agent' && (parsed.agentName || parsed.agentId)) return parsed.agentName || parsed.agentId
|
|
29
31
|
if (toolName === 'check_delegation_status' && parsed.agentName) return parsed.agentName
|
|
@@ -21,6 +21,7 @@ import { HeartbeatHistoryPanel } from './heartbeat-history-panel'
|
|
|
21
21
|
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
22
22
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
23
23
|
import { speak } from '@/lib/tts'
|
|
24
|
+
import { api } from '@/lib/api-client'
|
|
24
25
|
|
|
25
26
|
const PROMPT_SUGGESTIONS = [
|
|
26
27
|
{ text: 'What can you help me with?', icon: 'book', gradient: 'from-[#6366F1]/10 to-[#818CF8]/5' },
|
|
@@ -66,6 +67,15 @@ export function ChatArea() {
|
|
|
66
67
|
const [heartbeatHistoryOpen, setHeartbeatHistoryOpen] = useState(false)
|
|
67
68
|
const [messagesLoading, setMessagesLoading] = useState(true)
|
|
68
69
|
const [connectorFilter, setConnectorFilter] = useState<string | null>(null)
|
|
70
|
+
const [pluginChatActions, setPluginChatActions] = useState<Array<{ id: string; label: string; action: string; value: string; tooltip?: string }>>([])
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (sessionId) {
|
|
74
|
+
api<Array<{ id: string; label: string; action: string; value: string; tooltip?: string }>>('GET', '/plugins/ui?type=chat_actions').then(actions => {
|
|
75
|
+
if (Array.isArray(actions)) setPluginChatActions(actions)
|
|
76
|
+
}).catch(() => {})
|
|
77
|
+
}
|
|
78
|
+
}, [sessionId])
|
|
69
79
|
|
|
70
80
|
// Collect unique connector sources from messages for filter UI
|
|
71
81
|
const { connectorSources, hasDirectMessages } = useMemo(() => {
|
|
@@ -421,6 +431,7 @@ export function ChatArea() {
|
|
|
421
431
|
streaming={streamingForThisSession}
|
|
422
432
|
onSend={sendMessage}
|
|
423
433
|
onStop={stopStreaming}
|
|
434
|
+
pluginChatActions={pluginChatActions}
|
|
424
435
|
/>
|
|
425
436
|
|
|
426
437
|
<Dropdown open={menuOpen} onClose={() => setMenuOpen(false)}>
|
|
@@ -5,7 +5,6 @@ import type { Session } from '@/types'
|
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { IconButton } from '@/components/shared/icon-button'
|
|
8
|
-
import { UsageBadge } from '@/components/shared/usage-badge'
|
|
9
8
|
import { ChatToolToggles } from './chat-tool-toggles'
|
|
10
9
|
import { api } from '@/lib/api-client'
|
|
11
10
|
import {
|
|
@@ -17,6 +16,7 @@ import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
|
17
16
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
18
17
|
import { toast } from 'sonner'
|
|
19
18
|
import type { ProviderType } from '@/types'
|
|
19
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
20
20
|
import { useWs } from '@/hooks/use-ws'
|
|
21
21
|
|
|
22
22
|
function shortPath(p: string): string {
|
|
@@ -67,7 +67,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
67
67
|
const toggleSound = useChatStore((s) => s.toggleSound)
|
|
68
68
|
const debugOpen = useChatStore((s) => s.debugOpen)
|
|
69
69
|
const setDebugOpen = useChatStore((s) => s.setDebugOpen)
|
|
70
|
-
const lastUsage = useChatStore((s) => s.lastUsage)
|
|
71
70
|
const agentStatus = useChatStore((s) => s.agentStatus)
|
|
72
71
|
const agents = useAppStore((s) => s.agents)
|
|
73
72
|
const tasks = useAppStore((s) => s.tasks)
|
|
@@ -109,18 +108,50 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
109
108
|
const renameContainerRef = useRef<HTMLSpanElement>(null)
|
|
110
109
|
const setWalletPanelAgentId = useAppStore((s) => s.setWalletPanelAgentId)
|
|
111
110
|
const [walletBalance, setWalletBalance] = useState<number | null>(null)
|
|
111
|
+
const [headerWidgets, setHeaderWidgets] = useState<Array<{ id: string; label: string; icon?: string }>>([])
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
api<Array<{ id: string; label: string; icon?: string }>>('GET', `/plugins/ui?type=header&sessionId=${session.id}`).then(widgets => {
|
|
115
|
+
if (Array.isArray(widgets)) setHeaderWidgets(widgets)
|
|
116
|
+
}).catch(() => {})
|
|
117
|
+
}, [session.id])
|
|
112
118
|
|
|
113
119
|
const fetchWalletBalance = useCallback(async () => {
|
|
114
|
-
if (!agent?.walletId) {
|
|
120
|
+
if (!agent?.walletId) {
|
|
121
|
+
setWalletBalance(null)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
115
124
|
try {
|
|
116
125
|
const data = await api<{ balanceSol?: number }>('GET', `/wallets/${agent.walletId}`)
|
|
117
126
|
setWalletBalance(data.balanceSol ?? null)
|
|
118
|
-
} catch {
|
|
127
|
+
} catch {
|
|
128
|
+
setWalletBalance(null)
|
|
129
|
+
}
|
|
119
130
|
}, [agent?.walletId])
|
|
120
131
|
|
|
121
|
-
useEffect(() => {
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
void fetchWalletBalance()
|
|
134
|
+
}, [fetchWalletBalance])
|
|
122
135
|
useWs('wallets', fetchWalletBalance)
|
|
123
136
|
|
|
137
|
+
|
|
138
|
+
const visibleHeaderWidgets = useMemo(() => {
|
|
139
|
+
const seen = new Set<string>()
|
|
140
|
+
return headerWidgets.filter((widget) => {
|
|
141
|
+
const key = widget.id || widget.label
|
|
142
|
+
if (seen.has(key)) return false
|
|
143
|
+
seen.add(key)
|
|
144
|
+
return true
|
|
145
|
+
})
|
|
146
|
+
}, [headerWidgets])
|
|
147
|
+
|
|
148
|
+
const handleHeaderWidgetClick = (widgetId: string) => {
|
|
149
|
+
if (widgetId === 'wallet-status') {
|
|
150
|
+
if (agent?.id) setWalletPanelAgentId(agent.id)
|
|
151
|
+
setActiveView('wallets')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
// Find linked task for this session
|
|
125
156
|
const linkedTask = useMemo(() => {
|
|
126
157
|
return Object.values(tasks).find((t) => t.sessionId === session.id)
|
|
@@ -156,9 +187,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
156
187
|
|
|
157
188
|
const handleCopySessionId = () => {
|
|
158
189
|
if (!resumeHandle) return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
void copyTextToClipboard(resumeHandle.command).then((copiedCommand) => {
|
|
191
|
+
if (!copiedCommand) return
|
|
192
|
+
setCopied(true)
|
|
193
|
+
setTimeout(() => setCopied(false), 2000)
|
|
194
|
+
})
|
|
162
195
|
}
|
|
163
196
|
|
|
164
197
|
const handleDismissResumeHandle = async (e: React.MouseEvent) => {
|
|
@@ -606,21 +639,37 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
606
639
|
{/* Metadata tray: wallet · model · path · status */}
|
|
607
640
|
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
|
|
608
641
|
<span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
|
|
609
|
-
{
|
|
610
|
-
|
|
642
|
+
{visibleHeaderWidgets.map((widget) => {
|
|
643
|
+
const actionable = widget.id === 'wallet-status'
|
|
644
|
+
const walletLabel = walletBalance !== null
|
|
645
|
+
? `${walletBalance.toFixed(3)} SOL`
|
|
646
|
+
: (widget.label || 'Wallet')
|
|
647
|
+
return (
|
|
611
648
|
<button
|
|
649
|
+
key={widget.id}
|
|
612
650
|
type="button"
|
|
613
|
-
onClick={() =>
|
|
614
|
-
className=
|
|
615
|
-
|
|
651
|
+
onClick={actionable ? () => handleHeaderWidgetClick(widget.id) : undefined}
|
|
652
|
+
className={`inline-flex items-center gap-1 shrink-0 bg-transparent border-none p-0.5 rounded-[4px] text-[11px] font-mono transition-colors ${
|
|
653
|
+
actionable ? 'cursor-pointer text-text-3/45 hover:text-text-3/70 hover:bg-white/[0.04]' : 'cursor-default text-text-3/55'
|
|
654
|
+
}`}
|
|
655
|
+
title={actionable ? 'View wallet' : widget.label}
|
|
616
656
|
>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
657
|
+
{actionable ? (
|
|
658
|
+
<>
|
|
659
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
660
|
+
<rect x="2" y="6" width="20" height="14" rx="2" />
|
|
661
|
+
<path d="M22 10H18a2 2 0 0 0 0 4h4" />
|
|
662
|
+
</svg>
|
|
663
|
+
{walletLabel}
|
|
664
|
+
</>
|
|
665
|
+
) : (
|
|
666
|
+
widget.label
|
|
667
|
+
)}
|
|
621
668
|
</button>
|
|
622
|
-
|
|
623
|
-
|
|
669
|
+
)
|
|
670
|
+
})}
|
|
671
|
+
{visibleHeaderWidgets.length > 0 && (
|
|
672
|
+
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
624
673
|
)}
|
|
625
674
|
{modelName && (
|
|
626
675
|
<div className="relative shrink-0" ref={modelSwitcherRef}>
|
|
@@ -668,12 +717,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
668
717
|
)}
|
|
669
718
|
</div>
|
|
670
719
|
)}
|
|
671
|
-
{lastUsage && !streaming && (
|
|
672
|
-
<>
|
|
673
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
674
|
-
<UsageBadge {...lastUsage} />
|
|
675
|
-
</>
|
|
676
|
-
)}
|
|
677
720
|
<button
|
|
678
721
|
type="button"
|
|
679
722
|
onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
|
|
@@ -855,6 +898,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
855
898
|
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
856
899
|
{missionPaused ? 'Paused' : 'Live'}
|
|
857
900
|
</button>
|
|
901
|
+
|
|
858
902
|
<button
|
|
859
903
|
onClick={handleToggleMissionMode}
|
|
860
904
|
disabled={mainLoopSaving}
|
|
@@ -8,8 +8,8 @@ import type { ToolDefinition } from '@/lib/tool-definitions'
|
|
|
8
8
|
import type { Session } from '@/types'
|
|
9
9
|
|
|
10
10
|
const TOOL_GROUPS: { label: string; tools: ToolDefinition[] }[] = [
|
|
11
|
-
{ label: '
|
|
12
|
-
{ label: 'Platform', tools: PLATFORM_TOOLS },
|
|
11
|
+
{ label: 'Plugins', tools: AVAILABLE_TOOLS },
|
|
12
|
+
{ label: 'Platform Plugins', tools: PLATFORM_TOOLS },
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
const TOTAL_TOOL_COUNT = AVAILABLE_TOOLS.length + PLATFORM_TOOLS.length
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { api } from '@/lib/api-client'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { toast } from 'sonner'
|
|
7
|
+
|
|
8
|
+
interface Checkpoint {
|
|
9
|
+
checkpointId: string
|
|
10
|
+
parentCheckpointId?: string
|
|
11
|
+
metadata: Record<string, unknown>
|
|
12
|
+
createdAt: number
|
|
13
|
+
values?: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
sessionId: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function CheckpointTimeline({ sessionId }: Props) {
|
|
21
|
+
const [checkpoints, setCheckpoints] = useState<Checkpoint[]>([])
|
|
22
|
+
const [loading, setLoading] = useState(true)
|
|
23
|
+
const [restoringId, setRestoringId] = useState<string | null>(null)
|
|
24
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
25
|
+
|
|
26
|
+
const load = async () => {
|
|
27
|
+
setLoading(true)
|
|
28
|
+
try {
|
|
29
|
+
const data = await api<Checkpoint[]>('GET', `/sessions/${sessionId}/checkpoints`)
|
|
30
|
+
setCheckpoints(data)
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('Failed to load checkpoints', err)
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
load()
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
}, [sessionId])
|
|
42
|
+
|
|
43
|
+
const handleRestore = async (checkpoint: Checkpoint) => {
|
|
44
|
+
if (!confirm('Restore session to this point? This will delete all subsequent history.')) return
|
|
45
|
+
|
|
46
|
+
setRestoringId(checkpoint.checkpointId)
|
|
47
|
+
try {
|
|
48
|
+
await api('POST', `/sessions/${sessionId}/restore`, {
|
|
49
|
+
checkpointId: checkpoint.checkpointId,
|
|
50
|
+
timestamp: checkpoint.createdAt
|
|
51
|
+
})
|
|
52
|
+
toast.success('Session restored successfully')
|
|
53
|
+
await loadSessions()
|
|
54
|
+
await load()
|
|
55
|
+
} catch (err) {
|
|
56
|
+
toast.error('Failed to restore session')
|
|
57
|
+
console.error(err)
|
|
58
|
+
} finally {
|
|
59
|
+
setRestoringId(null)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (loading) {
|
|
64
|
+
return <div className="p-8 text-center text-text-3 text-[13px]">Retrieving history...</div>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (checkpoints.length === 0) {
|
|
68
|
+
return (
|
|
69
|
+
<div className="p-8 text-center">
|
|
70
|
+
<p className="text-text-3 text-[13px]">No checkpoints found for this session.</p>
|
|
71
|
+
<p className="text-[11px] text-text-3/50 mt-1">Only LangGraph-orchestrated sessions support time travel.</p>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex flex-col gap-3 p-5">
|
|
78
|
+
{checkpoints.map((cp, i) => (
|
|
79
|
+
<div
|
|
80
|
+
key={cp.checkpointId}
|
|
81
|
+
className="group relative flex flex-col gap-2 p-3 rounded-[12px] border border-white/[0.06] bg-white/[0.02] hover:bg-white/[0.04] transition-all"
|
|
82
|
+
>
|
|
83
|
+
<div className="flex items-center justify-between">
|
|
84
|
+
<div className="flex flex-col">
|
|
85
|
+
<span className="text-[11px] font-700 text-accent-bright uppercase tracking-wider">
|
|
86
|
+
{i === 0 ? 'Current State' : `Point ${checkpoints.length - i}`}
|
|
87
|
+
</span>
|
|
88
|
+
<span className="text-[10px] text-text-3 font-mono">
|
|
89
|
+
{new Date(cp.createdAt).toLocaleString()}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
{i > 0 && (
|
|
93
|
+
<button
|
|
94
|
+
onClick={() => handleRestore(cp)}
|
|
95
|
+
disabled={!!restoringId}
|
|
96
|
+
className="px-3 py-1 rounded-[6px] bg-accent-soft text-accent-bright text-[11px] font-600 border-none cursor-pointer hover:brightness-110 disabled:opacity-50"
|
|
97
|
+
>
|
|
98
|
+
{restoringId === cp.checkpointId ? 'Restoring...' : 'Restore here'}
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{cp.values && Array.isArray(cp.values.messages) && cp.values.messages.length > 0 && (
|
|
104
|
+
<div className="mt-1 p-2 rounded-[8px] bg-black/20 text-[11px] text-text-3 line-clamp-2 italic">
|
|
105
|
+
Last message: {String((cp.values.messages[cp.values.messages.length - 1] as Record<string, unknown>)?.content ?? 'Empty state')}
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState, type ReactNode } from 'react'
|
|
4
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
4
5
|
|
|
5
6
|
function extractText(node: ReactNode): string {
|
|
6
7
|
if (typeof node === 'string') return node
|
|
@@ -29,7 +30,8 @@ export function CodeBlock({ children, className }: Props) {
|
|
|
29
30
|
const getText = useCallback(() => extractText(children), [children])
|
|
30
31
|
|
|
31
32
|
const handleCopy = useCallback(() => {
|
|
32
|
-
|
|
33
|
+
void copyTextToClipboard(getText()).then((copiedText) => {
|
|
34
|
+
if (!copiedText) return
|
|
33
35
|
setCopied(true)
|
|
34
36
|
setTimeout(() => setCopied(false), 2000)
|
|
35
37
|
})
|