@swarmclawai/swarmclaw 0.6.8 → 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 +70 -45
- 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 +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- 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/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/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -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 +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- 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/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 +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- 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 +29 -6
- package/src/components/home/home-view.tsx +20 -14
- 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 +73 -21
- 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-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- 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 +19 -7
- 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/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 +144 -0
- 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 +170 -66
- 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 +66 -64
- 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 +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -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 +1 -1
- 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 +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -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 +11 -1
- 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 +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- 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 +66 -31
- 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 +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- 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 +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
DEFAULT_AGENT_LOOP_RECURSION_LIMIT,
|
|
5
5
|
DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
|
|
6
6
|
DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
|
|
7
|
+
DEFAULT_DELEGATION_MAX_DEPTH,
|
|
7
8
|
DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
|
|
8
9
|
DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
|
|
9
10
|
DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
|
|
@@ -130,6 +131,24 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
130
131
|
</div>
|
|
131
132
|
)}
|
|
132
133
|
|
|
134
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
|
135
|
+
<div>
|
|
136
|
+
<label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Delegation Depth <HintTip text="Maximum delegation chain depth for delegate_to_agent and spawn_subagent to prevent runaway fan-out" /></label>
|
|
137
|
+
<input
|
|
138
|
+
type="number"
|
|
139
|
+
min={1}
|
|
140
|
+
max={12}
|
|
141
|
+
value={appSettings.delegationMaxDepth ?? DEFAULT_DELEGATION_MAX_DEPTH}
|
|
142
|
+
onChange={(e) => {
|
|
143
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
144
|
+
patchSettings({ delegationMaxDepth: Number.isFinite(n) ? n : DEFAULT_DELEGATION_MAX_DEPTH })
|
|
145
|
+
}}
|
|
146
|
+
className={inputClass}
|
|
147
|
+
style={{ fontFamily: 'inherit' }}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
133
152
|
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Execution Timeouts (Seconds)</label>
|
|
134
153
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
135
154
|
<div>
|
|
@@ -178,6 +197,131 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
178
197
|
/>
|
|
179
198
|
</div>
|
|
180
199
|
</div>
|
|
200
|
+
|
|
201
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mt-6 mb-3">LLM Response Cache</label>
|
|
202
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
|
203
|
+
<div className="md:col-span-3 flex items-center gap-3">
|
|
204
|
+
<button
|
|
205
|
+
onClick={() => patchSettings({ responseCacheEnabled: !(appSettings.responseCacheEnabled ?? true) })}
|
|
206
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.responseCacheEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
207
|
+
>
|
|
208
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.responseCacheEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
|
|
209
|
+
</button>
|
|
210
|
+
<span className="text-[12px] text-text-2">Enable deterministic cache (TTL + LRU) for non-tool model responses</span>
|
|
211
|
+
</div>
|
|
212
|
+
<div>
|
|
213
|
+
<label className="block text-[11px] text-text-3 mb-2">TTL (seconds)</label>
|
|
214
|
+
<input
|
|
215
|
+
type="number"
|
|
216
|
+
min={5}
|
|
217
|
+
max={604800}
|
|
218
|
+
value={appSettings.responseCacheTtlSec ?? 900}
|
|
219
|
+
onChange={(e) => {
|
|
220
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
221
|
+
patchSettings({ responseCacheTtlSec: Number.isFinite(n) ? n : 900 })
|
|
222
|
+
}}
|
|
223
|
+
className={inputClass}
|
|
224
|
+
style={{ fontFamily: 'inherit' }}
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
<div>
|
|
228
|
+
<label className="block text-[11px] text-text-3 mb-2">Max Entries</label>
|
|
229
|
+
<input
|
|
230
|
+
type="number"
|
|
231
|
+
min={1}
|
|
232
|
+
max={20000}
|
|
233
|
+
value={appSettings.responseCacheMaxEntries ?? 500}
|
|
234
|
+
onChange={(e) => {
|
|
235
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
236
|
+
patchSettings({ responseCacheMaxEntries: Number.isFinite(n) ? n : 500 })
|
|
237
|
+
}}
|
|
238
|
+
className={inputClass}
|
|
239
|
+
style={{ fontFamily: 'inherit' }}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Task Quality Gate Defaults</label>
|
|
245
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
|
246
|
+
<div className="md:col-span-3 flex items-center gap-3">
|
|
247
|
+
<button
|
|
248
|
+
onClick={() => patchSettings({ taskQualityGateEnabled: !(appSettings.taskQualityGateEnabled ?? true) })}
|
|
249
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.taskQualityGateEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
250
|
+
>
|
|
251
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.taskQualityGateEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
|
|
252
|
+
</button>
|
|
253
|
+
<span className="text-[12px] text-text-2">Enable quality gate checks before tasks can be marked complete</span>
|
|
254
|
+
</div>
|
|
255
|
+
<div>
|
|
256
|
+
<label className="block text-[11px] text-text-3 mb-2">Min Result Chars</label>
|
|
257
|
+
<input
|
|
258
|
+
type="number"
|
|
259
|
+
min={10}
|
|
260
|
+
max={2000}
|
|
261
|
+
value={appSettings.taskQualityGateMinResultChars ?? 80}
|
|
262
|
+
onChange={(e) => {
|
|
263
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
264
|
+
patchSettings({ taskQualityGateMinResultChars: Number.isFinite(n) ? n : 80 })
|
|
265
|
+
}}
|
|
266
|
+
className={inputClass}
|
|
267
|
+
style={{ fontFamily: 'inherit' }}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
<div>
|
|
271
|
+
<label className="block text-[11px] text-text-3 mb-2">Min Evidence Signals</label>
|
|
272
|
+
<input
|
|
273
|
+
type="number"
|
|
274
|
+
min={0}
|
|
275
|
+
max={8}
|
|
276
|
+
value={appSettings.taskQualityGateMinEvidenceItems ?? 2}
|
|
277
|
+
onChange={(e) => {
|
|
278
|
+
const n = Number.parseInt(e.target.value, 10)
|
|
279
|
+
patchSettings({ taskQualityGateMinEvidenceItems: Number.isFinite(n) ? n : 2 })
|
|
280
|
+
}}
|
|
281
|
+
className={inputClass}
|
|
282
|
+
style={{ fontFamily: 'inherit' }}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
<div className="md:col-span-3 grid grid-cols-1 md:grid-cols-3 gap-2">
|
|
286
|
+
<label className="flex items-center gap-2 text-[12px] text-text-2">
|
|
287
|
+
<input
|
|
288
|
+
type="checkbox"
|
|
289
|
+
checked={appSettings.taskQualityGateRequireVerification ?? false}
|
|
290
|
+
onChange={(e) => patchSettings({ taskQualityGateRequireVerification: e.target.checked })}
|
|
291
|
+
/>
|
|
292
|
+
Require verification evidence
|
|
293
|
+
</label>
|
|
294
|
+
<label className="flex items-center gap-2 text-[12px] text-text-2">
|
|
295
|
+
<input
|
|
296
|
+
type="checkbox"
|
|
297
|
+
checked={appSettings.taskQualityGateRequireArtifact ?? false}
|
|
298
|
+
onChange={(e) => patchSettings({ taskQualityGateRequireArtifact: e.target.checked })}
|
|
299
|
+
/>
|
|
300
|
+
Require artifact evidence
|
|
301
|
+
</label>
|
|
302
|
+
<label className="flex items-center gap-2 text-[12px] text-text-2">
|
|
303
|
+
<input
|
|
304
|
+
type="checkbox"
|
|
305
|
+
checked={appSettings.taskQualityGateRequireReport ?? false}
|
|
306
|
+
onChange={(e) => patchSettings({ taskQualityGateRequireReport: e.target.checked })}
|
|
307
|
+
/>
|
|
308
|
+
Require task report
|
|
309
|
+
</label>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Integrity Monitor</label>
|
|
314
|
+
<div className="flex items-center gap-3">
|
|
315
|
+
<button
|
|
316
|
+
onClick={() => patchSettings({ integrityMonitorEnabled: !(appSettings.integrityMonitorEnabled ?? true) })}
|
|
317
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.integrityMonitorEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
318
|
+
>
|
|
319
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.integrityMonitorEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
|
|
320
|
+
</button>
|
|
321
|
+
<span className="text-[12px] text-text-2">
|
|
322
|
+
Watch critical identity/config files for drift and raise alerts.
|
|
323
|
+
</span>
|
|
324
|
+
</div>
|
|
181
325
|
</div>
|
|
182
326
|
</div>
|
|
183
327
|
)
|
|
@@ -163,10 +163,14 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
163
163
|
|
|
164
164
|
{hubSkills.length > 0 && (
|
|
165
165
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
|
|
166
|
-
{hubSkills.map((skill) => (
|
|
166
|
+
{hubSkills.map((skill, idx) => (
|
|
167
167
|
<div
|
|
168
168
|
key={skill.id}
|
|
169
|
-
className="p-4 rounded-[14px] border border-white/[0.06] bg-surface"
|
|
169
|
+
className="p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:border-white/[0.12] transition-all hover:scale-[1.01]"
|
|
170
|
+
style={{
|
|
171
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
172
|
+
animationDelay: `${idx * 0.03}s`
|
|
173
|
+
}}
|
|
170
174
|
>
|
|
171
175
|
<div className="flex items-start justify-between gap-2">
|
|
172
176
|
<div className="min-w-0 flex-1">
|
|
@@ -225,21 +229,22 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
225
229
|
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
|
|
226
230
|
{/* Sidebar: ClawHub button + Sheet */}
|
|
227
231
|
{inSidebar && (
|
|
228
|
-
|
|
232
|
+
<div style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
229
233
|
<button
|
|
230
234
|
onClick={() => setClawHubOpen(true)}
|
|
231
|
-
className="w-full mb-3 py-2.5 px-4 rounded-[12px] border border-dashed border-white/[0.1] text-[13px] font-600 text-text-3 hover:text-accent-bright hover:border-accent-bright/30 transition-all cursor-pointer bg-transparent"
|
|
235
|
+
className="w-full mb-3 py-2.5 px-4 rounded-[12px] border border-dashed border-white/[0.1] text-[13px] font-600 text-text-3 hover:text-accent-bright hover:border-accent-bright/30 transition-all cursor-pointer bg-transparent relative overflow-hidden group/hub"
|
|
232
236
|
style={{ fontFamily: 'inherit' }}
|
|
233
237
|
>
|
|
234
|
-
Browse ClawHub Skills
|
|
238
|
+
<span className="relative z-10">Browse ClawHub Skills</span>
|
|
239
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover/hub:animate-[shimmer-bar_2s_infinite]" />
|
|
235
240
|
</button>
|
|
236
241
|
<ClawHubBrowser open={clawHubOpen} onOpenChange={setClawHubOpen} onInstalled={() => loadSkills()} />
|
|
237
|
-
|
|
242
|
+
</div>
|
|
238
243
|
)}
|
|
239
244
|
|
|
240
245
|
{/* Full-width: tabs */}
|
|
241
246
|
{!inSidebar && (
|
|
242
|
-
<div className="flex gap-1 mb-4">
|
|
247
|
+
<div className="flex gap-1 mb-4" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
243
248
|
<button onClick={() => setTab('skills')} className={tabClass('skills')} style={{ fontFamily: 'inherit' }}>
|
|
244
249
|
My Skills
|
|
245
250
|
</button>
|
|
@@ -251,7 +256,7 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
251
256
|
|
|
252
257
|
{(!inSidebar && tab === 'clawhub') ? renderClawHub() : (
|
|
253
258
|
skillList.length === 0 ? (
|
|
254
|
-
<div className="text-center py-12">
|
|
259
|
+
<div className="text-center py-12" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
|
|
255
260
|
<p className="text-[13px] text-text-3/60">No skills yet</p>
|
|
256
261
|
<button
|
|
257
262
|
onClick={() => setSkillSheetOpen(true)}
|
|
@@ -263,7 +268,7 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
263
268
|
</div>
|
|
264
269
|
) : (
|
|
265
270
|
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
266
|
-
{skillList.map((skill) => {
|
|
271
|
+
{skillList.map((skill, idx) => {
|
|
267
272
|
const skillScope = skill.scope || 'global'
|
|
268
273
|
const skillAgentIds = skill.agentIds || []
|
|
269
274
|
const scopeLabel = skillScope === 'global' ? 'Global' : `${skillAgentIds.length} agent(s)`
|
|
@@ -271,10 +276,23 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
271
276
|
? skillAgentIds.map((id) => agents[id]).filter(Boolean)
|
|
272
277
|
: []
|
|
273
278
|
return (
|
|
274
|
-
<
|
|
279
|
+
<div
|
|
275
280
|
key={skill.id}
|
|
276
281
|
onClick={() => handleEdit(skill.id)}
|
|
277
|
-
|
|
282
|
+
onKeyDown={(e) => {
|
|
283
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
284
|
+
e.preventDefault()
|
|
285
|
+
handleEdit(skill.id)
|
|
286
|
+
}
|
|
287
|
+
}}
|
|
288
|
+
role="button"
|
|
289
|
+
tabIndex={0}
|
|
290
|
+
className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer hover:border-white/[0.12] hover:scale-[1.01]"
|
|
291
|
+
style={{
|
|
292
|
+
fontFamily: 'inherit',
|
|
293
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
294
|
+
animationDelay: `${idx * 0.05}s`
|
|
295
|
+
}}
|
|
278
296
|
>
|
|
279
297
|
<div className="flex items-center justify-between mb-1">
|
|
280
298
|
<span className="font-display text-[14px] font-600 text-text truncate">{skill.name}</span>
|
|
@@ -282,6 +300,7 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
282
300
|
<span className="text-[10px] font-mono text-text-3/50">{skill.filename}</span>
|
|
283
301
|
{!inSidebar && (
|
|
284
302
|
<button
|
|
303
|
+
type="button"
|
|
285
304
|
onClick={(e) => handleDelete(e, skill.id)}
|
|
286
305
|
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
287
306
|
title="Delete"
|
|
@@ -317,7 +336,7 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
317
336
|
)}
|
|
318
337
|
</div>
|
|
319
338
|
)}
|
|
320
|
-
</
|
|
339
|
+
</div>
|
|
321
340
|
)
|
|
322
341
|
})}
|
|
323
342
|
</div>
|
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
7
|
import { api } from '@/lib/api-client'
|
|
8
|
+
import { toast } from 'sonner'
|
|
8
9
|
|
|
9
10
|
export function SkillSheet() {
|
|
10
11
|
const open = useAppStore((s) => s.skillSheetOpen)
|
|
@@ -121,20 +122,32 @@ export function SkillSheet() {
|
|
|
121
122
|
scope,
|
|
122
123
|
agentIds: scope === 'agent' ? agentIds : [],
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
try {
|
|
126
|
+
if (editing) {
|
|
127
|
+
await api('PUT', `/skills/${editing.id}`, data)
|
|
128
|
+
toast.success('Skill updated successfully')
|
|
129
|
+
} else {
|
|
130
|
+
await api('POST', '/skills', data)
|
|
131
|
+
toast.success('Skill created successfully')
|
|
132
|
+
}
|
|
133
|
+
await loadSkills()
|
|
134
|
+
onClose()
|
|
135
|
+
} catch (err: unknown) {
|
|
136
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save skill')
|
|
128
137
|
}
|
|
129
|
-
await loadSkills()
|
|
130
|
-
onClose()
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
const handleDelete = async () => {
|
|
134
|
-
if (editing)
|
|
141
|
+
if (!editing) return
|
|
142
|
+
if (!confirm(`Delete skill "${editing.name}"? This will remove it from all assigned agents.`)) return
|
|
143
|
+
|
|
144
|
+
try {
|
|
135
145
|
await api('DELETE', `/skills/${editing.id}`)
|
|
146
|
+
toast.success('Skill deleted')
|
|
136
147
|
await loadSkills()
|
|
137
148
|
onClose()
|
|
149
|
+
} catch (err: unknown) {
|
|
150
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete skill')
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
|
|
@@ -1,33 +1,114 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useMemo } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
5
6
|
import { api } from '@/lib/api-client'
|
|
6
7
|
import { toast } from 'sonner'
|
|
8
|
+
import { useWs } from '@/hooks/use-ws'
|
|
9
|
+
import { ExecApprovalCard } from '@/components/chat/exec-approval-card'
|
|
10
|
+
import { getApprovalPayload, getApprovalTitle } from '@/lib/approval-display'
|
|
11
|
+
import type { ApprovalRequest } from '@/types'
|
|
7
12
|
|
|
13
|
+
const CATEGORY_LABELS: Record<string, string> = {
|
|
14
|
+
tool_access: 'Plugin Access',
|
|
15
|
+
wallet_transfer: 'Wallet Transfer',
|
|
16
|
+
plugin_scaffold: 'Plugin Creation',
|
|
17
|
+
plugin_install: 'Plugin Install',
|
|
18
|
+
task_tool: 'Task Plugin Call',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CATEGORY_ICONS: Record<string, string> = {
|
|
22
|
+
tool_access: '🔑',
|
|
23
|
+
wallet_transfer: '💰',
|
|
24
|
+
plugin_scaffold: '🔌',
|
|
25
|
+
plugin_install: '📦',
|
|
26
|
+
task_tool: '🤖',
|
|
27
|
+
}
|
|
8
28
|
|
|
9
29
|
export function ApprovalsPanel() {
|
|
10
30
|
const tasks = useAppStore((s) => s.tasks)
|
|
11
31
|
const agents = useAppStore((s) => s.agents)
|
|
32
|
+
const serverApprovals = useAppStore((s) => s.approvals)
|
|
12
33
|
const loadTasks = useAppStore((s) => s.loadTasks)
|
|
34
|
+
const loadServerApprovals = useAppStore((s) => s.loadApprovals)
|
|
35
|
+
|
|
36
|
+
const execApprovals = useApprovalStore((s) => s.approvals)
|
|
37
|
+
const loadExecApprovals = useApprovalStore((s) => s.loadApprovals)
|
|
38
|
+
const pruneExecApprovals = useApprovalStore((s) => s.pruneExpired)
|
|
39
|
+
|
|
40
|
+
const refreshServerApprovals = useCallback(() => {
|
|
41
|
+
void loadServerApprovals()
|
|
42
|
+
}, [loadServerApprovals])
|
|
43
|
+
|
|
44
|
+
const refreshExecApprovals = useCallback(() => {
|
|
45
|
+
void loadExecApprovals()
|
|
46
|
+
pruneExecApprovals()
|
|
47
|
+
}, [loadExecApprovals, pruneExecApprovals])
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
refreshServerApprovals()
|
|
51
|
+
refreshExecApprovals()
|
|
52
|
+
const interval = setInterval(() => {
|
|
53
|
+
refreshServerApprovals()
|
|
54
|
+
refreshExecApprovals()
|
|
55
|
+
}, 5000)
|
|
56
|
+
return () => clearInterval(interval)
|
|
57
|
+
}, [refreshServerApprovals, refreshExecApprovals])
|
|
13
58
|
|
|
14
|
-
|
|
59
|
+
useWs('approvals', refreshServerApprovals, 5000)
|
|
60
|
+
useWs('openclaw:approvals', refreshExecApprovals, 5000)
|
|
61
|
+
|
|
62
|
+
const taskApprovals = useMemo(() => {
|
|
15
63
|
return Object.values(tasks)
|
|
16
64
|
.filter((t) => t.pendingApproval)
|
|
17
|
-
.
|
|
65
|
+
.map((t) => ({
|
|
66
|
+
id: t.id,
|
|
67
|
+
category: 'task_tool' as const,
|
|
68
|
+
agentId: t.agentId,
|
|
69
|
+
sessionId: null,
|
|
70
|
+
taskId: t.id,
|
|
71
|
+
title: `Task Plugin Call: ${t.pendingApproval?.toolName || 'unknown'}`,
|
|
72
|
+
description: t.title,
|
|
73
|
+
data: t.pendingApproval?.args ?? {},
|
|
74
|
+
createdAt: t.updatedAt,
|
|
75
|
+
updatedAt: t.updatedAt,
|
|
76
|
+
status: 'pending' as const,
|
|
77
|
+
}))
|
|
18
78
|
}, [tasks])
|
|
19
79
|
|
|
20
|
-
const
|
|
80
|
+
const sessionApprovals = useMemo(() => {
|
|
81
|
+
return Object.values(serverApprovals)
|
|
82
|
+
.filter((a) => a.status === 'pending')
|
|
83
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
84
|
+
}, [serverApprovals])
|
|
85
|
+
|
|
86
|
+
const workflowApprovals = useMemo(() => {
|
|
87
|
+
return [...sessionApprovals, ...taskApprovals].sort((a, b) => b.updatedAt - a.updatedAt)
|
|
88
|
+
}, [sessionApprovals, taskApprovals])
|
|
89
|
+
|
|
90
|
+
const sortedExecApprovals = useMemo(() => {
|
|
91
|
+
return Object.values(execApprovals).sort((a, b) => b.createdAtMs - a.createdAtMs)
|
|
92
|
+
}, [execApprovals])
|
|
93
|
+
|
|
94
|
+
const pendingCount = sortedExecApprovals.length + workflowApprovals.length
|
|
95
|
+
|
|
96
|
+
const handleDecision = async (req: ApprovalRequest, approved: boolean) => {
|
|
21
97
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
98
|
+
if (req.category === 'task_tool') {
|
|
99
|
+
await api('POST', `/tasks/${req.id}/approve`, { approved })
|
|
100
|
+
void loadTasks()
|
|
101
|
+
} else {
|
|
102
|
+
await api('POST', '/approvals', { id: req.id, approved })
|
|
103
|
+
refreshServerApprovals()
|
|
104
|
+
}
|
|
105
|
+
toast.success(approved ? 'Action approved' : 'Action rejected')
|
|
25
106
|
} catch (err: unknown) {
|
|
26
107
|
toast.error(err instanceof Error ? err.message : 'Failed to submit decision')
|
|
27
108
|
}
|
|
28
109
|
}
|
|
29
110
|
|
|
30
|
-
if (
|
|
111
|
+
if (pendingCount === 0) {
|
|
31
112
|
return (
|
|
32
113
|
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
|
|
33
114
|
<div className="w-16 h-16 rounded-[24px] bg-white/[0.02] border border-white/[0.04] flex items-center justify-center mb-6">
|
|
@@ -37,8 +118,8 @@ export function ApprovalsPanel() {
|
|
|
37
118
|
</svg>
|
|
38
119
|
</div>
|
|
39
120
|
<h2 className="font-display text-[18px] font-600 text-text-2 mb-2">No pending approvals</h2>
|
|
40
|
-
<p className="text-[13px] text-text-3/60 max-w-[
|
|
41
|
-
Your swarm is operating autonomously.
|
|
121
|
+
<p className="text-[13px] text-text-3/60 max-w-[320px]">
|
|
122
|
+
Your swarm is operating autonomously. Actions requiring oversight will appear here.
|
|
42
123
|
</p>
|
|
43
124
|
</div>
|
|
44
125
|
)
|
|
@@ -46,74 +127,97 @@ export function ApprovalsPanel() {
|
|
|
46
127
|
|
|
47
128
|
return (
|
|
48
129
|
<div className="flex-1 overflow-y-auto px-6 py-8">
|
|
49
|
-
<div className="max-w-
|
|
130
|
+
<div className="max-w-3xl mx-auto">
|
|
50
131
|
<div className="flex items-center justify-between mb-8">
|
|
51
132
|
<div>
|
|
52
133
|
<h1 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-1">Approvals</h1>
|
|
53
|
-
<p className="text-[13px] text-text-3">
|
|
134
|
+
<p className="text-[13px] text-text-3">Execution and plugin governance requests pending review</p>
|
|
54
135
|
</div>
|
|
55
136
|
<div className="px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[11px] font-600">
|
|
56
|
-
{
|
|
137
|
+
{pendingCount} Pending
|
|
57
138
|
</div>
|
|
58
139
|
</div>
|
|
59
140
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
141
|
+
{sortedExecApprovals.length > 0 && (
|
|
142
|
+
<div className="mb-6">
|
|
143
|
+
<h2 className="text-[12px] font-700 uppercase tracking-[0.1em] text-amber-400/90 mb-2">Execution Approvals</h2>
|
|
144
|
+
<div className="grid grid-cols-1 gap-3">
|
|
145
|
+
{sortedExecApprovals.map((approval) => (
|
|
146
|
+
<ExecApprovalCard key={approval.id} approval={approval} />
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{workflowApprovals.length > 0 && (
|
|
153
|
+
<div>
|
|
154
|
+
<h2 className="text-[12px] font-700 uppercase tracking-[0.1em] text-amber-400/90 mb-2">Plugin Workflow Approvals</h2>
|
|
155
|
+
<div className="grid grid-cols-1 gap-4">
|
|
156
|
+
{workflowApprovals.map((req) => {
|
|
157
|
+
const agent = req.agentId ? agents[req.agentId] : null
|
|
158
|
+
const icon = CATEGORY_ICONS[req.category] || '⚠️'
|
|
159
|
+
const categoryLabel = CATEGORY_LABELS[req.category] || req.category
|
|
160
|
+
const payload = getApprovalPayload(req)
|
|
161
|
+
const payloadText = JSON.stringify(payload, null, 2)
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div key={req.id} className="bg-surface rounded-[16px] border border-white/[0.06] overflow-hidden">
|
|
165
|
+
<div className="px-5 py-3 border-b border-white/[0.04] flex items-center justify-between bg-surface-2/50">
|
|
166
|
+
<div className="flex items-center gap-3">
|
|
167
|
+
<div className="w-8 h-8 rounded-[8px] bg-white/[0.04] flex items-center justify-center">
|
|
168
|
+
<span className="text-[14px]">{icon}</span>
|
|
169
|
+
</div>
|
|
170
|
+
<div>
|
|
171
|
+
<div className="flex items-center gap-2">
|
|
172
|
+
<h3 className="text-[13px] font-600 text-text">{getApprovalTitle(req)}</h3>
|
|
173
|
+
<span className="px-1.5 py-0.5 rounded-[4px] bg-white/[0.04] text-[9px] font-600 text-text-3/60 uppercase tracking-wider">
|
|
174
|
+
{categoryLabel}
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
<p className="text-[11px] text-text-3">
|
|
178
|
+
{agent?.name || 'System'}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
<span className="text-[10px] text-text-3/50 font-mono">
|
|
183
|
+
{new Date(req.updatedAt).toLocaleString()}
|
|
184
|
+
</span>
|
|
76
185
|
</div>
|
|
77
|
-
</div>
|
|
78
|
-
<span className="text-[10px] text-text-3/50 font-mono">
|
|
79
|
-
{new Date(task.updatedAt).toLocaleString()}
|
|
80
|
-
</span>
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
{/* Body */}
|
|
84
|
-
<div className="p-5">
|
|
85
|
-
<div className="flex items-center gap-2 mb-3">
|
|
86
|
-
<span className="px-2 py-0.5 rounded-[6px] bg-accent-soft text-accent-bright text-[10px] font-mono font-600">
|
|
87
|
-
{task.pendingApproval!.toolName}
|
|
88
|
-
</span>
|
|
89
|
-
<span className="text-[12px] text-text-3">requested permission to execute.</span>
|
|
90
|
-
</div>
|
|
91
186
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
187
|
+
<div className="p-5">
|
|
188
|
+
{req.description && (
|
|
189
|
+
<p className="text-[13px] text-text-2/90 mb-4">{req.description}</p>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
<div className="bg-black/30 rounded-[10px] border border-white/[0.04] p-4 mb-5 overflow-x-auto max-h-[250px] overflow-y-auto">
|
|
193
|
+
<pre className="text-[12px] font-mono text-text-2/80 whitespace-pre-wrap break-all leading-relaxed">
|
|
194
|
+
{payloadText === '{}' ? 'No structured payload provided.' : payloadText}
|
|
195
|
+
</pre>
|
|
196
|
+
</div>
|
|
97
197
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
198
|
+
<div className="flex items-center justify-end gap-3 pt-4 border-t border-white/[0.04]">
|
|
199
|
+
<button
|
|
200
|
+
onClick={() => handleDecision(req, false)}
|
|
201
|
+
className="px-5 py-2 rounded-[10px] bg-transparent border border-red-500/30 text-red-400 text-[12px] font-600 hover:bg-red-500/10 transition-colors cursor-pointer"
|
|
202
|
+
style={{ fontFamily: 'inherit' }}
|
|
203
|
+
>
|
|
204
|
+
Reject
|
|
205
|
+
</button>
|
|
206
|
+
<button
|
|
207
|
+
onClick={() => handleDecision(req, true)}
|
|
208
|
+
className="px-5 py-2 rounded-[10px] bg-emerald-500 border border-emerald-400 text-[#000] text-[12px] font-700 hover:brightness-110 transition-all shadow-[0_0_15px_rgba(16,185,129,0.3)] cursor-pointer"
|
|
209
|
+
style={{ fontFamily: 'inherit' }}
|
|
210
|
+
>
|
|
211
|
+
Approve
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
111
215
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
216
|
+
)
|
|
217
|
+
})}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
117
221
|
</div>
|
|
118
222
|
</div>
|
|
119
223
|
)
|