@swarmclawai/swarmclaw 0.5.3 → 0.6.2
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 +53 -9
- package/bin/server-cmd.js +1 -0
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +5 -2
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/canvas/[sessionId]/route.ts +31 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
- package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
- package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
- package/src/app/api/chatrooms/[id]/route.ts +84 -0
- package/src/app/api/chatrooms/route.ts +50 -0
- package/src/app/api/connectors/[id]/route.ts +1 -0
- package/src/app/api/connectors/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/files/open/route.ts +43 -0
- package/src/app/api/knowledge/[id]/route.ts +13 -2
- package/src/app/api/knowledge/route.ts +8 -1
- package/src/app/api/memory/route.ts +8 -0
- package/src/app/api/notifications/route.ts +4 -0
- package/src/app/api/orchestrator/run/route.ts +1 -1
- package/src/app/api/plugins/install/route.ts +2 -2
- package/src/app/api/search/route.ts +53 -1
- package/src/app/api/sessions/[id]/chat/route.ts +2 -0
- package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
- package/src/app/api/sessions/[id]/fork/route.ts +1 -1
- package/src/app/api/sessions/[id]/messages/route.ts +70 -2
- package/src/app/api/sessions/[id]/route.ts +4 -0
- package/src/app/api/sessions/route.ts +3 -3
- package/src/app/api/settings/route.ts +9 -0
- package/src/app/api/setup/check-provider/route.ts +3 -16
- package/src/app/api/skills/[id]/route.ts +6 -0
- package/src/app/api/skills/route.ts +6 -0
- package/src/app/api/tasks/[id]/route.ts +12 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/metrics/route.ts +101 -0
- package/src/app/api/tasks/route.ts +18 -2
- package/src/app/api/tts/route.ts +3 -2
- package/src/app/api/tts/stream/route.ts +3 -2
- package/src/app/api/uploads/[filename]/route.ts +19 -34
- package/src/app/api/uploads/route.ts +94 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +63 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +40 -1
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +50 -17
- package/src/components/agents/agent-chat-list.tsx +148 -12
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +120 -65
- package/src/components/agents/inspector-panel.tsx +81 -6
- package/src/components/agents/openclaw-skills-panel.tsx +32 -3
- package/src/components/agents/personality-builder.tsx +42 -14
- package/src/components/agents/soul-library-picker.tsx +89 -0
- package/src/components/auth/access-key-gate.tsx +10 -3
- package/src/components/auth/setup-wizard.tsx +2 -2
- package/src/components/auth/user-picker.tsx +31 -3
- package/src/components/canvas/canvas-panel.tsx +96 -0
- package/src/components/chat/activity-moment.tsx +173 -0
- package/src/components/chat/chat-area.tsx +46 -22
- package/src/components/chat/chat-header.tsx +457 -286
- package/src/components/chat/chat-preview-panel.tsx +1 -2
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.tsx +371 -0
- package/src/components/chat/file-path-chip.tsx +146 -0
- package/src/components/chat/heartbeat-history-panel.tsx +269 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +356 -315
- package/src/components/chat/message-list.tsx +230 -8
- package/src/components/chat/streaming-bubble.tsx +104 -47
- package/src/components/chat/suggestions-bar.tsx +1 -1
- package/src/components/chat/thinking-indicator.tsx +72 -10
- package/src/components/chat/tool-call-bubble.tsx +111 -73
- package/src/components/chat/tool-request-banner.tsx +31 -7
- package/src/components/chat/transfer-agent-picker.tsx +63 -0
- package/src/components/chatrooms/agent-hover-card.tsx +124 -0
- package/src/components/chatrooms/chatroom-input.tsx +320 -0
- package/src/components/chatrooms/chatroom-list.tsx +130 -0
- package/src/components/chatrooms/chatroom-message.tsx +432 -0
- package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
- package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
- package/src/components/chatrooms/chatroom-view.tsx +344 -0
- package/src/components/chatrooms/reaction-picker.tsx +273 -0
- package/src/components/connectors/connector-list.tsx +168 -90
- package/src/components/connectors/connector-sheet.tsx +95 -56
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +107 -43
- package/src/components/knowledge/knowledge-list.tsx +31 -1
- package/src/components/knowledge/knowledge-sheet.tsx +83 -2
- package/src/components/layout/app-layout.tsx +194 -97
- package/src/components/layout/update-banner.tsx +2 -2
- package/src/components/logs/log-list.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
- package/src/components/memory/memory-agent-list.tsx +143 -0
- package/src/components/memory/memory-browser.tsx +205 -0
- package/src/components/memory/memory-card.tsx +34 -7
- package/src/components/memory/memory-detail.tsx +359 -120
- package/src/components/memory/memory-sheet.tsx +157 -23
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/plugins/plugin-sheet.tsx +1 -1
- package/src/components/projects/project-detail.tsx +509 -0
- package/src/components/projects/project-list.tsx +195 -59
- package/src/components/providers/provider-list.tsx +2 -2
- package/src/components/providers/provider-sheet.tsx +3 -3
- package/src/components/schedules/schedule-card.tsx +1 -1
- package/src/components/schedules/schedule-list.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +259 -126
- package/src/components/secrets/secret-sheet.tsx +47 -24
- package/src/components/secrets/secrets-list.tsx +18 -8
- package/src/components/sessions/new-session-sheet.tsx +33 -65
- package/src/components/sessions/session-card.tsx +45 -14
- package/src/components/sessions/session-list.tsx +35 -18
- package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
- package/src/components/shared/agent-picker-list.tsx +90 -0
- package/src/components/shared/agent-switch-dialog.tsx +156 -0
- package/src/components/shared/attachment-chip.tsx +165 -0
- package/src/components/shared/avatar.tsx +10 -1
- package/src/components/shared/chatroom-picker-list.tsx +61 -0
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/connector-platform-icon.tsx +51 -4
- package/src/components/shared/empty-state.tsx +32 -0
- package/src/components/shared/file-preview.tsx +34 -0
- package/src/components/shared/form-styles.ts +2 -0
- package/src/components/shared/icon-button.tsx +16 -2
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
- package/src/components/shared/notification-center.tsx +44 -6
- package/src/components/shared/profile-sheet.tsx +115 -0
- package/src/components/shared/reply-quote.tsx +26 -0
- package/src/components/shared/search-dialog.tsx +31 -15
- package/src/components/shared/section-label.tsx +12 -0
- package/src/components/shared/settings/plugin-manager.tsx +1 -1
- package/src/components/shared/settings/section-embedding.tsx +48 -13
- package/src/components/shared/settings/section-orchestrator.tsx +46 -15
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-secrets.tsx +1 -1
- package/src/components/shared/settings/section-storage.tsx +206 -0
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +57 -0
- package/src/components/shared/settings/section-voice.tsx +42 -21
- package/src/components/shared/settings/section-web-search.tsx +30 -6
- package/src/components/shared/settings/settings-page.tsx +182 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/settings/storage-browser.tsx +259 -0
- package/src/components/shared/sheet-footer.tsx +33 -0
- package/src/components/skills/skill-list.tsx +61 -30
- package/src/components/skills/skill-sheet.tsx +81 -2
- package/src/components/tasks/task-board.tsx +448 -26
- package/src/components/tasks/task-card.tsx +59 -9
- package/src/components/tasks/task-column.tsx +62 -3
- package/src/components/tasks/task-list.tsx +12 -4
- package/src/components/tasks/task-sheet.tsx +416 -74
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/metrics-dashboard.tsx +90 -6
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-continuous-speech.ts +10 -4
- package/src/hooks/use-view-router.ts +69 -19
- package/src/hooks/use-voice-conversation.ts +53 -10
- package/src/hooks/use-ws.ts +4 -2
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/providers/anthropic.ts +13 -7
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/providers/openai.ts +13 -7
- package/src/lib/server/chat-execution.ts +75 -15
- package/src/lib/server/chatroom-helpers.ts +146 -0
- package/src/lib/server/connectors/manager.ts +229 -7
- package/src/lib/server/context-manager.ts +225 -13
- package/src/lib/server/create-notification.ts +14 -2
- package/src/lib/server/daemon-state.ts +157 -10
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +48 -6
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/memory-consolidation.ts +105 -0
- package/src/lib/server/memory-db.ts +183 -10
- package/src/lib/server/mime.ts +51 -0
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/orchestrator-lg.ts +2 -0
- package/src/lib/server/orchestrator.ts +5 -2
- package/src/lib/server/playwright-proxy.mjs +2 -3
- package/src/lib/server/prompt-runtime-context.ts +53 -0
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +56 -10
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/canvas.ts +67 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/connector.ts +83 -9
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/crud.ts +21 -0
- package/src/lib/server/session-tools/delegate.ts +68 -4
- package/src/lib/server/session-tools/git.ts +71 -0
- package/src/lib/server/session-tools/http.ts +57 -0
- package/src/lib/server/session-tools/index.ts +10 -0
- package/src/lib/server/session-tools/memory.ts +7 -1
- package/src/lib/server/session-tools/search-providers.ts +16 -8
- package/src/lib/server/session-tools/subagent.ts +106 -0
- package/src/lib/server/session-tools/web.ts +115 -4
- package/src/lib/server/storage.ts +53 -29
- package/src/lib/server/stream-agent-chat.ts +185 -57
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/task-mention.ts +41 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/sessions.ts +10 -0
- package/src/lib/soul-library.ts +103 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/task-dedupe.ts +26 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/tool-definitions.ts +2 -0
- package/src/lib/tts.ts +2 -2
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/stores/use-app-store.ts +41 -3
- package/src/stores/use-chat-store.ts +113 -5
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +88 -4
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
-
import {
|
|
3
|
+
import { HumanMessage } from '@langchain/core/messages'
|
|
4
|
+
import { loadSessions, saveSessions, loadCredentials, decryptKey } from '../storage'
|
|
5
|
+
import { buildChatModel } from '../build-llm'
|
|
6
|
+
import { getProvider } from '@/lib/providers'
|
|
4
7
|
import type { ToolBuildContext } from './context'
|
|
5
8
|
|
|
6
9
|
export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
@@ -19,8 +22,8 @@ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
19
22
|
const systemPromptTokens = 2000
|
|
20
23
|
const status = getContextStatus(messages, systemPromptTokens, session.provider, session.model)
|
|
21
24
|
return JSON.stringify(status)
|
|
22
|
-
} catch (err:
|
|
23
|
-
return `Error: ${err.message
|
|
25
|
+
} catch (err: unknown) {
|
|
26
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24
27
|
}
|
|
25
28
|
},
|
|
26
29
|
{
|
|
@@ -46,20 +49,33 @@ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
46
49
|
return JSON.stringify({ status: 'no_action', reason: 'Not enough messages to compact', messageCount: messages.length })
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
// Resolve API key for the session's provider
|
|
53
|
+
let apiKey: string | null = null
|
|
54
|
+
const providerInfo = getProvider(session.provider)
|
|
55
|
+
if ((providerInfo?.requiresApiKey || providerInfo?.optionalApiKey) && session.credentialId) {
|
|
56
|
+
try {
|
|
57
|
+
const creds = loadCredentials()
|
|
58
|
+
const cred = creds[session.credentialId]
|
|
59
|
+
if (cred) apiKey = decryptKey(cred.encryptedKey)
|
|
60
|
+
} catch { /* continue without key */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build LLM summarizer using the session's provider/model
|
|
64
|
+
const generateSummary = async (prompt: string): Promise<string> => {
|
|
65
|
+
const llm = buildChatModel({
|
|
66
|
+
provider: session.provider,
|
|
67
|
+
model: session.model,
|
|
68
|
+
apiKey,
|
|
69
|
+
apiEndpoint: session.apiEndpoint,
|
|
70
|
+
})
|
|
71
|
+
const response = await llm.invoke([new HumanMessage(prompt)])
|
|
72
|
+
if (typeof response.content === 'string') return response.content
|
|
73
|
+
if (Array.isArray(response.content)) {
|
|
74
|
+
return response.content
|
|
75
|
+
.map((b: Record<string, unknown>) => (typeof b.text === 'string' ? b.text : ''))
|
|
76
|
+
.join('')
|
|
61
77
|
}
|
|
62
|
-
return
|
|
78
|
+
return ''
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
const result = await summarizeAndCompact({
|
|
@@ -67,6 +83,8 @@ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
67
83
|
keepLastN: keep,
|
|
68
84
|
agentId: ctx?.agentId || session.agentId || null,
|
|
69
85
|
sessionId: ctx.sessionId,
|
|
86
|
+
provider: session.provider,
|
|
87
|
+
model: session.model,
|
|
70
88
|
generateSummary,
|
|
71
89
|
})
|
|
72
90
|
|
|
@@ -84,8 +102,8 @@ export function buildContextTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
84
102
|
summaryAdded: result.summaryAdded,
|
|
85
103
|
remainingMessages: result.messages.length,
|
|
86
104
|
})
|
|
87
|
-
} catch (err:
|
|
88
|
-
return `Error: ${err.message
|
|
105
|
+
} catch (err: unknown) {
|
|
106
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
89
107
|
}
|
|
90
108
|
},
|
|
91
109
|
{
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
} from '../storage'
|
|
21
21
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
22
22
|
import { findDuplicateSchedule, type ScheduleLike } from '@/lib/schedule-dedupe'
|
|
23
|
+
import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
|
|
24
|
+
import { resolveTaskAgentFromDescription } from '@/lib/server/task-mention'
|
|
23
25
|
import type { ToolBuildContext } from './context'
|
|
24
26
|
import { safePath, findBinaryOnPath } from './context'
|
|
25
27
|
|
|
@@ -115,6 +117,7 @@ const RESOURCE_DEFAULTS: Record<string, (parsed: any) => any> = {
|
|
|
115
117
|
queuedAt: null,
|
|
116
118
|
startedAt: null,
|
|
117
119
|
completedAt: null,
|
|
120
|
+
priority: ['low', 'medium', 'high', 'critical'].includes(p.priority) ? p.priority : undefined,
|
|
118
121
|
...p,
|
|
119
122
|
}),
|
|
120
123
|
manage_schedules: (p) => {
|
|
@@ -342,6 +345,24 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
|
|
|
342
345
|
})
|
|
343
346
|
}
|
|
344
347
|
}
|
|
348
|
+
// @mention agent resolution for tasks
|
|
349
|
+
if (toolKey === 'manage_tasks' && parsed.description) {
|
|
350
|
+
const agents = loadAgents()
|
|
351
|
+
parsed.agentId = resolveTaskAgentFromDescription(
|
|
352
|
+
parsed.description,
|
|
353
|
+
parsed.agentId || ctx?.agentId || '',
|
|
354
|
+
agents,
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
// Task dedup
|
|
358
|
+
if (toolKey === 'manage_tasks') {
|
|
359
|
+
const fp = computeTaskFingerprint(parsed.title || 'Untitled Task', parsed.agentId || ctx?.agentId || '')
|
|
360
|
+
parsed.fingerprint = fp
|
|
361
|
+
const dupe = findDuplicateTask(all as Record<string, import('@/types').BoardTask>, { fingerprint: fp })
|
|
362
|
+
if (dupe) {
|
|
363
|
+
return JSON.stringify({ ...dupe, deduplicated: true })
|
|
364
|
+
}
|
|
365
|
+
}
|
|
345
366
|
const newId = genId()
|
|
346
367
|
const entry = {
|
|
347
368
|
id: newId,
|
|
@@ -634,6 +634,69 @@ export function buildDelegateTools(bctx: ToolBuildContext): StructuredToolInterf
|
|
|
634
634
|
}
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
+
// check_delegation_status: lets agents check on tasks they delegated
|
|
638
|
+
if (ctx?.platformAssignScope === 'all' && ctx?.agentId) {
|
|
639
|
+
tools.push(
|
|
640
|
+
tool(
|
|
641
|
+
async ({ taskId }) => {
|
|
642
|
+
try {
|
|
643
|
+
const tasks = loadTasks()
|
|
644
|
+
const task = tasks[taskId] as Record<string, unknown> | undefined
|
|
645
|
+
if (!task) return `Error: Task "${taskId}" not found.`
|
|
646
|
+
|
|
647
|
+
const status = task.status as string || 'unknown'
|
|
648
|
+
const result = typeof task.result === 'string' ? task.result : null
|
|
649
|
+
const error = typeof task.error === 'string' ? task.error : null
|
|
650
|
+
const agentId = task.agentId as string || ''
|
|
651
|
+
const agents = loadAgents()
|
|
652
|
+
const agent = agents[agentId]
|
|
653
|
+
const startedAt = typeof task.startedAt === 'number' ? task.startedAt : null
|
|
654
|
+
const completedAt = typeof task.completedAt === 'number' ? task.completedAt : null
|
|
655
|
+
|
|
656
|
+
const info: Record<string, unknown> = {
|
|
657
|
+
taskId,
|
|
658
|
+
status,
|
|
659
|
+
agentId,
|
|
660
|
+
agentName: agent?.name || agentId,
|
|
661
|
+
agentAvatarSeed: agent?.avatarSeed || null,
|
|
662
|
+
title: task.title || '',
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (startedAt) info.startedAt = new Date(startedAt).toISOString()
|
|
666
|
+
if (completedAt) info.completedAt = new Date(completedAt).toISOString()
|
|
667
|
+
if (startedAt && !completedAt && status === 'running') {
|
|
668
|
+
info.runningForSeconds = Math.round((Date.now() - startedAt) / 1000)
|
|
669
|
+
}
|
|
670
|
+
if (result) info.result = result.slice(0, 4000)
|
|
671
|
+
if (error) info.error = error.slice(0, 1000)
|
|
672
|
+
|
|
673
|
+
// Include latest comments for context
|
|
674
|
+
const comments = Array.isArray(task.comments) ? task.comments as Array<{ text: string; author: string; createdAt: number }> : []
|
|
675
|
+
if (comments.length > 0) {
|
|
676
|
+
const latest = comments.slice(-3).map((c) => ({
|
|
677
|
+
author: c.author,
|
|
678
|
+
text: (c.text || '').slice(0, 500),
|
|
679
|
+
time: new Date(c.createdAt).toISOString(),
|
|
680
|
+
}))
|
|
681
|
+
info.latestComments = latest
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return JSON.stringify(info)
|
|
685
|
+
} catch (err: unknown) {
|
|
686
|
+
return `Error checking task: ${err instanceof Error ? err.message : String(err)}`
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: 'check_delegation_status',
|
|
691
|
+
description: 'Check the status and result of a delegated task. Use this after delegate_to_agent to monitor progress. Returns status (todo/queued/running/completed/failed), result if completed, and latest comments.',
|
|
692
|
+
schema: z.object({
|
|
693
|
+
taskId: z.string().describe('The task ID returned by delegate_to_agent'),
|
|
694
|
+
}),
|
|
695
|
+
},
|
|
696
|
+
),
|
|
697
|
+
)
|
|
698
|
+
}
|
|
699
|
+
|
|
637
700
|
// delegate_to_agent: requires "Assign to Other Agents" (platformAssignScope: 'all')
|
|
638
701
|
if (ctx?.platformAssignScope === 'all' && ctx?.agentId) {
|
|
639
702
|
tools.push(
|
|
@@ -698,9 +761,10 @@ export function buildDelegateTools(bctx: ToolBuildContext): StructuredToolInterf
|
|
|
698
761
|
taskId,
|
|
699
762
|
agentId: resolvedId,
|
|
700
763
|
agentName: target.name,
|
|
764
|
+
agentAvatarSeed: target.avatarSeed || null,
|
|
701
765
|
message: startImmediately
|
|
702
|
-
? `Task delegated to ${target.name} and queued for immediate execution. Task ID: ${taskId}.`
|
|
703
|
-
: `Task delegated to ${target.name}. Task ID: ${taskId}. Status: todo
|
|
766
|
+
? `Task delegated to ${target.name} and queued for immediate execution. Task ID: ${taskId}. Use check_delegation_status to monitor progress.`
|
|
767
|
+
: `Task delegated to ${target.name}. Task ID: ${taskId}. Status: todo (not auto-started). Use delegate_to_agent with startImmediately: true to queue it.`,
|
|
704
768
|
})
|
|
705
769
|
} catch (err: unknown) {
|
|
706
770
|
return `Error delegating task: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -708,12 +772,12 @@ export function buildDelegateTools(bctx: ToolBuildContext): StructuredToolInterf
|
|
|
708
772
|
},
|
|
709
773
|
{
|
|
710
774
|
name: 'delegate_to_agent',
|
|
711
|
-
description: 'Delegate a task to another agent. Creates a task on the task board
|
|
775
|
+
description: 'Delegate a task to another agent. Creates a task on the task board and queues it for immediate execution by default. Set startImmediately=false if you want the task to go to "todo" status instead.',
|
|
712
776
|
schema: z.object({
|
|
713
777
|
agentId: z.string().describe('ID or name of the target agent to delegate to'),
|
|
714
778
|
task: z.string().describe('What the target agent should do'),
|
|
715
779
|
description: z.string().optional().describe('Optional longer description of the task'),
|
|
716
|
-
startImmediately: z.boolean().optional().default(
|
|
780
|
+
startImmediately: z.boolean().optional().default(true).describe('If true (default), queue the task for immediate execution. Set false to put in todo for manual start.'),
|
|
717
781
|
}),
|
|
718
782
|
},
|
|
719
783
|
),
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import { execFile } from 'child_process'
|
|
4
|
+
import { promisify } from 'util'
|
|
5
|
+
import type { ToolBuildContext } from './context'
|
|
6
|
+
import { findBinaryOnPath, safePath, truncate, MAX_OUTPUT } from './context'
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile)
|
|
9
|
+
|
|
10
|
+
const GIT_ACTIONS = [
|
|
11
|
+
'status', 'log', 'diff', 'commit', 'add', 'push', 'pull',
|
|
12
|
+
'branch', 'checkout', 'stash', 'merge', 'clone', 'remote',
|
|
13
|
+
'tag', 'reset', 'show',
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
export function buildGitTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
17
|
+
if (!bctx.hasTool('git')) return []
|
|
18
|
+
|
|
19
|
+
const gitPath = findBinaryOnPath('git')
|
|
20
|
+
if (!gitPath) return []
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
tool(
|
|
24
|
+
async ({ action, args, repoPath, timeoutSec }) => {
|
|
25
|
+
try {
|
|
26
|
+
const cwd = repoPath ? safePath(bctx.cwd, repoPath) : bctx.cwd
|
|
27
|
+
const timeout = Math.max(5, Math.min(timeoutSec ?? 60, 300)) * 1000
|
|
28
|
+
|
|
29
|
+
// Verify we're in a git repo (except for clone)
|
|
30
|
+
if (action !== 'clone') {
|
|
31
|
+
try {
|
|
32
|
+
await execFileAsync(gitPath, ['rev-parse', '--is-inside-work-tree'], { cwd, timeout: 5000 })
|
|
33
|
+
} catch {
|
|
34
|
+
return JSON.stringify({ error: `Not a git repository: ${cwd}` })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cmdArgs = [action, ...(args ?? [])]
|
|
39
|
+
const result = await execFileAsync(gitPath, cmdArgs, {
|
|
40
|
+
cwd,
|
|
41
|
+
timeout,
|
|
42
|
+
maxBuffer: MAX_OUTPUT,
|
|
43
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
44
|
+
})
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
exitCode: 0,
|
|
47
|
+
stdout: truncate(result.stdout ?? '', MAX_OUTPUT),
|
|
48
|
+
stderr: truncate(result.stderr ?? '', MAX_OUTPUT),
|
|
49
|
+
})
|
|
50
|
+
} catch (err: unknown) {
|
|
51
|
+
const execErr = err as { code?: number; stdout?: string; stderr?: string; message?: string }
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
exitCode: execErr.code ?? 1,
|
|
54
|
+
stdout: truncate(execErr.stdout ?? '', MAX_OUTPUT),
|
|
55
|
+
stderr: truncate(execErr.stderr ?? execErr.message ?? String(err), MAX_OUTPUT),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'git',
|
|
61
|
+
description: 'Run git operations. Verify the repo exists before committing or pushing. Use args for subcommand flags (e.g. args: ["-m", "message"] for commit).',
|
|
62
|
+
schema: z.object({
|
|
63
|
+
action: z.enum(GIT_ACTIONS).describe('Git subcommand to run'),
|
|
64
|
+
args: z.array(z.string()).optional().describe('Additional arguments (e.g. ["-m", "fix: typo"], ["--oneline", "-n", "5"])'),
|
|
65
|
+
repoPath: z.string().optional().describe('Relative path to git repo (defaults to working directory)'),
|
|
66
|
+
timeoutSec: z.number().optional().describe('Timeout in seconds (default 60, max 300)'),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import type { ToolBuildContext } from './context'
|
|
4
|
+
import { truncate, MAX_OUTPUT } from './context'
|
|
5
|
+
|
|
6
|
+
export function buildHttpTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
7
|
+
if (!bctx.hasTool('http_request')) return []
|
|
8
|
+
|
|
9
|
+
return [
|
|
10
|
+
tool(
|
|
11
|
+
async ({ method, url, headers, body, timeoutSec, followRedirects }) => {
|
|
12
|
+
try {
|
|
13
|
+
const timeout = Math.max(1, Math.min(timeoutSec ?? 30, 120)) * 1000
|
|
14
|
+
const init: RequestInit = {
|
|
15
|
+
method,
|
|
16
|
+
headers: (headers ?? undefined) as Record<string, string> | undefined,
|
|
17
|
+
signal: AbortSignal.timeout(timeout),
|
|
18
|
+
}
|
|
19
|
+
if (body && method !== 'GET' && method !== 'HEAD') {
|
|
20
|
+
init.body = body
|
|
21
|
+
}
|
|
22
|
+
if (followRedirects === false) {
|
|
23
|
+
init.redirect = 'manual'
|
|
24
|
+
}
|
|
25
|
+
const res = await fetch(url, init)
|
|
26
|
+
const resHeaders: Record<string, string> = {}
|
|
27
|
+
for (const key of ['content-type', 'location', 'x-request-id', 'retry-after', 'content-length']) {
|
|
28
|
+
const val = res.headers.get(key)
|
|
29
|
+
if (val) resHeaders[key] = val
|
|
30
|
+
}
|
|
31
|
+
let resBody: string
|
|
32
|
+
const ct = res.headers.get('content-type') ?? ''
|
|
33
|
+
if (ct.includes('image/') || ct.includes('audio/') || ct.includes('video/') || ct.includes('application/octet-stream')) {
|
|
34
|
+
resBody = `[binary content, ${res.headers.get('content-length') ?? 'unknown'} bytes]`
|
|
35
|
+
} else {
|
|
36
|
+
resBody = truncate(await res.text(), MAX_OUTPUT)
|
|
37
|
+
}
|
|
38
|
+
return JSON.stringify({ status: res.status, statusText: res.statusText, headers: resHeaders, body: resBody })
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'http_request',
|
|
45
|
+
description: 'Make an HTTP API request. Supports all methods, custom headers, and request bodies. Returns status, headers, and body.',
|
|
46
|
+
schema: z.object({
|
|
47
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).describe('HTTP method'),
|
|
48
|
+
url: z.string().describe('Full URL to request'),
|
|
49
|
+
headers: z.record(z.string(), z.string()).optional().describe('Request headers as key-value pairs'),
|
|
50
|
+
body: z.string().optional().describe('Request body (JSON string, form data, or plain text). Ignored for GET/HEAD.'),
|
|
51
|
+
timeoutSec: z.number().optional().describe('Timeout in seconds (default 30, max 120)'),
|
|
52
|
+
followRedirects: z.boolean().optional().describe('Follow redirects (default true). Set false to inspect redirect responses.'),
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -16,6 +16,11 @@ import { buildConnectorTools } from './connector'
|
|
|
16
16
|
import { buildContextTools } from './context-mgmt'
|
|
17
17
|
import { buildSandboxTools } from './sandbox'
|
|
18
18
|
import { buildOpenClawNodeTools } from './openclaw-nodes'
|
|
19
|
+
import { buildChatroomTools } from './chatroom'
|
|
20
|
+
import { buildSubagentTools } from './subagent'
|
|
21
|
+
import { buildCanvasTools } from './canvas'
|
|
22
|
+
import { buildHttpTools } from './http'
|
|
23
|
+
import { buildGitTools } from './git'
|
|
19
24
|
|
|
20
25
|
export type { ToolContext, SessionToolsResult }
|
|
21
26
|
export { sweepOrphanedBrowsers, cleanupSessionBrowser, getActiveBrowserCount, hasActiveBrowser }
|
|
@@ -97,6 +102,11 @@ export async function buildSessionTools(cwd: string, enabledTools: string[], ctx
|
|
|
97
102
|
...buildContextTools(bctx),
|
|
98
103
|
...buildSandboxTools(bctx),
|
|
99
104
|
...buildOpenClawNodeTools(bctx),
|
|
105
|
+
...buildChatroomTools(bctx),
|
|
106
|
+
...buildSubagentTools(bctx),
|
|
107
|
+
...buildCanvasTools(bctx),
|
|
108
|
+
...buildHttpTools(bctx),
|
|
109
|
+
...buildGitTools(bctx),
|
|
100
110
|
)
|
|
101
111
|
|
|
102
112
|
// ---------------------------------------------------------------------------
|
|
@@ -15,7 +15,8 @@ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterfac
|
|
|
15
15
|
|
|
16
16
|
tools.push(
|
|
17
17
|
tool(
|
|
18
|
-
async (
|
|
18
|
+
async (input) => {
|
|
19
|
+
const { action, key, value, category, query, scope, filePaths, references, project, imagePath, linkedMemoryIds, depth, linkedLimit, targetIds, tags, pinned, sharedWith } = input as Record<string, any>
|
|
19
20
|
try {
|
|
20
21
|
const scopeMode = scope || 'auto'
|
|
21
22
|
const currentAgentId = ctx?.agentId || null
|
|
@@ -59,6 +60,7 @@ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterfac
|
|
|
59
60
|
|
|
60
61
|
const formatEntry = (m: any) => {
|
|
61
62
|
let line = `[${m.id}] (${m.agentId ? `agent:${m.agentId}` : 'shared'}) ${m.category}/${m.title}: ${m.content}`
|
|
63
|
+
if (m.reinforcementCount) line += ` (reinforced ×${m.reinforcementCount})`
|
|
62
64
|
if (m.references?.length) {
|
|
63
65
|
line += `\n refs: ${m.references.map((r: any) => {
|
|
64
66
|
const core = r.path || r.title || r.type
|
|
@@ -98,6 +100,8 @@ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterfac
|
|
|
98
100
|
image: storedImage,
|
|
99
101
|
imagePath: storedImage?.path || undefined,
|
|
100
102
|
linkedMemoryIds,
|
|
103
|
+
pinned: pinned === true,
|
|
104
|
+
sharedWith: Array.isArray(sharedWith) ? sharedWith : undefined,
|
|
101
105
|
})
|
|
102
106
|
const memoryScope = entry.agentId ? 'agent' : 'shared'
|
|
103
107
|
let result = `Stored ${memoryScope} memory "${key}" (id: ${entry.id})`
|
|
@@ -220,6 +224,8 @@ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterfac
|
|
|
220
224
|
linkedLimit: z.number().optional().describe('Max linked memories expanded during traversal. Respects configured server cap.'),
|
|
221
225
|
targetIds: z.array(z.string()).optional().describe('Memory IDs to link/unlink (for link/unlink actions)'),
|
|
222
226
|
tags: z.array(z.string()).optional().describe('Tags for categorizing knowledge entries'),
|
|
227
|
+
pinned: z.boolean().optional().describe('Mark memory as pinned (always preloaded in agent context). For store action.'),
|
|
228
|
+
sharedWith: z.array(z.string()).optional().describe('Agent IDs to share this memory with (for store action). They can read it in their context.'),
|
|
223
229
|
}),
|
|
224
230
|
},
|
|
225
231
|
),
|
|
@@ -260,16 +260,24 @@ export async function getSearchProvider(settings: Partial<AppSettings>): Promise
|
|
|
260
260
|
return new SearXNGProvider(url)
|
|
261
261
|
}
|
|
262
262
|
case 'tavily': {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
let apiKey = settings.tavilyApiKey
|
|
264
|
+
if (!apiKey) {
|
|
265
|
+
const { getSecret } = await import('../storage')
|
|
266
|
+
const secret = await getSecret('tavily')
|
|
267
|
+
apiKey = secret?.value ?? null
|
|
268
|
+
}
|
|
269
|
+
if (!apiKey) throw new Error('Tavily requires an API key. Set one in Settings > Web Search.')
|
|
270
|
+
return new TavilyProvider(apiKey)
|
|
267
271
|
}
|
|
268
272
|
case 'brave': {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
+
let apiKey = settings.braveApiKey
|
|
274
|
+
if (!apiKey) {
|
|
275
|
+
const { getSecret } = await import('../storage')
|
|
276
|
+
const secret = await getSecret('brave')
|
|
277
|
+
apiKey = secret?.value ?? null
|
|
278
|
+
}
|
|
279
|
+
if (!apiKey) throw new Error('Brave Search requires an API key. Set one in Settings > Web Search.')
|
|
280
|
+
return new BraveProvider(apiKey)
|
|
273
281
|
}
|
|
274
282
|
default:
|
|
275
283
|
return new DuckDuckGoProvider()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import { genId } from '@/lib/id'
|
|
4
|
+
import { loadAgents, loadSessions, saveSessions } from '../storage'
|
|
5
|
+
import { executeSessionChatTurn } from '../chat-execution'
|
|
6
|
+
import { log } from '../logger'
|
|
7
|
+
import type { ToolBuildContext } from './context'
|
|
8
|
+
|
|
9
|
+
const MAX_RECURSION_DEPTH = 3
|
|
10
|
+
|
|
11
|
+
function getSessionDepth(sessionId: string | undefined): number {
|
|
12
|
+
if (!sessionId) return 0
|
|
13
|
+
const sessions = loadSessions()
|
|
14
|
+
let depth = 0
|
|
15
|
+
let current = sessionId
|
|
16
|
+
while (current && depth < MAX_RECURSION_DEPTH + 1) {
|
|
17
|
+
const session = sessions[current]
|
|
18
|
+
if (!session?.parentSessionId) break
|
|
19
|
+
current = session.parentSessionId
|
|
20
|
+
depth++
|
|
21
|
+
}
|
|
22
|
+
return depth
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildSubagentTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
26
|
+
const { ctx, hasTool } = bctx
|
|
27
|
+
if (!hasTool('spawn_subagent')) return []
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
tool(
|
|
31
|
+
async ({ agentId, message, cwd }) => {
|
|
32
|
+
try {
|
|
33
|
+
// Validate agent exists
|
|
34
|
+
const agents = loadAgents()
|
|
35
|
+
const agent = agents[agentId]
|
|
36
|
+
if (!agent) return `Error: Agent "${agentId}" not found. Available agents: ${Object.values(agents).map((a) => `"${a.id}" (${a.name})`).join(', ')}`
|
|
37
|
+
|
|
38
|
+
// Check recursion depth
|
|
39
|
+
const depth = getSessionDepth(ctx?.sessionId ?? undefined)
|
|
40
|
+
if (depth >= MAX_RECURSION_DEPTH) {
|
|
41
|
+
return `Error: Maximum subagent recursion depth (${MAX_RECURSION_DEPTH}) reached. Cannot spawn further subagents.`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create ephemeral session
|
|
45
|
+
const sessionId = genId()
|
|
46
|
+
const now = Date.now()
|
|
47
|
+
const sessions = loadSessions()
|
|
48
|
+
sessions[sessionId] = {
|
|
49
|
+
id: sessionId,
|
|
50
|
+
name: `subagent-${agent.name}-${sessionId.slice(0, 6)}`,
|
|
51
|
+
cwd: cwd || bctx.cwd,
|
|
52
|
+
user: 'agent',
|
|
53
|
+
provider: agent.provider,
|
|
54
|
+
model: agent.model,
|
|
55
|
+
credentialId: agent.credentialId || null,
|
|
56
|
+
fallbackCredentialIds: agent.fallbackCredentialIds || [],
|
|
57
|
+
apiEndpoint: agent.apiEndpoint || null,
|
|
58
|
+
claudeSessionId: null,
|
|
59
|
+
messages: [],
|
|
60
|
+
createdAt: now,
|
|
61
|
+
lastActiveAt: now,
|
|
62
|
+
sessionType: 'orchestrated',
|
|
63
|
+
agentId: agent.id,
|
|
64
|
+
parentSessionId: ctx?.sessionId || null,
|
|
65
|
+
tools: agent.tools || [],
|
|
66
|
+
}
|
|
67
|
+
saveSessions(sessions)
|
|
68
|
+
|
|
69
|
+
log.info('subagent', `Spawning subagent "${agent.name}" (depth=${depth + 1})`, {
|
|
70
|
+
parentSessionId: ctx?.sessionId,
|
|
71
|
+
childSessionId: sessionId,
|
|
72
|
+
agentId,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Execute the chat turn
|
|
76
|
+
const result = await executeSessionChatTurn({
|
|
77
|
+
sessionId,
|
|
78
|
+
message,
|
|
79
|
+
internal: true,
|
|
80
|
+
source: 'subagent',
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return JSON.stringify({
|
|
84
|
+
agentId,
|
|
85
|
+
agentName: agent.name,
|
|
86
|
+
sessionId,
|
|
87
|
+
response: result.text.slice(0, 8000),
|
|
88
|
+
toolEvents: result.toolEvents?.length || 0,
|
|
89
|
+
error: result.error || null,
|
|
90
|
+
})
|
|
91
|
+
} catch (err: unknown) {
|
|
92
|
+
return `Error spawning subagent: ${err instanceof Error ? err.message : String(err)}`
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'spawn_subagent',
|
|
97
|
+
description: `Delegate a task to another agent. The subagent runs independently and returns its response. Use this to leverage specialized agents for subtasks. Max recursion depth: ${MAX_RECURSION_DEPTH}.`,
|
|
98
|
+
schema: z.object({
|
|
99
|
+
agentId: z.string().describe('ID of the agent to delegate to'),
|
|
100
|
+
message: z.string().describe('The message/task to send to the subagent'),
|
|
101
|
+
cwd: z.string().optional().describe('Optional working directory for the subagent (defaults to current)'),
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
),
|
|
105
|
+
]
|
|
106
|
+
}
|