@swarmclawai/swarmclaw 0.7.0 → 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 +51 -6
- 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
|
@@ -4,30 +4,17 @@ import { HumanMessage, AIMessage } from '@langchain/core/messages'
|
|
|
4
4
|
import { buildSessionTools } from './session-tools'
|
|
5
5
|
import { buildChatModel } from './build-llm'
|
|
6
6
|
import { loadSettings, loadAgents, loadSkills, appendUsage } from './storage'
|
|
7
|
-
import { estimateCost } from './cost'
|
|
7
|
+
import { estimateCost, buildPluginDefinitionCosts } from './cost'
|
|
8
8
|
import { getPluginManager } from './plugins'
|
|
9
9
|
import { loadRuntimeSettings, getAgentLoopRecursionLimit } from './runtime-settings'
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import { logExecution } from './execution-log'
|
|
12
12
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
13
|
-
import {
|
|
14
|
-
import type { Session, Message, UsageRecord } from '@/types'
|
|
13
|
+
import { expandPluginIds } from './tool-aliases'
|
|
14
|
+
import type { Session, Message, UsageRecord, PluginInvocationRecord } from '@/types'
|
|
15
15
|
import { extractSuggestions } from './suggestions'
|
|
16
16
|
|
|
17
17
|
/** Extract a breadcrumb title from notable tool completions (task/schedule/agent creation). */
|
|
18
|
-
function extractBreadcrumbTitle(toolName: string, input: unknown, output: string | undefined): string | null {
|
|
19
|
-
if (!input || typeof input !== 'object') return null
|
|
20
|
-
const inp = input as Record<string, unknown>
|
|
21
|
-
const action = typeof inp.action === 'string' ? inp.action : ''
|
|
22
|
-
if (toolName === 'manage_tasks') {
|
|
23
|
-
if (action === 'create') return `Created task: ${inp.title || 'Untitled'}`
|
|
24
|
-
if (output && /status.*completed|completed.*successfully/i.test(output)) return `Completed task: ${inp.title || inp.taskId || 'unknown'}`
|
|
25
|
-
}
|
|
26
|
-
if (toolName === 'manage_schedules' && action === 'create') return `Created schedule: ${inp.name || 'Untitled'}`
|
|
27
|
-
if (toolName === 'manage_agents' && action === 'create') return `Created agent: ${inp.name || 'Untitled'}`
|
|
28
|
-
return null
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
interface StreamAgentChatOpts {
|
|
32
19
|
session: Session
|
|
33
20
|
message: string
|
|
@@ -41,41 +28,17 @@ interface StreamAgentChatOpts {
|
|
|
41
28
|
signal?: AbortSignal
|
|
42
29
|
}
|
|
43
30
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
if (enabledTools.includes('edit_file')) lines.push('- I can make precise edits to files (`edit_file`) — surgical find-and-replace without rewriting the whole file.')
|
|
52
|
-
if (enabledTools.includes('web_search')) lines.push('- I can search the web (`web_search`) for research, fact-checking, and discovery.')
|
|
53
|
-
if (enabledTools.includes('web_fetch')) lines.push('- I can fetch and read web pages (`web_fetch`) to pull in real content for analysis.')
|
|
54
|
-
if (enabledTools.includes('browser')) lines.push('- I can control a browser (`browser`) — navigate sites, fill forms, take screenshots, interact with web apps.')
|
|
55
|
-
if (enabledTools.includes('claude_code')) lines.push('- I can hand off deep coding work to Claude Code (`delegate_to_claude_code`) for complex multi-file refactors and code generation. Resume IDs may come back via `[delegate_meta]`.')
|
|
56
|
-
if (enabledTools.includes('codex_cli')) lines.push('- I can hand off deep coding work to Codex (`delegate_to_codex_cli`) for complex multi-file refactors and code generation. Resume IDs may come back via `[delegate_meta]`.')
|
|
57
|
-
if (enabledTools.includes('opencode_cli')) lines.push('- I can hand off deep coding work to OpenCode (`delegate_to_opencode_cli`) for complex multi-file refactors and code generation. Resume IDs may come back via `[delegate_meta]`.')
|
|
58
|
-
if (enabledTools.includes('memory')) lines.push('- I have long-term memory (`memory_tool`) — I can remember things across conversations and recall them when needed.')
|
|
59
|
-
if (enabledTools.includes('sandbox')) lines.push('- 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.')
|
|
60
|
-
if (enabledTools.includes('manage_agents')) lines.push('- I can create and configure other agents (`manage_agents`) — spin up specialists when a task calls for it.')
|
|
61
|
-
if (enabledTools.includes('manage_tasks')) lines.push('- I can manage tasks (`manage_tasks`) — create plans, track progress, and stay organized over time.')
|
|
62
|
-
if (enabledTools.includes('manage_schedules')) lines.push('- I can set up schedules (`manage_schedules`) for recurring work or future follow-ups.')
|
|
63
|
-
if (enabledTools.includes('schedule_wake')) lines.push('- I can set a conversational timer (`schedule_wake`) to remind myself to check back on something later in this chat.')
|
|
64
|
-
if (enabledTools.includes('manage_documents')) lines.push('- I can store and search documents (`manage_documents`) for long-term knowledge and reference.')
|
|
65
|
-
if (enabledTools.includes('manage_webhooks')) lines.push('- I can register webhooks (`manage_webhooks`) so external events can trigger my work automatically.')
|
|
66
|
-
if (enabledTools.includes('manage_skills')) lines.push('- I can manage reusable skills (`manage_skills`) — building blocks I can learn and apply.')
|
|
67
|
-
if (enabledTools.includes('manage_connectors')) lines.push('- I can manage messaging channels (`manage_connectors`) — WhatsApp, Telegram, Slack, Discord — and send proactive messages via `connector_message_tool`.')
|
|
68
|
-
if (enabledTools.includes('manage_sessions')) lines.push('- 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.')
|
|
69
|
-
// Context tools are available to any session with tools (not just manage_sessions)
|
|
70
|
-
if (enabledTools.length > 0) {
|
|
31
|
+
function buildPluginCapabilityLines(enabledPlugins: string[], opts?: { platformAssignScope?: 'self' | 'all' }): string[] {
|
|
32
|
+
// Collect capability descriptions dynamically from plugins
|
|
33
|
+
const lines = getPluginManager().collectCapabilityDescriptions(enabledPlugins)
|
|
34
|
+
|
|
35
|
+
// Context tools are available to any session with plugins
|
|
36
|
+
if (enabledPlugins.length > 0) {
|
|
71
37
|
lines.push('- I can monitor my own context usage (`context_status`) and compact my conversation history (`context_summarize`) when I\'m running low on space.')
|
|
72
38
|
if (opts?.platformAssignScope === 'all') {
|
|
73
39
|
lines.push('- I can delegate tasks to other agents (`delegate_to_agent`) based on their strengths and availability.')
|
|
74
40
|
}
|
|
75
41
|
}
|
|
76
|
-
if (enabledTools.includes('manage_secrets')) lines.push('- I can store and retrieve encrypted secrets (`manage_secrets`) — API keys, credentials, tokens.')
|
|
77
|
-
if (enabledTools.includes('manage_chatrooms')) lines.push('- I can create and participate in chatrooms (`manage_chatrooms`) for multi-agent collaboration with @mention-based discussions.')
|
|
78
|
-
if (enabledTools.includes('wallet')) lines.push('- I have my own crypto wallet (`wallet_tool`) — I can check my balance, send SOL, and review my transaction history.')
|
|
79
42
|
return lines
|
|
80
43
|
}
|
|
81
44
|
|
|
@@ -102,7 +65,7 @@ const GOAL_DECOMPOSITION_BLOCK = [
|
|
|
102
65
|
].join('\n')
|
|
103
66
|
|
|
104
67
|
function buildAgenticExecutionPolicy(opts: {
|
|
105
|
-
|
|
68
|
+
enabledPlugins: string[]
|
|
106
69
|
loopMode: 'bounded' | 'ongoing'
|
|
107
70
|
heartbeatPrompt: string
|
|
108
71
|
heartbeatIntervalSec: number
|
|
@@ -110,10 +73,8 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
110
73
|
userMessage?: string
|
|
111
74
|
hasExistingPlan?: boolean
|
|
112
75
|
}) {
|
|
113
|
-
const hasTooling = opts.
|
|
114
|
-
const
|
|
115
|
-
const has = (t: string) => opts.enabledTools.includes(t)
|
|
116
|
-
const hasDelegationTool = has('claude_code') || has('codex_cli') || has('opencode_cli')
|
|
76
|
+
const hasTooling = opts.enabledPlugins.length > 0
|
|
77
|
+
const pluginLines = buildPluginCapabilityLines(opts.enabledPlugins, { platformAssignScope: opts.platformAssignScope })
|
|
117
78
|
|
|
118
79
|
const parts: string[] = []
|
|
119
80
|
|
|
@@ -130,30 +91,9 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
130
91
|
: 'Loop: BOUNDED — execute multiple steps but finish within recursion budget.',
|
|
131
92
|
)
|
|
132
93
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
'Shell: use `execute_command` for servers, installs, scripts, git. Use `background=true` for long-lived processes.',
|
|
137
|
-
'Verify servers with `process_tool` status/log and liveness probes before claiming success.',
|
|
138
|
-
'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.',
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
if (hasDelegationTool) {
|
|
142
|
-
parts.push(
|
|
143
|
-
'CRITICAL: `execute_command` (not delegation) for running servers, installs, scripts. Delegation sessions end and kill processes.',
|
|
144
|
-
'Delegate only for deep multi-file code work: refactors, debugging, generation, test suites.',
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
if (has('memory')) {
|
|
148
|
-
parts.push(
|
|
149
|
-
'Memory: search before major tasks, store concise notes after meaningful steps. Platform preloads context each turn.',
|
|
150
|
-
'For open goals, form a hypothesis and execute — do not keep re-asking broad questions.',
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
if (has('manage_tasks')) parts.push('Create/update tasks for long-lived goals to track progress.')
|
|
154
|
-
if (has('manage_schedules')) parts.push('Use schedules for follow-ups. Check existing schedules before creating new ones.')
|
|
155
|
-
if (has('manage_connectors')) parts.push('Connectors: proactive outreach for significant events only. Keep messages concise, no duplicates.')
|
|
156
|
-
if (has('manage_sessions')) parts.push('Inspect existing chats before creating duplicates.')
|
|
94
|
+
// Plugin-specific operating guidance (collected dynamically from plugins)
|
|
95
|
+
const guidanceLines = getPluginManager().collectOperatingGuidance(opts.enabledPlugins)
|
|
96
|
+
if (guidanceLines.length) parts.push(...guidanceLines)
|
|
157
97
|
|
|
158
98
|
// Response behavior
|
|
159
99
|
parts.push(
|
|
@@ -167,7 +107,7 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
167
107
|
'For SWARM_MAIN_MISSION_TICK / SWARM_MAIN_AUTO_FOLLOWUP messages, follow the response contract and include [MAIN_LOOP_META] JSON.',
|
|
168
108
|
)
|
|
169
109
|
|
|
170
|
-
if (
|
|
110
|
+
if (pluginLines.length) parts.push('What I can do:\n' + pluginLines.join('\n'))
|
|
171
111
|
if (opts.userMessage && !opts.hasExistingPlan && isBroadGoal(opts.userMessage)) parts.push(GOAL_DECOMPOSITION_BLOCK)
|
|
172
112
|
|
|
173
113
|
return parts.filter(Boolean).join('\n')
|
|
@@ -184,10 +124,10 @@ export interface StreamAgentChatResult {
|
|
|
184
124
|
export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
|
|
185
125
|
const startTs = Date.now()
|
|
186
126
|
const { session, message, imagePath, attachedFiles, apiKey, systemPrompt, write, history, fallbackCredentialIds, signal } = opts
|
|
187
|
-
const
|
|
188
|
-
const hasShellCapability =
|
|
189
|
-
const
|
|
190
|
-
...
|
|
127
|
+
const rawPlugins = Array.isArray(session.plugins) ? session.plugins : []
|
|
128
|
+
const hasShellCapability = rawPlugins.some((toolId) => ['shell', 'execute_command'].includes(String(toolId)))
|
|
129
|
+
const sessionPlugins = expandPluginIds([
|
|
130
|
+
...rawPlugins,
|
|
191
131
|
...(hasShellCapability ? ['process'] : []),
|
|
192
132
|
])
|
|
193
133
|
|
|
@@ -279,109 +219,8 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
279
219
|
stateModifierParts.push(`## Reasoning Depth\n${thinkingGuidance[agentThinkingLevel]}`)
|
|
280
220
|
}
|
|
281
221
|
|
|
282
|
-
if ((session.tools || []).includes('memory') && session.agentId) {
|
|
283
|
-
try {
|
|
284
|
-
const memDb = getMemoryDb()
|
|
285
|
-
const memoryQuerySeed = [
|
|
286
|
-
message,
|
|
287
|
-
...history
|
|
288
|
-
.slice(-4)
|
|
289
|
-
.filter((h) => h.role === 'user')
|
|
290
|
-
.map((h) => h.text),
|
|
291
|
-
].join('\n')
|
|
292
|
-
|
|
293
|
-
const seen = new Set<string>()
|
|
294
|
-
const formatMemoryLine = (m: { category?: string; title?: string; content?: string; pinned?: boolean }) => {
|
|
295
|
-
const category = String(m.category || 'note')
|
|
296
|
-
const title = String(m.title || 'Untitled').replace(/\s+/g, ' ').trim()
|
|
297
|
-
const snippet = String(m.content || '').replace(/\s+/g, ' ').trim().slice(0, 220)
|
|
298
|
-
const pin = m.pinned ? ' [pinned]' : ''
|
|
299
|
-
return `- [${category}]${pin} ${title}: ${snippet}`
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Pinned memories always appear first
|
|
303
|
-
const pinned = memDb.listPinned(session.agentId, 5)
|
|
304
|
-
const pinnedLines = pinned
|
|
305
|
-
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
306
|
-
.map(formatMemoryLine)
|
|
307
|
-
|
|
308
|
-
// Reduce relevant slice by pinned count to keep total context bounded
|
|
309
|
-
const relevantSlice = Math.max(2, 6 - pinnedLines.length)
|
|
310
|
-
const relevantLookup = memDb.searchWithLinked(memoryQuerySeed, session.agentId, 1, 10, 14)
|
|
311
|
-
const relevant = relevantLookup.entries.slice(0, relevantSlice)
|
|
312
|
-
const recent = memDb.list(session.agentId, 12).slice(0, 6)
|
|
313
|
-
|
|
314
|
-
const relevantLines = relevant
|
|
315
|
-
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
316
|
-
.map(formatMemoryLine)
|
|
317
|
-
|
|
318
|
-
const recentLines = recent
|
|
319
|
-
.filter((m) => { if (!m?.id || seen.has(m.id)) return false; seen.add(m.id); return true })
|
|
320
|
-
.map(formatMemoryLine)
|
|
321
|
-
|
|
322
|
-
const memorySections: string[] = []
|
|
323
|
-
if (pinnedLines.length) {
|
|
324
|
-
memorySections.push(
|
|
325
|
-
['## Pinned Memories', 'Always-loaded memories marked as important.', ...pinnedLines].join('\n'),
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
if (relevantLines.length) {
|
|
329
|
-
memorySections.push(
|
|
330
|
-
['## Relevant Memory Hits', 'These memories were retrieved by relevance for the current objective.', ...relevantLines].join('\n'),
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
if (recentLines.length) {
|
|
334
|
-
memorySections.push(
|
|
335
|
-
['## Recent Memory Notes', 'Recent durable notes that may still apply.', ...recentLines].join('\n'),
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (memorySections.length) {
|
|
340
|
-
stateModifierParts.push(memorySections.join('\n\n'))
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Memory Policy — always injected when memory tool is available
|
|
344
|
-
stateModifierParts.push([
|
|
345
|
-
'## My Memory',
|
|
346
|
-
'I have long-term memory that persists across conversations. I use it naturally — I don\'t wait to be asked to remember things.',
|
|
347
|
-
'',
|
|
348
|
-
'**Things worth remembering:**',
|
|
349
|
-
'- What the user likes, dislikes, or has corrected me on',
|
|
350
|
-
'- Important decisions, outcomes, and lessons learned',
|
|
351
|
-
'- What I\'ve discovered about projects, codebases, or environments',
|
|
352
|
-
'- Problems I\'ve hit and how I solved them',
|
|
353
|
-
'- Who people are and how they relate to each other',
|
|
354
|
-
'- Configuration details and environment specifics that I\'ll need again',
|
|
355
|
-
'',
|
|
356
|
-
'**Not worth cluttering my memory with:**',
|
|
357
|
-
'- Throwaway acknowledgments or small talk',
|
|
358
|
-
'- Work-in-progress that\'ll change soon (use category "working" for scratch notes)',
|
|
359
|
-
'- Things already in my system prompt',
|
|
360
|
-
'- Something I\'ve already stored',
|
|
361
|
-
'',
|
|
362
|
-
'**Good habits:**',
|
|
363
|
-
'- Give memories clear titles ("User prefers dark mode" not "Note 1")',
|
|
364
|
-
'- Use categories: preference, fact, learning, project, identity, decision',
|
|
365
|
-
'- Check what I already know before storing something new',
|
|
366
|
-
'- When I learn something that corrects old knowledge, update or remove the old memory',
|
|
367
|
-
].join('\n'))
|
|
368
|
-
|
|
369
|
-
// Pre-compaction memory flush: nudge agent to save important context before it's lost
|
|
370
|
-
const msgCount = history.filter(m => m.role === 'user' || m.role === 'assistant').length
|
|
371
|
-
if (msgCount > 20) {
|
|
372
|
-
stateModifierParts.push([
|
|
373
|
-
'## Reflection & Consolidation Reminder',
|
|
374
|
-
'This conversation is getting long and I might lose older context soon.',
|
|
375
|
-
'Save anything important I\'ve learned, decided, or discovered to memory now. Only what matters, not every detail.',
|
|
376
|
-
].join('\n'))
|
|
377
|
-
}
|
|
378
|
-
} catch {
|
|
379
|
-
// If memory context fails to load, continue without blocking the run.
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
222
|
// Inject agent awareness (Phase 2: agents know about each other)
|
|
384
|
-
if ((session.
|
|
223
|
+
if ((session.plugins || []).length > 0 && session.agentId) {
|
|
385
224
|
try {
|
|
386
225
|
const { buildAgentAwarenessBlock } = await import('./agent-registry')
|
|
387
226
|
const awarenessBlock = buildAgentAwarenessBlock(session.agentId)
|
|
@@ -391,39 +230,17 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
391
230
|
}
|
|
392
231
|
}
|
|
393
232
|
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (wallet) {
|
|
401
|
-
const { getBalance, lamportsToSol } = await import('./solana')
|
|
402
|
-
let balanceSol = 0
|
|
403
|
-
try {
|
|
404
|
-
const lamports = await getBalance(wallet.publicKey)
|
|
405
|
-
balanceSol = lamportsToSol(lamports)
|
|
406
|
-
} catch { /* RPC failure — show 0 */ }
|
|
407
|
-
const perTxLimit = lamportsToSol(wallet.spendingLimitLamports ?? 100_000_000)
|
|
408
|
-
const dailyLimit = lamportsToSol(wallet.dailyLimitLamports ?? 1_000_000_000)
|
|
409
|
-
stateModifierParts.push([
|
|
410
|
-
'## Your Wallet',
|
|
411
|
-
`You own a ${wallet.chain} wallet — this is your wallet, your funds, your address. Speak about it in the first person ("my wallet", "my balance", "I have X SOL"). You can spend from it freely within your limits${wallet.requireApproval ? ', though the user needs to approve each send before it goes through' : ''}.`,
|
|
412
|
-
`- Address: ${wallet.publicKey}`,
|
|
413
|
-
`- Balance: ${balanceSol} SOL`,
|
|
414
|
-
`- Per-transaction limit: ${perTxLimit} SOL`,
|
|
415
|
-
`- Daily limit: ${dailyLimit} SOL`,
|
|
416
|
-
'Use the `wallet_tool` to check your balance, send SOL, or view your transaction history.',
|
|
417
|
-
].join('\n'))
|
|
418
|
-
}
|
|
419
|
-
} catch {
|
|
420
|
-
// Wallet context is non-critical
|
|
421
|
-
}
|
|
233
|
+
// Collect dynamic context from enabled plugins (wallet, memory, etc.)
|
|
234
|
+
try {
|
|
235
|
+
const pluginContextParts = await getPluginManager().collectAgentContext(session, sessionPlugins, message, history)
|
|
236
|
+
stateModifierParts.push(...pluginContextParts)
|
|
237
|
+
} catch {
|
|
238
|
+
// Plugin context injection is non-critical
|
|
422
239
|
}
|
|
423
240
|
|
|
424
241
|
// Tell the LLM about available plugins and their access status
|
|
425
242
|
{
|
|
426
|
-
const agentEnabledSet = new Set(
|
|
243
|
+
const agentEnabledSet = new Set(sessionPlugins)
|
|
427
244
|
const { getPluginManager } = await import('./plugins')
|
|
428
245
|
const allPlugins = getPluginManager().listPlugins()
|
|
429
246
|
const mcpDisabled = agentMcpDisabledTools ?? []
|
|
@@ -478,7 +295,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
478
295
|
|
|
479
296
|
stateModifierParts.push(
|
|
480
297
|
buildAgenticExecutionPolicy({
|
|
481
|
-
|
|
298
|
+
enabledPlugins: sessionPlugins,
|
|
482
299
|
loopMode: runtime.loopMode,
|
|
483
300
|
heartbeatPrompt,
|
|
484
301
|
heartbeatIntervalSec,
|
|
@@ -490,7 +307,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
490
307
|
|
|
491
308
|
let stateModifier = stateModifierParts.join('\n\n')
|
|
492
309
|
|
|
493
|
-
const { tools, cleanup } = await buildSessionTools(session.cwd,
|
|
310
|
+
const { tools, cleanup, toolToPluginMap } = await buildSessionTools(session.cwd, sessionPlugins, {
|
|
494
311
|
agentId: session.agentId,
|
|
495
312
|
sessionId: session.id,
|
|
496
313
|
platformAssignScope: agentPlatformAssignScope,
|
|
@@ -582,12 +399,27 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
582
399
|
return parts
|
|
583
400
|
}
|
|
584
401
|
|
|
585
|
-
//
|
|
586
|
-
let
|
|
402
|
+
// Apply context-clear boundary: slice from most recent context-clear marker
|
|
403
|
+
let contextStart = 0
|
|
404
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
405
|
+
if (history[i].kind === 'context-clear') {
|
|
406
|
+
contextStart = i + 1
|
|
407
|
+
break
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const postClearHistory = history.slice(contextStart)
|
|
411
|
+
|
|
412
|
+
// Hard cap: only send the most recent 30 messages to the LLM
|
|
413
|
+
const recentHistory = postClearHistory.slice(-30)
|
|
414
|
+
|
|
415
|
+
// Auto-compaction: only trigger if the messages we'll actually send exceed context limits.
|
|
416
|
+
// The .slice(-30) hard cap already prevents context overflow for long conversations,
|
|
417
|
+
// so this only fires for sessions with very large individual messages.
|
|
418
|
+
let effectiveHistory = recentHistory
|
|
587
419
|
try {
|
|
588
420
|
const { shouldAutoCompact, llmCompact, estimateTokens } = await import('./context-manager')
|
|
589
421
|
const systemPromptTokens = estimateTokens(stateModifier)
|
|
590
|
-
if (shouldAutoCompact(
|
|
422
|
+
if (shouldAutoCompact(recentHistory, systemPromptTokens, session.provider, session.model)) {
|
|
591
423
|
const summarize = async (prompt: string): Promise<string> => {
|
|
592
424
|
const response = await llm.invoke([new HumanMessage(prompt)])
|
|
593
425
|
if (typeof response.content === 'string') return response.content
|
|
@@ -599,7 +431,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
599
431
|
return ''
|
|
600
432
|
}
|
|
601
433
|
const result = await llmCompact({
|
|
602
|
-
messages:
|
|
434
|
+
messages: recentHistory,
|
|
603
435
|
provider: session.provider,
|
|
604
436
|
model: session.model,
|
|
605
437
|
agentId: session.agentId || null,
|
|
@@ -608,12 +440,12 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
608
440
|
})
|
|
609
441
|
effectiveHistory = result.messages
|
|
610
442
|
console.log(
|
|
611
|
-
`[stream-agent-chat] Auto-compacted ${session.id}: ${
|
|
443
|
+
`[stream-agent-chat] Auto-compacted ${session.id}: ${recentHistory.length} → ${effectiveHistory.length} msgs` +
|
|
612
444
|
(result.summaryAdded ? ' (LLM summary)' : ' (sliding window fallback)'),
|
|
613
445
|
)
|
|
614
446
|
}
|
|
615
447
|
} catch {
|
|
616
|
-
// Context manager failure — continue with
|
|
448
|
+
// Context manager failure — continue with recent history
|
|
617
449
|
}
|
|
618
450
|
|
|
619
451
|
// Context degradation warning: prepend warning to system prompt when nearing limits
|
|
@@ -629,18 +461,8 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
629
461
|
// Warning failure is non-critical
|
|
630
462
|
}
|
|
631
463
|
|
|
632
|
-
// Apply context-clear boundary: slice from most recent context-clear marker
|
|
633
|
-
let contextStart = 0
|
|
634
|
-
for (let i = effectiveHistory.length - 1; i >= 0; i--) {
|
|
635
|
-
if (effectiveHistory[i].kind === 'context-clear') {
|
|
636
|
-
contextStart = i + 1
|
|
637
|
-
break
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
const postClearHistory = effectiveHistory.slice(contextStart)
|
|
641
|
-
|
|
642
464
|
const langchainMessages: Array<HumanMessage | AIMessage> = []
|
|
643
|
-
for (const m of
|
|
465
|
+
for (const m of effectiveHistory) {
|
|
644
466
|
if (m.role === 'user') {
|
|
645
467
|
langchainMessages.push(new HumanMessage({ content: await buildLangChainContent(m.text, m.imagePath, m.attachedFiles) }))
|
|
646
468
|
} else {
|
|
@@ -660,6 +482,8 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
660
482
|
let totalOutputTokens = 0
|
|
661
483
|
let lastToolInput: unknown = null
|
|
662
484
|
let accumulatedThinking = ''
|
|
485
|
+
const pluginInvocations: PluginInvocationRecord[] = []
|
|
486
|
+
let currentToolInputTokens = 0
|
|
663
487
|
|
|
664
488
|
// Plugin hooks: beforeAgentStart
|
|
665
489
|
const pluginMgr = getPluginManager()
|
|
@@ -763,9 +587,11 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
763
587
|
const toolName = event.name || 'unknown'
|
|
764
588
|
const input = event.data?.input
|
|
765
589
|
lastToolInput = input
|
|
590
|
+
// Estimate input tokens for plugin invocation tracking
|
|
591
|
+
const inputStr = typeof input === 'string' ? input : JSON.stringify(input)
|
|
592
|
+
currentToolInputTokens = Math.ceil((inputStr?.length || 0) / 4)
|
|
766
593
|
// Plugin hooks: beforeToolExec
|
|
767
594
|
await pluginMgr.runHook('beforeToolExec', { toolName, input })
|
|
768
|
-
const inputStr = typeof input === 'string' ? input : JSON.stringify(input)
|
|
769
595
|
logExecution(session.id, 'tool_call', `${toolName} invoked`, {
|
|
770
596
|
agentId: session.agentId,
|
|
771
597
|
detail: { toolName, input: inputStr?.slice(0, 4000) },
|
|
@@ -784,23 +610,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
784
610
|
? String(output.content)
|
|
785
611
|
: JSON.stringify(output)
|
|
786
612
|
// Plugin hooks: afterToolExec
|
|
787
|
-
await pluginMgr.runHook('afterToolExec', { toolName, input: null, output: outputStr })
|
|
788
|
-
// Event-driven memory breadcrumbs
|
|
789
|
-
if (session.agentId && (session.tools || []).includes('memory')) {
|
|
790
|
-
try {
|
|
791
|
-
const breadcrumbTitle = extractBreadcrumbTitle(toolName, lastToolInput, outputStr)
|
|
792
|
-
if (breadcrumbTitle) {
|
|
793
|
-
const memDb = getMemoryDb()
|
|
794
|
-
memDb.add({
|
|
795
|
-
agentId: session.agentId,
|
|
796
|
-
sessionId: session.id,
|
|
797
|
-
category: 'breadcrumb',
|
|
798
|
-
title: breadcrumbTitle,
|
|
799
|
-
content: '',
|
|
800
|
-
})
|
|
801
|
-
}
|
|
802
|
-
} catch { /* breadcrumbs are best-effort */ }
|
|
803
|
-
}
|
|
613
|
+
await pluginMgr.runHook('afterToolExec', { session, toolName, input: lastToolInput as Record<string, unknown> | null, output: outputStr })
|
|
804
614
|
lastToolInput = null
|
|
805
615
|
logExecution(session.id, 'tool_result', `${toolName} returned`, {
|
|
806
616
|
agentId: session.agentId,
|
|
@@ -825,6 +635,16 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
825
635
|
})
|
|
826
636
|
}
|
|
827
637
|
}
|
|
638
|
+
// Track plugin invocation token estimates
|
|
639
|
+
const pluginId = toolToPluginMap[toolName] || '_unknown'
|
|
640
|
+
pluginInvocations.push({
|
|
641
|
+
pluginId,
|
|
642
|
+
toolName,
|
|
643
|
+
inputTokens: currentToolInputTokens,
|
|
644
|
+
outputTokens: Math.ceil((outputStr?.length || 0) / 4),
|
|
645
|
+
})
|
|
646
|
+
currentToolInputTokens = 0
|
|
647
|
+
|
|
828
648
|
write(`data: ${JSON.stringify({
|
|
829
649
|
t: 'tool_result',
|
|
830
650
|
toolName,
|
|
@@ -931,6 +751,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
931
751
|
const totalTokens = totalInputTokens + totalOutputTokens
|
|
932
752
|
if (totalTokens > 0) {
|
|
933
753
|
const cost = estimateCost(session.model, totalInputTokens, totalOutputTokens)
|
|
754
|
+
const pluginDefinitionCosts = buildPluginDefinitionCosts(tools, toolToPluginMap)
|
|
934
755
|
const usageRecord: UsageRecord = {
|
|
935
756
|
sessionId: session.id,
|
|
936
757
|
messageIndex: history.length,
|
|
@@ -942,6 +763,8 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
942
763
|
estimatedCost: cost,
|
|
943
764
|
timestamp: Date.now(),
|
|
944
765
|
durationMs: Date.now() - startTs,
|
|
766
|
+
pluginDefinitionCosts,
|
|
767
|
+
pluginInvocations: pluginInvocations.length > 0 ? pluginInvocations : undefined,
|
|
945
768
|
}
|
|
946
769
|
appendUsage(session.id, usageRecord)
|
|
947
770
|
// Send usage metadata to client
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const PLUGIN_ALIAS_GROUPS: string[][] = [
|
|
2
2
|
['shell', 'execute_command', 'process_tool', 'process'],
|
|
3
3
|
['files', 'read_file', 'write_file', 'list_files', 'copy_file', 'move_file', 'delete_file', 'send_file'],
|
|
4
4
|
['edit_file'],
|
|
5
5
|
['web', 'web_search', 'web_fetch'],
|
|
6
6
|
['browser', 'openclaw_browser'],
|
|
7
|
-
['delegate', 'claude_code', 'codex_cli', 'opencode_cli', 'delegate_to_claude_code', 'delegate_to_codex_cli', 'delegate_to_opencode_cli'],
|
|
7
|
+
['delegate', 'claude_code', 'codex_cli', 'opencode_cli', 'gemini_cli', 'delegate_to_claude_code', 'delegate_to_codex_cli', 'delegate_to_opencode_cli', 'delegate_to_gemini_cli'],
|
|
8
8
|
['manage_platform', 'manage_agents', 'manage_tasks', 'manage_schedules', 'manage_skills', 'manage_documents', 'manage_webhooks', 'manage_secrets', 'manage_sessions'],
|
|
9
9
|
['manage_connectors', 'connectors', 'connector_message_tool'],
|
|
10
10
|
['manage_chatrooms', 'chatroom'],
|
|
@@ -20,37 +20,41 @@ const TOOL_ALIAS_GROUPS: string[][] = [
|
|
|
20
20
|
['context_mgmt', 'context_status', 'context_summarize'],
|
|
21
21
|
['openclaw_workspace'],
|
|
22
22
|
['openclaw_nodes'],
|
|
23
|
+
['image_gen', 'generate_image'],
|
|
24
|
+
['email', 'send_email'],
|
|
25
|
+
['calendar', 'calendar_events'],
|
|
26
|
+
['replicate', 'replicate_run', 'replicate_models'],
|
|
23
27
|
]
|
|
24
28
|
|
|
25
|
-
const
|
|
29
|
+
const PLUGIN_ALIAS_MAP = (() => {
|
|
26
30
|
const map = new Map<string, Set<string>>()
|
|
27
|
-
for (const group of
|
|
28
|
-
const normalized = group.map((
|
|
29
|
-
for (const
|
|
30
|
-
const current = map.get(
|
|
31
|
+
for (const group of PLUGIN_ALIAS_GROUPS) {
|
|
32
|
+
const normalized = group.map((id) => id.trim().toLowerCase()).filter(Boolean)
|
|
33
|
+
for (const id of normalized) {
|
|
34
|
+
const current = map.get(id) || new Set<string>()
|
|
31
35
|
for (const alias of normalized) current.add(alias)
|
|
32
|
-
map.set(
|
|
36
|
+
map.set(id, current)
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
return map
|
|
36
40
|
})()
|
|
37
41
|
|
|
38
|
-
export function
|
|
42
|
+
export function normalizePluginId(value: unknown): string {
|
|
39
43
|
return typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export function
|
|
46
|
+
export function expandPluginIds(values: string[] | null | undefined): string[] {
|
|
43
47
|
if (!Array.isArray(values) || values.length === 0) return []
|
|
44
48
|
const expanded = new Set<string>()
|
|
45
49
|
const queue: string[] = values
|
|
46
|
-
.map((
|
|
50
|
+
.map((id) => normalizePluginId(id))
|
|
47
51
|
.filter(Boolean)
|
|
48
52
|
|
|
49
53
|
while (queue.length > 0) {
|
|
50
54
|
const next = queue.shift()!
|
|
51
55
|
if (expanded.has(next)) continue
|
|
52
56
|
expanded.add(next)
|
|
53
|
-
const aliases =
|
|
57
|
+
const aliases = PLUGIN_ALIAS_MAP.get(next)
|
|
54
58
|
if (!aliases) continue
|
|
55
59
|
for (const alias of aliases) {
|
|
56
60
|
if (!expanded.has(alias)) queue.push(alias)
|
|
@@ -60,9 +64,16 @@ export function expandToolIds(values: string[] | null | undefined): string[] {
|
|
|
60
64
|
return Array.from(expanded)
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
export function
|
|
64
|
-
const normalized =
|
|
67
|
+
export function pluginIdMatches(enabledPlugins: string[] | null | undefined, pluginId: string): boolean {
|
|
68
|
+
const normalized = normalizePluginId(pluginId)
|
|
65
69
|
if (!normalized) return false
|
|
66
|
-
return
|
|
70
|
+
return expandPluginIds(enabledPlugins).includes(normalized)
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
/** @deprecated Use normalizePluginId */
|
|
74
|
+
export const normalizeToolId = normalizePluginId
|
|
75
|
+
/** @deprecated Use expandPluginIds */
|
|
76
|
+
export const expandToolIds = expandPluginIds
|
|
77
|
+
/** @deprecated Use pluginIdMatches */
|
|
78
|
+
export const toolIdMatches = pluginIdMatches
|
|
79
|
+
|
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
|
|
8
8
|
test('capability policy permissive mode allows non-blocked tools', () => {
|
|
9
9
|
const decision = resolveSessionToolPolicy(['shell', 'web_search'], { capabilityPolicyMode: 'permissive' })
|
|
10
|
-
assert.deepEqual(decision.
|
|
11
|
-
assert.equal(decision.
|
|
10
|
+
assert.deepEqual(decision.enabledPlugins, ['shell', 'web_search'])
|
|
11
|
+
assert.equal(decision.blockedPlugins.length, 0)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('capability policy balanced mode blocks destructive delete_file', () => {
|
|
15
15
|
const decision = resolveSessionToolPolicy(['files', 'delete_file'], { capabilityPolicyMode: 'balanced' })
|
|
16
|
-
assert.deepEqual(decision.
|
|
17
|
-
assert.equal(decision.
|
|
18
|
-
assert.equal(decision.
|
|
16
|
+
assert.deepEqual(decision.enabledPlugins, ['files'])
|
|
17
|
+
assert.equal(decision.blockedPlugins.length, 1)
|
|
18
|
+
assert.equal(decision.blockedPlugins[0].tool, 'delete_file')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
test('capability policy strict mode blocks execution/platform families', () => {
|
|
@@ -23,9 +23,9 @@ test('capability policy strict mode blocks execution/platform families', () => {
|
|
|
23
23
|
['shell', 'manage_tasks', 'web_search', 'memory'],
|
|
24
24
|
{ capabilityPolicyMode: 'strict' },
|
|
25
25
|
)
|
|
26
|
-
assert.deepEqual(decision.
|
|
27
|
-
assert.equal(decision.
|
|
28
|
-
assert.equal(decision.
|
|
26
|
+
assert.deepEqual(decision.enabledPlugins, ['web_search', 'memory'])
|
|
27
|
+
assert.equal(decision.blockedPlugins.some((entry) => entry.tool === 'shell'), true)
|
|
28
|
+
assert.equal(decision.blockedPlugins.some((entry) => entry.tool === 'manage_tasks'), true)
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
test('capability policy respects explicit allow overrides', () => {
|
|
@@ -36,7 +36,7 @@ test('capability policy respects explicit allow overrides', () => {
|
|
|
36
36
|
capabilityAllowedTools: ['shell'],
|
|
37
37
|
},
|
|
38
38
|
)
|
|
39
|
-
assert.deepEqual(decision.
|
|
39
|
+
assert.deepEqual(decision.enabledPlugins, ['shell', 'web_search'])
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
test('concrete tool checks inherit blocked family rules', () => {
|