@swarmclawai/swarmclaw 1.1.7 → 1.1.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 +16 -0
- package/next.config.ts +3 -2
- package/package.json +1 -1
- package/src/app/api/agents/[id]/status/route.ts +28 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +9 -1
- package/src/app/api/chatrooms/[id]/route.ts +10 -0
- package/src/app/api/chatrooms/route.ts +10 -0
- package/src/app/api/chats/route.ts +3 -19
- package/src/app/api/learned-skills/[id]/route.ts +83 -0
- package/src/app/api/skill-review-counts/route.ts +20 -0
- package/src/app/autonomy/page.tsx +1 -1
- package/src/app/globals.css +8 -0
- package/src/app/home/page.tsx +18 -2
- package/src/app/usage/page.tsx +1 -1
- package/src/cli/index.js +6 -1
- package/src/cli/spec.js +12 -1
- package/src/components/agents/agent-chat-list.tsx +3 -3
- package/src/components/agents/agent-list.tsx +1 -1
- package/src/components/chat/chat-area.tsx +34 -15
- package/src/components/chatrooms/chatroom-sheet.tsx +2 -1
- package/src/components/connectors/connector-inbox.tsx +1 -1
- package/src/components/home/cost-trend-chart.tsx +45 -0
- package/src/components/layout/daemon-indicator.tsx +1 -1
- package/src/components/layout/dashboard-shell.tsx +1 -1
- package/src/components/org-chart/delegation-bubble.tsx +102 -0
- package/src/components/org-chart/mini-chat-bubble.tsx +22 -4
- package/src/components/org-chart/org-chart-activity-feed.tsx +156 -0
- package/src/components/org-chart/org-chart-edge.tsx +1 -30
- package/src/components/org-chart/org-chart-view.tsx +27 -2
- package/src/components/shared/notification-center.tsx +1 -1
- package/src/hooks/use-agent-live-status.ts +59 -0
- package/src/hooks/use-delegation-edge-state.ts +166 -4
- package/src/hooks/use-ws.ts +69 -39
- package/src/lib/fetch-dedup.test.ts +8 -4
- package/src/lib/fetch-dedup.ts +6 -3
- package/src/lib/keyed-queue.ts +18 -2
- package/src/lib/provider-sets.ts +2 -2
- package/src/lib/providers/claude-cli.ts +23 -54
- package/src/lib/providers/cli-utils.ts +301 -0
- package/src/lib/providers/codex-cli.ts +42 -72
- package/src/lib/providers/error-classification.ts +8 -0
- package/src/lib/providers/gemini-cli.ts +177 -0
- package/src/lib/providers/index.ts +10 -1
- package/src/lib/providers/opencode-cli.ts +39 -57
- package/src/lib/server/agents/agent-availability.test.ts +74 -0
- package/src/lib/server/agents/agent-availability.ts +16 -0
- package/src/lib/server/agents/autonomy-contract.ts +5 -0
- package/src/lib/server/agents/delegation-jobs.ts +77 -5
- package/src/lib/server/agents/main-agent-loop.ts +39 -13
- package/src/lib/server/agents/subagent-runtime.ts +42 -4
- package/src/lib/server/agents/subagent-swarm.ts +47 -1
- package/src/lib/server/agents/team-resolution.test.ts +169 -0
- package/src/lib/server/agents/team-resolution.ts +96 -0
- package/src/lib/server/autonomy/supervisor-reflection.ts +132 -1
- package/src/lib/server/chat-execution/chat-execution.ts +23 -5
- package/src/lib/server/chat-execution/continuation-limits.ts +5 -1
- package/src/lib/server/chat-execution/iteration-event-handler.ts +15 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +6 -4
- package/src/lib/server/chat-execution/prompt-builder.ts +30 -2
- package/src/lib/server/chat-execution/prompt-sections.ts +80 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +66 -3
- package/src/lib/server/chat-execution/stream-continuation.ts +36 -7
- package/src/lib/server/connectors/connector-inbound.ts +1 -15
- package/src/lib/server/connectors/connector-lifecycle.ts +1 -1
- package/src/lib/server/connectors/manager.test.ts +1 -1
- package/src/lib/server/context-manager.ts +31 -0
- package/src/lib/server/debug.ts +20 -0
- package/src/lib/server/embeddings.ts +36 -16
- package/src/lib/server/execution-log.ts +10 -0
- package/src/lib/server/extensions.ts +5 -5
- package/src/lib/server/openclaw/gateway.ts +4 -6
- package/src/lib/server/protocols/protocol-agent-turn.ts +1 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +2 -3
- package/src/lib/server/provider-health.ts +23 -12
- package/src/lib/server/runtime/daemon-state.ts +40 -2
- package/src/lib/server/runtime/heartbeat-service.ts +103 -8
- package/src/lib/server/runtime/run-ledger.ts +54 -0
- package/src/lib/server/runtime/session-run-manager.ts +112 -7
- package/src/lib/server/runtime/system-events.ts +16 -0
- package/src/lib/server/runtime/wake-dispatcher.ts +15 -5
- package/src/lib/server/session-tools/chatroom.ts +37 -0
- package/src/lib/server/session-tools/delegate.ts +29 -20
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/peer-query.test.ts +71 -0
- package/src/lib/server/session-tools/peer-query.ts +300 -0
- package/src/lib/server/session-tools/team-context.test.ts +41 -0
- package/src/lib/server/session-tools/team-context.ts +277 -0
- package/src/lib/server/skills/learned-skills.ts +1 -1
- package/src/lib/server/storage-normalization.test.ts +26 -25
- package/src/lib/server/storage-normalization.ts +36 -6
- package/src/lib/server/storage.ts +39 -28
- package/src/lib/server/tool-loop-detection.test.ts +44 -1
- package/src/lib/server/tool-loop-detection.ts +122 -38
- package/src/lib/ws-client.ts +19 -6
- package/src/stores/slices/agent-slice.ts +2 -2
- package/src/stores/slices/session-slice.ts +2 -2
- package/src/stores/use-chat-store.test.ts +7 -0
- package/src/stores/use-chat-store.ts +14 -53
- package/src/types/index.ts +6 -3
package/README.md
CHANGED
|
@@ -190,6 +190,22 @@ The building blocks are the same: **agents, tools, memory, delegation, schedules
|
|
|
190
190
|
|
|
191
191
|
## Release Notes
|
|
192
192
|
|
|
193
|
+
### v1.1.8 Highlights
|
|
194
|
+
|
|
195
|
+
- **Agent live status**: real-time `/agents/:id/status` endpoint exposes goal, progress, and plan steps; org chart detail panel consumes it via `useAgentLiveStatus` hook.
|
|
196
|
+
- **Learned skills lifecycle**: promote, dismiss, and delete learned skills via `/learned-skills/:id`; `/skill-review-counts` provides badge counts for the skills workspace.
|
|
197
|
+
- **Gemini CLI provider**: Google Gemini CLI joins the provider roster alongside claude-cli and codex-cli, with shared CLI utilities factored into `cli-utils.ts`.
|
|
198
|
+
- **Peer query & team context tools**: new session tools let agents query peers and access team context during conversations.
|
|
199
|
+
- **Team resolution**: dedicated `team-resolution.ts` module resolves agent teams for delegation routing.
|
|
200
|
+
- **Org chart activity feed**: timeline feed component and delegation bubble visualization for the org chart view.
|
|
201
|
+
- **Skills workspace improvements**: expanded skills management UI with review-ready badges.
|
|
202
|
+
- **Cost trend chart**: new dashboard component for cost visualization.
|
|
203
|
+
- **Streaming fix**: text no longer gets stuck on the thinking indicator.
|
|
204
|
+
- **Delegation normalization**: `delegationEnabled` now derived from agent role, removed from starter kit templates.
|
|
205
|
+
- **Chat execution refinements**: improved continuation limits, post-stream finalization, and stream continuation.
|
|
206
|
+
- **Memory and storage improvements**: memory tier management, consolidation enhancements, and storage cache updates.
|
|
207
|
+
- **WebSocket and provider health**: improved WS client handling, delegation edge state, and provider health monitoring.
|
|
208
|
+
|
|
193
209
|
### v1.1.7 Highlights
|
|
194
210
|
|
|
195
211
|
- **Projects page redesign**: tabbed navigation (Overview, Work, Operations, Activity) with health grid, sortable task list, and timeline feed.
|
package/next.config.ts
CHANGED
|
@@ -58,9 +58,10 @@ const nextConfig: NextConfig = {
|
|
|
58
58
|
root: PROJECT_ROOT,
|
|
59
59
|
},
|
|
60
60
|
experimental: {
|
|
61
|
-
// Disable Turbopack persistent cache — concurrent HMR writes cause
|
|
62
|
-
// "Another write batch or compaction is already active" errors
|
|
63
61
|
turbopackFileSystemCacheForDev: false,
|
|
62
|
+
// Limit build workers to 1 inside Docker to avoid SQLITE_BUSY contention
|
|
63
|
+
// when multiple workers collect page data concurrently.
|
|
64
|
+
...(process.env.SWARMCLAW_BUILD_MODE ? { cpus: 1 } : {}),
|
|
64
65
|
},
|
|
65
66
|
env: {
|
|
66
67
|
NEXT_PUBLIC_GIT_SHA: getGitSha(),
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadAgents } from '@/lib/server/storage'
|
|
3
|
+
import { getMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params
|
|
9
|
+
const agents = loadAgents()
|
|
10
|
+
const agent = agents[id]
|
|
11
|
+
if (!agent) return NextResponse.json(null, { status: 404 })
|
|
12
|
+
|
|
13
|
+
const sessionId = agent.threadSessionId
|
|
14
|
+
if (!sessionId) return NextResponse.json({ status: 'no_session' }, { status: 200 })
|
|
15
|
+
|
|
16
|
+
const state = getMainLoopStateForSession(sessionId)
|
|
17
|
+
if (!state) return NextResponse.json({ status: 'no_state' }, { status: 200 })
|
|
18
|
+
|
|
19
|
+
return NextResponse.json({
|
|
20
|
+
goal: state.goal,
|
|
21
|
+
status: state.status,
|
|
22
|
+
summary: state.summary,
|
|
23
|
+
nextAction: state.nextAction,
|
|
24
|
+
planSteps: state.planSteps,
|
|
25
|
+
currentPlanStep: state.currentPlanStep,
|
|
26
|
+
updatedAt: state.updatedAt,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -4,6 +4,7 @@ import { notify } from '@/lib/server/ws-hub'
|
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
6
|
import { genId } from '@/lib/id'
|
|
7
|
+
import { isWorkerOnlyAgent, buildWorkerOnlyAgentMessage } from '@/lib/server/agents/agent-availability'
|
|
7
8
|
|
|
8
9
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
10
|
const { id } = await params
|
|
@@ -16,11 +17,18 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
16
17
|
const agentId = typeof body.agentId === 'string' ? body.agentId : ''
|
|
17
18
|
if (!agentId) return NextResponse.json({ error: 'agentId is required' }, { status: 400 })
|
|
18
19
|
|
|
20
|
+
const agents = loadAgents()
|
|
21
|
+
if (isWorkerOnlyAgent(agents[agentId])) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: buildWorkerOnlyAgentMessage(agents[agentId], 'join chatrooms') },
|
|
24
|
+
{ status: 400 },
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
if (!chatroom.agentIds.includes(agentId)) {
|
|
20
29
|
chatroom.agentIds.push(agentId)
|
|
21
30
|
|
|
22
31
|
// Inject a system event message
|
|
23
|
-
const agents = loadAgents()
|
|
24
32
|
const agentName = agents[agentId]?.name || 'Unknown agent'
|
|
25
33
|
if (!Array.isArray(chatroom.messages)) chatroom.messages = []
|
|
26
34
|
chatroom.messages.push({
|
|
@@ -4,6 +4,7 @@ import { notify } from '@/lib/server/ws-hub'
|
|
|
4
4
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
5
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
6
|
import { genId } from '@/lib/id'
|
|
7
|
+
import { isWorkerOnlyAgent } from '@/lib/server/agents/agent-availability'
|
|
7
8
|
|
|
8
9
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
10
|
const { id } = await params
|
|
@@ -50,6 +51,15 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
50
51
|
{ status: 400 },
|
|
51
52
|
)
|
|
52
53
|
}
|
|
54
|
+
const cliAgentNames = agentIds
|
|
55
|
+
.filter((agentId) => isWorkerOnlyAgent(agents[agentId]))
|
|
56
|
+
.map((agentId) => agents[agentId]?.name || agentId)
|
|
57
|
+
if (cliAgentNames.length > 0) {
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: `CLI-based agents cannot join chatrooms: ${cliAgentNames.join(', ')}. They can only be used for direct chats and delegation.` },
|
|
60
|
+
{ status: 400 },
|
|
61
|
+
)
|
|
62
|
+
}
|
|
53
63
|
|
|
54
64
|
const oldIds = new Set(chatroom.agentIds)
|
|
55
65
|
const newIds = new Set(agentIds)
|
|
@@ -6,6 +6,7 @@ import { ChatroomCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
|
6
6
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
7
7
|
import { z } from 'zod'
|
|
8
8
|
import type { Chatroom, ChatroomMessage } from '@/types'
|
|
9
|
+
import { isWorkerOnlyAgent } from '@/lib/server/agents/agent-availability'
|
|
9
10
|
|
|
10
11
|
export const dynamic = 'force-dynamic'
|
|
11
12
|
|
|
@@ -49,6 +50,15 @@ export async function POST(req: Request) {
|
|
|
49
50
|
{ status: 400 },
|
|
50
51
|
)
|
|
51
52
|
}
|
|
53
|
+
const cliAgentNames = requestedAgentIds
|
|
54
|
+
.filter((agentId) => isWorkerOnlyAgent(knownAgents[agentId]))
|
|
55
|
+
.map((agentId) => knownAgents[agentId]?.name || agentId)
|
|
56
|
+
if (cliAgentNames.length > 0) {
|
|
57
|
+
return NextResponse.json(
|
|
58
|
+
{ error: `CLI-based agents cannot join chatrooms: ${cliAgentNames.join(', ')}. They can only be used for direct chats and delegation.` },
|
|
59
|
+
{ status: 400 },
|
|
60
|
+
)
|
|
61
|
+
}
|
|
52
62
|
const agentIds: string[] = requestedAgentIds
|
|
53
63
|
const chatMode = body.chatMode === 'parallel' ? 'parallel' : 'sequential'
|
|
54
64
|
const autoAddress = Boolean(body.autoAddress)
|
|
@@ -3,14 +3,13 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { perf } from '@/lib/server/runtime/perf'
|
|
6
|
-
import { loadSessions, saveSessions, deleteSession, active, loadAgents
|
|
6
|
+
import { loadSessions, saveSessions, deleteSession, active, loadAgents } from '@/lib/server/storage'
|
|
7
7
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
8
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
9
9
|
import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
10
10
|
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
11
11
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
12
12
|
import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
|
|
13
|
-
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
14
13
|
import { buildSessionListSummary } from '@/lib/chat/session-summary'
|
|
15
14
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
16
15
|
import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
|
|
@@ -24,30 +23,15 @@ async function ensureDaemonIfNeeded(source: string) {
|
|
|
24
23
|
|
|
25
24
|
export async function GET(req: Request) {
|
|
26
25
|
const endPerf = perf.start('api', 'GET /api/chats')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
pruneThreadConnectorMirrors()
|
|
30
|
-
} catch (err) {
|
|
31
|
-
console.error('[api/chats] pruneThreadConnectorMirrors failed:', err)
|
|
32
|
-
}
|
|
26
|
+
// Note: pruneThreadConnectorMirrors and materializeStreamingAssistantArtifacts
|
|
27
|
+
// are handled by the daemon periodic health check, not on every list fetch.
|
|
33
28
|
const sessions = loadSessions()
|
|
34
|
-
const changedSessionIds: string[] = []
|
|
35
29
|
for (const id of Object.keys(sessions)) {
|
|
36
30
|
const run = getSessionRunState(id)
|
|
37
31
|
const queue = getSessionQueueSnapshot(id)
|
|
38
32
|
sessions[id].active = active.has(id) || !!run.runningRunId
|
|
39
33
|
sessions[id].queuedCount = queue.queueLength
|
|
40
34
|
sessions[id].currentRunId = run.runningRunId || null
|
|
41
|
-
if (!sessions[id].active && Array.isArray(sessions[id].messages)) {
|
|
42
|
-
if (materializeStreamingAssistantArtifacts(sessions[id].messages)) changedSessionIds.push(id)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const id of changedSessionIds) {
|
|
46
|
-
const persisted = { ...sessions[id] } as Record<string, unknown>
|
|
47
|
-
delete persisted.active
|
|
48
|
-
delete persisted.queuedCount
|
|
49
|
-
delete persisted.currentRunId
|
|
50
|
-
upsertStoredItem('sessions', id, persisted)
|
|
51
35
|
}
|
|
52
36
|
|
|
53
37
|
const summarized = Object.fromEntries(
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { loadLearnedSkill, upsertLearnedSkill, deleteLearnedSkill } from '@/lib/server/storage'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
9
|
+
const { id } = await params
|
|
10
|
+
const url = new URL(req.url)
|
|
11
|
+
const action = url.searchParams.get('action')
|
|
12
|
+
|
|
13
|
+
const skill = loadLearnedSkill(id)
|
|
14
|
+
if (!skill) {
|
|
15
|
+
return NextResponse.json({ error: 'Learned skill not found' }, { status: 404 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (action === 'promote') {
|
|
19
|
+
if (skill.lifecycle !== 'review_ready') {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ error: 'Only review_ready skills can be promoted' },
|
|
22
|
+
{ status: 400 },
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Demote parent first — if this fails, child stays unpromoted (safe)
|
|
27
|
+
if (skill.parentSkillId) {
|
|
28
|
+
const parent = loadLearnedSkill(skill.parentSkillId)
|
|
29
|
+
if (parent && parent.lifecycle === 'active') {
|
|
30
|
+
parent.lifecycle = 'demoted'
|
|
31
|
+
parent.demotedAt = Date.now()
|
|
32
|
+
parent.demotionReason = 'Replaced by promoted revision'
|
|
33
|
+
parent.updatedAt = Date.now()
|
|
34
|
+
upsertLearnedSkill(parent.id, parent)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
skill.lifecycle = 'active'
|
|
39
|
+
skill.activationCount = (skill.activationCount ?? 0) + 1
|
|
40
|
+
skill.reviewReadyAt = null
|
|
41
|
+
skill.updatedAt = Date.now()
|
|
42
|
+
upsertLearnedSkill(id, skill)
|
|
43
|
+
|
|
44
|
+
notify('learned_skills')
|
|
45
|
+
return NextResponse.json(skill)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (action === 'dismiss') {
|
|
49
|
+
let reason: string | null = null
|
|
50
|
+
try {
|
|
51
|
+
const body = await req.json()
|
|
52
|
+
if (body && typeof body.reason === 'string') {
|
|
53
|
+
reason = body.reason
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// no body or invalid JSON — reason stays null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
skill.lifecycle = 'demoted'
|
|
60
|
+
skill.demotedAt = Date.now()
|
|
61
|
+
skill.demotionReason = reason ?? 'Dismissed by user'
|
|
62
|
+
skill.updatedAt = Date.now()
|
|
63
|
+
upsertLearnedSkill(id, skill)
|
|
64
|
+
|
|
65
|
+
notify('learned_skills')
|
|
66
|
+
return NextResponse.json(skill)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
73
|
+
const { id } = await params
|
|
74
|
+
|
|
75
|
+
const skill = loadLearnedSkill(id)
|
|
76
|
+
if (!skill) {
|
|
77
|
+
return NextResponse.json({ error: 'Learned skill not found' }, { status: 404 })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
deleteLearnedSkill(id)
|
|
81
|
+
notify('learned_skills')
|
|
82
|
+
return NextResponse.json({ ok: true })
|
|
83
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
import { listLearnedSkills } from '@/lib/server/skills/learned-skills'
|
|
4
|
+
import { listSkillSuggestions } from '@/lib/server/skills/skill-suggestions'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const suggestions = listSkillSuggestions()
|
|
10
|
+
const learned = listLearnedSkills()
|
|
11
|
+
|
|
12
|
+
const draftSuggestions = suggestions.filter((s) => s.status === 'draft').length
|
|
13
|
+
const reviewReadyLearned = learned.filter((s) => s.lifecycle === 'review_ready').length
|
|
14
|
+
|
|
15
|
+
return NextResponse.json({
|
|
16
|
+
draftSuggestions,
|
|
17
|
+
reviewReadyLearned,
|
|
18
|
+
total: draftSuggestions + reviewReadyLearned,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -266,7 +266,7 @@ export default function AutonomyPage() {
|
|
|
266
266
|
setAgents(agentMap ? Object.values(agentMap) : [])
|
|
267
267
|
} catch { /* swallow — load() will surface errors */ }
|
|
268
268
|
}, [])
|
|
269
|
-
useWs('agents', loadAgents,
|
|
269
|
+
useWs('agents', loadAgents, 60_000)
|
|
270
270
|
|
|
271
271
|
async function toggleOrchestrator(agent: Agent) {
|
|
272
272
|
setPendingAction('orchestrator-toggle')
|
package/src/app/globals.css
CHANGED
|
@@ -324,6 +324,14 @@ textarea:hover::-webkit-scrollbar { width: 6px; }
|
|
|
324
324
|
100% { opacity: 0; }
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/* Delegation bubble above org-chart nodes: pop-in, hold, fade-out over 5s */
|
|
328
|
+
@keyframes delegationBubbleFade {
|
|
329
|
+
0% { opacity: 0; transform: translateY(6px) scale(0.95); }
|
|
330
|
+
6% { opacity: 1; transform: translateY(0) scale(1); }
|
|
331
|
+
80% { opacity: 1; }
|
|
332
|
+
100% { opacity: 0; transform: translateY(-2px); }
|
|
333
|
+
}
|
|
334
|
+
|
|
327
335
|
/* ===== SwarmClaw Loader Keyframes ===== */
|
|
328
336
|
@keyframes sc-orbit {
|
|
329
337
|
from { transform: rotate(0deg); }
|
package/src/app/home/page.tsx
CHANGED
|
@@ -13,7 +13,7 @@ import { isLocalhostBrowser, isVisibleSessionForViewer } from '@/lib/observabili
|
|
|
13
13
|
import { getSessionLastMessage } from '@/lib/chat/session-summary'
|
|
14
14
|
import { getNotificationActivityAt, getNotificationOccurrenceCount } from '@/lib/notifications/notification-utils'
|
|
15
15
|
import { timeAgo, timeUntil } from '@/lib/time-format'
|
|
16
|
-
import type { Agent, Session,
|
|
16
|
+
import type { Agent, Session, BoardTask, AppNotification, ActivityEntry } from '@/types'
|
|
17
17
|
import { getEnabledCapabilityIds } from '@/lib/capability-selection'
|
|
18
18
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
19
19
|
import { MainContent } from '@/components/layout/main-content'
|
|
@@ -34,9 +34,17 @@ const ACTIVITY_ICONS: Record<ActivityEntry['action'], string> = {
|
|
|
34
34
|
failed: 'M18 6L6 18M6 6l12 12',
|
|
35
35
|
approved: 'M22 11.08V12a10 10 0 1 1-5.93-9.14',
|
|
36
36
|
rejected: 'M10 15l5-5m0 5l-5-5',
|
|
37
|
+
delegated: 'M7 17l9.2-9.2M17 17V7H7',
|
|
38
|
+
queried: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z',
|
|
39
|
+
spawned: 'M12 2v4m0 12v4m10-10h-4M6 12H2m15.07-5.07l-2.83 2.83M9.76 14.24l-2.83 2.83m11.14 0l-2.83-2.83M9.76 9.76L6.93 6.93',
|
|
40
|
+
timeout: 'M12 6v6l4 2M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20',
|
|
41
|
+
cancelled: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20m5 5L7 17',
|
|
42
|
+
incident: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4m0 4h.01',
|
|
43
|
+
running: 'M12 2v4m0 12v4m10-10h-4M6 12H2',
|
|
44
|
+
claimed: 'M9 12l2 2 4-4m6 2a10 10 0 1 1-20 0 10 10 0 0 1 20 0z',
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
const ACTIVITY_COLORS: Record<
|
|
47
|
+
const ACTIVITY_COLORS: Record<string, string> = {
|
|
40
48
|
created: 'text-emerald-400',
|
|
41
49
|
updated: 'text-sky-400',
|
|
42
50
|
deleted: 'text-red-400',
|
|
@@ -49,6 +57,14 @@ const ACTIVITY_COLORS: Record<ActivityEntry['action'], string> = {
|
|
|
49
57
|
failed: 'text-red-400',
|
|
50
58
|
approved: 'text-emerald-400',
|
|
51
59
|
rejected: 'text-red-400',
|
|
60
|
+
delegated: 'text-purple-400',
|
|
61
|
+
queried: 'text-sky-400',
|
|
62
|
+
spawned: 'text-purple-400',
|
|
63
|
+
timeout: 'text-amber-400',
|
|
64
|
+
cancelled: 'text-gray-400',
|
|
65
|
+
incident: 'text-red-400',
|
|
66
|
+
running: 'text-blue-400',
|
|
67
|
+
claimed: 'text-emerald-400',
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
const PLATFORM_LABELS: Record<string, string> = {
|
package/src/app/usage/page.tsx
CHANGED
|
@@ -141,7 +141,7 @@ export default function UsagePage() {
|
|
|
141
141
|
|
|
142
142
|
useEffect(() => { loadTaskMetrics() }, [loadTaskMetrics])
|
|
143
143
|
|
|
144
|
-
useWs('usage', loadData,
|
|
144
|
+
useWs('usage', loadData, 60_000)
|
|
145
145
|
useWs('tasks', loadTaskMetrics, 15_000)
|
|
146
146
|
|
|
147
147
|
const completionRate = computeCompletionRate(tasks)
|
package/src/cli/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const COMMAND_GROUPS = [
|
|
|
25
25
|
cmd('thread', 'POST', '/agents/:id/thread', 'Get or create agent thread session'),
|
|
26
26
|
cmd('clone', 'POST', '/agents/:id/clone', 'Clone an agent'),
|
|
27
27
|
cmd('bulk-update', 'PATCH', '/agents/bulk', 'Bulk update agents', { expectsJsonBody: true }),
|
|
28
|
+
cmd('status', 'GET', '/agents/:id/status', 'Get live status for an agent'),
|
|
28
29
|
],
|
|
29
30
|
},
|
|
30
31
|
{
|
|
@@ -619,6 +620,10 @@ const COMMAND_GROUPS = [
|
|
|
619
620
|
description: 'Inspect agent-scoped learned skills',
|
|
620
621
|
commands: [
|
|
621
622
|
cmd('list', 'GET', '/learned-skills', 'List learned skills'),
|
|
623
|
+
cmd('promote', 'POST', '/learned-skills/:id?action=promote', 'Promote a review-ready skill to active'),
|
|
624
|
+
cmd('dismiss', 'POST', '/learned-skills/:id?action=dismiss', 'Dismiss a learned skill'),
|
|
625
|
+
cmd('delete', 'DELETE', '/learned-skills/:id', 'Delete a learned skill'),
|
|
626
|
+
cmd('review-counts', 'GET', '/skill-review-counts', 'Show pending review counts'),
|
|
622
627
|
],
|
|
623
628
|
},
|
|
624
629
|
{
|
|
@@ -1566,7 +1571,7 @@ function extractById(payload, id) {
|
|
|
1566
1571
|
function getApiCoveragePairs() {
|
|
1567
1572
|
return COMMANDS
|
|
1568
1573
|
.filter((command) => !command.virtual)
|
|
1569
|
-
.map((command) => `${command.method} ${command.route}`)
|
|
1574
|
+
.map((command) => `${command.method} ${command.route.split('?')[0]}`)
|
|
1570
1575
|
}
|
|
1571
1576
|
|
|
1572
1577
|
module.exports = {
|
package/src/cli/spec.js
CHANGED
|
@@ -11,6 +11,7 @@ const COMMAND_GROUPS = {
|
|
|
11
11
|
trash: { description: 'List trashed agents', method: 'GET', path: '/agents/trash' },
|
|
12
12
|
restore: { description: 'Restore a trashed agent', method: 'POST', path: '/agents/trash' },
|
|
13
13
|
purge: { description: 'Permanently delete a trashed agent', method: 'DELETE', path: '/agents/trash' },
|
|
14
|
+
status: { description: 'Get live status for an agent', method: 'GET', path: '/agents/:id/status', params: ['id'] },
|
|
14
15
|
},
|
|
15
16
|
},
|
|
16
17
|
activity: {
|
|
@@ -460,6 +461,16 @@ const COMMAND_GROUPS = {
|
|
|
460
461
|
doctor: { description: 'Run local setup diagnostics', method: 'GET', path: '/setup/doctor' },
|
|
461
462
|
},
|
|
462
463
|
},
|
|
464
|
+
'learned-skills': {
|
|
465
|
+
description: 'Inspect agent-scoped learned skills',
|
|
466
|
+
commands: {
|
|
467
|
+
list: { description: 'List learned skills', method: 'GET', path: '/learned-skills' },
|
|
468
|
+
promote: { description: 'Promote a review-ready skill to active', method: 'POST', path: '/learned-skills/:id?action=promote', params: ['id'] },
|
|
469
|
+
dismiss: { description: 'Dismiss a learned skill', method: 'POST', path: '/learned-skills/:id?action=dismiss', params: ['id'] },
|
|
470
|
+
delete: { description: 'Delete a learned skill', method: 'DELETE', path: '/learned-skills/:id', params: ['id'] },
|
|
471
|
+
'review-counts': { description: 'Show pending review counts', method: 'GET', path: '/skill-review-counts' },
|
|
472
|
+
},
|
|
473
|
+
},
|
|
463
474
|
skills: {
|
|
464
475
|
description: 'SwarmClaw and Claude skills',
|
|
465
476
|
commands: {
|
|
@@ -544,7 +555,7 @@ function listCoveredRoutes() {
|
|
|
544
555
|
for (const action of Object.keys(commands)) {
|
|
545
556
|
const cmd = commands[action]
|
|
546
557
|
if (cmd.method && cmd.path) {
|
|
547
|
-
routes.push(`${cmd.method.toUpperCase()} ${cmd.path}`)
|
|
558
|
+
routes.push(`${cmd.method.toUpperCase()} ${cmd.path.split('?')[0]}`)
|
|
548
559
|
}
|
|
549
560
|
}
|
|
550
561
|
}
|
|
@@ -94,7 +94,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
94
94
|
}, [])
|
|
95
95
|
|
|
96
96
|
useEffect(() => { loadAgents() }, [loadAgents])
|
|
97
|
-
useWs('agents', loadAgents,
|
|
97
|
+
useWs('agents', loadAgents, 60_000)
|
|
98
98
|
useWs('sessions', loadSessions, 15_000)
|
|
99
99
|
useWs('runs', loadSessions, 5_000)
|
|
100
100
|
|
|
@@ -450,7 +450,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
450
450
|
}`} />
|
|
451
451
|
</div>
|
|
452
452
|
<div className="flex flex-col flex-1 min-w-0">
|
|
453
|
-
<div className="flex items-center gap-2">
|
|
453
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
454
454
|
<span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
|
|
455
455
|
{agent.name}
|
|
456
456
|
</span>
|
|
@@ -464,7 +464,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
464
464
|
Default
|
|
465
465
|
</span>
|
|
466
466
|
)}
|
|
467
|
-
<span className="text-[10px] text-text-3/60 font-mono shrink-0">
|
|
467
|
+
<span className="text-[10px] text-text-3/60 font-mono shrink-0 max-w-[30%] truncate">
|
|
468
468
|
{(threadSession?.model || agent.model)
|
|
469
469
|
? (threadSession?.model || agent.model)!.split('/').pop()?.split(':')[0]
|
|
470
470
|
: agent.provider}
|
|
@@ -49,7 +49,7 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
49
49
|
|
|
50
50
|
const [loaded, setLoaded] = useState(Object.keys(agents).length > 0)
|
|
51
51
|
useEffect(() => { loadAgents().then(() => setLoaded(true)) }, [loadAgents])
|
|
52
|
-
useWs('agents', loadAgents,
|
|
52
|
+
useWs('agents', loadAgents, 60_000)
|
|
53
53
|
|
|
54
54
|
// Compute which agents are "running" (have active sessions)
|
|
55
55
|
const runningAgentIds = useMemo(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useEffect, useCallback, useState, useRef, useMemo } from 'react'
|
|
4
|
+
import dynamic from 'next/dynamic'
|
|
4
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
6
|
import { selectActiveSessionId } from '@/stores/slices/session-slice'
|
|
6
7
|
import { useWs } from '@/hooks/use-ws'
|
|
@@ -12,13 +13,15 @@ import { useMediaQuery } from '@/hooks/use-media-query'
|
|
|
12
13
|
import { ChatHeader } from './chat-header'
|
|
13
14
|
import { DevServerBar } from './dev-server-bar'
|
|
14
15
|
import { MessageList } from './message-list'
|
|
15
|
-
import { SessionDebugPanel } from './session-debug-panel'
|
|
16
16
|
import { VoiceOverlay } from './voice-overlay'
|
|
17
17
|
import { useVoiceConversation } from '@/hooks/use-voice-conversation'
|
|
18
18
|
import { ChatInput } from '@/components/input/chat-input'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
|
|
20
|
+
// Lazy-load conditional panels — only bundled when actually rendered
|
|
21
|
+
const SessionDebugPanel = dynamic(() => import('./session-debug-panel').then((m) => m.SessionDebugPanel), { ssr: false })
|
|
22
|
+
const ChatPreviewPanel = dynamic(() => import('./chat-preview-panel').then((m) => m.ChatPreviewPanel), { ssr: false })
|
|
23
|
+
const InspectorPanel = dynamic(() => import('@/components/agents/inspector-panel').then((m) => m.InspectorPanel), { ssr: false })
|
|
24
|
+
const HeartbeatHistoryPanel = dynamic(() => import('./heartbeat-history-panel').then((m) => m.HeartbeatHistoryPanel), { ssr: false })
|
|
22
25
|
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
23
26
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
24
27
|
import { speak } from '@/lib/tts'
|
|
@@ -68,7 +71,10 @@ export function ChatArea() {
|
|
|
68
71
|
const setPreviewContent = useChatStore((s) => s.setPreviewContent)
|
|
69
72
|
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
70
73
|
|
|
71
|
-
const
|
|
74
|
+
const currentAgent = useAppStore((s) => {
|
|
75
|
+
const agentId = session?.agentId
|
|
76
|
+
return agentId ? s.agents[agentId] ?? null : null
|
|
77
|
+
})
|
|
72
78
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
73
79
|
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
|
|
74
80
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
@@ -76,7 +82,6 @@ export function ChatArea() {
|
|
|
76
82
|
const inspectorOpen = useAppStore((s) => s.inspectorOpen)
|
|
77
83
|
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
|
78
84
|
const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
|
|
79
|
-
const currentAgent = session?.agentId ? agents[session.agentId] ?? null : null
|
|
80
85
|
const queuedCount = session?.queuedCount ?? 0
|
|
81
86
|
const promptSuggestions = useMemo(
|
|
82
87
|
() => (currentAgent ? AGENT_PROMPT_SUGGESTIONS : DIRECT_PROMPT_SUGGESTIONS),
|
|
@@ -146,7 +151,7 @@ export function ChatArea() {
|
|
|
146
151
|
if (!preserveLocalStream) setMessages([])
|
|
147
152
|
setMessagesLoading(true)
|
|
148
153
|
if (!preserveLocalStream) {
|
|
149
|
-
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '', assistantRenderId: null, toolEvents: [] })
|
|
154
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', assistantRenderId: null, toolEvents: [] })
|
|
150
155
|
}
|
|
151
156
|
fetchMessagesPaginated(requestedSessionId, 100).then((data) => {
|
|
152
157
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
@@ -174,7 +179,7 @@ export function ChatArea() {
|
|
|
174
179
|
|
|
175
180
|
const sessionAtLoad = useAppStore.getState().sessions[requestedSessionId]
|
|
176
181
|
if (sessionAtLoad?.active) {
|
|
177
|
-
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamText: '' })
|
|
182
|
+
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamSource: 'server', streamText: '' })
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
return () => {
|
|
@@ -191,7 +196,7 @@ export function ChatArea() {
|
|
|
191
196
|
if (cancelled || selectActiveSessionId(useAppStore.getState()) !== requestedSessionId) return
|
|
192
197
|
const refreshed = useAppStore.getState().sessions[requestedSessionId]
|
|
193
198
|
if (refreshed?.active) {
|
|
194
|
-
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamText: '' })
|
|
199
|
+
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamSource: 'server', streamText: '' })
|
|
195
200
|
}
|
|
196
201
|
}).catch((err) => console.error('Failed to refresh session:', err))
|
|
197
202
|
|
|
@@ -248,11 +253,11 @@ export function ChatArea() {
|
|
|
248
253
|
// Fetching messages here would replace the array with new objects on every
|
|
249
254
|
// WS notification, causing the full MessageList to re-render and flash.
|
|
250
255
|
const chatState = useChatStore.getState()
|
|
251
|
-
if (chatState.streaming && chatState.streamingSessionId === sessionId) return
|
|
256
|
+
if (chatState.streaming && chatState.streamingSessionId === sessionId && chatState.streamSource === 'local') return
|
|
252
257
|
try {
|
|
253
258
|
const msgs = await fetchMessages(sessionId)
|
|
254
259
|
const currentChatState = useChatStore.getState()
|
|
255
|
-
if (currentChatState.streaming && currentChatState.streamingSessionId === sessionId) return
|
|
260
|
+
if (currentChatState.streaming && currentChatState.streamingSessionId === sessionId && currentChatState.streamSource === 'local') return
|
|
256
261
|
const previous = messagesRef.current
|
|
257
262
|
if (messagesDiffer(msgs, previous)) {
|
|
258
263
|
const newMsgs = msgs.length > previous.length ? msgs.slice(previous.length) : []
|
|
@@ -290,6 +295,7 @@ export function ChatArea() {
|
|
|
290
295
|
useChatStore.setState({
|
|
291
296
|
streaming: true,
|
|
292
297
|
streamingSessionId: sessionId,
|
|
298
|
+
streamSource: 'server',
|
|
293
299
|
streamPhase: 'thinking',
|
|
294
300
|
streamText: '',
|
|
295
301
|
thinkingStartTime: Date.now(),
|
|
@@ -313,13 +319,26 @@ export function ChatArea() {
|
|
|
313
319
|
sessionId && (isServerActive || queuedCount > 0) ? 2000 : undefined,
|
|
314
320
|
)
|
|
315
321
|
|
|
322
|
+
// Listen for stream-end signal from the server — clears streaming state
|
|
323
|
+
// when a server-only run finishes without a local SSE stream driving the UI.
|
|
324
|
+
const handleStreamEnd = useCallback(() => {
|
|
325
|
+
if (!sessionId) return
|
|
326
|
+
const state = useChatStore.getState()
|
|
327
|
+
if (state.streamSource === 'server' && state.streamingSessionId === sessionId) {
|
|
328
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
329
|
+
void refreshMessages()
|
|
330
|
+
void refreshSession(sessionId)
|
|
331
|
+
}
|
|
332
|
+
}, [sessionId, refreshMessages, refreshSession])
|
|
333
|
+
useWs(sessionId ? `stream-end:${sessionId}` : '', handleStreamEnd)
|
|
334
|
+
|
|
316
335
|
// Keep the local typing indicator aligned with the server's active state
|
|
317
336
|
useEffect(() => {
|
|
318
337
|
if (!sessionId) return
|
|
319
338
|
const state = useChatStore.getState()
|
|
320
339
|
if (isServerActive) {
|
|
321
340
|
if (!state.streaming && !state.streamText) {
|
|
322
|
-
useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
|
|
341
|
+
useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamSource: 'server', streamText: '' })
|
|
323
342
|
}
|
|
324
343
|
return
|
|
325
344
|
}
|
|
@@ -330,7 +349,7 @@ export function ChatArea() {
|
|
|
330
349
|
) {
|
|
331
350
|
// Server finished — clear all streaming state and fetch final messages
|
|
332
351
|
fetchMessages(sessionId).then(setMessages).catch(() => {})
|
|
333
|
-
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '', displayText: '', streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
352
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamSource: null, streamText: '', displayText: '', streamPhase: 'thinking', streamToolName: '', thinkingText: '', thinkingStartTime: 0 })
|
|
334
353
|
}
|
|
335
354
|
}, [isServerActive, sessionId, setMessages])
|
|
336
355
|
|
|
@@ -606,11 +625,11 @@ export function ChatArea() {
|
|
|
606
625
|
onConfirm={handleDelete}
|
|
607
626
|
onCancel={() => setConfirmDelete(false)}
|
|
608
627
|
/>
|
|
609
|
-
{session.agentId &&
|
|
628
|
+
{session.agentId && currentAgent && (
|
|
610
629
|
<ConfirmDialog
|
|
611
630
|
open={confirmDeleteAgent}
|
|
612
631
|
title="Delete Agent"
|
|
613
|
-
message={`Delete agent "${
|
|
632
|
+
message={`Delete agent "${currentAgent.name}"? This cannot be undone.`}
|
|
614
633
|
confirmLabel="Delete"
|
|
615
634
|
danger
|
|
616
635
|
onConfirm={async () => {
|
|
@@ -9,6 +9,7 @@ import { toast } from 'sonner'
|
|
|
9
9
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
10
10
|
import type { Agent, ChatroomRoutingRule } from '@/types'
|
|
11
11
|
import { CheckIcon } from '@/components/shared/check-icon'
|
|
12
|
+
import { WORKER_ONLY_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
12
13
|
|
|
13
14
|
function genRuleId(): string {
|
|
14
15
|
return `rule-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
@@ -285,7 +286,7 @@ export function ChatroomSheet() {
|
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
const agentList = Object.values(agents).filter(
|
|
288
|
-
(a: Agent) => !a.trashedAt
|
|
289
|
+
(a: Agent) => !a.trashedAt && !WORKER_ONLY_PROVIDER_IDS.has(a.provider)
|
|
289
290
|
) as Agent[]
|
|
290
291
|
|
|
291
292
|
const memberAgents = agentList.filter((a) => selectedAgentIds.includes(a.id))
|