@swarmclawai/swarmclaw 0.4.5 → 0.5.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 +8 -2
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +5 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +23 -5
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/connectors/[id]/route.ts +7 -4
- package/src/app/api/connectors/[id]/webhook/route.ts +6 -2
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +4 -3
- package/src/app/api/openclaw/discover/route.ts +3 -2
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +3 -2
- package/src/app/api/projects/[id]/route.ts +1 -1
- package/src/app/api/projects/route.ts +1 -1
- package/src/app/api/secrets/[id]/route.ts +1 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +18 -1
- package/src/app/api/sessions/[id]/route.ts +12 -3
- package/src/app/api/sessions/route.ts +6 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/cli/index.js +33 -1
- package/src/cli/spec.js +40 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +82 -3
- package/src/components/agents/agent-sheet.tsx +31 -0
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +14 -2
- package/src/components/chat/chat-header.tsx +168 -4
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +89 -16
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/projects/project-list.tsx +1 -0
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/settings/settings-page.tsx +1 -0
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +42 -5
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/providers/openclaw.ts +17 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/chat-execution.ts +44 -2
- package/src/lib/server/connectors/bluebubbles.test.ts +17 -8
- package/src/lib/server/connectors/bluebubbles.ts +5 -2
- package/src/lib/server/connectors/googlechat.ts +14 -10
- package/src/lib/server/connectors/manager.ts +37 -15
- package/src/lib/server/connectors/openclaw.ts +1 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/main-agent-loop.ts +2 -3
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +11 -10
- package/src/lib/server/queue.ts +2 -1
- package/src/lib/server/session-tools/connector.ts +4 -4
- package/src/lib/server/session-tools/delegate.ts +19 -3
- package/src/lib/server/session-tools/file.ts +20 -20
- package/src/lib/server/session-tools/index.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +6 -6
- package/src/lib/server/session-tools/sandbox.ts +2 -2
- package/src/lib/server/session-tools/search-providers.ts +13 -6
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +8 -8
- package/src/lib/server/storage.ts +62 -11
- package/src/lib/server/stream-agent-chat.ts +24 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/stores/use-app-store.ts +53 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +153 -5
- package/src/types/index.ts +127 -1
- package/tsconfig.json +13 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { create } from 'zustand'
|
|
4
|
-
import type { Sessions, Session, NetworkInfo, Directory, ProviderInfo, Credentials, Agent, Schedule, AppView, BoardTask, AppSettings, OrchestratorSecret, ProviderConfig, Skill, Connector, Webhook, McpServerConfig, PluginMeta, Project } from '../types'
|
|
4
|
+
import type { Sessions, Session, NetworkInfo, Directory, ProviderInfo, Credentials, Agent, Schedule, AppView, BoardTask, AppSettings, OrchestratorSecret, ProviderConfig, Skill, Connector, Webhook, McpServerConfig, PluginMeta, Project, FleetFilter } from '../types'
|
|
5
5
|
import { fetchSessions, fetchDirs, fetchProviders, fetchCredentials } from '../lib/sessions'
|
|
6
6
|
import { fetchAgents } from '../lib/agents'
|
|
7
7
|
import { fetchSchedules } from '../lib/schedules'
|
|
@@ -22,6 +22,7 @@ interface AppState {
|
|
|
22
22
|
clearSessions: (ids: string[]) => Promise<void>
|
|
23
23
|
togglePinSession: (id: string) => void
|
|
24
24
|
updateSessionInStore: (session: Session) => void
|
|
25
|
+
forkSession: (sessionId: string, messageIndex: number) => Promise<string | null>
|
|
25
26
|
|
|
26
27
|
sidebarOpen: boolean
|
|
27
28
|
setSidebarOpen: (open: boolean) => void
|
|
@@ -161,6 +162,22 @@ interface AppState {
|
|
|
161
162
|
activeProjectFilter: string | null
|
|
162
163
|
setActiveProjectFilter: (id: string | null) => void
|
|
163
164
|
|
|
165
|
+
// Agent trash
|
|
166
|
+
trashedAgents: Record<string, Agent>
|
|
167
|
+
loadTrashedAgents: () => Promise<void>
|
|
168
|
+
showTrash: boolean
|
|
169
|
+
setShowTrash: (show: boolean) => void
|
|
170
|
+
|
|
171
|
+
// Inspector panel
|
|
172
|
+
inspectorOpen: boolean
|
|
173
|
+
setInspectorOpen: (open: boolean) => void
|
|
174
|
+
inspectorTab: 'overview' | 'files' | 'skills' | 'automations' | 'advanced'
|
|
175
|
+
setInspectorTab: (tab: 'overview' | 'files' | 'skills' | 'automations' | 'advanced') => void
|
|
176
|
+
|
|
177
|
+
// Fleet sidebar filter (F16)
|
|
178
|
+
fleetFilter: FleetFilter
|
|
179
|
+
setFleetFilter: (filter: FleetFilter) => void
|
|
180
|
+
|
|
164
181
|
}
|
|
165
182
|
|
|
166
183
|
export const useAppStore = create<AppState>((set, get) => ({
|
|
@@ -212,6 +229,18 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
212
229
|
updateSessionInStore: (session) => {
|
|
213
230
|
set({ sessions: { ...get().sessions, [session.id]: session } })
|
|
214
231
|
},
|
|
232
|
+
forkSession: async (sessionId, messageIndex) => {
|
|
233
|
+
try {
|
|
234
|
+
const forked = await api<Session>('POST', `/sessions/${sessionId}/fork`, { messageIndex })
|
|
235
|
+
if (!forked?.id) return null
|
|
236
|
+
await get().loadSessions()
|
|
237
|
+
set({ currentSessionId: forked.id })
|
|
238
|
+
return forked.id
|
|
239
|
+
} catch (err: unknown) {
|
|
240
|
+
console.error('Fork failed:', err instanceof Error ? err.message : String(err))
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
},
|
|
215
244
|
|
|
216
245
|
sidebarOpen: false,
|
|
217
246
|
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
|
@@ -492,4 +521,27 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
492
521
|
activeProjectFilter: null,
|
|
493
522
|
setActiveProjectFilter: (id) => set({ activeProjectFilter: id }),
|
|
494
523
|
|
|
524
|
+
// Agent trash
|
|
525
|
+
trashedAgents: {},
|
|
526
|
+
loadTrashedAgents: async () => {
|
|
527
|
+
try {
|
|
528
|
+
const trashedAgents = await api<Record<string, Agent>>('GET', '/agents/trash')
|
|
529
|
+
set({ trashedAgents })
|
|
530
|
+
} catch {
|
|
531
|
+
// ignore
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
showTrash: false,
|
|
535
|
+
setShowTrash: (show) => set({ showTrash: show }),
|
|
536
|
+
|
|
537
|
+
// Inspector panel
|
|
538
|
+
inspectorOpen: false,
|
|
539
|
+
setInspectorOpen: (open) => set({ inspectorOpen: open }),
|
|
540
|
+
inspectorTab: 'overview',
|
|
541
|
+
setInspectorTab: (tab) => set({ inspectorTab: tab }),
|
|
542
|
+
|
|
543
|
+
// Fleet sidebar filter
|
|
544
|
+
fleetFilter: 'all',
|
|
545
|
+
setFleetFilter: (filter) => set({ fleetFilter: filter }),
|
|
546
|
+
|
|
495
547
|
}))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { create } from 'zustand'
|
|
4
|
+
import type { PendingExecApproval, ExecApprovalDecision } from '@/types'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
|
|
7
|
+
interface ApprovalState {
|
|
8
|
+
approvals: Record<string, PendingExecApproval>
|
|
9
|
+
addApproval: (approval: PendingExecApproval) => void
|
|
10
|
+
removeApproval: (id: string) => void
|
|
11
|
+
resolveApproval: (id: string, decision: ExecApprovalDecision) => Promise<void>
|
|
12
|
+
pruneExpired: () => void
|
|
13
|
+
loadApprovals: () => Promise<void>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useApprovalStore = create<ApprovalState>((set) => ({
|
|
17
|
+
approvals: {},
|
|
18
|
+
|
|
19
|
+
addApproval: (approval) => {
|
|
20
|
+
set((s) => ({ approvals: { ...s.approvals, [approval.id]: approval } }))
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
removeApproval: (id) => {
|
|
24
|
+
set((s) => {
|
|
25
|
+
const next = { ...s.approvals }
|
|
26
|
+
delete next[id]
|
|
27
|
+
return { approvals: next }
|
|
28
|
+
})
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
resolveApproval: async (id, decision) => {
|
|
32
|
+
// Mark as resolving
|
|
33
|
+
set((s) => {
|
|
34
|
+
const approval = s.approvals[id]
|
|
35
|
+
if (!approval) return s
|
|
36
|
+
return { approvals: { ...s.approvals, [id]: { ...approval, resolving: true, error: undefined } } }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await api('POST', '/openclaw/approvals', { id, decision })
|
|
41
|
+
// Remove on success
|
|
42
|
+
set((s) => {
|
|
43
|
+
const next = { ...s.approvals }
|
|
44
|
+
delete next[id]
|
|
45
|
+
return { approvals: next }
|
|
46
|
+
})
|
|
47
|
+
} catch (err: unknown) {
|
|
48
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
49
|
+
set((s) => {
|
|
50
|
+
const approval = s.approvals[id]
|
|
51
|
+
if (!approval) return s
|
|
52
|
+
return { approvals: { ...s.approvals, [id]: { ...approval, resolving: false, error: message } } }
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
pruneExpired: () => {
|
|
58
|
+
const now = Date.now()
|
|
59
|
+
set((s) => {
|
|
60
|
+
const next: Record<string, PendingExecApproval> = {}
|
|
61
|
+
for (const [id, a] of Object.entries(s.approvals)) {
|
|
62
|
+
if (a.expiresAtMs > now) next[id] = a
|
|
63
|
+
}
|
|
64
|
+
return { approvals: next }
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
loadApprovals: async () => {
|
|
69
|
+
try {
|
|
70
|
+
const result = await api<PendingExecApproval[]>('GET', '/openclaw/approvals')
|
|
71
|
+
const approvals: Record<string, PendingExecApproval> = {}
|
|
72
|
+
for (const a of result) approvals[a.id] = a
|
|
73
|
+
set({ approvals })
|
|
74
|
+
} catch {
|
|
75
|
+
// ignore — gateway may be offline
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
}))
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { create } from 'zustand'
|
|
4
|
-
import type { Message, DevServerStatus, SSEEvent } from '../types'
|
|
4
|
+
import type { Message, DevServerStatus, SSEEvent, ChatTraceBlock } from '../types'
|
|
5
5
|
import { streamChat } from '../lib/chat'
|
|
6
6
|
import { speak } from '../lib/tts'
|
|
7
7
|
import { getStoredAccessKey } from '../lib/api-client'
|
|
8
8
|
import { useAppStore } from './use-app-store'
|
|
9
|
+
import { getSoundEnabled, setSoundEnabled, playStreamStart, playStreamEnd, playToolComplete, playError } from '../lib/notification-sounds'
|
|
9
10
|
|
|
10
11
|
export interface PendingFile {
|
|
11
12
|
file: File
|
|
@@ -33,6 +34,16 @@ interface ChatState {
|
|
|
33
34
|
streamingSessionId: string | null
|
|
34
35
|
streamText: string
|
|
35
36
|
|
|
37
|
+
// Task 1: Rich status indicator
|
|
38
|
+
streamPhase: 'thinking' | 'tool' | 'responding'
|
|
39
|
+
streamToolName: string
|
|
40
|
+
|
|
41
|
+
// Task 2: Typing cadence simulation
|
|
42
|
+
displayText: string
|
|
43
|
+
|
|
44
|
+
// Task 4: Live agent status bar
|
|
45
|
+
agentStatus: { goal?: string; status?: string; summary?: string; nextAction?: string } | null
|
|
46
|
+
|
|
36
47
|
messages: Message[]
|
|
37
48
|
setMessages: (msgs: Message[]) => void
|
|
38
49
|
|
|
@@ -44,6 +55,9 @@ interface ChatState {
|
|
|
44
55
|
ttsEnabled: boolean
|
|
45
56
|
toggleTts: () => void
|
|
46
57
|
|
|
58
|
+
soundEnabled: boolean
|
|
59
|
+
toggleSound: () => void
|
|
60
|
+
|
|
47
61
|
// Multi-file attachment support
|
|
48
62
|
pendingFiles: PendingFile[]
|
|
49
63
|
addPendingFile: (f: PendingFile) => void
|
|
@@ -57,23 +71,50 @@ interface ChatState {
|
|
|
57
71
|
devServer: DevServerStatus | null
|
|
58
72
|
setDevServer: (ds: DevServerStatus | null) => void
|
|
59
73
|
|
|
74
|
+
previewContent: { type: 'browser' | 'image' | 'code' | 'html'; url?: string; content?: string; title?: string } | null
|
|
75
|
+
setPreviewContent: (content: { type: 'browser' | 'image' | 'code' | 'html'; url?: string; content?: string; title?: string } | null) => void
|
|
76
|
+
|
|
60
77
|
debugOpen: boolean
|
|
61
78
|
setDebugOpen: (open: boolean) => void
|
|
62
79
|
|
|
63
80
|
sendMessage: (text: string) => Promise<void>
|
|
81
|
+
editAndResend: (messageIndex: number, newText: string) => Promise<void>
|
|
64
82
|
retryLastMessage: () => Promise<void>
|
|
65
83
|
sendHeartbeat: (sessionId: string) => Promise<void>
|
|
66
84
|
stopStreaming: () => void
|
|
67
85
|
|
|
86
|
+
// Rich trace blocks during streaming (F13)
|
|
87
|
+
streamTraces: ChatTraceBlock[]
|
|
88
|
+
|
|
68
89
|
// Voice conversation
|
|
69
90
|
voiceConversationActive: boolean
|
|
70
91
|
onStreamEvent: ((event: { t: string; text?: string }) => void) | null
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
// Module-level cadence interval (not in state to avoid re-renders)
|
|
95
|
+
let _cadenceInterval: ReturnType<typeof setInterval> | null = null
|
|
96
|
+
let _cadenceBuffer = ''
|
|
97
|
+
let _cadencePos = 0
|
|
98
|
+
|
|
99
|
+
function clearCadence() {
|
|
100
|
+
if (_cadenceInterval) {
|
|
101
|
+
clearInterval(_cadenceInterval)
|
|
102
|
+
_cadenceInterval = null
|
|
103
|
+
}
|
|
104
|
+
_cadenceBuffer = ''
|
|
105
|
+
_cadencePos = 0
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const CADENCE_THRESHOLD = 120
|
|
109
|
+
|
|
73
110
|
export const useChatStore = create<ChatState>((set, get) => ({
|
|
74
111
|
streaming: false,
|
|
75
112
|
streamingSessionId: null,
|
|
76
113
|
streamText: '',
|
|
114
|
+
streamPhase: 'thinking',
|
|
115
|
+
streamToolName: '',
|
|
116
|
+
displayText: '',
|
|
117
|
+
agentStatus: null,
|
|
77
118
|
messages: [],
|
|
78
119
|
setMessages: (msgs) => set({ messages: msgs, toolEvents: [] }),
|
|
79
120
|
toolEvents: [],
|
|
@@ -81,6 +122,13 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
81
122
|
lastUsage: null,
|
|
82
123
|
ttsEnabled: false,
|
|
83
124
|
toggleTts: () => set((s) => ({ ttsEnabled: !s.ttsEnabled })),
|
|
125
|
+
soundEnabled: getSoundEnabled(),
|
|
126
|
+
toggleSound: () => {
|
|
127
|
+
const next = !get().soundEnabled
|
|
128
|
+
setSoundEnabled(next)
|
|
129
|
+
set({ soundEnabled: next })
|
|
130
|
+
},
|
|
131
|
+
streamTraces: [],
|
|
84
132
|
voiceConversationActive: false,
|
|
85
133
|
onStreamEvent: null,
|
|
86
134
|
|
|
@@ -93,6 +141,9 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
93
141
|
get pendingImage() { const files = get().pendingFiles; return files.length ? files[0] : null },
|
|
94
142
|
setPendingImage: (img) => set({ pendingFiles: img ? [img] : [] }),
|
|
95
143
|
|
|
144
|
+
previewContent: null,
|
|
145
|
+
setPreviewContent: (content) => set({ previewContent: content }),
|
|
146
|
+
|
|
96
147
|
devServer: null,
|
|
97
148
|
setDevServer: (ds) => set({ devServer: ds }),
|
|
98
149
|
debugOpen: false,
|
|
@@ -120,10 +171,15 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
120
171
|
imageUrl,
|
|
121
172
|
attachedFiles,
|
|
122
173
|
}
|
|
174
|
+
clearCadence()
|
|
123
175
|
set((s) => ({
|
|
124
176
|
streaming: true,
|
|
125
177
|
streamingSessionId: sessionId,
|
|
126
178
|
streamText: '',
|
|
179
|
+
streamPhase: 'thinking' as const,
|
|
180
|
+
streamToolName: '',
|
|
181
|
+
displayText: '',
|
|
182
|
+
agentStatus: null,
|
|
127
183
|
messages: [...s.messages, userMsg],
|
|
128
184
|
pendingFiles: [],
|
|
129
185
|
toolEvents: [],
|
|
@@ -136,7 +192,9 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
136
192
|
}
|
|
137
193
|
|
|
138
194
|
let fullText = ''
|
|
195
|
+
let suggestions: string[] | null = null
|
|
139
196
|
let toolCallCounter = 0
|
|
197
|
+
let soundFiredStart = false
|
|
140
198
|
const shouldIgnoreTransientError = (msg: string) =>
|
|
141
199
|
/cancelled by steer mode|stopped by user/i.test(msg || '')
|
|
142
200
|
|
|
@@ -146,6 +204,42 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
146
204
|
if (event.t === 'd') {
|
|
147
205
|
fullText += event.text || ''
|
|
148
206
|
set({ streamText: fullText })
|
|
207
|
+
|
|
208
|
+
// Phase: first text data → 'responding'
|
|
209
|
+
if (get().streamPhase !== 'responding') {
|
|
210
|
+
set({ streamPhase: 'responding' })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Sound: stream start
|
|
214
|
+
if (!soundFiredStart && get().soundEnabled) {
|
|
215
|
+
soundFiredStart = true
|
|
216
|
+
playStreamStart()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Typing cadence: buffer first CADENCE_THRESHOLD chars, release word-by-word
|
|
220
|
+
if (fullText.length <= CADENCE_THRESHOLD) {
|
|
221
|
+
_cadenceBuffer = fullText
|
|
222
|
+
if (!_cadenceInterval) {
|
|
223
|
+
_cadenceInterval = setInterval(() => {
|
|
224
|
+
if (_cadencePos >= _cadenceBuffer.length) {
|
|
225
|
+
// Buffer fully released — check if we've passed threshold
|
|
226
|
+
if (get().streamText.length > CADENCE_THRESHOLD) {
|
|
227
|
+
clearCadence()
|
|
228
|
+
set({ displayText: get().streamText })
|
|
229
|
+
}
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
// Release ~2 chars per 16ms tick
|
|
233
|
+
const nextPos = Math.min(_cadencePos + 2, _cadenceBuffer.length)
|
|
234
|
+
_cadencePos = nextPos
|
|
235
|
+
set({ displayText: _cadenceBuffer.slice(0, _cadencePos) })
|
|
236
|
+
}, 16)
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
// Past threshold — sync displayText directly
|
|
240
|
+
if (_cadenceInterval) clearCadence()
|
|
241
|
+
set({ displayText: fullText })
|
|
242
|
+
}
|
|
149
243
|
} else if (event.t === 'md') {
|
|
150
244
|
// Parse metadata events (usage/run/queue). Ignore unknown keys.
|
|
151
245
|
try {
|
|
@@ -153,6 +247,9 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
153
247
|
if (meta.usage) {
|
|
154
248
|
set({ lastUsage: meta.usage })
|
|
155
249
|
}
|
|
250
|
+
if (meta.suggestions) {
|
|
251
|
+
suggestions = meta.suggestions
|
|
252
|
+
}
|
|
156
253
|
} catch {
|
|
157
254
|
// Ignore non-JSON metadata payloads.
|
|
158
255
|
}
|
|
@@ -162,6 +259,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
162
259
|
} else if (event.t === 'tool_call') {
|
|
163
260
|
const id = `tc-${++toolCallCounter}`
|
|
164
261
|
set((s) => ({
|
|
262
|
+
streamPhase: 'tool' as const,
|
|
263
|
+
streamToolName: event.toolName || 'unknown',
|
|
165
264
|
toolEvents: [...s.toolEvents, {
|
|
166
265
|
id,
|
|
167
266
|
name: event.toolName || 'unknown',
|
|
@@ -170,9 +269,9 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
170
269
|
}],
|
|
171
270
|
}))
|
|
172
271
|
} else if (event.t === 'tool_result') {
|
|
272
|
+
const soundOn = get().soundEnabled
|
|
173
273
|
set((s) => {
|
|
174
274
|
const events = [...s.toolEvents]
|
|
175
|
-
// Find the last running event with matching name
|
|
176
275
|
const idx = events.findLastIndex(
|
|
177
276
|
(e) => e.name === event.toolName && e.status === 'running',
|
|
178
277
|
)
|
|
@@ -183,6 +282,10 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
183
282
|
|| output.includes('ETIMEDOUT')
|
|
184
283
|
|| output.includes('Error:')
|
|
185
284
|
events[idx] = { ...events[idx], status: isError ? 'error' : 'done', output }
|
|
285
|
+
if (soundOn) {
|
|
286
|
+
if (isError) playError()
|
|
287
|
+
else playToolComplete()
|
|
288
|
+
}
|
|
186
289
|
}
|
|
187
290
|
return { toolEvents: events }
|
|
188
291
|
})
|
|
@@ -191,12 +294,22 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
191
294
|
if (!shouldIgnoreTransientError(errText)) {
|
|
192
295
|
fullText += '\n[Error: ' + errText + ']'
|
|
193
296
|
set({ streamText: fullText })
|
|
297
|
+
if (get().soundEnabled) playError()
|
|
298
|
+
}
|
|
299
|
+
} else if (event.t === 'status') {
|
|
300
|
+
try {
|
|
301
|
+
const parsed = JSON.parse(event.text || '{}')
|
|
302
|
+
set({ agentStatus: parsed })
|
|
303
|
+
} catch {
|
|
304
|
+
// ignore malformed status
|
|
194
305
|
}
|
|
195
306
|
} else if (event.t === 'done') {
|
|
196
307
|
// done
|
|
197
308
|
}
|
|
198
309
|
}, attachedFiles)
|
|
199
310
|
|
|
311
|
+
clearCadence()
|
|
312
|
+
if (get().soundEnabled && soundFiredStart) playStreamEnd()
|
|
200
313
|
if (fullText.trim()) {
|
|
201
314
|
const currentToolEvents = get().toolEvents
|
|
202
315
|
const assistantMsg: Message = {
|
|
@@ -210,21 +323,55 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
210
323
|
output: e.output,
|
|
211
324
|
error: e.status === 'error' || undefined,
|
|
212
325
|
})) : undefined,
|
|
326
|
+
suggestions: suggestions || undefined,
|
|
213
327
|
}
|
|
214
328
|
set((s) => ({
|
|
215
329
|
messages: [...s.messages, assistantMsg],
|
|
216
330
|
streaming: false,
|
|
217
331
|
streamingSessionId: null,
|
|
218
332
|
streamText: '',
|
|
333
|
+
displayText: '',
|
|
334
|
+
streamPhase: 'thinking' as const,
|
|
335
|
+
streamToolName: '',
|
|
219
336
|
}))
|
|
220
337
|
if (get().ttsEnabled && !get().voiceConversationActive) speak(fullText)
|
|
221
338
|
} else {
|
|
222
|
-
set({ streaming: false, streamingSessionId: null, streamText: '' })
|
|
339
|
+
set({ streaming: false, streamingSessionId: null, streamText: '', displayText: '', streamPhase: 'thinking' as const, streamToolName: '' })
|
|
223
340
|
}
|
|
224
341
|
|
|
225
342
|
useAppStore.getState().loadSessions()
|
|
226
343
|
},
|
|
227
344
|
|
|
345
|
+
editAndResend: async (messageIndex: number, newText: string) => {
|
|
346
|
+
if (get().streaming) return
|
|
347
|
+
const sessionId = useAppStore.getState().currentSessionId
|
|
348
|
+
if (!sessionId) return
|
|
349
|
+
try {
|
|
350
|
+
const key = getStoredAccessKey()
|
|
351
|
+
const res = await fetch(`/api/sessions/${sessionId}/edit-resend`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: {
|
|
354
|
+
'Content-Type': 'application/json',
|
|
355
|
+
...(key ? { 'X-Access-Key': key } : {}),
|
|
356
|
+
},
|
|
357
|
+
body: JSON.stringify({ messageIndex, newText }),
|
|
358
|
+
})
|
|
359
|
+
if (!res.ok) return
|
|
360
|
+
// Reload messages from server (truncated)
|
|
361
|
+
const msgsRes = await fetch(`/api/sessions/${sessionId}/messages`, {
|
|
362
|
+
headers: key ? { 'X-Access-Key': key } : undefined,
|
|
363
|
+
})
|
|
364
|
+
if (msgsRes.ok) {
|
|
365
|
+
const msgs = await msgsRes.json()
|
|
366
|
+
set({ messages: msgs })
|
|
367
|
+
}
|
|
368
|
+
// Re-send with the new text
|
|
369
|
+
await get().sendMessage(newText)
|
|
370
|
+
} catch {
|
|
371
|
+
// ignore
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
|
|
228
375
|
retryLastMessage: async () => {
|
|
229
376
|
if (get().streaming) return
|
|
230
377
|
const sessionId = useAppStore.getState().currentSessionId
|
|
@@ -312,7 +459,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
312
459
|
.filter((line) => !line.includes('[MAIN_LOOP_META]'))
|
|
313
460
|
.join('\n')
|
|
314
461
|
.trim()
|
|
315
|
-
if (!trimmed || trimmed === 'HEARTBEAT_OK' || sawError) return
|
|
462
|
+
if (!trimmed || trimmed === 'HEARTBEAT_OK' || trimmed === 'NO_MESSAGE' || sawError) return
|
|
316
463
|
|
|
317
464
|
const assistantMsg: Message = {
|
|
318
465
|
role: 'assistant',
|
|
@@ -346,6 +493,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|
|
346
493
|
// ignore
|
|
347
494
|
}
|
|
348
495
|
}
|
|
349
|
-
|
|
496
|
+
clearCadence()
|
|
497
|
+
set({ streaming: false, streamingSessionId: null, streamText: '', displayText: '', streamPhase: 'thinking' as const, streamToolName: '' })
|
|
350
498
|
},
|
|
351
499
|
}))
|
package/src/types/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface Message {
|
|
|
15
15
|
toolEvents?: MessageToolEvent[]
|
|
16
16
|
kind?: 'chat' | 'heartbeat' | 'system'
|
|
17
17
|
suppressed?: boolean
|
|
18
|
+
bookmarked?: boolean
|
|
19
|
+
suggestions?: string[]
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export type ProviderType = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'openai' | 'ollama' | 'anthropic' | 'openclaw' | 'google' | 'deepseek' | 'groq' | 'together' | 'mistral' | 'xai' | 'fireworks'
|
|
@@ -61,6 +63,7 @@ export interface Session {
|
|
|
61
63
|
createdAt: number
|
|
62
64
|
lastActiveAt: number
|
|
63
65
|
active?: boolean
|
|
66
|
+
mainSession?: boolean
|
|
64
67
|
sessionType?: SessionType
|
|
65
68
|
agentId?: string | null
|
|
66
69
|
parentSessionId?: string | null
|
|
@@ -109,6 +112,7 @@ export interface Session {
|
|
|
109
112
|
file?: string | null
|
|
110
113
|
queuedCount?: number
|
|
111
114
|
currentRunId?: string | null
|
|
115
|
+
conversationTone?: string
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
export type Sessions = Record<string, Session>
|
|
@@ -178,7 +182,7 @@ export interface MarketplacePlugin {
|
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
export interface SSEEvent {
|
|
181
|
-
t: 'd' | 'md' | 'r' | 'done' | 'err' | 'tool_call' | 'tool_result'
|
|
185
|
+
t: 'd' | 'md' | 'r' | 'done' | 'err' | 'tool_call' | 'tool_result' | 'status'
|
|
182
186
|
text?: string
|
|
183
187
|
toolName?: string
|
|
184
188
|
toolInput?: string
|
|
@@ -248,6 +252,10 @@ export interface Agent {
|
|
|
248
252
|
heartbeatNextAction?: string | null
|
|
249
253
|
thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high'
|
|
250
254
|
projectId?: string
|
|
255
|
+
avatarSeed?: string
|
|
256
|
+
trashedAt?: number
|
|
257
|
+
openclawSkillMode?: SkillAllowlistMode
|
|
258
|
+
openclawAllowedSkills?: string[]
|
|
251
259
|
createdAt: number
|
|
252
260
|
updatedAt: number
|
|
253
261
|
}
|
|
@@ -535,6 +543,8 @@ export interface Connector {
|
|
|
535
543
|
authenticated?: boolean
|
|
536
544
|
/** WhatsApp has stored credentials from previous pairing (runtime only) */
|
|
537
545
|
hasCredentials?: boolean
|
|
546
|
+
/** Connector presence info (runtime only) */
|
|
547
|
+
presence?: { lastMessageAt?: number | null; channelId?: string | null }
|
|
538
548
|
createdAt: number
|
|
539
549
|
updatedAt: number
|
|
540
550
|
}
|
|
@@ -651,3 +661,119 @@ export interface ClawHubSkill {
|
|
|
651
661
|
url: string
|
|
652
662
|
version: string
|
|
653
663
|
}
|
|
664
|
+
|
|
665
|
+
// --- OpenClaw Execution Approvals ---
|
|
666
|
+
|
|
667
|
+
export interface PendingExecApproval {
|
|
668
|
+
id: string
|
|
669
|
+
agentId: string
|
|
670
|
+
sessionKey: string
|
|
671
|
+
command: string
|
|
672
|
+
cwd?: string
|
|
673
|
+
host?: string
|
|
674
|
+
security?: string
|
|
675
|
+
ask?: string
|
|
676
|
+
createdAtMs: number
|
|
677
|
+
expiresAtMs: number
|
|
678
|
+
resolving?: boolean
|
|
679
|
+
error?: string
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
export type ExecApprovalDecision = 'allow-once' | 'allow-always' | 'deny'
|
|
683
|
+
|
|
684
|
+
// --- OpenClaw Skills ---
|
|
685
|
+
|
|
686
|
+
export interface OpenClawSkillEntry {
|
|
687
|
+
name: string
|
|
688
|
+
description?: string
|
|
689
|
+
source: 'bundled' | 'managed' | 'personal' | 'workspace'
|
|
690
|
+
eligible: boolean
|
|
691
|
+
requirements?: string[]
|
|
692
|
+
missing?: string[]
|
|
693
|
+
disabled?: boolean
|
|
694
|
+
installOptions?: SkillInstallOption[]
|
|
695
|
+
skillRequirements?: SkillRequirements
|
|
696
|
+
configChecks?: { key: string; ok: boolean }[]
|
|
697
|
+
skillKey?: string
|
|
698
|
+
baseDir?: string
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export type SkillAllowlistMode = 'all' | 'none' | 'selected'
|
|
702
|
+
|
|
703
|
+
// --- Fleet Sidebar Filters (F16) ---
|
|
704
|
+
export type FleetFilter = 'all' | 'running' | 'approvals'
|
|
705
|
+
|
|
706
|
+
// --- Exec Approval Config (F8) ---
|
|
707
|
+
export interface ExecApprovalConfig {
|
|
708
|
+
security: 'deny' | 'allowlist' | 'full'
|
|
709
|
+
askMode: 'off' | 'on-miss' | 'always'
|
|
710
|
+
patterns: string[]
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export interface ExecApprovalSnapshot {
|
|
714
|
+
path: string
|
|
715
|
+
exists: boolean
|
|
716
|
+
hash: string
|
|
717
|
+
file: ExecApprovalConfig
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// --- Permission Presets (F9) ---
|
|
721
|
+
export type PermissionPreset = 'conservative' | 'collaborative' | 'autonomous'
|
|
722
|
+
|
|
723
|
+
// --- Personality Builder (F10) ---
|
|
724
|
+
export interface PersonalityDraft {
|
|
725
|
+
identity: { name?: string; creature?: string; vibe?: string; emoji?: string }
|
|
726
|
+
user: { name?: string; callThem?: string; pronouns?: string; timezone?: string; notes?: string; context?: string }
|
|
727
|
+
soul: { coreTruths?: string; boundaries?: string; vibe?: string; continuity?: string }
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// --- Skill Lifecycle (F11) ---
|
|
731
|
+
export interface SkillInstallOption {
|
|
732
|
+
kind: 'brew' | 'node' | 'go' | 'uv' | 'download'
|
|
733
|
+
label: string
|
|
734
|
+
bins?: string[]
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export interface SkillRequirements {
|
|
738
|
+
bins?: string[]
|
|
739
|
+
anyBins?: string[][]
|
|
740
|
+
env?: string[]
|
|
741
|
+
config?: string[]
|
|
742
|
+
os?: string[]
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// --- Cron Jobs (F12) ---
|
|
746
|
+
export interface GatewayCronJob {
|
|
747
|
+
id: string
|
|
748
|
+
name: string
|
|
749
|
+
agentId: string
|
|
750
|
+
enabled: boolean
|
|
751
|
+
schedule: { kind: 'at' | 'every' | 'cron'; value: string; timezone?: string }
|
|
752
|
+
payload: {
|
|
753
|
+
kind: 'systemEvent' | 'agentTurn'
|
|
754
|
+
text?: string
|
|
755
|
+
message?: string
|
|
756
|
+
model?: string
|
|
757
|
+
deliver?: { mode: 'none' | 'announce'; channel?: string }
|
|
758
|
+
}
|
|
759
|
+
sessionTarget: 'main' | 'isolated'
|
|
760
|
+
state?: { nextRun?: string; lastRun?: string; lastStatus?: string }
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// --- Rich Chat Traces (F13) ---
|
|
764
|
+
export interface ChatTraceBlock {
|
|
765
|
+
type: 'thinking' | 'tool-call' | 'tool-result'
|
|
766
|
+
content: string
|
|
767
|
+
label?: string
|
|
768
|
+
collapsed?: boolean
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// --- Chat History Sync (F18) ---
|
|
772
|
+
export interface GatewaySessionPreview {
|
|
773
|
+
sessionKey: string
|
|
774
|
+
epoch: number
|
|
775
|
+
messages: Array<{ role: string; content: string; ts: number }>
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// --- Gateway Reload Mode (F21) ---
|
|
779
|
+
export type GatewayReloadMode = 'hot' | 'hybrid' | 'full'
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2017",
|
|
4
|
-
"lib": [
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
5
9
|
"allowJs": true,
|
|
6
10
|
"skipLibCheck": true,
|
|
7
11
|
"strict": true,
|
|
@@ -19,7 +23,9 @@
|
|
|
19
23
|
}
|
|
20
24
|
],
|
|
21
25
|
"paths": {
|
|
22
|
-
"@/*": [
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./src/*"
|
|
28
|
+
]
|
|
23
29
|
}
|
|
24
30
|
},
|
|
25
31
|
"include": [
|
|
@@ -28,7 +34,10 @@
|
|
|
28
34
|
"**/*.tsx",
|
|
29
35
|
".next/types/**/*.ts",
|
|
30
36
|
".next/dev/types/**/*.ts",
|
|
31
|
-
"**/*.mts"
|
|
37
|
+
"**/*.mts",
|
|
38
|
+
".next/dev/dev/types/**/*.ts"
|
|
32
39
|
],
|
|
33
|
-
"exclude": [
|
|
40
|
+
"exclude": [
|
|
41
|
+
"node_modules"
|
|
42
|
+
]
|
|
34
43
|
}
|