@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
|
@@ -1,255 +1,170 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { tool
|
|
2
|
+
import { tool } from '@langchain/core/tools'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import { genId } from '@/lib/id'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
filterMemoriesByScope,
|
|
7
|
+
getMemoryDb,
|
|
8
|
+
getMemoryLookupLimits,
|
|
9
|
+
normalizeMemoryScopeMode,
|
|
10
|
+
storeMemoryImageAsset,
|
|
11
|
+
} from '../memory-db'
|
|
6
12
|
import { loadSettings } from '../storage'
|
|
13
|
+
import { expandQuery } from '../query-expansion'
|
|
14
|
+
import type { MemoryEntry, Plugin, PluginHooks } from '@/types'
|
|
7
15
|
import type { ToolBuildContext } from './context'
|
|
16
|
+
import { getPluginManager } from '../plugins'
|
|
17
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Advanced Database-Backed Memory logic.
|
|
21
|
+
*/
|
|
22
|
+
async function executeMemoryAction(input: any, ctx: any) {
|
|
23
|
+
const normalized = normalizeToolInputArgs((input ?? {}) as Record<string, unknown>)
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
const n = normalized as Record<string, any>
|
|
26
|
+
const {
|
|
27
|
+
action, key, value, query, scope, rerank,
|
|
28
|
+
scopeSessionId, projectRoot, filePaths, references, project,
|
|
29
|
+
linkedMemoryIds, depth, linkedLimit, targetIds,
|
|
30
|
+
tags, pinned, sharedWith
|
|
31
|
+
} = n
|
|
32
|
+
const category = typeof n.category === 'string' ? n.category : 'note'
|
|
33
|
+
const imagePath = typeof n.imagePath === 'string' ? n.imagePath : undefined
|
|
34
|
+
|
|
35
|
+
const memDb = getMemoryDb()
|
|
36
|
+
const currentAgentId = ctx?.agentId || null
|
|
37
|
+
const rawScope = typeof scope === 'string' ? scope : 'auto'
|
|
38
|
+
const scopeMode = normalizeMemoryScopeMode(rawScope === 'shared' ? 'global' : rawScope)
|
|
39
|
+
const rerankMode = rerank === 'semantic' || rerank === 'lexical' ? rerank : 'balanced'
|
|
40
|
+
|
|
41
|
+
const scopeFilter = {
|
|
42
|
+
mode: scopeMode,
|
|
43
|
+
agentId: currentAgentId,
|
|
44
|
+
sessionId: (typeof scopeSessionId === 'string' && scopeSessionId.trim()) ? scopeSessionId.trim() : (ctx?.sessionId || null),
|
|
45
|
+
projectRoot: (typeof projectRoot === 'string' && projectRoot.trim()) ? projectRoot.trim() : ((project && typeof project === 'object' && 'rootPath' in project && typeof (project as Record<string, unknown>).rootPath === 'string') ? (project as Record<string, unknown>).rootPath as string : null),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const filterScope = (rows: MemoryEntry[]) => filterMemoriesByScope(rows, scopeFilter)
|
|
49
|
+
const canReadMemory = (m: MemoryEntry) => filterScope([m]).length > 0
|
|
50
|
+
const canMutateMemory = (m: MemoryEntry) => !m?.agentId || m.agentId === currentAgentId
|
|
12
51
|
|
|
13
|
-
|
|
14
|
-
|
|
52
|
+
const limits = getMemoryLookupLimits(loadSettings())
|
|
53
|
+
const maxPerLookup = limits.maxPerLookup
|
|
15
54
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (scopeMode === 'agent') return rows.filter((m) => currentAgentId && m.agentId === currentAgentId)
|
|
27
|
-
return rows.filter(canAccessMemory)
|
|
28
|
-
}
|
|
55
|
+
const formatEntry = (m: any) => {
|
|
56
|
+
let line = `[${m.id}] (${m.agentId ? `agent:${m.agentId}` : 'shared'}) ${m.category}/${m.title}: ${m.content}`
|
|
57
|
+
if (m.reinforcementCount) line += ` (reinforced ×${m.reinforcementCount})`
|
|
58
|
+
if (m.references?.length) {
|
|
59
|
+
line += `\n refs: ${m.references.map((r: any) => `${r.type}:${r.path || r.title || r.type}`).join(', ')}`
|
|
60
|
+
}
|
|
61
|
+
if (m.imagePath) line += `\n image: ${m.imagePath}`
|
|
62
|
+
if (m.linkedMemoryIds?.length) line += `\n linked: ${m.linkedMemoryIds.join(', ')}`
|
|
63
|
+
return line
|
|
64
|
+
}
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
if (action === 'store') {
|
|
67
|
+
let storedImage: any = null
|
|
68
|
+
if (imagePath && fs.existsSync(imagePath)) {
|
|
69
|
+
storedImage = await storeMemoryImageAsset(imagePath, genId(6))
|
|
70
|
+
}
|
|
71
|
+
const entry = memDb.add({
|
|
72
|
+
agentId: scopeMode === 'global' ? null : currentAgentId,
|
|
73
|
+
sessionId: ctx?.sessionId || null,
|
|
74
|
+
category: category || 'note',
|
|
75
|
+
title: key,
|
|
76
|
+
content: value || '',
|
|
77
|
+
references: Array.isArray(references) ? references : [],
|
|
78
|
+
filePaths: filePaths as any,
|
|
79
|
+
imagePath: storedImage?.path || undefined,
|
|
80
|
+
linkedMemoryIds,
|
|
81
|
+
pinned: pinned === true,
|
|
82
|
+
sharedWith: Array.isArray(sharedWith) ? sharedWith : undefined,
|
|
83
|
+
})
|
|
84
|
+
return `Stored memory "${key}" (id: ${entry.id})`
|
|
85
|
+
}
|
|
36
86
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
projectName: f.projectName,
|
|
43
|
-
note: f.contextSnippet,
|
|
44
|
-
timestamp: typeof f.timestamp === 'number' ? f.timestamp : Date.now(),
|
|
45
|
-
}))
|
|
46
|
-
: []
|
|
47
|
-
const normalizedRefs = Array.isArray(references) ? references : []
|
|
48
|
-
if (project?.rootPath) {
|
|
49
|
-
normalizedRefs.push({
|
|
50
|
-
type: 'project',
|
|
51
|
-
path: project.rootPath,
|
|
52
|
-
projectRoot: project.rootPath,
|
|
53
|
-
projectName: project.name,
|
|
54
|
-
title: project.name,
|
|
55
|
-
note: project.note,
|
|
56
|
-
timestamp: Date.now(),
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
const mergedRefs = [...normalizedLegacyRefs, ...normalizedRefs]
|
|
87
|
+
if (action === 'get') {
|
|
88
|
+
const found = memDb.get(key)
|
|
89
|
+
if (!found || !canReadMemory(found)) return `Memory not found or access denied: ${key}`
|
|
90
|
+
return formatEntry(found)
|
|
91
|
+
}
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return line
|
|
77
|
-
}
|
|
93
|
+
if (action === 'search') {
|
|
94
|
+
const queries = query ? await expandQuery(query) : [key || '']
|
|
95
|
+
const allResults: MemoryEntry[] = []
|
|
96
|
+
const seenIds = new Set<string>()
|
|
97
|
+
for (const q of queries) {
|
|
98
|
+
const results = memDb.search(q, currentAgentId || undefined, { scope: scopeFilter, rerankMode })
|
|
99
|
+
for (const r of results) {
|
|
100
|
+
if (!seenIds.has(r.id)) {
|
|
101
|
+
seenIds.add(r.id); allResults.push(r)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!allResults.length) return 'No memories found.'
|
|
106
|
+
return allResults.slice(0, maxPerLookup).map(formatEntry).join('\n')
|
|
107
|
+
}
|
|
78
108
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return `Error: image file not found: ${imagePath}`
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
storedImage = await storeMemoryImageAsset(imagePath, genId(6))
|
|
87
|
-
} catch {
|
|
88
|
-
return `Error: failed to process image at ${imagePath}`
|
|
89
|
-
}
|
|
90
|
-
}
|
|
109
|
+
if (action === 'list') {
|
|
110
|
+
const results = filterScope(memDb.list(undefined, maxPerLookup))
|
|
111
|
+
return results.length ? results.map(formatEntry).join('\n') : 'No memories stored yet.'
|
|
112
|
+
}
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
content: value || '',
|
|
98
|
-
references: mergedRefs as any,
|
|
99
|
-
filePaths: filePaths as any,
|
|
100
|
-
image: storedImage,
|
|
101
|
-
imagePath: storedImage?.path || undefined,
|
|
102
|
-
linkedMemoryIds,
|
|
103
|
-
pinned: pinned === true,
|
|
104
|
-
sharedWith: Array.isArray(sharedWith) ? sharedWith : undefined,
|
|
105
|
-
})
|
|
106
|
-
const memoryScope = entry.agentId ? 'agent' : 'shared'
|
|
107
|
-
let result = `Stored ${memoryScope} memory "${key}" (id: ${entry.id})`
|
|
108
|
-
if (mergedRefs.length) result += ` with ${mergedRefs.length} reference(s)`
|
|
109
|
-
if (storedImage?.path) result += ` with image`
|
|
110
|
-
if (linkedMemoryIds?.length) result += ` linked to ${linkedMemoryIds.length} memor${linkedMemoryIds.length === 1 ? 'y' : 'ies'}`
|
|
111
|
-
return result
|
|
112
|
-
}
|
|
113
|
-
if (action === 'get') {
|
|
114
|
-
if (effectiveDepth > 0) {
|
|
115
|
-
const result = memDb.getWithLinked(key, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
|
|
116
|
-
if (!result) return `Memory not found: ${key}`
|
|
117
|
-
const accessible = result.entries.filter(canAccessMemory)
|
|
118
|
-
if (!accessible.length) return 'Error: you do not have access to that memory.'
|
|
119
|
-
let output = accessible.map(formatEntry).join('\n---\n')
|
|
120
|
-
if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
|
|
121
|
-
return output
|
|
122
|
-
}
|
|
123
|
-
const found = memDb.get(key)
|
|
124
|
-
if (!found) return `Memory not found: ${key}`
|
|
125
|
-
if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
|
|
126
|
-
return formatEntry(found)
|
|
127
|
-
}
|
|
128
|
-
if (action === 'search') {
|
|
129
|
-
if (effectiveDepth > 0) {
|
|
130
|
-
const result = memDb.searchWithLinked(query || key, undefined, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
|
|
131
|
-
const accessible = filterScope(result.entries)
|
|
132
|
-
if (!accessible.length) return 'No memories found.'
|
|
133
|
-
let output = accessible.map(formatEntry).join('\n')
|
|
134
|
-
if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
|
|
135
|
-
return output
|
|
136
|
-
}
|
|
137
|
-
const results = filterScope(memDb.search(query || key))
|
|
138
|
-
if (!results.length) return 'No memories found.'
|
|
139
|
-
return results.slice(0, maxPerLookup).map(formatEntry).join('\n')
|
|
140
|
-
}
|
|
141
|
-
if (action === 'list') {
|
|
142
|
-
const results = filterScope(memDb.list(undefined, maxPerLookup))
|
|
143
|
-
if (!results.length) return 'No memories stored yet.'
|
|
144
|
-
return results.map(formatEntry).join('\n')
|
|
145
|
-
}
|
|
146
|
-
if (action === 'delete') {
|
|
147
|
-
const found = memDb.get(key)
|
|
148
|
-
if (!found) return `Memory not found: ${key}`
|
|
149
|
-
if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
|
|
150
|
-
memDb.delete(key)
|
|
151
|
-
return `Deleted memory "${key}"`
|
|
152
|
-
}
|
|
153
|
-
if (action === 'link') {
|
|
154
|
-
if (!targetIds?.length) return 'Error: targetIds required for link action.'
|
|
155
|
-
const result = memDb.link(key, targetIds, true)
|
|
156
|
-
if (!result) return `Memory not found: ${key}`
|
|
157
|
-
return `Linked memory "${key}" to ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} (bidirectional): ${targetIds.join(', ')}`
|
|
158
|
-
}
|
|
159
|
-
if (action === 'unlink') {
|
|
160
|
-
if (!targetIds?.length) return 'Error: targetIds required for unlink action.'
|
|
161
|
-
const result = memDb.unlink(key, targetIds, true)
|
|
162
|
-
if (!result) return `Memory not found: ${key}`
|
|
163
|
-
return `Unlinked ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} from "${key}" (bidirectional)`
|
|
164
|
-
}
|
|
165
|
-
if (action === 'knowledge_store') {
|
|
166
|
-
const { addKnowledge } = await import('../memory-db')
|
|
167
|
-
if (!value) return 'Error: value (content) is required for knowledge_store'
|
|
168
|
-
const source = (input as Record<string, unknown>).source as string | undefined
|
|
169
|
-
const sourceUrl = (input as Record<string, unknown>).sourceUrl as string | undefined
|
|
170
|
-
const entry = addKnowledge({
|
|
171
|
-
title: key || 'Untitled',
|
|
172
|
-
content: value,
|
|
173
|
-
tags: tags,
|
|
174
|
-
createdByAgentId: ctx?.agentId || null,
|
|
175
|
-
createdBySessionId: ctx?.sessionId || null,
|
|
176
|
-
source: source || undefined,
|
|
177
|
-
sourceUrl: sourceUrl || undefined,
|
|
178
|
-
})
|
|
179
|
-
return `Knowledge stored: "${entry.title}" (id: ${entry.id})`
|
|
180
|
-
}
|
|
181
|
-
if (action === 'knowledge_search') {
|
|
182
|
-
const { searchKnowledge } = await import('../memory-db')
|
|
183
|
-
const results = searchKnowledge(query || key || '', tags, 10)
|
|
184
|
-
if (!results.length) return 'No knowledge entries found.'
|
|
185
|
-
return results.map(r => {
|
|
186
|
-
const meta = r.metadata as Record<string, unknown> | undefined
|
|
187
|
-
const src = meta?.source as string | undefined
|
|
188
|
-
const srcUrl = meta?.sourceUrl as string | undefined
|
|
189
|
-
let line = `[${r.id}] ${r.title}: ${r.content.slice(0, 200)}`
|
|
190
|
-
if (src && srcUrl) {
|
|
191
|
-
line += ` [${src}](${srcUrl})`
|
|
192
|
-
} else if (src) {
|
|
193
|
-
line += ` (source: ${src})`
|
|
194
|
-
} else if (srcUrl) {
|
|
195
|
-
line += ` (${srcUrl})`
|
|
196
|
-
}
|
|
197
|
-
return line
|
|
198
|
-
}).join('\n---\n')
|
|
199
|
-
}
|
|
200
|
-
return `Unknown action "${action}". Use: store, get, search, list, delete, link, unlink, knowledge_store, or knowledge_search.`
|
|
201
|
-
} catch (err: unknown) {
|
|
202
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: 'memory_tool',
|
|
207
|
-
description: 'My long-term memory — things I remember across conversations. I can store personal notes, recall past context, and build up knowledge over time. Memories can be private to me or shared with other agents. I can also attach files, link related memories, and contribute to a shared knowledge base. Actions: store, get, search, list, delete, link, unlink, knowledge_store, knowledge_search.',
|
|
208
|
-
schema: z.object({
|
|
209
|
-
action: z.enum(['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']).describe('The action to perform'),
|
|
210
|
-
key: z.string().describe('For store: memory title. For get/delete/link/unlink: memory ID. For search: optional query fallback.'),
|
|
211
|
-
value: z.string().optional().describe('The memory content (for store action)'),
|
|
212
|
-
category: z.string().optional().describe('Category like "note", "fact", "preference", "project", "identity" (for store action, defaults to "note")'),
|
|
213
|
-
query: z.string().optional().describe('Search query (alternative to key for search action)'),
|
|
214
|
-
scope: z.enum(['auto', 'shared', 'agent']).optional().describe('Scope hint: auto (shared + own), shared, or agent'),
|
|
215
|
-
filePaths: z.array(z.object({
|
|
216
|
-
path: z.string().describe('File or folder path'),
|
|
217
|
-
contextSnippet: z.string().optional().describe('Brief context about this file reference'),
|
|
218
|
-
kind: z.enum(['file', 'folder', 'project']).optional().describe('Reference type for legacy filePaths compatibility'),
|
|
219
|
-
projectRoot: z.string().optional().describe('Optional project root path'),
|
|
220
|
-
projectName: z.string().optional().describe('Optional project display name'),
|
|
221
|
-
exists: z.boolean().optional().describe('Optional known existence state'),
|
|
222
|
-
timestamp: z.number().describe('When this file was referenced'),
|
|
223
|
-
})).optional().describe('File/folder references to attach to the memory (for store action)'),
|
|
224
|
-
references: z.array(z.object({
|
|
225
|
-
type: z.enum(['project', 'folder', 'file', 'task', 'session', 'url']),
|
|
226
|
-
path: z.string().optional(),
|
|
227
|
-
projectRoot: z.string().optional(),
|
|
228
|
-
projectName: z.string().optional(),
|
|
229
|
-
title: z.string().optional(),
|
|
230
|
-
note: z.string().optional(),
|
|
231
|
-
timestamp: z.number().optional(),
|
|
232
|
-
})).optional().describe('Structured references attached to the memory (preferred over filePaths).'),
|
|
233
|
-
project: z.object({
|
|
234
|
-
rootPath: z.string().describe('Project/workspace root path'),
|
|
235
|
-
name: z.string().optional().describe('Optional project display name'),
|
|
236
|
-
note: z.string().optional().describe('Optional note about the project context'),
|
|
237
|
-
}).optional().describe('Shortcut to add a project reference on store action.'),
|
|
238
|
-
imagePath: z.string().optional().describe('Path to an image file to attach (will be compressed and stored). For store action.'),
|
|
239
|
-
linkedMemoryIds: z.array(z.string()).optional().describe('IDs of other memories to link to (for store action)'),
|
|
240
|
-
depth: z.number().optional().describe('How deep to traverse linked memories (for get/search). Respects configured maxDepth limit. Default: 0 (no traversal).'),
|
|
241
|
-
linkedLimit: z.number().optional().describe('Max linked memories expanded during traversal. Respects configured server cap.'),
|
|
242
|
-
targetIds: z.array(z.string()).optional().describe('Memory IDs to link/unlink (for link/unlink actions)'),
|
|
243
|
-
tags: z.array(z.string()).optional().describe('Tags for categorizing knowledge entries'),
|
|
244
|
-
source: z.string().optional().describe("Source of the knowledge, e.g. 'user', 'web', 'document'"),
|
|
245
|
-
sourceUrl: z.string().optional().describe('URL where the knowledge was sourced from'),
|
|
246
|
-
pinned: z.boolean().optional().describe('Mark memory as pinned (always preloaded in agent context). For store action.'),
|
|
247
|
-
sharedWith: z.array(z.string()).optional().describe('Agent IDs to share this memory with (for store action). They can read it in their context.'),
|
|
248
|
-
}),
|
|
249
|
-
},
|
|
250
|
-
),
|
|
251
|
-
)
|
|
114
|
+
if (action === 'delete') {
|
|
115
|
+
const found = memDb.get(key)
|
|
116
|
+
if (!found || !canMutateMemory(found)) return 'Memory not found or access denied.'
|
|
117
|
+
memDb.delete(key)
|
|
118
|
+
return `Deleted memory "${key}"`
|
|
252
119
|
}
|
|
253
120
|
|
|
254
|
-
return
|
|
121
|
+
return `Unknown action "${action}".`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register as a Built-in Plugin
|
|
126
|
+
*/
|
|
127
|
+
const MemoryPlugin: Plugin = {
|
|
128
|
+
name: 'Core Memory',
|
|
129
|
+
description: 'Advanced database-backed long-term memory with semantic search and graph linking.',
|
|
130
|
+
hooks: {} as PluginHooks,
|
|
131
|
+
tools: [
|
|
132
|
+
{
|
|
133
|
+
name: 'memory_tool',
|
|
134
|
+
description: 'Advanced long-term memory system. Use to store and recall facts across all conversations.',
|
|
135
|
+
parameters: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
action: { type: 'string', enum: ['store', 'get', 'search', 'list', 'delete'] },
|
|
139
|
+
key: { type: 'string' },
|
|
140
|
+
value: { type: 'string' },
|
|
141
|
+
category: { type: 'string' },
|
|
142
|
+
query: { type: 'string' },
|
|
143
|
+
scope: { type: 'string', enum: ['auto', 'all', 'global', 'shared', 'agent', 'session', 'project'] },
|
|
144
|
+
},
|
|
145
|
+
required: ['action']
|
|
146
|
+
},
|
|
147
|
+
execute: async (args, context) => {
|
|
148
|
+
return executeMemoryAction(args, context.session)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Auto-register when imported
|
|
155
|
+
getPluginManager().registerBuiltin('memory', MemoryPlugin)
|
|
156
|
+
|
|
157
|
+
export function buildMemoryTools(bctx: ToolBuildContext) {
|
|
158
|
+
if (!bctx.hasTool('memory')) return []
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
tool(
|
|
162
|
+
async (args) => executeMemoryAction(args, bctx.ctx),
|
|
163
|
+
{
|
|
164
|
+
name: 'memory_tool',
|
|
165
|
+
description: MemoryPlugin.tools![0].description,
|
|
166
|
+
schema: z.object({}).passthrough()
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
]
|
|
255
170
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import * as os from 'os'
|
|
6
|
+
import type { ToolBuildContext } from './context'
|
|
7
|
+
import { getPluginManager } from '../plugins'
|
|
8
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
9
|
+
import { safePath, truncate } from './context'
|
|
10
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unified Monitoring Logic
|
|
14
|
+
*/
|
|
15
|
+
async function executeMonitorAction(args: any, bctx: { cwd: string }) {
|
|
16
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
17
|
+
const action = normalized.action as string | undefined
|
|
18
|
+
const target = (normalized.target ?? normalized.url ?? normalized.path) as string | undefined
|
|
19
|
+
const limit = normalized.limit as number | undefined
|
|
20
|
+
const threshold = normalized.threshold as number | undefined
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
switch (action) {
|
|
24
|
+
case 'sys_info': {
|
|
25
|
+
const freeMem = os.freemem()
|
|
26
|
+
const totalMem = os.totalmem()
|
|
27
|
+
const load = os.loadavg()
|
|
28
|
+
const uptime = os.uptime()
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
platform: os.platform(),
|
|
31
|
+
arch: os.arch(),
|
|
32
|
+
cpus: os.cpus().length,
|
|
33
|
+
memory: {
|
|
34
|
+
free: `${Math.round(freeMem / 1024 / 1024)}MB`,
|
|
35
|
+
total: `${Math.round(totalMem / 1024 / 1024)}MB`,
|
|
36
|
+
usage: `${Math.round(((totalMem - freeMem) / totalMem) * 100)}%`
|
|
37
|
+
},
|
|
38
|
+
loadAvg: load,
|
|
39
|
+
uptime: `${Math.round(uptime / 3600)} hours`
|
|
40
|
+
}, null, 2)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'watch_log': {
|
|
44
|
+
const resolved = safePath(bctx.cwd, target!)
|
|
45
|
+
if (!fs.existsSync(resolved)) return `Error: File not found ${target}`
|
|
46
|
+
|
|
47
|
+
const stats = fs.statSync(resolved)
|
|
48
|
+
const size = stats.size
|
|
49
|
+
const bufferSize = Math.min(size, 5000) // Read last 5KB
|
|
50
|
+
const fd = fs.openSync(resolved, 'r')
|
|
51
|
+
const buffer = Buffer.alloc(bufferSize)
|
|
52
|
+
fs.readSync(fd, buffer, 0, bufferSize, size - bufferSize)
|
|
53
|
+
fs.closeSync(fd)
|
|
54
|
+
|
|
55
|
+
return truncate(buffer.toString('utf8'), 2000)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case 'ping': {
|
|
59
|
+
const url = target?.startsWith('http') ? target : `http://${target}`
|
|
60
|
+
const start = Date.now()
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(5000) })
|
|
63
|
+
const latency = Date.now() - start
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
status: res.status,
|
|
66
|
+
ok: res.ok,
|
|
67
|
+
latency: `${latency}ms`,
|
|
68
|
+
url
|
|
69
|
+
}, null, 2)
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
status: 'error',
|
|
73
|
+
error: err.message,
|
|
74
|
+
url
|
|
75
|
+
}, null, 2)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
return `Error: Unknown action "${action}"`
|
|
81
|
+
}
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
return `Error: ${err.message}`
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register as a Built-in Plugin
|
|
89
|
+
*/
|
|
90
|
+
const MonitorPlugin: Plugin = {
|
|
91
|
+
name: 'Core Monitor',
|
|
92
|
+
description: 'System observability: check resource usage, watch logs, and ping endpoints.',
|
|
93
|
+
hooks: {} as PluginHooks,
|
|
94
|
+
tools: [
|
|
95
|
+
{
|
|
96
|
+
name: 'monitor_tool',
|
|
97
|
+
description: 'Observe system health, log activity, or endpoint availability.',
|
|
98
|
+
parameters: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
action: { type: 'string', enum: ['sys_info', 'watch_log', 'ping'] },
|
|
102
|
+
target: { type: 'string', description: 'Log file path (for watch_log) or URL (for ping)' },
|
|
103
|
+
limit: { type: 'number', description: 'Number of lines or bytes to retrieve' }
|
|
104
|
+
},
|
|
105
|
+
required: ['action']
|
|
106
|
+
},
|
|
107
|
+
execute: async (args, context) => executeMonitorAction(args, { cwd: context.session.cwd || process.cwd() })
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getPluginManager().registerBuiltin('monitor', MonitorPlugin)
|
|
113
|
+
|
|
114
|
+
export function buildMonitorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
115
|
+
if (!bctx.hasTool('monitor')) return []
|
|
116
|
+
return [
|
|
117
|
+
tool(
|
|
118
|
+
async (args) => executeMonitorAction(args, { cwd: bctx.cwd }),
|
|
119
|
+
{
|
|
120
|
+
name: 'monitor_tool',
|
|
121
|
+
description: MonitorPlugin.tools![0].description,
|
|
122
|
+
schema: z.object({}).passthrough()
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
]
|
|
126
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
4
|
+
|
|
5
|
+
describe('normalizeToolInputArgs', () => {
|
|
6
|
+
it('keeps top-level args as-is when there is no wrapper', () => {
|
|
7
|
+
const out = normalizeToolInputArgs({ action: 'list', path: '/tmp' })
|
|
8
|
+
assert.equal(out.action, 'list')
|
|
9
|
+
assert.equal(out.path, '/tmp')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('merges nested input object payloads', () => {
|
|
13
|
+
const out = normalizeToolInputArgs({
|
|
14
|
+
input: {
|
|
15
|
+
action: 'execute',
|
|
16
|
+
execute_command: 'pwd',
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
assert.equal(out.action, 'execute')
|
|
20
|
+
assert.equal(out.execute_command, 'pwd')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('merges stringified input payloads', () => {
|
|
24
|
+
const out = normalizeToolInputArgs({
|
|
25
|
+
input: JSON.stringify({
|
|
26
|
+
action: 'list',
|
|
27
|
+
path: '/Users/dev/project',
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
assert.equal(out.action, 'list')
|
|
31
|
+
assert.equal(out.path, '/Users/dev/project')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('supports args/payload wrapper aliases', () => {
|
|
35
|
+
const out = normalizeToolInputArgs({
|
|
36
|
+
args: { action: 'read', filePath: 'README.md' },
|
|
37
|
+
payload: { limit: 10 },
|
|
38
|
+
})
|
|
39
|
+
assert.equal(out.action, 'read')
|
|
40
|
+
assert.equal(out.filePath, 'README.md')
|
|
41
|
+
assert.equal(out.limit, 10)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('keeps explicit top-level values over nested wrappers', () => {
|
|
45
|
+
const out = normalizeToolInputArgs({
|
|
46
|
+
action: 'top-level',
|
|
47
|
+
input: { action: 'nested' },
|
|
48
|
+
})
|
|
49
|
+
assert.equal(out.action, 'top-level')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('preserves falsey top-level values', () => {
|
|
53
|
+
const out = normalizeToolInputArgs({
|
|
54
|
+
input: { limit: 5, approved: true },
|
|
55
|
+
limit: 0,
|
|
56
|
+
approved: false,
|
|
57
|
+
})
|
|
58
|
+
assert.equal(out.limit, 0)
|
|
59
|
+
assert.equal(out.approved, false)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type ToolArgsRecord = Record<string, unknown>
|
|
2
|
+
|
|
3
|
+
function parseRecordCandidate(value: unknown): ToolArgsRecord | null {
|
|
4
|
+
if (!value) return null
|
|
5
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
6
|
+
return value as ToolArgsRecord
|
|
7
|
+
}
|
|
8
|
+
if (typeof value !== 'string') return null
|
|
9
|
+
const trimmed = value.trim()
|
|
10
|
+
if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) return null
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(trimmed)
|
|
13
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
14
|
+
return parsed as ToolArgsRecord
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// ignore non-JSON strings
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Normalize common wrapper payloads used by older tool callers.
|
|
24
|
+
*
|
|
25
|
+
* Supports payloads nested under `input`, `args`, `arguments`, or `payload`
|
|
26
|
+
* as either objects or JSON strings.
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeToolInputArgs(rawArgs: ToolArgsRecord): ToolArgsRecord {
|
|
29
|
+
const nestedSources: Array<ToolArgsRecord | null> = [
|
|
30
|
+
parseRecordCandidate(rawArgs.input),
|
|
31
|
+
parseRecordCandidate(rawArgs.args),
|
|
32
|
+
parseRecordCandidate(rawArgs.arguments),
|
|
33
|
+
parseRecordCandidate(rawArgs.payload),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const normalized: ToolArgsRecord = {}
|
|
37
|
+
for (const nested of nestedSources) {
|
|
38
|
+
if (!nested) continue
|
|
39
|
+
Object.assign(normalized, nested)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [key, value] of Object.entries(rawArgs)) {
|
|
43
|
+
if (value === undefined || value === null) continue
|
|
44
|
+
normalized[key] = value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return normalized
|
|
48
|
+
}
|