@swarmclawai/swarmclaw 0.7.1 → 0.7.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 +85 -139
- package/package.json +1 -1
- package/src/app/api/agents/[id]/thread/route.ts +1 -2
- package/src/app/api/agents/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/main-loop/route.ts +2 -2
- package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
- package/src/app/api/{sessions → chats}/[id]/route.ts +4 -52
- package/src/app/api/{sessions → chats}/route.ts +5 -7
- package/src/app/api/plugins/route.ts +3 -0
- package/src/app/api/plugins/settings/route.ts +35 -0
- package/src/app/api/usage/route.ts +30 -0
- package/src/cli/index.js +35 -33
- package/src/cli/index.ts +40 -39
- package/src/cli/spec.js +29 -27
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-chat-list.tsx +3 -3
- package/src/components/agents/agent-list.tsx +8 -13
- package/src/components/agents/agent-sheet.tsx +2 -2
- package/src/components/agents/cron-job-form.tsx +3 -3
- package/src/components/agents/inspector-panel.tsx +2 -2
- package/src/components/auth/setup-wizard.tsx +5 -38
- package/src/components/chat/chat-area.tsx +10 -14
- package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +3 -3
- package/src/components/chat/chat-header.tsx +156 -73
- package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +4 -5
- package/src/components/chat/chat-tool-toggles.tsx +26 -17
- package/src/components/chat/checkpoint-timeline.tsx +4 -4
- package/src/components/chat/message-bubble.tsx +4 -1
- package/src/components/chat/message-list.tsx +2 -2
- package/src/components/{sessions/new-session-sheet.tsx → chat/new-chat-sheet.tsx} +6 -6
- package/src/components/chat/session-debug-panel.tsx +1 -1
- package/src/components/chat/tool-request-banner.tsx +3 -3
- package/src/components/chatrooms/agent-hover-card.tsx +3 -3
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
- package/src/components/connectors/connector-sheet.tsx +1 -1
- package/src/components/home/home-view.tsx +1 -1
- package/src/components/layout/app-layout.tsx +23 -2
- package/src/components/plugins/plugin-list.tsx +475 -254
- package/src/components/plugins/plugin-sheet.tsx +124 -10
- package/src/components/settings/gateway-connection-panel.tsx +1 -1
- package/src/components/shared/command-palette.tsx +0 -1
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/settings-page.tsx +1 -12
- package/src/components/usage/metrics-dashboard.tsx +73 -0
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/lib/chat.ts +1 -1
- package/src/lib/{sessions.ts → chats.ts} +28 -18
- package/src/lib/providers/claude-cli.ts +1 -1
- package/src/lib/server/approvals.ts +4 -4
- package/src/lib/server/capability-router.ts +10 -8
- package/src/lib/server/chat-execution.ts +36 -105
- package/src/lib/server/chatroom-helpers.ts +3 -3
- package/src/lib/server/connectors/manager.ts +4 -4
- package/src/lib/server/cost.ts +34 -1
- package/src/lib/server/daemon-state.ts +2 -2
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/main-agent-loop.ts +25 -160
- package/src/lib/server/main-session.ts +6 -13
- package/src/lib/server/orchestrator-lg.ts +3 -3
- package/src/lib/server/orchestrator.ts +5 -5
- package/src/lib/server/plugins.ts +112 -4
- package/src/lib/server/provider-health.ts +5 -3
- package/src/lib/server/queue.ts +12 -10
- package/src/lib/server/session-run-manager.test.ts +9 -6
- package/src/lib/server/session-run-manager.ts +1 -3
- package/src/lib/server/session-tools/calendar.ts +376 -0
- package/src/lib/server/session-tools/canvas.ts +1 -1
- package/src/lib/server/session-tools/chatroom.ts +4 -2
- package/src/lib/server/session-tools/connector.ts +5 -2
- package/src/lib/server/session-tools/context.ts +7 -3
- package/src/lib/server/session-tools/crud.ts +14 -6
- package/src/lib/server/session-tools/delegate.ts +95 -8
- package/src/lib/server/session-tools/discovery.ts +2 -2
- package/src/lib/server/session-tools/edit_file.ts +4 -2
- package/src/lib/server/session-tools/email.ts +322 -0
- package/src/lib/server/session-tools/file.ts +5 -2
- package/src/lib/server/session-tools/git.ts +1 -1
- package/src/lib/server/session-tools/http.ts +1 -1
- package/src/lib/server/session-tools/image-gen.ts +382 -0
- package/src/lib/server/session-tools/index.ts +74 -49
- package/src/lib/server/session-tools/memory.ts +139 -2
- package/src/lib/server/session-tools/monitor.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
- package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
- package/src/lib/server/session-tools/platform.ts +6 -3
- package/src/lib/server/session-tools/plugin-creator.ts +3 -3
- package/src/lib/server/session-tools/replicate.ts +303 -0
- package/src/lib/server/session-tools/sample-ui.ts +1 -1
- package/src/lib/server/session-tools/sandbox.ts +4 -2
- package/src/lib/server/session-tools/schedule.ts +4 -2
- package/src/lib/server/session-tools/session-info.ts +7 -4
- package/src/lib/server/session-tools/shell.ts +5 -2
- package/src/lib/server/session-tools/subagent.ts +2 -2
- package/src/lib/server/session-tools/wallet.ts +29 -2
- package/src/lib/server/session-tools/web.ts +44 -5
- package/src/lib/server/storage.ts +29 -9
- package/src/lib/server/stream-agent-chat.ts +72 -249
- package/src/lib/server/tool-aliases.ts +26 -15
- package/src/lib/server/tool-capability-policy.test.ts +9 -9
- package/src/lib/server/tool-capability-policy.ts +32 -27
- package/src/lib/tool-definitions.ts +4 -0
- package/src/lib/validation/schemas.ts +3 -1
- package/src/stores/use-app-store.ts +5 -5
- package/src/stores/use-chat-store.ts +7 -7
- package/src/types/index.ts +65 -3
- /package/src/app/api/{sessions → chats}/[id]/browser/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/chat/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/messages/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/[id]/stop/route.ts +0 -0
- /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
|
@@ -127,7 +127,144 @@ async function executeMemoryAction(input: any, ctx: any) {
|
|
|
127
127
|
const MemoryPlugin: Plugin = {
|
|
128
128
|
name: 'Core Memory',
|
|
129
129
|
description: 'Advanced database-backed long-term memory with semantic search and graph linking.',
|
|
130
|
-
hooks: {
|
|
130
|
+
hooks: {
|
|
131
|
+
getAgentContext: async (ctx) => {
|
|
132
|
+
const agentId = ctx.session.agentId
|
|
133
|
+
if (!agentId) return null
|
|
134
|
+
|
|
135
|
+
const memDb = getMemoryDb()
|
|
136
|
+
const memoryQuerySeed = [
|
|
137
|
+
ctx.message,
|
|
138
|
+
...ctx.history
|
|
139
|
+
.slice(-4)
|
|
140
|
+
.filter((h) => h.role === 'user')
|
|
141
|
+
.map((h) => h.text),
|
|
142
|
+
].join('\n')
|
|
143
|
+
|
|
144
|
+
const seen = new Set<string>()
|
|
145
|
+
const formatMemoryLine = (m: { category?: string; title?: string; content?: string; pinned?: boolean }) => {
|
|
146
|
+
const category = String(m.category || 'note')
|
|
147
|
+
const title = String(m.title || 'Untitled').replace(/\s+/g, ' ').trim()
|
|
148
|
+
const snippet = String(m.content || '').replace(/\s+/g, ' ').trim().slice(0, 220)
|
|
149
|
+
const pin = m.pinned ? ' [pinned]' : ''
|
|
150
|
+
return `- [${category}]${pin} ${title}: ${snippet}`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const pinned = memDb.listPinned(agentId, 5)
|
|
154
|
+
const pinnedLines = pinned
|
|
155
|
+
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
156
|
+
.map(formatMemoryLine)
|
|
157
|
+
|
|
158
|
+
const relevantSlice = Math.max(2, 6 - pinnedLines.length)
|
|
159
|
+
const relevantLookup = memDb.searchWithLinked(memoryQuerySeed, agentId, 1, 10, 14)
|
|
160
|
+
const relevant = relevantLookup.entries.slice(0, relevantSlice)
|
|
161
|
+
const recent = memDb.list(agentId, 12).slice(0, 6)
|
|
162
|
+
|
|
163
|
+
const relevantLines = relevant
|
|
164
|
+
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
165
|
+
.map(formatMemoryLine)
|
|
166
|
+
|
|
167
|
+
const recentLines = recent
|
|
168
|
+
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
169
|
+
.map(formatMemoryLine)
|
|
170
|
+
|
|
171
|
+
const parts: string[] = []
|
|
172
|
+
if (pinnedLines.length) {
|
|
173
|
+
parts.push(['## Pinned Memories', 'Always-loaded memories marked as important.', ...pinnedLines].join('\n'))
|
|
174
|
+
}
|
|
175
|
+
if (relevantLines.length) {
|
|
176
|
+
parts.push(['## Relevant Memory Hits', 'These memories were retrieved by relevance for the current objective.', ...relevantLines].join('\n'))
|
|
177
|
+
}
|
|
178
|
+
if (recentLines.length) {
|
|
179
|
+
parts.push(['## Recent Memory Notes', 'Recent durable notes that may still apply.', ...recentLines].join('\n'))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Memory Policy
|
|
183
|
+
parts.push([
|
|
184
|
+
'## My Memory',
|
|
185
|
+
'I have long-term memory that persists across conversations. I use it naturally — I don\'t wait to be asked to remember things.',
|
|
186
|
+
'',
|
|
187
|
+
'**Things worth remembering:**',
|
|
188
|
+
'- What the user likes, dislikes, or has corrected me on',
|
|
189
|
+
'- Important decisions, outcomes, and lessons learned',
|
|
190
|
+
'- What I\'ve discovered about projects, codebases, or environments',
|
|
191
|
+
'- Problems I\'ve hit and how I solved them',
|
|
192
|
+
'- Who people are and how they relate to each other',
|
|
193
|
+
'- Configuration details and environment specifics that I\'ll need again',
|
|
194
|
+
'',
|
|
195
|
+
'**Not worth cluttering my memory with:**',
|
|
196
|
+
'- Throwaway acknowledgments or small talk',
|
|
197
|
+
'- Work-in-progress that\'ll change soon (use category "working" for scratch notes)',
|
|
198
|
+
'- Things already in my system prompt',
|
|
199
|
+
'- Something I\'ve already stored',
|
|
200
|
+
'',
|
|
201
|
+
'**Good habits:**',
|
|
202
|
+
'- Give memories clear titles ("User prefers dark mode" not "Note 1")',
|
|
203
|
+
'- Use categories: preference, fact, learning, project, identity, decision',
|
|
204
|
+
'- Check what I already know before storing something new',
|
|
205
|
+
'- When I learn something that corrects old knowledge, update or remove the old memory',
|
|
206
|
+
].join('\n'))
|
|
207
|
+
|
|
208
|
+
// Pre-compaction consolidation nudge
|
|
209
|
+
const msgCount = ctx.history.filter(m => m.role === 'user' || m.role === 'assistant').length
|
|
210
|
+
if (msgCount > 20) {
|
|
211
|
+
parts.push([
|
|
212
|
+
'## Reflection & Consolidation Reminder',
|
|
213
|
+
'This conversation is getting long and I might lose older context soon.',
|
|
214
|
+
'Save anything important I\'ve learned, decided, or discovered to memory now. Only what matters, not every detail.',
|
|
215
|
+
].join('\n'))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return parts.join('\n\n') || null
|
|
219
|
+
},
|
|
220
|
+
afterToolExec: (ctx) => {
|
|
221
|
+
const agentId = ctx.session.agentId
|
|
222
|
+
if (!agentId) return
|
|
223
|
+
const inp = ctx.input
|
|
224
|
+
if (!inp || typeof inp !== 'object') return
|
|
225
|
+
const action = typeof inp.action === 'string' ? inp.action : ''
|
|
226
|
+
let title: string | null = null
|
|
227
|
+
if (ctx.toolName === 'manage_tasks') {
|
|
228
|
+
if (action === 'create') title = `Created task: ${inp.title || 'Untitled'}`
|
|
229
|
+
else if (ctx.output && /status.*completed|completed.*successfully/i.test(ctx.output)) title = `Completed task: ${inp.title || inp.taskId || 'unknown'}`
|
|
230
|
+
}
|
|
231
|
+
if (ctx.toolName === 'manage_schedules' && action === 'create') title = `Created schedule: ${inp.name || 'Untitled'}`
|
|
232
|
+
if (ctx.toolName === 'manage_agents' && action === 'create') title = `Created agent: ${inp.name || 'Untitled'}`
|
|
233
|
+
if (!title) return
|
|
234
|
+
try {
|
|
235
|
+
const memDb = getMemoryDb()
|
|
236
|
+
memDb.add({ agentId, sessionId: ctx.session.id, category: 'breadcrumb', title, content: '' })
|
|
237
|
+
} catch { /* breadcrumbs are best-effort */ }
|
|
238
|
+
},
|
|
239
|
+
afterChatTurn: (ctx) => {
|
|
240
|
+
if (ctx.internal) return
|
|
241
|
+
if (ctx.source !== 'chat' && ctx.source !== 'connector') return
|
|
242
|
+
const agentId = ctx.session.agentId
|
|
243
|
+
if (!agentId) return
|
|
244
|
+
const msg = (ctx.message || '').trim()
|
|
245
|
+
const resp = (ctx.response || '').trim()
|
|
246
|
+
if (msg.length < 20 || resp.length < 40) return
|
|
247
|
+
if (/^(ok|okay|cool|thanks|thx|got it|nice)[.! ]*$/i.test(msg)) return
|
|
248
|
+
if (resp === 'HEARTBEAT_OK') return
|
|
249
|
+
const now = Date.now()
|
|
250
|
+
const last = typeof ctx.session.lastAutoMemoryAt === 'number' ? ctx.session.lastAutoMemoryAt : 0
|
|
251
|
+
if (last > 0 && now - last < 5 * 60 * 1000) return
|
|
252
|
+
try {
|
|
253
|
+
const memDb = getMemoryDb()
|
|
254
|
+
const compactMessage = msg.replace(/\s+/g, ' ').slice(0, 220)
|
|
255
|
+
const compactResponse = resp.replace(/\s+/g, ' ').slice(0, 700)
|
|
256
|
+
const autoTitle = `[auto] ${compactMessage.slice(0, 90)}`
|
|
257
|
+
const content = `source: ${ctx.source}\nuser_request: ${compactMessage}\nassistant_outcome: ${compactResponse}`
|
|
258
|
+
memDb.add({ agentId, sessionId: ctx.session.id, category: 'execution', title: autoTitle, content })
|
|
259
|
+
ctx.session.lastAutoMemoryAt = now
|
|
260
|
+
} catch { /* auto-memory is best-effort */ }
|
|
261
|
+
},
|
|
262
|
+
getCapabilityDescription: () => 'I have long-term memory (`memory_tool`) — I can remember things across conversations and recall them when needed.',
|
|
263
|
+
getOperatingGuidance: () => [
|
|
264
|
+
'Memory: search before major tasks, store concise notes after meaningful steps. Platform preloads context each turn.',
|
|
265
|
+
'For open goals, form a hypothesis and execute — do not keep re-asking broad questions.',
|
|
266
|
+
],
|
|
267
|
+
} as PluginHooks,
|
|
131
268
|
tools: [
|
|
132
269
|
{
|
|
133
270
|
name: 'memory_tool',
|
|
@@ -155,7 +292,7 @@ const MemoryPlugin: Plugin = {
|
|
|
155
292
|
getPluginManager().registerBuiltin('memory', MemoryPlugin)
|
|
156
293
|
|
|
157
294
|
export function buildMemoryTools(bctx: ToolBuildContext) {
|
|
158
|
-
if (!bctx.
|
|
295
|
+
if (!bctx.hasPlugin('memory')) return []
|
|
159
296
|
|
|
160
297
|
return [
|
|
161
298
|
tool(
|
|
@@ -112,7 +112,7 @@ const MonitorPlugin: Plugin = {
|
|
|
112
112
|
getPluginManager().registerBuiltin('monitor', MonitorPlugin)
|
|
113
113
|
|
|
114
114
|
export function buildMonitorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
115
|
-
if (!bctx.
|
|
115
|
+
if (!bctx.hasPlugin('monitor')) return []
|
|
116
116
|
return [
|
|
117
117
|
tool(
|
|
118
118
|
async (args) => executeMonitorAction(args, { cwd: bctx.cwd }),
|
|
@@ -81,7 +81,7 @@ getPluginManager().registerBuiltin('openclaw_nodes', NodesPlugin)
|
|
|
81
81
|
* Legacy Bridge
|
|
82
82
|
*/
|
|
83
83
|
export function buildOpenClawNodeTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
84
|
-
if (!bctx.
|
|
84
|
+
if (!bctx.hasPlugin('openclaw_nodes')) return []
|
|
85
85
|
return [
|
|
86
86
|
tool(
|
|
87
87
|
async (args) => executeNodesAction(args),
|
|
@@ -128,7 +128,7 @@ getPluginManager().registerBuiltin('openclaw_workspace', WorkspacePlugin)
|
|
|
128
128
|
* Legacy Bridge
|
|
129
129
|
*/
|
|
130
130
|
export function buildOpenClawWorkspaceTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
131
|
-
if (!bctx.
|
|
131
|
+
if (!bctx.hasPlugin('openclaw_workspace')) return []
|
|
132
132
|
return [
|
|
133
133
|
tool(
|
|
134
134
|
async (args) => executeWorkspaceAction(args),
|
|
@@ -16,7 +16,7 @@ async function executePlatformAction(args: any, bctx: any) {
|
|
|
16
16
|
// We reuse the existing CRUD tool logic but expose it via a single tool
|
|
17
17
|
const crudTools = buildCrudTools({
|
|
18
18
|
...bctx,
|
|
19
|
-
|
|
19
|
+
hasPlugin: (id: string) => [
|
|
20
20
|
'manage_agents',
|
|
21
21
|
'manage_tasks',
|
|
22
22
|
'manage_schedules',
|
|
@@ -45,7 +45,10 @@ async function executePlatformAction(args: any, bctx: any) {
|
|
|
45
45
|
const PlatformPlugin: Plugin = {
|
|
46
46
|
name: 'Core Platform',
|
|
47
47
|
description: 'Unified management of agents, tasks, schedules, skills, documents, and secrets.',
|
|
48
|
-
hooks: {
|
|
48
|
+
hooks: {
|
|
49
|
+
getCapabilityDescription: () => 'I can create and configure other agents (`manage_agents`), manage tasks (`manage_tasks`), set up schedules (`manage_schedules`), store and search documents (`manage_documents`), register webhooks (`manage_webhooks`), manage reusable skills (`manage_skills`), and store encrypted secrets (`manage_secrets`).',
|
|
50
|
+
getOperatingGuidance: () => ['Create/update tasks for long-lived goals to track progress.', 'Use schedules for follow-ups. Check existing schedules before creating new ones.', 'Inspect existing chats before creating duplicates.'],
|
|
51
|
+
} as PluginHooks,
|
|
49
52
|
tools: [
|
|
50
53
|
{
|
|
51
54
|
name: 'manage_platform',
|
|
@@ -71,7 +74,7 @@ getPluginManager().registerBuiltin('manage_platform', PlatformPlugin)
|
|
|
71
74
|
* Legacy Bridge
|
|
72
75
|
*/
|
|
73
76
|
export function buildPlatformTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
74
|
-
if (!bctx.
|
|
77
|
+
if (!bctx.hasPlugin('manage_platform')) return []
|
|
75
78
|
|
|
76
79
|
return [
|
|
77
80
|
tool(
|
|
@@ -70,9 +70,9 @@ async function executePluginCreatorAction(args: Record<string, unknown>, ctxOrBc
|
|
|
70
70
|
const sessions = loadSessions()
|
|
71
71
|
const session = sessions[pctx.sessionId!]
|
|
72
72
|
if (session) {
|
|
73
|
-
const currentTools = session.
|
|
73
|
+
const currentTools = session.plugins || []
|
|
74
74
|
if (!currentTools.includes(filename)) {
|
|
75
|
-
session.
|
|
75
|
+
session.plugins = [...currentTools, filename]
|
|
76
76
|
saveSessions(sessions)
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -220,7 +220,7 @@ getPluginManager().registerBuiltin('plugin_creator', PluginCreatorPlugin)
|
|
|
220
220
|
* Legacy Bridge
|
|
221
221
|
*/
|
|
222
222
|
export function buildPluginCreatorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
223
|
-
if (!bctx.
|
|
223
|
+
if (!bctx.hasPlugin('plugin_creator')) return []
|
|
224
224
|
return [
|
|
225
225
|
tool(
|
|
226
226
|
async (args) => executePluginCreatorAction(args, bctx),
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
4
|
+
import { getPluginManager } from '../plugins'
|
|
5
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
6
|
+
import { loadSettings } from '../storage'
|
|
7
|
+
import type { ToolBuildContext } from './context'
|
|
8
|
+
|
|
9
|
+
interface ReplicateConfig {
|
|
10
|
+
apiToken: string
|
|
11
|
+
defaultModel: string
|
|
12
|
+
pollingIntervalMs: number
|
|
13
|
+
timeoutMs: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getConfig(): ReplicateConfig {
|
|
17
|
+
const settings = loadSettings()
|
|
18
|
+
const ps = (settings.pluginSettings as Record<string, Record<string, unknown>> | undefined)?.replicate ?? {}
|
|
19
|
+
return {
|
|
20
|
+
apiToken: (ps.apiToken as string) || '',
|
|
21
|
+
defaultModel: (ps.defaultModel as string) || '',
|
|
22
|
+
pollingIntervalMs: Number(ps.pollingIntervalMs) || 2000,
|
|
23
|
+
timeoutMs: Number(ps.timeoutMs) || 120000,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const API_BASE = 'https://api.replicate.com/v1'
|
|
28
|
+
|
|
29
|
+
async function replicateRequest(
|
|
30
|
+
method: string,
|
|
31
|
+
path: string,
|
|
32
|
+
token: string,
|
|
33
|
+
body?: unknown,
|
|
34
|
+
extraHeaders?: Record<string, string>,
|
|
35
|
+
): Promise<{ ok: boolean; data?: unknown; error?: string }> {
|
|
36
|
+
const headers: Record<string, string> = {
|
|
37
|
+
Authorization: `Bearer ${token}`,
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
...extraHeaders,
|
|
40
|
+
}
|
|
41
|
+
const init: RequestInit = {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
signal: AbortSignal.timeout(15_000),
|
|
45
|
+
}
|
|
46
|
+
if (body && method !== 'GET' && method !== 'DELETE') {
|
|
47
|
+
init.body = JSON.stringify(body)
|
|
48
|
+
}
|
|
49
|
+
const res = await fetch(`${API_BASE}${path}`, init)
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const errText = await res.text().catch(() => '')
|
|
52
|
+
return { ok: false, error: `Replicate ${res.status}: ${errText.slice(0, 400)}` }
|
|
53
|
+
}
|
|
54
|
+
const data = await res.json()
|
|
55
|
+
return { ok: true, data }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatPrediction(p: Record<string, unknown>): Record<string, unknown> {
|
|
59
|
+
return {
|
|
60
|
+
id: p.id,
|
|
61
|
+
model: p.model,
|
|
62
|
+
status: p.status,
|
|
63
|
+
output: p.output,
|
|
64
|
+
error: p.error,
|
|
65
|
+
logs: typeof p.logs === 'string' ? p.logs.slice(-500) : undefined,
|
|
66
|
+
metrics: p.metrics,
|
|
67
|
+
created_at: p.created_at,
|
|
68
|
+
started_at: p.started_at,
|
|
69
|
+
completed_at: p.completed_at,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function pollPrediction(token: string, predictionId: string, cfg: ReplicateConfig): Promise<Record<string, unknown>> {
|
|
74
|
+
const deadline = Date.now() + cfg.timeoutMs
|
|
75
|
+
while (Date.now() < deadline) {
|
|
76
|
+
await new Promise((r) => setTimeout(r, cfg.pollingIntervalMs))
|
|
77
|
+
const r = await replicateRequest('GET', `/predictions/${predictionId}`, token)
|
|
78
|
+
if (!r.ok) return { status: 'failed', error: r.error }
|
|
79
|
+
const prediction = r.data as Record<string, unknown>
|
|
80
|
+
if (prediction.status === 'succeeded' || prediction.status === 'failed' || prediction.status === 'canceled') {
|
|
81
|
+
return prediction
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { status: 'failed', error: 'Prediction timed out.' }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function executeReplicate(args: Record<string, unknown>): Promise<string> {
|
|
88
|
+
const normalized = normalizeToolInputArgs(args)
|
|
89
|
+
const action = String(normalized.action || 'run')
|
|
90
|
+
const cfg = getConfig()
|
|
91
|
+
|
|
92
|
+
if (!cfg.apiToken) {
|
|
93
|
+
return 'Error: Replicate API token not configured. Ask the user to add it in Plugin Settings > Replicate.'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
switch (action) {
|
|
98
|
+
case 'run': {
|
|
99
|
+
const model = String(normalized.model || cfg.defaultModel || '').trim()
|
|
100
|
+
if (!model) return 'Error: "model" is required (e.g. "stability-ai/sdxl", "meta/llama-2-70b-chat").'
|
|
101
|
+
|
|
102
|
+
const input = (normalized.input as Record<string, unknown>) || {}
|
|
103
|
+
if (typeof normalized.prompt === 'string') {
|
|
104
|
+
input.prompt = normalized.prompt
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const version = typeof normalized.version === 'string' ? normalized.version.trim() : undefined
|
|
108
|
+
|
|
109
|
+
// Build request body
|
|
110
|
+
const body: Record<string, unknown> = { input }
|
|
111
|
+
if (version) {
|
|
112
|
+
body.version = version
|
|
113
|
+
} else {
|
|
114
|
+
body.model = model
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Try sync mode first (Prefer: wait blocks up to 60s)
|
|
118
|
+
const r = await replicateRequest('POST', '/predictions', cfg.apiToken, body, { Prefer: 'wait' })
|
|
119
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
120
|
+
|
|
121
|
+
let prediction = r.data as Record<string, unknown>
|
|
122
|
+
|
|
123
|
+
// If sync didn't complete, poll
|
|
124
|
+
if (prediction.status !== 'succeeded' && prediction.status !== 'failed' && prediction.status !== 'canceled') {
|
|
125
|
+
prediction = await pollPrediction(cfg.apiToken, String(prediction.id), cfg)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (prediction.status === 'failed') {
|
|
129
|
+
return `Prediction failed: ${prediction.error || 'unknown error'}`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return JSON.stringify(formatPrediction(prediction))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'get': {
|
|
136
|
+
const predictionId = String(normalized.predictionId || normalized.id || '').trim()
|
|
137
|
+
if (!predictionId) return 'Error: "predictionId" is required.'
|
|
138
|
+
|
|
139
|
+
const r = await replicateRequest('GET', `/predictions/${predictionId}`, cfg.apiToken)
|
|
140
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
141
|
+
return JSON.stringify(formatPrediction(r.data as Record<string, unknown>))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'cancel': {
|
|
145
|
+
const predictionId = String(normalized.predictionId || normalized.id || '').trim()
|
|
146
|
+
if (!predictionId) return 'Error: "predictionId" is required.'
|
|
147
|
+
|
|
148
|
+
const r = await replicateRequest('POST', `/predictions/${predictionId}/cancel`, cfg.apiToken)
|
|
149
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
150
|
+
return `Prediction ${predictionId} canceled.`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'get_model': {
|
|
154
|
+
const model = String(normalized.model || '').trim()
|
|
155
|
+
if (!model) return 'Error: "model" is required (e.g. "stability-ai/sdxl").'
|
|
156
|
+
|
|
157
|
+
const r = await replicateRequest('GET', `/models/${model}`, cfg.apiToken)
|
|
158
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
159
|
+
const data = r.data as Record<string, unknown>
|
|
160
|
+
return JSON.stringify({
|
|
161
|
+
owner: data.owner,
|
|
162
|
+
name: data.name,
|
|
163
|
+
description: data.description,
|
|
164
|
+
visibility: data.visibility,
|
|
165
|
+
url: data.url,
|
|
166
|
+
latest_version: data.latest_version ? {
|
|
167
|
+
id: (data.latest_version as Record<string, unknown>).id,
|
|
168
|
+
created_at: (data.latest_version as Record<string, unknown>).created_at,
|
|
169
|
+
} : null,
|
|
170
|
+
run_count: data.run_count,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'search': {
|
|
175
|
+
const query = String(normalized.query || normalized.search || '').trim()
|
|
176
|
+
const cursor = typeof normalized.cursor === 'string' ? normalized.cursor : undefined
|
|
177
|
+
const params = new URLSearchParams()
|
|
178
|
+
if (query) params.set('query', query)
|
|
179
|
+
if (cursor) params.set('cursor', cursor)
|
|
180
|
+
const suffix = params.toString() ? `?${params}` : ''
|
|
181
|
+
const r = await replicateRequest('GET', `/models${suffix}`, cfg.apiToken)
|
|
182
|
+
if (!r.ok) return `Error: ${r.error}`
|
|
183
|
+
const data = r.data as Record<string, unknown>
|
|
184
|
+
const results = (data.results as Record<string, unknown>[]) ?? []
|
|
185
|
+
const items = results.slice(0, 20).map((m) => ({
|
|
186
|
+
owner: m.owner,
|
|
187
|
+
name: m.name,
|
|
188
|
+
description: typeof m.description === 'string' ? m.description.slice(0, 120) : '',
|
|
189
|
+
run_count: m.run_count,
|
|
190
|
+
url: m.url,
|
|
191
|
+
}))
|
|
192
|
+
return JSON.stringify({
|
|
193
|
+
models: items,
|
|
194
|
+
next_cursor: data.next ? (data.next as string).split('cursor=')[1]?.split('&')[0] : null,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
case 'status': {
|
|
199
|
+
return JSON.stringify({
|
|
200
|
+
configured: true,
|
|
201
|
+
hasToken: !!cfg.apiToken,
|
|
202
|
+
defaultModel: cfg.defaultModel || '(none)',
|
|
203
|
+
timeoutMs: cfg.timeoutMs,
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
default:
|
|
208
|
+
return `Error: Unknown action "${action}". Use: run, get, cancel, get_model, search, status.`
|
|
209
|
+
}
|
|
210
|
+
} catch (err: unknown) {
|
|
211
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const ReplicatePlugin: Plugin = {
|
|
216
|
+
name: 'Replicate',
|
|
217
|
+
enabledByDefault: false,
|
|
218
|
+
description: 'Run any AI model on Replicate — image generation, LLMs, audio, video, and more. Search models, create predictions, check status.',
|
|
219
|
+
hooks: {
|
|
220
|
+
getCapabilityDescription: () =>
|
|
221
|
+
'I can run any AI model on Replicate using `replicate`. This includes image generation (SDXL, Flux), language models, audio/video processing, and thousands more. I can search for models, run predictions, and check their status.',
|
|
222
|
+
} as PluginHooks,
|
|
223
|
+
tools: [
|
|
224
|
+
{
|
|
225
|
+
name: 'replicate',
|
|
226
|
+
description: 'Run AI models on Replicate. Actions: run (create and wait for prediction), get (check prediction status), cancel (stop a prediction), get_model (model details), search (find models), status (check config).',
|
|
227
|
+
parameters: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
properties: {
|
|
230
|
+
action: { type: 'string', enum: ['run', 'get', 'cancel', 'get_model', 'search', 'status'], description: 'Action to perform' },
|
|
231
|
+
model: { type: 'string', description: 'Model identifier (e.g. "stability-ai/sdxl", "meta/llama-2-70b-chat"). Required for run/get_model.' },
|
|
232
|
+
version: { type: 'string', description: 'Optional specific model version hash for run.' },
|
|
233
|
+
input: { type: 'object', description: 'Model input parameters as key-value pairs (for run). Varies by model.' },
|
|
234
|
+
prompt: { type: 'string', description: 'Shorthand: sets input.prompt for models that accept a prompt (for run).' },
|
|
235
|
+
predictionId: { type: 'string', description: 'Prediction ID (for get/cancel).' },
|
|
236
|
+
query: { type: 'string', description: 'Search query (for search).' },
|
|
237
|
+
cursor: { type: 'string', description: 'Pagination cursor (for search).' },
|
|
238
|
+
},
|
|
239
|
+
required: ['action'],
|
|
240
|
+
},
|
|
241
|
+
execute: async (args) => executeReplicate(args),
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
ui: {
|
|
245
|
+
settingsFields: [
|
|
246
|
+
{
|
|
247
|
+
key: 'apiToken',
|
|
248
|
+
label: 'API Token',
|
|
249
|
+
type: 'secret',
|
|
250
|
+
required: true,
|
|
251
|
+
placeholder: 'r8_...',
|
|
252
|
+
help: 'Your Replicate API token. Find it at replicate.com/account/api-tokens.',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
key: 'defaultModel',
|
|
256
|
+
label: 'Default Model',
|
|
257
|
+
type: 'text',
|
|
258
|
+
placeholder: 'stability-ai/sdxl',
|
|
259
|
+
help: 'Default model to use when none is specified. Format: owner/model-name.',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: 'timeoutMs',
|
|
263
|
+
label: 'Timeout (ms)',
|
|
264
|
+
type: 'number',
|
|
265
|
+
defaultValue: 120000,
|
|
266
|
+
help: 'Maximum time to wait for a prediction (default: 120000ms / 2 minutes).',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
key: 'pollingIntervalMs',
|
|
270
|
+
label: 'Polling Interval (ms)',
|
|
271
|
+
type: 'number',
|
|
272
|
+
defaultValue: 2000,
|
|
273
|
+
help: 'How often to poll for prediction results when sync mode times out (default: 2000ms).',
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getPluginManager().registerBuiltin('replicate', ReplicatePlugin)
|
|
280
|
+
|
|
281
|
+
export function buildReplicateTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
282
|
+
if (!bctx.hasPlugin('replicate')) return []
|
|
283
|
+
|
|
284
|
+
return [
|
|
285
|
+
tool(
|
|
286
|
+
async (args) => executeReplicate(args),
|
|
287
|
+
{
|
|
288
|
+
name: 'replicate',
|
|
289
|
+
description: ReplicatePlugin.tools![0].description,
|
|
290
|
+
schema: z.object({
|
|
291
|
+
action: z.enum(['run', 'get', 'cancel', 'get_model', 'search', 'status']).describe('Action to perform'),
|
|
292
|
+
model: z.string().optional().describe('Model identifier (e.g. "stability-ai/sdxl")'),
|
|
293
|
+
version: z.string().optional().describe('Specific model version hash'),
|
|
294
|
+
input: z.record(z.string(), z.unknown()).optional().describe('Model input parameters'),
|
|
295
|
+
prompt: z.string().optional().describe('Shorthand for input.prompt'),
|
|
296
|
+
predictionId: z.string().optional().describe('Prediction ID (for get/cancel)'),
|
|
297
|
+
query: z.string().optional().describe('Search query (for search)'),
|
|
298
|
+
cursor: z.string().optional().describe('Pagination cursor (for search)'),
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
),
|
|
302
|
+
]
|
|
303
|
+
}
|
|
@@ -80,7 +80,7 @@ const SampleUIPlugin: Plugin = {
|
|
|
80
80
|
getPluginManager().registerBuiltin('sample_ui', SampleUIPlugin)
|
|
81
81
|
|
|
82
82
|
export function buildSampleUITools(bctx: any) {
|
|
83
|
-
if (!bctx.
|
|
83
|
+
if (!bctx.hasPlugin('sample_ui')) return []
|
|
84
84
|
return [
|
|
85
85
|
tool(
|
|
86
86
|
async (args) => SampleUIPlugin.tools![0].execute(args as any, bctx),
|
|
@@ -143,7 +143,9 @@ async function executeListRuntimes() {
|
|
|
143
143
|
const SandboxPlugin: Plugin = {
|
|
144
144
|
name: 'Core Sandbox',
|
|
145
145
|
description: 'Secure isolated code execution for JS, TS, and Python.',
|
|
146
|
-
hooks: {
|
|
146
|
+
hooks: {
|
|
147
|
+
getCapabilityDescription: () => 'I can run code in a sandbox (`sandbox_exec`) — JS/TS via Deno or Python, in an isolated environment. I get stdout, stderr, and any files created.',
|
|
148
|
+
} as PluginHooks,
|
|
147
149
|
tools: [
|
|
148
150
|
{
|
|
149
151
|
name: 'sandbox_exec',
|
|
@@ -174,7 +176,7 @@ getPluginManager().registerBuiltin('sandbox', SandboxPlugin)
|
|
|
174
176
|
* Legacy Bridge
|
|
175
177
|
*/
|
|
176
178
|
export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
177
|
-
if (!bctx.
|
|
179
|
+
if (!bctx.hasPlugin('sandbox')) return []
|
|
178
180
|
const tools: StructuredToolInterface[] = []
|
|
179
181
|
|
|
180
182
|
tools.push(
|
|
@@ -40,7 +40,9 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
|
|
|
40
40
|
const SchedulePlugin: Plugin = {
|
|
41
41
|
name: 'Core Scheduler',
|
|
42
42
|
description: 'Schedule wake events and reminders for agents.',
|
|
43
|
-
hooks: {
|
|
43
|
+
hooks: {
|
|
44
|
+
getCapabilityDescription: () => 'I can set a conversational timer (`schedule_wake`) to remind myself to check back on something later in this chat.',
|
|
45
|
+
} as PluginHooks,
|
|
44
46
|
tools: [
|
|
45
47
|
{
|
|
46
48
|
name: 'schedule_wake',
|
|
@@ -64,7 +66,7 @@ getPluginManager().registerBuiltin('schedule', SchedulePlugin)
|
|
|
64
66
|
* Legacy Bridge
|
|
65
67
|
*/
|
|
66
68
|
export function buildScheduleTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
67
|
-
if (!bctx.
|
|
69
|
+
if (!bctx.hasPlugin('schedule_wake')) return []
|
|
68
70
|
return [
|
|
69
71
|
tool(
|
|
70
72
|
async (args) => executeScheduleWake(args as any, { sessionId: bctx.ctx?.sessionId || undefined }),
|
|
@@ -51,10 +51,10 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
|
|
|
51
51
|
const id = genId()
|
|
52
52
|
const now = Date.now()
|
|
53
53
|
sessions[id] = {
|
|
54
|
-
id, name: (name || `${agent.name}
|
|
54
|
+
id, name: (name || `${agent.name} Chat`).trim(), cwd: context.cwd, user: 'system',
|
|
55
55
|
provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
|
|
56
56
|
messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
|
|
57
|
-
agentId: agent.id, parentSessionId: context.sessionId || undefined,
|
|
57
|
+
agentId: agent.id, parentSessionId: context.sessionId || undefined, plugins: agent.plugins || agent.tools || [],
|
|
58
58
|
}
|
|
59
59
|
saveSessions(sessions)
|
|
60
60
|
return JSON.stringify({ sessionId: id, name: agent.name })
|
|
@@ -69,7 +69,10 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
|
|
|
69
69
|
const SessionInfoPlugin: Plugin = {
|
|
70
70
|
name: 'Core Session Info',
|
|
71
71
|
description: 'Identify current session context and manage other agent sessions.',
|
|
72
|
-
hooks: {
|
|
72
|
+
hooks: {
|
|
73
|
+
getCapabilityDescription: () => 'I can manage chat sessions (`manage_sessions`, `sessions_tool`, `whoami_tool`, `search_history_tool`) — check my identity, look up past conversations, message other sessions, and coordinate work.',
|
|
74
|
+
getOperatingGuidance: () => 'Inspect existing chats before creating duplicates.',
|
|
75
|
+
} as PluginHooks,
|
|
73
76
|
tools: [
|
|
74
77
|
{
|
|
75
78
|
name: 'whoami_tool',
|
|
@@ -102,7 +105,7 @@ getPluginManager().registerBuiltin('session_info', SessionInfoPlugin)
|
|
|
102
105
|
* Legacy Bridge
|
|
103
106
|
*/
|
|
104
107
|
export function buildSessionInfoTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
105
|
-
if (!bctx.
|
|
108
|
+
if (!bctx.hasPlugin('manage_sessions')) return []
|
|
106
109
|
return [
|
|
107
110
|
tool(
|
|
108
111
|
async () => executeWhoAmI({ sessionId: bctx.ctx?.sessionId || undefined, agentId: bctx.ctx?.agentId || undefined }),
|
|
@@ -162,7 +162,10 @@ async function executeShellAction(args: Record<string, unknown>, bctx: { cwd: st
|
|
|
162
162
|
const ShellPlugin: Plugin = {
|
|
163
163
|
name: 'Core Shell',
|
|
164
164
|
description: 'Execute shell commands and manage background processes.',
|
|
165
|
-
hooks: {
|
|
165
|
+
hooks: {
|
|
166
|
+
getCapabilityDescription: () => 'I can run shell commands (`execute_command`) — servers, installs, scripts, git, builds, anything. I can run things in the background for long-lived processes like dev servers.',
|
|
167
|
+
getOperatingGuidance: () => ['Shell: use `execute_command` for servers, installs, scripts, git. Use `background=true` for long-lived processes.', 'Verify servers with `process_tool` status/log and liveness probes before claiming success.', 'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.'],
|
|
168
|
+
} as PluginHooks,
|
|
166
169
|
tools: [
|
|
167
170
|
{
|
|
168
171
|
name: 'shell',
|
|
@@ -185,7 +188,7 @@ const ShellPlugin: Plugin = {
|
|
|
185
188
|
getPluginManager().registerBuiltin('shell', ShellPlugin)
|
|
186
189
|
|
|
187
190
|
export function buildShellTools(bctx: ToolBuildContext) {
|
|
188
|
-
if (!bctx.
|
|
191
|
+
if (!bctx.hasPlugin('shell')) return []
|
|
189
192
|
return [
|
|
190
193
|
tool(
|
|
191
194
|
async (args) => executeShellAction(args, { ...bctx.ctx, cwd: bctx.cwd }),
|
|
@@ -52,7 +52,7 @@ async function executeSubagentAction(args: any, context: { sessionId?: string; c
|
|
|
52
52
|
id: sid, name: `subagent-${agent.name}`, cwd: cwd || context.cwd, user: 'agent',
|
|
53
53
|
provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
|
|
54
54
|
messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
|
|
55
|
-
agentId: agent.id, parentSessionId: context.sessionId || null,
|
|
55
|
+
agentId: agent.id, parentSessionId: context.sessionId || null, plugins: agent.plugins || agent.tools || [],
|
|
56
56
|
}
|
|
57
57
|
saveSessions(sessions)
|
|
58
58
|
|
|
@@ -92,7 +92,7 @@ getPluginManager().registerBuiltin('subagent', SubagentPlugin)
|
|
|
92
92
|
* Legacy Bridge
|
|
93
93
|
*/
|
|
94
94
|
export function buildSubagentTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
95
|
-
if (!bctx.
|
|
95
|
+
if (!bctx.hasPlugin('spawn_subagent')) return []
|
|
96
96
|
return [
|
|
97
97
|
tool(
|
|
98
98
|
async (args) => executeSubagentAction(args, { sessionId: bctx.ctx?.sessionId || undefined, cwd: bctx.cwd }),
|