@swarmclawai/swarmclaw 0.6.7 → 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 +82 -39
- 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 +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- 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/graph/route.ts +46 -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/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -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 +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- 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/checkpoint-timeline.tsx +112 -0
- 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 +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- 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 +37 -7
- package/src/components/home/home-view.tsx +54 -24
- 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 +87 -19
- 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-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- 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 +28 -9
- 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/hint-tip.tsx +31 -0
- 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 +149 -4
- 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 +224 -0
- 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 +72 -48
- 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 +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -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 +115 -16
- 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 +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -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 +32 -2
- 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 +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- 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 +78 -0
- 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 +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- 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 +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -3,420 +3,114 @@ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
|
3
3
|
import { genId } from '@/lib/id'
|
|
4
4
|
import { loadSessions, saveSessions, loadAgents } from '../storage'
|
|
5
5
|
import type { ToolBuildContext } from './context'
|
|
6
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
7
|
+
import { getPluginManager } from '../plugins'
|
|
8
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Core Session Info Execution Logic
|
|
12
|
+
*/
|
|
13
|
+
async function executeWhoAmI(context: { sessionId?: string; agentId?: string }) {
|
|
14
|
+
try {
|
|
15
|
+
const sessions = loadSessions()
|
|
16
|
+
const current = context.sessionId ? sessions[context.sessionId] : null
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
sessionId: context.sessionId || undefined,
|
|
19
|
+
sessionName: current?.name || undefined,
|
|
20
|
+
sessionType: current?.sessionType || undefined,
|
|
21
|
+
user: current?.user || undefined,
|
|
22
|
+
agentId: context.agentId || current?.agentId || undefined,
|
|
23
|
+
parentSessionId: current?.parentSessionId || undefined,
|
|
24
|
+
})
|
|
25
|
+
} catch (err: any) { return `Error: ${err.message}` }
|
|
26
|
+
}
|
|
6
27
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const sessions = loadSessions()
|
|
46
|
-
if (action === 'list') {
|
|
47
|
-
const { getSessionRunState } = await import('../session-run-manager')
|
|
48
|
-
const items = Object.values(sessions)
|
|
49
|
-
.sort((a: any, b: any) => (b.lastActiveAt || 0) - (a.lastActiveAt || 0))
|
|
50
|
-
.slice(0, Math.max(1, Math.min(limit || 50, 200)))
|
|
51
|
-
.map((s: any) => {
|
|
52
|
-
const runState = getSessionRunState(s.id)
|
|
53
|
-
return {
|
|
54
|
-
id: s.id,
|
|
55
|
-
name: s.name,
|
|
56
|
-
sessionType: s.sessionType || 'human',
|
|
57
|
-
agentId: s.agentId || null,
|
|
58
|
-
provider: s.provider,
|
|
59
|
-
model: s.model,
|
|
60
|
-
parentSessionId: s.parentSessionId || null,
|
|
61
|
-
active: !!runState.runningRunId,
|
|
62
|
-
queuedCount: runState.queueLength,
|
|
63
|
-
heartbeatEnabled: s.heartbeatEnabled !== false,
|
|
64
|
-
lastActiveAt: s.lastActiveAt,
|
|
65
|
-
createdAt: s.createdAt,
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
return JSON.stringify(items)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (action === 'history') {
|
|
72
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
73
|
-
if (!targetSessionId) return 'Error: sessionId is required for history when no current session context exists.'
|
|
74
|
-
const target = sessions[targetSessionId]
|
|
75
|
-
if (!target) return `Not found: session "${targetSessionId}"`
|
|
76
|
-
const max = Math.max(1, Math.min(limit || 20, 100))
|
|
77
|
-
const history = (target.messages || []).slice(-max).map((m: any) => ({
|
|
78
|
-
role: m.role,
|
|
79
|
-
text: m.text,
|
|
80
|
-
time: m.time,
|
|
81
|
-
kind: m.kind || 'chat',
|
|
82
|
-
}))
|
|
83
|
-
return JSON.stringify({ sessionId: target.id, name: target.name, history, currentSessionDefaulted: !sessionId })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (action === 'status') {
|
|
87
|
-
if (!sessionId) return 'Error: sessionId is required for status.'
|
|
88
|
-
const target = sessions[sessionId]
|
|
89
|
-
if (!target) return `Not found: session "${sessionId}"`
|
|
90
|
-
const { getSessionRunState } = await import('../session-run-manager')
|
|
91
|
-
const run = getSessionRunState(sessionId)
|
|
92
|
-
return JSON.stringify({
|
|
93
|
-
id: target.id,
|
|
94
|
-
name: target.name,
|
|
95
|
-
runningRunId: run.runningRunId || null,
|
|
96
|
-
queuedCount: run.queueLength,
|
|
97
|
-
heartbeatEnabled: target.heartbeatEnabled !== false,
|
|
98
|
-
lastActiveAt: target.lastActiveAt,
|
|
99
|
-
messageCount: (target.messages || []).length,
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (action === 'stop') {
|
|
104
|
-
if (!sessionId) return 'Error: sessionId is required for stop.'
|
|
105
|
-
if (!sessions[sessionId]) return `Not found: session "${sessionId}"`
|
|
106
|
-
const { cancelSessionRuns } = await import('../session-run-manager')
|
|
107
|
-
const out = cancelSessionRuns(sessionId, 'Stopped by manage_sessions')
|
|
108
|
-
return JSON.stringify({ sessionId, ...out })
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (action === 'send') {
|
|
112
|
-
if (!sessionId) return 'Error: sessionId is required for send.'
|
|
113
|
-
if (!message?.trim()) return 'Error: message is required for send.'
|
|
114
|
-
if (!sessions[sessionId]) return `Not found: session "${sessionId}"`
|
|
115
|
-
if (ctx?.sessionId && sessionId === ctx.sessionId) return 'Error: cannot send to the current session itself.'
|
|
116
|
-
|
|
117
|
-
const sourceSession = ctx?.sessionId ? sessions[ctx.sessionId] : null
|
|
118
|
-
const sourceLabel = sourceSession
|
|
119
|
-
? `${sourceSession.name} (${sourceSession.id})`
|
|
120
|
-
: (ctx?.agentId ? `agent:${ctx.agentId}` : 'platform')
|
|
121
|
-
const bridgedMessage = `[Session message from ${sourceLabel}]\n${message.trim()}`
|
|
122
|
-
|
|
123
|
-
const { enqueueSessionRun } = await import('../session-run-manager')
|
|
124
|
-
const mode = queueMode === 'steer' || queueMode === 'collect' || queueMode === 'followup'
|
|
125
|
-
? queueMode
|
|
126
|
-
: 'followup'
|
|
127
|
-
const run = enqueueSessionRun({
|
|
128
|
-
sessionId,
|
|
129
|
-
message: bridgedMessage,
|
|
130
|
-
source: 'session-send',
|
|
131
|
-
internal: false,
|
|
132
|
-
mode,
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
if (waitForReply === false) {
|
|
136
|
-
return JSON.stringify({
|
|
137
|
-
sessionId,
|
|
138
|
-
runId: run.runId,
|
|
139
|
-
status: 'queued',
|
|
140
|
-
mode,
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const timeoutMs = Math.max(5, Math.min(timeoutSec || 120, 900)) * 1000
|
|
145
|
-
const result = await Promise.race([
|
|
146
|
-
run.promise,
|
|
147
|
-
new Promise<never>((_, reject) =>
|
|
148
|
-
setTimeout(() => reject(new Error(`Timed out waiting for session reply after ${Math.round(timeoutMs / 1000)}s`)), timeoutMs),
|
|
149
|
-
),
|
|
150
|
-
])
|
|
151
|
-
return JSON.stringify({
|
|
152
|
-
sessionId,
|
|
153
|
-
runId: run.runId,
|
|
154
|
-
status: result.error ? 'failed' : 'completed',
|
|
155
|
-
reply: result.text || '',
|
|
156
|
-
error: result.error || null,
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (action === 'spawn') {
|
|
161
|
-
if (!agentId) return 'Error: agentId is required for spawn.'
|
|
162
|
-
const agents = loadAgents()
|
|
163
|
-
const agent = agents[agentId]
|
|
164
|
-
if (!agent) return `Not found: agent "${agentId}"`
|
|
165
|
-
const sourceSession = ctx?.sessionId ? sessions[ctx.sessionId] : null
|
|
166
|
-
const ownerUser = sourceSession?.user || 'system'
|
|
167
|
-
|
|
168
|
-
const id = genId()
|
|
169
|
-
const now = Date.now()
|
|
170
|
-
const entry = {
|
|
171
|
-
id,
|
|
172
|
-
name: (name || `${agent.name} Session`).trim(),
|
|
173
|
-
cwd,
|
|
174
|
-
user: ownerUser,
|
|
175
|
-
provider: agent.provider || 'claude-cli',
|
|
176
|
-
model: agent.model || '',
|
|
177
|
-
credentialId: agent.credentialId || null,
|
|
178
|
-
apiEndpoint: agent.apiEndpoint || null,
|
|
179
|
-
claudeSessionId: null,
|
|
180
|
-
codexThreadId: null,
|
|
181
|
-
opencodeSessionId: null,
|
|
182
|
-
delegateResumeIds: {
|
|
183
|
-
claudeCode: null,
|
|
184
|
-
codex: null,
|
|
185
|
-
opencode: null,
|
|
186
|
-
},
|
|
187
|
-
messages: [],
|
|
188
|
-
createdAt: now,
|
|
189
|
-
lastActiveAt: now,
|
|
190
|
-
sessionType: 'orchestrated',
|
|
191
|
-
agentId: agent.id,
|
|
192
|
-
parentSessionId: ctx?.sessionId || null,
|
|
193
|
-
tools: agent.tools || [],
|
|
194
|
-
heartbeatEnabled: agent.heartbeatEnabled ?? true,
|
|
195
|
-
heartbeatIntervalSec: agent.heartbeatIntervalSec ?? null,
|
|
196
|
-
}
|
|
197
|
-
sessions[id] = entry as any
|
|
198
|
-
saveSessions(sessions)
|
|
199
|
-
|
|
200
|
-
let runId: string | null = null
|
|
201
|
-
if (message?.trim()) {
|
|
202
|
-
const { enqueueSessionRun } = await import('../session-run-manager')
|
|
203
|
-
const run = enqueueSessionRun({
|
|
204
|
-
sessionId: id,
|
|
205
|
-
message: message.trim(),
|
|
206
|
-
source: 'session-spawn',
|
|
207
|
-
internal: false,
|
|
208
|
-
mode: 'followup',
|
|
209
|
-
})
|
|
210
|
-
runId = run.runId
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return JSON.stringify({
|
|
214
|
-
sessionId: id,
|
|
215
|
-
name: entry.name,
|
|
216
|
-
agentId: agent.id,
|
|
217
|
-
queuedRunId: runId,
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (action === 'set_heartbeat') {
|
|
222
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
223
|
-
if (!targetSessionId) return 'Error: sessionId is required when no current session context exists.'
|
|
224
|
-
const target = sessions[targetSessionId]
|
|
225
|
-
if (!target) return `Not found: session "${targetSessionId}"`
|
|
226
|
-
const intervalFromMs = typeof heartbeatIntervalMs === 'number'
|
|
227
|
-
? Math.max(0, Math.round(heartbeatIntervalMs / 1000))
|
|
228
|
-
: undefined
|
|
229
|
-
const nextIntervalSecRaw = typeof heartbeatIntervalSec === 'number'
|
|
230
|
-
? heartbeatIntervalSec
|
|
231
|
-
: intervalFromMs
|
|
232
|
-
const nextIntervalSec = typeof nextIntervalSecRaw === 'number'
|
|
233
|
-
? Math.max(0, Math.min(3600, Math.round(nextIntervalSecRaw)))
|
|
234
|
-
: undefined
|
|
235
|
-
|
|
236
|
-
if (typeof heartbeatEnabled !== 'boolean' && typeof nextIntervalSec !== 'number') {
|
|
237
|
-
return 'Error: set_heartbeat requires heartbeatEnabled and/or heartbeatIntervalSec/heartbeatIntervalMs.'
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (typeof heartbeatEnabled === 'boolean') target.heartbeatEnabled = heartbeatEnabled
|
|
241
|
-
if (typeof nextIntervalSec === 'number') target.heartbeatIntervalSec = nextIntervalSec
|
|
242
|
-
target.lastActiveAt = Date.now()
|
|
243
|
-
|
|
244
|
-
let statusMessageAdded = false
|
|
245
|
-
if (target.heartbeatEnabled === false && finalStatus?.trim()) {
|
|
246
|
-
if (!Array.isArray(target.messages)) target.messages = []
|
|
247
|
-
target.messages.push({
|
|
248
|
-
role: 'assistant',
|
|
249
|
-
text: finalStatus.trim(),
|
|
250
|
-
time: Date.now(),
|
|
251
|
-
kind: 'heartbeat',
|
|
252
|
-
})
|
|
253
|
-
statusMessageAdded = true
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
saveSessions(sessions)
|
|
257
|
-
return JSON.stringify({
|
|
258
|
-
sessionId: targetSessionId,
|
|
259
|
-
heartbeatEnabled: target.heartbeatEnabled !== false,
|
|
260
|
-
heartbeatIntervalSec: target.heartbeatIntervalSec ?? null,
|
|
261
|
-
heartbeatIntervalMs: typeof target.heartbeatIntervalSec === 'number' ? target.heartbeatIntervalSec * 1000 : null,
|
|
262
|
-
statusMessageAdded,
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (action === 'mailbox_send') {
|
|
267
|
-
if (!sessionId) return 'Error: sessionId (target session) is required for mailbox_send.'
|
|
268
|
-
if (!message?.trim()) return 'Error: message is required for mailbox_send.'
|
|
269
|
-
const { sendMailboxEnvelope } = await import('../session-mailbox')
|
|
270
|
-
const envelope = sendMailboxEnvelope({
|
|
271
|
-
toSessionId: sessionId,
|
|
272
|
-
type: type?.trim() || 'message',
|
|
273
|
-
payload: message.trim(),
|
|
274
|
-
fromSessionId: ctx?.sessionId || null,
|
|
275
|
-
fromAgentId: ctx?.agentId || null,
|
|
276
|
-
correlationId: correlationId?.trim() || null,
|
|
277
|
-
ttlSec: typeof ttlSec === 'number' ? ttlSec : null,
|
|
278
|
-
})
|
|
279
|
-
return JSON.stringify({ ok: true, envelope })
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (action === 'mailbox_inbox') {
|
|
283
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
284
|
-
if (!targetSessionId) return 'Error: sessionId is required for mailbox_inbox when no current session context exists.'
|
|
285
|
-
const { listMailbox } = await import('../session-mailbox')
|
|
286
|
-
const envelopes = listMailbox(targetSessionId, { limit, includeAcked: false })
|
|
287
|
-
return JSON.stringify({
|
|
288
|
-
sessionId: targetSessionId,
|
|
289
|
-
count: envelopes.length,
|
|
290
|
-
envelopes,
|
|
291
|
-
currentSessionDefaulted: !sessionId,
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (action === 'mailbox_ack') {
|
|
296
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
297
|
-
if (!targetSessionId) return 'Error: sessionId is required for mailbox_ack when no current session context exists.'
|
|
298
|
-
if (!envelopeId?.trim()) return 'Error: envelopeId is required for mailbox_ack.'
|
|
299
|
-
const { ackMailboxEnvelope } = await import('../session-mailbox')
|
|
300
|
-
const envelope = ackMailboxEnvelope(targetSessionId, envelopeId.trim())
|
|
301
|
-
if (!envelope) return `Not found: envelope "${envelopeId.trim()}"`
|
|
302
|
-
return JSON.stringify({ ok: true, envelope })
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (action === 'mailbox_clear') {
|
|
306
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
307
|
-
if (!targetSessionId) return 'Error: sessionId is required for mailbox_clear when no current session context exists.'
|
|
308
|
-
const { clearMailbox } = await import('../session-mailbox')
|
|
309
|
-
const cleared = clearMailbox(targetSessionId, true)
|
|
310
|
-
return JSON.stringify({ ok: true, ...cleared })
|
|
311
|
-
}
|
|
28
|
+
async function executeSessionsAction(args: any, context: { sessionId?: string; agentId?: string; cwd: string }) {
|
|
29
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
30
|
+
const action = normalized.action as string | undefined
|
|
31
|
+
const sessionId = (normalized.sessionId ?? normalized.session_id) as string | undefined
|
|
32
|
+
const message = normalized.message as string | undefined
|
|
33
|
+
const limit = normalized.limit as number | undefined
|
|
34
|
+
const agentId = (normalized.agentId ?? normalized.agent_id) as string | undefined
|
|
35
|
+
const name = normalized.name as string | undefined
|
|
36
|
+
try {
|
|
37
|
+
const sessions = loadSessions()
|
|
38
|
+
if (action === 'list') {
|
|
39
|
+
return JSON.stringify(Object.values(sessions).slice(0, limit || 50).map((s: any) => ({ id: s.id, name: s.name })))
|
|
40
|
+
}
|
|
41
|
+
if (action === 'history') {
|
|
42
|
+
const target = sessions[sessionId || context.sessionId || '']
|
|
43
|
+
if (!target) return 'Not found.'
|
|
44
|
+
return JSON.stringify((target.messages || []).slice(-(limit || 20)))
|
|
45
|
+
}
|
|
46
|
+
if (action === 'spawn') {
|
|
47
|
+
if (!agentId) return 'agentId required.'
|
|
48
|
+
const agents = loadAgents()
|
|
49
|
+
const agent = agents[agentId]
|
|
50
|
+
if (!agent) return 'Agent not found.'
|
|
51
|
+
const id = genId()
|
|
52
|
+
const now = Date.now()
|
|
53
|
+
sessions[id] = {
|
|
54
|
+
id, name: (name || `${agent.name} Session`).trim(), cwd: context.cwd, user: 'system',
|
|
55
|
+
provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
|
|
56
|
+
messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
|
|
57
|
+
agentId: agent.id, parentSessionId: context.sessionId || undefined, tools: agent.tools || [],
|
|
58
|
+
}
|
|
59
|
+
saveSessions(sessions)
|
|
60
|
+
return JSON.stringify({ sessionId: id, name: agent.name })
|
|
61
|
+
}
|
|
62
|
+
return `Unknown action "${action}".`
|
|
63
|
+
} catch (err: any) { return `Error: ${err.message}` }
|
|
64
|
+
}
|
|
312
65
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Register as a Built-in Plugin
|
|
68
|
+
*/
|
|
69
|
+
const SessionInfoPlugin: Plugin = {
|
|
70
|
+
name: 'Core Session Info',
|
|
71
|
+
description: 'Identify current session context and manage other agent sessions.',
|
|
72
|
+
hooks: {} as PluginHooks,
|
|
73
|
+
tools: [
|
|
74
|
+
{
|
|
75
|
+
name: 'whoami_tool',
|
|
76
|
+
description: 'Return identity/runtime context for this agent execution.',
|
|
77
|
+
parameters: { type: 'object', properties: {} },
|
|
78
|
+
execute: async (args, context) => executeWhoAmI({ sessionId: context.session.id, agentId: context.session.agentId ?? undefined })
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'sessions_tool',
|
|
82
|
+
description: 'Manage and interact with other sessions.',
|
|
83
|
+
parameters: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
action: { type: 'string', enum: ['list', 'history', 'spawn', 'status', 'stop'] },
|
|
87
|
+
sessionId: { type: 'string' },
|
|
88
|
+
agentId: { type: 'string' },
|
|
89
|
+
message: { type: 'string' },
|
|
90
|
+
limit: { type: 'number' }
|
|
317
91
|
},
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
message: z.string().optional().describe('Message body (required for send, optional initial task for spawn)'),
|
|
325
|
-
limit: z.number().optional().describe('Max items/messages for list/history'),
|
|
326
|
-
agentId: z.string().optional().describe('Agent id to spawn (required for spawn)'),
|
|
327
|
-
name: z.string().optional().describe('Optional session name for spawn'),
|
|
328
|
-
waitForReply: z.boolean().optional().describe('For send: if false, queue and return immediately'),
|
|
329
|
-
timeoutSec: z.number().optional().describe('For send with waitForReply=true, max wait time in seconds (default 120)'),
|
|
330
|
-
queueMode: z.enum(['followup', 'steer', 'collect']).optional().describe('Queue mode for send'),
|
|
331
|
-
heartbeatEnabled: z.boolean().optional().describe('For set_heartbeat: true to enable heartbeat, false to disable'),
|
|
332
|
-
heartbeatIntervalSec: z.number().optional().describe('For set_heartbeat: optional heartbeat interval in seconds (0-3600).'),
|
|
333
|
-
heartbeatIntervalMs: z.number().optional().describe('For set_heartbeat: optional heartbeat interval in milliseconds (alias of heartbeatIntervalSec).'),
|
|
334
|
-
finalStatus: z.string().optional().describe('For set_heartbeat when disabling: optional final status update to append in the session'),
|
|
335
|
-
envelopeId: z.string().optional().describe('For mailbox_ack: envelope id to acknowledge.'),
|
|
336
|
-
type: z.string().optional().describe('For mailbox_send: protocol message type (default "message").'),
|
|
337
|
-
correlationId: z.string().optional().describe('For mailbox_send: optional request/response correlation id.'),
|
|
338
|
-
ttlSec: z.number().optional().describe('For mailbox_send: optional envelope TTL in seconds.'),
|
|
339
|
-
}),
|
|
340
|
-
},
|
|
341
|
-
),
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
tools.push(
|
|
345
|
-
tool(
|
|
346
|
-
async ({ query, sessionId, limit, dateRange }) => {
|
|
347
|
-
try {
|
|
348
|
-
const sessions = loadSessions()
|
|
349
|
-
const targetSessionId = sessionId || ctx?.sessionId || null
|
|
350
|
-
if (!targetSessionId) return 'Error: sessionId is required when no current session context exists.'
|
|
351
|
-
const target = sessions[targetSessionId]
|
|
352
|
-
if (!target) return `Not found: session "${targetSessionId}"`
|
|
353
|
-
|
|
354
|
-
const from = typeof dateRange?.from === 'number' ? dateRange.from : Number.NEGATIVE_INFINITY
|
|
355
|
-
const to = typeof dateRange?.to === 'number' ? dateRange.to : Number.POSITIVE_INFINITY
|
|
356
|
-
const max = Math.max(1, Math.min(limit || 20, 200))
|
|
357
|
-
const q = (query || '').trim().toLowerCase()
|
|
358
|
-
const terms = q ? q.split(/\s+/).filter(Boolean) : []
|
|
92
|
+
required: ['action']
|
|
93
|
+
},
|
|
94
|
+
execute: async (args, context) => executeSessionsAction(args, { sessionId: context.session.id, agentId: context.session.agentId ?? undefined, cwd: context.session.cwd || process.cwd() })
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
359
98
|
|
|
360
|
-
|
|
361
|
-
.map((m: any, idx: number) => ({ ...m, _idx: idx }))
|
|
362
|
-
.filter((m: any) => {
|
|
363
|
-
const t = typeof m.time === 'number' ? m.time : 0
|
|
364
|
-
if (t < from || t > to) return false
|
|
365
|
-
if (!terms.length) return true
|
|
366
|
-
const hay = `${m.role || ''}\n${m.kind || ''}\n${m.text || ''}`.toLowerCase()
|
|
367
|
-
return terms.every((term) => hay.includes(term))
|
|
368
|
-
})
|
|
369
|
-
.map((m: any) => {
|
|
370
|
-
const hay = `${m.text || ''}`.toLowerCase()
|
|
371
|
-
let score = 0
|
|
372
|
-
if (q && hay.includes(q)) score += 5
|
|
373
|
-
for (const term of terms) {
|
|
374
|
-
if (hay.includes(term)) score += 1
|
|
375
|
-
}
|
|
376
|
-
const ageBoost = Math.max(0, (m.time || 0) / 1e13)
|
|
377
|
-
score += ageBoost
|
|
378
|
-
return { ...m, _score: score }
|
|
379
|
-
})
|
|
380
|
-
.sort((a: any, b: any) => b._score - a._score)
|
|
381
|
-
const scored = scoredAll
|
|
382
|
-
.slice(0, max)
|
|
383
|
-
.map((m: any) => ({
|
|
384
|
-
index: m._idx,
|
|
385
|
-
role: m.role,
|
|
386
|
-
kind: m.kind || 'chat',
|
|
387
|
-
time: m.time,
|
|
388
|
-
text: typeof m.text === 'string' && m.text.length > 1200 ? `${m.text.slice(0, 1200)}...` : (m.text || ''),
|
|
389
|
-
}))
|
|
99
|
+
getPluginManager().registerBuiltin('session_info', SessionInfoPlugin)
|
|
390
100
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
{
|
|
405
|
-
name: 'search_history_tool',
|
|
406
|
-
description: 'Search message history for the current session by default, or another session if sessionId is provided. Useful for recalling prior commitments, decisions, and details.',
|
|
407
|
-
schema: z.object({
|
|
408
|
-
query: z.string().describe('Search query text (keywords, phrase, or topic).'),
|
|
409
|
-
sessionId: z.string().optional().describe('Optional target session id; defaults to current session.'),
|
|
410
|
-
limit: z.number().optional().describe('Maximum number of matches to return (default 20, max 200).'),
|
|
411
|
-
dateRange: z.object({
|
|
412
|
-
from: z.number().optional().describe('Unix epoch ms lower bound (inclusive).'),
|
|
413
|
-
to: z.number().optional().describe('Unix epoch ms upper bound (inclusive).'),
|
|
414
|
-
}).optional().describe('Optional time filter for message timestamps.'),
|
|
415
|
-
}),
|
|
416
|
-
},
|
|
417
|
-
),
|
|
101
|
+
/**
|
|
102
|
+
* Legacy Bridge
|
|
103
|
+
*/
|
|
104
|
+
export function buildSessionInfoTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
105
|
+
if (!bctx.hasTool('manage_sessions')) return []
|
|
106
|
+
return [
|
|
107
|
+
tool(
|
|
108
|
+
async () => executeWhoAmI({ sessionId: bctx.ctx?.sessionId || undefined, agentId: bctx.ctx?.agentId || undefined }),
|
|
109
|
+
{ name: 'whoami_tool', description: SessionInfoPlugin.tools![0].description, schema: z.object({}).passthrough() }
|
|
110
|
+
),
|
|
111
|
+
tool(
|
|
112
|
+
async (args) => executeSessionsAction(args, { sessionId: bctx.ctx?.sessionId || undefined, agentId: bctx.ctx?.agentId || undefined, cwd: bctx.cwd }),
|
|
113
|
+
{ name: 'sessions_tool', description: SessionInfoPlugin.tools![1].description, schema: z.object({}).passthrough() }
|
|
418
114
|
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return tools
|
|
115
|
+
]
|
|
422
116
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { normalizeShellArgs } from './shell'
|
|
4
|
+
|
|
5
|
+
describe('normalizeShellArgs', () => {
|
|
6
|
+
it('keeps explicit action + command', () => {
|
|
7
|
+
const out = normalizeShellArgs({ action: 'execute', command: 'pwd' })
|
|
8
|
+
assert.equal(out.action, 'execute')
|
|
9
|
+
assert.equal(out.command, 'pwd')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('maps top-level execute_command to execute action', () => {
|
|
13
|
+
const out = normalizeShellArgs({ execute_command: 'ls -la' })
|
|
14
|
+
assert.equal(out.action, 'execute')
|
|
15
|
+
assert.equal(out.command, 'ls -la')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('maps nested input.execute_command payload', () => {
|
|
19
|
+
const out = normalizeShellArgs({
|
|
20
|
+
input: {
|
|
21
|
+
execute_command: 'cd openclaw/site && ls -la',
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
assert.equal(out.action, 'execute')
|
|
25
|
+
assert.equal(out.command, 'cd openclaw/site && ls -la')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('maps stringified input payload', () => {
|
|
29
|
+
const out = normalizeShellArgs({
|
|
30
|
+
input: JSON.stringify({ execute_command: 'echo hello' }),
|
|
31
|
+
})
|
|
32
|
+
assert.equal(out.action, 'execute')
|
|
33
|
+
assert.equal(out.command, 'echo hello')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('maps args wrapper payload', () => {
|
|
37
|
+
const out = normalizeShellArgs({
|
|
38
|
+
args: { execute_command: 'pwd' },
|
|
39
|
+
})
|
|
40
|
+
assert.equal(out.action, 'execute')
|
|
41
|
+
assert.equal(out.command, 'pwd')
|
|
42
|
+
})
|
|
43
|
+
})
|