@johpaz/hive-sdk 0.0.4 → 0.0.6
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/dist/agents/index.js +175861 -28
- package/dist/channels/index.js +11016 -7
- package/dist/cron/index.d.ts +1 -1
- package/dist/cron/index.js +3246 -24
- package/dist/database/index.js +8345 -22
- package/dist/ethics/index.js +8342 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +179592 -176
- package/dist/mcp/index.js +723 -3
- package/dist/skills/index.js +5871 -4
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +175733 -85
- package/package.json +18 -4
- package/src/cron/index.ts +6 -4
- package/src/index.ts +6 -4
- package/src/tools/index.ts +7 -4
package/dist/cron/index.js
CHANGED
|
@@ -1,25 +1,3245 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __toCommonJS = (from) => {
|
|
34
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
35
|
+
if (entry)
|
|
36
|
+
return entry;
|
|
37
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
38
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
39
|
+
for (var key of __getOwnPropNames(from))
|
|
40
|
+
if (!__hasOwnProp.call(entry, key))
|
|
41
|
+
__defProp(entry, key, {
|
|
42
|
+
get: __accessProp.bind(from, key),
|
|
43
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
__moduleCache.set(from, entry);
|
|
47
|
+
return entry;
|
|
48
|
+
};
|
|
49
|
+
var __moduleCache;
|
|
50
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
51
|
+
var __returnValue = (v) => v;
|
|
52
|
+
function __exportSetter(name, newValue) {
|
|
53
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
54
|
+
}
|
|
55
|
+
var __export = (target, all) => {
|
|
56
|
+
for (var name in all)
|
|
57
|
+
__defProp(target, name, {
|
|
58
|
+
get: all[name],
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
set: __exportSetter.bind(all, name)
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
65
|
+
var __require = import.meta.require;
|
|
66
|
+
|
|
67
|
+
// ../core/src/config/loader.ts
|
|
68
|
+
import * as z from "zod";
|
|
69
|
+
import { existsSync, readFileSync } from "fs";
|
|
70
|
+
import * as path from "path";
|
|
71
|
+
function loadEnv(hiveDir) {
|
|
72
|
+
const envPath = path.join(hiveDir, ".env");
|
|
73
|
+
if (existsSync(envPath)) {
|
|
74
|
+
try {
|
|
75
|
+
const text = readFileSync(envPath, "utf8");
|
|
76
|
+
const lines = text.split(`
|
|
77
|
+
`);
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
81
|
+
continue;
|
|
82
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
83
|
+
if (key && valueParts.length > 0) {
|
|
84
|
+
const value = valueParts.join("=").trim().replace(/^['"]|['"]$/g, "");
|
|
85
|
+
process.env[key.trim()] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function getHiveDir() {
|
|
92
|
+
if (process.env.HIVE_HOME) {
|
|
93
|
+
const hiveDir = process.env.HIVE_HOME.startsWith("~") ? path.join(process.env.HOME || "", process.env.HIVE_HOME.slice(1)) : process.env.HIVE_HOME;
|
|
94
|
+
loadEnv(hiveDir);
|
|
95
|
+
return hiveDir;
|
|
96
|
+
}
|
|
97
|
+
if (process.env.HIVE_DEV === "1" || process.env.HIVE_DEV === "true") {
|
|
98
|
+
const localDir = path.join(process.cwd(), ".hive-dev");
|
|
99
|
+
loadEnv(localDir);
|
|
100
|
+
return localDir;
|
|
101
|
+
}
|
|
102
|
+
const defaultDir = path.join(process.env.HOME || "", ".hive");
|
|
103
|
+
loadEnv(defaultDir);
|
|
104
|
+
return defaultDir;
|
|
105
|
+
}
|
|
106
|
+
function buildDefaultConfig() {
|
|
107
|
+
const hiveDir = getHiveDir();
|
|
108
|
+
return {
|
|
109
|
+
gateway: {
|
|
110
|
+
host: process.env.HIVE_HOST || "127.0.0.1",
|
|
111
|
+
port: parseInt(process.env.HIVE_PORT || "18790", 10),
|
|
112
|
+
pidFile: path.join(hiveDir, "gateway.pid"),
|
|
113
|
+
authToken: process.env.HIVE_AUTH_TOKEN || undefined,
|
|
114
|
+
tools: {
|
|
115
|
+
allow: ["*"],
|
|
116
|
+
deny: []
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
logging: {
|
|
120
|
+
level: process.env.HIVE_LOG_LEVEL || "info",
|
|
121
|
+
dir: path.join(hiveDir, "logs"),
|
|
122
|
+
maxSizeMB: 10,
|
|
123
|
+
maxFiles: 5,
|
|
124
|
+
redactSensitive: true,
|
|
125
|
+
console: true
|
|
126
|
+
},
|
|
127
|
+
agent: {
|
|
128
|
+
defaultAgentId: "main",
|
|
129
|
+
baseDir: path.join(hiveDir, "agents"),
|
|
130
|
+
context: {
|
|
131
|
+
maxTokens: 0,
|
|
132
|
+
compactionThreshold: 0.8,
|
|
133
|
+
minMessagesAfterCompaction: 4,
|
|
134
|
+
maxCompactionRetries: 3
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
models: {
|
|
138
|
+
defaultProvider: "openai",
|
|
139
|
+
defaults: {
|
|
140
|
+
openai: "gpt-4o",
|
|
141
|
+
anthropic: "claude-sonnet-4-20250514",
|
|
142
|
+
ollama: "llama3.2",
|
|
143
|
+
openrouter: "anthropic/claude-sonnet-4"
|
|
144
|
+
},
|
|
145
|
+
providers: {}
|
|
146
|
+
},
|
|
147
|
+
sessions: {
|
|
148
|
+
dir: path.join(hiveDir, "sessions"),
|
|
149
|
+
pruneAfterHours: 24,
|
|
150
|
+
maxTranscriptSizeMB: 50
|
|
151
|
+
},
|
|
152
|
+
agents: {
|
|
153
|
+
list: [
|
|
154
|
+
{
|
|
155
|
+
id: "main",
|
|
156
|
+
default: true,
|
|
157
|
+
workspace: path.join(hiveDir, "agents", "main", "workspace"),
|
|
158
|
+
description: "Default personal assistant"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
bindings: [],
|
|
163
|
+
channels: {
|
|
164
|
+
webchat: { enabled: true }
|
|
165
|
+
},
|
|
166
|
+
tools: {
|
|
167
|
+
allow: ["*"],
|
|
168
|
+
deny: [],
|
|
169
|
+
exec: {
|
|
170
|
+
enabled: true,
|
|
171
|
+
allowlist: [],
|
|
172
|
+
denylist: ["rm -rf /", "sudo", "chmod 777", "> /dev/", "mkfs"],
|
|
173
|
+
timeoutSeconds: 30,
|
|
174
|
+
workDir: path.join(process.env.HOME || "", "exec")
|
|
175
|
+
},
|
|
176
|
+
web: {
|
|
177
|
+
allowlist: [],
|
|
178
|
+
denylist: ["file://", "ftp://"],
|
|
179
|
+
timeoutSeconds: 30
|
|
180
|
+
},
|
|
181
|
+
browser: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
cdpUrl: "ws://127.0.0.1:9222",
|
|
184
|
+
headless: true,
|
|
185
|
+
timeoutMs: 30000
|
|
186
|
+
},
|
|
187
|
+
canvas: {
|
|
188
|
+
enabled: true,
|
|
189
|
+
port: 18793
|
|
190
|
+
},
|
|
191
|
+
sandbox: {
|
|
192
|
+
dm: { allow: ["*"], deny: [] },
|
|
193
|
+
group: { allow: ["*"], deny: [] }
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
skills: {
|
|
197
|
+
allowBundled: [],
|
|
198
|
+
managedDir: path.join(hiveDir, "skills"),
|
|
199
|
+
extraDirs: [],
|
|
200
|
+
hotReload: true,
|
|
201
|
+
maxSkillSizeKB: 100
|
|
202
|
+
},
|
|
203
|
+
mcp: {
|
|
204
|
+
enabled: true,
|
|
205
|
+
servers: {},
|
|
206
|
+
healthCheck: {
|
|
207
|
+
enabled: true,
|
|
208
|
+
intervalSeconds: 60
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
memory: {
|
|
212
|
+
dbPath: path.join(hiveDir, "memory.db"),
|
|
213
|
+
notesDir: path.join(hiveDir, "agents", "main", "workspace", "memory"),
|
|
214
|
+
episodic: {
|
|
215
|
+
enabled: false,
|
|
216
|
+
provider: "openai",
|
|
217
|
+
maxEpisodesPerSession: 100
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
cron: {
|
|
221
|
+
enabled: true,
|
|
222
|
+
dbPath: path.join(hiveDir, "cron.db"),
|
|
223
|
+
maxConcurrentJobs: 5,
|
|
224
|
+
timezone: "UTC"
|
|
225
|
+
},
|
|
226
|
+
retry: {
|
|
227
|
+
maxAttempts: 3,
|
|
228
|
+
initialDelayMs: 1000,
|
|
229
|
+
backoffMultiplier: 2,
|
|
230
|
+
maxDelayMs: 30000
|
|
231
|
+
},
|
|
232
|
+
security: {
|
|
233
|
+
maxMessageLength: {
|
|
234
|
+
telegram: 4096,
|
|
235
|
+
discord: 2000,
|
|
236
|
+
slack: 40000,
|
|
237
|
+
webchat: 1e5,
|
|
238
|
+
whatsapp: 65536
|
|
239
|
+
},
|
|
240
|
+
skillScanning: true,
|
|
241
|
+
warnOnInsecureConfig: true
|
|
242
|
+
},
|
|
243
|
+
hooks: {
|
|
244
|
+
scripts: {}
|
|
245
|
+
},
|
|
246
|
+
captcha: {
|
|
247
|
+
enabled: false,
|
|
248
|
+
autoSolve: true,
|
|
249
|
+
visionProvider: "gemini",
|
|
250
|
+
visionModel: "gemini-2.0-flash-exp",
|
|
251
|
+
maxAttempts: 3,
|
|
252
|
+
maxRounds: 5,
|
|
253
|
+
enabledSites: []
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function loadConfig() {
|
|
258
|
+
return buildDefaultConfig();
|
|
259
|
+
}
|
|
260
|
+
var LogLevelSchema, DMPolicySchema, TransportSchema, ProviderConfigSchema, ToolRestrictionsSchema, ExecConfigSchema, WebConfigSchema, BrowserConfigSchema, CanvasConfigSchema, SandboxConfigSchema, ToolsConfigSchema, ContextConfigSchema, AgentEntrySchema, AccountConfigSchema, ChannelConfigSchema, PeerMatchSchema, BindingMatchSchema, BindingSchema, MCPServerConfigSchema, MCPConfigSchema, EpisodicMemoryConfigSchema, MemoryConfigSchema, CronConfigSchema, RetryConfigSchema, HooksConfigSchema, LoggingConfigSchema, GatewayConfigSchema, ModelsConfigSchema, SessionsConfigSchema, SkillsConfigSchema, SecurityConfigSchema, CaptchaConfigSchema, UserConfigSchema, ConfigSchema;
|
|
261
|
+
var init_loader = __esm(() => {
|
|
262
|
+
LogLevelSchema = z.enum(["debug", "info", "warn", "error"]);
|
|
263
|
+
DMPolicySchema = z.enum(["open", "pairing", "allowlist"]);
|
|
264
|
+
TransportSchema = z.enum(["stdio", "sse", "websocket"]);
|
|
265
|
+
ProviderConfigSchema = z.object({
|
|
266
|
+
apiKey: z.string().optional(),
|
|
267
|
+
baseUrl: z.string().optional(),
|
|
268
|
+
rateLimit: z.number().optional(),
|
|
269
|
+
retries: z.number().optional(),
|
|
270
|
+
retryDelayMs: z.number().optional()
|
|
271
|
+
});
|
|
272
|
+
ToolRestrictionsSchema = z.object({
|
|
273
|
+
allow: z.array(z.string()).optional(),
|
|
274
|
+
deny: z.array(z.string()).optional()
|
|
275
|
+
});
|
|
276
|
+
ExecConfigSchema = z.object({
|
|
277
|
+
enabled: z.boolean().optional(),
|
|
278
|
+
allowlist: z.array(z.string()).optional(),
|
|
279
|
+
denylist: z.array(z.string()).optional(),
|
|
280
|
+
timeoutSeconds: z.number().optional(),
|
|
281
|
+
workDir: z.string().optional()
|
|
282
|
+
});
|
|
283
|
+
WebConfigSchema = z.object({
|
|
284
|
+
allowlist: z.array(z.string()).optional(),
|
|
285
|
+
denylist: z.array(z.string()).optional(),
|
|
286
|
+
timeoutSeconds: z.number().optional()
|
|
287
|
+
});
|
|
288
|
+
BrowserConfigSchema = z.object({
|
|
289
|
+
enabled: z.boolean().optional(),
|
|
290
|
+
cdpUrl: z.string().optional(),
|
|
291
|
+
headless: z.boolean().optional(),
|
|
292
|
+
timeoutMs: z.number().optional()
|
|
293
|
+
});
|
|
294
|
+
CanvasConfigSchema = z.object({
|
|
295
|
+
enabled: z.boolean().optional(),
|
|
296
|
+
port: z.number().optional()
|
|
297
|
+
});
|
|
298
|
+
SandboxConfigSchema = z.object({
|
|
299
|
+
dm: ToolRestrictionsSchema.optional(),
|
|
300
|
+
group: ToolRestrictionsSchema.optional()
|
|
301
|
+
});
|
|
302
|
+
ToolsConfigSchema = z.object({
|
|
303
|
+
allow: z.array(z.string()).optional(),
|
|
304
|
+
deny: z.array(z.string()).optional(),
|
|
305
|
+
exec: ExecConfigSchema.optional(),
|
|
306
|
+
web: WebConfigSchema.optional(),
|
|
307
|
+
browser: BrowserConfigSchema.optional(),
|
|
308
|
+
canvas: CanvasConfigSchema.optional(),
|
|
309
|
+
sandbox: SandboxConfigSchema.optional()
|
|
310
|
+
});
|
|
311
|
+
ContextConfigSchema = z.object({
|
|
312
|
+
maxTokens: z.number().optional(),
|
|
313
|
+
compactionThreshold: z.number().optional(),
|
|
314
|
+
minMessagesAfterCompaction: z.number().optional(),
|
|
315
|
+
maxCompactionRetries: z.number().optional()
|
|
316
|
+
});
|
|
317
|
+
AgentEntrySchema = z.object({
|
|
318
|
+
id: z.string(),
|
|
319
|
+
default: z.boolean().optional(),
|
|
320
|
+
workspace: z.string(),
|
|
321
|
+
description: z.string().optional()
|
|
322
|
+
});
|
|
323
|
+
AccountConfigSchema = z.object({
|
|
324
|
+
botToken: z.string().optional(),
|
|
325
|
+
applicationId: z.string().optional(),
|
|
326
|
+
appToken: z.string().optional(),
|
|
327
|
+
signingSecret: z.string().optional(),
|
|
328
|
+
dmPolicy: DMPolicySchema.optional(),
|
|
329
|
+
allowFrom: z.array(z.string()).optional()
|
|
330
|
+
});
|
|
331
|
+
ChannelConfigSchema = z.object({
|
|
332
|
+
enabled: z.boolean().optional(),
|
|
333
|
+
accounts: z.record(z.string(), AccountConfigSchema).optional(),
|
|
334
|
+
dmPolicy: DMPolicySchema.optional(),
|
|
335
|
+
allowFrom: z.array(z.string()).optional(),
|
|
336
|
+
groups: z.boolean().optional(),
|
|
337
|
+
guilds: z.record(z.string(), z.unknown()).optional(),
|
|
338
|
+
experimental: z.boolean().optional()
|
|
339
|
+
});
|
|
340
|
+
PeerMatchSchema = z.object({
|
|
341
|
+
kind: z.enum(["direct", "group"]).optional(),
|
|
342
|
+
id: z.string().optional()
|
|
343
|
+
});
|
|
344
|
+
BindingMatchSchema = z.object({
|
|
345
|
+
channel: z.string().optional(),
|
|
346
|
+
accountId: z.string().optional(),
|
|
347
|
+
peer: PeerMatchSchema.optional(),
|
|
348
|
+
guildId: z.string().optional(),
|
|
349
|
+
teamId: z.string().optional(),
|
|
350
|
+
roles: z.array(z.string()).optional()
|
|
351
|
+
});
|
|
352
|
+
BindingSchema = z.object({
|
|
353
|
+
agentId: z.string(),
|
|
354
|
+
match: BindingMatchSchema
|
|
355
|
+
});
|
|
356
|
+
MCPServerConfigSchema = z.object({
|
|
357
|
+
enabled: z.boolean().optional(),
|
|
358
|
+
transport: TransportSchema,
|
|
359
|
+
command: z.string().optional(),
|
|
360
|
+
args: z.array(z.string()).optional(),
|
|
361
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
362
|
+
url: z.string().optional(),
|
|
363
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
364
|
+
reconnect: z.object({
|
|
365
|
+
enabled: z.boolean().optional(),
|
|
366
|
+
maxRetries: z.number().optional(),
|
|
367
|
+
delayMs: z.number().optional(),
|
|
368
|
+
backoffMultiplier: z.number().optional()
|
|
369
|
+
}).optional()
|
|
370
|
+
});
|
|
371
|
+
MCPConfigSchema = z.object({
|
|
372
|
+
enabled: z.boolean().optional(),
|
|
373
|
+
servers: z.record(z.string(), MCPServerConfigSchema).optional(),
|
|
374
|
+
healthCheck: z.object({
|
|
375
|
+
enabled: z.boolean().optional(),
|
|
376
|
+
intervalSeconds: z.number().optional()
|
|
377
|
+
}).optional()
|
|
378
|
+
});
|
|
379
|
+
EpisodicMemoryConfigSchema = z.object({
|
|
380
|
+
enabled: z.boolean().optional(),
|
|
381
|
+
provider: z.enum(["openai", "local"]).optional(),
|
|
382
|
+
maxEpisodesPerSession: z.number().optional()
|
|
383
|
+
});
|
|
384
|
+
MemoryConfigSchema = z.object({
|
|
385
|
+
dbPath: z.string().optional(),
|
|
386
|
+
notesDir: z.string().optional(),
|
|
387
|
+
episodic: EpisodicMemoryConfigSchema.optional()
|
|
388
|
+
});
|
|
389
|
+
CronConfigSchema = z.object({
|
|
390
|
+
enabled: z.boolean().optional(),
|
|
391
|
+
dbPath: z.string().optional(),
|
|
392
|
+
maxConcurrentJobs: z.number().optional(),
|
|
393
|
+
timezone: z.string().optional()
|
|
394
|
+
});
|
|
395
|
+
RetryConfigSchema = z.object({
|
|
396
|
+
maxAttempts: z.number().optional(),
|
|
397
|
+
initialDelayMs: z.number().optional(),
|
|
398
|
+
backoffMultiplier: z.number().optional(),
|
|
399
|
+
maxDelayMs: z.number().optional()
|
|
400
|
+
});
|
|
401
|
+
HooksConfigSchema = z.object({
|
|
402
|
+
scripts: z.object({
|
|
403
|
+
before_model_resolve: z.string().optional(),
|
|
404
|
+
before_prompt_build: z.string().optional(),
|
|
405
|
+
before_tool_call: z.string().optional(),
|
|
406
|
+
after_tool_call: z.string().optional(),
|
|
407
|
+
tool_result_persist: z.string().optional(),
|
|
408
|
+
before_compaction: z.string().optional(),
|
|
409
|
+
after_compaction: z.string().optional(),
|
|
410
|
+
message_received: z.string().optional(),
|
|
411
|
+
message_sending: z.string().optional(),
|
|
412
|
+
message_sent: z.string().optional(),
|
|
413
|
+
session_start: z.string().optional(),
|
|
414
|
+
session_end: z.string().optional(),
|
|
415
|
+
gateway_start: z.string().optional(),
|
|
416
|
+
gateway_stop: z.string().optional()
|
|
417
|
+
}).optional()
|
|
418
|
+
});
|
|
419
|
+
LoggingConfigSchema = z.object({
|
|
420
|
+
level: LogLevelSchema.optional(),
|
|
421
|
+
dir: z.string().optional(),
|
|
422
|
+
maxSizeMB: z.number().optional(),
|
|
423
|
+
maxFiles: z.number().optional(),
|
|
424
|
+
redactSensitive: z.boolean().optional(),
|
|
425
|
+
console: z.boolean().optional()
|
|
426
|
+
});
|
|
427
|
+
GatewayConfigSchema = z.object({
|
|
428
|
+
host: z.string().optional(),
|
|
429
|
+
port: z.number().optional(),
|
|
430
|
+
authToken: z.string().optional(),
|
|
431
|
+
pidFile: z.string().optional(),
|
|
432
|
+
tools: ToolRestrictionsSchema.optional()
|
|
433
|
+
});
|
|
434
|
+
ModelsConfigSchema = z.object({
|
|
435
|
+
defaultProvider: z.enum(["openai", "anthropic", "gemini", "mistral", "kimi", "ollama", "openrouter", "deepseek"]).optional(),
|
|
436
|
+
defaults: z.record(z.string(), z.string()).optional(),
|
|
437
|
+
providers: z.record(z.string(), ProviderConfigSchema).optional()
|
|
438
|
+
});
|
|
439
|
+
SessionsConfigSchema = z.object({
|
|
440
|
+
dir: z.string().optional(),
|
|
441
|
+
pruneAfterHours: z.number().optional(),
|
|
442
|
+
maxTranscriptSizeMB: z.number().optional()
|
|
443
|
+
});
|
|
444
|
+
SkillsConfigSchema = z.object({
|
|
445
|
+
allowBundled: z.array(z.string()).optional(),
|
|
446
|
+
managedDir: z.string().optional(),
|
|
447
|
+
extraDirs: z.array(z.string()).optional(),
|
|
448
|
+
hotReload: z.boolean().optional(),
|
|
449
|
+
maxSkillSizeKB: z.number().optional()
|
|
450
|
+
});
|
|
451
|
+
SecurityConfigSchema = z.object({
|
|
452
|
+
maxMessageLength: z.record(z.string(), z.number()).optional(),
|
|
453
|
+
skillScanning: z.boolean().optional(),
|
|
454
|
+
warnOnInsecureConfig: z.boolean().optional(),
|
|
455
|
+
allowedUsers: z.array(z.string()).optional()
|
|
456
|
+
});
|
|
457
|
+
CaptchaConfigSchema = z.object({
|
|
458
|
+
enabled: z.boolean().optional(),
|
|
459
|
+
autoSolve: z.boolean().optional(),
|
|
460
|
+
visionProvider: z.enum(["gemini", "openai", "anthropic"]).optional(),
|
|
461
|
+
visionModel: z.string().optional(),
|
|
462
|
+
maxAttempts: z.number().optional(),
|
|
463
|
+
maxRounds: z.number().optional(),
|
|
464
|
+
apiKey: z.string().optional(),
|
|
465
|
+
enabledSites: z.array(z.string()).optional()
|
|
466
|
+
});
|
|
467
|
+
UserConfigSchema = z.object({
|
|
468
|
+
id: z.string(),
|
|
469
|
+
name: z.string(),
|
|
470
|
+
channels: z.record(z.string(), z.string()).optional()
|
|
471
|
+
});
|
|
472
|
+
ConfigSchema = z.object({
|
|
473
|
+
gateway: GatewayConfigSchema.optional(),
|
|
474
|
+
logging: LoggingConfigSchema.optional(),
|
|
475
|
+
user: UserConfigSchema.optional(),
|
|
476
|
+
agent: z.object({
|
|
477
|
+
defaultAgentId: z.string().optional(),
|
|
478
|
+
baseDir: z.string().optional(),
|
|
479
|
+
context: ContextConfigSchema.optional()
|
|
480
|
+
}).optional(),
|
|
481
|
+
models: ModelsConfigSchema.optional(),
|
|
482
|
+
sessions: SessionsConfigSchema.optional(),
|
|
483
|
+
agents: z.object({
|
|
484
|
+
list: z.array(AgentEntrySchema).optional()
|
|
485
|
+
}).optional(),
|
|
486
|
+
bindings: z.array(BindingSchema).optional(),
|
|
487
|
+
channels: z.record(z.string(), ChannelConfigSchema).optional(),
|
|
488
|
+
tools: ToolsConfigSchema.optional(),
|
|
489
|
+
skills: SkillsConfigSchema.optional(),
|
|
490
|
+
mcp: MCPConfigSchema.optional(),
|
|
491
|
+
memory: MemoryConfigSchema.optional(),
|
|
492
|
+
cron: CronConfigSchema.optional(),
|
|
493
|
+
retry: RetryConfigSchema.optional(),
|
|
494
|
+
security: SecurityConfigSchema.optional(),
|
|
495
|
+
hooks: HooksConfigSchema.optional(),
|
|
496
|
+
captcha: CaptchaConfigSchema.optional()
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// ../core/src/utils/logger.ts
|
|
501
|
+
import { mkdirSync as mkdirSync2, unlinkSync, renameSync, existsSync as existsSync2 } from "fs";
|
|
502
|
+
import * as path2 from "path";
|
|
503
|
+
function emitLogEntry(entry) {
|
|
504
|
+
for (const cb of _logListeners) {
|
|
505
|
+
try {
|
|
506
|
+
cb(entry);
|
|
507
|
+
} catch {}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function expandPath(p) {
|
|
511
|
+
if (p.startsWith("~")) {
|
|
512
|
+
return path2.join(process.env.HOME || "", p.slice(1));
|
|
513
|
+
}
|
|
514
|
+
return p;
|
|
515
|
+
}
|
|
516
|
+
function redact(obj, seen = new WeakSet) {
|
|
517
|
+
if (obj === null || typeof obj !== "object") {
|
|
518
|
+
return obj;
|
|
519
|
+
}
|
|
520
|
+
if (seen.has(obj)) {
|
|
521
|
+
return "[Circular]";
|
|
522
|
+
}
|
|
523
|
+
seen.add(obj);
|
|
524
|
+
if (Array.isArray(obj)) {
|
|
525
|
+
return obj.map((item) => redact(item, seen));
|
|
526
|
+
}
|
|
527
|
+
const result = {};
|
|
528
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
529
|
+
const isSensitive = SENSITIVE_PATTERNS.some((p) => p.test(key));
|
|
530
|
+
if (isSensitive) {
|
|
531
|
+
result[key] = "[REDACTED]";
|
|
532
|
+
} else if (typeof value === "object" && value !== null) {
|
|
533
|
+
result[key] = redact(value, seen);
|
|
534
|
+
} else {
|
|
535
|
+
result[key] = value;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
function formatTimestamp() {
|
|
541
|
+
return new Date().toISOString();
|
|
542
|
+
}
|
|
543
|
+
function formatMessage(level, message, meta, correlationId) {
|
|
544
|
+
const timestamp = formatTimestamp();
|
|
545
|
+
const corrStr = correlationId ? ` [${correlationId.slice(0, 8)}]` : "";
|
|
546
|
+
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
547
|
+
return `[${timestamp}]${corrStr} [${level.toUpperCase()}] ${message}${metaStr}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
class Logger {
|
|
551
|
+
config;
|
|
552
|
+
logFile = null;
|
|
553
|
+
currentSize = 0;
|
|
554
|
+
correlationContext = {};
|
|
555
|
+
constructor(config = {}) {
|
|
556
|
+
this.config = {
|
|
557
|
+
level: config.level ?? "info",
|
|
558
|
+
dir: config.dir ?? path2.join(getHiveDir(), "logs"),
|
|
559
|
+
maxSizeMB: config.maxSizeMB ?? 10,
|
|
560
|
+
maxFiles: config.maxFiles ?? 5,
|
|
561
|
+
redactSensitive: config.redactSensitive ?? true,
|
|
562
|
+
console: config.console ?? true
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
setCorrelationContext(context) {
|
|
566
|
+
this.correlationContext = { ...this.correlationContext, ...context };
|
|
567
|
+
}
|
|
568
|
+
clearCorrelationContext() {
|
|
569
|
+
this.correlationContext = {};
|
|
570
|
+
}
|
|
571
|
+
getCorrelationId() {
|
|
572
|
+
return this.correlationContext.correlationId;
|
|
573
|
+
}
|
|
574
|
+
withCorrelationId(id) {
|
|
575
|
+
this.correlationContext.correlationId = id;
|
|
576
|
+
return this;
|
|
577
|
+
}
|
|
578
|
+
initLogFile() {
|
|
579
|
+
const logDir = expandPath(this.config.dir);
|
|
580
|
+
try {
|
|
581
|
+
if (!existsSync2(logDir)) {
|
|
582
|
+
mkdirSync2(logDir, { recursive: true });
|
|
583
|
+
}
|
|
584
|
+
this.logFile = path2.join(logDir, `hive-${new Date().toISOString().split("T")[0]}.log`);
|
|
585
|
+
const file = Bun.file(this.logFile);
|
|
586
|
+
this.currentSize = file.size ?? 0;
|
|
587
|
+
} catch {
|
|
588
|
+
this.logFile = null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
shouldLog(level) {
|
|
592
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.config.level];
|
|
593
|
+
}
|
|
594
|
+
writeToConsole(level, message, meta) {
|
|
595
|
+
if (!this.config.console)
|
|
596
|
+
return;
|
|
597
|
+
const color = COLORS[level];
|
|
598
|
+
const mergedMeta = this.mergeMeta(meta);
|
|
599
|
+
const displayMeta = this.config.redactSensitive && mergedMeta ? redact(mergedMeta) : mergedMeta;
|
|
600
|
+
const metaStr = displayMeta && Object.keys(displayMeta).length > 0 ? ` ${JSON.stringify(displayMeta)}` : "";
|
|
601
|
+
const prefix = `${COLORS.dim}${formatTimestamp()}${COLORS.reset}`;
|
|
602
|
+
const corrStr = this.correlationContext.correlationId ? ` ${COLORS.dim}[${this.correlationContext.correlationId.slice(0, 8)}]${COLORS.reset}` : "";
|
|
603
|
+
const levelStr = `${color}${COLORS.bright}[${level.toUpperCase().padEnd(5)}]${COLORS.reset}`;
|
|
604
|
+
console.log(`${prefix}${corrStr} ${levelStr} ${message}${metaStr}`);
|
|
605
|
+
}
|
|
606
|
+
mergeMeta(meta) {
|
|
607
|
+
if (!meta && Object.keys(this.correlationContext).length === 0)
|
|
608
|
+
return;
|
|
609
|
+
const contextWithoutCorrId = { ...this.correlationContext };
|
|
610
|
+
delete contextWithoutCorrId.correlationId;
|
|
611
|
+
if (!meta)
|
|
612
|
+
return contextWithoutCorrId;
|
|
613
|
+
if (typeof meta !== "object")
|
|
614
|
+
return meta;
|
|
615
|
+
return { ...contextWithoutCorrId, ...meta };
|
|
616
|
+
}
|
|
617
|
+
writeToFile(message) {
|
|
618
|
+
if (!this.logFile) {
|
|
619
|
+
this.initLogFile();
|
|
620
|
+
}
|
|
621
|
+
if (!this.logFile)
|
|
622
|
+
return;
|
|
623
|
+
try {
|
|
624
|
+
const line = message + `
|
|
625
|
+
`;
|
|
626
|
+
const bytes = Buffer.byteLength(line);
|
|
627
|
+
if (this.currentSize + bytes > this.config.maxSizeMB * 1024 * 1024) {
|
|
628
|
+
this.rotateLogs();
|
|
629
|
+
}
|
|
630
|
+
const encoder = new TextEncoder;
|
|
631
|
+
const data = encoder.encode(line);
|
|
632
|
+
Bun.write(this.logFile, data).catch(() => {});
|
|
633
|
+
this.currentSize += bytes;
|
|
634
|
+
} catch {}
|
|
635
|
+
}
|
|
636
|
+
rotateLogs() {
|
|
637
|
+
if (!this.logFile)
|
|
638
|
+
return;
|
|
639
|
+
const logDir = path2.dirname(this.logFile);
|
|
640
|
+
const baseName = path2.basename(this.logFile, ".log");
|
|
641
|
+
for (let i = this.config.maxFiles - 1;i >= 1; i--) {
|
|
642
|
+
const oldFile = path2.join(logDir, `${baseName}.${i}.log`);
|
|
643
|
+
const newFile = path2.join(logDir, `${baseName}.${i + 1}.log`);
|
|
644
|
+
try {
|
|
645
|
+
if (existsSync2(oldFile)) {
|
|
646
|
+
if (i === this.config.maxFiles - 1) {
|
|
647
|
+
unlinkSync(oldFile);
|
|
648
|
+
} else {
|
|
649
|
+
renameSync(oldFile, newFile);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch {}
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
renameSync(this.logFile, path2.join(logDir, `${baseName}.1.log`));
|
|
656
|
+
this.currentSize = 0;
|
|
657
|
+
} catch {}
|
|
658
|
+
}
|
|
659
|
+
debug(message, meta) {
|
|
660
|
+
if (!this.shouldLog("debug"))
|
|
661
|
+
return;
|
|
662
|
+
const mergedMeta = this.mergeMeta(meta);
|
|
663
|
+
const formatted = formatMessage("debug", message, mergedMeta, this.correlationContext.correlationId);
|
|
664
|
+
this.writeToConsole("debug", message, meta);
|
|
665
|
+
this.writeToFile(formatted);
|
|
666
|
+
emitLogEntry({ timestamp: formatTimestamp(), level: "debug", source: "core", message, meta: mergedMeta });
|
|
667
|
+
}
|
|
668
|
+
info(message, meta) {
|
|
669
|
+
if (!this.shouldLog("info"))
|
|
670
|
+
return;
|
|
671
|
+
const mergedMeta = this.mergeMeta(meta);
|
|
672
|
+
const formatted = formatMessage("info", message, mergedMeta, this.correlationContext.correlationId);
|
|
673
|
+
this.writeToConsole("info", message, meta);
|
|
674
|
+
this.writeToFile(formatted);
|
|
675
|
+
emitLogEntry({ timestamp: formatTimestamp(), level: "info", source: "core", message, meta: mergedMeta });
|
|
676
|
+
}
|
|
677
|
+
warn(message, meta) {
|
|
678
|
+
if (!this.shouldLog("warn"))
|
|
679
|
+
return;
|
|
680
|
+
const mergedMeta = this.mergeMeta(meta);
|
|
681
|
+
const formatted = formatMessage("warn", message, mergedMeta, this.correlationContext.correlationId);
|
|
682
|
+
this.writeToConsole("warn", message, meta);
|
|
683
|
+
this.writeToFile(formatted);
|
|
684
|
+
emitLogEntry({ timestamp: formatTimestamp(), level: "warn", source: "core", message, meta: mergedMeta });
|
|
685
|
+
}
|
|
686
|
+
error(message, meta) {
|
|
687
|
+
if (!this.shouldLog("error"))
|
|
688
|
+
return;
|
|
689
|
+
const mergedMeta = this.mergeMeta(meta);
|
|
690
|
+
const formatted = formatMessage("error", message, mergedMeta, this.correlationContext.correlationId);
|
|
691
|
+
this.writeToConsole("error", message, meta);
|
|
692
|
+
this.writeToFile(formatted);
|
|
693
|
+
emitLogEntry({ timestamp: formatTimestamp(), level: "error", source: "core", message, meta: mergedMeta });
|
|
694
|
+
}
|
|
695
|
+
child(context) {
|
|
696
|
+
return new ChildLogger(this, context, this.correlationContext);
|
|
697
|
+
}
|
|
698
|
+
setLevel(level) {
|
|
699
|
+
this.config.level = level;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
class ChildLogger {
|
|
704
|
+
parent;
|
|
705
|
+
context;
|
|
706
|
+
correlationContext;
|
|
707
|
+
constructor(parent, context, correlationContext = {}) {
|
|
708
|
+
this.parent = parent;
|
|
709
|
+
this.context = context;
|
|
710
|
+
this.correlationContext = correlationContext;
|
|
711
|
+
}
|
|
712
|
+
prefix(message) {
|
|
713
|
+
return `[${this.context}] ${message}`;
|
|
714
|
+
}
|
|
715
|
+
withCorrelationId(id) {
|
|
716
|
+
this.correlationContext.correlationId = id;
|
|
717
|
+
return this;
|
|
718
|
+
}
|
|
719
|
+
setContext(context) {
|
|
720
|
+
this.correlationContext = { ...this.correlationContext, ...context };
|
|
721
|
+
}
|
|
722
|
+
debug(message, meta) {
|
|
723
|
+
this.parent.debug(this.prefix(message), this.mergeMeta(meta));
|
|
724
|
+
}
|
|
725
|
+
info(message, meta) {
|
|
726
|
+
this.parent.info(this.prefix(message), this.mergeMeta(meta));
|
|
727
|
+
}
|
|
728
|
+
warn(message, meta) {
|
|
729
|
+
this.parent.warn(this.prefix(message), this.mergeMeta(meta));
|
|
730
|
+
}
|
|
731
|
+
error(message, meta) {
|
|
732
|
+
this.parent.error(this.prefix(message), this.mergeMeta(meta));
|
|
733
|
+
}
|
|
734
|
+
child(subContext) {
|
|
735
|
+
return new ChildLogger(this.parent, `${this.context}:${subContext}`, this.correlationContext);
|
|
736
|
+
}
|
|
737
|
+
mergeMeta(meta) {
|
|
738
|
+
if (!meta && Object.keys(this.correlationContext).length === 0)
|
|
739
|
+
return;
|
|
740
|
+
if (!meta)
|
|
741
|
+
return { ...this.correlationContext };
|
|
742
|
+
if (typeof meta !== "object")
|
|
743
|
+
return meta;
|
|
744
|
+
return { ...this.correlationContext, ...meta };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function getLogger() {
|
|
748
|
+
if (!_logger) {
|
|
749
|
+
const config = loadConfig();
|
|
750
|
+
_logger = new Logger({ level: config.logging?.level });
|
|
751
|
+
}
|
|
752
|
+
return _logger;
|
|
753
|
+
}
|
|
754
|
+
var _logListeners, LOG_LEVELS, SENSITIVE_PATTERNS, COLORS, _logger = null, logger;
|
|
755
|
+
var init_logger = __esm(() => {
|
|
756
|
+
init_loader();
|
|
757
|
+
_logListeners = new Set;
|
|
758
|
+
LOG_LEVELS = {
|
|
759
|
+
debug: 0,
|
|
760
|
+
info: 1,
|
|
761
|
+
warn: 2,
|
|
762
|
+
error: 3
|
|
763
|
+
};
|
|
764
|
+
SENSITIVE_PATTERNS = [
|
|
765
|
+
/api[_-]?key/i,
|
|
766
|
+
/token/i,
|
|
767
|
+
/secret/i,
|
|
768
|
+
/password/i,
|
|
769
|
+
/credential/i,
|
|
770
|
+
/auth/i
|
|
771
|
+
];
|
|
772
|
+
COLORS = {
|
|
773
|
+
debug: "\x1B[36m",
|
|
774
|
+
info: "\x1B[32m",
|
|
775
|
+
warn: "\x1B[33m",
|
|
776
|
+
error: "\x1B[31m",
|
|
777
|
+
reset: "\x1B[0m",
|
|
778
|
+
dim: "\x1B[2m",
|
|
779
|
+
bright: "\x1B[1m"
|
|
780
|
+
};
|
|
781
|
+
logger = {
|
|
782
|
+
child: (opts) => getLogger().child(opts),
|
|
783
|
+
debug: (msg, meta) => getLogger().debug(msg, meta),
|
|
784
|
+
info: (msg, meta) => getLogger().info(msg, meta),
|
|
785
|
+
warn: (msg, meta) => getLogger().warn(msg, meta),
|
|
786
|
+
error: (msg, meta) => getLogger().error(msg, meta),
|
|
787
|
+
setCorrelationContext: (ctx) => getLogger().setCorrelationContext(ctx),
|
|
788
|
+
clearCorrelationContext: () => getLogger().clearCorrelationContext(),
|
|
789
|
+
getCorrelationId: () => getLogger().getCorrelationId(),
|
|
790
|
+
withCorrelationId: (id) => getLogger().withCorrelationId(id),
|
|
791
|
+
setLevel: (level) => getLogger().setLevel(level),
|
|
792
|
+
setHandler: (handler) => {}
|
|
793
|
+
};
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
// ../core/src/storage/schema.ts
|
|
797
|
+
var SCHEMA = `
|
|
798
|
+
PRAGMA journal_mode = WAL;
|
|
799
|
+
PRAGMA foreign_keys = ON;
|
|
800
|
+
|
|
801
|
+
-- ENCRYPTION KEY (stored separately, used for encrypting sensitive data)
|
|
802
|
+
-- The encryption key is derived from HIVE_MASTER_KEY env var or generated on first run
|
|
803
|
+
|
|
804
|
+
-- CONFIGURATION (all linked to user)
|
|
805
|
+
|
|
806
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
807
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
808
|
+
name TEXT,
|
|
809
|
+
language TEXT,
|
|
810
|
+
timezone TEXT,
|
|
811
|
+
occupation TEXT,
|
|
812
|
+
notes TEXT,
|
|
813
|
+
master_key_hash TEXT,
|
|
814
|
+
email TEXT UNIQUE,
|
|
815
|
+
password_hash TEXT,
|
|
816
|
+
preferred_cron_channel TEXT NOT NULL DEFAULT 'auto',
|
|
817
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
-- Providers: linked to user (API key encrypted)
|
|
821
|
+
-- Solo la empresa (OpenAI, Groq, ElevenLabs, etc.)
|
|
822
|
+
-- La API key es del provider, no del modelo
|
|
823
|
+
-- category: 'llm', 'stt', 'tts' (default: llm)
|
|
824
|
+
CREATE TABLE IF NOT EXISTS providers (
|
|
825
|
+
id TEXT PRIMARY KEY,
|
|
826
|
+
name TEXT NOT NULL UNIQUE,
|
|
827
|
+
api_key_encrypted TEXT,
|
|
828
|
+
api_key_iv TEXT,
|
|
829
|
+
headers_encrypted TEXT,
|
|
830
|
+
headers_iv TEXT,
|
|
831
|
+
base_url TEXT,
|
|
832
|
+
category TEXT NOT NULL DEFAULT 'llm',
|
|
833
|
+
num_ctx INTEGER,
|
|
834
|
+
num_gpu INTEGER DEFAULT -1,
|
|
835
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
836
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
837
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
-- Models: linked to provider
|
|
841
|
+
-- model_type: 'llm', 'stt', 'tts', 'vision', 'embedding'
|
|
842
|
+
-- stt models: whisper-1, whisper-large-v3
|
|
843
|
+
-- tts models: tts-1, gpt-4o-mini-tts, eleven_multilingual_v2
|
|
844
|
+
CREATE TABLE IF NOT EXISTS models (
|
|
845
|
+
id TEXT PRIMARY KEY,
|
|
846
|
+
provider_id TEXT REFERENCES providers(id) ON DELETE CASCADE,
|
|
847
|
+
name TEXT NOT NULL,
|
|
848
|
+
model_type TEXT NOT NULL DEFAULT 'llm',
|
|
849
|
+
context_window INTEGER NOT NULL DEFAULT 20000,
|
|
850
|
+
capabilities TEXT,
|
|
851
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
852
|
+
active INTEGER NOT NULL DEFAULT 0
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
-- Agents: linked to user + provider/model
|
|
856
|
+
-- role: 'coordinator' | 'worker'
|
|
857
|
+
-- system_prompt: explicit prompt (description is a human-readable summary)
|
|
858
|
+
-- tools_json: JSON array of tool IDs this agent can use (NULL = all)
|
|
859
|
+
-- skills_json: JSON array of skill IDs this agent can use (NULL = all)
|
|
860
|
+
-- parent_id: agent that created this one (NULL for coordinator)
|
|
861
|
+
-- max_iterations: loop limit per invocation
|
|
862
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
863
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
864
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
865
|
+
name TEXT NOT NULL,
|
|
866
|
+
description TEXT,
|
|
867
|
+
system_prompt TEXT,
|
|
868
|
+
tone TEXT,
|
|
869
|
+
role TEXT NOT NULL DEFAULT 'coordinator' CHECK(role IN ('coordinator', 'worker')),
|
|
870
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
871
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
872
|
+
provider_id TEXT REFERENCES providers(id),
|
|
873
|
+
model_id TEXT REFERENCES models(id),
|
|
874
|
+
tools_json TEXT,
|
|
875
|
+
skills_json TEXT,
|
|
876
|
+
parent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
877
|
+
max_iterations INTEGER NOT NULL DEFAULT 10,
|
|
878
|
+
headers_encrypted TEXT,
|
|
879
|
+
headers_iv TEXT,
|
|
880
|
+
workspace TEXT,
|
|
881
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
882
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
-- Channels: linked to user (or global if user_id is NULL)
|
|
886
|
+
-- voice_enabled: enables speech-to-text for incoming audio
|
|
887
|
+
-- tts_enabled: enables text-to-speech for outgoing responses
|
|
888
|
+
-- stt_provider: which STT provider to use (groq-whisper, openai-whisper)
|
|
889
|
+
-- tts_provider: which TTS provider to use (elevenlabs, openai-tts)
|
|
890
|
+
-- tts_voice_id: specific voice ID for TTS (e.g., ElevenLabs voice ID)
|
|
891
|
+
-- step_delivery_mode: how to send intermediate steps to user:
|
|
892
|
+
-- "new_message" = send new message for each step (default)
|
|
893
|
+
-- "edit" = edit same message (Telegram/Discord only)
|
|
894
|
+
-- "thread" = use threading (Slack only)
|
|
895
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
896
|
+
id TEXT PRIMARY KEY,
|
|
897
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
898
|
+
type TEXT NOT NULL,
|
|
899
|
+
config_encrypted TEXT,
|
|
900
|
+
config_iv TEXT,
|
|
901
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
902
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
903
|
+
status TEXT NOT NULL DEFAULT 'disconnected',
|
|
904
|
+
last_active INTEGER,
|
|
905
|
+
voice_enabled INTEGER NOT NULL DEFAULT 0,
|
|
906
|
+
tts_enabled INTEGER NOT NULL DEFAULT 0,
|
|
907
|
+
stt_provider TEXT,
|
|
908
|
+
tts_provider TEXT,
|
|
909
|
+
tts_voice_id TEXT,
|
|
910
|
+
step_delivery_mode TEXT DEFAULT 'new_messages'
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
-- MCP Servers: linked to user (or global if user_id is NULL)
|
|
914
|
+
CREATE TABLE IF NOT EXISTS mcp_servers (
|
|
915
|
+
id TEXT PRIMARY KEY,
|
|
916
|
+
name TEXT NOT NULL,
|
|
917
|
+
transport TEXT NOT NULL,
|
|
918
|
+
command TEXT,
|
|
919
|
+
args TEXT,
|
|
920
|
+
env_encrypted TEXT,
|
|
921
|
+
env_iv TEXT,
|
|
922
|
+
headers_encrypted TEXT,
|
|
923
|
+
headers_iv TEXT,
|
|
924
|
+
url TEXT,
|
|
925
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
926
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
927
|
+
builtin INTEGER NOT NULL DEFAULT 0,
|
|
928
|
+
status TEXT NOT NULL DEFAULT 'disconnected',
|
|
929
|
+
tools_count INTEGER DEFAULT 0
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
-- MCP Servers: external tool providers (stdio, SSE, etc.)
|
|
933
|
+
-- MCP tools are loaded at runtime from connected servers, not stored in DB
|
|
934
|
+
CREATE TABLE IF NOT EXISTS mcp_servers (
|
|
935
|
+
id TEXT PRIMARY KEY,
|
|
936
|
+
name TEXT NOT NULL,
|
|
937
|
+
transport TEXT NOT NULL,
|
|
938
|
+
command TEXT,
|
|
939
|
+
args TEXT,
|
|
940
|
+
env_encrypted TEXT,
|
|
941
|
+
env_iv TEXT,
|
|
942
|
+
headers_encrypted TEXT,
|
|
943
|
+
headers_iv TEXT,
|
|
944
|
+
url TEXT,
|
|
945
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
946
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
947
|
+
builtin INTEGER NOT NULL DEFAULT 0,
|
|
948
|
+
status TEXT NOT NULL DEFAULT 'disconnected',
|
|
949
|
+
tools_count INTEGER DEFAULT 0
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
-- Note: MCP tools are NOT stored in DB. They are loaded from MCP servers at runtime
|
|
953
|
+
-- and made available directly via context-compiler (Direct Connection architecture)
|
|
954
|
+
|
|
955
|
+
-- Skills: can be global (system) or user-specific
|
|
956
|
+
-- Simplified schema with body field for markdown content
|
|
957
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
958
|
+
id TEXT PRIMARY KEY, -- 'web_research'
|
|
959
|
+
name TEXT NOT NULL, -- 'Web Research'
|
|
960
|
+
category TEXT NOT NULL, -- 'web'
|
|
961
|
+
tools TEXT NOT NULL, -- 'web_search,web_fetch'
|
|
962
|
+
triggers TEXT NOT NULL, -- 'investiga,busca,research'
|
|
963
|
+
body TEXT NOT NULL, -- Markdown completo
|
|
964
|
+
version INTEGER DEFAULT 1,
|
|
965
|
+
active INTEGER DEFAULT 1, -- 1=activo, 0=desactivado
|
|
966
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
967
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
-- \xCDndices para filtros directos
|
|
971
|
+
CREATE INDEX IF NOT EXISTS idx_skills_category ON skills(category);
|
|
972
|
+
CREATE INDEX IF NOT EXISTS idx_skills_active ON skills(active);
|
|
973
|
+
|
|
974
|
+
-- Tools: global (bundled), not user-specific
|
|
975
|
+
-- category: 'bundled', 'workspace', 'project', 'builtin', 'voice'
|
|
976
|
+
CREATE TABLE IF NOT EXISTS tools (
|
|
977
|
+
id TEXT PRIMARY KEY,
|
|
978
|
+
name TEXT NOT NULL UNIQUE,
|
|
979
|
+
description TEXT,
|
|
980
|
+
category TEXT,
|
|
981
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
982
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
983
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
984
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
-- Ethics: global templates (user selects one)
|
|
988
|
+
CREATE TABLE IF NOT EXISTS ethics (
|
|
989
|
+
id TEXT PRIMARY KEY,
|
|
990
|
+
name TEXT NOT NULL,
|
|
991
|
+
description TEXT,
|
|
992
|
+
content TEXT NOT NULL,
|
|
993
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
994
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
995
|
+
active INTEGER NOT NULL DEFAULT 0
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
-- Code Bridge: external CLI tools configuration (global)
|
|
999
|
+
CREATE TABLE IF NOT EXISTS code_bridge (
|
|
1000
|
+
id TEXT PRIMARY KEY,
|
|
1001
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
1002
|
+
name TEXT NOT NULL UNIQUE,
|
|
1003
|
+
cli_command TEXT NOT NULL,
|
|
1004
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
1005
|
+
active INTEGER NOT NULL DEFAULT 0,
|
|
1006
|
+
port INTEGER DEFAULT 18791,
|
|
1007
|
+
config TEXT
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
-- Code Bridge Config: key-value store for configuration (voice_wake_word, etc.)
|
|
1011
|
+
CREATE TABLE IF NOT EXISTS code_bridge_config (
|
|
1012
|
+
id TEXT PRIMARY KEY,
|
|
1013
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
1014
|
+
key TEXT NOT NULL,
|
|
1015
|
+
value TEXT,
|
|
1016
|
+
UNIQUE(user_id, key)
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
-- USER IDENTITIES (channel + user mapping)
|
|
1020
|
+
|
|
1021
|
+
CREATE TABLE IF NOT EXISTS user_identities (
|
|
1022
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
1023
|
+
channel TEXT NOT NULL,
|
|
1024
|
+
channel_user_id TEXT NOT NULL,
|
|
1025
|
+
linked_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1026
|
+
PRIMARY KEY (user_id, channel)
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
-- USER CHANNELS (user-specific channel configurations)
|
|
1030
|
+
-- Stores per-user channel account configurations (Telegram bot, Discord bot, etc.)
|
|
1031
|
+
-- config: JSON object with channel-specific settings (bot token, webhook URL, etc.)
|
|
1032
|
+
-- active: 1 = enabled and running, 0 = disabled
|
|
1033
|
+
CREATE TABLE IF NOT EXISTS user_channels (
|
|
1034
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1035
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
1036
|
+
channel TEXT NOT NULL,
|
|
1037
|
+
account_id TEXT NOT NULL,
|
|
1038
|
+
config TEXT,
|
|
1039
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1040
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1041
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1042
|
+
UNIQUE(user_id, channel, account_id)
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
CREATE INDEX IF NOT EXISTS idx_user_channels_user ON user_channels(user_id);
|
|
1046
|
+
CREATE INDEX IF NOT EXISTS idx_user_channels_channel ON user_channels(channel);
|
|
1047
|
+
|
|
1048
|
+
-- ONBOARDING PROGRESS
|
|
1049
|
+
|
|
1050
|
+
CREATE TABLE IF NOT EXISTS onboarding_progress (
|
|
1051
|
+
id TEXT PRIMARY KEY,
|
|
1052
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
1053
|
+
step TEXT NOT NULL,
|
|
1054
|
+
data TEXT,
|
|
1055
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
-- USAGE TRACKING (tokens, costs)
|
|
1059
|
+
CREATE TABLE IF NOT EXISTS usage_records (
|
|
1060
|
+
id TEXT PRIMARY KEY,
|
|
1061
|
+
provider TEXT NOT NULL,
|
|
1062
|
+
model TEXT NOT NULL,
|
|
1063
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1064
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1065
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
1066
|
+
latency_ms INTEGER,
|
|
1067
|
+
|
|
1068
|
+
-- TOON Savings
|
|
1069
|
+
toon_saved_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1070
|
+
toon_saved_cost REAL NOT NULL DEFAULT 0,
|
|
1071
|
+
|
|
1072
|
+
-- TOON Metrics (complete compression analysis)
|
|
1073
|
+
toon_json_bytes INTEGER NOT NULL DEFAULT 0,
|
|
1074
|
+
toon_toon_bytes INTEGER NOT NULL DEFAULT 0,
|
|
1075
|
+
toon_saved_bytes INTEGER NOT NULL DEFAULT 0,
|
|
1076
|
+
toon_saved_percent REAL NOT NULL DEFAULT 0,
|
|
1077
|
+
toon_json_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1078
|
+
toon_toon_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1079
|
+
toon_saved_tokens_pct REAL NOT NULL DEFAULT 0,
|
|
1080
|
+
|
|
1081
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
-- CRON JOBS: scheduled tasks with notification channel
|
|
1085
|
+
-- notify_channel_id: which channel to send notifications to when task runs
|
|
1086
|
+
-- max_runs: NULL = unlimited; auto-disables when run_count reaches max_runs
|
|
1087
|
+
-- expires_at: NULL = never expires; auto-disables after this UTC timestamp
|
|
1088
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
1089
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
1090
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
1091
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1092
|
+
name TEXT NOT NULL,
|
|
1093
|
+
cron_expression TEXT NOT NULL,
|
|
1094
|
+
task_type TEXT NOT NULL DEFAULT 'message',
|
|
1095
|
+
task_config TEXT,
|
|
1096
|
+
notify_channel_id TEXT,
|
|
1097
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
1098
|
+
max_runs INTEGER,
|
|
1099
|
+
run_count INTEGER NOT NULL DEFAULT 0,
|
|
1100
|
+
expires_at INTEGER,
|
|
1101
|
+
last_run INTEGER,
|
|
1102
|
+
next_run INTEGER,
|
|
1103
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
-- SCHEDULER: New Croner-based scheduled tasks (replaces cron_jobs)
|
|
1107
|
+
-- task_type: 'recurring' (uses cron_expression) or 'one_shot' (uses fire_at)
|
|
1108
|
+
-- cron_expression: stored in user's local time, not UTC
|
|
1109
|
+
-- fire_at: ISO 8601 format in user's local time (e.g., "2026-04-01T09:00:00")
|
|
1110
|
+
-- timezone: IANA timezone string (e.g., "America/Bogota") inherited from users.timezone
|
|
1111
|
+
-- protect: overrun protection (1 = enabled, 0 = disabled)
|
|
1112
|
+
-- payload: JSON string with at least { prompt: string } or { message: string }
|
|
1113
|
+
-- CHECK constraint: recurring requires cron_expression, one_shot requires fire_at
|
|
1114
|
+
CREATE TABLE IF NOT EXISTS scheduled_tasks (
|
|
1115
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
|
|
1116
|
+
name TEXT NOT NULL,
|
|
1117
|
+
description TEXT DEFAULT '',
|
|
1118
|
+
task_type TEXT NOT NULL CHECK(task_type IN ('recurring', 'one_shot')),
|
|
1119
|
+
cron_expression TEXT,
|
|
1120
|
+
fire_at TEXT,
|
|
1121
|
+
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
1122
|
+
max_runs INTEGER,
|
|
1123
|
+
protect INTEGER NOT NULL DEFAULT 1,
|
|
1124
|
+
interval_sec INTEGER,
|
|
1125
|
+
agent_id TEXT,
|
|
1126
|
+
channel TEXT DEFAULT 'system',
|
|
1127
|
+
payload TEXT DEFAULT '{}',
|
|
1128
|
+
tool_name TEXT,
|
|
1129
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'failed', 'cancelled')),
|
|
1130
|
+
run_count INTEGER NOT NULL DEFAULT 0,
|
|
1131
|
+
error_count INTEGER NOT NULL DEFAULT 0,
|
|
1132
|
+
last_error TEXT,
|
|
1133
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
1134
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
1135
|
+
last_run_at TEXT,
|
|
1136
|
+
next_run_at TEXT,
|
|
1137
|
+
completed_at TEXT,
|
|
1138
|
+
CHECK(
|
|
1139
|
+
(task_type = 'recurring' AND cron_expression IS NOT NULL) OR
|
|
1140
|
+
(task_type = 'one_shot' AND fire_at IS NOT NULL)
|
|
1141
|
+
)
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
-- SCHEDULER: Task execution history
|
|
1145
|
+
-- Stores results of each task execution for debugging and auditing
|
|
1146
|
+
CREATE TABLE IF NOT EXISTS task_runs (
|
|
1147
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
|
|
1148
|
+
task_id TEXT NOT NULL REFERENCES scheduled_tasks(id) ON DELETE CASCADE,
|
|
1149
|
+
status TEXT NOT NULL CHECK(status IN ('running', 'success', 'failed', 'timeout')),
|
|
1150
|
+
started_at TEXT NOT NULL,
|
|
1151
|
+
finished_at TEXT,
|
|
1152
|
+
duration_ms INTEGER,
|
|
1153
|
+
error_message TEXT,
|
|
1154
|
+
payload_snapshot TEXT,
|
|
1155
|
+
agent_response TEXT
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
-- INDICES for scheduled_tasks
|
|
1159
|
+
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_status ON scheduled_tasks(status);
|
|
1160
|
+
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_type ON scheduled_tasks(task_type);
|
|
1161
|
+
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_next_run ON scheduled_tasks(next_run_at);
|
|
1162
|
+
CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_agent ON scheduled_tasks(agent_id);
|
|
1163
|
+
|
|
1164
|
+
-- INDICES for task_runs
|
|
1165
|
+
CREATE INDEX IF NOT EXISTS idx_task_runs_task ON task_runs(task_id);
|
|
1166
|
+
CREATE INDEX IF NOT EXISTS idx_task_runs_started ON task_runs(started_at);
|
|
1167
|
+
CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status);
|
|
1168
|
+
|
|
1169
|
+
-- TRIGGER: Update updated_at on scheduled_tasks UPDATE
|
|
1170
|
+
CREATE TRIGGER IF NOT EXISTS update_scheduled_tasks_updated_at
|
|
1171
|
+
AFTER UPDATE ON scheduled_tasks
|
|
1172
|
+
BEGIN
|
|
1173
|
+
UPDATE scheduled_tasks SET updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = NEW.id;
|
|
1174
|
+
END;
|
|
1175
|
+
|
|
1176
|
+
-- INDICES
|
|
1177
|
+
|
|
1178
|
+
CREATE INDEX IF NOT EXISTS idx_models_provider ON models(provider_id);
|
|
1179
|
+
CREATE INDEX IF NOT EXISTS idx_models_type ON models(model_type);
|
|
1180
|
+
CREATE INDEX IF NOT EXISTS idx_agents_user ON agents(user_id);
|
|
1181
|
+
CREATE INDEX IF NOT EXISTS idx_channels_user ON channels(user_id);
|
|
1182
|
+
CREATE INDEX IF NOT EXISTS idx_channels_type ON channels(type);
|
|
1183
|
+
CREATE INDEX IF NOT EXISTS idx_ethics ON ethics(id);
|
|
1184
|
+
CREATE INDEX IF NOT EXISTS idx_code_bridge ON code_bridge(id);
|
|
1185
|
+
CREATE INDEX IF NOT EXISTS idx_identities_user ON user_identities(user_id);
|
|
1186
|
+
CREATE INDEX IF NOT EXISTS idx_usage_provider_model ON usage_records(provider, model);
|
|
1187
|
+
CREATE INDEX IF NOT EXISTS idx_usage_created_at ON usage_records(created_at);
|
|
1188
|
+
CREATE INDEX IF NOT EXISTS idx_code_bridge_config_user ON code_bridge_config(user_id);
|
|
1189
|
+
CREATE INDEX IF NOT EXISTS idx_cron_jobs_user ON cron_jobs(user_id);
|
|
1190
|
+
CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled ON cron_jobs(enabled);
|
|
1191
|
+
CREATE INDEX IF NOT EXISTS idx_cron_jobs_next_run ON cron_jobs(next_run);
|
|
1192
|
+
`, PROJECTS_SCHEMA = `
|
|
1193
|
+
-- PROJECTS: tareas multi-paso con seguimiento de progreso
|
|
1194
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
1195
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
1196
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
1197
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
1198
|
+
name TEXT NOT NULL,
|
|
1199
|
+
description TEXT,
|
|
1200
|
+
type TEXT NOT NULL DEFAULT 'general',
|
|
1201
|
+
task TEXT,
|
|
1202
|
+
progress INTEGER NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 100),
|
|
1203
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','active','paused','done','failed')),
|
|
1204
|
+
context TEXT,
|
|
1205
|
+
parent_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1206
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1207
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1208
|
+
started_at INTEGER,
|
|
1209
|
+
completed_at INTEGER
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
-- TASKS: subtareas at\xF3micas asociadas a un proyecto, con agente asignado
|
|
1213
|
+
-- depends_on: JSON array of task IDs that must complete before this one
|
|
1214
|
+
-- priority: higher = more urgent
|
|
1215
|
+
-- error: reason for failure if status='failed'
|
|
1216
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1217
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1218
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
1219
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
1220
|
+
parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1221
|
+
name TEXT NOT NULL,
|
|
1222
|
+
description TEXT,
|
|
1223
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','in_progress','completed','failed','blocked')),
|
|
1224
|
+
progress INTEGER NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 100),
|
|
1225
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
1226
|
+
depends_on TEXT,
|
|
1227
|
+
result TEXT,
|
|
1228
|
+
error TEXT,
|
|
1229
|
+
metadata TEXT,
|
|
1230
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1231
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1232
|
+
completed_at INTEGER
|
|
1233
|
+
);
|
|
1234
|
+
|
|
1235
|
+
CREATE INDEX IF NOT EXISTS idx_projects_user ON projects(user_id);
|
|
1236
|
+
CREATE INDEX IF NOT EXISTS idx_projects_agent ON projects(agent_id);
|
|
1237
|
+
CREATE INDEX IF NOT EXISTS idx_projects_parent ON projects(parent_id);
|
|
1238
|
+
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
|
|
1239
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
|
|
1240
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(agent_id);
|
|
1241
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
1242
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
|
1243
|
+
`, CONTEXT_ENGINE_SCHEMA = `
|
|
1244
|
+
-- CONVERSATIONS: full message history per thread (replaces lg_checkpoints)
|
|
1245
|
+
-- role: 'user' | 'assistant' | 'tool' | 'system'
|
|
1246
|
+
-- tool_calls_json: JSON array of tool calls if the message triggered any
|
|
1247
|
+
-- token_count: estimated tokens for context budget tracking
|
|
1248
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
1249
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1250
|
+
thread_id TEXT NOT NULL,
|
|
1251
|
+
channel TEXT NOT NULL DEFAULT 'webchat',
|
|
1252
|
+
role TEXT NOT NULL CHECK(role IN ('user','assistant','tool','system')),
|
|
1253
|
+
content TEXT NOT NULL,
|
|
1254
|
+
tool_calls_json TEXT,
|
|
1255
|
+
tool_call_id TEXT,
|
|
1256
|
+
token_count INTEGER NOT NULL DEFAULT 0,
|
|
1257
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1258
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
-- SUMMARIES: compressed digests of long conversations
|
|
1262
|
+
-- The Context Compiler uses the summary instead of full history
|
|
1263
|
+
CREATE TABLE IF NOT EXISTS summaries (
|
|
1264
|
+
thread_id TEXT PRIMARY KEY,
|
|
1265
|
+
summary TEXT NOT NULL,
|
|
1266
|
+
messages_covered INTEGER NOT NULL DEFAULT 0,
|
|
1267
|
+
last_message_id INTEGER,
|
|
1268
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1269
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
-- SCRATCHPAD: persistent key-value notes per conversation
|
|
1273
|
+
-- Survives context compression. Written by agents via save_note tool.
|
|
1274
|
+
CREATE TABLE IF NOT EXISTS scratchpad (
|
|
1275
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1276
|
+
thread_id TEXT NOT NULL,
|
|
1277
|
+
key TEXT NOT NULL,
|
|
1278
|
+
value TEXT NOT NULL,
|
|
1279
|
+
source TEXT,
|
|
1280
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1281
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1282
|
+
UNIQUE(thread_id, key)
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
-- TRACES: execution log for every agent invocation (ACE Generator output)
|
|
1286
|
+
-- success: 1 = ok, 0 = failure
|
|
1287
|
+
-- tokens_used: total tokens consumed in this invocation
|
|
1288
|
+
CREATE TABLE IF NOT EXISTS traces (
|
|
1289
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1290
|
+
thread_id TEXT NOT NULL,
|
|
1291
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
1292
|
+
agent_name TEXT NOT NULL,
|
|
1293
|
+
tool_used TEXT,
|
|
1294
|
+
input_summary TEXT NOT NULL,
|
|
1295
|
+
output_summary TEXT NOT NULL,
|
|
1296
|
+
success INTEGER NOT NULL DEFAULT 1,
|
|
1297
|
+
error_message TEXT,
|
|
1298
|
+
duration_ms INTEGER,
|
|
1299
|
+
tokens_used INTEGER,
|
|
1300
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
-- REFLECTIONS: insights extracted by the ACE Reflector from traces
|
|
1304
|
+
-- insight_type: 'success_pattern' | 'failure_pattern' | 'optimization' | 'ethics_violation'
|
|
1305
|
+
-- confidence: 0.0 to 1.0
|
|
1306
|
+
CREATE TABLE IF NOT EXISTS reflections (
|
|
1307
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1308
|
+
trace_ids TEXT NOT NULL,
|
|
1309
|
+
insight_type TEXT NOT NULL CHECK(insight_type IN ('success_pattern','failure_pattern','optimization','ethics_violation')),
|
|
1310
|
+
description TEXT NOT NULL,
|
|
1311
|
+
affected_tools TEXT,
|
|
1312
|
+
affected_agents TEXT,
|
|
1313
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
1314
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
-- PLAYBOOK: evolved rules injected by Context Compiler (ACE Curator output)
|
|
1318
|
+
-- category: 'tool_selection' | 'response_quality' | 'error_avoidance' | 'optimization' | 'agent_creation'
|
|
1319
|
+
-- applicable_to: JSON array of contexts where this rule applies
|
|
1320
|
+
-- helpful_count / harmful_count: feedback from execution outcomes
|
|
1321
|
+
-- active: 0 = pruned by Curator
|
|
1322
|
+
CREATE TABLE IF NOT EXISTS playbook (
|
|
1323
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1324
|
+
rule TEXT NOT NULL,
|
|
1325
|
+
category TEXT NOT NULL CHECK(category IN ('tool_selection','response_quality','error_avoidance','optimization','agent_creation')),
|
|
1326
|
+
applicable_to TEXT,
|
|
1327
|
+
helpful_count INTEGER NOT NULL DEFAULT 0,
|
|
1328
|
+
harmful_count INTEGER NOT NULL DEFAULT 0,
|
|
1329
|
+
source_reflection_id INTEGER REFERENCES reflections(id) ON DELETE SET NULL,
|
|
1330
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1331
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1332
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1333
|
+
);
|
|
1334
|
+
|
|
1335
|
+
-- TOOL_CACHE: cached results for deterministic/expensive tool calls
|
|
1336
|
+
-- cache_key: hash of tool_id + serialized params
|
|
1337
|
+
-- ttl_seconds: 0 = no expiry
|
|
1338
|
+
CREATE TABLE IF NOT EXISTS tool_cache (
|
|
1339
|
+
cache_key TEXT PRIMARY KEY,
|
|
1340
|
+
tool_id TEXT NOT NULL,
|
|
1341
|
+
result TEXT NOT NULL,
|
|
1342
|
+
ttl_seconds INTEGER NOT NULL DEFAULT 3600,
|
|
1343
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
-- FTS5 indexes for fast semantic search in Context Compiler
|
|
1347
|
+
-- Created by initializeDatabase() via CONTEXT_ENGINE_SCHEMA
|
|
1348
|
+
-- Populated by syncToolsToFTS() and syncSkillsToFTS() from gateway/initializer.ts
|
|
1349
|
+
-- Triggers are NOT used - data is cleared and re-inserted on each sync to avoid schema drift
|
|
1350
|
+
|
|
1351
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS playbook_fts USING fts5(
|
|
1352
|
+
rule,
|
|
1353
|
+
category,
|
|
1354
|
+
applicable_to
|
|
1355
|
+
);
|
|
1356
|
+
|
|
1357
|
+
-- FTS5: tool catalog search (populated by syncToolCatalogToFTS from gateway/initializer.ts)
|
|
1358
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tools_fts USING fts5(
|
|
1359
|
+
tool_name,
|
|
1360
|
+
name,
|
|
1361
|
+
description,
|
|
1362
|
+
category
|
|
1363
|
+
);
|
|
1364
|
+
|
|
1365
|
+
-- FTS5: skills catalog search (populated by syncSkillsToFTS from gateway/initializer.ts)
|
|
1366
|
+
-- Simplified schema - standalone FTS5 table (no content table sync)
|
|
1367
|
+
-- Note: FTS5 tables are created programmatically in seed.ts to avoid "already exists" errors
|
|
1368
|
+
|
|
1369
|
+
-- REFRESH TOKENS: JWT refresh token storage (hash-based for security)
|
|
1370
|
+
-- Stores hashed refresh tokens with expiry and user linkage
|
|
1371
|
+
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|
1372
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
1373
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
1374
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
1375
|
+
expires_at INTEGER NOT NULL,
|
|
1376
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1377
|
+
revoked INTEGER NOT NULL DEFAULT 0
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1380
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user ON refresh_tokens(user_id);
|
|
1381
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash ON refresh_tokens(token_hash);
|
|
1382
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires ON refresh_tokens(expires_at);
|
|
1383
|
+
|
|
1384
|
+
-- Agent Bus: Message queue for worker-to-worker communication
|
|
1385
|
+
CREATE TABLE IF NOT EXISTS agent_bus_messages (
|
|
1386
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1387
|
+
event_type TEXT NOT NULL,
|
|
1388
|
+
from_worker_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
1389
|
+
to_worker_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
1390
|
+
topic TEXT,
|
|
1391
|
+
content TEXT NOT NULL,
|
|
1392
|
+
metadata TEXT,
|
|
1393
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1394
|
+
read INTEGER NOT NULL DEFAULT 0
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
CREATE INDEX IF NOT EXISTS idx_agent_bus_from_worker ON agent_bus_messages(from_worker_id);
|
|
1398
|
+
CREATE INDEX IF NOT EXISTS idx_agent_bus_to_worker ON agent_bus_messages(to_worker_id);
|
|
1399
|
+
CREATE INDEX IF NOT EXISTS idx_agent_bus_event_type ON agent_bus_messages(event_type);
|
|
1400
|
+
CREATE INDEX IF NOT EXISTS idx_agent_bus_read ON agent_bus_messages(read);
|
|
1401
|
+
|
|
1402
|
+
-- INDICES
|
|
1403
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_thread ON conversations(thread_id);
|
|
1404
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_role ON conversations(role);
|
|
1405
|
+
CREATE INDEX IF NOT EXISTS idx_scratchpad_thread ON scratchpad(thread_id);
|
|
1406
|
+
CREATE INDEX IF NOT EXISTS idx_traces_thread ON traces(thread_id);
|
|
1407
|
+
CREATE INDEX IF NOT EXISTS idx_traces_agent ON traces(agent_id);
|
|
1408
|
+
CREATE INDEX IF NOT EXISTS idx_traces_success ON traces(success);
|
|
1409
|
+
CREATE INDEX IF NOT EXISTS idx_reflections_type ON reflections(insight_type);
|
|
1410
|
+
CREATE INDEX IF NOT EXISTS idx_playbook_active ON playbook(active);
|
|
1411
|
+
CREATE INDEX IF NOT EXISTS idx_playbook_category ON playbook(category);
|
|
1412
|
+
CREATE INDEX IF NOT EXISTS idx_tool_cache_tool ON tool_cache(tool_id);
|
|
1413
|
+
|
|
1414
|
+
|
|
1415
|
+
`;
|
|
1416
|
+
|
|
1417
|
+
// ../core/src/storage/sqlite.ts
|
|
1418
|
+
var exports_sqlite = {};
|
|
1419
|
+
__export(exports_sqlite, {
|
|
1420
|
+
initializeDatabase: () => initializeDatabase,
|
|
1421
|
+
getDbPathLazy: () => getDbPathLazy,
|
|
1422
|
+
getDb: () => getDb,
|
|
1423
|
+
dbService: () => dbService,
|
|
1424
|
+
DatabaseService: () => DatabaseService
|
|
1425
|
+
});
|
|
1426
|
+
import { Database } from "bun:sqlite";
|
|
1427
|
+
import * as path3 from "path";
|
|
1428
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
1429
|
+
function getDbPath() {
|
|
1430
|
+
return path3.join(getHiveDir(), "data", "hive.db");
|
|
1431
|
+
}
|
|
1432
|
+
function getDbPathLazy() {
|
|
1433
|
+
return getDbPath();
|
|
1434
|
+
}
|
|
1435
|
+
function getDb() {
|
|
1436
|
+
if (!_db)
|
|
1437
|
+
throw new Error("DB no inicializada. Llama initializeDatabase() primero.");
|
|
1438
|
+
return _db;
|
|
1439
|
+
}
|
|
1440
|
+
function initializeDatabase() {
|
|
1441
|
+
const hiveDir = getHiveDir();
|
|
1442
|
+
const dir = path3.join(hiveDir, "data");
|
|
1443
|
+
if (!existsSync3(dir)) {
|
|
1444
|
+
mkdirSync3(dir, { recursive: true });
|
|
1445
|
+
}
|
|
1446
|
+
const dbPath = getDbPath();
|
|
1447
|
+
const dbFileExists = existsSync3(dbPath);
|
|
1448
|
+
_db = new Database(dbPath, { create: true });
|
|
1449
|
+
_db.exec(SCHEMA);
|
|
1450
|
+
_db.exec(PROJECTS_SCHEMA);
|
|
1451
|
+
_db.exec(CONTEXT_ENGINE_SCHEMA);
|
|
1452
|
+
ensureSchemaSync();
|
|
1453
|
+
return _db;
|
|
1454
|
+
}
|
|
1455
|
+
function ensureColumnExists(tableName, columnName, columnDefinition) {
|
|
1456
|
+
if (!_db)
|
|
1457
|
+
return;
|
|
1458
|
+
try {
|
|
1459
|
+
const info = _db.query(`PRAGMA table_info(${tableName})`).all();
|
|
1460
|
+
const exists = info.some((col) => col.name === columnName);
|
|
1461
|
+
if (!exists) {
|
|
1462
|
+
logger.info(`\uD83D\uDEE0\uFE0F A\xF1adiendo columna faltante '${columnName}' a la tabla '${tableName}'`);
|
|
1463
|
+
_db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDefinition}`);
|
|
1464
|
+
}
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
logger.warn(`\u26A0\uFE0F No se pudo verificar/a\xF1adir la columna '${columnName}' en '${tableName}':`, { error: err.message });
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function ensureSchemaSync() {
|
|
1470
|
+
if (!_db)
|
|
1471
|
+
return;
|
|
1472
|
+
ensureColumnExists("users", "email", "TEXT");
|
|
1473
|
+
ensureColumnExists("users", "password_hash", "TEXT");
|
|
1474
|
+
ensureColumnExists("mcp_servers", "tools_count", "INTEGER DEFAULT 0");
|
|
1475
|
+
ensureColumnExists("mcp_servers", "status", "TEXT NOT NULL DEFAULT 'disconnected'");
|
|
1476
|
+
ensureColumnExists("mcp_servers", "env_encrypted", "TEXT");
|
|
1477
|
+
ensureColumnExists("mcp_servers", "env_iv", "TEXT");
|
|
1478
|
+
ensureColumnExists("mcp_servers", "headers_encrypted", "TEXT");
|
|
1479
|
+
ensureColumnExists("mcp_servers", "headers_iv", "TEXT");
|
|
1480
|
+
ensureColumnExists("providers", "api_key_encrypted", "TEXT");
|
|
1481
|
+
ensureColumnExists("providers", "api_key_iv", "TEXT");
|
|
1482
|
+
ensureColumnExists("providers", "headers_encrypted", "TEXT");
|
|
1483
|
+
ensureColumnExists("providers", "headers_iv", "TEXT");
|
|
1484
|
+
ensureColumnExists("providers", "num_ctx", "INTEGER");
|
|
1485
|
+
ensureColumnExists("providers", "num_gpu", "INTEGER DEFAULT -1");
|
|
1486
|
+
ensureColumnExists("agents", "headers_encrypted", "TEXT");
|
|
1487
|
+
ensureColumnExists("agents", "headers_iv", "TEXT");
|
|
1488
|
+
ensureColumnExists("agents", "system_prompt", "TEXT");
|
|
1489
|
+
ensureColumnExists("agents", "role", "TEXT NOT NULL DEFAULT 'coordinator'");
|
|
1490
|
+
ensureColumnExists("agents", "tools_json", "TEXT");
|
|
1491
|
+
ensureColumnExists("agents", "skills_json", "TEXT");
|
|
1492
|
+
ensureColumnExists("agents", "parent_id", "TEXT");
|
|
1493
|
+
ensureColumnExists("agents", "max_iterations", "INTEGER NOT NULL DEFAULT 10");
|
|
1494
|
+
ensureColumnExists("agents", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1495
|
+
ensureColumnExists("agents", "workspace", "TEXT");
|
|
1496
|
+
ensureColumnExists("tasks", "priority", "INTEGER NOT NULL DEFAULT 0");
|
|
1497
|
+
ensureColumnExists("tasks", "depends_on", "TEXT");
|
|
1498
|
+
ensureColumnExists("tasks", "error", "TEXT");
|
|
1499
|
+
ensureColumnExists("tasks", "completed_at", "INTEGER");
|
|
1500
|
+
ensureColumnExists("tools", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1501
|
+
ensureColumnExists("tools", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1502
|
+
ensureColumnExists("skills", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1503
|
+
ensureColumnExists("skills", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1504
|
+
ensureColumnExists("cron_jobs", "max_runs", "INTEGER");
|
|
1505
|
+
ensureColumnExists("cron_jobs", "run_count", "INTEGER NOT NULL DEFAULT 0");
|
|
1506
|
+
ensureColumnExists("cron_jobs", "expires_at", "INTEGER");
|
|
1507
|
+
ensureColumnExists("conversations", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1508
|
+
ensureColumnExists("conversations", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1509
|
+
ensureColumnExists("conversations", "reasoning_content", "TEXT");
|
|
1510
|
+
ensureColumnExists("summaries", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1511
|
+
ensureColumnExists("summaries", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1512
|
+
ensureColumnExists("scratchpad", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1513
|
+
ensureColumnExists("scratchpad", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1514
|
+
ensureColumnExists("traces", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1515
|
+
ensureColumnExists("reflections", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1516
|
+
ensureColumnExists("playbook", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1517
|
+
ensureColumnExists("playbook", "updated_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1518
|
+
ensureColumnExists("tool_cache", "created_at", "INTEGER NOT NULL DEFAULT (unixepoch())");
|
|
1519
|
+
if (_db) {
|
|
1520
|
+
_db.query(`UPDATE providers SET base_url = 'https://api.groq.com/openai/v1' WHERE id = 'groq' AND base_url = 'https://api.groq.com/v1'`).run();
|
|
1521
|
+
_db.query(`UPDATE providers SET base_url = 'https://api.openai.com/v1' WHERE id = 'openai' AND base_url = 'https://api.openai.com'`).run();
|
|
1522
|
+
_db.query(`UPDATE providers SET base_url = NULL WHERE id = 'gemini' AND base_url = 'https://generativelanguage.googleapis.com/v1beta'`).run();
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
class DatabaseService {
|
|
1527
|
+
log = logger.child("sqlite");
|
|
1528
|
+
get db() {
|
|
1529
|
+
if (!_db) {
|
|
1530
|
+
initializeDatabase();
|
|
1531
|
+
}
|
|
1532
|
+
return _db;
|
|
1533
|
+
}
|
|
1534
|
+
close() {
|
|
1535
|
+
if (_db) {
|
|
1536
|
+
_db.close();
|
|
1537
|
+
_db = null;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
updateMCPServer(id, updates) {
|
|
1541
|
+
const fields = [];
|
|
1542
|
+
const values = { $id: id };
|
|
1543
|
+
if (updates.enabled !== undefined) {
|
|
1544
|
+
fields.push("enabled = $enabled");
|
|
1545
|
+
values.$enabled = updates.enabled ? 1 : 0;
|
|
1546
|
+
}
|
|
1547
|
+
if (updates.active !== undefined) {
|
|
1548
|
+
fields.push("active = $active");
|
|
1549
|
+
values.$active = updates.active ? 1 : 0;
|
|
1550
|
+
}
|
|
1551
|
+
if (updates.status !== undefined) {
|
|
1552
|
+
fields.push("status = $status");
|
|
1553
|
+
values.$status = updates.status;
|
|
1554
|
+
}
|
|
1555
|
+
if (updates.tools_count !== undefined) {
|
|
1556
|
+
fields.push("tools_count = $tools_count");
|
|
1557
|
+
values.$tools_count = updates.tools_count;
|
|
1558
|
+
}
|
|
1559
|
+
if (updates.transport !== undefined) {
|
|
1560
|
+
fields.push("transport = $transport");
|
|
1561
|
+
values.$transport = updates.transport;
|
|
1562
|
+
}
|
|
1563
|
+
if (updates.command !== undefined) {
|
|
1564
|
+
fields.push("command = $command");
|
|
1565
|
+
values.$command = updates.command;
|
|
1566
|
+
}
|
|
1567
|
+
if (updates.args !== undefined) {
|
|
1568
|
+
fields.push("args = $args");
|
|
1569
|
+
values.$args = JSON.stringify(updates.args);
|
|
1570
|
+
}
|
|
1571
|
+
if (updates.url !== undefined) {
|
|
1572
|
+
fields.push("url = $url");
|
|
1573
|
+
values.$url = updates.url;
|
|
1574
|
+
}
|
|
1575
|
+
if (updates.env_encrypted !== undefined) {
|
|
1576
|
+
fields.push("env_encrypted = $env_encrypted");
|
|
1577
|
+
values.$env_encrypted = updates.env_encrypted;
|
|
1578
|
+
}
|
|
1579
|
+
if (updates.env_iv !== undefined) {
|
|
1580
|
+
fields.push("env_iv = $env_iv");
|
|
1581
|
+
values.$env_iv = updates.env_iv;
|
|
1582
|
+
}
|
|
1583
|
+
if (updates.headers_encrypted !== undefined) {
|
|
1584
|
+
fields.push("headers_encrypted = $headers_encrypted");
|
|
1585
|
+
values.$headers_encrypted = updates.headers_encrypted;
|
|
1586
|
+
}
|
|
1587
|
+
if (updates.headers_iv !== undefined) {
|
|
1588
|
+
fields.push("headers_iv = $headers_iv");
|
|
1589
|
+
values.$headers_iv = updates.headers_iv;
|
|
1590
|
+
}
|
|
1591
|
+
if (fields.length === 0)
|
|
1592
|
+
return;
|
|
1593
|
+
const query = `UPDATE mcp_servers SET ${fields.join(", ")} WHERE id = $id`;
|
|
1594
|
+
try {
|
|
1595
|
+
this.db.query(query).run(values);
|
|
1596
|
+
this.log.debug(`MCP server ${id} updated in DB`);
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
this.log.error(`Failed to update MCP server ${id}: ${error.message}`);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
getActiveAgentWorkspace() {
|
|
1602
|
+
try {
|
|
1603
|
+
const row = this.db.query("SELECT workspace FROM agents WHERE role = 'coordinator' LIMIT 1").get();
|
|
1604
|
+
const ws = row?.workspace;
|
|
1605
|
+
return ws && ws !== "null" ? ws : null;
|
|
1606
|
+
} catch {
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
listMCPServers() {
|
|
1611
|
+
try {
|
|
1612
|
+
return this.db.query("SELECT * FROM mcp_servers").all();
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
this.log.error(`Failed to list MCP servers: ${error.message}`);
|
|
1615
|
+
return [];
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
createTask(task) {
|
|
1619
|
+
const result = this.db.query(`
|
|
1620
|
+
INSERT INTO tasks (project_id, agent_id, parent_task_id, name, description)
|
|
1621
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1622
|
+
`).run(task.project_id, task.agent_id ?? null, task.parent_task_id ?? null, task.name, task.description ?? null);
|
|
1623
|
+
return Number(result.lastInsertRowid);
|
|
1624
|
+
}
|
|
1625
|
+
updateTask(taskId, updates) {
|
|
1626
|
+
const fields = ["updated_at = unixepoch()"];
|
|
1627
|
+
const values = [];
|
|
1628
|
+
if (updates.status !== undefined) {
|
|
1629
|
+
fields.push("status = ?");
|
|
1630
|
+
values.push(updates.status);
|
|
1631
|
+
}
|
|
1632
|
+
if (updates.progress !== undefined) {
|
|
1633
|
+
fields.push("progress = ?");
|
|
1634
|
+
values.push(updates.progress);
|
|
1635
|
+
}
|
|
1636
|
+
if (updates.result !== undefined) {
|
|
1637
|
+
fields.push("result = ?");
|
|
1638
|
+
values.push(updates.result);
|
|
1639
|
+
}
|
|
1640
|
+
if (updates.agent_id !== undefined) {
|
|
1641
|
+
fields.push("agent_id = ?");
|
|
1642
|
+
values.push(updates.agent_id);
|
|
1643
|
+
}
|
|
1644
|
+
values.push(taskId);
|
|
1645
|
+
const res = this.db.query(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
1646
|
+
return res.changes > 0;
|
|
1647
|
+
}
|
|
1648
|
+
getTasksByProject(projectId) {
|
|
1649
|
+
return this.db.query("SELECT * FROM tasks WHERE project_id = ? ORDER BY id ASC").all(projectId);
|
|
1650
|
+
}
|
|
1651
|
+
getProjectWithTasks(projectId) {
|
|
1652
|
+
const project = this.db.query("SELECT * FROM projects WHERE id = ?").get(projectId);
|
|
1653
|
+
if (!project)
|
|
1654
|
+
return null;
|
|
1655
|
+
project.tasks = this.getTasksByProject(projectId);
|
|
1656
|
+
return project;
|
|
1657
|
+
}
|
|
1658
|
+
recalcProjectProgress(projectId) {
|
|
1659
|
+
const row = this.db.query("SELECT AVG(progress) as avg_progress FROM tasks WHERE project_id = ?").get(projectId);
|
|
1660
|
+
const avg = Math.round(row?.avg_progress ?? 0);
|
|
1661
|
+
this.db.query("UPDATE projects SET progress = ?, updated_at = unixepoch() WHERE id = ?").run(avg, projectId);
|
|
1662
|
+
return avg;
|
|
1663
|
+
}
|
|
1664
|
+
saveMCPServer(server) {
|
|
1665
|
+
try {
|
|
1666
|
+
this.db.query(`
|
|
1667
|
+
INSERT OR REPLACE INTO mcp_servers (id, name, transport, command, args, url, env_encrypted, env_iv, headers_encrypted, headers_iv, enabled, active, builtin, tools_count, status)
|
|
1668
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1669
|
+
`).run(server.id || server.name, server.name, server.transport, server.command || null, JSON.stringify(server.args || []), server.url || null, server.env_encrypted || null, server.env_iv || null, server.headers_encrypted || null, server.headers_iv || null, server.enabled ? 1 : 0, server.active ? 1 : 0, server.builtin ? 1 : 0, server.tools_count || 0, server.status || "disconnected");
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
this.log.error(`Failed to save MCP server ${server.name}: ${error.message}`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
var _db = null, dbService;
|
|
1676
|
+
var init_sqlite = __esm(() => {
|
|
1677
|
+
init_logger();
|
|
1678
|
+
init_loader();
|
|
1679
|
+
dbService = new DatabaseService;
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
// ../../node_modules/.bun/croner@10.0.1/node_modules/croner/dist/croner.js
|
|
1683
|
+
function T(s) {
|
|
1684
|
+
return Date.UTC(s.y, s.m - 1, s.d, s.h, s.i, s.s);
|
|
1685
|
+
}
|
|
1686
|
+
function D(s, e) {
|
|
1687
|
+
return s.y === e.y && s.m === e.m && s.d === e.d && s.h === e.h && s.i === e.i && s.s === e.s;
|
|
1688
|
+
}
|
|
1689
|
+
function A(s, e) {
|
|
1690
|
+
let t = new Date(Date.parse(s));
|
|
1691
|
+
if (isNaN(t))
|
|
1692
|
+
throw new Error("Invalid ISO8601 passed to timezone parser.");
|
|
1693
|
+
let r = s.substring(9);
|
|
1694
|
+
return r.includes("Z") || r.includes("+") || r.includes("-") ? b(t.getUTCFullYear(), t.getUTCMonth() + 1, t.getUTCDate(), t.getUTCHours(), t.getUTCMinutes(), t.getUTCSeconds(), "Etc/UTC") : b(t.getFullYear(), t.getMonth() + 1, t.getDate(), t.getHours(), t.getMinutes(), t.getSeconds(), e);
|
|
1695
|
+
}
|
|
1696
|
+
function v(s, e, t) {
|
|
1697
|
+
return k(A(s, e), t);
|
|
1698
|
+
}
|
|
1699
|
+
function k(s, e) {
|
|
1700
|
+
let t = new Date(T(s)), r = g(t, s.tz), n = T(s), i = T(r), a = n - i, o = new Date(t.getTime() + a), h = g(o, s.tz);
|
|
1701
|
+
if (D(h, s)) {
|
|
1702
|
+
let u = new Date(o.getTime() - 3600000), d = g(u, s.tz);
|
|
1703
|
+
return D(d, s) ? u : o;
|
|
1704
|
+
}
|
|
1705
|
+
let l = new Date(o.getTime() + T(s) - T(h)), y = g(l, s.tz);
|
|
1706
|
+
if (D(y, s))
|
|
1707
|
+
return l;
|
|
1708
|
+
if (e)
|
|
1709
|
+
throw new Error("Invalid date passed to fromTZ()");
|
|
1710
|
+
return o.getTime() > l.getTime() ? o : l;
|
|
1711
|
+
}
|
|
1712
|
+
function g(s, e) {
|
|
1713
|
+
let t, r;
|
|
1714
|
+
try {
|
|
1715
|
+
t = new Intl.DateTimeFormat("en-US", { timeZone: e, year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hour12: false }), r = t.formatToParts(s);
|
|
1716
|
+
} catch (i) {
|
|
1717
|
+
let a = i instanceof Error ? i.message : String(i);
|
|
1718
|
+
throw new RangeError(`toTZ: Invalid timezone '${e}' or date. Please provide a valid IANA timezone (e.g., 'America/New_York', 'Europe/Stockholm'). Original error: ${a}`);
|
|
1719
|
+
}
|
|
1720
|
+
let n = { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0 };
|
|
1721
|
+
for (let i of r)
|
|
1722
|
+
(i.type === "year" || i.type === "month" || i.type === "day" || i.type === "hour" || i.type === "minute" || i.type === "second") && (n[i.type] = parseInt(i.value, 10));
|
|
1723
|
+
if (isNaN(n.year) || isNaN(n.month) || isNaN(n.day) || isNaN(n.hour) || isNaN(n.minute) || isNaN(n.second))
|
|
1724
|
+
throw new Error(`toTZ: Failed to parse all date components from timezone '${e}'. This may indicate an invalid date or timezone configuration. Parsed components: ${JSON.stringify(n)}`);
|
|
1725
|
+
return n.hour === 24 && (n.hour = 0), { y: n.year, m: n.month, d: n.day, h: n.hour, i: n.minute, s: n.second, tz: e };
|
|
1726
|
+
}
|
|
1727
|
+
function b(s, e, t, r, n, i, a) {
|
|
1728
|
+
return { y: s, m: e, d: t, h: r, i: n, s: i, tz: a };
|
|
1729
|
+
}
|
|
1730
|
+
function R(s2) {
|
|
1731
|
+
if (s2 === undefined && (s2 = {}), delete s2.name, s2.legacyMode !== undefined && s2.domAndDow === undefined ? s2.domAndDow = !s2.legacyMode : s2.domAndDow === undefined && (s2.domAndDow = false), s2.legacyMode = !s2.domAndDow, s2.paused = s2.paused === undefined ? false : s2.paused, s2.maxRuns = s2.maxRuns === undefined ? 1 / 0 : s2.maxRuns, s2.catch = s2.catch === undefined ? false : s2.catch, s2.interval = s2.interval === undefined ? 0 : parseInt(s2.interval.toString(), 10), s2.utcOffset = s2.utcOffset === undefined ? undefined : parseInt(s2.utcOffset.toString(), 10), s2.dayOffset = s2.dayOffset === undefined ? 0 : parseInt(s2.dayOffset.toString(), 10), s2.unref = s2.unref === undefined ? false : s2.unref, s2.mode = s2.mode === undefined ? "auto" : s2.mode, s2.alternativeWeekdays = s2.alternativeWeekdays === undefined ? false : s2.alternativeWeekdays, s2.sloppyRanges = s2.sloppyRanges === undefined ? false : s2.sloppyRanges, !["auto", "5-part", "6-part", "7-part", "5-or-6-parts", "6-or-7-parts"].includes(s2.mode))
|
|
1732
|
+
throw new Error("CronOptions: mode must be one of 'auto', '5-part', '6-part', '7-part', '5-or-6-parts', or '6-or-7-parts'.");
|
|
1733
|
+
if (s2.startAt && (s2.startAt = new m(s2.startAt, s2.timezone)), s2.stopAt && (s2.stopAt = new m(s2.stopAt, s2.timezone)), s2.interval !== null) {
|
|
1734
|
+
if (isNaN(s2.interval))
|
|
1735
|
+
throw new Error("CronOptions: Supplied value for interval is not a number");
|
|
1736
|
+
if (s2.interval < 0)
|
|
1737
|
+
throw new Error("CronOptions: Supplied value for interval can not be negative");
|
|
1738
|
+
}
|
|
1739
|
+
if (s2.utcOffset !== undefined) {
|
|
1740
|
+
if (isNaN(s2.utcOffset))
|
|
1741
|
+
throw new Error("CronOptions: Invalid value passed for utcOffset, should be number representing minutes offset from UTC.");
|
|
1742
|
+
if (s2.utcOffset < -870 || s2.utcOffset > 870)
|
|
1743
|
+
throw new Error("CronOptions: utcOffset out of bounds.");
|
|
1744
|
+
if (s2.utcOffset !== undefined && s2.timezone)
|
|
1745
|
+
throw new Error("CronOptions: Combining 'utcOffset' with 'timezone' is not allowed.");
|
|
1746
|
+
}
|
|
1747
|
+
if (s2.unref !== true && s2.unref !== false)
|
|
1748
|
+
throw new Error("CronOptions: Unref should be either true, false or undefined(false).");
|
|
1749
|
+
if (s2.dayOffset !== undefined && s2.dayOffset !== 0 && isNaN(s2.dayOffset))
|
|
1750
|
+
throw new Error("CronOptions: Invalid value passed for dayOffset, should be a number representing days to offset.");
|
|
1751
|
+
return s2;
|
|
1752
|
+
}
|
|
1753
|
+
function p(s2) {
|
|
1754
|
+
return Object.prototype.toString.call(s2) === "[object Function]" || typeof s2 == "function" || s2 instanceof Function;
|
|
1755
|
+
}
|
|
1756
|
+
function _(s2) {
|
|
1757
|
+
return p(s2);
|
|
1758
|
+
}
|
|
1759
|
+
function x(s2) {
|
|
1760
|
+
typeof Deno < "u" && typeof Deno.unrefTimer < "u" ? Deno.unrefTimer(s2) : s2 && typeof s2.unref < "u" && s2.unref();
|
|
1761
|
+
}
|
|
1762
|
+
var O, C = class {
|
|
1763
|
+
pattern;
|
|
1764
|
+
timezone;
|
|
1765
|
+
mode;
|
|
1766
|
+
alternativeWeekdays;
|
|
1767
|
+
sloppyRanges;
|
|
1768
|
+
second;
|
|
1769
|
+
minute;
|
|
1770
|
+
hour;
|
|
1771
|
+
day;
|
|
1772
|
+
month;
|
|
1773
|
+
dayOfWeek;
|
|
1774
|
+
year;
|
|
1775
|
+
lastDayOfMonth;
|
|
1776
|
+
lastWeekday;
|
|
1777
|
+
nearestWeekdays;
|
|
1778
|
+
starDOM;
|
|
1779
|
+
starDOW;
|
|
1780
|
+
starYear;
|
|
1781
|
+
useAndLogic;
|
|
1782
|
+
constructor(e, t, r) {
|
|
1783
|
+
this.pattern = e, this.timezone = t, this.mode = r?.mode ?? "auto", this.alternativeWeekdays = r?.alternativeWeekdays ?? false, this.sloppyRanges = r?.sloppyRanges ?? false, this.second = Array(60).fill(0), this.minute = Array(60).fill(0), this.hour = Array(24).fill(0), this.day = Array(31).fill(0), this.month = Array(12).fill(0), this.dayOfWeek = Array(7).fill(0), this.year = Array(1e4).fill(0), this.lastDayOfMonth = false, this.lastWeekday = false, this.nearestWeekdays = Array(31).fill(0), this.starDOM = false, this.starDOW = false, this.starYear = false, this.useAndLogic = false, this.parse();
|
|
1784
|
+
}
|
|
1785
|
+
parse() {
|
|
1786
|
+
if (!(typeof this.pattern == "string" || this.pattern instanceof String))
|
|
1787
|
+
throw new TypeError("CronPattern: Pattern has to be of type string.");
|
|
1788
|
+
this.pattern.indexOf("@") >= 0 && (this.pattern = this.handleNicknames(this.pattern).trim());
|
|
1789
|
+
let e = this.pattern.match(/\S+/g) || [""], t = e.length;
|
|
1790
|
+
if (e.length < 5 || e.length > 7)
|
|
1791
|
+
throw new TypeError("CronPattern: invalid configuration format ('" + this.pattern + "'), exactly five, six, or seven space separated parts are required.");
|
|
1792
|
+
if (this.mode !== "auto") {
|
|
1793
|
+
let n;
|
|
1794
|
+
switch (this.mode) {
|
|
1795
|
+
case "5-part":
|
|
1796
|
+
n = 5;
|
|
1797
|
+
break;
|
|
1798
|
+
case "6-part":
|
|
1799
|
+
n = 6;
|
|
1800
|
+
break;
|
|
1801
|
+
case "7-part":
|
|
1802
|
+
n = 7;
|
|
1803
|
+
break;
|
|
1804
|
+
case "5-or-6-parts":
|
|
1805
|
+
n = [5, 6];
|
|
1806
|
+
break;
|
|
1807
|
+
case "6-or-7-parts":
|
|
1808
|
+
n = [6, 7];
|
|
1809
|
+
break;
|
|
1810
|
+
default:
|
|
1811
|
+
n = 0;
|
|
1812
|
+
}
|
|
1813
|
+
if (!(Array.isArray(n) ? n.includes(t) : t === n)) {
|
|
1814
|
+
let a = Array.isArray(n) ? n.join(" or ") : n.toString();
|
|
1815
|
+
throw new TypeError(`CronPattern: mode '${this.mode}' requires exactly ${a} parts, but pattern '${this.pattern}' has ${t} parts.`);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (e.length === 5 && e.unshift("0"), e.length === 6 && e.push("*"), e[3].toUpperCase() === "LW" ? (this.lastWeekday = true, e[3] = "") : e[3].toUpperCase().indexOf("L") >= 0 && (e[3] = e[3].replace(/L/gi, ""), this.lastDayOfMonth = true), e[3] == "*" && (this.starDOM = true), e[6] == "*" && (this.starYear = true), e[4].length >= 3 && (e[4] = this.replaceAlphaMonths(e[4])), e[5].length >= 3 && (e[5] = this.alternativeWeekdays ? this.replaceAlphaDaysQuartz(e[5]) : this.replaceAlphaDays(e[5])), e[5].startsWith("+") && (this.useAndLogic = true, e[5] = e[5].substring(1), e[5] === ""))
|
|
1819
|
+
throw new TypeError("CronPattern: Day-of-week field cannot be empty after '+' modifier.");
|
|
1820
|
+
switch (e[5] == "*" && (this.starDOW = true), this.pattern.indexOf("?") >= 0 && (e[0] = e[0].replace(/\?/g, "*"), e[1] = e[1].replace(/\?/g, "*"), e[2] = e[2].replace(/\?/g, "*"), e[3] = e[3].replace(/\?/g, "*"), e[4] = e[4].replace(/\?/g, "*"), e[5] = e[5].replace(/\?/g, "*"), e[6] && (e[6] = e[6].replace(/\?/g, "*"))), this.mode) {
|
|
1821
|
+
case "5-part":
|
|
1822
|
+
e[0] = "0", e[6] = "*";
|
|
1823
|
+
break;
|
|
1824
|
+
case "6-part":
|
|
1825
|
+
e[6] = "*";
|
|
1826
|
+
break;
|
|
1827
|
+
case "5-or-6-parts":
|
|
1828
|
+
e[6] = "*";
|
|
1829
|
+
break;
|
|
1830
|
+
case "6-or-7-parts":
|
|
1831
|
+
break;
|
|
1832
|
+
case "7-part":
|
|
1833
|
+
case "auto":
|
|
1834
|
+
break;
|
|
1835
|
+
}
|
|
1836
|
+
this.throwAtIllegalCharacters(e), this.partToArray("second", e[0], 0, 1), this.partToArray("minute", e[1], 0, 1), this.partToArray("hour", e[2], 0, 1), this.partToArray("day", e[3], -1, 1), this.partToArray("month", e[4], -1, 1);
|
|
1837
|
+
let r = this.alternativeWeekdays ? -1 : 0;
|
|
1838
|
+
this.partToArray("dayOfWeek", e[5], r, 63), this.partToArray("year", e[6], 0, 1), !this.alternativeWeekdays && this.dayOfWeek[7] && (this.dayOfWeek[0] = this.dayOfWeek[7]);
|
|
1839
|
+
}
|
|
1840
|
+
partToArray(e, t, r, n) {
|
|
1841
|
+
let i = this[e], a = e === "day" && this.lastDayOfMonth, o = e === "day" && this.lastWeekday;
|
|
1842
|
+
if (t === "" && !a && !o)
|
|
1843
|
+
throw new TypeError("CronPattern: configuration entry " + e + " (" + t + ") is empty, check for trailing spaces.");
|
|
1844
|
+
if (t === "*")
|
|
1845
|
+
return i.fill(n);
|
|
1846
|
+
let h = t.split(",");
|
|
1847
|
+
if (h.length > 1)
|
|
1848
|
+
for (let l = 0;l < h.length; l++)
|
|
1849
|
+
this.partToArray(e, h[l], r, n);
|
|
1850
|
+
else
|
|
1851
|
+
t.indexOf("-") !== -1 && t.indexOf("/") !== -1 ? this.handleRangeWithStepping(t, e, r, n) : t.indexOf("-") !== -1 ? this.handleRange(t, e, r, n) : t.indexOf("/") !== -1 ? this.handleStepping(t, e, r, n) : t !== "" && this.handleNumber(t, e, r, n);
|
|
1852
|
+
}
|
|
1853
|
+
throwAtIllegalCharacters(e) {
|
|
1854
|
+
for (let t = 0;t < e.length; t++)
|
|
1855
|
+
if ((t === 3 ? /[^/*0-9,\-WwLl]+/ : t === 5 ? /[^/*0-9,\-#Ll]+/ : /[^/*0-9,\-]+/).test(e[t]))
|
|
1856
|
+
throw new TypeError("CronPattern: configuration entry " + t + " (" + e[t] + ") contains illegal characters.");
|
|
1857
|
+
}
|
|
1858
|
+
handleNumber(e, t, r, n) {
|
|
1859
|
+
let i = this.extractNth(e, t), a = e.toUpperCase().includes("W");
|
|
1860
|
+
if (t !== "day" && a)
|
|
1861
|
+
throw new TypeError("CronPattern: Nearest weekday modifier (W) only allowed in day-of-month.");
|
|
1862
|
+
a && (t = "nearestWeekdays");
|
|
1863
|
+
let o = parseInt(i[0], 10) + r;
|
|
1864
|
+
if (isNaN(o))
|
|
1865
|
+
throw new TypeError("CronPattern: " + t + " is not a number: '" + e + "'");
|
|
1866
|
+
this.setPart(t, o, i[1] || n);
|
|
1867
|
+
}
|
|
1868
|
+
setPart(e, t, r) {
|
|
1869
|
+
if (!Object.prototype.hasOwnProperty.call(this, e))
|
|
1870
|
+
throw new TypeError("CronPattern: Invalid part specified: " + e);
|
|
1871
|
+
if (e === "dayOfWeek") {
|
|
1872
|
+
if (t === 7 && (t = 0), t < 0 || t > 6)
|
|
1873
|
+
throw new RangeError("CronPattern: Invalid value for dayOfWeek: " + t);
|
|
1874
|
+
this.setNthWeekdayOfMonth(t, r);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (e === "second" || e === "minute") {
|
|
1878
|
+
if (t < 0 || t >= 60)
|
|
1879
|
+
throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
1880
|
+
} else if (e === "hour") {
|
|
1881
|
+
if (t < 0 || t >= 24)
|
|
1882
|
+
throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
1883
|
+
} else if (e === "day" || e === "nearestWeekdays") {
|
|
1884
|
+
if (t < 0 || t >= 31)
|
|
1885
|
+
throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
1886
|
+
} else if (e === "month") {
|
|
1887
|
+
if (t < 0 || t >= 12)
|
|
1888
|
+
throw new RangeError("CronPattern: Invalid value for " + e + ": " + t);
|
|
1889
|
+
} else if (e === "year" && (t < 1 || t >= 1e4))
|
|
1890
|
+
throw new RangeError("CronPattern: Invalid value for " + e + ": " + t + " (supported range: 1-9999)");
|
|
1891
|
+
this[e][t] = r;
|
|
1892
|
+
}
|
|
1893
|
+
validateNotNaN(e, t) {
|
|
1894
|
+
if (isNaN(e))
|
|
1895
|
+
throw new TypeError(t);
|
|
1896
|
+
}
|
|
1897
|
+
validateRange(e, t, r, n, i) {
|
|
1898
|
+
if (e > t)
|
|
1899
|
+
throw new TypeError("CronPattern: From value is larger than to value: '" + i + "'");
|
|
1900
|
+
if (r !== undefined) {
|
|
1901
|
+
if (r === 0)
|
|
1902
|
+
throw new TypeError("CronPattern: Syntax error, illegal stepping: 0");
|
|
1903
|
+
if (r > this[n].length)
|
|
1904
|
+
throw new TypeError("CronPattern: Syntax error, steps cannot be greater than maximum value of part (" + this[n].length + ")");
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
handleRangeWithStepping(e, t, r, n) {
|
|
1908
|
+
if (e.toUpperCase().includes("W"))
|
|
1909
|
+
throw new TypeError("CronPattern: Syntax error, W is not allowed in ranges with stepping.");
|
|
1910
|
+
let i = this.extractNth(e, t), a = i[0].match(/^(\d+)-(\d+)\/(\d+)$/);
|
|
1911
|
+
if (a === null)
|
|
1912
|
+
throw new TypeError("CronPattern: Syntax error, illegal range with stepping: '" + e + "'");
|
|
1913
|
+
let [, o, h, l] = a, y = parseInt(o, 10) + r, u = parseInt(h, 10) + r, d = parseInt(l, 10);
|
|
1914
|
+
this.validateNotNaN(y, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(u, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateNotNaN(d, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(y, u, d, t, e);
|
|
1915
|
+
for (let c = y;c <= u; c += d)
|
|
1916
|
+
this.setPart(t, c, i[1] || n);
|
|
1917
|
+
}
|
|
1918
|
+
extractNth(e, t) {
|
|
1919
|
+
let r = e, n;
|
|
1920
|
+
if (r.includes("#")) {
|
|
1921
|
+
if (t !== "dayOfWeek")
|
|
1922
|
+
throw new Error("CronPattern: nth (#) only allowed in day-of-week field");
|
|
1923
|
+
n = r.split("#")[1], r = r.split("#")[0];
|
|
1924
|
+
} else if (r.toUpperCase().endsWith("L")) {
|
|
1925
|
+
if (t !== "dayOfWeek")
|
|
1926
|
+
throw new Error("CronPattern: L modifier only allowed in day-of-week field (use L alone for day-of-month)");
|
|
1927
|
+
n = "L", r = r.slice(0, -1);
|
|
1928
|
+
}
|
|
1929
|
+
return [r, n];
|
|
1930
|
+
}
|
|
1931
|
+
handleRange(e, t, r, n) {
|
|
1932
|
+
if (e.toUpperCase().includes("W"))
|
|
1933
|
+
throw new TypeError("CronPattern: Syntax error, W is not allowed in a range.");
|
|
1934
|
+
let i = this.extractNth(e, t), a = i[0].split("-");
|
|
1935
|
+
if (a.length !== 2)
|
|
1936
|
+
throw new TypeError("CronPattern: Syntax error, illegal range: '" + e + "'");
|
|
1937
|
+
let o = parseInt(a[0], 10) + r, h = parseInt(a[1], 10) + r;
|
|
1938
|
+
this.validateNotNaN(o, "CronPattern: Syntax error, illegal lower range (NaN)"), this.validateNotNaN(h, "CronPattern: Syntax error, illegal upper range (NaN)"), this.validateRange(o, h, undefined, t, e);
|
|
1939
|
+
for (let l = o;l <= h; l++)
|
|
1940
|
+
this.setPart(t, l, i[1] || n);
|
|
1941
|
+
}
|
|
1942
|
+
handleStepping(e, t, r, n) {
|
|
1943
|
+
if (e.toUpperCase().includes("W"))
|
|
1944
|
+
throw new TypeError("CronPattern: Syntax error, W is not allowed in parts with stepping.");
|
|
1945
|
+
let i = this.extractNth(e, t), a = i[0].split("/");
|
|
1946
|
+
if (a.length !== 2)
|
|
1947
|
+
throw new TypeError("CronPattern: Syntax error, illegal stepping: '" + e + "'");
|
|
1948
|
+
if (this.sloppyRanges)
|
|
1949
|
+
a[0] === "" && (a[0] = "*");
|
|
1950
|
+
else {
|
|
1951
|
+
if (a[0] === "")
|
|
1952
|
+
throw new TypeError("CronPattern: Syntax error, stepping with missing prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
1953
|
+
if (a[0] !== "*")
|
|
1954
|
+
throw new TypeError("CronPattern: Syntax error, stepping with numeric prefix ('" + e + "') is not allowed. Use wildcard (*/step) or range (min-max/step) instead.");
|
|
1955
|
+
}
|
|
1956
|
+
let o = 0;
|
|
1957
|
+
a[0] !== "*" && (o = parseInt(a[0], 10) + r);
|
|
1958
|
+
let h = parseInt(a[1], 10);
|
|
1959
|
+
this.validateNotNaN(h, "CronPattern: Syntax error, illegal stepping: (NaN)"), this.validateRange(0, this[t].length - 1, h, t, e);
|
|
1960
|
+
for (let l = o;l < this[t].length; l += h)
|
|
1961
|
+
this.setPart(t, l, i[1] || n);
|
|
1962
|
+
}
|
|
1963
|
+
replaceAlphaDays(e) {
|
|
1964
|
+
return e.replace(/-sun/gi, "-7").replace(/sun/gi, "0").replace(/mon/gi, "1").replace(/tue/gi, "2").replace(/wed/gi, "3").replace(/thu/gi, "4").replace(/fri/gi, "5").replace(/sat/gi, "6");
|
|
1965
|
+
}
|
|
1966
|
+
replaceAlphaDaysQuartz(e) {
|
|
1967
|
+
return e.replace(/sun/gi, "1").replace(/mon/gi, "2").replace(/tue/gi, "3").replace(/wed/gi, "4").replace(/thu/gi, "5").replace(/fri/gi, "6").replace(/sat/gi, "7");
|
|
1968
|
+
}
|
|
1969
|
+
replaceAlphaMonths(e) {
|
|
1970
|
+
return e.replace(/jan/gi, "1").replace(/feb/gi, "2").replace(/mar/gi, "3").replace(/apr/gi, "4").replace(/may/gi, "5").replace(/jun/gi, "6").replace(/jul/gi, "7").replace(/aug/gi, "8").replace(/sep/gi, "9").replace(/oct/gi, "10").replace(/nov/gi, "11").replace(/dec/gi, "12");
|
|
1971
|
+
}
|
|
1972
|
+
handleNicknames(e) {
|
|
1973
|
+
let t = e.trim().toLowerCase();
|
|
1974
|
+
if (t === "@yearly" || t === "@annually")
|
|
1975
|
+
return "0 0 1 1 *";
|
|
1976
|
+
if (t === "@monthly")
|
|
1977
|
+
return "0 0 1 * *";
|
|
1978
|
+
if (t === "@weekly")
|
|
1979
|
+
return "0 0 * * 0";
|
|
1980
|
+
if (t === "@daily" || t === "@midnight")
|
|
1981
|
+
return "0 0 * * *";
|
|
1982
|
+
if (t === "@hourly")
|
|
1983
|
+
return "0 * * * *";
|
|
1984
|
+
if (t === "@reboot")
|
|
1985
|
+
throw new TypeError("CronPattern: @reboot is not supported in this environment. This is an event-based trigger that requires system startup detection.");
|
|
1986
|
+
return e;
|
|
1987
|
+
}
|
|
1988
|
+
setNthWeekdayOfMonth(e, t) {
|
|
1989
|
+
if (typeof t != "number" && t.toUpperCase() === "L")
|
|
1990
|
+
this.dayOfWeek[e] = this.dayOfWeek[e] | 32;
|
|
1991
|
+
else if (t === 63)
|
|
1992
|
+
this.dayOfWeek[e] = 63;
|
|
1993
|
+
else if (t < 6 && t > 0)
|
|
1994
|
+
this.dayOfWeek[e] = this.dayOfWeek[e] | O[t - 1];
|
|
1995
|
+
else
|
|
1996
|
+
throw new TypeError(`CronPattern: nth weekday out of range, should be 1-5 or L. Value: ${t}, Type: ${typeof t}`);
|
|
1997
|
+
}
|
|
1998
|
+
}, P, f, m = class s {
|
|
1999
|
+
tz;
|
|
2000
|
+
ms;
|
|
2001
|
+
second;
|
|
2002
|
+
minute;
|
|
2003
|
+
hour;
|
|
2004
|
+
day;
|
|
2005
|
+
month;
|
|
2006
|
+
year;
|
|
2007
|
+
constructor(e, t) {
|
|
2008
|
+
if (this.tz = t, e && e instanceof Date)
|
|
2009
|
+
if (!isNaN(e))
|
|
2010
|
+
this.fromDate(e);
|
|
2011
|
+
else
|
|
2012
|
+
throw new TypeError("CronDate: Invalid date passed to CronDate constructor");
|
|
2013
|
+
else if (e == null)
|
|
2014
|
+
this.fromDate(new Date);
|
|
2015
|
+
else if (e && typeof e == "string")
|
|
2016
|
+
this.fromString(e);
|
|
2017
|
+
else if (e instanceof s)
|
|
2018
|
+
this.fromCronDate(e);
|
|
2019
|
+
else
|
|
2020
|
+
throw new TypeError("CronDate: Invalid type (" + typeof e + ") passed to CronDate constructor");
|
|
2021
|
+
}
|
|
2022
|
+
getLastDayOfMonth(e, t) {
|
|
2023
|
+
return t !== 1 ? P[t] : new Date(Date.UTC(e, t + 1, 0)).getUTCDate();
|
|
2024
|
+
}
|
|
2025
|
+
getLastWeekday(e, t) {
|
|
2026
|
+
let r = this.getLastDayOfMonth(e, t), i = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
2027
|
+
return i === 0 ? r - 2 : i === 6 ? r - 1 : r;
|
|
2028
|
+
}
|
|
2029
|
+
getNearestWeekday(e, t, r) {
|
|
2030
|
+
let n = this.getLastDayOfMonth(e, t);
|
|
2031
|
+
if (r > n)
|
|
2032
|
+
return -1;
|
|
2033
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay();
|
|
2034
|
+
return a === 0 ? r === n ? r - 2 : r + 1 : a === 6 ? r === 1 ? r + 2 : r - 1 : r;
|
|
2035
|
+
}
|
|
2036
|
+
isNthWeekdayOfMonth(e, t, r, n) {
|
|
2037
|
+
let a = new Date(Date.UTC(e, t, r)).getUTCDay(), o = 0;
|
|
2038
|
+
for (let h = 1;h <= r; h++)
|
|
2039
|
+
new Date(Date.UTC(e, t, h)).getUTCDay() === a && o++;
|
|
2040
|
+
if (n & 63 && O[o - 1] & n)
|
|
2041
|
+
return true;
|
|
2042
|
+
if (n & 32) {
|
|
2043
|
+
let h = this.getLastDayOfMonth(e, t);
|
|
2044
|
+
for (let l = r + 1;l <= h; l++)
|
|
2045
|
+
if (new Date(Date.UTC(e, t, l)).getUTCDay() === a)
|
|
2046
|
+
return false;
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
return false;
|
|
2050
|
+
}
|
|
2051
|
+
fromDate(e) {
|
|
2052
|
+
if (this.tz !== undefined)
|
|
2053
|
+
if (typeof this.tz == "number")
|
|
2054
|
+
this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes() + this.tz, this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), this.apply();
|
|
2055
|
+
else
|
|
2056
|
+
try {
|
|
2057
|
+
let t = g(e, this.tz);
|
|
2058
|
+
this.ms = e.getMilliseconds(), this.second = t.s, this.minute = t.i, this.hour = t.h, this.day = t.d, this.month = t.m - 1, this.year = t.y;
|
|
2059
|
+
} catch (t) {
|
|
2060
|
+
let r = t instanceof Error ? t.message : String(t);
|
|
2061
|
+
throw new TypeError(`CronDate: Failed to convert date to timezone '${this.tz}'. This may happen with invalid timezone names or dates. Original error: ${r}`);
|
|
2062
|
+
}
|
|
2063
|
+
else
|
|
2064
|
+
this.ms = e.getMilliseconds(), this.second = e.getSeconds(), this.minute = e.getMinutes(), this.hour = e.getHours(), this.day = e.getDate(), this.month = e.getMonth(), this.year = e.getFullYear();
|
|
2065
|
+
}
|
|
2066
|
+
fromCronDate(e) {
|
|
2067
|
+
this.tz = e.tz, this.year = e.year, this.month = e.month, this.day = e.day, this.hour = e.hour, this.minute = e.minute, this.second = e.second, this.ms = e.ms;
|
|
2068
|
+
}
|
|
2069
|
+
apply() {
|
|
2070
|
+
if (this.month > 11 || this.month < 0 || this.day > P[this.month] || this.day < 1 || this.hour > 59 || this.minute > 59 || this.second > 59 || this.hour < 0 || this.minute < 0 || this.second < 0) {
|
|
2071
|
+
let e = new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms));
|
|
2072
|
+
return this.ms = e.getUTCMilliseconds(), this.second = e.getUTCSeconds(), this.minute = e.getUTCMinutes(), this.hour = e.getUTCHours(), this.day = e.getUTCDate(), this.month = e.getUTCMonth(), this.year = e.getUTCFullYear(), true;
|
|
2073
|
+
} else
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
fromString(e) {
|
|
2077
|
+
if (typeof this.tz == "number") {
|
|
2078
|
+
let t = v(e);
|
|
2079
|
+
this.ms = t.getUTCMilliseconds(), this.second = t.getUTCSeconds(), this.minute = t.getUTCMinutes(), this.hour = t.getUTCHours(), this.day = t.getUTCDate(), this.month = t.getUTCMonth(), this.year = t.getUTCFullYear(), this.apply();
|
|
2080
|
+
} else
|
|
2081
|
+
return this.fromDate(v(e, this.tz));
|
|
2082
|
+
}
|
|
2083
|
+
findNext(e, t, r, n) {
|
|
2084
|
+
return this._findMatch(e, t, r, n, 1);
|
|
2085
|
+
}
|
|
2086
|
+
_findMatch(e, t, r, n, i) {
|
|
2087
|
+
let a = this[t], o;
|
|
2088
|
+
r.lastDayOfMonth && (o = this.getLastDayOfMonth(this.year, this.month));
|
|
2089
|
+
let h = !r.starDOW && t == "day" ? new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay() : undefined, l = this[t] + n, y = i === 1 ? (u) => u < r[t].length : (u) => u >= 0;
|
|
2090
|
+
for (let u = l;y(u); u += i) {
|
|
2091
|
+
let d = r[t][u];
|
|
2092
|
+
if (t === "day" && !d) {
|
|
2093
|
+
for (let c = 0;c < r.nearestWeekdays.length; c++)
|
|
2094
|
+
if (r.nearestWeekdays[c]) {
|
|
2095
|
+
let M = this.getNearestWeekday(this.year, this.month, c - n);
|
|
2096
|
+
if (M === -1)
|
|
2097
|
+
continue;
|
|
2098
|
+
if (M === u - n) {
|
|
2099
|
+
d = 1;
|
|
2100
|
+
break;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (t === "day" && r.lastWeekday) {
|
|
2105
|
+
let c = this.getLastWeekday(this.year, this.month);
|
|
2106
|
+
u - n === c && (d = 1);
|
|
2107
|
+
}
|
|
2108
|
+
if (t === "day" && r.lastDayOfMonth && u - n == o && (d = 1), t === "day" && !r.starDOW) {
|
|
2109
|
+
let c = r.dayOfWeek[(h + (u - n - 1)) % 7];
|
|
2110
|
+
if (c && c & 63)
|
|
2111
|
+
c = this.isNthWeekdayOfMonth(this.year, this.month, u - n, c) ? 1 : 0;
|
|
2112
|
+
else if (c)
|
|
2113
|
+
throw new Error(`CronDate: Invalid value for dayOfWeek encountered. ${c}`);
|
|
2114
|
+
r.useAndLogic ? d = d && c : !e.domAndDow && !r.starDOM ? d = d || c : d = d && c;
|
|
2115
|
+
}
|
|
2116
|
+
if (d)
|
|
2117
|
+
return this[t] = u - n, a !== this[t] ? 2 : 1;
|
|
2118
|
+
}
|
|
2119
|
+
return 3;
|
|
2120
|
+
}
|
|
2121
|
+
recurse(e, t, r) {
|
|
2122
|
+
if (r === 0 && !e.starYear) {
|
|
2123
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
2124
|
+
let i = -1;
|
|
2125
|
+
for (let a = this.year + 1;a < e.year.length && a < 1e4; a++)
|
|
2126
|
+
if (e.year[a] === 1) {
|
|
2127
|
+
i = a;
|
|
2128
|
+
break;
|
|
2129
|
+
}
|
|
2130
|
+
if (i === -1)
|
|
2131
|
+
return null;
|
|
2132
|
+
this.year = i, this.month = 0, this.day = 1, this.hour = 0, this.minute = 0, this.second = 0, this.ms = 0;
|
|
2133
|
+
}
|
|
2134
|
+
if (this.year >= 1e4)
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
let n = this.findNext(t, f[r][0], e, f[r][2]);
|
|
2138
|
+
if (n > 1) {
|
|
2139
|
+
let i = r + 1;
|
|
2140
|
+
for (;i < f.length; )
|
|
2141
|
+
this[f[i][0]] = -f[i][2], i++;
|
|
2142
|
+
if (n === 3) {
|
|
2143
|
+
if (this[f[r][1]]++, this[f[r][0]] = -f[r][2], this.apply(), r === 0 && !e.starYear) {
|
|
2144
|
+
for (;this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0 && this.year < 1e4; )
|
|
2145
|
+
this.year++;
|
|
2146
|
+
if (this.year >= 1e4 || this.year >= e.year.length)
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
return this.recurse(e, t, 0);
|
|
2150
|
+
} else if (this.apply())
|
|
2151
|
+
return this.recurse(e, t, r - 1);
|
|
2152
|
+
}
|
|
2153
|
+
return r += 1, r >= f.length ? this : (e.starYear ? this.year >= 3000 : this.year >= 1e4) ? null : this.recurse(e, t, r);
|
|
2154
|
+
}
|
|
2155
|
+
increment(e, t, r) {
|
|
2156
|
+
return this.second += t.interval !== undefined && t.interval > 1 && r ? t.interval : 1, this.ms = 0, this.apply(), this.recurse(e, t, 0);
|
|
2157
|
+
}
|
|
2158
|
+
decrement(e, t) {
|
|
2159
|
+
return this.second -= t.interval !== undefined && t.interval > 1 ? t.interval : 1, this.ms = 0, this.apply(), this.recurseBackward(e, t, 0, 0);
|
|
2160
|
+
}
|
|
2161
|
+
recurseBackward(e, t, r, n = 0) {
|
|
2162
|
+
if (n > 1e4)
|
|
2163
|
+
return null;
|
|
2164
|
+
if (r === 0 && !e.starYear) {
|
|
2165
|
+
if (this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0) {
|
|
2166
|
+
let a = -1;
|
|
2167
|
+
for (let o = this.year - 1;o >= 0; o--)
|
|
2168
|
+
if (e.year[o] === 1) {
|
|
2169
|
+
a = o;
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2172
|
+
if (a === -1)
|
|
2173
|
+
return null;
|
|
2174
|
+
this.year = a, this.month = 11, this.day = 31, this.hour = 23, this.minute = 59, this.second = 59, this.ms = 0;
|
|
2175
|
+
}
|
|
2176
|
+
if (this.year < 0)
|
|
2177
|
+
return null;
|
|
2178
|
+
}
|
|
2179
|
+
let i = this.findPrevious(t, f[r][0], e, f[r][2]);
|
|
2180
|
+
if (i > 1) {
|
|
2181
|
+
let a = r + 1;
|
|
2182
|
+
for (;a < f.length; ) {
|
|
2183
|
+
let o = f[a][0], h = f[a][2], l = this.getMaxPatternValue(o, e, h);
|
|
2184
|
+
this[o] = l, a++;
|
|
2185
|
+
}
|
|
2186
|
+
if (i === 3) {
|
|
2187
|
+
if (this[f[r][1]]--, r === 0) {
|
|
2188
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
2189
|
+
this.day > y && (this.day = y);
|
|
2190
|
+
}
|
|
2191
|
+
if (r === 1)
|
|
2192
|
+
if (this.day <= 0)
|
|
2193
|
+
this.day = 1;
|
|
2194
|
+
else {
|
|
2195
|
+
let y = this.year, u = this.month;
|
|
2196
|
+
for (;u < 0; )
|
|
2197
|
+
u += 12, y--;
|
|
2198
|
+
for (;u > 11; )
|
|
2199
|
+
u -= 12, y++;
|
|
2200
|
+
let d = u !== 1 ? P[u] : new Date(Date.UTC(y, u + 1, 0)).getUTCDate();
|
|
2201
|
+
this.day > d && (this.day = d);
|
|
2202
|
+
}
|
|
2203
|
+
this.apply();
|
|
2204
|
+
let o = f[r][0], h = f[r][2], l = this.getMaxPatternValue(o, e, h);
|
|
2205
|
+
if (o === "day") {
|
|
2206
|
+
let y = this.getLastDayOfMonth(this.year, this.month);
|
|
2207
|
+
this[o] = Math.min(l, y);
|
|
2208
|
+
} else
|
|
2209
|
+
this[o] = l;
|
|
2210
|
+
if (this.apply(), r === 0) {
|
|
2211
|
+
let y = f[1][2], u = this.getMaxPatternValue("day", e, y), d = this.getLastDayOfMonth(this.year, this.month), c = Math.min(u, d);
|
|
2212
|
+
c !== this.day && (this.day = c, this.hour = this.getMaxPatternValue("hour", e, f[2][2]), this.minute = this.getMaxPatternValue("minute", e, f[3][2]), this.second = this.getMaxPatternValue("second", e, f[4][2]));
|
|
2213
|
+
}
|
|
2214
|
+
if (r === 0 && !e.starYear) {
|
|
2215
|
+
for (;this.year >= 0 && this.year < e.year.length && e.year[this.year] === 0; )
|
|
2216
|
+
this.year--;
|
|
2217
|
+
if (this.year < 0)
|
|
2218
|
+
return null;
|
|
2219
|
+
}
|
|
2220
|
+
return this.recurseBackward(e, t, 0, n + 1);
|
|
2221
|
+
} else if (this.apply())
|
|
2222
|
+
return this.recurseBackward(e, t, r - 1, n + 1);
|
|
2223
|
+
}
|
|
2224
|
+
return r += 1, r >= f.length ? this : this.year < 0 ? null : this.recurseBackward(e, t, r, n + 1);
|
|
2225
|
+
}
|
|
2226
|
+
getMaxPatternValue(e, t, r) {
|
|
2227
|
+
if (e === "day" && t.lastDayOfMonth)
|
|
2228
|
+
return this.getLastDayOfMonth(this.year, this.month);
|
|
2229
|
+
if (e === "day" && !t.starDOW)
|
|
2230
|
+
return this.getLastDayOfMonth(this.year, this.month);
|
|
2231
|
+
for (let n = t[e].length - 1;n >= 0; n--)
|
|
2232
|
+
if (t[e][n])
|
|
2233
|
+
return n - r;
|
|
2234
|
+
return t[e].length - 1 - r;
|
|
2235
|
+
}
|
|
2236
|
+
findPrevious(e, t, r, n) {
|
|
2237
|
+
return this._findMatch(e, t, r, n, -1);
|
|
2238
|
+
}
|
|
2239
|
+
getDate(e) {
|
|
2240
|
+
return e || this.tz === undefined ? new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.ms) : typeof this.tz == "number" ? new Date(Date.UTC(this.year, this.month, this.day, this.hour, this.minute - this.tz, this.second, this.ms)) : k(b(this.year, this.month + 1, this.day, this.hour, this.minute, this.second, this.tz), false);
|
|
2241
|
+
}
|
|
2242
|
+
getTime() {
|
|
2243
|
+
return this.getDate(false).getTime();
|
|
2244
|
+
}
|
|
2245
|
+
match(e, t) {
|
|
2246
|
+
if (!e.starYear && (this.year < 0 || this.year >= e.year.length || e.year[this.year] === 0))
|
|
2247
|
+
return false;
|
|
2248
|
+
for (let r = 0;r < f.length; r++) {
|
|
2249
|
+
let n = f[r][0], i = f[r][2], a = this[n];
|
|
2250
|
+
if (a + i < 0 || a + i >= e[n].length)
|
|
2251
|
+
return false;
|
|
2252
|
+
let o = e[n][a + i];
|
|
2253
|
+
if (n === "day") {
|
|
2254
|
+
if (!o) {
|
|
2255
|
+
for (let h = 0;h < e.nearestWeekdays.length; h++)
|
|
2256
|
+
if (e.nearestWeekdays[h]) {
|
|
2257
|
+
let l = this.getNearestWeekday(this.year, this.month, h - i);
|
|
2258
|
+
if (l !== -1 && l === a) {
|
|
2259
|
+
o = 1;
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (e.lastWeekday) {
|
|
2265
|
+
let h = this.getLastWeekday(this.year, this.month);
|
|
2266
|
+
a === h && (o = 1);
|
|
2267
|
+
}
|
|
2268
|
+
if (e.lastDayOfMonth) {
|
|
2269
|
+
let h = this.getLastDayOfMonth(this.year, this.month);
|
|
2270
|
+
a === h && (o = 1);
|
|
2271
|
+
}
|
|
2272
|
+
if (!e.starDOW) {
|
|
2273
|
+
let h = new Date(Date.UTC(this.year, this.month, 1, 0, 0, 0, 0)).getUTCDay(), l = e.dayOfWeek[(h + (a - 1)) % 7];
|
|
2274
|
+
l && l & 63 && (l = this.isNthWeekdayOfMonth(this.year, this.month, a, l) ? 1 : 0), e.useAndLogic ? o = o && l : !t.domAndDow && !e.starDOM ? o = o || l : o = o && l;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (!o)
|
|
2278
|
+
return false;
|
|
2279
|
+
}
|
|
2280
|
+
return true;
|
|
2281
|
+
}
|
|
2282
|
+
}, W, w, E = class {
|
|
2283
|
+
name;
|
|
2284
|
+
options;
|
|
2285
|
+
_states;
|
|
2286
|
+
fn;
|
|
2287
|
+
getTz() {
|
|
2288
|
+
return this.options.timezone || this.options.utcOffset;
|
|
2289
|
+
}
|
|
2290
|
+
applyDayOffset(e) {
|
|
2291
|
+
if (this.options.dayOffset !== undefined && this.options.dayOffset !== 0) {
|
|
2292
|
+
let t = this.options.dayOffset * 24 * 60 * 60 * 1000;
|
|
2293
|
+
return new Date(e.getTime() + t);
|
|
2294
|
+
}
|
|
2295
|
+
return e;
|
|
2296
|
+
}
|
|
2297
|
+
constructor(e, t, r) {
|
|
2298
|
+
let n, i;
|
|
2299
|
+
if (p(t))
|
|
2300
|
+
i = t;
|
|
2301
|
+
else if (typeof t == "object")
|
|
2302
|
+
n = t;
|
|
2303
|
+
else if (t !== undefined)
|
|
2304
|
+
throw new Error("Cron: Invalid argument passed for optionsIn. Should be one of function, or object (options).");
|
|
2305
|
+
if (p(r))
|
|
2306
|
+
i = r;
|
|
2307
|
+
else if (typeof r == "object")
|
|
2308
|
+
n = r;
|
|
2309
|
+
else if (r !== undefined)
|
|
2310
|
+
throw new Error("Cron: Invalid argument passed for funcIn. Should be one of function, or object (options).");
|
|
2311
|
+
if (this.name = n?.name, this.options = R(n), this._states = { kill: false, blocking: false, previousRun: undefined, currentRun: undefined, once: undefined, currentTimeout: undefined, maxRuns: n ? n.maxRuns : undefined, paused: n ? n.paused : false, pattern: new C("* * * * *", undefined, { mode: "auto" }) }, e && (e instanceof Date || typeof e == "string" && e.indexOf(":") > 0) ? this._states.once = new m(e, this.getTz()) : this._states.pattern = new C(e, this.options.timezone, { mode: this.options.mode, alternativeWeekdays: this.options.alternativeWeekdays, sloppyRanges: this.options.sloppyRanges }), this.name) {
|
|
2312
|
+
if (w.find((o) => o.name === this.name))
|
|
2313
|
+
throw new Error("Cron: Tried to initialize new named job '" + this.name + "', but name already taken.");
|
|
2314
|
+
w.push(this);
|
|
2315
|
+
}
|
|
2316
|
+
return i !== undefined && _(i) && (this.fn = i, this.schedule()), this;
|
|
2317
|
+
}
|
|
2318
|
+
nextRun(e) {
|
|
2319
|
+
let t = this._next(e);
|
|
2320
|
+
return t ? this.applyDayOffset(t.getDate(false)) : null;
|
|
2321
|
+
}
|
|
2322
|
+
nextRuns(e, t) {
|
|
2323
|
+
this._states.maxRuns !== undefined && e > this._states.maxRuns && (e = this._states.maxRuns);
|
|
2324
|
+
let r = t || this._states.currentRun || undefined;
|
|
2325
|
+
return this._enumerateRuns(e, r, "next");
|
|
2326
|
+
}
|
|
2327
|
+
previousRuns(e, t) {
|
|
2328
|
+
return this._enumerateRuns(e, t || undefined, "previous");
|
|
2329
|
+
}
|
|
2330
|
+
_enumerateRuns(e, t, r) {
|
|
2331
|
+
let n = [], i = t ? new m(t, this.getTz()) : null, a = r === "next" ? this._next : this._previous;
|
|
2332
|
+
for (;e--; ) {
|
|
2333
|
+
let o = a.call(this, i);
|
|
2334
|
+
if (!o)
|
|
2335
|
+
break;
|
|
2336
|
+
let h = o.getDate(false);
|
|
2337
|
+
n.push(this.applyDayOffset(h)), i = o;
|
|
2338
|
+
}
|
|
2339
|
+
return n;
|
|
2340
|
+
}
|
|
2341
|
+
match(e) {
|
|
2342
|
+
if (this._states.once) {
|
|
2343
|
+
let r = new m(e, this.getTz());
|
|
2344
|
+
r.ms = 0;
|
|
2345
|
+
let n = new m(this._states.once, this.getTz());
|
|
2346
|
+
return n.ms = 0, r.getTime() === n.getTime();
|
|
2347
|
+
}
|
|
2348
|
+
let t = new m(e, this.getTz());
|
|
2349
|
+
return t.ms = 0, t.match(this._states.pattern, this.options);
|
|
2350
|
+
}
|
|
2351
|
+
getPattern() {
|
|
2352
|
+
if (!this._states.once)
|
|
2353
|
+
return this._states.pattern ? this._states.pattern.pattern : undefined;
|
|
2354
|
+
}
|
|
2355
|
+
getOnce() {
|
|
2356
|
+
return this._states.once ? this._states.once.getDate() : null;
|
|
2357
|
+
}
|
|
2358
|
+
isRunning() {
|
|
2359
|
+
let e = this.nextRun(this._states.currentRun), t = !this._states.paused, r = this.fn !== undefined, n = !this._states.kill;
|
|
2360
|
+
return t && r && n && e !== null;
|
|
2361
|
+
}
|
|
2362
|
+
isStopped() {
|
|
2363
|
+
return this._states.kill;
|
|
2364
|
+
}
|
|
2365
|
+
isBusy() {
|
|
2366
|
+
return this._states.blocking;
|
|
2367
|
+
}
|
|
2368
|
+
currentRun() {
|
|
2369
|
+
return this._states.currentRun ? this._states.currentRun.getDate() : null;
|
|
2370
|
+
}
|
|
2371
|
+
previousRun() {
|
|
2372
|
+
return this._states.previousRun ? this._states.previousRun.getDate() : null;
|
|
2373
|
+
}
|
|
2374
|
+
msToNext(e) {
|
|
2375
|
+
let t = this._next(e);
|
|
2376
|
+
return t ? e instanceof m || e instanceof Date ? t.getTime() - e.getTime() : t.getTime() - new m(e).getTime() : null;
|
|
2377
|
+
}
|
|
2378
|
+
stop() {
|
|
2379
|
+
this._states.kill = true, this._states.currentTimeout && clearTimeout(this._states.currentTimeout);
|
|
2380
|
+
let e = w.indexOf(this);
|
|
2381
|
+
e >= 0 && w.splice(e, 1);
|
|
2382
|
+
}
|
|
2383
|
+
pause() {
|
|
2384
|
+
return this._states.paused = true, !this._states.kill;
|
|
2385
|
+
}
|
|
2386
|
+
resume() {
|
|
2387
|
+
return this._states.paused = false, !this._states.kill;
|
|
2388
|
+
}
|
|
2389
|
+
schedule(e) {
|
|
2390
|
+
if (e && this.fn)
|
|
2391
|
+
throw new Error("Cron: It is not allowed to schedule two functions using the same Croner instance.");
|
|
2392
|
+
e && (this.fn = e);
|
|
2393
|
+
let t = this.msToNext(), r = this.nextRun(this._states.currentRun);
|
|
2394
|
+
return t == null || isNaN(t) || r === null ? this : (t > W && (t = W), this._states.currentTimeout = setTimeout(() => this._checkTrigger(r), t), this._states.currentTimeout && this.options.unref && x(this._states.currentTimeout), this);
|
|
2395
|
+
}
|
|
2396
|
+
async _trigger(e) {
|
|
2397
|
+
this._states.blocking = true, this._states.currentRun = new m(undefined, this.getTz());
|
|
2398
|
+
try {
|
|
2399
|
+
if (this.options.catch)
|
|
2400
|
+
try {
|
|
2401
|
+
this.fn !== undefined && await this.fn(this, this.options.context);
|
|
2402
|
+
} catch (t) {
|
|
2403
|
+
if (p(this.options.catch))
|
|
2404
|
+
try {
|
|
2405
|
+
this.options.catch(t, this);
|
|
2406
|
+
} catch {}
|
|
2407
|
+
}
|
|
2408
|
+
else
|
|
2409
|
+
this.fn !== undefined && await this.fn(this, this.options.context);
|
|
2410
|
+
} finally {
|
|
2411
|
+
this._states.previousRun = new m(e, this.getTz()), this._states.blocking = false;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
async trigger() {
|
|
2415
|
+
await this._trigger();
|
|
2416
|
+
}
|
|
2417
|
+
runsLeft() {
|
|
2418
|
+
return this._states.maxRuns;
|
|
2419
|
+
}
|
|
2420
|
+
_checkTrigger(e) {
|
|
2421
|
+
let t = new Date, r = !this._states.paused && t.getTime() >= e.getTime(), n = this._states.blocking && this.options.protect;
|
|
2422
|
+
r && !n ? (this._states.maxRuns !== undefined && this._states.maxRuns--, this._trigger()) : r && n && p(this.options.protect) && setTimeout(() => this.options.protect(this), 0), this.schedule();
|
|
2423
|
+
}
|
|
2424
|
+
_next(e) {
|
|
2425
|
+
let t = !!(e || this._states.currentRun), r = false;
|
|
2426
|
+
!e && this.options.startAt && this.options.interval && ([e, t] = this._calculatePreviousRun(e, t), r = !e), e = new m(e, this.getTz()), this.options.startAt && e && e.getTime() < this.options.startAt.getTime() && (e = this.options.startAt);
|
|
2427
|
+
let n = this._states.once || new m(e, this.getTz());
|
|
2428
|
+
return !r && n !== this._states.once && (n = n.increment(this._states.pattern, this.options, t)), this._states.once && this._states.once.getTime() <= e.getTime() || n === null || this._states.maxRuns !== undefined && this._states.maxRuns <= 0 || this._states.kill || this.options.stopAt && n.getTime() >= this.options.stopAt.getTime() ? null : n;
|
|
2429
|
+
}
|
|
2430
|
+
_previous(e) {
|
|
2431
|
+
let t = new m(e, this.getTz());
|
|
2432
|
+
this.options.stopAt && t.getTime() > this.options.stopAt.getTime() && (t = this.options.stopAt);
|
|
2433
|
+
let r = new m(t, this.getTz());
|
|
2434
|
+
return this._states.once ? this._states.once.getTime() < t.getTime() ? this._states.once : null : (r = r.decrement(this._states.pattern, this.options), r === null || this.options.startAt && r.getTime() < this.options.startAt.getTime() ? null : r);
|
|
2435
|
+
}
|
|
2436
|
+
_calculatePreviousRun(e, t) {
|
|
2437
|
+
let r = new m(undefined, this.getTz()), n = e;
|
|
2438
|
+
if (this.options.startAt.getTime() <= r.getTime()) {
|
|
2439
|
+
n = this.options.startAt;
|
|
2440
|
+
let i = n.getTime() + this.options.interval * 1000;
|
|
2441
|
+
for (;i <= r.getTime(); )
|
|
2442
|
+
n = new m(n, this.getTz()).increment(this._states.pattern, this.options, true), i = n.getTime() + this.options.interval * 1000;
|
|
2443
|
+
t = true;
|
|
2444
|
+
}
|
|
2445
|
+
return n === null && (n = undefined), [n, t];
|
|
2446
|
+
}
|
|
2447
|
+
};
|
|
2448
|
+
var init_croner = __esm(() => {
|
|
2449
|
+
O = [1, 2, 4, 8, 16];
|
|
2450
|
+
P = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
2451
|
+
f = [["month", "year", 0], ["day", "month", -1], ["hour", "day", 0], ["minute", "hour", 0], ["second", "minute", 0]];
|
|
2452
|
+
W = 30 * 1000;
|
|
2453
|
+
w = [];
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
// ../core/src/tools/cron/index.ts
|
|
2457
|
+
function setSchedulerInstance2(scheduler) {
|
|
2458
|
+
_scheduler2 = scheduler;
|
|
2459
|
+
}
|
|
2460
|
+
function getUserTimezone2() {
|
|
2461
|
+
const db = getDb();
|
|
2462
|
+
const user = db.query("SELECT timezone FROM users LIMIT 1").get();
|
|
2463
|
+
return user?.timezone || "UTC";
|
|
2464
|
+
}
|
|
2465
|
+
function resolveBestChannel(userId, explicitChannel) {
|
|
2466
|
+
const db = getDb();
|
|
2467
|
+
const user = db.query("SELECT preferred_cron_channel FROM users WHERE id = ?").get(userId);
|
|
2468
|
+
const activeChannels = db.query(`
|
|
2469
|
+
SELECT ui.channel FROM user_identities ui
|
|
2470
|
+
JOIN channels c ON c.id = ui.channel
|
|
2471
|
+
WHERE ui.user_id = ? AND c.active = 1 AND c.status = 'connected'
|
|
2472
|
+
`).all(userId);
|
|
2473
|
+
const identities = activeChannels.length > 0 ? activeChannels : db.query("SELECT channel FROM user_identities WHERE user_id = ?").all(userId);
|
|
2474
|
+
if (identities.length === 0) {
|
|
2475
|
+
return "webchat";
|
|
2476
|
+
}
|
|
2477
|
+
let bestChannel = "";
|
|
2478
|
+
if (explicitChannel && explicitChannel !== "system") {
|
|
2479
|
+
if (identities.some((i) => i.channel === explicitChannel)) {
|
|
2480
|
+
bestChannel = explicitChannel;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
if (!bestChannel && user?.preferred_cron_channel && user.preferred_cron_channel !== "auto") {
|
|
2484
|
+
if (identities.some((i) => i.channel === user.preferred_cron_channel)) {
|
|
2485
|
+
bestChannel = user.preferred_cron_channel;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
if (!bestChannel) {
|
|
2489
|
+
const preferred = ["telegram", "discord", "slack", "whatsapp", "webchat"];
|
|
2490
|
+
for (const p2 of preferred) {
|
|
2491
|
+
if (identities.some((i) => i.channel === p2)) {
|
|
2492
|
+
bestChannel = p2;
|
|
2493
|
+
break;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (!bestChannel) {
|
|
2498
|
+
bestChannel = identities[0].channel;
|
|
2499
|
+
}
|
|
2500
|
+
return bestChannel;
|
|
2501
|
+
}
|
|
2502
|
+
function createTools2() {
|
|
2503
|
+
return [
|
|
2504
|
+
cronCreateTool,
|
|
2505
|
+
cronListTool,
|
|
2506
|
+
cronPauseTool,
|
|
2507
|
+
cronResumeTool,
|
|
2508
|
+
cronDeleteTool,
|
|
2509
|
+
cronTriggerTool,
|
|
2510
|
+
cronHistoryTool
|
|
2511
|
+
];
|
|
2512
|
+
}
|
|
2513
|
+
var log2, _scheduler2 = null, cronCreateTool, cronListTool, cronPauseTool, cronResumeTool, cronDeleteTool, cronTriggerTool, cronHistoryTool;
|
|
2514
|
+
var init_cron = __esm(() => {
|
|
2515
|
+
init_sqlite();
|
|
2516
|
+
init_logger();
|
|
2517
|
+
init_croner();
|
|
2518
|
+
log2 = logger.child("CronTools");
|
|
2519
|
+
cronCreateTool = {
|
|
2520
|
+
name: "cron.create",
|
|
2521
|
+
description: "Create a new scheduled task. Use for recurring reminders, daily reports, automated checks. Spanish: crear tarea programada, agendar recordatorio, programar reporte",
|
|
2522
|
+
parameters: {
|
|
2523
|
+
type: "object",
|
|
2524
|
+
properties: {
|
|
2525
|
+
name: { type: "string", description: "Name for the scheduled task (e.g., 'daily-report', 'morning-reminder')" },
|
|
2526
|
+
task_type: { type: "string", enum: ["recurring", "one_shot"], description: "Type: 'recurring' for cron-based, 'one_shot' for single execution" },
|
|
2527
|
+
cron_expression: { type: "string", description: "Cron expression (5-7 fields) for recurring tasks. Example: '0 9 * * *' (daily at 9 AM)" },
|
|
2528
|
+
fire_at: { type: "string", description: "ISO 8601 datetime for one_shot tasks. Example: '2026-04-01T09:00:00'" },
|
|
2529
|
+
payload: { type: "object", description: "Payload with 'prompt' or 'message' field containing the task content" },
|
|
2530
|
+
agent_id: { type: "string", description: "Target agent ID (optional, defaults to Coordinator)" },
|
|
2531
|
+
tool_name: { type: "string", description: "Specific tool to execute (optional)" },
|
|
2532
|
+
max_runs: { type: "number", description: "Maximum executions (optional, null = unlimited)" },
|
|
2533
|
+
channel: { type: "string", description: "Notification channel (system, telegram, discord, whatsapp, cli)" }
|
|
2534
|
+
},
|
|
2535
|
+
required: ["name", "task_type"]
|
|
2536
|
+
},
|
|
2537
|
+
execute: async (params) => {
|
|
2538
|
+
const db = getDb();
|
|
2539
|
+
const timezone = getUserTimezone2();
|
|
2540
|
+
const name = params.name;
|
|
2541
|
+
const task_type = params.task_type;
|
|
2542
|
+
const cron_expression = params.cron_expression;
|
|
2543
|
+
const fire_at = params.fire_at;
|
|
2544
|
+
const payload = params.payload;
|
|
2545
|
+
const agent_id = params.agent_id;
|
|
2546
|
+
const tool_name = params.tool_name;
|
|
2547
|
+
const max_runs = params.max_runs;
|
|
2548
|
+
const channel = params.channel || "system";
|
|
2549
|
+
if (!name) {
|
|
2550
|
+
return { ok: false, error: "Missing required field: name" };
|
|
2551
|
+
}
|
|
2552
|
+
if (!task_type) {
|
|
2553
|
+
return { ok: false, error: "Missing required field: task_type (recurring or one_shot)" };
|
|
2554
|
+
}
|
|
2555
|
+
if (task_type === "recurring" && !cron_expression) {
|
|
2556
|
+
return { ok: false, error: "recurring task requires cron_expression" };
|
|
2557
|
+
}
|
|
2558
|
+
if (task_type === "one_shot" && !fire_at) {
|
|
2559
|
+
return { ok: false, error: "one_shot task requires fire_at" };
|
|
2560
|
+
}
|
|
2561
|
+
if (cron_expression) {
|
|
2562
|
+
try {
|
|
2563
|
+
new E(cron_expression);
|
|
2564
|
+
} catch (err) {
|
|
2565
|
+
return { ok: false, error: `Invalid cron expression: ${err.message}` };
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
if (fire_at) {
|
|
2569
|
+
const fireAtDate = new Date(fire_at);
|
|
2570
|
+
if (fireAtDate.getTime() <= Date.now()) {
|
|
2571
|
+
return { ok: false, error: "fire_at must be in the future" };
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
if (payload && !payload._internal) {
|
|
2575
|
+
const hasPrompt = !!(payload.prompt || payload.message);
|
|
2576
|
+
if (!hasPrompt) {
|
|
2577
|
+
return { ok: false, error: "Payload must contain 'prompt' or 'message' field" };
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
if (agent_id) {
|
|
2581
|
+
const agent = db.query("SELECT id FROM agents WHERE id = ?").get(agent_id);
|
|
2582
|
+
if (!agent) {
|
|
2583
|
+
return { ok: false, error: `Agent not found: ${agent_id}` };
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
try {
|
|
2587
|
+
if (_scheduler2) {
|
|
2588
|
+
const result = _scheduler2.create({
|
|
2589
|
+
name,
|
|
2590
|
+
task_type,
|
|
2591
|
+
cron_expression,
|
|
2592
|
+
fire_at,
|
|
2593
|
+
timezone,
|
|
2594
|
+
payload: payload || { prompt: `Execute scheduled task: ${name}` },
|
|
2595
|
+
agent_id: agent_id || null,
|
|
2596
|
+
tool_name: tool_name || null,
|
|
2597
|
+
max_runs: max_runs || null,
|
|
2598
|
+
channel
|
|
2599
|
+
});
|
|
2600
|
+
log2.info(`[create] Task "${name}" created via scheduler: ${result.id}`);
|
|
2601
|
+
return {
|
|
2602
|
+
ok: true,
|
|
2603
|
+
task_id: result.id,
|
|
2604
|
+
next_run: result.nextRun,
|
|
2605
|
+
message: `Task "${name}" scheduled. Next run: ${result.nextRun ? new Date(result.nextRun).toLocaleString() : "unknown"}`
|
|
2606
|
+
};
|
|
2607
|
+
} else {
|
|
2608
|
+
const id = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
2609
|
+
const now = new Date().toISOString();
|
|
2610
|
+
db.query(`
|
|
2611
|
+
INSERT INTO scheduled_tasks (
|
|
2612
|
+
id, name, task_type, cron_expression, fire_at, timezone, payload,
|
|
2613
|
+
agent_id, tool_name, max_runs, channel, created_at, updated_at
|
|
2614
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2615
|
+
`).run(id, name, task_type, cron_expression || null, fire_at || null, timezone, JSON.stringify(payload || {}), agent_id || null, tool_name || null, max_runs || null, channel, now, now);
|
|
2616
|
+
return {
|
|
2617
|
+
ok: true,
|
|
2618
|
+
task_id: id,
|
|
2619
|
+
message: `Task "${name}" saved (scheduler not active)`
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
log2.error(`[create] Failed: ${err.message}`);
|
|
2624
|
+
return { ok: false, error: `Failed to create task: ${err.message}` };
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
cronListTool = {
|
|
2629
|
+
name: "cron.list",
|
|
2630
|
+
description: "List all scheduled tasks with their next execution times. Spanish: ver tareas programadas, listar cronograma",
|
|
2631
|
+
parameters: {
|
|
2632
|
+
type: "object",
|
|
2633
|
+
properties: {
|
|
2634
|
+
status: { type: "string", enum: ["active", "paused", "completed", "failed", "cancelled"], description: "Filter by status" },
|
|
2635
|
+
task_type: { type: "string", enum: ["recurring", "one_shot"], description: "Filter by type" }
|
|
2636
|
+
}
|
|
2637
|
+
},
|
|
2638
|
+
execute: async (params) => {
|
|
2639
|
+
const db = getDb();
|
|
2640
|
+
const status = params.status;
|
|
2641
|
+
const task_type = params.task_type;
|
|
2642
|
+
try {
|
|
2643
|
+
let query = "SELECT * FROM scheduled_tasks WHERE 1=1";
|
|
2644
|
+
const args = [];
|
|
2645
|
+
if (status) {
|
|
2646
|
+
query += " AND status = ?";
|
|
2647
|
+
args.push(status);
|
|
2648
|
+
}
|
|
2649
|
+
if (task_type) {
|
|
2650
|
+
query += " AND task_type = ?";
|
|
2651
|
+
args.push(task_type);
|
|
2652
|
+
}
|
|
2653
|
+
query += " ORDER BY next_run_at ASC";
|
|
2654
|
+
const tasks = db.query(query).all(...args);
|
|
2655
|
+
return {
|
|
2656
|
+
ok: true,
|
|
2657
|
+
tasks: tasks.map((t) => ({
|
|
2658
|
+
id: t.id,
|
|
2659
|
+
name: t.name,
|
|
2660
|
+
type: t.task_type,
|
|
2661
|
+
status: t.status,
|
|
2662
|
+
cron_expression: t.cron_expression,
|
|
2663
|
+
fire_at: t.fire_at,
|
|
2664
|
+
next_run: t.next_run_at,
|
|
2665
|
+
last_run: t.last_run_at,
|
|
2666
|
+
run_count: t.run_count,
|
|
2667
|
+
channel: t.channel
|
|
2668
|
+
})),
|
|
2669
|
+
count: tasks.length
|
|
2670
|
+
};
|
|
2671
|
+
} catch (err) {
|
|
2672
|
+
log2.error(`[list] Failed: ${err.message}`);
|
|
2673
|
+
return { ok: false, error: `Failed to list tasks: ${err.message}` };
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
cronPauseTool = {
|
|
2678
|
+
name: "cron.pause",
|
|
2679
|
+
description: "Pause a scheduled task. Spanish: pausar tarea programada, detener temporalmente",
|
|
2680
|
+
parameters: {
|
|
2681
|
+
type: "object",
|
|
2682
|
+
properties: {
|
|
2683
|
+
task_id: { type: "string", description: "ID of the task to pause" }
|
|
2684
|
+
},
|
|
2685
|
+
required: ["task_id"]
|
|
2686
|
+
},
|
|
2687
|
+
execute: async (params) => {
|
|
2688
|
+
const task_id = params.task_id;
|
|
2689
|
+
if (!task_id) {
|
|
2690
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
2691
|
+
}
|
|
2692
|
+
try {
|
|
2693
|
+
if (_scheduler2) {
|
|
2694
|
+
const success = _scheduler2.pause(task_id);
|
|
2695
|
+
if (success) {
|
|
2696
|
+
return { ok: true, message: `Task "${task_id}" paused` };
|
|
2697
|
+
} else {
|
|
2698
|
+
return { ok: false, error: `Task "${task_id}" not found or already paused` };
|
|
2699
|
+
}
|
|
2700
|
+
} else {
|
|
2701
|
+
const db = getDb();
|
|
2702
|
+
const result = db.query("UPDATE scheduled_tasks SET status = 'paused' WHERE id = ?").run(task_id);
|
|
2703
|
+
if (result.changes > 0) {
|
|
2704
|
+
return { ok: true, message: `Task "${task_id}" paused (scheduler not active)` };
|
|
2705
|
+
} else {
|
|
2706
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
} catch (err) {
|
|
2710
|
+
log2.error(`[pause] Failed: ${err.message}`);
|
|
2711
|
+
return { ok: false, error: `Failed to pause task: ${err.message}` };
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
cronResumeTool = {
|
|
2716
|
+
name: "cron.resume",
|
|
2717
|
+
description: "Resume a paused scheduled task. Spanish: reanudar tarea programada, continuar",
|
|
2718
|
+
parameters: {
|
|
2719
|
+
type: "object",
|
|
2720
|
+
properties: {
|
|
2721
|
+
task_id: { type: "string", description: "ID of the task to resume" }
|
|
2722
|
+
},
|
|
2723
|
+
required: ["task_id"]
|
|
2724
|
+
},
|
|
2725
|
+
execute: async (params) => {
|
|
2726
|
+
const task_id = params.task_id;
|
|
2727
|
+
if (!task_id) {
|
|
2728
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
2729
|
+
}
|
|
2730
|
+
try {
|
|
2731
|
+
if (_scheduler2) {
|
|
2732
|
+
const success = _scheduler2.resume(task_id);
|
|
2733
|
+
if (success) {
|
|
2734
|
+
return { ok: true, message: `Task "${task_id}" resumed` };
|
|
2735
|
+
} else {
|
|
2736
|
+
return { ok: false, error: `Task "${task_id}" not found or already active` };
|
|
2737
|
+
}
|
|
2738
|
+
} else {
|
|
2739
|
+
const db = getDb();
|
|
2740
|
+
const result = db.query("UPDATE scheduled_tasks SET status = 'active' WHERE id = ?").run(task_id);
|
|
2741
|
+
if (result.changes > 0) {
|
|
2742
|
+
return { ok: true, message: `Task "${task_id}" resumed (scheduler not active)` };
|
|
2743
|
+
} else {
|
|
2744
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
log2.error(`[resume] Failed: ${err.message}`);
|
|
2749
|
+
return { ok: false, error: `Failed to resume task: ${err.message}` };
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2753
|
+
cronDeleteTool = {
|
|
2754
|
+
name: "cron.delete",
|
|
2755
|
+
description: "Delete a scheduled task permanently. Spanish: eliminar tarea programada, cancelar recordatorio",
|
|
2756
|
+
parameters: {
|
|
2757
|
+
type: "object",
|
|
2758
|
+
properties: {
|
|
2759
|
+
task_id: { type: "string", description: "ID of the task to delete" }
|
|
2760
|
+
},
|
|
2761
|
+
required: ["task_id"]
|
|
2762
|
+
},
|
|
2763
|
+
execute: async (params) => {
|
|
2764
|
+
const task_id = params.task_id;
|
|
2765
|
+
if (!task_id) {
|
|
2766
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
2767
|
+
}
|
|
2768
|
+
try {
|
|
2769
|
+
if (_scheduler2) {
|
|
2770
|
+
const success = _scheduler2.delete(task_id);
|
|
2771
|
+
if (success) {
|
|
2772
|
+
return { ok: true, message: `Task "${task_id}" deleted` };
|
|
2773
|
+
} else {
|
|
2774
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
2775
|
+
}
|
|
2776
|
+
} else {
|
|
2777
|
+
const db = getDb();
|
|
2778
|
+
const result = db.query("DELETE FROM scheduled_tasks WHERE id = ?").run(task_id);
|
|
2779
|
+
if (result.changes > 0) {
|
|
2780
|
+
return { ok: true, message: `Task "${task_id}" deleted (scheduler not active)` };
|
|
2781
|
+
} else {
|
|
2782
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
} catch (err) {
|
|
2786
|
+
log2.error(`[delete] Failed: ${err.message}`);
|
|
2787
|
+
return { ok: false, error: `Failed to delete task: ${err.message}` };
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
cronTriggerTool = {
|
|
2792
|
+
name: "cron.trigger",
|
|
2793
|
+
description: "Manually trigger a scheduled task execution immediately. Spanish: ejecutar tarea ahora, forzar ejecuci\xF3n",
|
|
2794
|
+
parameters: {
|
|
2795
|
+
type: "object",
|
|
2796
|
+
properties: {
|
|
2797
|
+
task_id: { type: "string", description: "ID of the task to trigger" }
|
|
2798
|
+
},
|
|
2799
|
+
required: ["task_id"]
|
|
2800
|
+
},
|
|
2801
|
+
execute: async (params) => {
|
|
2802
|
+
const task_id = params.task_id;
|
|
2803
|
+
if (!task_id) {
|
|
2804
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
2805
|
+
}
|
|
2806
|
+
try {
|
|
2807
|
+
if (_scheduler2) {
|
|
2808
|
+
const success = _scheduler2.trigger(task_id);
|
|
2809
|
+
if (success) {
|
|
2810
|
+
return { ok: true, message: `Task "${task_id}" triggered` };
|
|
2811
|
+
} else {
|
|
2812
|
+
return { ok: false, error: `Task "${task_id}" not found or not active` };
|
|
2813
|
+
}
|
|
2814
|
+
} else {
|
|
2815
|
+
return { ok: false, error: "Scheduler not active - cannot trigger tasks" };
|
|
2816
|
+
}
|
|
2817
|
+
} catch (err) {
|
|
2818
|
+
log2.error(`[trigger] Failed: ${err.message}`);
|
|
2819
|
+
return { ok: false, error: `Failed to trigger task: ${err.message}` };
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
cronHistoryTool = {
|
|
2824
|
+
name: "cron.history",
|
|
2825
|
+
description: "Get execution history for a scheduled task. Spanish: historial de ejecuciones, logs de tarea",
|
|
2826
|
+
parameters: {
|
|
2827
|
+
type: "object",
|
|
2828
|
+
properties: {
|
|
2829
|
+
task_id: { type: "string", description: "ID of the task" },
|
|
2830
|
+
limit: { type: "number", description: "Maximum number of records (default: 10)" }
|
|
2831
|
+
},
|
|
2832
|
+
required: ["task_id"]
|
|
2833
|
+
},
|
|
2834
|
+
execute: async (params) => {
|
|
2835
|
+
const task_id = params.task_id;
|
|
2836
|
+
const limit = params.limit || 10;
|
|
2837
|
+
if (!task_id) {
|
|
2838
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
2839
|
+
}
|
|
2840
|
+
try {
|
|
2841
|
+
const db = getDb();
|
|
2842
|
+
const runs = db.query(`
|
|
2843
|
+
SELECT * FROM task_runs
|
|
2844
|
+
WHERE task_id = ?
|
|
2845
|
+
ORDER BY started_at DESC
|
|
2846
|
+
LIMIT ?
|
|
2847
|
+
`).all(task_id, limit);
|
|
2848
|
+
return {
|
|
2849
|
+
ok: true,
|
|
2850
|
+
history: runs.map((r) => ({
|
|
2851
|
+
id: r.id,
|
|
2852
|
+
status: r.status,
|
|
2853
|
+
started_at: r.started_at,
|
|
2854
|
+
finished_at: r.finished_at,
|
|
2855
|
+
duration_ms: r.duration_ms,
|
|
2856
|
+
error_message: r.error_message
|
|
2857
|
+
})),
|
|
2858
|
+
count: runs.length
|
|
2859
|
+
};
|
|
2860
|
+
} catch (err) {
|
|
2861
|
+
log2.error(`[history] Failed: ${err.message}`);
|
|
2862
|
+
return { ok: false, error: `Failed to get history: ${err.message}` };
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
});
|
|
2867
|
+
|
|
2868
|
+
// ../core/src/tools/schedule.ts
|
|
2869
|
+
init_sqlite();
|
|
2870
|
+
init_logger();
|
|
2871
|
+
init_croner();
|
|
2872
|
+
var log = logger.child("ScheduleTools");
|
|
2873
|
+
var _scheduler = null;
|
|
2874
|
+
function setSchedulerInstance(scheduler) {
|
|
2875
|
+
_scheduler = scheduler;
|
|
2876
|
+
}
|
|
2877
|
+
function getUserTimezone() {
|
|
2878
|
+
const db = getDb();
|
|
2879
|
+
const user = db.query("SELECT timezone FROM users LIMIT 1").get();
|
|
2880
|
+
return user?.timezone || "UTC";
|
|
2881
|
+
}
|
|
2882
|
+
var scheduleCreateTool = {
|
|
2883
|
+
name: "hive.schedule.create",
|
|
2884
|
+
description: "Create a new scheduled task. Use for recurring reminders, daily reports, automated checks. Spanish: crear tarea programada, agendar recordatorio, programar reporte",
|
|
2885
|
+
parameters: {
|
|
2886
|
+
type: "object",
|
|
2887
|
+
properties: {
|
|
2888
|
+
name: { type: "string", description: "Name for the scheduled task (e.g., 'daily-report', 'morning-reminder')" },
|
|
2889
|
+
task_type: { type: "string", enum: ["recurring", "one_shot"], description: "Type: 'recurring' for cron-based, 'one_shot' for single execution" },
|
|
2890
|
+
cron_expression: { type: "string", description: "Cron expression (5-7 fields) for recurring tasks. Example: '0 9 * * *' (daily at 9 AM)" },
|
|
2891
|
+
fire_at: { type: "string", description: "ISO 8601 datetime for one_shot tasks. Example: '2026-04-01T09:00:00'" },
|
|
2892
|
+
payload: { type: "object", description: "Payload with 'prompt' or 'message' field containing the task content" },
|
|
2893
|
+
agent_id: { type: "string", description: "Target agent ID (optional, defaults to Coordinator)" },
|
|
2894
|
+
tool_name: { type: "string", description: "Specific tool to execute (optional)" },
|
|
2895
|
+
max_runs: { type: "number", description: "Maximum executions (optional, null = unlimited)" },
|
|
2896
|
+
channel: { type: "string", description: "Notification channel (system, telegram, discord, whatsapp, cli)" }
|
|
2897
|
+
},
|
|
2898
|
+
required: ["name", "task_type"]
|
|
2899
|
+
},
|
|
2900
|
+
execute: async (params) => {
|
|
2901
|
+
const db = getDb();
|
|
2902
|
+
const timezone = getUserTimezone();
|
|
2903
|
+
const name = params.name;
|
|
2904
|
+
const task_type = params.task_type;
|
|
2905
|
+
const cron_expression = params.cron_expression;
|
|
2906
|
+
const fire_at = params.fire_at;
|
|
2907
|
+
const payload = params.payload;
|
|
2908
|
+
const agent_id = params.agent_id;
|
|
2909
|
+
const tool_name = params.tool_name;
|
|
2910
|
+
const max_runs = params.max_runs;
|
|
2911
|
+
const channel = params.channel || "system";
|
|
2912
|
+
if (!name) {
|
|
2913
|
+
return { ok: false, error: "Missing required field: name" };
|
|
2914
|
+
}
|
|
2915
|
+
if (!task_type) {
|
|
2916
|
+
return { ok: false, error: "Missing required field: task_type (recurring or one_shot)" };
|
|
2917
|
+
}
|
|
2918
|
+
if (task_type === "recurring" && !cron_expression) {
|
|
2919
|
+
return { ok: false, error: "recurring task requires cron_expression" };
|
|
2920
|
+
}
|
|
2921
|
+
if (task_type === "one_shot" && !fire_at) {
|
|
2922
|
+
return { ok: false, error: "one_shot task requires fire_at" };
|
|
2923
|
+
}
|
|
2924
|
+
if (cron_expression) {
|
|
2925
|
+
try {
|
|
2926
|
+
new E(cron_expression);
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
return { ok: false, error: `Invalid cron expression: ${err.message}` };
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
if (fire_at) {
|
|
2932
|
+
const fireAtDate = new Date(fire_at);
|
|
2933
|
+
if (fireAtDate.getTime() <= Date.now()) {
|
|
2934
|
+
return { ok: false, error: "fire_at must be in the future" };
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
if (payload && !payload._internal) {
|
|
2938
|
+
const hasPrompt = !!(payload.prompt || payload.message);
|
|
2939
|
+
if (!hasPrompt) {
|
|
2940
|
+
return { ok: false, error: "Payload must contain 'prompt' or 'message' field" };
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
if (agent_id) {
|
|
2944
|
+
const agent = db.query("SELECT id FROM agents WHERE id = ?").get(agent_id);
|
|
2945
|
+
if (!agent) {
|
|
2946
|
+
return { ok: false, error: `Agent not found: ${agent_id}` };
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
try {
|
|
2950
|
+
if (_scheduler) {
|
|
2951
|
+
const result = _scheduler.create({
|
|
2952
|
+
name,
|
|
2953
|
+
task_type,
|
|
2954
|
+
cron_expression,
|
|
2955
|
+
fire_at,
|
|
2956
|
+
timezone,
|
|
2957
|
+
payload: payload || { prompt: `Execute scheduled task: ${name}` },
|
|
2958
|
+
agent_id: agent_id || null,
|
|
2959
|
+
tool_name: tool_name || null,
|
|
2960
|
+
max_runs: max_runs || null,
|
|
2961
|
+
channel
|
|
2962
|
+
});
|
|
2963
|
+
log.info(`[create] Task "${name}" created via scheduler: ${result.id}`);
|
|
2964
|
+
return {
|
|
2965
|
+
ok: true,
|
|
2966
|
+
task_id: result.id,
|
|
2967
|
+
next_run: result.nextRun,
|
|
2968
|
+
message: `Task "${name}" scheduled. Next run: ${result.nextRun ? new Date(result.nextRun).toLocaleString() : "unknown"}`
|
|
2969
|
+
};
|
|
2970
|
+
} else {
|
|
2971
|
+
const id = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
2972
|
+
const now = new Date().toISOString();
|
|
2973
|
+
db.query(`
|
|
2974
|
+
INSERT INTO scheduled_tasks (
|
|
2975
|
+
id, name, task_type, cron_expression, fire_at, timezone, payload,
|
|
2976
|
+
agent_id, tool_name, max_runs, channel, created_at, updated_at
|
|
2977
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2978
|
+
`).run(id, name, task_type, cron_expression || null, fire_at || null, timezone, JSON.stringify(payload || {}), agent_id || null, tool_name || null, max_runs || null, channel, now, now);
|
|
2979
|
+
return {
|
|
2980
|
+
ok: true,
|
|
2981
|
+
task_id: id,
|
|
2982
|
+
message: `Task "${name}" saved (scheduler not active)`
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
} catch (err) {
|
|
2986
|
+
log.error(`[create] Failed: ${err.message}`);
|
|
2987
|
+
return { ok: false, error: `Failed to create task: ${err.message}` };
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
var scheduleListTool = {
|
|
2992
|
+
name: "hive.schedule.list",
|
|
2993
|
+
description: "List all scheduled tasks with their next execution times. Spanish: ver tareas programadas, listar cronograma",
|
|
2994
|
+
parameters: {
|
|
2995
|
+
type: "object",
|
|
2996
|
+
properties: {
|
|
2997
|
+
status: { type: "string", enum: ["active", "paused", "completed", "failed", "cancelled"], description: "Filter by status" },
|
|
2998
|
+
task_type: { type: "string", enum: ["recurring", "one_shot"], description: "Filter by type" }
|
|
2999
|
+
}
|
|
3000
|
+
},
|
|
3001
|
+
execute: async (params) => {
|
|
3002
|
+
const db = getDb();
|
|
3003
|
+
const status = params.status;
|
|
3004
|
+
const task_type = params.task_type;
|
|
3005
|
+
try {
|
|
3006
|
+
let query = "SELECT * FROM scheduled_tasks WHERE 1=1";
|
|
3007
|
+
const args = [];
|
|
3008
|
+
if (status) {
|
|
3009
|
+
query += " AND status = ?";
|
|
3010
|
+
args.push(status);
|
|
3011
|
+
}
|
|
3012
|
+
if (task_type) {
|
|
3013
|
+
query += " AND task_type = ?";
|
|
3014
|
+
args.push(task_type);
|
|
3015
|
+
}
|
|
3016
|
+
query += " ORDER BY next_run_at ASC";
|
|
3017
|
+
const tasks = db.query(query).all(...args);
|
|
3018
|
+
return {
|
|
3019
|
+
ok: true,
|
|
3020
|
+
tasks: tasks.map((t) => ({
|
|
3021
|
+
id: t.id,
|
|
3022
|
+
name: t.name,
|
|
3023
|
+
type: t.task_type,
|
|
3024
|
+
status: t.status,
|
|
3025
|
+
cron_expression: t.cron_expression,
|
|
3026
|
+
fire_at: t.fire_at,
|
|
3027
|
+
next_run: t.next_run_at,
|
|
3028
|
+
last_run: t.last_run_at,
|
|
3029
|
+
run_count: t.run_count,
|
|
3030
|
+
channel: t.channel
|
|
3031
|
+
})),
|
|
3032
|
+
count: tasks.length
|
|
3033
|
+
};
|
|
3034
|
+
} catch (err) {
|
|
3035
|
+
log.error(`[list] Failed: ${err.message}`);
|
|
3036
|
+
return { ok: false, error: `Failed to list tasks: ${err.message}` };
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
var schedulePauseTool = {
|
|
3041
|
+
name: "hive.schedule.pause",
|
|
3042
|
+
description: "Pause a scheduled task. Spanish: pausar tarea programada, detener temporalmente",
|
|
3043
|
+
parameters: {
|
|
3044
|
+
type: "object",
|
|
3045
|
+
properties: {
|
|
3046
|
+
task_id: { type: "string", description: "ID of the task to pause" }
|
|
3047
|
+
},
|
|
3048
|
+
required: ["task_id"]
|
|
3049
|
+
},
|
|
3050
|
+
execute: async (params) => {
|
|
3051
|
+
const task_id = params.task_id;
|
|
3052
|
+
if (!task_id) {
|
|
3053
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
3054
|
+
}
|
|
3055
|
+
try {
|
|
3056
|
+
if (_scheduler) {
|
|
3057
|
+
const success = _scheduler.pause(task_id);
|
|
3058
|
+
if (success) {
|
|
3059
|
+
return { ok: true, message: `Task "${task_id}" paused` };
|
|
3060
|
+
} else {
|
|
3061
|
+
return { ok: false, error: `Task "${task_id}" not found or already paused` };
|
|
3062
|
+
}
|
|
3063
|
+
} else {
|
|
3064
|
+
const db = getDb();
|
|
3065
|
+
const result = db.query("UPDATE scheduled_tasks SET status = 'paused' WHERE id = ?").run(task_id);
|
|
3066
|
+
if (result.changes > 0) {
|
|
3067
|
+
return { ok: true, message: `Task "${task_id}" paused (scheduler not active)` };
|
|
3068
|
+
} else {
|
|
3069
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
} catch (err) {
|
|
3073
|
+
log.error(`[pause] Failed: ${err.message}`);
|
|
3074
|
+
return { ok: false, error: `Failed to pause task: ${err.message}` };
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
var scheduleResumeTool = {
|
|
3079
|
+
name: "hive.schedule.resume",
|
|
3080
|
+
description: "Resume a paused scheduled task. Spanish: reanudar tarea programada, continuar",
|
|
3081
|
+
parameters: {
|
|
3082
|
+
type: "object",
|
|
3083
|
+
properties: {
|
|
3084
|
+
task_id: { type: "string", description: "ID of the task to resume" }
|
|
3085
|
+
},
|
|
3086
|
+
required: ["task_id"]
|
|
3087
|
+
},
|
|
3088
|
+
execute: async (params) => {
|
|
3089
|
+
const task_id = params.task_id;
|
|
3090
|
+
if (!task_id) {
|
|
3091
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
3092
|
+
}
|
|
3093
|
+
try {
|
|
3094
|
+
if (_scheduler) {
|
|
3095
|
+
const success = _scheduler.resume(task_id);
|
|
3096
|
+
if (success) {
|
|
3097
|
+
return { ok: true, message: `Task "${task_id}" resumed` };
|
|
3098
|
+
} else {
|
|
3099
|
+
return { ok: false, error: `Task "${task_id}" not found or already active` };
|
|
3100
|
+
}
|
|
3101
|
+
} else {
|
|
3102
|
+
const db = getDb();
|
|
3103
|
+
const result = db.query("UPDATE scheduled_tasks SET status = 'active' WHERE id = ?").run(task_id);
|
|
3104
|
+
if (result.changes > 0) {
|
|
3105
|
+
return { ok: true, message: `Task "${task_id}" resumed (scheduler not active)` };
|
|
3106
|
+
} else {
|
|
3107
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
} catch (err) {
|
|
3111
|
+
log.error(`[resume] Failed: ${err.message}`);
|
|
3112
|
+
return { ok: false, error: `Failed to resume task: ${err.message}` };
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
var scheduleDeleteTool = {
|
|
3117
|
+
name: "hive.schedule.delete",
|
|
3118
|
+
description: "Delete a scheduled task permanently. Spanish: eliminar tarea programada, cancelar recordatorio",
|
|
3119
|
+
parameters: {
|
|
3120
|
+
type: "object",
|
|
3121
|
+
properties: {
|
|
3122
|
+
task_id: { type: "string", description: "ID of the task to delete" }
|
|
3123
|
+
},
|
|
3124
|
+
required: ["task_id"]
|
|
3125
|
+
},
|
|
3126
|
+
execute: async (params) => {
|
|
3127
|
+
const task_id = params.task_id;
|
|
3128
|
+
if (!task_id) {
|
|
3129
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
3130
|
+
}
|
|
3131
|
+
try {
|
|
3132
|
+
if (_scheduler) {
|
|
3133
|
+
const success = _scheduler.delete(task_id);
|
|
3134
|
+
if (success) {
|
|
3135
|
+
return { ok: true, message: `Task "${task_id}" deleted` };
|
|
3136
|
+
} else {
|
|
3137
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
3138
|
+
}
|
|
3139
|
+
} else {
|
|
3140
|
+
const db = getDb();
|
|
3141
|
+
const result = db.query("DELETE FROM scheduled_tasks WHERE id = ?").run(task_id);
|
|
3142
|
+
if (result.changes > 0) {
|
|
3143
|
+
return { ok: true, message: `Task "${task_id}" deleted (scheduler not active)` };
|
|
3144
|
+
} else {
|
|
3145
|
+
return { ok: false, error: `Task "${task_id}" not found` };
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
} catch (err) {
|
|
3149
|
+
log.error(`[delete] Failed: ${err.message}`);
|
|
3150
|
+
return { ok: false, error: `Failed to delete task: ${err.message}` };
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
var scheduleTriggerTool = {
|
|
3155
|
+
name: "hive.schedule.trigger",
|
|
3156
|
+
description: "Manually trigger a scheduled task execution immediately. Spanish: ejecutar tarea ahora, forzar ejecuci\xF3n",
|
|
3157
|
+
parameters: {
|
|
3158
|
+
type: "object",
|
|
3159
|
+
properties: {
|
|
3160
|
+
task_id: { type: "string", description: "ID of the task to trigger" }
|
|
3161
|
+
},
|
|
3162
|
+
required: ["task_id"]
|
|
3163
|
+
},
|
|
3164
|
+
execute: async (params) => {
|
|
3165
|
+
const task_id = params.task_id;
|
|
3166
|
+
if (!task_id) {
|
|
3167
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
3168
|
+
}
|
|
3169
|
+
try {
|
|
3170
|
+
if (_scheduler) {
|
|
3171
|
+
const success = _scheduler.trigger(task_id);
|
|
3172
|
+
if (success) {
|
|
3173
|
+
return { ok: true, message: `Task "${task_id}" triggered` };
|
|
3174
|
+
} else {
|
|
3175
|
+
return { ok: false, error: `Task "${task_id}" not found or not active` };
|
|
3176
|
+
}
|
|
3177
|
+
} else {
|
|
3178
|
+
return { ok: false, error: "Scheduler not active - cannot trigger tasks" };
|
|
3179
|
+
}
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
log.error(`[trigger] Failed: ${err.message}`);
|
|
3182
|
+
return { ok: false, error: `Failed to trigger task: ${err.message}` };
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
var scheduleHistoryTool = {
|
|
3187
|
+
name: "hive.schedule.history",
|
|
3188
|
+
description: "Get execution history for a scheduled task. Spanish: historial de ejecuciones, logs de tarea",
|
|
3189
|
+
parameters: {
|
|
3190
|
+
type: "object",
|
|
3191
|
+
properties: {
|
|
3192
|
+
task_id: { type: "string", description: "ID of the task" },
|
|
3193
|
+
limit: { type: "number", description: "Maximum number of records (default: 10)" }
|
|
3194
|
+
},
|
|
3195
|
+
required: ["task_id"]
|
|
3196
|
+
},
|
|
3197
|
+
execute: async (params) => {
|
|
3198
|
+
const task_id = params.task_id;
|
|
3199
|
+
const limit = params.limit || 10;
|
|
3200
|
+
if (!task_id) {
|
|
3201
|
+
return { ok: false, error: "Missing required field: task_id" };
|
|
3202
|
+
}
|
|
3203
|
+
try {
|
|
3204
|
+
const db = getDb();
|
|
3205
|
+
const runs = db.query(`
|
|
3206
|
+
SELECT * FROM task_runs
|
|
3207
|
+
WHERE task_id = ?
|
|
3208
|
+
ORDER BY started_at DESC
|
|
3209
|
+
LIMIT ?
|
|
3210
|
+
`).all(task_id, limit);
|
|
3211
|
+
return {
|
|
3212
|
+
ok: true,
|
|
3213
|
+
history: runs.map((r) => ({
|
|
3214
|
+
id: r.id,
|
|
3215
|
+
status: r.status,
|
|
3216
|
+
started_at: r.started_at,
|
|
3217
|
+
finished_at: r.finished_at,
|
|
3218
|
+
duration_ms: r.duration_ms,
|
|
3219
|
+
error_message: r.error_message
|
|
3220
|
+
})),
|
|
3221
|
+
count: runs.length
|
|
3222
|
+
};
|
|
3223
|
+
} catch (err) {
|
|
3224
|
+
log.error(`[history] Failed: ${err.message}`);
|
|
3225
|
+
return { ok: false, error: `Failed to get history: ${err.message}` };
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
};
|
|
3229
|
+
function createTools() {
|
|
3230
|
+
return [
|
|
3231
|
+
scheduleCreateTool,
|
|
3232
|
+
scheduleListTool,
|
|
3233
|
+
schedulePauseTool,
|
|
3234
|
+
scheduleResumeTool,
|
|
3235
|
+
scheduleDeleteTool,
|
|
3236
|
+
scheduleTriggerTool,
|
|
3237
|
+
scheduleHistoryTool
|
|
3238
|
+
];
|
|
3239
|
+
}
|
|
3240
|
+
|
|
2
3241
|
// src/cron/index.ts
|
|
3
|
-
|
|
4
|
-
scheduleCreateTool,
|
|
5
|
-
scheduleListTool,
|
|
6
|
-
schedulePauseTool,
|
|
7
|
-
scheduleResumeTool,
|
|
8
|
-
scheduleDeleteTool,
|
|
9
|
-
scheduleTriggerTool,
|
|
10
|
-
scheduleHistoryTool,
|
|
11
|
-
createTools,
|
|
12
|
-
setSchedulerInstance
|
|
13
|
-
} from "@johpaz/hive-agents-core/tools/schedule";
|
|
14
|
-
import {
|
|
15
|
-
cronAddTool,
|
|
16
|
-
cronListTool,
|
|
17
|
-
cronEditTool,
|
|
18
|
-
cronRemoveTool,
|
|
19
|
-
createTools as createTools2,
|
|
20
|
-
initCronScheduler,
|
|
21
|
-
resolveBestChannel
|
|
22
|
-
} from "@johpaz/hive-agents-core/tools/cron";
|
|
3242
|
+
init_cron();
|
|
23
3243
|
export {
|
|
24
3244
|
setSchedulerInstance,
|
|
25
3245
|
scheduleTriggerTool,
|
|
@@ -30,11 +3250,13 @@ export {
|
|
|
30
3250
|
scheduleDeleteTool,
|
|
31
3251
|
scheduleCreateTool,
|
|
32
3252
|
resolveBestChannel,
|
|
33
|
-
|
|
34
|
-
|
|
3253
|
+
cronTriggerTool,
|
|
3254
|
+
cronResumeTool,
|
|
3255
|
+
cronPauseTool,
|
|
35
3256
|
cronListTool,
|
|
36
|
-
|
|
37
|
-
|
|
3257
|
+
cronHistoryTool,
|
|
3258
|
+
cronDeleteTool,
|
|
3259
|
+
cronCreateTool,
|
|
38
3260
|
createTools as createScheduleTools,
|
|
39
3261
|
createTools2 as createCronTools
|
|
40
3262
|
};
|