@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,270 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.ts";
|
|
2
|
+
|
|
3
|
+
export interface TokenBucketConfig {
|
|
4
|
+
maxTokens: number;
|
|
5
|
+
refillRate: number;
|
|
6
|
+
refillIntervalMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TokenBucket {
|
|
10
|
+
tokens: number;
|
|
11
|
+
lastUpdate: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RateLimitResult {
|
|
15
|
+
allowed: boolean;
|
|
16
|
+
remaining: number;
|
|
17
|
+
retryAfterMs: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TokenBucketStats {
|
|
21
|
+
totalBuckets: number;
|
|
22
|
+
activeBuckets: number;
|
|
23
|
+
totalTokensAvailable: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TokenBucketRateLimiter {
|
|
27
|
+
private buckets: Map<string, TokenBucket> = new Map();
|
|
28
|
+
private config: Required<TokenBucketConfig>;
|
|
29
|
+
private log = logger.child("token-bucket-limiter");
|
|
30
|
+
|
|
31
|
+
constructor(config: TokenBucketConfig) {
|
|
32
|
+
this.config = {
|
|
33
|
+
maxTokens: config.maxTokens,
|
|
34
|
+
refillRate: config.refillRate,
|
|
35
|
+
refillIntervalMs: config.refillIntervalMs ?? 1000,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.startCleanup();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
canProceed(key: string): RateLimitResult {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
let bucket = this.buckets.get(key);
|
|
44
|
+
|
|
45
|
+
if (!bucket) {
|
|
46
|
+
bucket = {
|
|
47
|
+
tokens: this.config.maxTokens,
|
|
48
|
+
lastUpdate: now,
|
|
49
|
+
};
|
|
50
|
+
this.buckets.set(key, bucket);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const elapsed = now - bucket.lastUpdate;
|
|
54
|
+
const tokensToAdd = (elapsed / this.config.refillIntervalMs) * this.config.refillRate;
|
|
55
|
+
bucket.tokens = Math.min(this.config.maxTokens, bucket.tokens + tokensToAdd);
|
|
56
|
+
bucket.lastUpdate = now;
|
|
57
|
+
|
|
58
|
+
if (bucket.tokens >= 1) {
|
|
59
|
+
bucket.tokens--;
|
|
60
|
+
return {
|
|
61
|
+
allowed: true,
|
|
62
|
+
remaining: Math.floor(bucket.tokens),
|
|
63
|
+
retryAfterMs: 0,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const retryAfterMs = Math.ceil(
|
|
68
|
+
((1 - bucket.tokens) / this.config.refillRate) * this.config.refillIntervalMs
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
this.log.debug(`Rate limit hit for ${key}, retry after ${retryAfterMs}ms`);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
allowed: false,
|
|
75
|
+
remaining: 0,
|
|
76
|
+
retryAfterMs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
consume(key: string, tokens: number = 1): RateLimitResult {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
let bucket = this.buckets.get(key);
|
|
83
|
+
|
|
84
|
+
if (!bucket) {
|
|
85
|
+
bucket = {
|
|
86
|
+
tokens: this.config.maxTokens,
|
|
87
|
+
lastUpdate: now,
|
|
88
|
+
};
|
|
89
|
+
this.buckets.set(key, bucket);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const elapsed = now - bucket.lastUpdate;
|
|
93
|
+
const tokensToAdd = (elapsed / this.config.refillIntervalMs) * this.config.refillRate;
|
|
94
|
+
bucket.tokens = Math.min(this.config.maxTokens, bucket.tokens + tokensToAdd);
|
|
95
|
+
bucket.lastUpdate = now;
|
|
96
|
+
|
|
97
|
+
if (bucket.tokens >= tokens) {
|
|
98
|
+
bucket.tokens -= tokens;
|
|
99
|
+
return {
|
|
100
|
+
allowed: true,
|
|
101
|
+
remaining: Math.floor(bucket.tokens),
|
|
102
|
+
retryAfterMs: 0,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const retryAfterMs = Math.ceil(
|
|
107
|
+
((tokens - bucket.tokens) / this.config.refillRate) * this.config.refillIntervalMs
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
allowed: false,
|
|
112
|
+
remaining: 0,
|
|
113
|
+
retryAfterMs,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
peek(key: string): number {
|
|
118
|
+
const bucket = this.buckets.get(key);
|
|
119
|
+
if (!bucket) return this.config.maxTokens;
|
|
120
|
+
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
const elapsed = now - bucket.lastUpdate;
|
|
123
|
+
const tokensToAdd = (elapsed / this.config.refillIntervalMs) * this.config.refillRate;
|
|
124
|
+
|
|
125
|
+
return Math.min(this.config.maxTokens, bucket.tokens + tokensToAdd);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
refill(key: string, tokens?: number): void {
|
|
129
|
+
const bucket = this.buckets.get(key);
|
|
130
|
+
if (!bucket) return;
|
|
131
|
+
|
|
132
|
+
bucket.tokens = Math.min(
|
|
133
|
+
this.config.maxTokens,
|
|
134
|
+
bucket.tokens + (tokens ?? this.config.maxTokens)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
reset(key: string): void {
|
|
139
|
+
this.buckets.delete(key);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
resetAll(): void {
|
|
143
|
+
this.buckets.clear();
|
|
144
|
+
this.log.info("All rate limit buckets cleared");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getStats(): TokenBucketStats {
|
|
148
|
+
let totalTokens = 0;
|
|
149
|
+
let activeBuckets = 0;
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
|
|
152
|
+
for (const bucket of this.buckets.values()) {
|
|
153
|
+
const elapsed = now - bucket.lastUpdate;
|
|
154
|
+
const tokens = Math.min(
|
|
155
|
+
this.config.maxTokens,
|
|
156
|
+
bucket.tokens + (elapsed / this.config.refillIntervalMs) * this.config.refillRate
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (tokens < this.config.maxTokens) {
|
|
160
|
+
activeBuckets++;
|
|
161
|
+
}
|
|
162
|
+
totalTokens += tokens;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
totalBuckets: this.buckets.size,
|
|
167
|
+
activeBuckets,
|
|
168
|
+
totalTokensAvailable: Math.floor(totalTokens),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private startCleanup(): void {
|
|
173
|
+
setInterval(() => {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
for (const [key, bucket] of this.buckets) {
|
|
176
|
+
const elapsed = now - bucket.lastUpdate;
|
|
177
|
+
const fullTokens =
|
|
178
|
+
bucket.tokens + (elapsed / this.config.refillIntervalMs) * this.config.refillRate;
|
|
179
|
+
|
|
180
|
+
if (fullTokens >= this.config.maxTokens) {
|
|
181
|
+
this.buckets.delete(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}, 5 * 60 * 1000);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface SlidingWindowConfig {
|
|
189
|
+
windowMs: number;
|
|
190
|
+
maxRequests: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface SlidingWindowEntry {
|
|
194
|
+
timestamps: number[];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export class SlidingWindowRateLimiter {
|
|
198
|
+
private windows: Map<string, SlidingWindowEntry> = new Map();
|
|
199
|
+
private config: SlidingWindowConfig;
|
|
200
|
+
private log = logger.child("sliding-window-limiter");
|
|
201
|
+
|
|
202
|
+
constructor(config: SlidingWindowConfig) {
|
|
203
|
+
this.config = config;
|
|
204
|
+
this.startCleanup();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
check(key: string): RateLimitResult {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
const windowStart = now - this.config.windowMs;
|
|
210
|
+
|
|
211
|
+
let entry = this.windows.get(key);
|
|
212
|
+
if (!entry) {
|
|
213
|
+
entry = { timestamps: [] };
|
|
214
|
+
this.windows.set(key, entry);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
|
|
218
|
+
|
|
219
|
+
if (entry.timestamps.length >= this.config.maxRequests) {
|
|
220
|
+
const oldestInWindow = entry.timestamps[0];
|
|
221
|
+
const retryAfterMs = oldestInWindow + this.config.windowMs - now;
|
|
222
|
+
|
|
223
|
+
this.log.debug(`Sliding window limit hit for ${key}`);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
allowed: false,
|
|
227
|
+
remaining: 0,
|
|
228
|
+
retryAfterMs: Math.max(0, retryAfterMs),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
entry.timestamps.push(now);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
allowed: true,
|
|
236
|
+
remaining: this.config.maxRequests - entry.timestamps.length,
|
|
237
|
+
retryAfterMs: 0,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
reset(key: string): void {
|
|
242
|
+
this.windows.delete(key);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
resetAll(): void {
|
|
246
|
+
this.windows.clear();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private startCleanup(): void {
|
|
250
|
+
setInterval(() => {
|
|
251
|
+
const now = Date.now();
|
|
252
|
+
const windowStart = now - this.config.windowMs;
|
|
253
|
+
|
|
254
|
+
for (const [key, entry] of this.windows) {
|
|
255
|
+
entry.timestamps = entry.timestamps.filter((t) => t > windowStart);
|
|
256
|
+
if (entry.timestamps.length === 0) {
|
|
257
|
+
this.windows.delete(key);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}, 60 * 1000);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createTokenBucketLimiter(config: TokenBucketConfig): TokenBucketRateLimiter {
|
|
265
|
+
return new TokenBucketRateLimiter(config);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function createSlidingWindowLimiter(config: SlidingWindowConfig): SlidingWindowRateLimiter {
|
|
269
|
+
return new SlidingWindowRateLimiter(config);
|
|
270
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { PairingCode, PairingConfig, PairingStats } from "./Pairing.ts";
|
|
2
|
+
export { PairingService } from "./Pairing.ts";
|
|
3
|
+
export type { TokenBucketConfig, TokenBucket, RateLimitResult, TokenBucketStats } from "./RateLimit.ts";
|
|
4
|
+
export { TokenBucketRateLimiter } from "./RateLimit.ts";
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { YAML } from "bun";
|
|
4
|
+
|
|
5
|
+
// Static bundled data generated by packages/skills/scripts/generate-bundle.ts
|
|
6
|
+
// Loaded at build time so it works inside dist/hive.js without FS access
|
|
7
|
+
let STATIC_BUNDLED_DATA: Array<{
|
|
8
|
+
name: string; description: string; category: string; version: string;
|
|
9
|
+
tools: string[]; triggers: string[]; body: string;
|
|
10
|
+
}> = [];
|
|
11
|
+
try {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
+
STATIC_BUNDLED_DATA = require("./bundled-data.generated").BUNDLED_SKILLS_DATA ?? [];
|
|
14
|
+
} catch {
|
|
15
|
+
// Not generated yet (dev mode without running generate-bundle script) — FS scan used instead
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SkillsConfig {
|
|
19
|
+
allowBundled?: string[];
|
|
20
|
+
managedDir?: string;
|
|
21
|
+
extraDirs?: string[];
|
|
22
|
+
hotReload?: boolean;
|
|
23
|
+
maxSkillSizeKB?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Config {
|
|
27
|
+
skills?: SkillsConfig;
|
|
28
|
+
workspacePath?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Logger {
|
|
32
|
+
debug: (msg: string, ...args: unknown[]) => void;
|
|
33
|
+
info: (msg: string, ...args: unknown[]) => void;
|
|
34
|
+
warn: (msg: string, ...args: unknown[]) => void;
|
|
35
|
+
error: (msg: string, ...args: unknown[]) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createLogger(): Logger {
|
|
39
|
+
return {
|
|
40
|
+
debug: (msg, ...args) => console.debug(`[skills] ${msg}`, ...args),
|
|
41
|
+
info: (msg, ...args) => console.info(`[skills] ${msg}`, ...args),
|
|
42
|
+
warn: (msg, ...args) => console.warn(`[skills] ${msg}`, ...args),
|
|
43
|
+
error: (msg, ...args) => console.error(`[skills] ${msg}`, ...args),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SkillStep {
|
|
48
|
+
step: number;
|
|
49
|
+
action: string;
|
|
50
|
+
instruction: string;
|
|
51
|
+
output?: string;
|
|
52
|
+
params?: Record<string, any>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface OutputFormat {
|
|
56
|
+
structure: "markdown" | "code" | "json" | "text";
|
|
57
|
+
language?: string;
|
|
58
|
+
sections?: string[];
|
|
59
|
+
max_length?: string;
|
|
60
|
+
include_diff?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SkillExample {
|
|
64
|
+
user_input: string;
|
|
65
|
+
expected_behavior: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SkillMetadata {
|
|
69
|
+
name: string;
|
|
70
|
+
version?: string;
|
|
71
|
+
author?: string;
|
|
72
|
+
description: string;
|
|
73
|
+
icon?: string;
|
|
74
|
+
category?: string;
|
|
75
|
+
permissions?: string[];
|
|
76
|
+
dependencies?: string[];
|
|
77
|
+
tools?: string[];
|
|
78
|
+
|
|
79
|
+
// Structured skill fields
|
|
80
|
+
triggers?: string[]; // Patrones de activación explícita
|
|
81
|
+
steps?: SkillStep[]; // Workflow secuenciado
|
|
82
|
+
rules?: string[]; // Reglas de ejecución
|
|
83
|
+
output_format?: OutputFormat; // Estructura de output esperado
|
|
84
|
+
preferred_agents?: string[]; // Agents preferidos para delegación
|
|
85
|
+
examples?: SkillExample[]; // Ejemplos de uso
|
|
86
|
+
|
|
87
|
+
requirements?: {
|
|
88
|
+
os?: string[];
|
|
89
|
+
bins?: string[];
|
|
90
|
+
env?: string[];
|
|
91
|
+
config?: string[];
|
|
92
|
+
};
|
|
93
|
+
// Retrocompatibilidad con el campo metadata.hive previo
|
|
94
|
+
metadata?: {
|
|
95
|
+
hive?: {
|
|
96
|
+
emoji?: string;
|
|
97
|
+
bins?: string[];
|
|
98
|
+
userInvocable?: boolean;
|
|
99
|
+
requires?: {
|
|
100
|
+
config?: string[];
|
|
101
|
+
env?: string[];
|
|
102
|
+
};
|
|
103
|
+
install?: {
|
|
104
|
+
brew?: string;
|
|
105
|
+
apt?: string;
|
|
106
|
+
npm?: string;
|
|
107
|
+
};
|
|
108
|
+
os?: string[];
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Skill {
|
|
114
|
+
name: string;
|
|
115
|
+
description: string;
|
|
116
|
+
version?: string;
|
|
117
|
+
author?: string;
|
|
118
|
+
icon?: string;
|
|
119
|
+
category?: string;
|
|
120
|
+
content: string;
|
|
121
|
+
raw: string;
|
|
122
|
+
metadata: SkillMetadata;
|
|
123
|
+
source: "bundled" | "managed" | "workspace";
|
|
124
|
+
path: string;
|
|
125
|
+
permissions?: string[];
|
|
126
|
+
dependencies?: string[];
|
|
127
|
+
tools?: string[];
|
|
128
|
+
// Structured skill fields (parsed from frontmatter)
|
|
129
|
+
triggers?: string[];
|
|
130
|
+
steps?: SkillStep[];
|
|
131
|
+
rules?: string[];
|
|
132
|
+
output_format?: OutputFormat;
|
|
133
|
+
preferred_agents?: string[];
|
|
134
|
+
examples?: SkillExample[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
|
|
138
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
139
|
+
|
|
140
|
+
if (!match) {
|
|
141
|
+
return { frontmatter: {}, body: content };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const frontmatter = YAML.parse(match[1]!) as Record<string, unknown>;
|
|
146
|
+
const body = match[2]!;
|
|
147
|
+
return { frontmatter, body };
|
|
148
|
+
} catch {
|
|
149
|
+
return { frontmatter: {}, body: content };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class SkillLoader {
|
|
154
|
+
private config: Config;
|
|
155
|
+
private log: Logger;
|
|
156
|
+
private cache: Map<string, Skill> = new Map();
|
|
157
|
+
private bundledDir: string;
|
|
158
|
+
|
|
159
|
+
constructor(config: Config) {
|
|
160
|
+
this.config = config;
|
|
161
|
+
this.log = createLogger();
|
|
162
|
+
this.bundledDir = path.join(__dirname, "bundled");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private expandPath(p: string): string {
|
|
166
|
+
if (p.startsWith("~")) {
|
|
167
|
+
return path.join(process.env.HOME ?? "", p.slice(1));
|
|
168
|
+
}
|
|
169
|
+
return p;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
loadSkill(skillPath: string, source: Skill["source"]): Skill | null {
|
|
173
|
+
try {
|
|
174
|
+
const skillMdPath = path.join(skillPath, "SKILL.md");
|
|
175
|
+
|
|
176
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
181
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
182
|
+
|
|
183
|
+
const rawMetadata = (frontmatter.metadata as any) ?? {};
|
|
184
|
+
const hiveMetadata = rawMetadata.hive ?? {};
|
|
185
|
+
|
|
186
|
+
const name = (frontmatter.name as string) ?? path.basename(skillPath);
|
|
187
|
+
const description = (frontmatter.description as string) ?? "";
|
|
188
|
+
const version = (frontmatter.version as string) ?? "0.0.1";
|
|
189
|
+
const author = (frontmatter.author as string) ?? "Anonymous";
|
|
190
|
+
const icon = (frontmatter.icon as string) ?? hiveMetadata.emoji ?? "🧩";
|
|
191
|
+
const category = (frontmatter.category as string) ?? "general";
|
|
192
|
+
|
|
193
|
+
const permissions = (frontmatter.permissions as string[]) ?? [];
|
|
194
|
+
const dependencies = (frontmatter.dependencies as string[]) ?? [];
|
|
195
|
+
const tools = (frontmatter.tools as string[]) ?? [];
|
|
196
|
+
|
|
197
|
+
// Structured skill fields
|
|
198
|
+
const triggers = (frontmatter.triggers as string[]) ?? [];
|
|
199
|
+
const steps = (frontmatter.steps as SkillStep[]) ?? [];
|
|
200
|
+
const rules = (frontmatter.rules as string[]) ?? [];
|
|
201
|
+
const output_format = (frontmatter.output_format as OutputFormat) ?? undefined;
|
|
202
|
+
const preferred_agents = (frontmatter.preferred_agents as string[]) ?? [];
|
|
203
|
+
const examples = (frontmatter.examples as SkillExample[]) ?? [];
|
|
204
|
+
|
|
205
|
+
const metadata: SkillMetadata = {
|
|
206
|
+
...(frontmatter as any),
|
|
207
|
+
name,
|
|
208
|
+
description,
|
|
209
|
+
version,
|
|
210
|
+
author,
|
|
211
|
+
icon,
|
|
212
|
+
category,
|
|
213
|
+
permissions,
|
|
214
|
+
dependencies,
|
|
215
|
+
tools,
|
|
216
|
+
triggers,
|
|
217
|
+
steps,
|
|
218
|
+
rules,
|
|
219
|
+
output_format,
|
|
220
|
+
preferred_agents,
|
|
221
|
+
examples
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const skill: Skill = {
|
|
225
|
+
name,
|
|
226
|
+
description,
|
|
227
|
+
version,
|
|
228
|
+
author,
|
|
229
|
+
icon,
|
|
230
|
+
category,
|
|
231
|
+
content: body,
|
|
232
|
+
raw: content,
|
|
233
|
+
metadata,
|
|
234
|
+
source,
|
|
235
|
+
path: skillPath,
|
|
236
|
+
permissions,
|
|
237
|
+
dependencies,
|
|
238
|
+
tools,
|
|
239
|
+
triggers,
|
|
240
|
+
steps,
|
|
241
|
+
rules,
|
|
242
|
+
output_format,
|
|
243
|
+
preferred_agents,
|
|
244
|
+
examples
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
this.cache.set(name, skill);
|
|
248
|
+
return skill;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
this.log.error(`Failed to load skill: ${skillPath}`, { error: (error as Error).message });
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
loadBundledSkills(): Skill[] {
|
|
256
|
+
// Use static pre-generated data when available (production bundle, dist/hive.js)
|
|
257
|
+
if (STATIC_BUNDLED_DATA.length > 0) {
|
|
258
|
+
const skills: Skill[] = STATIC_BUNDLED_DATA.map((entry) => ({
|
|
259
|
+
name: entry.name,
|
|
260
|
+
description: entry.description,
|
|
261
|
+
version: entry.version,
|
|
262
|
+
author: "Hive",
|
|
263
|
+
icon: "🧩",
|
|
264
|
+
category: entry.category,
|
|
265
|
+
content: entry.body,
|
|
266
|
+
raw: entry.body,
|
|
267
|
+
metadata: {
|
|
268
|
+
name: entry.name,
|
|
269
|
+
description: entry.description,
|
|
270
|
+
version: entry.version,
|
|
271
|
+
category: entry.category,
|
|
272
|
+
tools: entry.tools,
|
|
273
|
+
triggers: entry.triggers,
|
|
274
|
+
},
|
|
275
|
+
source: "bundled" as const,
|
|
276
|
+
path: "",
|
|
277
|
+
tools: entry.tools,
|
|
278
|
+
triggers: entry.triggers,
|
|
279
|
+
}));
|
|
280
|
+
this.log.debug(`Loaded ${skills.length} bundled skills from static data`);
|
|
281
|
+
return skills;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Fallback: FS scan for development (before generate-bundle is run)
|
|
285
|
+
const skills: Map<string, Skill> = new Map();
|
|
286
|
+
|
|
287
|
+
if (!fs.existsSync(this.bundledDir)) {
|
|
288
|
+
this.log.debug(`Bundled skills directory not found: ${this.bundledDir}`);
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const scanDir = (dir: string) => {
|
|
293
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
294
|
+
if (!entry.isDirectory()) continue;
|
|
295
|
+
const entryPath = path.join(dir, entry.name);
|
|
296
|
+
if (fs.existsSync(path.join(entryPath, "SKILL.md"))) {
|
|
297
|
+
const skill = this.loadSkill(entryPath, "bundled");
|
|
298
|
+
if (skill) skills.set(skill.name, skill);
|
|
299
|
+
} else {
|
|
300
|
+
// Es un directorio de categoría — bajar un nivel más
|
|
301
|
+
scanDir(entryPath);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
scanDir(this.bundledDir);
|
|
307
|
+
|
|
308
|
+
this.log.debug(`Loaded ${skills.size} bundled skills from filesystem`);
|
|
309
|
+
return Array.from(skills.values());
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
loadAllSkills(): Skill[] {
|
|
313
|
+
const skills: Map<string, Skill> = new Map();
|
|
314
|
+
const allowBundled = this.config.skills?.allowBundled;
|
|
315
|
+
|
|
316
|
+
// 1. Load bundled skills (lowest priority)
|
|
317
|
+
const bundledSkills = this.loadBundledSkills();
|
|
318
|
+
for (const skill of bundledSkills) {
|
|
319
|
+
// If allowBundled is undefined, empty, or contains the skill name, include it
|
|
320
|
+
if (!allowBundled || allowBundled.length === 0 || allowBundled.includes(skill.name)) {
|
|
321
|
+
skills.set(skill.name, skill);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 2. Load managed skills (medium priority)
|
|
326
|
+
const managedDir = this.expandPath(this.config.skills?.managedDir ?? "~/.hive/skills");
|
|
327
|
+
if (fs.existsSync(managedDir)) {
|
|
328
|
+
for (const entry of fs.readdirSync(managedDir, { withFileTypes: true })) {
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
const skill = this.loadSkill(path.join(managedDir, entry.name), "managed");
|
|
331
|
+
if (skill) {
|
|
332
|
+
skills.set(skill.name, skill);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 3. Load extra directories
|
|
339
|
+
const extraDirs = this.config.skills?.extraDirs ?? [];
|
|
340
|
+
for (const extraDir of extraDirs) {
|
|
341
|
+
const expandedDir = this.expandPath(extraDir);
|
|
342
|
+
if (fs.existsSync(expandedDir)) {
|
|
343
|
+
for (const entry of fs.readdirSync(expandedDir, { withFileTypes: true })) {
|
|
344
|
+
if (entry.isDirectory()) {
|
|
345
|
+
const skill = this.loadSkill(path.join(expandedDir, entry.name), "managed");
|
|
346
|
+
if (skill) {
|
|
347
|
+
skills.set(skill.name, skill);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 4. Load workspace skills (highest priority)
|
|
355
|
+
const workspacePath = this.config.workspacePath ?? process.cwd();
|
|
356
|
+
const workspaceSkillsDir = path.join(workspacePath, "skills");
|
|
357
|
+
if (fs.existsSync(workspaceSkillsDir)) {
|
|
358
|
+
for (const entry of fs.readdirSync(workspaceSkillsDir, { withFileTypes: true })) {
|
|
359
|
+
if (entry.isDirectory()) {
|
|
360
|
+
const skill = this.loadSkill(path.join(workspaceSkillsDir, entry.name), "workspace");
|
|
361
|
+
if (skill) {
|
|
362
|
+
skills.set(skill.name, skill);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this.log.info(`Loaded ${skills.size} total skills`);
|
|
369
|
+
return Array.from(skills.values());
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
getSkill(name: string): Skill | undefined {
|
|
373
|
+
return this.cache.get(name);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
listSkills(): string[] {
|
|
377
|
+
return Array.from(this.cache.keys());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
clearCache(): void {
|
|
381
|
+
this.cache.clear();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function createSkillLoader(config: Config): SkillLoader {
|
|
386
|
+
return new SkillLoader(config);
|
|
387
|
+
}
|
|
388
|
+
|