@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
|
@@ -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,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
} from '@/lib/runtime-loop'
|
|
13
14
|
import type { LoopMode } from '@/types'
|
|
14
15
|
import type { SettingsSectionProps } from './types'
|
|
16
|
+
import { HintTip } from '@/components/shared/hint-tip'
|
|
15
17
|
|
|
16
18
|
export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
17
19
|
const loopMode: LoopMode = appSettings.loopMode === 'ongoing' ? 'ongoing' : 'bounded'
|
|
@@ -25,7 +27,7 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
25
27
|
Choose bounded or ongoing agent loops and set safety guards for task execution.
|
|
26
28
|
</p>
|
|
27
29
|
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
28
|
-
<label className="
|
|
30
|
+
<label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Loop Mode <HintTip text="Bounded = fixed max steps. Ongoing = runs until the task completes (with a safety cap)" /></label>
|
|
29
31
|
<div className="grid grid-cols-2 gap-2 mb-5">
|
|
30
32
|
{([
|
|
31
33
|
{ id: 'bounded' as const, name: 'Bounded' },
|
|
@@ -48,7 +50,7 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
48
50
|
{loopMode === 'bounded' ? (
|
|
49
51
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
|
50
52
|
<div>
|
|
51
|
-
<label className="
|
|
53
|
+
<label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Agent Steps <HintTip text="Maximum actions an agent can take before stopping — prevents infinite loops" /></label>
|
|
52
54
|
<input
|
|
53
55
|
type="number"
|
|
54
56
|
min={1}
|
|
@@ -63,7 +65,7 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
63
65
|
/>
|
|
64
66
|
</div>
|
|
65
67
|
<div>
|
|
66
|
-
<label className="
|
|
68
|
+
<label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Orchestrator Steps <HintTip text="Maximum tool calls the orchestrator can make when coordinating multiple agents" /></label>
|
|
67
69
|
<input
|
|
68
70
|
type="number"
|
|
69
71
|
min={1}
|
|
@@ -78,7 +80,7 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
78
80
|
/>
|
|
79
81
|
</div>
|
|
80
82
|
<div>
|
|
81
|
-
<label className="
|
|
83
|
+
<label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Legacy Turns <HintTip text="Max conversation turns for older orchestration mode — increase if agents get cut off mid-task" /></label>
|
|
82
84
|
<input
|
|
83
85
|
type="number"
|
|
84
86
|
min={1}
|
|
@@ -129,6 +131,24 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
129
131
|
</div>
|
|
130
132
|
)}
|
|
131
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
|
+
|
|
132
152
|
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Execution Timeouts (Seconds)</label>
|
|
133
153
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
134
154
|
<div>
|
|
@@ -177,6 +197,131 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
|
|
|
177
197
|
/>
|
|
178
198
|
</div>
|
|
179
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>
|
|
180
325
|
</div>
|
|
181
326
|
</div>
|
|
182
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
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
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'
|
|
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
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ApprovalsPanel() {
|
|
30
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
31
|
+
const agents = useAppStore((s) => s.agents)
|
|
32
|
+
const serverApprovals = useAppStore((s) => s.approvals)
|
|
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])
|
|
58
|
+
|
|
59
|
+
useWs('approvals', refreshServerApprovals, 5000)
|
|
60
|
+
useWs('openclaw:approvals', refreshExecApprovals, 5000)
|
|
61
|
+
|
|
62
|
+
const taskApprovals = useMemo(() => {
|
|
63
|
+
return Object.values(tasks)
|
|
64
|
+
.filter((t) => t.pendingApproval)
|
|
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
|
+
}))
|
|
78
|
+
}, [tasks])
|
|
79
|
+
|
|
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) => {
|
|
97
|
+
try {
|
|
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')
|
|
106
|
+
} catch (err: unknown) {
|
|
107
|
+
toast.error(err instanceof Error ? err.message : 'Failed to submit decision')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (pendingCount === 0) {
|
|
112
|
+
return (
|
|
113
|
+
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
|
|
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">
|
|
115
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40">
|
|
116
|
+
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
|
|
117
|
+
<path d="m9 12 2 2 4-4"/>
|
|
118
|
+
</svg>
|
|
119
|
+
</div>
|
|
120
|
+
<h2 className="font-display text-[18px] font-600 text-text-2 mb-2">No pending approvals</h2>
|
|
121
|
+
<p className="text-[13px] text-text-3/60 max-w-[320px]">
|
|
122
|
+
Your swarm is operating autonomously. Actions requiring oversight will appear here.
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="flex-1 overflow-y-auto px-6 py-8">
|
|
130
|
+
<div className="max-w-3xl mx-auto">
|
|
131
|
+
<div className="flex items-center justify-between mb-8">
|
|
132
|
+
<div>
|
|
133
|
+
<h1 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-1">Approvals</h1>
|
|
134
|
+
<p className="text-[13px] text-text-3">Execution and plugin governance requests pending review</p>
|
|
135
|
+
</div>
|
|
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">
|
|
137
|
+
{pendingCount} Pending
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
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>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
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>
|
|
197
|
+
|
|
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>
|
|
215
|
+
</div>
|
|
216
|
+
)
|
|
217
|
+
})}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
@@ -231,7 +231,7 @@ export function TaskBoard() {
|
|
|
231
231
|
}, [selectionMode])
|
|
232
232
|
|
|
233
233
|
return (
|
|
234
|
-
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
|
234
|
+
<div className="flex-1 min-h-0 flex flex-col h-full overflow-hidden">
|
|
235
235
|
<div className="flex items-center justify-between px-8 pt-6 pb-4 shrink-0">
|
|
236
236
|
<div>
|
|
237
237
|
<h1 className="font-display text-[28px] font-800 tracking-[-0.03em]">Task Board</h1>
|
|
@@ -399,7 +399,7 @@ export function TaskBoard() {
|
|
|
399
399
|
</div>
|
|
400
400
|
)}
|
|
401
401
|
|
|
402
|
-
<div className="flex-1 flex gap-5 px-8 pb-6 overflow-x-auto overflow-y-hidden">
|
|
402
|
+
<div className="flex-1 min-h-0 flex gap-5 px-8 pb-6 overflow-x-auto overflow-y-hidden overscroll-x-contain touch-pan-x">
|
|
403
403
|
{!loaded ? (
|
|
404
404
|
ACTIVE_COLUMNS.map((status) => (
|
|
405
405
|
<div key={status} className="flex flex-col gap-3 min-w-[260px] flex-1">
|
|
@@ -410,17 +410,25 @@ export function TaskBoard() {
|
|
|
410
410
|
</div>
|
|
411
411
|
))
|
|
412
412
|
) : (
|
|
413
|
-
columns.map((status) => (
|
|
414
|
-
<
|
|
413
|
+
columns.map((status, idx) => (
|
|
414
|
+
<div
|
|
415
415
|
key={status}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
416
|
+
className="flex flex-col gap-3 min-w-[260px] flex-1"
|
|
417
|
+
style={{
|
|
418
|
+
animation: 'fade-up 0.6s var(--ease-spring) both',
|
|
419
|
+
animationDelay: `${idx * 0.1}s`
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
422
|
+
<TaskColumn
|
|
423
|
+
status={status}
|
|
424
|
+
tasks={tasksByStatus(status)}
|
|
425
|
+
onDrop={handleDrop}
|
|
426
|
+
selectionMode={selectionMode}
|
|
427
|
+
selectedIds={selectedIds}
|
|
428
|
+
onToggleSelect={toggleSelect}
|
|
429
|
+
onSelectAll={() => selectAllInColumn(status)}
|
|
430
|
+
/>
|
|
431
|
+
</div>
|
|
424
432
|
))
|
|
425
433
|
)}
|
|
426
434
|
</div>
|