@swarmclawai/swarmclaw 0.7.6 → 0.7.8
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 +19 -10
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +16 -0
- package/src/app/api/agents/route.ts +2 -0
- package/src/app/api/chats/[id]/route.ts +21 -1
- package/src/app/api/chats/route.ts +13 -1
- package/src/app/api/connectors/[id]/route.ts +20 -2
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
- package/src/app/api/external-agents/[id]/route.ts +38 -6
- package/src/app/api/external-agents/route.ts +17 -1
- package/src/app/api/gateways/[id]/health/route.ts +8 -0
- package/src/app/api/gateways/[id]/route.ts +53 -1
- package/src/app/api/gateways/route.ts +53 -0
- package/src/app/api/openclaw/deploy/route.ts +139 -0
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/cli/index.js +40 -0
- package/src/cli/index.test.js +68 -0
- package/src/cli/spec.js +60 -0
- package/src/components/agents/agent-sheet.tsx +281 -33
- package/src/components/auth/setup-wizard.tsx +75 -2
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-header.tsx +4 -0
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/gateways/gateway-sheet.tsx +140 -8
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/openclaw/openclaw-deploy-panel.tsx +591 -9
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +221 -17
- package/src/components/shared/settings/section-capability-policy.tsx +38 -0
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/tasks/approvals-panel.tsx +177 -18
- package/src/components/tasks/task-board.tsx +137 -23
- package/src/components/tasks/task-card.tsx +29 -0
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/lib/server/agent-runtime-config.ts +142 -7
- package/src/lib/server/agent-thread-session.ts +9 -1
- package/src/lib/server/capability-router.test.ts +22 -0
- package/src/lib/server/capability-router.ts +54 -18
- package/src/lib/server/chat-execution.ts +33 -3
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.ts +99 -74
- package/src/lib/server/daemon-state.ts +83 -46
- package/src/lib/server/elevenlabs.test.ts +59 -1
- package/src/lib/server/heartbeat-service.ts +5 -1
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/openclaw-deploy.test.ts +8 -0
- package/src/lib/server/openclaw-deploy.ts +679 -19
- package/src/lib/server/orchestrator-lg.ts +1 -0
- package/src/lib/server/orchestrator.ts +11 -0
- package/src/lib/server/plugins.ts +6 -1
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-followups.test.ts +147 -2
- package/src/lib/server/queue.ts +278 -8
- package/src/lib/server/session-run-manager.ts +31 -0
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.ts +26 -1
- package/src/lib/server/session-tools/context.ts +5 -0
- package/src/lib/server/session-tools/crud.ts +265 -76
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +38 -2
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.ts +14 -2
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/web-inputs.test.ts +17 -0
- package/src/lib/server/session-tools/web.ts +153 -6
- package/src/lib/server/stream-agent-chat.test.ts +27 -2
- package/src/lib/server/stream-agent-chat.ts +104 -30
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +269 -0
- package/src/lib/setup-defaults.ts +2 -2
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/validation/schemas.ts +9 -0
- package/src/types/index.ts +104 -0
|
@@ -8,7 +8,7 @@ import { toast } from 'sonner'
|
|
|
8
8
|
import { useWs } from '@/hooks/use-ws'
|
|
9
9
|
import { ExecApprovalCard } from '@/components/chat/exec-approval-card'
|
|
10
10
|
import { getApprovalPayload, getApprovalTitle } from '@/lib/approval-display'
|
|
11
|
-
import type { ApprovalRequest } from '@/types'
|
|
11
|
+
import type { AppSettings, ApprovalCategory, ApprovalRequest } from '@/types'
|
|
12
12
|
|
|
13
13
|
const CATEGORY_LABELS: Record<string, string> = {
|
|
14
14
|
tool_access: 'Plugin Access',
|
|
@@ -28,6 +28,15 @@ const CATEGORY_ICONS: Record<string, string> = {
|
|
|
28
28
|
|
|
29
29
|
type ApprovalScope = 'all' | 'execution' | 'workflow' | 'task'
|
|
30
30
|
|
|
31
|
+
const AUTO_APPROVE_OPTIONS: Array<{ id: ApprovalCategory; label: string; description: string }> = [
|
|
32
|
+
{ id: 'tool_access', label: 'Plugin Access', description: 'Auto-enable requested plugins for a chat.' },
|
|
33
|
+
{ id: 'plugin_scaffold', label: 'Plugin Scaffold', description: 'Auto-create plugin files requested by agents.' },
|
|
34
|
+
{ id: 'plugin_install', label: 'Plugin Install', description: 'Auto-install plugins from approved URLs.' },
|
|
35
|
+
{ id: 'human_loop', label: 'Human Approval Requests', description: 'Auto-approve ask-human approval prompts.' },
|
|
36
|
+
{ id: 'wallet_transfer', label: 'Wallet Transfers', description: 'Auto-approve wallet send requests. High risk.' },
|
|
37
|
+
{ id: 'task_tool', label: 'Task Tool Calls', description: 'Auto-approve task-level tool approvals.' },
|
|
38
|
+
]
|
|
39
|
+
|
|
31
40
|
function relativeTime(ts: number): string {
|
|
32
41
|
const diff = Date.now() - ts
|
|
33
42
|
if (diff < 60_000) return 'just now'
|
|
@@ -39,9 +48,11 @@ function relativeTime(ts: number): string {
|
|
|
39
48
|
export function ApprovalsPanel() {
|
|
40
49
|
const tasks = useAppStore((s) => s.tasks)
|
|
41
50
|
const agents = useAppStore((s) => s.agents)
|
|
51
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
42
52
|
const serverApprovals = useAppStore((s) => s.approvals)
|
|
43
53
|
const loadTasks = useAppStore((s) => s.loadTasks)
|
|
44
54
|
const loadServerApprovals = useAppStore((s) => s.loadApprovals)
|
|
55
|
+
const loadAppSettings = useAppStore((s) => s.loadSettings)
|
|
45
56
|
|
|
46
57
|
const execApprovals = useApprovalStore((s) => s.approvals)
|
|
47
58
|
const loadExecApprovals = useApprovalStore((s) => s.loadApprovals)
|
|
@@ -73,12 +84,17 @@ export function ApprovalsPanel() {
|
|
|
73
84
|
const [scope, setScope] = useState<ApprovalScope>('all')
|
|
74
85
|
const [categoryFilter, setCategoryFilter] = useState('all')
|
|
75
86
|
const [now, setNow] = useState(() => Date.now())
|
|
87
|
+
const [savingSetting, setSavingSetting] = useState<string | null>(null)
|
|
76
88
|
|
|
77
89
|
useEffect(() => {
|
|
78
90
|
const intervalId = window.setInterval(() => setNow(Date.now()), 60_000)
|
|
79
91
|
return () => window.clearInterval(intervalId)
|
|
80
92
|
}, [])
|
|
81
93
|
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
void loadAppSettings()
|
|
96
|
+
}, [loadAppSettings])
|
|
97
|
+
|
|
82
98
|
const taskApprovals = useMemo(() => {
|
|
83
99
|
return Object.values(tasks)
|
|
84
100
|
.filter((t) => t.pendingApproval)
|
|
@@ -180,6 +196,23 @@ export function ApprovalsPanel() {
|
|
|
180
196
|
},
|
|
181
197
|
]
|
|
182
198
|
|
|
199
|
+
const autoApproved = useMemo(() => new Set(appSettings.approvalAutoApproveCategories || []), [appSettings.approvalAutoApproveCategories])
|
|
200
|
+
const approvalsEnabled = appSettings.approvalsEnabled ?? true
|
|
201
|
+
const outboundApprovalEnabled = appSettings.safetyRequireApprovalForOutbound ?? false
|
|
202
|
+
|
|
203
|
+
const saveApprovalSettings = async (patch: Partial<AppSettings>, successMessage: string, key: string) => {
|
|
204
|
+
try {
|
|
205
|
+
setSavingSetting(key)
|
|
206
|
+
const settings = await api<AppSettings>('PUT', '/settings', patch)
|
|
207
|
+
useAppStore.setState({ appSettings: settings })
|
|
208
|
+
toast.success(successMessage)
|
|
209
|
+
} catch (err: unknown) {
|
|
210
|
+
toast.error(err instanceof Error ? err.message : 'Failed to update approval settings')
|
|
211
|
+
} finally {
|
|
212
|
+
setSavingSetting((current) => (current === key ? null : current))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
183
216
|
const handleDecision = async (req: ApprovalRequest, approved: boolean) => {
|
|
184
217
|
try {
|
|
185
218
|
if (req.category === 'task_tool') {
|
|
@@ -195,23 +228,6 @@ export function ApprovalsPanel() {
|
|
|
195
228
|
}
|
|
196
229
|
}
|
|
197
230
|
|
|
198
|
-
if (pendingCount === 0) {
|
|
199
|
-
return (
|
|
200
|
-
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
|
|
201
|
-
<div className="w-16 h-16 rounded-[24px] bg-white/[0.02] border border-white/[0.04] flex items-center justify-center mb-6">
|
|
202
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40">
|
|
203
|
-
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
|
|
204
|
-
<path d="m9 12 2 2 4-4"/>
|
|
205
|
-
</svg>
|
|
206
|
-
</div>
|
|
207
|
-
<h2 className="font-display text-[18px] font-600 text-text-2 mb-2">No pending approvals</h2>
|
|
208
|
-
<p className="text-[13px] text-text-3/60 max-w-[320px]">
|
|
209
|
-
Your swarm is operating autonomously. Actions requiring oversight will appear here.
|
|
210
|
-
</p>
|
|
211
|
-
</div>
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
231
|
return (
|
|
216
232
|
<div className="flex-1 overflow-y-auto px-6 py-8">
|
|
217
233
|
<div className="max-w-3xl mx-auto">
|
|
@@ -237,6 +253,132 @@ export function ApprovalsPanel() {
|
|
|
237
253
|
))}
|
|
238
254
|
</div>
|
|
239
255
|
|
|
256
|
+
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4 mb-6">
|
|
257
|
+
<div className="flex flex-col gap-4">
|
|
258
|
+
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-3">
|
|
259
|
+
<div>
|
|
260
|
+
<h2 className="text-[13px] font-700 text-text">Approval Controls</h2>
|
|
261
|
+
<p className="text-[12px] text-text-3/70 mt-1 max-w-[640px]">
|
|
262
|
+
Control whether actions queue for review, which approval types auto-run, and whether outbound connector sends need explicit confirmation.
|
|
263
|
+
</p>
|
|
264
|
+
</div>
|
|
265
|
+
<div className={`px-3 py-1.5 rounded-full text-[11px] font-700 ${
|
|
266
|
+
approvalsEnabled
|
|
267
|
+
? 'bg-amber-500/10 border border-amber-500/20 text-amber-300'
|
|
268
|
+
: 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-300'
|
|
269
|
+
}`}>
|
|
270
|
+
{approvalsEnabled ? 'Manual approvals enabled' : 'Approvals disabled'}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
275
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-black/20 px-4 py-4">
|
|
276
|
+
<div className="flex items-center justify-between gap-4">
|
|
277
|
+
<div>
|
|
278
|
+
<div className="text-[12px] font-600 text-text-2">Platform Approvals</div>
|
|
279
|
+
<p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
|
|
280
|
+
Turn this off to auto-approve workflow approvals across the app. Audit records are still kept.
|
|
281
|
+
</p>
|
|
282
|
+
</div>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
disabled={savingSetting === 'approvalsEnabled'}
|
|
286
|
+
onClick={() => {
|
|
287
|
+
const next = !approvalsEnabled
|
|
288
|
+
void saveApprovalSettings(
|
|
289
|
+
{ approvalsEnabled: next },
|
|
290
|
+
next ? 'Platform approvals enabled' : 'Platform approvals disabled',
|
|
291
|
+
'approvalsEnabled',
|
|
292
|
+
)
|
|
293
|
+
}}
|
|
294
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer disabled:opacity-50 ${approvalsEnabled ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
295
|
+
aria-label="Toggle platform approvals"
|
|
296
|
+
>
|
|
297
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${approvalsEnabled ? 'translate-x-[18px]' : ''}`} />
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-black/20 px-4 py-4">
|
|
303
|
+
<div className="flex items-center justify-between gap-4">
|
|
304
|
+
<div>
|
|
305
|
+
<div className="text-[12px] font-600 text-text-2">Outbound Send Approvals</div>
|
|
306
|
+
<p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
|
|
307
|
+
Require explicit approval before agents send messages or media over connectors.
|
|
308
|
+
</p>
|
|
309
|
+
</div>
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
disabled={savingSetting === 'safetyRequireApprovalForOutbound'}
|
|
313
|
+
onClick={() => {
|
|
314
|
+
const next = !outboundApprovalEnabled
|
|
315
|
+
void saveApprovalSettings(
|
|
316
|
+
{ safetyRequireApprovalForOutbound: next },
|
|
317
|
+
next ? 'Outbound send approvals enabled' : 'Outbound send approvals disabled',
|
|
318
|
+
'safetyRequireApprovalForOutbound',
|
|
319
|
+
)
|
|
320
|
+
}}
|
|
321
|
+
className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer disabled:opacity-50 ${outboundApprovalEnabled ? 'bg-accent' : 'bg-white/[0.12]'}`}
|
|
322
|
+
aria-label="Toggle outbound send approvals"
|
|
323
|
+
>
|
|
324
|
+
<span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${outboundApprovalEnabled ? 'translate-x-[18px]' : ''}`} />
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<div>
|
|
331
|
+
<div className="flex items-center justify-between gap-3 mb-2">
|
|
332
|
+
<div className="text-[12px] font-600 text-text-2">Auto-Approve Categories</div>
|
|
333
|
+
<div className="text-[11px] text-text-3/60">
|
|
334
|
+
{autoApproved.size} enabled
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
338
|
+
{AUTO_APPROVE_OPTIONS.map((option) => {
|
|
339
|
+
const checked = autoApproved.has(option.id)
|
|
340
|
+
return (
|
|
341
|
+
<label
|
|
342
|
+
key={option.id}
|
|
343
|
+
className={`rounded-[12px] border px-3 py-3 cursor-pointer transition-all ${
|
|
344
|
+
checked
|
|
345
|
+
? 'border-accent-bright/30 bg-accent-soft/60'
|
|
346
|
+
: 'border-white/[0.06] bg-black/20 hover:bg-white/[0.04]'
|
|
347
|
+
}`}
|
|
348
|
+
>
|
|
349
|
+
<div className="flex items-start gap-3">
|
|
350
|
+
<input
|
|
351
|
+
type="checkbox"
|
|
352
|
+
checked={checked}
|
|
353
|
+
disabled={savingSetting === `auto:${option.id}`}
|
|
354
|
+
onChange={(e) => {
|
|
355
|
+
const next = new Set(appSettings.approvalAutoApproveCategories || [])
|
|
356
|
+
if (e.target.checked) next.add(option.id)
|
|
357
|
+
else next.delete(option.id)
|
|
358
|
+
void saveApprovalSettings(
|
|
359
|
+
{ approvalAutoApproveCategories: [...next] },
|
|
360
|
+
checked ? `${option.label} now requires approval` : `${option.label} will auto-approve`,
|
|
361
|
+
`auto:${option.id}`,
|
|
362
|
+
)
|
|
363
|
+
}}
|
|
364
|
+
className="mt-0.5"
|
|
365
|
+
/>
|
|
366
|
+
<div>
|
|
367
|
+
<div className="text-[12px] font-600 text-text-2">{option.label}</div>
|
|
368
|
+
<p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">{option.description}</p>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</label>
|
|
372
|
+
)
|
|
373
|
+
})}
|
|
374
|
+
</div>
|
|
375
|
+
<p className="text-[11px] text-text-3/60 mt-2">
|
|
376
|
+
Use category auto-approval when you still want the approval system on, but you do not want these request types to pause execution.
|
|
377
|
+
</p>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
240
382
|
<div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4 mb-6">
|
|
241
383
|
<div className="flex flex-col lg:flex-row gap-3 lg:items-center lg:justify-between">
|
|
242
384
|
<div className="flex flex-wrap gap-2">
|
|
@@ -411,6 +553,23 @@ export function ApprovalsPanel() {
|
|
|
411
553
|
<p className="text-[12px] text-text-3/60">Try clearing the search or switching the queue scope.</p>
|
|
412
554
|
</div>
|
|
413
555
|
)}
|
|
556
|
+
|
|
557
|
+
{pendingCount === 0 && (
|
|
558
|
+
<div className="flex flex-col items-center justify-center p-8 text-center">
|
|
559
|
+
<div className="w-16 h-16 rounded-[24px] bg-white/[0.02] border border-white/[0.04] flex items-center justify-center mb-6">
|
|
560
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40">
|
|
561
|
+
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
|
|
562
|
+
<path d="m9 12 2 2 4-4"/>
|
|
563
|
+
</svg>
|
|
564
|
+
</div>
|
|
565
|
+
<h2 className="font-display text-[18px] font-600 text-text-2 mb-2">No pending approvals</h2>
|
|
566
|
+
<p className="text-[13px] text-text-3/60 max-w-[360px]">
|
|
567
|
+
{approvalsEnabled
|
|
568
|
+
? 'Your swarm is operating autonomously. Actions requiring oversight will appear here.'
|
|
569
|
+
: 'Approvals are currently disabled, so eligible requests will auto-run instead of queuing here.'}
|
|
570
|
+
</p>
|
|
571
|
+
</div>
|
|
572
|
+
)}
|
|
414
573
|
</div>
|
|
415
574
|
</div>
|
|
416
575
|
)
|
|
@@ -14,11 +14,23 @@ import { toast } from 'sonner'
|
|
|
14
14
|
const ACTIVE_COLUMNS: BoardTaskStatus[] = ['backlog', 'queued', 'running', 'completed', 'failed']
|
|
15
15
|
type BoardViewMode = 'board' | 'list'
|
|
16
16
|
type AttentionFilter = 'all' | 'needs-attention' | 'approval' | 'blocked' | 'overdue' | 'failed'
|
|
17
|
+
type TaskScopeFilter = 'user-facing' | 'all' | 'agent'
|
|
17
18
|
|
|
18
19
|
function isTaskOverdue(task: BoardTask): boolean {
|
|
19
20
|
return !!task.dueAt && task.dueAt < Date.now() && task.status !== 'completed' && task.status !== 'archived'
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function isInternalAgentTask(task: BoardTask): boolean {
|
|
24
|
+
if (task.sourceType === 'schedule' || task.sourceType === 'delegation') return true
|
|
25
|
+
return Boolean(task.createdByAgentId || task.delegatedByAgentId)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isTaskRelevantToAgent(task: BoardTask, agentId: string): boolean {
|
|
29
|
+
return task.agentId === agentId
|
|
30
|
+
|| task.createdByAgentId === agentId
|
|
31
|
+
|| task.delegatedByAgentId === agentId
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
function matchesAttentionFilter(task: BoardTask, filter: AttentionFilter): boolean {
|
|
23
35
|
const blocked = !!task.blockedBy?.length
|
|
24
36
|
const pendingApproval = !!task.pendingApproval
|
|
@@ -139,6 +151,13 @@ export function TaskBoard() {
|
|
|
139
151
|
if (typeof window === 'undefined') return ''
|
|
140
152
|
return new URLSearchParams(window.location.search).get('tag') || ''
|
|
141
153
|
})
|
|
154
|
+
const [taskScopeFilter, setTaskScopeFilter] = useState<TaskScopeFilter>(() => {
|
|
155
|
+
if (typeof window === 'undefined') return 'user-facing'
|
|
156
|
+
const params = new URLSearchParams(window.location.search)
|
|
157
|
+
if (params.get('agent')) return 'agent'
|
|
158
|
+
const raw = params.get('taskView')
|
|
159
|
+
return raw === 'all' ? 'all' : 'user-facing'
|
|
160
|
+
})
|
|
142
161
|
const [viewMode, setViewMode] = useState<BoardViewMode>('board')
|
|
143
162
|
const [attentionFilter, setAttentionFilter] = useState<AttentionFilter>('all')
|
|
144
163
|
|
|
@@ -156,13 +175,14 @@ export function TaskBoard() {
|
|
|
156
175
|
useEffect(() => {
|
|
157
176
|
if (typeof window === 'undefined') return
|
|
158
177
|
const params = new URLSearchParams()
|
|
159
|
-
if (filterAgentId) params.set('agent', filterAgentId)
|
|
178
|
+
if (taskScopeFilter === 'agent' && filterAgentId) params.set('agent', filterAgentId)
|
|
179
|
+
else if (taskScopeFilter === 'all') params.set('taskView', 'all')
|
|
160
180
|
if (filterTag) params.set('tag', filterTag)
|
|
161
181
|
if (activeProjectFilter) params.set('project', activeProjectFilter)
|
|
162
182
|
const qs = params.toString()
|
|
163
183
|
const newUrl = `${window.location.pathname}${qs ? `?${qs}` : ''}`
|
|
164
184
|
window.history.replaceState(null, '', newUrl)
|
|
165
|
-
}, [filterAgentId, filterTag, activeProjectFilter])
|
|
185
|
+
}, [filterAgentId, filterTag, activeProjectFilter, taskScopeFilter])
|
|
166
186
|
|
|
167
187
|
const [loaded, setLoaded] = useState(Object.keys(tasks).length > 0)
|
|
168
188
|
useEffect(() => { Promise.all([loadTasks(), loadAgents(), loadProjects()]).then(() => setLoaded(true)) }, [])
|
|
@@ -173,17 +193,28 @@ export function TaskBoard() {
|
|
|
173
193
|
|
|
174
194
|
const columns: BoardTaskStatus[] = showArchived ? [...ACTIVE_COLUMNS, 'archived'] : ACTIVE_COLUMNS
|
|
175
195
|
|
|
176
|
-
const
|
|
196
|
+
const matchesScopeFilters = useCallback((task: BoardTask) => {
|
|
177
197
|
if (!showArchived && task.status === 'archived') return false
|
|
178
|
-
if (
|
|
198
|
+
if (taskScopeFilter === 'user-facing' && isInternalAgentTask(task)) return false
|
|
199
|
+
if (taskScopeFilter === 'agent' && (!filterAgentId || !isTaskRelevantToAgent(task, filterAgentId))) return false
|
|
179
200
|
if (filterTag && !(task.tags && task.tags.includes(filterTag))) return false
|
|
180
201
|
if (activeProjectFilter && task.projectId !== activeProjectFilter) return false
|
|
202
|
+
return true
|
|
203
|
+
}, [activeProjectFilter, filterAgentId, filterTag, showArchived, taskScopeFilter])
|
|
204
|
+
|
|
205
|
+
const matchesBaseFilters = useCallback((task: BoardTask) => {
|
|
206
|
+
if (!matchesScopeFilters(task)) return false
|
|
181
207
|
if (!matchesAttentionFilter(task, attentionFilter)) return false
|
|
182
208
|
return true
|
|
183
|
-
}, [
|
|
209
|
+
}, [attentionFilter, matchesScopeFilters])
|
|
210
|
+
|
|
211
|
+
const scopedTasks = useMemo(
|
|
212
|
+
() => Object.values(tasks).filter(matchesScopeFilters),
|
|
213
|
+
[tasks, matchesScopeFilters],
|
|
214
|
+
)
|
|
184
215
|
|
|
185
216
|
const filteredTasks = useMemo(() => (
|
|
186
|
-
|
|
217
|
+
scopedTasks
|
|
187
218
|
.filter(matchesBaseFilters)
|
|
188
219
|
.sort((a, b) => {
|
|
189
220
|
const rankDiff = attentionRank(a) - attentionRank(b)
|
|
@@ -192,7 +223,7 @@ export function TaskBoard() {
|
|
|
192
223
|
if (dueDiff !== 0) return dueDiff
|
|
193
224
|
return b.updatedAt - a.updatedAt
|
|
194
225
|
})
|
|
195
|
-
), [
|
|
226
|
+
), [scopedTasks, matchesBaseFilters])
|
|
196
227
|
|
|
197
228
|
const tasksByStatus = useCallback((status: BoardTaskStatus) =>
|
|
198
229
|
filteredTasks
|
|
@@ -223,7 +254,7 @@ export function TaskBoard() {
|
|
|
223
254
|
|
|
224
255
|
// Task counts per project (non-archived)
|
|
225
256
|
const projectTaskCounts: Record<string, number> = {}
|
|
226
|
-
for (const t of
|
|
257
|
+
for (const t of scopedTasks) {
|
|
227
258
|
if (t.projectId && t.status !== 'archived') {
|
|
228
259
|
projectTaskCounts[t.projectId] = (projectTaskCounts[t.projectId] || 0) + 1
|
|
229
260
|
}
|
|
@@ -231,7 +262,7 @@ export function TaskBoard() {
|
|
|
231
262
|
|
|
232
263
|
// Summary stats
|
|
233
264
|
const stats = useMemo(() => {
|
|
234
|
-
const all =
|
|
265
|
+
const all = scopedTasks.filter((t) => t.status !== 'archived')
|
|
235
266
|
return {
|
|
236
267
|
total: all.length,
|
|
237
268
|
running: all.filter((t) => t.status === 'running').length,
|
|
@@ -242,7 +273,13 @@ export function TaskBoard() {
|
|
|
242
273
|
approvals: all.filter((t) => !!t.pendingApproval).length,
|
|
243
274
|
attention: all.filter((t) => matchesAttentionFilter(t, 'needs-attention')).length,
|
|
244
275
|
}
|
|
245
|
-
}, [
|
|
276
|
+
}, [scopedTasks])
|
|
277
|
+
|
|
278
|
+
const activeScopeLabel = useMemo(() => {
|
|
279
|
+
if (taskScopeFilter === 'all') return 'All tasks'
|
|
280
|
+
if (taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId]) return `${agents[filterAgentId].name} activity`
|
|
281
|
+
return 'User-facing tasks'
|
|
282
|
+
}, [agents, filterAgentId, taskScopeFilter])
|
|
246
283
|
|
|
247
284
|
const activeAttentionLabel = useMemo(() => {
|
|
248
285
|
if (attentionFilter === 'all') return null
|
|
@@ -301,6 +338,16 @@ export function TaskBoard() {
|
|
|
301
338
|
<p className="text-[13px] text-text-3">
|
|
302
339
|
{stats.total} task{stats.total !== 1 ? 's' : ''}
|
|
303
340
|
</p>
|
|
341
|
+
<span className="inline-flex items-center gap-1 rounded-full bg-white/[0.04] px-2 py-1 text-[11px] font-600 text-text-2">
|
|
342
|
+
{taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
|
|
343
|
+
<>
|
|
344
|
+
<AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={14} />
|
|
345
|
+
{activeScopeLabel}
|
|
346
|
+
</>
|
|
347
|
+
) : (
|
|
348
|
+
activeScopeLabel
|
|
349
|
+
)}
|
|
350
|
+
</span>
|
|
304
351
|
{stats.running > 0 && (
|
|
305
352
|
<span className="inline-flex items-center gap-1 text-[11px] font-600 text-blue-400">
|
|
306
353
|
<span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
|
|
@@ -338,41 +385,83 @@ export function TaskBoard() {
|
|
|
338
385
|
<button
|
|
339
386
|
onClick={() => setAgentDropdownOpen(!agentDropdownOpen)}
|
|
340
387
|
className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
|
|
341
|
-
${
|
|
388
|
+
${taskScopeFilter !== 'user-facing'
|
|
342
389
|
? 'bg-white/[0.06] border-white/[0.1] text-text-2'
|
|
343
390
|
: 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`}
|
|
344
391
|
style={{ fontFamily: 'inherit', minWidth: 130 }}
|
|
345
392
|
>
|
|
346
|
-
{filterAgentId && agents[filterAgentId] ? (
|
|
393
|
+
{taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
|
|
347
394
|
<>
|
|
348
395
|
<AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={18} />
|
|
349
396
|
{agents[filterAgentId].name}
|
|
350
397
|
</>
|
|
351
|
-
) : 'All
|
|
398
|
+
) : taskScopeFilter === 'all' ? 'All Tasks' : 'User View'}
|
|
352
399
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" className="ml-auto opacity-50">
|
|
353
400
|
<path d="M2.5 4L5 6.5L7.5 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
354
401
|
</svg>
|
|
355
402
|
</button>
|
|
356
403
|
{agentDropdownOpen && (
|
|
357
|
-
<div className="absolute top-full right-0 mt-1 min-w-[
|
|
404
|
+
<div className="absolute top-full right-0 mt-1 min-w-[240px] py-1 rounded-[12px] border border-white/[0.08] bg-surface-2 shadow-lg z-50">
|
|
405
|
+
<button
|
|
406
|
+
onClick={() => {
|
|
407
|
+
setTaskScopeFilter('user-facing')
|
|
408
|
+
setFilterAgentId('')
|
|
409
|
+
setAgentDropdownOpen(false)
|
|
410
|
+
}}
|
|
411
|
+
className={`w-full flex items-start gap-2.5 px-3 py-2.5 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
|
|
412
|
+
${taskScopeFilter === 'user-facing' ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
|
|
413
|
+
style={{ fontFamily: 'inherit' }}
|
|
414
|
+
>
|
|
415
|
+
<span className="mt-0.5 inline-flex h-5 items-center rounded-full bg-emerald-500/12 px-1.5 text-[10px] font-700 uppercase tracking-[0.08em] text-emerald-400">
|
|
416
|
+
Default
|
|
417
|
+
</span>
|
|
418
|
+
<span className="min-w-0">
|
|
419
|
+
<span className="block">User-facing tasks</span>
|
|
420
|
+
<span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
|
|
421
|
+
Hide scheduled, delegated, and agent-created internal work.
|
|
422
|
+
</span>
|
|
423
|
+
</span>
|
|
424
|
+
</button>
|
|
358
425
|
<button
|
|
359
|
-
onClick={() => {
|
|
360
|
-
|
|
361
|
-
|
|
426
|
+
onClick={() => {
|
|
427
|
+
setTaskScopeFilter('all')
|
|
428
|
+
setFilterAgentId('')
|
|
429
|
+
setAgentDropdownOpen(false)
|
|
430
|
+
}}
|
|
431
|
+
className={`w-full flex items-start gap-2.5 px-3 py-2.5 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
|
|
432
|
+
${taskScopeFilter === 'all' ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
|
|
362
433
|
style={{ fontFamily: 'inherit' }}
|
|
363
434
|
>
|
|
364
|
-
|
|
435
|
+
<span className="mt-0.5 inline-flex h-5 items-center rounded-full bg-white/[0.06] px-1.5 text-[10px] font-700 uppercase tracking-[0.08em] text-text-3">
|
|
436
|
+
All
|
|
437
|
+
</span>
|
|
438
|
+
<span className="min-w-0">
|
|
439
|
+
<span className="block">All tasks</span>
|
|
440
|
+
<span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
|
|
441
|
+
Include internal agent execution, schedules, and delegations.
|
|
442
|
+
</span>
|
|
443
|
+
</span>
|
|
365
444
|
</button>
|
|
445
|
+
<div className="my-1 border-t border-white/[0.06]" />
|
|
366
446
|
{Object.values(agents).sort((a, b) => a.name.localeCompare(b.name)).map((a) => (
|
|
367
447
|
<button
|
|
368
448
|
key={a.id}
|
|
369
|
-
onClick={() => {
|
|
449
|
+
onClick={() => {
|
|
450
|
+
setTaskScopeFilter('agent')
|
|
451
|
+
setFilterAgentId(a.id)
|
|
452
|
+
setAgentDropdownOpen(false)
|
|
453
|
+
}}
|
|
370
454
|
className={`w-full flex items-center gap-2.5 px-3 py-2 text-[13px] font-600 cursor-pointer border-none text-left transition-colors
|
|
371
|
-
${filterAgentId === a.id ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
|
|
455
|
+
${taskScopeFilter === 'agent' && filterAgentId === a.id ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
|
|
372
456
|
style={{ fontFamily: 'inherit' }}
|
|
373
457
|
>
|
|
374
458
|
<AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={20} />
|
|
375
|
-
|
|
459
|
+
<span className="min-w-0 flex-1">
|
|
460
|
+
<span className="block truncate">{a.name}</span>
|
|
461
|
+
<span className="mt-0.5 block text-[11px] font-500 text-text-3/60">
|
|
462
|
+
Assigned, created, or delegated by this agent
|
|
463
|
+
</span>
|
|
464
|
+
</span>
|
|
376
465
|
</button>
|
|
377
466
|
))}
|
|
378
467
|
</div>
|
|
@@ -520,8 +609,33 @@ export function TaskBoard() {
|
|
|
520
609
|
))}
|
|
521
610
|
</div>
|
|
522
611
|
|
|
523
|
-
{(activeProjectFilter && projects[activeProjectFilter]) || activeAttentionLabel ? (
|
|
612
|
+
{(activeProjectFilter && projects[activeProjectFilter]) || activeAttentionLabel || taskScopeFilter !== 'all' ? (
|
|
524
613
|
<div className="flex flex-wrap items-center gap-2 px-8 pb-3">
|
|
614
|
+
{taskScopeFilter !== 'all' && (
|
|
615
|
+
<span className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] border text-[12px] font-600 ${
|
|
616
|
+
taskScopeFilter === 'agent'
|
|
617
|
+
? 'bg-accent-soft border-accent-bright/20 text-accent-bright'
|
|
618
|
+
: 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
|
|
619
|
+
}`}>
|
|
620
|
+
{taskScopeFilter === 'agent' && filterAgentId && agents[filterAgentId] ? (
|
|
621
|
+
<>
|
|
622
|
+
<AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={14} />
|
|
623
|
+
{agents[filterAgentId].name} activity
|
|
624
|
+
</>
|
|
625
|
+
) : (
|
|
626
|
+
'User-facing tasks'
|
|
627
|
+
)}
|
|
628
|
+
<button
|
|
629
|
+
onClick={() => {
|
|
630
|
+
setTaskScopeFilter('all')
|
|
631
|
+
setFilterAgentId('')
|
|
632
|
+
}}
|
|
633
|
+
className="ml-1 cursor-pointer border-none bg-transparent p-0 text-[14px] leading-none text-current opacity-80 hover:opacity-100"
|
|
634
|
+
>
|
|
635
|
+
×
|
|
636
|
+
</button>
|
|
637
|
+
</span>
|
|
638
|
+
)}
|
|
525
639
|
{activeProjectFilter && projects[activeProjectFilter] && (
|
|
526
640
|
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] bg-white/[0.04] border border-white/[0.06] text-[12px] font-600 text-text-2">
|
|
527
641
|
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: projects[activeProjectFilter].color || '#6366F1' }} />
|
|
@@ -593,7 +707,7 @@ export function TaskBoard() {
|
|
|
593
707
|
) : filteredTasks.length === 0 ? (
|
|
594
708
|
<div className="max-w-3xl mx-auto rounded-[16px] border border-dashed border-white/[0.08] px-6 py-14 text-center">
|
|
595
709
|
<p className="text-[14px] font-600 text-text-2 mb-1">No tasks match this view</p>
|
|
596
|
-
<p className="text-[12px] text-text-3/60">Try clearing one of the active filters or switching back to
|
|
710
|
+
<p className="text-[12px] text-text-3/60">Try clearing one of the active filters or switching back to all tasks.</p>
|
|
597
711
|
</div>
|
|
598
712
|
) : (
|
|
599
713
|
<div className="max-w-4xl mx-auto">
|
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import { updateTask, archiveTask } from '@/lib/tasks'
|
|
7
7
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
8
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
9
|
import type { BoardTask } from '@/types'
|
|
9
10
|
|
|
10
11
|
function timeAgo(ts: number) {
|
|
@@ -46,6 +47,8 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
|
|
|
46
47
|
const tasks = useAppStore((s) => s.tasks)
|
|
47
48
|
const agent = agents[task.agentId]
|
|
48
49
|
const project = task.projectId ? projects[task.projectId] : null
|
|
50
|
+
const creatorAgent = task.createdByAgentId ? agents[task.createdByAgentId] : null
|
|
51
|
+
const delegatorAgent = task.delegatedByAgentId ? agents[task.delegatedByAgentId] : null
|
|
49
52
|
|
|
50
53
|
const priorityConfig = {
|
|
51
54
|
critical: { label: 'Critical', cls: 'bg-red-500/10 text-red-400' },
|
|
@@ -207,6 +210,32 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect, index
|
|
|
207
210
|
</div>
|
|
208
211
|
)}
|
|
209
212
|
|
|
213
|
+
{(creatorAgent || delegatorAgent || task.sourceType === 'schedule') && (
|
|
214
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
215
|
+
{delegatorAgent && (
|
|
216
|
+
<span className="inline-flex items-center gap-1.5 rounded-[7px] bg-amber-500/10 px-2 py-1 text-[10px] font-600 text-amber-300">
|
|
217
|
+
<AgentAvatar seed={delegatorAgent.avatarSeed} avatarUrl={delegatorAgent.avatarUrl} name={delegatorAgent.name} size={14} />
|
|
218
|
+
Delegated by {delegatorAgent.name}
|
|
219
|
+
</span>
|
|
220
|
+
)}
|
|
221
|
+
{creatorAgent && creatorAgent.id !== delegatorAgent?.id && (
|
|
222
|
+
<span className="inline-flex items-center gap-1.5 rounded-[7px] bg-white/[0.05] px-2 py-1 text-[10px] font-600 text-text-2">
|
|
223
|
+
<AgentAvatar seed={creatorAgent.avatarSeed} avatarUrl={creatorAgent.avatarUrl} name={creatorAgent.name} size={14} />
|
|
224
|
+
Created by {creatorAgent.name}
|
|
225
|
+
</span>
|
|
226
|
+
)}
|
|
227
|
+
{task.sourceType === 'schedule' && (
|
|
228
|
+
<span className="inline-flex items-center gap-1.5 rounded-[7px] bg-purple-500/10 px-2 py-1 text-[10px] font-600 text-purple-300">
|
|
229
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
230
|
+
<circle cx="12" cy="12" r="8" />
|
|
231
|
+
<path d="M12 8v4l3 2" />
|
|
232
|
+
</svg>
|
|
233
|
+
{task.sourceScheduleName ? `Scheduled via ${task.sourceScheduleName}` : 'Scheduled task'}
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
|
|
210
239
|
<div className="flex items-center gap-2 flex-wrap">
|
|
211
240
|
{agent && (
|
|
212
241
|
<span className="px-2 py-1 rounded-[6px] bg-accent-soft text-accent-bright text-[11px] font-600">
|
|
@@ -467,7 +467,7 @@ export function TaskSheet() {
|
|
|
467
467
|
)}
|
|
468
468
|
|
|
469
469
|
{/* CLI Sessions */}
|
|
470
|
-
{(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.cliResumeId) && (
|
|
470
|
+
{(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.geminiResumeId || editing.cliResumeId) && (
|
|
471
471
|
<div className="mb-8">
|
|
472
472
|
<SectionLabel>CLI Sessions</SectionLabel>
|
|
473
473
|
<div className="flex flex-wrap gap-2">
|
|
@@ -489,7 +489,13 @@ export function TaskSheet() {
|
|
|
489
489
|
<code className="text-[11px] text-text-3 font-mono">{editing.opencodeResumeId}</code>
|
|
490
490
|
</div>
|
|
491
491
|
)}
|
|
492
|
-
{
|
|
492
|
+
{editing.geminiResumeId && (
|
|
493
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
494
|
+
<span className="text-[11px] font-600 text-fuchsia-400">Gemini</span>
|
|
495
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.geminiResumeId}</code>
|
|
496
|
+
</div>
|
|
497
|
+
)}
|
|
498
|
+
{!(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.geminiResumeId) && editing.cliResumeId && (
|
|
493
499
|
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
494
500
|
<span className="text-[11px] font-600 text-text-2">{editing.cliProvider || 'CLI'}</span>
|
|
495
501
|
<code className="text-[11px] text-text-3 font-mono">{editing.cliResumeId}</code>
|
|
@@ -971,7 +977,7 @@ export function TaskSheet() {
|
|
|
971
977
|
</div>
|
|
972
978
|
)}
|
|
973
979
|
|
|
974
|
-
{editing && (editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.cliResumeId) && (
|
|
980
|
+
{editing && (editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.geminiResumeId || editing.cliResumeId) && (
|
|
975
981
|
<div className="mb-8">
|
|
976
982
|
<SectionLabel>CLI Sessions</SectionLabel>
|
|
977
983
|
<div className="flex flex-wrap gap-2">
|
|
@@ -993,7 +999,13 @@ export function TaskSheet() {
|
|
|
993
999
|
<code className="text-[11px] text-text-3 font-mono">{editing.opencodeResumeId}</code>
|
|
994
1000
|
</div>
|
|
995
1001
|
)}
|
|
996
|
-
{
|
|
1002
|
+
{editing.geminiResumeId && (
|
|
1003
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
1004
|
+
<span className="text-[11px] font-600 text-fuchsia-400">Gemini</span>
|
|
1005
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.geminiResumeId}</code>
|
|
1006
|
+
</div>
|
|
1007
|
+
)}
|
|
1008
|
+
{!(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.geminiResumeId) && editing.cliResumeId && (
|
|
997
1009
|
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
998
1010
|
<span className="text-[11px] font-600 text-text-2">{editing.cliProvider || 'CLI'}</span>
|
|
999
1011
|
<code className="text-[11px] text-text-3 font-mono">{editing.cliResumeId}</code>
|