@johpaz/hive-sdk 0.0.12 → 0.0.15
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/.github/CODEOWNERS +9 -0
- package/.github/workflows/publish.yml +89 -0
- package/.github/workflows/version-bump.yml +102 -0
- package/CHANGELOG.md +38 -0
- package/README.md +158 -0
- package/bun.lock +543 -0
- package/bunfig.toml +7 -0
- package/docs/API-AGENTS.md +316 -0
- package/docs/API-CONTEXT-COMPILER.md +252 -0
- package/docs/API-DAG-SCHEDULER.md +273 -0
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +293 -0
- package/docs/API-WORKERS-EVENTS.md +152 -0
- package/docs/INDEX.md +141 -0
- package/docs/README.md +68 -0
- package/package.json +54 -105
- package/packages/cli/package.json +17 -0
- package/packages/cli/src/commands/init.ts +56 -0
- package/packages/cli/src/commands/run.ts +45 -0
- package/packages/cli/src/commands/test.ts +42 -0
- package/packages/cli/src/commands/trace.ts +55 -0
- package/packages/cli/src/index.ts +43 -0
- package/packages/core/package.json +58 -0
- package/packages/core/src/ace/Curator.ts +158 -0
- package/packages/core/src/ace/Reflector.ts +200 -0
- package/packages/core/src/ace/Tracer.ts +100 -0
- package/packages/core/src/ace/index.ts +4 -0
- package/packages/core/src/agent/AgentRunner.ts +699 -0
- package/packages/core/src/agent/Compaction.ts +221 -0
- package/packages/core/src/agent/ContextCompiler.ts +567 -0
- package/packages/core/src/agent/ContextGuard.ts +91 -0
- package/packages/core/src/agent/ConversationStore.ts +244 -0
- package/packages/core/src/agent/Hooks.ts +166 -0
- package/packages/core/src/agent/NativeTools.ts +31 -0
- package/packages/core/src/agent/PromptBuilder.ts +169 -0
- package/packages/core/src/agent/Service.ts +267 -0
- package/packages/core/src/agent/StuckLoop.ts +133 -0
- package/packages/core/src/agent/index.ts +12 -0
- package/packages/core/src/agent/providers/LLMClient.ts +149 -0
- package/packages/core/src/agent/providers/anthropic.ts +212 -0
- package/packages/core/src/agent/providers/gemini.ts +215 -0
- package/packages/core/src/agent/providers/index.ts +199 -0
- package/packages/core/src/agent/providers/interface.ts +195 -0
- package/packages/core/src/agent/providers/ollama.ts +175 -0
- package/packages/core/src/agent/providers/openai-compat.ts +231 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/selectors/PlaybookSelector.ts +147 -0
- package/packages/core/src/agent/selectors/SkillSelector.ts +478 -0
- package/packages/core/src/agent/selectors/ToolSelector.ts +577 -0
- package/packages/core/src/agent/selectors/index.ts +6 -0
- package/packages/core/src/api/createAgent.test.ts +48 -0
- package/packages/core/src/api/createAgent.ts +122 -0
- package/packages/core/src/api/index.ts +2 -0
- package/packages/core/src/canvas/CanvasManager.ts +390 -0
- package/packages/core/src/canvas/a2ui-tools.ts +255 -0
- package/packages/core/src/canvas/canvas-tools.ts +448 -0
- package/packages/core/src/canvas/emitter.ts +149 -0
- package/packages/core/src/canvas/index.ts +6 -0
- package/packages/core/src/config/index.ts +2 -0
- package/packages/core/src/config/loader.ts +554 -0
- package/packages/core/src/ethics/EthicsGuard.test.ts +54 -0
- package/packages/core/src/ethics/EthicsGuard.ts +66 -0
- package/packages/core/src/ethics/index.ts +2 -0
- package/packages/core/src/gateway/channel-notify.test.ts +14 -0
- package/packages/core/src/gateway/channel-notify.ts +12 -0
- package/packages/core/src/gateway/index.ts +1 -0
- package/packages/core/src/index.ts +37 -0
- package/packages/core/src/mcp/MCPClient.ts +439 -0
- package/packages/core/src/mcp/MCPToolAdapter.ts +176 -0
- package/packages/core/src/mcp/config.ts +13 -0
- package/packages/core/src/mcp/hot-reload.ts +147 -0
- package/packages/core/src/mcp/index.ts +11 -0
- package/packages/core/src/mcp/logger.ts +42 -0
- package/packages/core/src/mcp/singleton.ts +21 -0
- package/packages/core/src/mcp/transports/index.ts +67 -0
- package/packages/core/src/mcp/transports/sse.ts +241 -0
- package/packages/core/src/mcp/transports/websocket.ts +159 -0
- package/packages/core/src/memory/Scratchpad.test.ts +47 -0
- package/packages/core/src/memory/Scratchpad.ts +37 -0
- package/packages/core/src/memory/Storage.ts +6 -0
- package/packages/core/src/memory/index.ts +2 -0
- package/packages/core/src/multimodal/VisionService.ts +293 -0
- package/packages/core/src/multimodal/index.ts +2 -0
- package/packages/core/src/multimodal/types.ts +28 -0
- package/packages/core/src/security/Pairing.ts +250 -0
- package/packages/core/src/security/RateLimit.ts +270 -0
- package/packages/core/src/security/index.ts +4 -0
- package/packages/core/src/skills/SkillLoader.ts +388 -0
- package/packages/core/src/skills/bundled-data.generated.ts +3332 -0
- package/packages/core/src/skills/defineSkill.ts +18 -0
- package/packages/core/src/skills/index.ts +4 -0
- package/packages/core/src/state/index.ts +2 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/SQLiteStorage.ts +407 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/index.ts +10 -0
- package/packages/core/src/storage/onboarding.ts +1603 -0
- package/packages/core/src/storage/schema.ts +689 -0
- package/packages/core/src/storage/seed.ts +740 -0
- package/packages/core/src/storage/usage.ts +374 -0
- package/packages/core/src/swarm/AgentBus.ts +460 -0
- package/packages/core/src/swarm/AgentExecutor.ts +53 -0
- package/packages/core/src/swarm/Coordinator.ts +251 -0
- package/packages/core/src/swarm/EventBridge.ts +122 -0
- package/packages/core/src/swarm/EventBus.ts +169 -0
- package/packages/core/src/swarm/TaskGraph.ts +192 -0
- package/packages/core/src/swarm/TaskNode.ts +97 -0
- package/packages/core/src/swarm/TaskResult.ts +22 -0
- package/packages/core/src/swarm/WorkerPool.ts +236 -0
- package/packages/core/src/swarm/errors.ts +37 -0
- package/packages/core/src/swarm/index.ts +30 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +99 -0
- package/packages/core/src/swarm/presets/ResearchPreset.ts +97 -0
- package/packages/core/src/swarm/presets/index.ts +4 -0
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +21 -0
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +46 -0
- package/packages/core/src/swarm/strategies/index.ts +3 -0
- package/packages/core/src/swarm/types.ts +164 -0
- package/packages/core/src/tools/ToolExecutor.ts +58 -0
- package/packages/core/src/tools/ToolRegistry.test.ts +98 -0
- package/packages/core/src/tools/ToolRegistry.ts +61 -0
- package/packages/core/src/tools/agents/get-available-models.ts +118 -0
- package/packages/core/src/tools/agents/index.ts +715 -0
- package/packages/core/src/tools/bridge-events.ts +26 -0
- package/packages/core/src/tools/canvas/index.ts +375 -0
- package/packages/core/src/tools/cli/index.ts +142 -0
- package/packages/core/src/tools/codebridge/index.ts +342 -0
- package/packages/core/src/tools/core/index.ts +476 -0
- package/packages/core/src/tools/cron/index.ts +626 -0
- package/packages/core/src/tools/filesystem/fs-delete.ts +78 -0
- package/packages/core/src/tools/filesystem/fs-edit.ts +106 -0
- package/packages/core/src/tools/filesystem/fs-exists.ts +63 -0
- package/packages/core/src/tools/filesystem/fs-glob.ts +108 -0
- package/packages/core/src/tools/filesystem/fs-list.ts +129 -0
- package/packages/core/src/tools/filesystem/fs-read.ts +72 -0
- package/packages/core/src/tools/filesystem/fs-write.ts +67 -0
- package/packages/core/src/tools/filesystem/index.ts +34 -0
- package/packages/core/src/tools/filesystem/workspace-guard.ts +62 -0
- package/packages/core/src/tools/index.ts +231 -0
- package/packages/core/src/tools/meeting/index.ts +363 -0
- package/packages/core/src/tools/office/index.ts +47 -0
- package/packages/core/src/tools/office/office-escribir-docx.ts +192 -0
- package/packages/core/src/tools/office/office-escribir-pdf.ts +172 -0
- package/packages/core/src/tools/office/office-escribir-pptx.ts +174 -0
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +116 -0
- package/packages/core/src/tools/office/office-leer-docx.ts +93 -0
- package/packages/core/src/tools/office/office-leer-pdf.ts +114 -0
- package/packages/core/src/tools/office/office-leer-pptx.ts +136 -0
- package/packages/core/src/tools/office/office-leer-xlsx.ts +124 -0
- package/packages/core/src/tools/projects/index.ts +37 -0
- package/packages/core/src/tools/projects/project-create.ts +94 -0
- package/packages/core/src/tools/projects/project-done.ts +66 -0
- package/packages/core/src/tools/projects/project-fail.ts +66 -0
- package/packages/core/src/tools/projects/project-list.ts +96 -0
- package/packages/core/src/tools/projects/project-update.ts +72 -0
- package/packages/core/src/tools/projects/task-create.ts +68 -0
- package/packages/core/src/tools/projects/task-evaluate.ts +93 -0
- package/packages/core/src/tools/projects/task-update.ts +93 -0
- package/packages/core/src/tools/types.ts +39 -0
- package/packages/core/src/tools/voice/index.ts +104 -0
- package/packages/core/src/tools/web/browser-click.ts +78 -0
- package/packages/core/src/tools/web/browser-extract.ts +139 -0
- package/packages/core/src/tools/web/browser-navigate.ts +106 -0
- package/packages/core/src/tools/web/browser-screenshot.ts +87 -0
- package/packages/core/src/tools/web/browser-script.ts +88 -0
- package/packages/core/src/tools/web/browser-service.ts +554 -0
- package/packages/core/src/tools/web/browser-type.ts +101 -0
- package/packages/core/src/tools/web/browser-wait.ts +136 -0
- package/packages/core/src/tools/web/index.ts +41 -0
- package/packages/core/src/tools/web/web-fetch.ts +78 -0
- package/packages/core/src/tools/web/web-search.ts +123 -0
- package/packages/core/src/utils/benchmark.ts +80 -0
- package/packages/core/src/utils/crypto.ts +73 -0
- package/packages/core/src/utils/date.ts +42 -0
- package/packages/core/src/utils/index.ts +10 -0
- package/packages/core/src/utils/logger.ts +389 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/utils/toon.ts +253 -0
- package/packages/core/src/voice/index.ts +656 -0
- package/test/setup-db.ts +216 -0
- package/tsconfig.json +39 -0
- package/src/agents.ts +0 -1
- package/src/canvas.ts +0 -1
- package/src/channels.ts +0 -1
- package/src/config.ts +0 -1
- package/src/events.ts +0 -1
- package/src/gateway.ts +0 -1
- package/src/index.ts +0 -304
- package/src/mcp.ts +0 -1
- package/src/multimodal.ts +0 -1
- package/src/scheduler.ts +0 -1
- package/src/security.ts +0 -1
- package/src/skills.ts +0 -1
- package/src/state.ts +0 -1
- package/src/storage.ts +0 -1
- package/src/tools.ts +0 -1
- package/src/tts.ts +0 -1
- package/src/types.ts +0 -82
- package/src/utils.ts +0 -1
- package/src/voice.ts +0 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction — Fase 6.
|
|
3
|
+
*
|
|
4
|
+
* Compresses conversation history when token count exceeds threshold.
|
|
5
|
+
* Uses the active LLM to summarize old messages, preserving:
|
|
6
|
+
* - User data and preferences
|
|
7
|
+
* - Decisions made
|
|
8
|
+
* - Tool results
|
|
9
|
+
* - Context needed to continue
|
|
10
|
+
*
|
|
11
|
+
* Saves summary to `summaries` table. Original messages are kept (audit trail)
|
|
12
|
+
* but the Context Compiler uses the summary instead of old messages.
|
|
13
|
+
*
|
|
14
|
+
* Also implements "tool result clearing": replaces old tool results with
|
|
15
|
+
* short summaries in the in-memory message array before model calls.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { logger } from "../utils/logger.ts"
|
|
19
|
+
import {
|
|
20
|
+
getTotalTokens,
|
|
21
|
+
getHistory,
|
|
22
|
+
getSummary,
|
|
23
|
+
saveSummary,
|
|
24
|
+
toAPIMessages,
|
|
25
|
+
getMessageCount,
|
|
26
|
+
} from "./ConversationStore"
|
|
27
|
+
import { estimateTokens } from "../utils/toon.ts"
|
|
28
|
+
import { callLLM, resolveProviderConfig, type ContentPart } from "./providers/LLMClient"
|
|
29
|
+
import { getDb } from "../storage/SQLiteStorage.ts"
|
|
30
|
+
|
|
31
|
+
const log = logger.child("compaction")
|
|
32
|
+
|
|
33
|
+
// Token budget: compress when stored tokens exceed this threshold
|
|
34
|
+
const COMPACT_TOKEN_THRESHOLD = 6000 // ~60% of 10K context window
|
|
35
|
+
const KEEP_LAST_N_MESSAGES = 5 // always keep most recent N messages
|
|
36
|
+
const TOOL_RESULT_MAX_CHARS = 200 // max chars for old tool results after clearing
|
|
37
|
+
const MAX_TRANSCRIPT_MSGS = 30 // cap messages sent to summarizer (avoids OOM on small models)
|
|
38
|
+
const MAX_MSG_CHARS = 300 // chars per message in transcript
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if compaction is needed and run it if so.
|
|
42
|
+
* Called at the start of each agent loop iteration.
|
|
43
|
+
*/
|
|
44
|
+
export async function maybeCompact(
|
|
45
|
+
threadId: string,
|
|
46
|
+
notify?: { channel: string; userId: string }
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
try {
|
|
49
|
+
const totalTokens = getTotalTokens(threadId)
|
|
50
|
+
if (totalTokens < COMPACT_TOKEN_THRESHOLD) return
|
|
51
|
+
|
|
52
|
+
const summary = getSummary(threadId)
|
|
53
|
+
const totalMessages = getMessageCount(threadId)
|
|
54
|
+
|
|
55
|
+
// Already summarized up to near the current state
|
|
56
|
+
if (summary && summary.last_message_id > totalMessages - KEEP_LAST_N_MESSAGES) return
|
|
57
|
+
|
|
58
|
+
log.info(`[compaction] Compacting thread=${threadId} tokens=${totalTokens}`)
|
|
59
|
+
await compactThread(threadId, notify)
|
|
60
|
+
} catch (err) {
|
|
61
|
+
log.warn("[compaction] Error during compaction check:", err)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Compress a thread's history into a summary.
|
|
67
|
+
*/
|
|
68
|
+
export async function compactThread(
|
|
69
|
+
threadId: string,
|
|
70
|
+
notify?: { channel: string; userId: string }
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const allMessages = getHistory(threadId)
|
|
73
|
+
if (allMessages.length <= KEEP_LAST_N_MESSAGES) return
|
|
74
|
+
|
|
75
|
+
// Find a clean cut point: the "keep" side must begin with a user turn so
|
|
76
|
+
// we never leave orphaned tool messages at the start of the visible window.
|
|
77
|
+
let cutIndex = allMessages.length - KEEP_LAST_N_MESSAGES
|
|
78
|
+
while (cutIndex > 0 && allMessages[cutIndex]?.role !== "user") {
|
|
79
|
+
cutIndex--
|
|
80
|
+
}
|
|
81
|
+
if (cutIndex <= 0) {
|
|
82
|
+
log.info(`[compaction] No clean user-turn boundary found — skipping`)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const toSummarize = allMessages.slice(0, cutIndex)
|
|
87
|
+
if (toSummarize.length === 0) return
|
|
88
|
+
|
|
89
|
+
const lastSummarizedId = toSummarize[toSummarize.length - 1].id
|
|
90
|
+
|
|
91
|
+
const existingSummary = getSummary(threadId)
|
|
92
|
+
if (existingSummary && existingSummary.last_message_id >= lastSummarizedId) return
|
|
93
|
+
|
|
94
|
+
// Cap transcript to avoid overflowing small model contexts
|
|
95
|
+
const capped = toSummarize.slice(-MAX_TRANSCRIPT_MSGS)
|
|
96
|
+
const apiMessages = toAPIMessages(capped)
|
|
97
|
+
const transcript = apiMessages
|
|
98
|
+
.map((m) => {
|
|
99
|
+
const text = typeof m.content === "string"
|
|
100
|
+
? m.content
|
|
101
|
+
: Array.isArray(m.content)
|
|
102
|
+
? m.content.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
|
|
103
|
+
: ""
|
|
104
|
+
return `[${m.role.toUpperCase()}]: ${text.substring(0, MAX_MSG_CHARS)}`
|
|
105
|
+
})
|
|
106
|
+
.join("\n\n")
|
|
107
|
+
|
|
108
|
+
const db = getDb()
|
|
109
|
+
const coordinator = db.query<any, []>(
|
|
110
|
+
"SELECT provider_id, model_id FROM agents WHERE role = 'coordinator' LIMIT 1"
|
|
111
|
+
).get()
|
|
112
|
+
|
|
113
|
+
const providerCfg = await resolveProviderConfig(
|
|
114
|
+
coordinator?.provider_id || "openai",
|
|
115
|
+
coordinator?.model_id || "gpt-4o-mini"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const summaryResponse = await callLLM({
|
|
119
|
+
...providerCfg,
|
|
120
|
+
messages: [
|
|
121
|
+
{
|
|
122
|
+
role: "system",
|
|
123
|
+
content:
|
|
124
|
+
"You are a conversation summarizer. Create a concise summary preserving: " +
|
|
125
|
+
"user preferences, decisions made, important facts, tool results, and context needed to continue.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
role: "user",
|
|
129
|
+
content: `Summarize this conversation (${toSummarize.length} messages) in 3-5 sentences:\n\n${transcript}`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const summary = summaryResponse.content.trim()
|
|
135
|
+
if (!summary) return
|
|
136
|
+
|
|
137
|
+
saveSummary(threadId, summary, toSummarize.length, lastSummarizedId)
|
|
138
|
+
log.info(
|
|
139
|
+
`[compaction] Thread ${threadId} compacted: ${toSummarize.length} msgs → ${estimateTokens(summary)} tokens`
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Notify user in their active channel (non-critical)
|
|
143
|
+
if (notify?.channel && notify?.userId) {
|
|
144
|
+
try {
|
|
145
|
+
const { sendToUserChannel } = await import("../gateway/channel-notify.ts")
|
|
146
|
+
await sendToUserChannel(
|
|
147
|
+
notify.channel,
|
|
148
|
+
notify.userId,
|
|
149
|
+
`🗜️ Resumí ${toSummarize.length} mensajes anteriores para mantener el contexto limpio.`
|
|
150
|
+
)
|
|
151
|
+
} catch {
|
|
152
|
+
// Non-critical — don't break the flow if notification fails
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear old tool results in-memory to reduce tokens before a model call.
|
|
159
|
+
* Does NOT modify the database — only the in-memory messages array.
|
|
160
|
+
*
|
|
161
|
+
* Strategy: COMPRESS (Context Engineering)
|
|
162
|
+
* - Replaces old tool results with short summaries
|
|
163
|
+
* - Keeps recent tool results intact (keepLastN)
|
|
164
|
+
* - Uses TOON format for compact representation
|
|
165
|
+
*/
|
|
166
|
+
export function clearOldToolResults<T extends { role: string; content: string | ContentPart[] }>(
|
|
167
|
+
messages: T[],
|
|
168
|
+
keepLastN = 6
|
|
169
|
+
): T[] {
|
|
170
|
+
if (messages.length <= keepLastN) return messages
|
|
171
|
+
const cutoffIndex = messages.length - keepLastN
|
|
172
|
+
|
|
173
|
+
return messages.map((msg, i) => {
|
|
174
|
+
if (i >= cutoffIndex) return msg
|
|
175
|
+
|
|
176
|
+
if (msg.role === "tool" && typeof msg.content === "string") {
|
|
177
|
+
// For tool results older than keepLastN, summarize
|
|
178
|
+
if (msg.content.length > TOOL_RESULT_MAX_CHARS) {
|
|
179
|
+
// Try to extract key info from TOON/JSON format
|
|
180
|
+
let summary = msg.content.substring(0, TOOL_RESULT_MAX_CHARS)
|
|
181
|
+
|
|
182
|
+
// If it looks like JSON/TOON, add a marker
|
|
183
|
+
if (msg.content.trim().startsWith('{') || msg.content.trim().includes(':')) {
|
|
184
|
+
summary = `[Tool result summarized: ${summary}...]`
|
|
185
|
+
} else {
|
|
186
|
+
summary = `[Result truncated: ${summary}...]`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...msg,
|
|
191
|
+
content: summary,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return msg
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Summarize a tool result to a single line
|
|
202
|
+
* Used for very old tool results (> 10 turns)
|
|
203
|
+
*/
|
|
204
|
+
export function summarizeToolResult(content: string, toolName?: string): string {
|
|
205
|
+
// Try to extract success/failure status
|
|
206
|
+
const isError = content.includes('error') || content.includes('failed') || content.startsWith('[Tool Error]')
|
|
207
|
+
const isSuccess = content.includes('ok') || content.includes('success') || content.includes('true')
|
|
208
|
+
|
|
209
|
+
// Try to extract key result field from JSON/TOON
|
|
210
|
+
let keyInfo = ""
|
|
211
|
+
try {
|
|
212
|
+
// Simple extraction of first key value
|
|
213
|
+
const firstLine = content.split('\n')[0].substring(0, 80)
|
|
214
|
+
keyInfo = firstLine
|
|
215
|
+
} catch {
|
|
216
|
+
keyInfo = content.substring(0, 80)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const status = isError ? "failed" : isSuccess ? "success" : "completed"
|
|
220
|
+
return `[${toolName || 'Tool'} ${status}: ${keyInfo}...]`
|
|
221
|
+
}
|