@swarmclawai/swarmclaw 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { tool
|
|
2
|
+
import { tool } from '@langchain/core/tools'
|
|
3
|
+
import fs from 'fs'
|
|
3
4
|
import {
|
|
4
|
-
clearManagedProcess,
|
|
5
5
|
getManagedProcess,
|
|
6
6
|
killManagedProcess,
|
|
7
7
|
listManagedProcesses,
|
|
@@ -13,159 +13,187 @@ import {
|
|
|
13
13
|
} from '../process-manager'
|
|
14
14
|
import type { ToolBuildContext } from './context'
|
|
15
15
|
import { safePath, truncate, coerceEnvMap, MAX_OUTPUT } from './context'
|
|
16
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
17
|
+
import { getPluginManager } from '../plugins'
|
|
18
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const result = await startManagedProcess({
|
|
27
|
-
command,
|
|
28
|
-
cwd: workdir ? safePath(cwd, workdir) : cwd,
|
|
29
|
-
env: coerceEnvMap(env),
|
|
30
|
-
agentId: ctx?.agentId || null,
|
|
31
|
-
sessionId: ctx?.sessionId || null,
|
|
32
|
-
background: !!background,
|
|
33
|
-
yieldMs: typeof yieldMs === 'number' ? yieldMs : undefined,
|
|
34
|
-
timeoutMs: typeof timeoutSec === 'number'
|
|
35
|
-
? Math.max(1, Math.trunc(timeoutSec)) * 1000
|
|
36
|
-
: commandTimeoutMs,
|
|
37
|
-
})
|
|
38
|
-
if (result.status === 'completed') {
|
|
39
|
-
return truncate(result.output || '(no output)', MAX_OUTPUT)
|
|
40
|
-
}
|
|
41
|
-
return JSON.stringify({
|
|
42
|
-
status: 'running',
|
|
43
|
-
processId: result.processId,
|
|
44
|
-
tail: result.tail || '',
|
|
45
|
-
}, null, 2)
|
|
46
|
-
} catch (err: any) {
|
|
47
|
-
return truncate(`Error: ${err.message || String(err)}`, MAX_OUTPUT)
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: 'execute_command',
|
|
52
|
-
description: 'Run a shell command in my working directory. This is how I run servers, install packages, execute scripts, use git, and do anything hands-on. Use background=true for long-running processes like dev servers. Supports timeout/yield controls.',
|
|
53
|
-
schema: z.object({
|
|
54
|
-
command: z.string().describe('The shell command to execute'),
|
|
55
|
-
background: z.boolean().optional().describe('If true, start command in background immediately'),
|
|
56
|
-
yieldMs: z.number().optional().describe('If command runs longer than this, return a running process id instead of blocking'),
|
|
57
|
-
timeoutSec: z.number().optional().describe('Per-command timeout in seconds'),
|
|
58
|
-
workdir: z.string().optional().describe('Relative working directory override'),
|
|
59
|
-
env: z.record(z.string(), z.string()).optional().describe('Environment variable overrides'),
|
|
60
|
-
}),
|
|
61
|
-
},
|
|
62
|
-
),
|
|
63
|
-
)
|
|
64
|
-
}
|
|
20
|
+
function resolveShellWorkdir(baseCwd: string, requestedWorkdir?: string): string {
|
|
21
|
+
const raw = typeof requestedWorkdir === 'string' ? requestedWorkdir.trim() : ''
|
|
22
|
+
if (!raw) return baseCwd
|
|
23
|
+
try {
|
|
24
|
+
const resolved = safePath(baseCwd, raw)
|
|
25
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) return resolved
|
|
26
|
+
} catch { /* ignore */ }
|
|
27
|
+
return safePath(baseCwd, raw)
|
|
28
|
+
}
|
|
65
29
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return JSON.stringify(listManagedProcesses(ctx?.agentId || null).map((p) => ({
|
|
73
|
-
id: p.id,
|
|
74
|
-
command: p.command,
|
|
75
|
-
status: p.status,
|
|
76
|
-
pid: p.pid,
|
|
77
|
-
startedAt: p.startedAt,
|
|
78
|
-
endedAt: p.endedAt,
|
|
79
|
-
exitCode: p.exitCode,
|
|
80
|
-
signal: p.signal,
|
|
81
|
-
})), null, 2)
|
|
82
|
-
}
|
|
30
|
+
function isLikelyServerCommand(command: string): boolean {
|
|
31
|
+
const cmd = command.trim()
|
|
32
|
+
return /\bnpm\s+run\s+(dev|start|serve)\b/.test(cmd) ||
|
|
33
|
+
/\bnpx\s+(serve|next|vite|nuxt|astro)\b/.test(cmd) ||
|
|
34
|
+
/\bpython3?\s+-m\s+http\.server\b/.test(cmd)
|
|
35
|
+
}
|
|
83
36
|
|
|
84
|
-
|
|
37
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
38
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
39
|
+
return value as Record<string, unknown>
|
|
40
|
+
}
|
|
85
41
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
42
|
+
function parseNestedInput(raw: unknown): Record<string, unknown> | null {
|
|
43
|
+
if (typeof raw === 'string') {
|
|
44
|
+
try {
|
|
45
|
+
return asRecord(JSON.parse(raw))
|
|
46
|
+
} catch {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return asRecord(raw)
|
|
51
|
+
}
|
|
90
52
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
chunk: res.chunk,
|
|
100
|
-
}, null, 2)
|
|
101
|
-
}
|
|
53
|
+
function pickString(...values: unknown[]): string | undefined {
|
|
54
|
+
for (const value of values) {
|
|
55
|
+
if (typeof value !== 'string') continue
|
|
56
|
+
const trimmed = value.trim()
|
|
57
|
+
if (trimmed) return trimmed
|
|
58
|
+
}
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
102
61
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
62
|
+
function pickNumber(...values: unknown[]): number | undefined {
|
|
63
|
+
for (const value of values) {
|
|
64
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
65
|
+
if (typeof value === 'string' && value.trim()) {
|
|
66
|
+
const parsed = Number(value)
|
|
67
|
+
if (Number.isFinite(parsed)) return parsed
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
113
72
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
73
|
+
function pickBoolean(...values: unknown[]): boolean | undefined {
|
|
74
|
+
for (const value of values) {
|
|
75
|
+
if (typeof value === 'boolean') return value
|
|
76
|
+
}
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
118
79
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
80
|
+
export function normalizeShellArgs(rawArgs: Record<string, unknown>): Record<string, unknown> {
|
|
81
|
+
const base = normalizeToolInputArgs(rawArgs)
|
|
82
|
+
const nested = parseNestedInput(base.input)
|
|
123
83
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
84
|
+
const command = pickString(
|
|
85
|
+
base.command,
|
|
86
|
+
base.cmd,
|
|
87
|
+
base.execute_command,
|
|
88
|
+
nested?.command,
|
|
89
|
+
nested?.cmd,
|
|
90
|
+
nested?.execute_command,
|
|
91
|
+
)
|
|
92
|
+
const action = pickString(base.action, nested?.action) ?? (command ? 'execute' : undefined)
|
|
128
93
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
94
|
+
return {
|
|
95
|
+
action,
|
|
96
|
+
command,
|
|
97
|
+
processId: pickString(base.processId, base.process_id, nested?.processId, nested?.process_id),
|
|
98
|
+
background: pickBoolean(base.background, nested?.background),
|
|
99
|
+
workdir: pickString(base.workdir, base.cwd, nested?.workdir, nested?.cwd),
|
|
100
|
+
env: asRecord(base.env) || asRecord(nested?.env),
|
|
101
|
+
timeoutSec: pickNumber(base.timeoutSec, base.timeout, nested?.timeoutSec, nested?.timeout),
|
|
102
|
+
data: pickString(base.data, base.stdin, nested?.data, nested?.stdin),
|
|
103
|
+
signal: pickString(base.signal, base.killSignal, nested?.signal, nested?.killSignal),
|
|
104
|
+
offset: pickNumber(base.offset, nested?.offset),
|
|
105
|
+
limit: pickNumber(base.limit, nested?.limit),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
133
108
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Unified Shell Execution Logic
|
|
111
|
+
*/
|
|
112
|
+
async function executeShellAction(args: Record<string, unknown>, bctx: { cwd: string; agentId?: string | null; sessionId?: string | null }) {
|
|
113
|
+
const normalized = normalizeShellArgs(args)
|
|
114
|
+
const action = normalized.action as string | undefined
|
|
115
|
+
const command = normalized.command as string | undefined
|
|
116
|
+
const processId = normalized.processId as string | undefined
|
|
117
|
+
const background = normalized.background as boolean | undefined
|
|
118
|
+
const workdir = normalized.workdir as string | undefined
|
|
119
|
+
const env = normalized.env
|
|
120
|
+
const timeoutSec = normalized.timeoutSec as number | undefined
|
|
121
|
+
const data = normalized.data as string | undefined
|
|
122
|
+
const signal = normalized.signal as string | undefined
|
|
123
|
+
const offset = normalized.offset as number | undefined
|
|
124
|
+
const limit = normalized.limit as number | undefined
|
|
125
|
+
try {
|
|
126
|
+
switch (action) {
|
|
127
|
+
case 'execute': {
|
|
128
|
+
if (!command) return 'Error: command or cmd is required for execute action.'
|
|
129
|
+
const effectiveBackground = !!background || (typeof command === 'string' && isLikelyServerCommand(command))
|
|
130
|
+
const result = await startManagedProcess({
|
|
131
|
+
command: command,
|
|
132
|
+
cwd: resolveShellWorkdir(bctx.cwd, workdir),
|
|
133
|
+
env: coerceEnvMap(env),
|
|
134
|
+
agentId: bctx.agentId || null,
|
|
135
|
+
sessionId: bctx.sessionId || null,
|
|
136
|
+
background: effectiveBackground,
|
|
137
|
+
timeoutMs: typeof timeoutSec === 'number' ? timeoutSec * 1000 : 30000,
|
|
138
|
+
})
|
|
139
|
+
if (result.status === 'completed') return truncate(result.output || '(no output)', MAX_OUTPUT)
|
|
140
|
+
return JSON.stringify({ status: 'running', processId: result.processId, tail: result.tail || '' }, null, 2)
|
|
141
|
+
}
|
|
142
|
+
case 'list': return JSON.stringify(listManagedProcesses(bctx.agentId || null), null, 2)
|
|
143
|
+
case 'status': return JSON.stringify(getManagedProcess(processId!) || `Not found: ${processId}`, null, 2)
|
|
144
|
+
case 'poll': return JSON.stringify(pollManagedProcess(processId!) || `Not found: ${processId}`, null, 2)
|
|
145
|
+
case 'log': return JSON.stringify(readManagedProcessLog(processId!, offset, limit) || `Not found: ${processId}`, null, 2)
|
|
146
|
+
case 'write': return writeManagedProcessStdin(processId!, data || '', false).ok ? `Wrote to ${processId}` : `Error`
|
|
147
|
+
case 'kill': {
|
|
148
|
+
const killSignal = (typeof signal === 'string' && signal.trim() ? signal : 'SIGTERM') as NodeJS.Signals
|
|
149
|
+
return killManagedProcess(processId!, killSignal).ok ? `Killed ${processId}` : `Error`
|
|
150
|
+
}
|
|
151
|
+
case 'remove': return removeManagedProcess(processId!).ok ? `Removed ${processId}` : `Error`
|
|
152
|
+
default: return `Error: Unknown action "${action}"`
|
|
153
|
+
}
|
|
154
|
+
} catch (err: unknown) {
|
|
155
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
156
|
+
}
|
|
157
|
+
}
|
|
147
158
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Register as a Built-in Plugin
|
|
161
|
+
*/
|
|
162
|
+
const ShellPlugin: Plugin = {
|
|
163
|
+
name: 'Core Shell',
|
|
164
|
+
description: 'Execute shell commands and manage background processes.',
|
|
165
|
+
hooks: {} as PluginHooks,
|
|
166
|
+
tools: [
|
|
167
|
+
{
|
|
168
|
+
name: 'shell',
|
|
169
|
+
description: 'Execute commands and manage processes.',
|
|
170
|
+
parameters: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
action: { type: 'string', enum: ['execute', 'list', 'status', 'poll', 'log', 'write', 'kill', 'remove'] },
|
|
174
|
+
command: { type: 'string' },
|
|
175
|
+
processId: { type: 'string' },
|
|
176
|
+
background: { type: 'boolean' },
|
|
152
177
|
},
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
data: z.string().optional(),
|
|
162
|
-
eof: z.boolean().optional(),
|
|
163
|
-
signal: z.string().optional().describe('Signal for kill action, e.g. SIGTERM or SIGKILL'),
|
|
164
|
-
}),
|
|
165
|
-
},
|
|
166
|
-
),
|
|
167
|
-
)
|
|
168
|
-
}
|
|
178
|
+
required: ['action']
|
|
179
|
+
},
|
|
180
|
+
execute: async (args, context) => executeShellAction(args, { ...context.session, cwd: context.session.cwd || process.cwd() })
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getPluginManager().registerBuiltin('shell', ShellPlugin)
|
|
169
186
|
|
|
170
|
-
|
|
187
|
+
export function buildShellTools(bctx: ToolBuildContext) {
|
|
188
|
+
if (!bctx.hasTool('shell')) return []
|
|
189
|
+
return [
|
|
190
|
+
tool(
|
|
191
|
+
async (args) => executeShellAction(args, { ...bctx.ctx, cwd: bctx.cwd }),
|
|
192
|
+
{
|
|
193
|
+
name: 'shell',
|
|
194
|
+
description: ShellPlugin.tools![0].description,
|
|
195
|
+
schema: z.object({}).passthrough()
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
]
|
|
171
199
|
}
|
|
@@ -1,106 +1,106 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
3
|
import { genId } from '@/lib/id'
|
|
4
|
+
import { DEFAULT_DELEGATION_MAX_DEPTH } from '@/lib/runtime-loop'
|
|
4
5
|
import { loadAgents, loadSessions, saveSessions } from '../storage'
|
|
5
6
|
import { executeSessionChatTurn } from '../chat-execution'
|
|
6
7
|
import { log } from '../logger'
|
|
8
|
+
import { loadRuntimeSettings } from '../runtime-settings'
|
|
7
9
|
import type { ToolBuildContext } from './context'
|
|
10
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
11
|
+
import { getPluginManager } from '../plugins'
|
|
12
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function getSessionDepth(sessionId: string | undefined): number {
|
|
14
|
+
function getSessionDepth(sessionId: string | undefined, maxDepth: number): number {
|
|
12
15
|
if (!sessionId) return 0
|
|
13
16
|
const sessions = loadSessions()
|
|
14
17
|
let depth = 0
|
|
15
18
|
let current = sessionId
|
|
16
|
-
while (current && depth <
|
|
19
|
+
while (current && depth < maxDepth + 1) {
|
|
17
20
|
const session = sessions[current]
|
|
18
21
|
if (!session?.parentSessionId) break
|
|
19
|
-
current = session.parentSessionId
|
|
22
|
+
current = session.parentSessionId as string
|
|
20
23
|
depth++
|
|
21
24
|
}
|
|
22
25
|
return depth
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Core Subagent Execution Logic
|
|
30
|
+
*/
|
|
31
|
+
async function executeSubagentAction(args: any, context: { sessionId?: string; cwd: string }) {
|
|
32
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
33
|
+
const agentId = (normalized.agentId ?? normalized.agent_id) as string | undefined
|
|
34
|
+
const message = normalized.message as string | undefined
|
|
35
|
+
const cwd = normalized.cwd as string | undefined
|
|
36
|
+
try {
|
|
37
|
+
const runtime = loadRuntimeSettings()
|
|
38
|
+
const maxDepth = runtime.delegationMaxDepth || DEFAULT_DELEGATION_MAX_DEPTH
|
|
39
|
+
const agents = loadAgents()
|
|
40
|
+
if (!agentId) return 'Error: agentId is required.'
|
|
41
|
+
if (!message) return 'Error: message is required.'
|
|
42
|
+
const agent = agents[agentId]
|
|
43
|
+
if (!agent) return `Error: Agent "${agentId}" not found.`
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
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(', ')}`
|
|
45
|
+
const depth = getSessionDepth(context.sessionId, maxDepth)
|
|
46
|
+
if (depth >= maxDepth) return `Error: Max subagent depth reached.`
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const sid = genId()
|
|
49
|
+
const now = Date.now()
|
|
50
|
+
const sessions = loadSessions()
|
|
51
|
+
sessions[sid] = {
|
|
52
|
+
id: sid, name: `subagent-${agent.name}`, cwd: cwd || context.cwd, user: 'agent',
|
|
53
|
+
provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
|
|
54
|
+
messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
|
|
55
|
+
agentId: agent.id, parentSessionId: context.sessionId || null, tools: agent.tools || [],
|
|
56
|
+
}
|
|
57
|
+
saveSessions(sessions)
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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)
|
|
59
|
+
const result = await executeSessionChatTurn({ sessionId: sid, message, internal: true, source: 'subagent' })
|
|
60
|
+
return JSON.stringify({ agentId, agentName: agent.name, sessionId: sid, response: result.text.slice(0, 8000) })
|
|
61
|
+
} catch (err: any) { return `Error: ${err.message}` }
|
|
62
|
+
}
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Register as a Built-in Plugin
|
|
66
|
+
*/
|
|
67
|
+
const SubagentPlugin: Plugin = {
|
|
68
|
+
name: 'Core Subagents',
|
|
69
|
+
description: 'Delegate tasks to other specialized agents.',
|
|
70
|
+
hooks: {} as PluginHooks,
|
|
71
|
+
tools: [
|
|
72
|
+
{
|
|
73
|
+
name: 'spawn_subagent',
|
|
74
|
+
description: 'Delegate a task to another agent.',
|
|
75
|
+
parameters: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
agentId: { type: 'string' },
|
|
79
|
+
message: { type: 'string' },
|
|
80
|
+
cwd: { type: 'string' }
|
|
81
|
+
},
|
|
82
|
+
required: ['agentId', 'message']
|
|
83
|
+
},
|
|
84
|
+
execute: async (args, context) => executeSubagentAction(args, { sessionId: context.session.id, cwd: context.session.cwd || process.cwd() })
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
74
88
|
|
|
75
|
-
|
|
76
|
-
const result = await executeSessionChatTurn({
|
|
77
|
-
sessionId,
|
|
78
|
-
message,
|
|
79
|
-
internal: true,
|
|
80
|
-
source: 'subagent',
|
|
81
|
-
})
|
|
89
|
+
getPluginManager().registerBuiltin('subagent', SubagentPlugin)
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} catch (err: unknown) {
|
|
92
|
-
return `Error spawning subagent: ${err instanceof Error ? err.message : String(err)}`
|
|
93
|
-
}
|
|
94
|
-
},
|
|
91
|
+
/**
|
|
92
|
+
* Legacy Bridge
|
|
93
|
+
*/
|
|
94
|
+
export function buildSubagentTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
95
|
+
if (!bctx.hasTool('spawn_subagent')) return []
|
|
96
|
+
return [
|
|
97
|
+
tool(
|
|
98
|
+
async (args) => executeSubagentAction(args, { sessionId: bctx.ctx?.sessionId || undefined, cwd: bctx.cwd }),
|
|
95
99
|
{
|
|
96
100
|
name: 'spawn_subagent',
|
|
97
|
-
description:
|
|
98
|
-
schema: z.object({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
cwd: z.string().optional().describe('Optional working directory for the subagent (defaults to current)'),
|
|
102
|
-
}),
|
|
103
|
-
},
|
|
104
|
-
),
|
|
101
|
+
description: SubagentPlugin.tools![0].description,
|
|
102
|
+
schema: z.object({}).passthrough()
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
105
|
]
|
|
106
106
|
}
|