@johpaz/hive 1.1.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/CONTRIBUTING.md +44 -0
- package/README.md +310 -0
- package/package.json +96 -0
- package/packages/cli/package.json +28 -0
- package/packages/cli/src/commands/agent-run.ts +168 -0
- package/packages/cli/src/commands/agents.ts +398 -0
- package/packages/cli/src/commands/chat.ts +142 -0
- package/packages/cli/src/commands/config.ts +50 -0
- package/packages/cli/src/commands/cron.ts +161 -0
- package/packages/cli/src/commands/dev.ts +95 -0
- package/packages/cli/src/commands/doctor.ts +133 -0
- package/packages/cli/src/commands/gateway.ts +443 -0
- package/packages/cli/src/commands/logs.ts +57 -0
- package/packages/cli/src/commands/mcp.ts +175 -0
- package/packages/cli/src/commands/message.ts +77 -0
- package/packages/cli/src/commands/onboard.ts +1868 -0
- package/packages/cli/src/commands/security.ts +144 -0
- package/packages/cli/src/commands/service.ts +50 -0
- package/packages/cli/src/commands/sessions.ts +116 -0
- package/packages/cli/src/commands/skills.ts +187 -0
- package/packages/cli/src/commands/update.ts +25 -0
- package/packages/cli/src/index.ts +185 -0
- package/packages/cli/src/utils/token.ts +6 -0
- package/packages/code-bridge/README.md +78 -0
- package/packages/code-bridge/package.json +18 -0
- package/packages/code-bridge/src/index.ts +95 -0
- package/packages/code-bridge/src/process-manager.ts +212 -0
- package/packages/code-bridge/src/schemas.ts +133 -0
- package/packages/core/package.json +46 -0
- package/packages/core/src/agent/agent-loop.ts +369 -0
- package/packages/core/src/agent/compaction.ts +140 -0
- package/packages/core/src/agent/context-compiler.ts +378 -0
- package/packages/core/src/agent/context-guard.ts +91 -0
- package/packages/core/src/agent/context.ts +138 -0
- package/packages/core/src/agent/conversation-store.ts +198 -0
- package/packages/core/src/agent/curator.ts +158 -0
- package/packages/core/src/agent/hooks.ts +166 -0
- package/packages/core/src/agent/index.ts +116 -0
- package/packages/core/src/agent/llm-client.ts +503 -0
- package/packages/core/src/agent/native-tools.ts +505 -0
- package/packages/core/src/agent/prompt-builder.ts +532 -0
- package/packages/core/src/agent/providers/index.ts +167 -0
- package/packages/core/src/agent/providers.ts +1 -0
- package/packages/core/src/agent/reflector.ts +170 -0
- package/packages/core/src/agent/service.ts +64 -0
- package/packages/core/src/agent/stuck-loop.ts +133 -0
- package/packages/core/src/agent/supervisor.ts +39 -0
- package/packages/core/src/agent/tracer.ts +102 -0
- package/packages/core/src/agent/workspace.ts +110 -0
- package/packages/core/src/canvas/canvas-manager.test.ts +161 -0
- package/packages/core/src/canvas/canvas-manager.ts +319 -0
- package/packages/core/src/canvas/canvas-tools.ts +420 -0
- package/packages/core/src/canvas/emitter.ts +115 -0
- package/packages/core/src/canvas/index.ts +2 -0
- package/packages/core/src/channels/base.ts +138 -0
- package/packages/core/src/channels/discord.ts +260 -0
- package/packages/core/src/channels/index.ts +7 -0
- package/packages/core/src/channels/manager.ts +383 -0
- package/packages/core/src/channels/slack.ts +287 -0
- package/packages/core/src/channels/telegram.ts +502 -0
- package/packages/core/src/channels/webchat.ts +128 -0
- package/packages/core/src/channels/whatsapp.ts +375 -0
- package/packages/core/src/config/index.ts +12 -0
- package/packages/core/src/config/loader.ts +529 -0
- package/packages/core/src/events/event-bus.ts +169 -0
- package/packages/core/src/gateway/index.ts +5 -0
- package/packages/core/src/gateway/initializer.ts +290 -0
- package/packages/core/src/gateway/lane-queue.ts +169 -0
- package/packages/core/src/gateway/resolver.ts +108 -0
- package/packages/core/src/gateway/router.ts +124 -0
- package/packages/core/src/gateway/server.ts +3317 -0
- package/packages/core/src/gateway/session.ts +95 -0
- package/packages/core/src/gateway/slash-commands.ts +192 -0
- package/packages/core/src/heartbeat/index.ts +157 -0
- package/packages/core/src/index.ts +19 -0
- package/packages/core/src/integrations/catalog.ts +286 -0
- package/packages/core/src/integrations/env.ts +64 -0
- package/packages/core/src/integrations/index.ts +2 -0
- package/packages/core/src/memory/index.ts +1 -0
- package/packages/core/src/memory/notes.ts +68 -0
- package/packages/core/src/plugins/api.ts +128 -0
- package/packages/core/src/plugins/index.ts +2 -0
- package/packages/core/src/plugins/loader.ts +365 -0
- package/packages/core/src/resilience/circuit-breaker.ts +225 -0
- package/packages/core/src/security/google-chat.ts +269 -0
- package/packages/core/src/security/index.ts +192 -0
- package/packages/core/src/security/pairing.ts +250 -0
- package/packages/core/src/security/rate-limit.ts +270 -0
- package/packages/core/src/security/signal.ts +321 -0
- package/packages/core/src/state/store.ts +312 -0
- package/packages/core/src/storage/bun-sqlite-store.ts +188 -0
- package/packages/core/src/storage/crypto.ts +101 -0
- package/packages/core/src/storage/db-context.ts +333 -0
- package/packages/core/src/storage/onboarding.ts +1087 -0
- package/packages/core/src/storage/schema.ts +541 -0
- package/packages/core/src/storage/seed.ts +571 -0
- package/packages/core/src/storage/sqlite.ts +387 -0
- package/packages/core/src/storage/usage.ts +212 -0
- package/packages/core/src/tools/bridge-events.ts +74 -0
- package/packages/core/src/tools/browser.ts +275 -0
- package/packages/core/src/tools/codebridge.ts +421 -0
- package/packages/core/src/tools/coordinator-tools.ts +179 -0
- package/packages/core/src/tools/cron.ts +611 -0
- package/packages/core/src/tools/exec.ts +140 -0
- package/packages/core/src/tools/fs.ts +364 -0
- package/packages/core/src/tools/index.ts +12 -0
- package/packages/core/src/tools/memory.ts +176 -0
- package/packages/core/src/tools/notify.ts +113 -0
- package/packages/core/src/tools/project-management.ts +376 -0
- package/packages/core/src/tools/project.ts +375 -0
- package/packages/core/src/tools/read.ts +158 -0
- package/packages/core/src/tools/web.ts +436 -0
- package/packages/core/src/tools/workspace.ts +171 -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 +4 -0
- package/packages/core/src/utils/logger.ts +388 -0
- package/packages/core/src/utils/retry.ts +70 -0
- package/packages/core/src/voice/index.ts +583 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/config.ts +13 -0
- package/packages/mcp/src/index.ts +1 -0
- package/packages/mcp/src/logger.ts +42 -0
- package/packages/mcp/src/manager.ts +434 -0
- package/packages/mcp/src/transports/index.ts +67 -0
- package/packages/mcp/src/transports/sse.ts +241 -0
- package/packages/mcp/src/transports/websocket.ts +159 -0
- package/packages/skills/package.json +21 -0
- package/packages/skills/src/bundled/agent_management/SKILL.md +24 -0
- package/packages/skills/src/bundled/browser_automation/SKILL.md +30 -0
- package/packages/skills/src/bundled/context_compact/SKILL.md +35 -0
- package/packages/skills/src/bundled/cron_manager/SKILL.md +52 -0
- package/packages/skills/src/bundled/file_manager/SKILL.md +76 -0
- package/packages/skills/src/bundled/http_client/SKILL.md +24 -0
- package/packages/skills/src/bundled/memory/SKILL.md +42 -0
- package/packages/skills/src/bundled/project_management/SKILL.md +26 -0
- package/packages/skills/src/bundled/shell/SKILL.md +43 -0
- package/packages/skills/src/bundled/system_notify/SKILL.md +52 -0
- package/packages/skills/src/bundled/voice/SKILL.md +25 -0
- package/packages/skills/src/bundled/web_search/SKILL.md +29 -0
- package/packages/skills/src/index.ts +1 -0
- package/packages/skills/src/loader.ts +282 -0
- package/packages/tools/package.json +43 -0
- package/packages/tools/src/browser/browser.test.ts +111 -0
- package/packages/tools/src/browser/index.ts +272 -0
- package/packages/tools/src/canvas/index.ts +220 -0
- package/packages/tools/src/cron/cron.test.ts +164 -0
- package/packages/tools/src/cron/index.ts +304 -0
- package/packages/tools/src/filesystem/filesystem.test.ts +240 -0
- package/packages/tools/src/filesystem/index.ts +379 -0
- package/packages/tools/src/git/index.ts +239 -0
- package/packages/tools/src/index.ts +4 -0
- package/packages/tools/src/shell/detect-env.ts +70 -0
- package/packages/tools/tsconfig.json +9 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compiler
|
|
3
|
+
*
|
|
4
|
+
* Compiled BEFORE every model call. Implements the 4 strategies:
|
|
5
|
+
* WRITE → scratchpad (persistent notes survive compression)
|
|
6
|
+
* SELECT → history (recent N), playbook rules (FTS5), tool loadout (3 levels)
|
|
7
|
+
* COMPRESS → summaries (if conversation is long)
|
|
8
|
+
* ISOLATE → workers receive only their task context, not full history
|
|
9
|
+
*
|
|
10
|
+
* Constitutional ethics layer: always injected first, never filtered.
|
|
11
|
+
*
|
|
12
|
+
* Output structure (used directly by agent-loop.ts):
|
|
13
|
+
* systemPrompt — assembled from layers 1-4
|
|
14
|
+
* messages — history to send to model
|
|
15
|
+
* tools — LLMToolDef[] after 3-level filter
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { getDb } from "../storage/sqlite"
|
|
19
|
+
import { logger } from "../utils/logger"
|
|
20
|
+
import type { LLMMessage, LLMToolDef } from "./llm-client"
|
|
21
|
+
import type { Tool } from "./native-tools"
|
|
22
|
+
import type { MCPClientManager } from "@johpaz/hive-mcp"
|
|
23
|
+
import {
|
|
24
|
+
getRecentMessages,
|
|
25
|
+
getSummary,
|
|
26
|
+
getScratchpad,
|
|
27
|
+
getMessageCount,
|
|
28
|
+
toAPIMessages,
|
|
29
|
+
type StoredMessage,
|
|
30
|
+
} from "./conversation-store"
|
|
31
|
+
import {
|
|
32
|
+
collectNativeTools,
|
|
33
|
+
collectMCPTools,
|
|
34
|
+
filterToolsByAgent,
|
|
35
|
+
selectToolLoadout,
|
|
36
|
+
syncNativeToolsToDB,
|
|
37
|
+
toToolDefs,
|
|
38
|
+
getToolGroup,
|
|
39
|
+
} from "./native-tools"
|
|
40
|
+
import { getUserDate, getUserTime } from "../utils/date"
|
|
41
|
+
import { encode } from "toon-format-parser"
|
|
42
|
+
|
|
43
|
+
const log = logger.child("context-compiler")
|
|
44
|
+
|
|
45
|
+
// ─── Configuration ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const RECENT_MSGS_LIMIT = 6 // keep last N messages for context (small models need small context)
|
|
48
|
+
const PLAYBOOK_RULES_LIMIT = 5 // max playbook rules injected per turn
|
|
49
|
+
const MAX_TOOL_GROUPS = 3 // max semantic groups loaded per turn (each group = all tools in category)
|
|
50
|
+
|
|
51
|
+
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface CompileOptions {
|
|
54
|
+
agentId: string
|
|
55
|
+
threadId: string
|
|
56
|
+
userMessage: string
|
|
57
|
+
channel?: string
|
|
58
|
+
mcpManager?: MCPClientManager | null
|
|
59
|
+
/** If true, this is a worker receiving an isolated task — don't inject full history */
|
|
60
|
+
isolated?: boolean
|
|
61
|
+
/** For isolated workers: the task description to inject instead of full history */
|
|
62
|
+
taskContext?: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CompiledContext {
|
|
66
|
+
systemPrompt: string
|
|
67
|
+
messages: LLMMessage[]
|
|
68
|
+
tools: LLMToolDef[]
|
|
69
|
+
allTools: Tool[] // raw tool objects for execution
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Main compiler ──────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
export async function compileContext(opts: CompileOptions): Promise<CompiledContext> {
|
|
75
|
+
const t0 = performance.now()
|
|
76
|
+
const db = getDb()
|
|
77
|
+
|
|
78
|
+
// Load agent row
|
|
79
|
+
const agent = db.query<any, [string]>("SELECT * FROM agents WHERE id = ?").get(opts.agentId)
|
|
80
|
+
if (!agent) throw new Error(`Agent not found: ${opts.agentId}`)
|
|
81
|
+
|
|
82
|
+
// Load user
|
|
83
|
+
const user = db.query<any, [string]>(
|
|
84
|
+
"SELECT * FROM users WHERE id = (SELECT user_id FROM agents WHERE id = ? LIMIT 1)"
|
|
85
|
+
).get(opts.agentId) ?? db.query<any, []>("SELECT * FROM users LIMIT 1").get()
|
|
86
|
+
|
|
87
|
+
// ── Tool collection FIRST — needed to generate dynamic capabilities ────
|
|
88
|
+
// Level 1: full catalog (native + MCP) — must happen before system prompt assembly
|
|
89
|
+
const nativeTools = collectNativeTools()
|
|
90
|
+
const mcpTools = opts.mcpManager
|
|
91
|
+
? await collectMCPTools(opts.mcpManager)
|
|
92
|
+
: []
|
|
93
|
+
const allTools = [...nativeTools, ...mcpTools]
|
|
94
|
+
|
|
95
|
+
// Sync catalog to DB so FTS5 is current (fast, ~1ms for 50 tools)
|
|
96
|
+
syncNativeToolsToDB(allTools)
|
|
97
|
+
|
|
98
|
+
// Level 2: filter by agent assignment (tools_json NULL = all allowed)
|
|
99
|
+
const agentFiltered = filterToolsByAgent(allTools, agent.tools_json)
|
|
100
|
+
|
|
101
|
+
// Level 3: Group selection — always-include + top N semantic groups expanded
|
|
102
|
+
const loadout = selectToolLoadout(agentFiltered, opts.userMessage, MAX_TOOL_GROUPS)
|
|
103
|
+
|
|
104
|
+
const systemParts: string[] = []
|
|
105
|
+
|
|
106
|
+
// ── Layer 1: Ethics (constitutional, always first) ──────────────────────
|
|
107
|
+
const ethicsText = loadEthics()
|
|
108
|
+
if (ethicsText) {
|
|
109
|
+
systemParts.push(ethicsText)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Layer 2: Agent identity ─────────────────────────────────────────────
|
|
113
|
+
let agentPrompt: string
|
|
114
|
+
if (agent.system_prompt) {
|
|
115
|
+
agentPrompt = agent.description
|
|
116
|
+
? `# ROLE\n${agent.description}\n\n${agent.system_prompt}`
|
|
117
|
+
: agent.system_prompt
|
|
118
|
+
} else if (agent.description) {
|
|
119
|
+
agentPrompt = `# ROLE\n${agent.description}\n\nYou are a helpful assistant. Use your available tools to complete the user's requests.`
|
|
120
|
+
} else {
|
|
121
|
+
agentPrompt = "You are a helpful assistant. Use your available tools to complete the user's requests."
|
|
122
|
+
}
|
|
123
|
+
systemParts.push(agentPrompt)
|
|
124
|
+
|
|
125
|
+
// ── Layer 2b: Hive Capabilities Manifest (dynamic — from actual loaded tools) ─
|
|
126
|
+
// Only for coordinators — workers get minimal context
|
|
127
|
+
if (!opts.isolated) {
|
|
128
|
+
const capabilities = loadHiveCapabilities(allTools)
|
|
129
|
+
if (capabilities) {
|
|
130
|
+
systemParts.push(capabilities)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Layer 2c: User profile ──────────────────────────────────────────────
|
|
135
|
+
if (user) {
|
|
136
|
+
const userInfo: Record<string, string> = {}
|
|
137
|
+
if (user.name) userInfo.name = user.name
|
|
138
|
+
if (user.occupation) userInfo.occupation = user.occupation
|
|
139
|
+
if (user.language) userInfo.language = user.language
|
|
140
|
+
if (user.timezone) userInfo.timezone = user.timezone
|
|
141
|
+
if (user.notes) userInfo.notes = user.notes
|
|
142
|
+
if (Object.keys(userInfo).length > 0) {
|
|
143
|
+
systemParts.push(encode({ user: userInfo }))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Layer 3: Playbook rules (ACE, FTS5) ─────────────────────────────────
|
|
148
|
+
const playbookRules = loadPlaybookRules(opts.userMessage)
|
|
149
|
+
if (playbookRules.length > 0) {
|
|
150
|
+
systemParts.push(encode({ playbook: playbookRules }))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Layer 4: Scratchpad (persistent notes) ──────────────────────────────
|
|
154
|
+
if (!opts.isolated) {
|
|
155
|
+
const notes = getScratchpad(opts.threadId)
|
|
156
|
+
if (notes.length > 0) {
|
|
157
|
+
const notesMap: Record<string, string> = {}
|
|
158
|
+
for (const n of notes) notesMap[n.key] = n.value
|
|
159
|
+
systemParts.push(encode({ notes: notesMap }))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Layer 5: Isolated task context (for workers) ─────────────────────────
|
|
164
|
+
if (opts.isolated && opts.taskContext) {
|
|
165
|
+
systemParts.push(`# YOUR TASK\n${opts.taskContext}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Layer 6: Environment / temporal info ────────────────────────────────
|
|
169
|
+
const now = new Date()
|
|
170
|
+
const tz = user?.timezone || "UTC"
|
|
171
|
+
let localTime = "N/A"
|
|
172
|
+
try {
|
|
173
|
+
localTime = now.toLocaleString("en-US", { timeZone: tz, dateStyle: "full", timeStyle: "long" })
|
|
174
|
+
} catch { /* bad TZ */ }
|
|
175
|
+
|
|
176
|
+
systemParts.push(encode({
|
|
177
|
+
env: {
|
|
178
|
+
agent_id: agent.id,
|
|
179
|
+
thread_id: opts.threadId,
|
|
180
|
+
utc: now.toISOString(),
|
|
181
|
+
local: `${localTime} (${tz})`,
|
|
182
|
+
date: getUserDate(tz, now),
|
|
183
|
+
time: getUserTime(tz, now),
|
|
184
|
+
}
|
|
185
|
+
}))
|
|
186
|
+
|
|
187
|
+
// ── Tool loadout summary (TOON, grouped) — current turn selection ────────
|
|
188
|
+
if (loadout.length > 0) {
|
|
189
|
+
const grouped: Record<string, string[]> = {}
|
|
190
|
+
for (const t of loadout) {
|
|
191
|
+
const cat = t.name.includes("__")
|
|
192
|
+
? `mcp:${t.name.split("__")[0]}`
|
|
193
|
+
: getToolGroup(t.name)
|
|
194
|
+
if (!grouped[cat]) grouped[cat] = []
|
|
195
|
+
grouped[cat].push(t.name)
|
|
196
|
+
}
|
|
197
|
+
systemParts.push(encode({ tools: grouped }))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── History selection ───────────────────────────────────────────────────
|
|
201
|
+
let messages: LLMMessage[]
|
|
202
|
+
|
|
203
|
+
if (opts.isolated) {
|
|
204
|
+
// Workers only get the task description — no full conversation history
|
|
205
|
+
messages = []
|
|
206
|
+
} else {
|
|
207
|
+
const totalMessages = getMessageCount(opts.threadId)
|
|
208
|
+
const summary = getSummary(opts.threadId)
|
|
209
|
+
|
|
210
|
+
if (summary && totalMessages > RECENT_MSGS_LIMIT) {
|
|
211
|
+
// Inject summary as a system message, then recent messages
|
|
212
|
+
const recent = getRecentMessages(opts.threadId, RECENT_MSGS_LIMIT)
|
|
213
|
+
messages = [
|
|
214
|
+
{ role: "system", content: `[Previous conversation summary]\n${summary.summary}` },
|
|
215
|
+
...toAPIMessages(recent),
|
|
216
|
+
]
|
|
217
|
+
} else {
|
|
218
|
+
// Short conversation — pass all messages
|
|
219
|
+
const recent = getRecentMessages(opts.threadId, RECENT_MSGS_LIMIT)
|
|
220
|
+
messages = toAPIMessages(recent)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
log.info(
|
|
225
|
+
`[context-compiler] agent=${agent.name} tools=${loadout.length}/${allTools.length} ` +
|
|
226
|
+
`msgs=${messages.length} playbook=${playbookRules.length} ` +
|
|
227
|
+
`elapsed=${(performance.now() - t0).toFixed(1)}ms`
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
systemPrompt: systemParts.join("\n\n"),
|
|
232
|
+
messages,
|
|
233
|
+
tools: toToolDefs(loadout),
|
|
234
|
+
allTools: loadout,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ─── Ethics loader ──────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
function loadEthics(): string {
|
|
241
|
+
try {
|
|
242
|
+
const db = getDb()
|
|
243
|
+
const rows = db.query<any, []>(
|
|
244
|
+
"SELECT content FROM ethics WHERE active = 1 ORDER BY id ASC"
|
|
245
|
+
).all()
|
|
246
|
+
if (rows.length === 0) return ""
|
|
247
|
+
return encode({ ethics: rows.map((r: any) => r.content) })
|
|
248
|
+
} catch {
|
|
249
|
+
return ""
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ─── Playbook loader (FTS5) ─────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
function loadPlaybookRules(userMessage: string): string[] {
|
|
256
|
+
try {
|
|
257
|
+
const db = getDb()
|
|
258
|
+
|
|
259
|
+
// Try FTS5 first
|
|
260
|
+
const ftsRows = db.query<any, [string, number]>(`
|
|
261
|
+
SELECT p.rule FROM playbook p
|
|
262
|
+
JOIN playbook_fts f ON p.id = f.rowid
|
|
263
|
+
WHERE playbook_fts MATCH ?
|
|
264
|
+
AND p.active = 1
|
|
265
|
+
AND p.helpful_count >= p.harmful_count
|
|
266
|
+
ORDER BY rank
|
|
267
|
+
LIMIT ?
|
|
268
|
+
`).all(sanitizeFTSQuery(userMessage), PLAYBOOK_RULES_LIMIT)
|
|
269
|
+
|
|
270
|
+
if (ftsRows.length > 0) {
|
|
271
|
+
return ftsRows.map((r: any) => r.rule)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Fallback: most-used active rules
|
|
275
|
+
const fallback = db.query<any, [number]>(`
|
|
276
|
+
SELECT rule FROM playbook
|
|
277
|
+
WHERE active = 1 AND helpful_count >= harmful_count
|
|
278
|
+
ORDER BY helpful_count DESC
|
|
279
|
+
LIMIT ?
|
|
280
|
+
`).all(PLAYBOOK_RULES_LIMIT)
|
|
281
|
+
|
|
282
|
+
return fallback.map((r: any) => r.rule)
|
|
283
|
+
} catch {
|
|
284
|
+
return []
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function sanitizeFTSQuery(text: string): string {
|
|
289
|
+
// FTS5 query: take first N unique words, escape special chars
|
|
290
|
+
const words = text
|
|
291
|
+
.replace(/['"*]/g, " ")
|
|
292
|
+
.split(/\s+/)
|
|
293
|
+
.filter((w) => w.length > 2)
|
|
294
|
+
.slice(0, 8)
|
|
295
|
+
return words.join(" OR ")
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Hive Capabilities loader ───────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Generates the Hive Capabilities manifest dynamically from:
|
|
302
|
+
* - Static sections in `hive_capabilities` DB (architecture, agents, canvas, etc.)
|
|
303
|
+
* - Native tools grouped by category (from allTools — always current)
|
|
304
|
+
* - MCP tools grouped by server (from allTools — always current)
|
|
305
|
+
*
|
|
306
|
+
* The `tools_native` and `mcp` DB sections are intentionally skipped;
|
|
307
|
+
* those are replaced by the dynamic blocks built from allTools.
|
|
308
|
+
*/
|
|
309
|
+
function loadHiveCapabilities(allTools: Tool[]): string {
|
|
310
|
+
try {
|
|
311
|
+
const db = getDb()
|
|
312
|
+
|
|
313
|
+
// Load static architecture/context sections — skip tools & mcp (generated dynamically)
|
|
314
|
+
const rows = db.query<any, []>(
|
|
315
|
+
"SELECT section, title, content FROM hive_capabilities WHERE active = 1 ORDER BY sort_order ASC"
|
|
316
|
+
).all()
|
|
317
|
+
|
|
318
|
+
const DYNAMIC_SECTIONS = new Set(["tools", "mcp"])
|
|
319
|
+
const sections: string[] = rows
|
|
320
|
+
.filter((r: any) => !DYNAMIC_SECTIONS.has(r.section))
|
|
321
|
+
.map((r: any) => `## ${r.title}\n${r.content}`)
|
|
322
|
+
|
|
323
|
+
// ── Dynamic: native tools grouped by category ────────────────────────
|
|
324
|
+
const nativeByGroup: Record<string, { name: string; description: string }[]> = {}
|
|
325
|
+
const mcpByServer: Record<string, string[]> = {}
|
|
326
|
+
|
|
327
|
+
for (const t of allTools) {
|
|
328
|
+
if (t.name.includes("__")) {
|
|
329
|
+
// MCP tool: "serverName__toolName"
|
|
330
|
+
const server = t.name.split("__")[0]
|
|
331
|
+
if (!mcpByServer[server]) mcpByServer[server] = []
|
|
332
|
+
mcpByServer[server].push(t.name.split("__")[1])
|
|
333
|
+
} else {
|
|
334
|
+
const cat = getToolGroup(t.name)
|
|
335
|
+
if (!nativeByGroup[cat]) nativeByGroup[cat] = []
|
|
336
|
+
nativeByGroup[cat].push({ name: t.name, description: t.description })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (Object.keys(nativeByGroup).length > 0) {
|
|
341
|
+
const lines = ["## Herramientas Nativas Disponibles"]
|
|
342
|
+
for (const [cat, tools] of Object.entries(nativeByGroup)) {
|
|
343
|
+
lines.push(`\n**${cat.toUpperCase()}:**`)
|
|
344
|
+
for (const t of tools) {
|
|
345
|
+
lines.push(`- ${t.name}: ${t.description.split(".")[0].substring(0, 80)}`)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
sections.push(lines.join("\n"))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Dynamic: MCP servers — from DB (always) + loaded tools (if connected) ──
|
|
352
|
+
// Query DB so the agent always knows which servers exist, even if disconnected
|
|
353
|
+
try {
|
|
354
|
+
const dbServers = db.query<any, []>(
|
|
355
|
+
"SELECT name, transport, url, status, tools_count FROM mcp_servers WHERE enabled = 1"
|
|
356
|
+
).all()
|
|
357
|
+
|
|
358
|
+
if (dbServers.length > 0) {
|
|
359
|
+
const lines = ["## Servidores MCP Configurados"]
|
|
360
|
+
for (const srv of dbServers) {
|
|
361
|
+
const loadedTools = mcpByServer[srv.name]
|
|
362
|
+
if (loadedTools && loadedTools.length > 0) {
|
|
363
|
+
lines.push(`\n**${srv.name}** [${srv.transport}] ✓ conectado — tools: ${loadedTools.join(", ")}`)
|
|
364
|
+
} else {
|
|
365
|
+
const endpoint = srv.url ? ` (${srv.url})` : ""
|
|
366
|
+
lines.push(`\n**${srv.name}** [${srv.transport}]${endpoint} — ${srv.status} (${srv.tools_count ?? 0} tools registradas)`)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
sections.push(lines.join("\n"))
|
|
370
|
+
}
|
|
371
|
+
} catch { /* no mcp_servers table yet */ }
|
|
372
|
+
|
|
373
|
+
if (sections.length === 0) return ""
|
|
374
|
+
return `# HIVE — CAPACIDADES DEL SISTEMA\n${sections.join("\n\n")}`
|
|
375
|
+
} catch {
|
|
376
|
+
return ""
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Config } from "../config/loader.ts";
|
|
2
|
+
import { logger } from "../utils/logger.ts";
|
|
3
|
+
import type { Message } from "../agent/context.ts";
|
|
4
|
+
|
|
5
|
+
export interface ContextGuardResult {
|
|
6
|
+
canProceed: boolean;
|
|
7
|
+
currentTokens: number;
|
|
8
|
+
maxTokens: number;
|
|
9
|
+
utilizationPercent: number;
|
|
10
|
+
needsCompaction: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ContextGuard {
|
|
14
|
+
private config: Config;
|
|
15
|
+
private log = logger.child("context-guard");
|
|
16
|
+
|
|
17
|
+
constructor(config: Config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
estimateTokens(messages: Message[]): number {
|
|
22
|
+
let total = 0;
|
|
23
|
+
|
|
24
|
+
for (const msg of messages) {
|
|
25
|
+
total += Math.ceil(msg.content.length / 4);
|
|
26
|
+
|
|
27
|
+
if (msg.name) {
|
|
28
|
+
total += Math.ceil(msg.name.length / 4);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (msg.toolCalls) {
|
|
32
|
+
for (const tc of msg.toolCalls) {
|
|
33
|
+
total += Math.ceil(tc.name.length / 4);
|
|
34
|
+
total += Math.ceil(JSON.stringify(tc.arguments).length / 4);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
check(messages: Message[], systemPrompt?: string): ContextGuardResult {
|
|
43
|
+
const maxTokens = this.config.agent?.context?.maxTokens || 128000;
|
|
44
|
+
const threshold = this.config.agent?.context?.compactionThreshold || 0.8;
|
|
45
|
+
|
|
46
|
+
let currentTokens = this.estimateTokens(messages);
|
|
47
|
+
|
|
48
|
+
if (systemPrompt) {
|
|
49
|
+
currentTokens += Math.ceil(systemPrompt.length / 4);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
currentTokens += 500;
|
|
53
|
+
|
|
54
|
+
const utilizationPercent = currentTokens / maxTokens;
|
|
55
|
+
const needsCompaction = utilizationPercent >= threshold;
|
|
56
|
+
const canProceed = currentTokens < maxTokens * 0.95;
|
|
57
|
+
|
|
58
|
+
this.log.debug(`Context check: ${currentTokens}/${maxTokens} tokens (${(utilizationPercent * 100).toFixed(1)}%)`);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
canProceed,
|
|
62
|
+
currentTokens,
|
|
63
|
+
maxTokens,
|
|
64
|
+
utilizationPercent,
|
|
65
|
+
needsCompaction,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
shouldCompact(messages: Message[], systemPrompt?: string): boolean {
|
|
70
|
+
const result = this.check(messages, systemPrompt);
|
|
71
|
+
return result.needsCompaction;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getRecommendedAction(messages: Message[], systemPrompt?: string): "proceed" | "compact" | "error" {
|
|
75
|
+
const result = this.check(messages, systemPrompt);
|
|
76
|
+
|
|
77
|
+
if (result.utilizationPercent >= 0.95) {
|
|
78
|
+
return "error";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result.needsCompaction) {
|
|
82
|
+
return "compact";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return "proceed";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createContextGuard(config: Config): ContextGuard {
|
|
90
|
+
return new ContextGuard(config);
|
|
91
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export interface SoulConfig {
|
|
2
|
+
identity: string;
|
|
3
|
+
personality: string;
|
|
4
|
+
boundaries: string;
|
|
5
|
+
instructions: string;
|
|
6
|
+
capabilities: string[];
|
|
7
|
+
raw: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UserConfig {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
language: string;
|
|
14
|
+
timezone: string;
|
|
15
|
+
activeProjects: string[];
|
|
16
|
+
preferences: Record<string, unknown> | string;
|
|
17
|
+
raw: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EthicsConfig {
|
|
21
|
+
raw: string;
|
|
22
|
+
loadedAt: Date;
|
|
23
|
+
path: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Message {
|
|
27
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
28
|
+
content: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
toolCallId?: string;
|
|
31
|
+
toolCalls?: ToolCall[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ToolCall {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
arguments: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ToolInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
description: string | null;
|
|
44
|
+
category: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SkillInfo {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
description: string | null;
|
|
51
|
+
source: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface McpServerInfo {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
transport: string;
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
active: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ChannelInfo {
|
|
63
|
+
id: string;
|
|
64
|
+
type: string;
|
|
65
|
+
accountId: string;
|
|
66
|
+
status: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CodeBridgeInfo {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
cliCommand: string;
|
|
73
|
+
port: number;
|
|
74
|
+
active: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ProviderInfo {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
baseUrl: string | null;
|
|
81
|
+
hasApiKey: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ModelInfo {
|
|
85
|
+
id: string;
|
|
86
|
+
name: string;
|
|
87
|
+
providerId: string;
|
|
88
|
+
contextWindow: number | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ContextOptions {
|
|
92
|
+
ethics: EthicsConfig | null;
|
|
93
|
+
soul: SoulConfig;
|
|
94
|
+
user: UserConfig | null;
|
|
95
|
+
skills: string[];
|
|
96
|
+
memory: string[];
|
|
97
|
+
channel?: string;
|
|
98
|
+
maxTokens: number;
|
|
99
|
+
// New structured data from DB
|
|
100
|
+
tools?: ToolInfo[];
|
|
101
|
+
mcpServers?: McpServerInfo[];
|
|
102
|
+
channels?: ChannelInfo[];
|
|
103
|
+
codeBridge?: CodeBridgeInfo[];
|
|
104
|
+
providers?: ProviderInfo[];
|
|
105
|
+
models?: ModelInfo[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
export function estimateTokens(text: string): number {
|
|
111
|
+
return Math.ceil(text.length / 4);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function truncateToTokenLimit(
|
|
115
|
+
messages: Message[],
|
|
116
|
+
maxTokens: number,
|
|
117
|
+
keepLastN = 4
|
|
118
|
+
): { messages: Message[]; truncated: number } {
|
|
119
|
+
let totalTokens = 0;
|
|
120
|
+
let truncated = 0;
|
|
121
|
+
|
|
122
|
+
const result: Message[] = [];
|
|
123
|
+
const toKeep = messages.slice(-keepLastN);
|
|
124
|
+
|
|
125
|
+
for (const msg of messages.slice(0, -keepLastN)) {
|
|
126
|
+
const tokens = estimateTokens(msg.content);
|
|
127
|
+
if (totalTokens + tokens < maxTokens * 0.7) {
|
|
128
|
+
result.push(msg);
|
|
129
|
+
totalTokens += tokens;
|
|
130
|
+
} else {
|
|
131
|
+
truncated++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
result.push(...toKeep);
|
|
136
|
+
|
|
137
|
+
return { messages: result, truncated };
|
|
138
|
+
}
|