@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,312 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.ts";
|
|
2
|
+
|
|
3
|
+
export interface SessionState {
|
|
4
|
+
id: string;
|
|
5
|
+
agentId: string;
|
|
6
|
+
channel: string;
|
|
7
|
+
userId: string;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
lastActivityAt: number;
|
|
10
|
+
messageCount: number;
|
|
11
|
+
status: "active" | "idle" | "closed";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AgentState {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
status: "ready" | "busy" | "error";
|
|
18
|
+
currentSessionId?: string;
|
|
19
|
+
lastError?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ChannelState {
|
|
23
|
+
name: string;
|
|
24
|
+
accountId: string;
|
|
25
|
+
status: "connected" | "disconnected" | "error";
|
|
26
|
+
lastActivity?: number;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MetricsState {
|
|
31
|
+
totalMessages: number;
|
|
32
|
+
totalSessions: number;
|
|
33
|
+
totalToolCalls: number;
|
|
34
|
+
averageResponseTime: number;
|
|
35
|
+
errors: number;
|
|
36
|
+
startedAt: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface HiveState {
|
|
40
|
+
sessions: Map<string, SessionState>;
|
|
41
|
+
agents: Map<string, AgentState>;
|
|
42
|
+
channels: Map<string, ChannelState>;
|
|
43
|
+
metrics: MetricsState;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface StateSnapshot {
|
|
47
|
+
id: string;
|
|
48
|
+
timestamp: number;
|
|
49
|
+
state: HiveState;
|
|
50
|
+
reason?: string;
|
|
51
|
+
action?: string;
|
|
52
|
+
correlationId?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface StateStoreOptions {
|
|
56
|
+
maxSnapshots?: number;
|
|
57
|
+
enableSnapshots?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const defaultMetrics: MetricsState = {
|
|
61
|
+
totalMessages: 0,
|
|
62
|
+
totalSessions: 0,
|
|
63
|
+
totalToolCalls: 0,
|
|
64
|
+
averageResponseTime: 0,
|
|
65
|
+
errors: 0,
|
|
66
|
+
startedAt: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export class StateStore {
|
|
70
|
+
private state: HiveState;
|
|
71
|
+
private snapshots: StateSnapshot[] = [];
|
|
72
|
+
private readonly maxSnapshots: number;
|
|
73
|
+
private readonly enableSnapshots: boolean;
|
|
74
|
+
private listeners: Set<(state: Readonly<HiveState>) => void> = new Set();
|
|
75
|
+
private correlationId?: string;
|
|
76
|
+
|
|
77
|
+
constructor(options: StateStoreOptions = {}) {
|
|
78
|
+
this.maxSnapshots = options.maxSnapshots ?? 100;
|
|
79
|
+
this.enableSnapshots = options.enableSnapshots ?? true;
|
|
80
|
+
this.state = this.createInitialState();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setCorrelationId(id: string): void {
|
|
84
|
+
this.correlationId = id;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getCorrelationId(): string | undefined {
|
|
88
|
+
return this.correlationId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clearCorrelationId(): void {
|
|
92
|
+
this.correlationId = undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private createInitialState(): HiveState {
|
|
96
|
+
return {
|
|
97
|
+
sessions: new Map(),
|
|
98
|
+
agents: new Map(),
|
|
99
|
+
channels: new Map(),
|
|
100
|
+
metrics: { ...defaultMetrics },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getState(): Readonly<HiveState> {
|
|
105
|
+
return this.state;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
update(updater: (draft: HiveState) => void, reason?: string): void {
|
|
109
|
+
const newState = this.cloneState(this.state);
|
|
110
|
+
updater(newState);
|
|
111
|
+
|
|
112
|
+
if (this.enableSnapshots) {
|
|
113
|
+
this.saveSnapshot(newState, reason);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.state = newState;
|
|
117
|
+
this.notifyListeners();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private cloneState(state: HiveState): HiveState {
|
|
121
|
+
return {
|
|
122
|
+
sessions: new Map(state.sessions),
|
|
123
|
+
agents: new Map(state.agents),
|
|
124
|
+
channels: new Map(state.channels),
|
|
125
|
+
metrics: { ...state.metrics },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private saveSnapshot(state: HiveState, reason?: string): void {
|
|
130
|
+
const snapshot: StateSnapshot = {
|
|
131
|
+
id: crypto.randomUUID(),
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
state: this.cloneState(state),
|
|
134
|
+
reason,
|
|
135
|
+
action: reason,
|
|
136
|
+
correlationId: this.correlationId,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.snapshots.push(snapshot);
|
|
140
|
+
|
|
141
|
+
if (this.snapshots.length > this.maxSnapshots) {
|
|
142
|
+
this.snapshots.shift();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getSnapshotAt(timestamp: number): StateSnapshot | undefined {
|
|
147
|
+
return this.snapshots.find((s) => s.timestamp >= timestamp);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getSnapshotById(id: string): StateSnapshot | undefined {
|
|
151
|
+
return this.snapshots.find((s) => s.id === id);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getAllSnapshots(): StateSnapshot[] {
|
|
155
|
+
return [...this.snapshots];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getRecentSnapshots(count: number = 10): StateSnapshot[] {
|
|
159
|
+
return this.snapshots.slice(-count);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
subscribe(listener: (state: Readonly<HiveState>) => void): () => void {
|
|
163
|
+
this.listeners.add(listener);
|
|
164
|
+
return () => this.listeners.delete(listener);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private notifyListeners(): void {
|
|
168
|
+
const state = this.getState();
|
|
169
|
+
for (const listener of this.listeners) {
|
|
170
|
+
try {
|
|
171
|
+
listener(state);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.error("[StateStore] Listener error:", { error: (error as Error).message });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
createSession(session: Omit<SessionState, "createdAt" | "lastActivityAt" | "messageCount" | "status">): SessionState {
|
|
179
|
+
const newSession: SessionState = {
|
|
180
|
+
...session,
|
|
181
|
+
createdAt: Date.now(),
|
|
182
|
+
lastActivityAt: Date.now(),
|
|
183
|
+
messageCount: 0,
|
|
184
|
+
status: "active",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
this.update((state) => {
|
|
188
|
+
state.sessions.set(session.id, newSession);
|
|
189
|
+
state.metrics.totalSessions++;
|
|
190
|
+
}, `Session created: ${session.id}`);
|
|
191
|
+
|
|
192
|
+
return newSession;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
updateSession(sessionId: string, updates: Partial<SessionState>): void {
|
|
196
|
+
this.update((state) => {
|
|
197
|
+
const session = state.sessions.get(sessionId);
|
|
198
|
+
if (session) {
|
|
199
|
+
state.sessions.set(sessionId, { ...session, ...updates });
|
|
200
|
+
}
|
|
201
|
+
}, `Session updated: ${sessionId}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
closeSession(sessionId: string): void {
|
|
205
|
+
this.update((state) => {
|
|
206
|
+
const session = state.sessions.get(sessionId);
|
|
207
|
+
if (session) {
|
|
208
|
+
state.sessions.set(sessionId, { ...session, status: "closed" });
|
|
209
|
+
}
|
|
210
|
+
}, `Session closed: ${sessionId}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
incrementMessageCount(sessionId: string): void {
|
|
214
|
+
this.update((state) => {
|
|
215
|
+
const session = state.sessions.get(sessionId);
|
|
216
|
+
if (session) {
|
|
217
|
+
session.messageCount++;
|
|
218
|
+
session.lastActivityAt = Date.now();
|
|
219
|
+
state.sessions.set(sessionId, session);
|
|
220
|
+
state.metrics.totalMessages++;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
registerAgent(agent: Omit<AgentState, "status">): void {
|
|
226
|
+
this.update((state) => {
|
|
227
|
+
state.agents.set(agent.id, { ...agent, status: "ready" });
|
|
228
|
+
}, `Agent registered: ${agent.id}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
updateAgent(agentId: string, updates: Partial<AgentState>): void {
|
|
232
|
+
this.update((state) => {
|
|
233
|
+
const agent = state.agents.get(agentId);
|
|
234
|
+
if (agent) {
|
|
235
|
+
state.agents.set(agentId, { ...agent, ...updates });
|
|
236
|
+
}
|
|
237
|
+
}, `Agent updated: ${agentId}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
updateChannel(channelName: string, accountId: string, updates: Partial<ChannelState>): void {
|
|
241
|
+
this.update((state) => {
|
|
242
|
+
const key = `${channelName}:${accountId}`;
|
|
243
|
+
const channel = state.channels.get(key);
|
|
244
|
+
if (channel) {
|
|
245
|
+
state.channels.set(key, { ...channel, ...updates });
|
|
246
|
+
} else {
|
|
247
|
+
state.channels.set(key, {
|
|
248
|
+
name: channelName,
|
|
249
|
+
accountId,
|
|
250
|
+
status: "disconnected",
|
|
251
|
+
...updates,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}, `Channel updated: ${channelName}:${accountId}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
recordToolCall(duration: number, success: boolean): void {
|
|
258
|
+
this.update((state) => {
|
|
259
|
+
state.metrics.totalToolCalls++;
|
|
260
|
+
if (!success) {
|
|
261
|
+
state.metrics.errors++;
|
|
262
|
+
}
|
|
263
|
+
const total = state.metrics.totalToolCalls;
|
|
264
|
+
const prevAvg = state.metrics.averageResponseTime;
|
|
265
|
+
state.metrics.averageResponseTime = prevAvg + (duration - prevAvg) / total;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
reset(): void {
|
|
270
|
+
this.state = this.createInitialState();
|
|
271
|
+
this.snapshots = [];
|
|
272
|
+
this.saveSnapshot(this.state, "Store reset");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
exportState(): string {
|
|
276
|
+
const exportable = {
|
|
277
|
+
sessions: Object.fromEntries(this.state.sessions),
|
|
278
|
+
agents: Object.fromEntries(this.state.agents),
|
|
279
|
+
channels: Object.fromEntries(this.state.channels),
|
|
280
|
+
metrics: this.state.metrics,
|
|
281
|
+
};
|
|
282
|
+
return JSON.stringify(exportable, null, 2);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export(): string {
|
|
286
|
+
return this.exportState();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
getStats(): {
|
|
290
|
+
sessionsCount: number;
|
|
291
|
+
activeSessions: number;
|
|
292
|
+
agentsCount: number;
|
|
293
|
+
channelsCount: number;
|
|
294
|
+
snapshotsCount: number;
|
|
295
|
+
uptime: number;
|
|
296
|
+
} {
|
|
297
|
+
const activeSessions = Array.from(this.state.sessions.values()).filter(
|
|
298
|
+
(s) => s.status === "active"
|
|
299
|
+
).length;
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
sessionsCount: this.state.sessions.size,
|
|
303
|
+
activeSessions,
|
|
304
|
+
agentsCount: this.state.agents.size,
|
|
305
|
+
channelsCount: this.state.channels.size,
|
|
306
|
+
snapshotsCount: this.snapshots.length,
|
|
307
|
+
uptime: Date.now() - this.state.metrics.startedAt,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export const stateStore = new StateStore();
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite"
|
|
2
|
+
|
|
3
|
+
export interface StoreValue {
|
|
4
|
+
[key: string]: unknown
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface StoreRecord {
|
|
8
|
+
namespace: string
|
|
9
|
+
key: string
|
|
10
|
+
value: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class BunSqliteStore {
|
|
14
|
+
private db: Database
|
|
15
|
+
private cache: Map<string, StoreValue> = new Map()
|
|
16
|
+
|
|
17
|
+
constructor(db: Database) {
|
|
18
|
+
this.db = db
|
|
19
|
+
this.initTable()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private initTable() {
|
|
23
|
+
this.db.query(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS lg_store (
|
|
25
|
+
namespace TEXT NOT NULL,
|
|
26
|
+
key TEXT NOT NULL,
|
|
27
|
+
value TEXT NOT NULL,
|
|
28
|
+
PRIMARY KEY (namespace, key)
|
|
29
|
+
)
|
|
30
|
+
`).run()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async get(key: string): Promise<StoreValue | undefined> {
|
|
34
|
+
const cacheKey = `global:${key}`
|
|
35
|
+
if (this.cache.has(cacheKey)) {
|
|
36
|
+
return this.cache.get(cacheKey)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const row = this.db
|
|
40
|
+
.query<StoreRecord, [string]>(
|
|
41
|
+
"SELECT * FROM lg_store WHERE namespace = 'global' AND key = ?"
|
|
42
|
+
)
|
|
43
|
+
.get(key)
|
|
44
|
+
|
|
45
|
+
if (!row) return undefined
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(row.value) as StoreValue
|
|
49
|
+
this.cache.set(cacheKey, parsed)
|
|
50
|
+
return parsed
|
|
51
|
+
} catch {
|
|
52
|
+
const fallback = { value: row.value }
|
|
53
|
+
this.cache.set(cacheKey, fallback)
|
|
54
|
+
return fallback
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async set(key: string, value: StoreValue): Promise<void> {
|
|
59
|
+
const serialized = JSON.stringify(value)
|
|
60
|
+
this.db
|
|
61
|
+
.query(
|
|
62
|
+
"INSERT OR REPLACE INTO lg_store (namespace, key, value) VALUES (?, ?, ?)"
|
|
63
|
+
)
|
|
64
|
+
.run("global", key, serialized)
|
|
65
|
+
this.cache.set(`global:${key}`, value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async delete(key: string): Promise<void> {
|
|
69
|
+
this.db
|
|
70
|
+
.query("DELETE FROM lg_store WHERE namespace = 'global' AND key = ?")
|
|
71
|
+
.run(key)
|
|
72
|
+
this.cache.delete(`global:${key}`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async *list(prefix?: string): AsyncGenerator<[string, StoreValue]> {
|
|
76
|
+
let query = "SELECT * FROM lg_store WHERE namespace = 'global'"
|
|
77
|
+
const params: string[] = []
|
|
78
|
+
|
|
79
|
+
if (prefix) {
|
|
80
|
+
query += " AND key LIKE ?"
|
|
81
|
+
params.push(`${prefix}%`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const rows = this.db.query<StoreRecord, string[]>(query).all(...params)
|
|
85
|
+
|
|
86
|
+
for (const row of rows) {
|
|
87
|
+
try {
|
|
88
|
+
const value = JSON.parse(row.value) as StoreValue
|
|
89
|
+
yield [row.key, value]
|
|
90
|
+
} catch {
|
|
91
|
+
yield [row.key, { value: row.value }]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getByNamespace(namespace: string, key: string): Promise<StoreValue | undefined> {
|
|
97
|
+
const cacheKey = `${namespace}:${key}`
|
|
98
|
+
if (this.cache.has(cacheKey)) {
|
|
99
|
+
return this.cache.get(cacheKey)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const row = this.db
|
|
103
|
+
.query<StoreRecord, [string, string]>(
|
|
104
|
+
"SELECT * FROM lg_store WHERE namespace = ? AND key = ?"
|
|
105
|
+
)
|
|
106
|
+
.get(namespace, key)
|
|
107
|
+
|
|
108
|
+
if (!row) return undefined
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(row.value) as StoreValue
|
|
112
|
+
this.cache.set(cacheKey, parsed)
|
|
113
|
+
return parsed
|
|
114
|
+
} catch {
|
|
115
|
+
const fallback = { value: row.value }
|
|
116
|
+
this.cache.set(cacheKey, fallback)
|
|
117
|
+
return fallback
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async setByNamespace(namespace: string, key: string, value: StoreValue): Promise<void> {
|
|
122
|
+
const serialized = JSON.stringify(value)
|
|
123
|
+
this.db
|
|
124
|
+
.query(
|
|
125
|
+
"INSERT OR REPLACE INTO lg_store (namespace, key, value) VALUES (?, ?, ?)"
|
|
126
|
+
)
|
|
127
|
+
.run(namespace, key, serialized)
|
|
128
|
+
this.cache.set(`${namespace}:${key}`, value)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let storeInstance: BunSqliteStore | null = null
|
|
133
|
+
|
|
134
|
+
export function getGlobalStore(db: Database): BunSqliteStore {
|
|
135
|
+
if (!storeInstance) {
|
|
136
|
+
storeInstance = new BunSqliteStore(db)
|
|
137
|
+
}
|
|
138
|
+
return storeInstance
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function invalidateStore(): void {
|
|
142
|
+
storeInstance = null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function loadContextToStore(db: Database): Promise<void> {
|
|
146
|
+
const store = getGlobalStore(db)
|
|
147
|
+
|
|
148
|
+
const tools = db.query<any, []>("SELECT * FROM tools WHERE enabled = 1").all()
|
|
149
|
+
await store.set("tools:active", {
|
|
150
|
+
tools: tools.map((t: any) => ({
|
|
151
|
+
id: t.id,
|
|
152
|
+
name: t.name,
|
|
153
|
+
description: t.description,
|
|
154
|
+
category: t.category,
|
|
155
|
+
schema: t.schema ? JSON.parse(t.schema) : null,
|
|
156
|
+
}))
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const skills = db.query<any, []>("SELECT * FROM skills WHERE enabled = 1").all()
|
|
160
|
+
await store.set("skills:active", {
|
|
161
|
+
skills: skills.map((s: any) => ({
|
|
162
|
+
id: s.id,
|
|
163
|
+
name: s.name,
|
|
164
|
+
description: s.description,
|
|
165
|
+
}))
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const mcpServers = db.query<any, []>("SELECT * FROM mcp_servers WHERE enabled = 1 AND status = 'connected'").all()
|
|
169
|
+
await store.set("mcp:connected", {
|
|
170
|
+
servers: mcpServers.map((m: any) => ({
|
|
171
|
+
id: m.id,
|
|
172
|
+
name: m.name,
|
|
173
|
+
transport: m.transport,
|
|
174
|
+
command: m.command,
|
|
175
|
+
args: m.args,
|
|
176
|
+
url: m.url,
|
|
177
|
+
}))
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const ethics = db.query<any, []>("SELECT * FROM ethics WHERE enabled = 1").all()
|
|
181
|
+
await store.set("ethics:rules", {
|
|
182
|
+
rules: ethics.map((e: any) => ({
|
|
183
|
+
id: e.id,
|
|
184
|
+
name: e.name,
|
|
185
|
+
content: e.content,
|
|
186
|
+
}))
|
|
187
|
+
})
|
|
188
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv, createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
let _encryptionKey: Buffer | null = null;
|
|
7
|
+
|
|
8
|
+
function getEncryptionKey(): Buffer {
|
|
9
|
+
if (_encryptionKey) return _encryptionKey;
|
|
10
|
+
|
|
11
|
+
const masterKey = process.env.HIVE_MASTER_KEY;
|
|
12
|
+
|
|
13
|
+
if (masterKey) {
|
|
14
|
+
_encryptionKey = Buffer.from(masterKey.slice(0, 32).padEnd(32, "0"), "utf8");
|
|
15
|
+
} else {
|
|
16
|
+
const hiveDir = process.env.HIVE_HOME || path.join(homedir(), ".hive");
|
|
17
|
+
if (!existsSync(hiveDir)) {
|
|
18
|
+
mkdirSync(hiveDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
const keyPath = path.join(hiveDir, ".master.key");
|
|
21
|
+
|
|
22
|
+
if (existsSync(keyPath)) {
|
|
23
|
+
const storedKey = readFileSync(keyPath, "utf-8").trim();
|
|
24
|
+
_encryptionKey = Buffer.from(storedKey, "hex");
|
|
25
|
+
} else {
|
|
26
|
+
_encryptionKey = randomBytes(32);
|
|
27
|
+
writeFileSync(keyPath, _encryptionKey.toString("hex"), { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return _encryptionKey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface EncryptedData {
|
|
35
|
+
encrypted: string;
|
|
36
|
+
iv: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function encrypt(text: string): EncryptedData {
|
|
40
|
+
const key = getEncryptionKey();
|
|
41
|
+
const iv = randomBytes(16);
|
|
42
|
+
|
|
43
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
44
|
+
|
|
45
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
46
|
+
encrypted += cipher.final("hex");
|
|
47
|
+
const authTag = cipher.getAuthTag().toString("hex");
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
encrypted: encrypted + ":" + authTag,
|
|
51
|
+
iv: iv.toString("hex"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function decrypt(data: EncryptedData): string {
|
|
56
|
+
const key = getEncryptionKey();
|
|
57
|
+
const iv = Buffer.from(data.iv, "hex");
|
|
58
|
+
const [encrypted, authTag] = data.encrypted.split(":");
|
|
59
|
+
|
|
60
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
61
|
+
decipher.setAuthTag(Buffer.from(authTag, "hex"));
|
|
62
|
+
|
|
63
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
64
|
+
decrypted += decipher.final("utf8");
|
|
65
|
+
|
|
66
|
+
return decrypted;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function encryptApiKey(apiKey: string): { encrypted: string; iv: string } {
|
|
70
|
+
return encrypt(apiKey);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function decryptApiKey(encrypted: string, iv: string): string {
|
|
74
|
+
return decrypt({ encrypted, iv });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function encryptConfig(config: Record<string, unknown>): { encrypted: string; iv: string } {
|
|
78
|
+
return encrypt(JSON.stringify(config));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function decryptConfig(encrypted: string, iv: string): Record<string, unknown> {
|
|
82
|
+
const decrypted = decrypt({ encrypted, iv });
|
|
83
|
+
return JSON.parse(decrypted);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function hashPassword(password: string): string {
|
|
87
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
88
|
+
hasher.update(password);
|
|
89
|
+
return hasher.digest("hex");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function verifyPassword(password: string, hash: string): boolean {
|
|
93
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
94
|
+
hasher.update(password);
|
|
95
|
+
return hasher.digest("hex") === hash;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function maskApiKey(apiKey: string): string {
|
|
99
|
+
if (!apiKey || apiKey.length < 8) return "••••••••";
|
|
100
|
+
return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
|
|
101
|
+
}
|