augure 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +3455 -18
- package/package.json +19 -10
- package/dist/bin.d.ts +0 -3
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/colors.d.ts +0 -8
- package/dist/colors.d.ts.map +0 -1
- package/dist/colors.js +0 -9
- package/dist/colors.js.map +0 -1
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -108
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/skills.d.ts +0 -2
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js +0 -230
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/start.d.ts +0 -9
- package/dist/commands/start.d.ts.map +0 -1
- package/dist/commands/start.js +0 -30
- package/dist/commands/start.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,22 +1,3459 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
|
|
3
|
+
// src/bin.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { defineCommand as defineCommand4, runMain } from "citty";
|
|
6
|
+
|
|
7
|
+
// src/commands/start.ts
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
import { resolve as resolve2 } from "path";
|
|
10
|
+
|
|
11
|
+
// ../core/dist/config.js
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
import JSON5 from "json5";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var LLMModelConfigSchema = z.object({
|
|
16
|
+
provider: z.enum(["openrouter", "anthropic", "openai"]),
|
|
17
|
+
apiKey: z.string().min(1),
|
|
18
|
+
model: z.string().min(1),
|
|
19
|
+
maxTokens: z.number().int().positive()
|
|
20
|
+
});
|
|
21
|
+
var LLMConfigSchema = z.object({
|
|
22
|
+
default: LLMModelConfigSchema,
|
|
23
|
+
reasoning: LLMModelConfigSchema.partial().optional(),
|
|
24
|
+
ingestion: LLMModelConfigSchema.partial().optional(),
|
|
25
|
+
monitoring: LLMModelConfigSchema.partial().optional(),
|
|
26
|
+
coding: LLMModelConfigSchema.partial().optional()
|
|
27
|
+
});
|
|
28
|
+
var AppConfigSchema = z.object({
|
|
29
|
+
identity: z.object({
|
|
30
|
+
name: z.string().min(1),
|
|
31
|
+
personality: z.string().min(1)
|
|
32
|
+
}),
|
|
33
|
+
llm: LLMConfigSchema,
|
|
34
|
+
channels: z.object({
|
|
35
|
+
telegram: z.object({
|
|
36
|
+
enabled: z.boolean(),
|
|
37
|
+
botToken: z.string(),
|
|
38
|
+
allowedUsers: z.array(z.number())
|
|
39
|
+
}).optional(),
|
|
40
|
+
whatsapp: z.object({ enabled: z.boolean() }).optional(),
|
|
41
|
+
web: z.object({ enabled: z.boolean(), port: z.number() }).optional()
|
|
42
|
+
}),
|
|
43
|
+
memory: z.object({
|
|
44
|
+
path: z.string().min(1),
|
|
45
|
+
autoIngest: z.boolean(),
|
|
46
|
+
maxRetrievalTokens: z.number().int().positive()
|
|
47
|
+
}),
|
|
48
|
+
scheduler: z.object({
|
|
49
|
+
heartbeatInterval: z.string().min(1),
|
|
50
|
+
jobs: z.array(z.object({
|
|
51
|
+
id: z.string().min(1),
|
|
52
|
+
cron: z.string().min(1),
|
|
53
|
+
prompt: z.string().min(1),
|
|
54
|
+
channel: z.string().min(1)
|
|
55
|
+
}))
|
|
56
|
+
}),
|
|
57
|
+
sandbox: z.object({
|
|
58
|
+
runtime: z.literal("docker"),
|
|
59
|
+
image: z.string().min(1).optional(),
|
|
60
|
+
defaults: z.object({
|
|
61
|
+
timeout: z.number().int().positive(),
|
|
62
|
+
memoryLimit: z.string().min(1),
|
|
63
|
+
cpuLimit: z.string().min(1)
|
|
64
|
+
}),
|
|
65
|
+
codeAgent: z.object({
|
|
66
|
+
command: z.string().min(1),
|
|
67
|
+
args: z.array(z.string()).optional(),
|
|
68
|
+
env: z.record(z.string(), z.string()).optional()
|
|
69
|
+
}).optional()
|
|
70
|
+
}),
|
|
71
|
+
tools: z.object({
|
|
72
|
+
webSearch: z.object({
|
|
73
|
+
provider: z.enum(["tavily", "exa", "searxng"]),
|
|
74
|
+
apiKey: z.string().optional(),
|
|
75
|
+
baseUrl: z.string().optional(),
|
|
76
|
+
maxResults: z.number().int().positive().optional()
|
|
77
|
+
}).optional(),
|
|
78
|
+
http: z.object({
|
|
79
|
+
defaultHeaders: z.record(z.string(), z.string()).optional(),
|
|
80
|
+
presets: z.record(z.string(), z.object({
|
|
81
|
+
baseUrl: z.string(),
|
|
82
|
+
headers: z.record(z.string(), z.string())
|
|
83
|
+
})).optional(),
|
|
84
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
85
|
+
maxResponseBytes: z.number().int().positive().optional()
|
|
86
|
+
}).optional(),
|
|
87
|
+
email: z.object({
|
|
88
|
+
imap: z.object({
|
|
89
|
+
host: z.string(),
|
|
90
|
+
port: z.number(),
|
|
91
|
+
user: z.string(),
|
|
92
|
+
password: z.string()
|
|
93
|
+
}),
|
|
94
|
+
smtp: z.object({
|
|
95
|
+
host: z.string(),
|
|
96
|
+
port: z.number(),
|
|
97
|
+
user: z.string(),
|
|
98
|
+
password: z.string()
|
|
99
|
+
})
|
|
100
|
+
}).optional(),
|
|
101
|
+
github: z.object({ token: z.string() }).optional()
|
|
102
|
+
}),
|
|
103
|
+
security: z.object({
|
|
104
|
+
sandboxOnly: z.boolean(),
|
|
105
|
+
allowedHosts: z.array(z.string()),
|
|
106
|
+
maxConcurrentSandboxes: z.number().int().positive()
|
|
107
|
+
}),
|
|
108
|
+
skills: z.object({
|
|
109
|
+
path: z.string().min(1).default("./skills"),
|
|
110
|
+
maxFailures: z.number().int().positive().default(3),
|
|
111
|
+
autoSuggest: z.boolean().default(true),
|
|
112
|
+
hub: z.object({
|
|
113
|
+
repo: z.string().min(1),
|
|
114
|
+
branch: z.string().min(1).default("main")
|
|
115
|
+
}).optional()
|
|
116
|
+
}).optional(),
|
|
117
|
+
audit: z.object({
|
|
118
|
+
path: z.string().min(1).default("./logs"),
|
|
119
|
+
enabled: z.boolean().default(true)
|
|
120
|
+
}).optional(),
|
|
121
|
+
persona: z.object({
|
|
122
|
+
path: z.string().min(1).default("./config/personas")
|
|
123
|
+
}).optional()
|
|
124
|
+
});
|
|
125
|
+
function interpolateEnvVars(raw) {
|
|
126
|
+
return raw.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
127
|
+
const value = process.env[varName];
|
|
128
|
+
if (value === void 0) {
|
|
129
|
+
throw new Error(`Missing environment variable: ${varName}`);
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function loadConfig(path) {
|
|
135
|
+
const raw = await readFile(path, "utf-8");
|
|
136
|
+
const interpolated = interpolateEnvVars(raw);
|
|
137
|
+
const parsed = JSON5.parse(interpolated);
|
|
138
|
+
return AppConfigSchema.parse(parsed);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ../core/dist/llm.js
|
|
142
|
+
var OpenRouterClient = class {
|
|
143
|
+
apiKey;
|
|
144
|
+
model;
|
|
145
|
+
maxTokens;
|
|
146
|
+
baseUrl;
|
|
147
|
+
constructor(config) {
|
|
148
|
+
this.apiKey = config.apiKey;
|
|
149
|
+
this.model = config.model;
|
|
150
|
+
this.maxTokens = config.maxTokens;
|
|
151
|
+
this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
152
|
+
}
|
|
153
|
+
async chat(messages) {
|
|
154
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
model: this.model,
|
|
162
|
+
max_tokens: this.maxTokens,
|
|
163
|
+
messages: messages.map((m) => ({
|
|
164
|
+
role: m.role,
|
|
165
|
+
content: m.content,
|
|
166
|
+
...m.toolCallId ? { tool_call_id: m.toolCallId } : {}
|
|
167
|
+
}))
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const body = await response.text();
|
|
172
|
+
throw new Error(`OpenRouter API error ${response.status}: ${body}`);
|
|
173
|
+
}
|
|
174
|
+
const data = await response.json();
|
|
175
|
+
const choice = data.choices[0];
|
|
176
|
+
return {
|
|
177
|
+
content: choice.message.content ?? "",
|
|
178
|
+
toolCalls: (choice.message.tool_calls ?? []).map((tc) => ({
|
|
179
|
+
id: tc.id,
|
|
180
|
+
name: tc.function.name,
|
|
181
|
+
arguments: JSON.parse(tc.function.arguments)
|
|
182
|
+
})),
|
|
183
|
+
usage: {
|
|
184
|
+
inputTokens: data.usage.prompt_tokens,
|
|
185
|
+
outputTokens: data.usage.completion_tokens
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ../core/dist/context.js
|
|
192
|
+
function assembleContext(input) {
|
|
193
|
+
const { systemPrompt, memoryContent, toolSchemas, conversationHistory, persona } = input;
|
|
194
|
+
let system = systemPrompt;
|
|
195
|
+
if (persona) {
|
|
196
|
+
system += `
|
|
197
|
+
|
|
198
|
+
## Active Persona
|
|
199
|
+
${persona}`;
|
|
200
|
+
}
|
|
201
|
+
if (memoryContent) {
|
|
202
|
+
system += `
|
|
203
|
+
|
|
204
|
+
## Memory
|
|
205
|
+
${memoryContent}`;
|
|
206
|
+
}
|
|
207
|
+
if (toolSchemas.length > 0) {
|
|
208
|
+
const toolList = toolSchemas.map((s) => `- **${s.function.name}**: ${s.function.description}`).join("\n");
|
|
209
|
+
system += `
|
|
210
|
+
|
|
211
|
+
## Available Tools
|
|
212
|
+
${toolList}`;
|
|
213
|
+
}
|
|
214
|
+
const messages = [{ role: "system", content: system }];
|
|
215
|
+
messages.push(...conversationHistory);
|
|
216
|
+
return messages;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ../core/dist/audit.js
|
|
220
|
+
import { appendFile, mkdir } from "fs/promises";
|
|
221
|
+
import { join } from "path";
|
|
222
|
+
function summarize(text, maxLen = 200) {
|
|
223
|
+
if (text.length <= maxLen)
|
|
224
|
+
return text;
|
|
225
|
+
return text.slice(0, maxLen) + "...";
|
|
226
|
+
}
|
|
227
|
+
var FileAuditLogger = class {
|
|
228
|
+
basePath;
|
|
229
|
+
pendingWrite = Promise.resolve();
|
|
230
|
+
initialized = false;
|
|
231
|
+
constructor(basePath) {
|
|
232
|
+
this.basePath = basePath;
|
|
233
|
+
}
|
|
234
|
+
log(entry) {
|
|
235
|
+
this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) => console.error("[augure] Audit write error:", err2));
|
|
236
|
+
}
|
|
237
|
+
async close() {
|
|
238
|
+
await this.pendingWrite;
|
|
239
|
+
}
|
|
240
|
+
async writeEntry(entry) {
|
|
241
|
+
const dir = join(this.basePath, "actions");
|
|
242
|
+
if (!this.initialized) {
|
|
243
|
+
await mkdir(dir, { recursive: true });
|
|
244
|
+
this.initialized = true;
|
|
245
|
+
}
|
|
246
|
+
const date = entry.ts.slice(0, 10);
|
|
247
|
+
const filePath = join(dir, `${date}.jsonl`);
|
|
248
|
+
await appendFile(filePath, JSON.stringify(entry) + "\n");
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
var NullAuditLogger = class {
|
|
252
|
+
log(_entry) {
|
|
253
|
+
}
|
|
254
|
+
async close() {
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// ../core/dist/agent.js
|
|
259
|
+
var Agent = class {
|
|
260
|
+
config;
|
|
261
|
+
conversationHistory = [];
|
|
262
|
+
state = "running";
|
|
263
|
+
constructor(config) {
|
|
264
|
+
this.config = config;
|
|
265
|
+
}
|
|
266
|
+
getState() {
|
|
267
|
+
return this.state;
|
|
268
|
+
}
|
|
269
|
+
setState(s) {
|
|
270
|
+
this.state = s;
|
|
271
|
+
}
|
|
272
|
+
setPersona(text) {
|
|
273
|
+
this.config.persona = text;
|
|
274
|
+
}
|
|
275
|
+
async handleMessage(incoming) {
|
|
276
|
+
if (this.state === "killed") {
|
|
277
|
+
return "Agent is in emergency stop mode. Send /resume to reactivate.";
|
|
278
|
+
}
|
|
279
|
+
const start = Date.now();
|
|
280
|
+
this.conversationHistory.push({
|
|
281
|
+
role: "user",
|
|
282
|
+
content: incoming.text
|
|
283
|
+
});
|
|
284
|
+
if (this.config.guard) {
|
|
285
|
+
this.conversationHistory = this.config.guard.compact(this.conversationHistory);
|
|
286
|
+
}
|
|
287
|
+
let memoryContent = this.config.memoryContent;
|
|
288
|
+
if (this.config.retriever) {
|
|
289
|
+
memoryContent = await this.config.retriever.retrieve();
|
|
290
|
+
}
|
|
291
|
+
const maxLoops = this.config.maxToolLoops ?? 10;
|
|
292
|
+
let loopCount = 0;
|
|
293
|
+
while (loopCount < maxLoops) {
|
|
294
|
+
const messages = assembleContext({
|
|
295
|
+
systemPrompt: this.config.systemPrompt,
|
|
296
|
+
memoryContent,
|
|
297
|
+
toolSchemas: this.config.tools.toFunctionSchemas(),
|
|
298
|
+
conversationHistory: this.conversationHistory,
|
|
299
|
+
persona: this.config.persona
|
|
300
|
+
});
|
|
301
|
+
const response = await this.config.llm.chat(messages);
|
|
302
|
+
if (response.toolCalls.length === 0) {
|
|
303
|
+
this.conversationHistory.push({
|
|
304
|
+
role: "assistant",
|
|
305
|
+
content: response.content
|
|
306
|
+
});
|
|
307
|
+
if (this.config.audit) {
|
|
308
|
+
this.config.audit.log({
|
|
309
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
310
|
+
trigger: incoming.channelType === "system" ? "heartbeat" : "user",
|
|
311
|
+
action: "chat",
|
|
312
|
+
inputSummary: summarize(incoming.text),
|
|
313
|
+
outputSummary: summarize(response.content),
|
|
314
|
+
tokens: {
|
|
315
|
+
input: response.usage.inputTokens,
|
|
316
|
+
output: response.usage.outputTokens,
|
|
317
|
+
model: this.config.modelName ?? ""
|
|
318
|
+
},
|
|
319
|
+
durationMs: Date.now() - start,
|
|
320
|
+
success: true
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (this.config.ingester) {
|
|
324
|
+
this.config.ingester.ingest(this.conversationHistory).catch((err2) => console.error("[augure] Ingestion error:", err2));
|
|
325
|
+
}
|
|
326
|
+
return response.content;
|
|
327
|
+
}
|
|
328
|
+
this.conversationHistory.push({
|
|
329
|
+
role: "assistant",
|
|
330
|
+
content: response.content || ""
|
|
331
|
+
});
|
|
332
|
+
for (const toolCall of response.toolCalls) {
|
|
333
|
+
const toolStart = Date.now();
|
|
334
|
+
const result = await this.config.tools.execute(toolCall.name, toolCall.arguments);
|
|
335
|
+
this.conversationHistory.push({
|
|
336
|
+
role: "tool",
|
|
337
|
+
content: result.output,
|
|
338
|
+
toolCallId: toolCall.id
|
|
339
|
+
});
|
|
340
|
+
if (this.config.audit) {
|
|
341
|
+
this.config.audit.log({
|
|
342
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
343
|
+
trigger: incoming.channelType === "system" ? "heartbeat" : "user",
|
|
344
|
+
action: toolCall.name,
|
|
345
|
+
inputSummary: summarize(JSON.stringify(toolCall.arguments)),
|
|
346
|
+
outputSummary: summarize(result.output),
|
|
347
|
+
durationMs: Date.now() - toolStart,
|
|
348
|
+
success: result.success,
|
|
349
|
+
error: result.success ? void 0 : result.output
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
loopCount++;
|
|
354
|
+
}
|
|
355
|
+
return "Max tool call loops reached. Please try again.";
|
|
356
|
+
}
|
|
357
|
+
getConversationHistory() {
|
|
358
|
+
return [...this.conversationHistory];
|
|
359
|
+
}
|
|
360
|
+
clearHistory() {
|
|
361
|
+
this.conversationHistory = [];
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// ../core/dist/commands.js
|
|
366
|
+
async function handleCommand(text, ctx) {
|
|
367
|
+
const trimmed = text.trim();
|
|
368
|
+
if (!trimmed.startsWith("/")) {
|
|
369
|
+
return { handled: false };
|
|
370
|
+
}
|
|
371
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
372
|
+
const command = parts[0]?.toLowerCase();
|
|
373
|
+
const arg = parts[1];
|
|
374
|
+
switch (command) {
|
|
375
|
+
case "pause": {
|
|
376
|
+
if (arg) {
|
|
377
|
+
if (!ctx.skillManager) {
|
|
378
|
+
return { handled: true, response: "Skills system is not configured." };
|
|
379
|
+
}
|
|
380
|
+
await ctx.skillManager.updateStatus(arg, "paused");
|
|
381
|
+
return { handled: true, response: `Skill "${arg}" paused.` };
|
|
382
|
+
}
|
|
383
|
+
ctx.scheduler.stop();
|
|
384
|
+
ctx.agent.setState("paused");
|
|
385
|
+
return { handled: true, response: "Agent paused. Scheduler stopped. Direct messages still accepted." };
|
|
386
|
+
}
|
|
387
|
+
case "resume": {
|
|
388
|
+
ctx.scheduler.start();
|
|
389
|
+
ctx.agent.setState("running");
|
|
390
|
+
return { handled: true, response: "Agent resumed. Scheduler restarted." };
|
|
391
|
+
}
|
|
392
|
+
case "kill": {
|
|
393
|
+
ctx.scheduler.stop();
|
|
394
|
+
await ctx.pool.destroyAll();
|
|
395
|
+
ctx.agent.setState("killed");
|
|
396
|
+
return { handled: true, response: "Emergency stop. All containers destroyed. Agent in read-only mode." };
|
|
397
|
+
}
|
|
398
|
+
case "status": {
|
|
399
|
+
const state = ctx.agent.getState();
|
|
400
|
+
return { handled: true, response: `Agent state: ${state}` };
|
|
401
|
+
}
|
|
402
|
+
default:
|
|
403
|
+
return { handled: false };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ../core/dist/persona.js
|
|
408
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
409
|
+
import { join as join2 } from "path";
|
|
410
|
+
import matter from "gray-matter";
|
|
411
|
+
var PersonaResolver = class {
|
|
412
|
+
personaDir;
|
|
413
|
+
personas = [];
|
|
414
|
+
constructor(personaDir) {
|
|
415
|
+
this.personaDir = personaDir;
|
|
416
|
+
}
|
|
417
|
+
async loadAll() {
|
|
418
|
+
this.personas = [];
|
|
419
|
+
let entries;
|
|
420
|
+
try {
|
|
421
|
+
entries = await readdir(this.personaDir);
|
|
422
|
+
} catch {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (!entry.endsWith(".md"))
|
|
427
|
+
continue;
|
|
428
|
+
const content = await readFile2(join2(this.personaDir, entry), "utf-8");
|
|
429
|
+
const { data, content: body } = matter(content);
|
|
430
|
+
const meta = {
|
|
431
|
+
id: data.id ?? entry.replace(".md", ""),
|
|
432
|
+
name: data.name ?? entry.replace(".md", ""),
|
|
433
|
+
triggers: data.triggers,
|
|
434
|
+
priority: data.priority ?? 0
|
|
435
|
+
};
|
|
436
|
+
this.personas.push({ meta, body: body.trim() });
|
|
437
|
+
}
|
|
438
|
+
this.personas.sort((a, b) => a.meta.id.localeCompare(b.meta.id));
|
|
439
|
+
}
|
|
440
|
+
resolve(message, activeSkillId) {
|
|
441
|
+
const defaultPersona = this.personas.find((p) => p.meta.id === "default");
|
|
442
|
+
const baseParts = [];
|
|
443
|
+
if (defaultPersona) {
|
|
444
|
+
baseParts.push(defaultPersona.body);
|
|
445
|
+
}
|
|
446
|
+
let bestMatch;
|
|
447
|
+
let bestScore = 0;
|
|
448
|
+
const lowerMessage = message.toLowerCase();
|
|
449
|
+
for (const persona of this.personas) {
|
|
450
|
+
if (persona.meta.id === "default")
|
|
451
|
+
continue;
|
|
452
|
+
let score = 0;
|
|
453
|
+
if (persona.meta.triggers?.keywords) {
|
|
454
|
+
for (const kw of persona.meta.triggers.keywords) {
|
|
455
|
+
if (lowerMessage.includes(kw.toLowerCase())) {
|
|
456
|
+
score++;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (activeSkillId && persona.meta.triggers?.skills) {
|
|
461
|
+
for (const pattern of persona.meta.triggers.skills) {
|
|
462
|
+
if (matchGlob(pattern, activeSkillId)) {
|
|
463
|
+
score += 2;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (score === 0)
|
|
468
|
+
continue;
|
|
469
|
+
const priority = persona.meta.priority ?? 0;
|
|
470
|
+
const finalScore = score + priority;
|
|
471
|
+
if (finalScore > bestScore) {
|
|
472
|
+
bestScore = finalScore;
|
|
473
|
+
bestMatch = persona;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (bestMatch) {
|
|
477
|
+
baseParts.push(bestMatch.body);
|
|
478
|
+
}
|
|
479
|
+
return baseParts.join("\n\n");
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
function matchGlob(pattern, value) {
|
|
483
|
+
if (pattern.endsWith("*")) {
|
|
484
|
+
return value.startsWith(pattern.slice(0, -1));
|
|
485
|
+
}
|
|
486
|
+
return pattern === value;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ../core/dist/context-guard.js
|
|
490
|
+
var DEFAULT_CONFIG = {
|
|
491
|
+
maxContextTokens: 2e5,
|
|
492
|
+
reservedForOutput: 8192,
|
|
493
|
+
maxConversationTurns: 50
|
|
494
|
+
};
|
|
495
|
+
var ContextGuard = class {
|
|
496
|
+
config;
|
|
497
|
+
constructor(config = {}) {
|
|
498
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
499
|
+
}
|
|
500
|
+
estimateTokens(text) {
|
|
501
|
+
return Math.ceil(text.length / 4);
|
|
502
|
+
}
|
|
503
|
+
compact(messages) {
|
|
504
|
+
let result = [...messages];
|
|
505
|
+
if (result.length > this.config.maxConversationTurns) {
|
|
506
|
+
result = result.slice(-this.config.maxConversationTurns);
|
|
507
|
+
}
|
|
508
|
+
const recentToolCutoff = Math.max(0, result.length - 10);
|
|
509
|
+
result = result.map((msg, i) => {
|
|
510
|
+
if (msg.role === "tool" && i < recentToolCutoff) {
|
|
511
|
+
return { ...msg, content: "[Tool result truncated]" };
|
|
512
|
+
}
|
|
513
|
+
return msg;
|
|
514
|
+
});
|
|
515
|
+
result = result.map((msg) => {
|
|
516
|
+
if (msg.role === "tool" && msg.content.length > 2e3) {
|
|
517
|
+
return { ...msg, content: msg.content.slice(0, 2e3) + "... [truncated]" };
|
|
518
|
+
}
|
|
519
|
+
return msg;
|
|
520
|
+
});
|
|
521
|
+
const budget = this.config.maxContextTokens - this.config.reservedForOutput;
|
|
522
|
+
let totalTokens = result.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
|
|
523
|
+
while (totalTokens > budget && result.length > 1) {
|
|
524
|
+
const removed = result.shift();
|
|
525
|
+
totalTokens -= this.estimateTokens(removed.content);
|
|
526
|
+
}
|
|
527
|
+
return result;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// ../channels/dist/telegram.js
|
|
532
|
+
import { Bot } from "grammy";
|
|
533
|
+
var TelegramChannel = class {
|
|
534
|
+
type = "telegram";
|
|
535
|
+
bot;
|
|
536
|
+
allowedUsers;
|
|
537
|
+
handlers = [];
|
|
538
|
+
constructor(config) {
|
|
539
|
+
this.bot = new Bot(config.botToken);
|
|
540
|
+
this.allowedUsers = new Set(config.allowedUsers);
|
|
541
|
+
this.bot.on("message:text", async (ctx) => {
|
|
542
|
+
const userId = ctx.from.id;
|
|
543
|
+
if (!this.isUserAllowed(userId)) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const incoming = {
|
|
547
|
+
id: String(ctx.message.message_id),
|
|
548
|
+
channelType: "telegram",
|
|
549
|
+
userId: String(userId),
|
|
550
|
+
text: ctx.message.text,
|
|
551
|
+
timestamp: new Date(ctx.message.date * 1e3),
|
|
552
|
+
replyTo: ctx.message.reply_to_message ? String(ctx.message.reply_to_message.message_id) : void 0
|
|
553
|
+
};
|
|
554
|
+
for (const handler of this.handlers) {
|
|
555
|
+
await handler(incoming);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
isUserAllowed(userId) {
|
|
560
|
+
return this.allowedUsers.has(userId);
|
|
561
|
+
}
|
|
562
|
+
onMessage(handler) {
|
|
563
|
+
this.handlers.push(handler);
|
|
564
|
+
}
|
|
565
|
+
async start() {
|
|
566
|
+
await this.bot.start();
|
|
567
|
+
}
|
|
568
|
+
async stop() {
|
|
569
|
+
await this.bot.stop();
|
|
570
|
+
}
|
|
571
|
+
async send(message) {
|
|
572
|
+
await this.bot.api.sendMessage(Number(message.userId), message.text, {
|
|
573
|
+
parse_mode: "Markdown",
|
|
574
|
+
...message.replyTo ? { reply_parameters: { message_id: Number(message.replyTo) } } : {}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// ../tools/dist/registry.js
|
|
580
|
+
var ToolRegistry = class {
|
|
581
|
+
tools = /* @__PURE__ */ new Map();
|
|
582
|
+
context;
|
|
583
|
+
register(tool) {
|
|
584
|
+
this.tools.set(tool.name, tool);
|
|
585
|
+
}
|
|
586
|
+
get(name) {
|
|
587
|
+
return this.tools.get(name);
|
|
588
|
+
}
|
|
589
|
+
list() {
|
|
590
|
+
return Array.from(this.tools.values());
|
|
591
|
+
}
|
|
592
|
+
toFunctionSchemas() {
|
|
593
|
+
return this.list().map((tool) => ({
|
|
594
|
+
type: "function",
|
|
595
|
+
function: {
|
|
596
|
+
name: tool.name,
|
|
597
|
+
description: tool.description,
|
|
598
|
+
parameters: tool.parameters
|
|
599
|
+
}
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
async execute(name, params) {
|
|
603
|
+
const tool = this.tools.get(name);
|
|
604
|
+
if (!tool) {
|
|
605
|
+
throw new Error(`Tool not found: ${name}`);
|
|
606
|
+
}
|
|
607
|
+
return tool.execute(params, this.context);
|
|
608
|
+
}
|
|
609
|
+
setContext(ctx) {
|
|
610
|
+
this.context = ctx;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// ../tools/dist/memory.js
|
|
615
|
+
var memoryReadTool = {
|
|
616
|
+
name: "memory_read",
|
|
617
|
+
description: "Read content from memory. If path is provided, reads that file. If path is omitted, lists all memory files.",
|
|
618
|
+
parameters: {
|
|
619
|
+
type: "object",
|
|
620
|
+
properties: {
|
|
621
|
+
path: {
|
|
622
|
+
type: "string",
|
|
623
|
+
description: "The memory file path to read (omit to list all files)"
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
execute: async (params, ctx) => {
|
|
628
|
+
const { path } = params;
|
|
629
|
+
try {
|
|
630
|
+
if (!path) {
|
|
631
|
+
const files = await ctx.memory.list();
|
|
632
|
+
return { success: true, output: files.join("\n") };
|
|
633
|
+
}
|
|
634
|
+
const content = await ctx.memory.read(path);
|
|
635
|
+
return { success: true, output: content };
|
|
636
|
+
} catch (err2) {
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
var memoryWriteTool = {
|
|
645
|
+
name: "memory_write",
|
|
646
|
+
description: "Write content to a memory file at the given path",
|
|
647
|
+
parameters: {
|
|
648
|
+
type: "object",
|
|
649
|
+
properties: {
|
|
650
|
+
path: { type: "string", description: "The memory file path to write to" },
|
|
651
|
+
content: { type: "string", description: "The content to write" }
|
|
652
|
+
},
|
|
653
|
+
required: ["path", "content"]
|
|
654
|
+
},
|
|
655
|
+
execute: async (params, ctx) => {
|
|
656
|
+
const { path, content } = params;
|
|
657
|
+
try {
|
|
658
|
+
await ctx.memory.write(path, content);
|
|
659
|
+
return { success: true, output: `Written to ${path}` };
|
|
660
|
+
} catch (err2) {
|
|
661
|
+
return {
|
|
662
|
+
success: false,
|
|
663
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// ../tools/dist/schedule.js
|
|
670
|
+
var scheduleTool = {
|
|
671
|
+
name: "schedule",
|
|
672
|
+
description: "Manage scheduled tasks: create, delete, or list cron jobs",
|
|
673
|
+
parameters: {
|
|
674
|
+
type: "object",
|
|
675
|
+
properties: {
|
|
676
|
+
action: {
|
|
677
|
+
type: "string",
|
|
678
|
+
enum: ["create", "delete", "list"],
|
|
679
|
+
description: "The scheduling action to perform"
|
|
680
|
+
},
|
|
681
|
+
id: { type: "string", description: "The schedule ID (for create/delete)" },
|
|
682
|
+
cron: { type: "string", description: "Cron expression (for create)" },
|
|
683
|
+
prompt: { type: "string", description: "The prompt to execute on schedule (for create)" }
|
|
684
|
+
},
|
|
685
|
+
required: ["action"]
|
|
686
|
+
},
|
|
687
|
+
execute: async (params, ctx) => {
|
|
688
|
+
const { action, id, cron, prompt } = params;
|
|
689
|
+
try {
|
|
690
|
+
switch (action) {
|
|
691
|
+
case "list": {
|
|
692
|
+
const jobs = ctx.scheduler.listJobs();
|
|
693
|
+
if (jobs.length === 0) {
|
|
694
|
+
return { success: true, output: "No scheduled jobs." };
|
|
695
|
+
}
|
|
696
|
+
const lines = jobs.map((j) => `- ${j.id}: "${j.prompt}" @ ${j.cron} (${j.enabled ? "enabled" : "disabled"})`);
|
|
697
|
+
return { success: true, output: lines.join("\n") };
|
|
698
|
+
}
|
|
699
|
+
case "create": {
|
|
700
|
+
if (!cron || !prompt) {
|
|
701
|
+
return { success: false, output: "Missing required fields: cron and prompt" };
|
|
702
|
+
}
|
|
703
|
+
const jobId = id ?? `job-${Date.now()}`;
|
|
704
|
+
ctx.scheduler.addJob({
|
|
705
|
+
id: jobId,
|
|
706
|
+
cron,
|
|
707
|
+
prompt,
|
|
708
|
+
channel: "default",
|
|
709
|
+
enabled: true
|
|
710
|
+
});
|
|
711
|
+
return { success: true, output: `Created job ${jobId}` };
|
|
712
|
+
}
|
|
713
|
+
case "delete": {
|
|
714
|
+
if (!id) {
|
|
715
|
+
return { success: false, output: "Missing required field: id" };
|
|
716
|
+
}
|
|
717
|
+
ctx.scheduler.removeJob(id);
|
|
718
|
+
return { success: true, output: `Deleted job ${id}` };
|
|
719
|
+
}
|
|
720
|
+
default:
|
|
721
|
+
return { success: false, output: `Unknown action: ${action}` };
|
|
722
|
+
}
|
|
723
|
+
} catch (err2) {
|
|
724
|
+
return {
|
|
725
|
+
success: false,
|
|
726
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// ../tools/dist/web-search.js
|
|
733
|
+
var webSearchTool = {
|
|
734
|
+
name: "web_search",
|
|
735
|
+
description: "Search the web using the configured search provider (Tavily, Exa, or SearXNG)",
|
|
736
|
+
parameters: {
|
|
737
|
+
type: "object",
|
|
738
|
+
properties: {
|
|
739
|
+
query: { type: "string", description: "The search query" },
|
|
740
|
+
maxResults: {
|
|
741
|
+
type: "number",
|
|
742
|
+
description: "Max results (default: from config or 5)"
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
required: ["query"]
|
|
746
|
+
},
|
|
747
|
+
execute: async (params, ctx) => {
|
|
748
|
+
const { query, maxResults: maxResultsParam } = params;
|
|
749
|
+
const cfg = ctx.config.tools?.webSearch;
|
|
750
|
+
if (!cfg) {
|
|
751
|
+
return {
|
|
752
|
+
success: false,
|
|
753
|
+
output: "web_search is not configured. Set tools.webSearch in your config."
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const maxResults = maxResultsParam ?? cfg.maxResults ?? 5;
|
|
757
|
+
try {
|
|
758
|
+
let results;
|
|
759
|
+
switch (cfg.provider) {
|
|
760
|
+
case "tavily":
|
|
761
|
+
if (!cfg.apiKey) {
|
|
762
|
+
return { success: false, output: "Tavily requires tools.webSearch.apiKey in config." };
|
|
763
|
+
}
|
|
764
|
+
results = await searchTavily(query, maxResults, cfg.apiKey);
|
|
765
|
+
break;
|
|
766
|
+
case "exa":
|
|
767
|
+
if (!cfg.apiKey) {
|
|
768
|
+
return { success: false, output: "Exa requires tools.webSearch.apiKey in config." };
|
|
769
|
+
}
|
|
770
|
+
results = await searchExa(query, maxResults, cfg.apiKey);
|
|
771
|
+
break;
|
|
772
|
+
case "searxng":
|
|
773
|
+
if (!cfg.baseUrl) {
|
|
774
|
+
return { success: false, output: "SearXNG requires tools.webSearch.baseUrl in config." };
|
|
775
|
+
}
|
|
776
|
+
results = await searchSearXNG(query, maxResults, cfg.baseUrl);
|
|
777
|
+
break;
|
|
778
|
+
default:
|
|
779
|
+
return {
|
|
780
|
+
success: false,
|
|
781
|
+
output: `Unknown search provider: ${cfg.provider}`
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (results.length === 0) {
|
|
785
|
+
return { success: true, output: "No results found." };
|
|
786
|
+
}
|
|
787
|
+
const formatted = results.map((r, i) => `${i + 1}. **${r.title}**
|
|
788
|
+
${r.snippet}
|
|
789
|
+
${r.url}`).join("\n\n");
|
|
790
|
+
return { success: true, output: formatted };
|
|
791
|
+
} catch (err2) {
|
|
792
|
+
return {
|
|
793
|
+
success: false,
|
|
794
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
async function searchTavily(query, maxResults, apiKey) {
|
|
800
|
+
const res = await fetch("https://api.tavily.com/search", {
|
|
801
|
+
method: "POST",
|
|
802
|
+
headers: {
|
|
803
|
+
Authorization: `Bearer ${apiKey}`,
|
|
804
|
+
"Content-Type": "application/json"
|
|
805
|
+
},
|
|
806
|
+
body: JSON.stringify({ query, max_results: maxResults })
|
|
807
|
+
});
|
|
808
|
+
if (!res.ok) {
|
|
809
|
+
throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);
|
|
810
|
+
}
|
|
811
|
+
const data = await res.json();
|
|
812
|
+
return data.results.map((r) => ({
|
|
813
|
+
title: r.title,
|
|
814
|
+
url: r.url,
|
|
815
|
+
snippet: r.content
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
async function searchExa(query, maxResults, apiKey) {
|
|
819
|
+
const res = await fetch("https://api.exa.ai/search", {
|
|
820
|
+
method: "POST",
|
|
821
|
+
headers: {
|
|
822
|
+
"x-api-key": apiKey,
|
|
823
|
+
"Content-Type": "application/json"
|
|
824
|
+
},
|
|
825
|
+
body: JSON.stringify({
|
|
826
|
+
query,
|
|
827
|
+
numResults: maxResults,
|
|
828
|
+
contents: { text: { maxCharacters: 300 } }
|
|
829
|
+
})
|
|
830
|
+
});
|
|
831
|
+
if (!res.ok) {
|
|
832
|
+
throw new Error(`Exa API error: ${res.status} ${res.statusText}`);
|
|
833
|
+
}
|
|
834
|
+
const data = await res.json();
|
|
835
|
+
return data.results.map((r) => ({
|
|
836
|
+
title: r.title,
|
|
837
|
+
url: r.url,
|
|
838
|
+
snippet: r.text
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
841
|
+
async function searchSearXNG(query, maxResults, baseUrl) {
|
|
842
|
+
const url = new URL("/search", baseUrl);
|
|
843
|
+
url.searchParams.set("q", query);
|
|
844
|
+
url.searchParams.set("format", "json");
|
|
845
|
+
url.searchParams.set("pageno", "1");
|
|
846
|
+
const res = await fetch(url.toString(), {
|
|
847
|
+
method: "GET",
|
|
848
|
+
headers: { Accept: "application/json" }
|
|
849
|
+
});
|
|
850
|
+
if (!res.ok) {
|
|
851
|
+
throw new Error(`SearXNG API error: ${res.status} ${res.statusText}`);
|
|
852
|
+
}
|
|
853
|
+
const data = await res.json();
|
|
854
|
+
return data.results.slice(0, maxResults).map((r) => ({
|
|
855
|
+
title: r.title,
|
|
856
|
+
url: r.url,
|
|
857
|
+
snippet: r.content
|
|
858
|
+
}));
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// ../tools/dist/http.js
|
|
862
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
863
|
+
var DEFAULT_MAX_RESPONSE_BYTES = 1048576;
|
|
864
|
+
var MAX_OUTPUT_CHARS = 4e3;
|
|
865
|
+
var httpTool = {
|
|
866
|
+
name: "http",
|
|
867
|
+
description: "Make HTTP requests (GET or POST). Use a preset name to inject auth headers from config.",
|
|
868
|
+
parameters: {
|
|
869
|
+
type: "object",
|
|
870
|
+
properties: {
|
|
871
|
+
method: { type: "string", enum: ["GET", "POST"], description: "HTTP method" },
|
|
872
|
+
url: { type: "string", description: "Full URL, or path if using a preset" },
|
|
873
|
+
preset: { type: "string", description: "Config preset name for auth injection" },
|
|
874
|
+
body: { type: "object", description: "JSON body (POST only)" },
|
|
875
|
+
headers: { type: "object", description: "Additional headers (no auth \u2014 use preset)" }
|
|
876
|
+
},
|
|
877
|
+
required: ["method", "url"]
|
|
878
|
+
},
|
|
879
|
+
execute: async (params, ctx) => {
|
|
880
|
+
const { method, url, preset, body, headers: extraHeaders } = params;
|
|
881
|
+
if (method !== "GET" && method !== "POST") {
|
|
882
|
+
return { success: false, output: "Only GET or POST methods are allowed" };
|
|
883
|
+
}
|
|
884
|
+
const httpConfig = ctx.config.tools?.http;
|
|
885
|
+
let resolvedUrl = url;
|
|
886
|
+
let presetHeaders = {};
|
|
887
|
+
if (preset) {
|
|
888
|
+
const presetConfig = httpConfig?.presets?.[preset];
|
|
889
|
+
if (!presetConfig) {
|
|
890
|
+
return {
|
|
891
|
+
success: false,
|
|
892
|
+
output: `Unknown preset: "${preset}". Check your config.`
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
if (!url.startsWith("http")) {
|
|
896
|
+
resolvedUrl = presetConfig.baseUrl + url;
|
|
897
|
+
}
|
|
898
|
+
presetHeaders = presetConfig.headers ?? {};
|
|
899
|
+
}
|
|
900
|
+
const mergedHeaders = {
|
|
901
|
+
...httpConfig?.defaultHeaders ?? {},
|
|
902
|
+
...extraHeaders ?? {},
|
|
903
|
+
...presetHeaders
|
|
904
|
+
};
|
|
905
|
+
if (body) {
|
|
906
|
+
mergedHeaders["Content-Type"] = "application/json";
|
|
907
|
+
}
|
|
908
|
+
const timeoutMs = httpConfig?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
909
|
+
const maxBytes = httpConfig?.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
910
|
+
const controller = new AbortController();
|
|
911
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
912
|
+
try {
|
|
913
|
+
const res = await fetch(resolvedUrl, {
|
|
914
|
+
method,
|
|
915
|
+
headers: mergedHeaders,
|
|
916
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
917
|
+
signal: controller.signal
|
|
918
|
+
});
|
|
919
|
+
const raw = await res.arrayBuffer();
|
|
920
|
+
let text = new TextDecoder().decode(raw.slice(0, maxBytes));
|
|
921
|
+
if (raw.byteLength > maxBytes) {
|
|
922
|
+
text += "\n[response truncated at byte limit]";
|
|
923
|
+
}
|
|
924
|
+
if (text.length > MAX_OUTPUT_CHARS) {
|
|
925
|
+
text = text.slice(0, MAX_OUTPUT_CHARS) + "\n[truncated]";
|
|
926
|
+
}
|
|
927
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
928
|
+
const output = `Status: ${res.status} ${res.statusText}
|
|
929
|
+
Content-Type: ${contentType}
|
|
930
|
+
|
|
931
|
+
${text}`;
|
|
932
|
+
return { success: res.ok, output };
|
|
933
|
+
} catch (err2) {
|
|
934
|
+
return {
|
|
935
|
+
success: false,
|
|
936
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
937
|
+
};
|
|
938
|
+
} finally {
|
|
939
|
+
clearTimeout(timer);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// ../tools/dist/email.js
|
|
945
|
+
import { ImapFlow } from "imapflow";
|
|
946
|
+
import { createTransport } from "nodemailer";
|
|
947
|
+
|
|
948
|
+
// ../tools/dist/sandbox-exec.js
|
|
949
|
+
var sandboxExecTool = {
|
|
950
|
+
name: "sandbox_exec",
|
|
951
|
+
description: "Execute a shell command in an isolated Docker container. Returns stdout, stderr, and exit code.",
|
|
952
|
+
parameters: {
|
|
953
|
+
type: "object",
|
|
954
|
+
properties: {
|
|
955
|
+
command: {
|
|
956
|
+
type: "string",
|
|
957
|
+
description: "The shell command to execute"
|
|
958
|
+
},
|
|
959
|
+
trust: {
|
|
960
|
+
type: "string",
|
|
961
|
+
enum: ["sandboxed", "trusted"],
|
|
962
|
+
description: "Trust level (default: sandboxed). 'sandboxed' has no network. 'trusted' has host network."
|
|
963
|
+
},
|
|
964
|
+
timeout: {
|
|
965
|
+
type: "number",
|
|
966
|
+
description: "Timeout in seconds (default: from config)"
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
required: ["command"]
|
|
970
|
+
},
|
|
971
|
+
execute: async (params, ctx) => {
|
|
972
|
+
const { command, trust, timeout } = params;
|
|
973
|
+
if (!ctx.pool) {
|
|
974
|
+
return { success: false, output: "Sandbox pool is not available" };
|
|
975
|
+
}
|
|
976
|
+
const defaults = ctx.config.sandbox.defaults;
|
|
977
|
+
const effectiveTimeout = timeout ?? defaults.timeout;
|
|
978
|
+
let container;
|
|
979
|
+
try {
|
|
980
|
+
container = await ctx.pool.acquire({
|
|
981
|
+
trust: trust ?? "sandboxed",
|
|
982
|
+
timeout: effectiveTimeout,
|
|
983
|
+
memory: defaults.memoryLimit,
|
|
984
|
+
cpu: defaults.cpuLimit
|
|
985
|
+
});
|
|
986
|
+
} catch (err2) {
|
|
987
|
+
return {
|
|
988
|
+
success: false,
|
|
989
|
+
output: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
const result = await container.exec(command, {
|
|
994
|
+
timeout: effectiveTimeout
|
|
995
|
+
});
|
|
996
|
+
const parts = [];
|
|
997
|
+
if (result.stdout)
|
|
998
|
+
parts.push(result.stdout);
|
|
999
|
+
if (result.stderr)
|
|
1000
|
+
parts.push(`[stderr]
|
|
1001
|
+
${result.stderr}`);
|
|
1002
|
+
parts.push(`Exit code: ${result.exitCode}`);
|
|
1003
|
+
return {
|
|
1004
|
+
success: result.exitCode === 0,
|
|
1005
|
+
output: parts.join("\n")
|
|
1006
|
+
};
|
|
1007
|
+
} catch (err2) {
|
|
1008
|
+
return {
|
|
1009
|
+
success: false,
|
|
1010
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
1011
|
+
};
|
|
1012
|
+
} finally {
|
|
1013
|
+
await ctx.pool.release(container);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// ../tools/dist/opencode.js
|
|
1019
|
+
function shellEscape(s) {
|
|
1020
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
1021
|
+
}
|
|
1022
|
+
var opencodeTool = {
|
|
1023
|
+
name: "opencode",
|
|
1024
|
+
description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
|
|
1025
|
+
parameters: {
|
|
1026
|
+
type: "object",
|
|
1027
|
+
properties: {
|
|
1028
|
+
task: {
|
|
1029
|
+
type: "string",
|
|
1030
|
+
description: "Natural language task description for the code agent"
|
|
1031
|
+
},
|
|
1032
|
+
trust: {
|
|
1033
|
+
type: "string",
|
|
1034
|
+
enum: ["sandboxed", "trusted"],
|
|
1035
|
+
description: "Trust level (default: trusted)"
|
|
1036
|
+
},
|
|
1037
|
+
timeout: {
|
|
1038
|
+
type: "number",
|
|
1039
|
+
description: "Timeout in seconds (default: from config)"
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
required: ["task"]
|
|
1043
|
+
},
|
|
1044
|
+
execute: async (params, ctx) => {
|
|
1045
|
+
const { task, trust, timeout } = params;
|
|
1046
|
+
if (!ctx.pool) {
|
|
1047
|
+
return { success: false, output: "Sandbox pool is not available" };
|
|
1048
|
+
}
|
|
1049
|
+
const agentConfig = ctx.config.sandbox.codeAgent;
|
|
1050
|
+
if (!agentConfig) {
|
|
1051
|
+
return {
|
|
1052
|
+
success: false,
|
|
1053
|
+
output: "codeAgent is not configured in sandbox config"
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const defaults = ctx.config.sandbox.defaults;
|
|
1057
|
+
const effectiveTimeout = timeout ?? defaults.timeout;
|
|
1058
|
+
let container;
|
|
1059
|
+
try {
|
|
1060
|
+
container = await ctx.pool.acquire({
|
|
1061
|
+
trust: trust ?? "trusted",
|
|
1062
|
+
timeout: effectiveTimeout,
|
|
1063
|
+
memory: defaults.memoryLimit,
|
|
1064
|
+
cpu: defaults.cpuLimit
|
|
1065
|
+
});
|
|
1066
|
+
} catch (err2) {
|
|
1067
|
+
return {
|
|
1068
|
+
success: false,
|
|
1069
|
+
output: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
const cmdParts = [agentConfig.command];
|
|
1074
|
+
if (agentConfig.args)
|
|
1075
|
+
cmdParts.push(...agentConfig.args);
|
|
1076
|
+
cmdParts.push(shellEscape(task));
|
|
1077
|
+
const command = cmdParts.join(" ");
|
|
1078
|
+
const result = await container.exec(command, {
|
|
1079
|
+
timeout: effectiveTimeout,
|
|
1080
|
+
env: agentConfig.env
|
|
1081
|
+
});
|
|
1082
|
+
const parts = [];
|
|
1083
|
+
if (result.stdout)
|
|
1084
|
+
parts.push(result.stdout);
|
|
1085
|
+
if (result.stderr)
|
|
1086
|
+
parts.push(`[stderr]
|
|
1087
|
+
${result.stderr}`);
|
|
1088
|
+
parts.push(`Exit code: ${result.exitCode}`);
|
|
1089
|
+
return {
|
|
1090
|
+
success: result.exitCode === 0,
|
|
1091
|
+
output: parts.join("\n")
|
|
1092
|
+
};
|
|
1093
|
+
} catch (err2) {
|
|
1094
|
+
return {
|
|
1095
|
+
success: false,
|
|
1096
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
1097
|
+
};
|
|
1098
|
+
} finally {
|
|
1099
|
+
await ctx.pool.release(container);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// ../core/dist/main.js
|
|
1105
|
+
import Dockerode from "dockerode";
|
|
1106
|
+
|
|
1107
|
+
// ../sandbox/dist/container.js
|
|
1108
|
+
import { PassThrough } from "stream";
|
|
1109
|
+
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
1110
|
+
var DockerContainer = class {
|
|
1111
|
+
id;
|
|
1112
|
+
_status = "idle";
|
|
1113
|
+
raw;
|
|
1114
|
+
demux;
|
|
1115
|
+
constructor(raw, demux) {
|
|
1116
|
+
this.raw = raw;
|
|
1117
|
+
this.id = raw.id;
|
|
1118
|
+
this.demux = demux;
|
|
1119
|
+
}
|
|
1120
|
+
get status() {
|
|
1121
|
+
return this._status;
|
|
1122
|
+
}
|
|
1123
|
+
async exec(command, opts) {
|
|
1124
|
+
this._status = "busy";
|
|
1125
|
+
try {
|
|
1126
|
+
return await this._exec(command, opts);
|
|
1127
|
+
} finally {
|
|
1128
|
+
if (this._status !== "stopped") {
|
|
1129
|
+
this._status = "idle";
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async stop() {
|
|
1134
|
+
if (this._status === "stopped")
|
|
1135
|
+
return;
|
|
1136
|
+
try {
|
|
1137
|
+
await this.raw.stop({ t: 5 });
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
await this.raw.remove({ force: true });
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
this._status = "stopped";
|
|
1145
|
+
}
|
|
1146
|
+
/* ------------------------------------------------------------------ */
|
|
1147
|
+
async _exec(command, opts) {
|
|
1148
|
+
const createOpts = {
|
|
1149
|
+
Cmd: ["sh", "-c", command],
|
|
1150
|
+
AttachStdout: true,
|
|
1151
|
+
AttachStderr: true
|
|
1152
|
+
};
|
|
1153
|
+
if (opts?.cwd) {
|
|
1154
|
+
createOpts.WorkingDir = opts.cwd;
|
|
1155
|
+
}
|
|
1156
|
+
if (opts?.env) {
|
|
1157
|
+
createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
|
|
1158
|
+
}
|
|
1159
|
+
const exec = await this.raw.exec(createOpts);
|
|
1160
|
+
const stream = await exec.start({ hijack: true, stdin: false });
|
|
1161
|
+
return new Promise((resolve5, reject) => {
|
|
1162
|
+
const stdoutPT = new PassThrough();
|
|
1163
|
+
const stderrPT = new PassThrough();
|
|
1164
|
+
const stdoutBufs = [];
|
|
1165
|
+
const stderrBufs = [];
|
|
1166
|
+
let stdoutLen = 0;
|
|
1167
|
+
let stderrLen = 0;
|
|
1168
|
+
stdoutPT.on("data", (chunk) => {
|
|
1169
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1170
|
+
const remaining = MAX_OUTPUT_BYTES - stdoutLen;
|
|
1171
|
+
if (remaining > 0) {
|
|
1172
|
+
stdoutBufs.push(buf.subarray(0, remaining));
|
|
1173
|
+
stdoutLen += Math.min(buf.length, remaining);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
stderrPT.on("data", (chunk) => {
|
|
1177
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1178
|
+
const remaining = MAX_OUTPUT_BYTES - stderrLen;
|
|
1179
|
+
if (remaining > 0) {
|
|
1180
|
+
stderrBufs.push(buf.subarray(0, remaining));
|
|
1181
|
+
stderrLen += Math.min(buf.length, remaining);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
let timer;
|
|
1185
|
+
let settled = false;
|
|
1186
|
+
let stdoutEnded = false;
|
|
1187
|
+
let stderrEnded = false;
|
|
1188
|
+
const cleanup = () => {
|
|
1189
|
+
if (timer)
|
|
1190
|
+
clearTimeout(timer);
|
|
1191
|
+
};
|
|
1192
|
+
if (opts?.timeout && opts.timeout > 0) {
|
|
1193
|
+
const timeoutMs = opts.timeout * 1e3;
|
|
1194
|
+
timer = setTimeout(() => {
|
|
1195
|
+
if (!settled) {
|
|
1196
|
+
settled = true;
|
|
1197
|
+
stream.destroy();
|
|
1198
|
+
reject(new Error(`Exec timed out after ${opts.timeout}s`));
|
|
1199
|
+
}
|
|
1200
|
+
}, timeoutMs);
|
|
1201
|
+
}
|
|
1202
|
+
const tryResolve = async () => {
|
|
1203
|
+
if (!stdoutEnded || !stderrEnded)
|
|
1204
|
+
return;
|
|
1205
|
+
if (settled)
|
|
1206
|
+
return;
|
|
1207
|
+
settled = true;
|
|
1208
|
+
cleanup();
|
|
1209
|
+
try {
|
|
1210
|
+
const info = await exec.inspect();
|
|
1211
|
+
resolve5({
|
|
1212
|
+
exitCode: info.ExitCode ?? 1,
|
|
1213
|
+
stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
|
|
1214
|
+
stderr: Buffer.concat(stderrBufs).toString("utf-8")
|
|
1215
|
+
});
|
|
1216
|
+
} catch (err2) {
|
|
1217
|
+
reject(err2);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
stdoutPT.on("end", () => {
|
|
1221
|
+
stdoutEnded = true;
|
|
1222
|
+
tryResolve();
|
|
1223
|
+
});
|
|
1224
|
+
stderrPT.on("end", () => {
|
|
1225
|
+
stderrEnded = true;
|
|
1226
|
+
tryResolve();
|
|
1227
|
+
});
|
|
1228
|
+
stream.on("error", (err2) => {
|
|
1229
|
+
if (!settled) {
|
|
1230
|
+
settled = true;
|
|
1231
|
+
cleanup();
|
|
1232
|
+
reject(err2);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
this.demux(stream, stdoutPT, stderrPT);
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
// ../sandbox/dist/pool.js
|
|
1241
|
+
function parseMemory(mem) {
|
|
1242
|
+
const match = mem.match(/^(\d+(?:\.\d+)?)\s*([mg])$/i);
|
|
1243
|
+
if (!match)
|
|
1244
|
+
throw new Error(`Invalid memory value: ${mem}`);
|
|
1245
|
+
const value = parseFloat(match[1]);
|
|
1246
|
+
const unit = match[2].toLowerCase();
|
|
1247
|
+
if (unit === "m")
|
|
1248
|
+
return Math.round(value * 1024 * 1024);
|
|
1249
|
+
return Math.round(value * 1024 * 1024 * 1024);
|
|
1250
|
+
}
|
|
1251
|
+
function parseCpu(cpu) {
|
|
1252
|
+
const value = parseFloat(cpu);
|
|
1253
|
+
if (Number.isNaN(value))
|
|
1254
|
+
throw new Error(`Invalid cpu value: ${cpu}`);
|
|
1255
|
+
return Math.round(value * 1e9);
|
|
1256
|
+
}
|
|
1257
|
+
var DockerContainerPool = class {
|
|
1258
|
+
docker;
|
|
1259
|
+
image;
|
|
1260
|
+
maxTotal;
|
|
1261
|
+
// C3: idle cache keyed by trust level to prevent cross-trust reuse
|
|
1262
|
+
idle = /* @__PURE__ */ new Map([
|
|
1263
|
+
["sandboxed", /* @__PURE__ */ new Set()],
|
|
1264
|
+
["trusted", /* @__PURE__ */ new Set()]
|
|
1265
|
+
]);
|
|
1266
|
+
busy = /* @__PURE__ */ new Set();
|
|
1267
|
+
containerTrust = /* @__PURE__ */ new Map();
|
|
1268
|
+
constructor(docker, config) {
|
|
1269
|
+
this.docker = docker;
|
|
1270
|
+
this.image = config.image;
|
|
1271
|
+
this.maxTotal = config.maxTotal;
|
|
1272
|
+
}
|
|
1273
|
+
get idleCount() {
|
|
1274
|
+
let count = 0;
|
|
1275
|
+
for (const set of this.idle.values())
|
|
1276
|
+
count += set.size;
|
|
1277
|
+
return count;
|
|
1278
|
+
}
|
|
1279
|
+
/* ---- acquire ---- */
|
|
1280
|
+
async acquire(opts) {
|
|
1281
|
+
const trustIdle = this.idle.get(opts.trust);
|
|
1282
|
+
const cached = trustIdle.values().next();
|
|
1283
|
+
if (!cached.done) {
|
|
1284
|
+
const container2 = cached.value;
|
|
1285
|
+
trustIdle.delete(container2);
|
|
1286
|
+
this.busy.add(container2);
|
|
1287
|
+
return container2;
|
|
1288
|
+
}
|
|
1289
|
+
const total = this.idleCount + this.busy.size;
|
|
1290
|
+
if (total >= this.maxTotal) {
|
|
1291
|
+
throw new Error("Pool limit reached");
|
|
1292
|
+
}
|
|
1293
|
+
const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
|
|
1294
|
+
await raw.start();
|
|
1295
|
+
const modem = this.docker.modem;
|
|
1296
|
+
const demux = modem.demuxStream.bind(modem);
|
|
1297
|
+
const container = new DockerContainer(raw, demux);
|
|
1298
|
+
this.containerTrust.set(container.id, opts.trust);
|
|
1299
|
+
this.busy.add(container);
|
|
1300
|
+
return container;
|
|
1301
|
+
}
|
|
1302
|
+
/* ---- release ---- */
|
|
1303
|
+
async release(container) {
|
|
1304
|
+
this.busy.delete(container);
|
|
1305
|
+
if (container.status === "stopped") {
|
|
1306
|
+
this.containerTrust.delete(container.id);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
const trust = this.containerTrust.get(container.id) ?? "sandboxed";
|
|
1310
|
+
this.idle.get(trust).add(container);
|
|
1311
|
+
}
|
|
1312
|
+
/* ---- destroy ---- */
|
|
1313
|
+
async destroy(container) {
|
|
1314
|
+
this.busy.delete(container);
|
|
1315
|
+
for (const set of this.idle.values())
|
|
1316
|
+
set.delete(container);
|
|
1317
|
+
this.containerTrust.delete(container.id);
|
|
1318
|
+
await container.stop();
|
|
1319
|
+
}
|
|
1320
|
+
/* ---- destroyAll ---- */
|
|
1321
|
+
async destroyAll() {
|
|
1322
|
+
const all = [...this.busy];
|
|
1323
|
+
for (const set of this.idle.values()) {
|
|
1324
|
+
all.push(...set);
|
|
1325
|
+
set.clear();
|
|
1326
|
+
}
|
|
1327
|
+
this.busy.clear();
|
|
1328
|
+
this.containerTrust.clear();
|
|
1329
|
+
await Promise.allSettled(all.map((c) => c.stop()));
|
|
1330
|
+
}
|
|
1331
|
+
/* ---- stats ---- */
|
|
1332
|
+
stats() {
|
|
1333
|
+
const idle = this.idleCount;
|
|
1334
|
+
return {
|
|
1335
|
+
idle,
|
|
1336
|
+
busy: this.busy.size,
|
|
1337
|
+
total: idle + this.busy.size,
|
|
1338
|
+
maxTotal: this.maxTotal
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
/* ---- internal ---- */
|
|
1342
|
+
buildCreateOpts(opts) {
|
|
1343
|
+
const isSandboxed = opts.trust === "sandboxed";
|
|
1344
|
+
const hostConfig = {
|
|
1345
|
+
Memory: parseMemory(opts.memory),
|
|
1346
|
+
NanoCpus: parseCpu(opts.cpu),
|
|
1347
|
+
PidsLimit: 512
|
|
1348
|
+
// I6: prevent fork bombs
|
|
1349
|
+
};
|
|
1350
|
+
if (!isSandboxed) {
|
|
1351
|
+
hostConfig.NetworkMode = "host";
|
|
1352
|
+
}
|
|
1353
|
+
if (opts.mounts?.length) {
|
|
1354
|
+
hostConfig.Binds = opts.mounts.map((m) => `${m.host}:${m.container}${m.readonly ? ":ro" : ""}`);
|
|
1355
|
+
}
|
|
1356
|
+
const createOpts = {
|
|
1357
|
+
Image: this.image,
|
|
1358
|
+
Cmd: ["sleep", "infinity"],
|
|
1359
|
+
WorkingDir: "/workspace",
|
|
1360
|
+
NetworkDisabled: isSandboxed,
|
|
1361
|
+
HostConfig: hostConfig
|
|
1362
|
+
};
|
|
1363
|
+
if (opts.env) {
|
|
1364
|
+
createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
|
|
1365
|
+
}
|
|
1366
|
+
return createOpts;
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// ../memory/dist/store.js
|
|
1371
|
+
import { readFile as readFile3, writeFile, mkdir as mkdir2, readdir as readdir2, access } from "fs/promises";
|
|
1372
|
+
import { join as join3, dirname, relative } from "path";
|
|
1373
|
+
var FileMemoryStore = class {
|
|
1374
|
+
basePath;
|
|
1375
|
+
constructor(basePath) {
|
|
1376
|
+
this.basePath = basePath;
|
|
1377
|
+
}
|
|
1378
|
+
async read(path) {
|
|
1379
|
+
return readFile3(this.resolve(path), "utf-8");
|
|
1380
|
+
}
|
|
1381
|
+
async write(path, content) {
|
|
1382
|
+
const full = this.resolve(path);
|
|
1383
|
+
await mkdir2(dirname(full), { recursive: true });
|
|
1384
|
+
await writeFile(full, content, "utf-8");
|
|
1385
|
+
}
|
|
1386
|
+
async append(path, content) {
|
|
1387
|
+
const full = this.resolve(path);
|
|
1388
|
+
try {
|
|
1389
|
+
const existing = await readFile3(full, "utf-8");
|
|
1390
|
+
await writeFile(full, existing + content, "utf-8");
|
|
1391
|
+
} catch {
|
|
1392
|
+
await this.write(path, content);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async list(directory) {
|
|
1396
|
+
const dir = directory ? this.resolve(directory) : this.basePath;
|
|
1397
|
+
return this.listRecursive(dir);
|
|
1398
|
+
}
|
|
1399
|
+
async exists(path) {
|
|
1400
|
+
try {
|
|
1401
|
+
await access(this.resolve(path));
|
|
1402
|
+
return true;
|
|
1403
|
+
} catch {
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
resolve(path) {
|
|
1408
|
+
return join3(this.basePath, path);
|
|
1409
|
+
}
|
|
1410
|
+
async listRecursive(dir) {
|
|
1411
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1412
|
+
const files = [];
|
|
1413
|
+
for (const entry of entries) {
|
|
1414
|
+
const full = join3(dir, entry.name);
|
|
1415
|
+
if (entry.isDirectory()) {
|
|
1416
|
+
files.push(...await this.listRecursive(full));
|
|
1417
|
+
} else {
|
|
1418
|
+
files.push(relative(this.basePath, full));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return files;
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
// ../memory/dist/ingest.js
|
|
1426
|
+
var EXTRACTION_PROMPT = `You are a memory extraction agent. Given a conversation, extract key factual observations about the user.
|
|
1427
|
+
|
|
1428
|
+
Rules:
|
|
1429
|
+
- Return a markdown bullet list of observations (one per line, starting with "- ")
|
|
1430
|
+
- Only extract facts, preferences, decisions, plans, and personal details
|
|
1431
|
+
- Be concise: one fact per bullet
|
|
1432
|
+
- If there are no notable observations, return exactly "No notable observations."
|
|
1433
|
+
- Do not include greetings, small talk, or meta-conversation
|
|
1434
|
+
- Use present tense ("User prefers X", not "User said they prefer X")
|
|
1435
|
+
|
|
1436
|
+
Example output:
|
|
1437
|
+
- User prefers TypeScript over JavaScript
|
|
1438
|
+
- User is building a project called Augure
|
|
1439
|
+
- User lives in Bordeaux, France`;
|
|
1440
|
+
var MemoryIngester = class {
|
|
1441
|
+
llm;
|
|
1442
|
+
store;
|
|
1443
|
+
constructor(llm, store) {
|
|
1444
|
+
this.llm = llm;
|
|
1445
|
+
this.store = store;
|
|
1446
|
+
}
|
|
1447
|
+
async ingest(conversation) {
|
|
1448
|
+
if (conversation.length === 0)
|
|
1449
|
+
return;
|
|
1450
|
+
const conversationText = conversation.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${m.content}`).join("\n");
|
|
1451
|
+
const messages = [
|
|
1452
|
+
{ role: "system", content: EXTRACTION_PROMPT },
|
|
1453
|
+
{ role: "user", content: conversationText }
|
|
1454
|
+
];
|
|
1455
|
+
const response = await this.llm.chat(messages);
|
|
1456
|
+
const observations = this.parseObservations(response.content);
|
|
1457
|
+
if (observations.length === 0)
|
|
1458
|
+
return;
|
|
1459
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1460
|
+
const block = `## ${date}
|
|
1461
|
+
${observations.map((o) => `- ${o}`).join("\n")}
|
|
1462
|
+
|
|
1463
|
+
`;
|
|
1464
|
+
await this.store.append("observations.md", block);
|
|
1465
|
+
}
|
|
1466
|
+
parseObservations(content) {
|
|
1467
|
+
const lines = content.split("\n");
|
|
1468
|
+
return lines.filter((line) => line.trim().startsWith("- ")).map((line) => line.trim().slice(2).trim()).filter((line) => line.length > 0);
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
// ../memory/dist/retrieve.js
|
|
1473
|
+
var PRIORITY_FILES = ["identity.md", "observations.md"];
|
|
1474
|
+
var CHARS_PER_TOKEN = 4;
|
|
1475
|
+
var MemoryRetriever = class {
|
|
1476
|
+
store;
|
|
1477
|
+
maxChars;
|
|
1478
|
+
constructor(store, options = {}) {
|
|
1479
|
+
this.store = store;
|
|
1480
|
+
const maxTokens = options.maxTokens ?? 1e4;
|
|
1481
|
+
this.maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
1482
|
+
}
|
|
1483
|
+
async retrieve() {
|
|
1484
|
+
const allFiles = await this.safeList();
|
|
1485
|
+
if (allFiles.length === 0)
|
|
1486
|
+
return "";
|
|
1487
|
+
const prioritySet = new Set(PRIORITY_FILES);
|
|
1488
|
+
const orderedFiles = [
|
|
1489
|
+
...PRIORITY_FILES.filter((f) => allFiles.includes(f)),
|
|
1490
|
+
...allFiles.filter((f) => !prioritySet.has(f)).sort()
|
|
1491
|
+
];
|
|
1492
|
+
const sections = [];
|
|
1493
|
+
let totalChars = 0;
|
|
1494
|
+
for (const file of orderedFiles) {
|
|
1495
|
+
if (totalChars >= this.maxChars)
|
|
1496
|
+
break;
|
|
1497
|
+
try {
|
|
1498
|
+
const content = await this.store.read(file);
|
|
1499
|
+
const header = `### ${file}`;
|
|
1500
|
+
const section = `${header}
|
|
1501
|
+
${content}`;
|
|
1502
|
+
const sectionChars = section.length;
|
|
1503
|
+
if (totalChars + sectionChars > this.maxChars) {
|
|
1504
|
+
const remaining = this.maxChars - totalChars;
|
|
1505
|
+
if (remaining > header.length + 50) {
|
|
1506
|
+
sections.push(section.slice(0, remaining) + "\n[...truncated]");
|
|
1507
|
+
totalChars = this.maxChars;
|
|
1508
|
+
}
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
sections.push(section);
|
|
1512
|
+
totalChars += sectionChars;
|
|
1513
|
+
} catch {
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return sections.join("\n\n");
|
|
1517
|
+
}
|
|
1518
|
+
async safeList() {
|
|
1519
|
+
try {
|
|
1520
|
+
return await this.store.list();
|
|
1521
|
+
} catch {
|
|
1522
|
+
return [];
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
// ../scheduler/dist/cron.js
|
|
1528
|
+
import { createTask, validate } from "node-cron";
|
|
1529
|
+
var CronScheduler = class {
|
|
1530
|
+
store;
|
|
1531
|
+
jobs = /* @__PURE__ */ new Map();
|
|
1532
|
+
tasks = /* @__PURE__ */ new Map();
|
|
1533
|
+
handlers = [];
|
|
1534
|
+
persistChain = Promise.resolve();
|
|
1535
|
+
constructor(store) {
|
|
1536
|
+
this.store = store;
|
|
1537
|
+
}
|
|
1538
|
+
onJobTrigger(handler) {
|
|
1539
|
+
this.handlers.push(handler);
|
|
1540
|
+
}
|
|
1541
|
+
addJob(job) {
|
|
1542
|
+
if (!validate(job.cron)) {
|
|
1543
|
+
throw new Error(`Invalid cron expression: ${job.cron}`);
|
|
1544
|
+
}
|
|
1545
|
+
this.jobs.set(job.id, job);
|
|
1546
|
+
if (job.enabled) {
|
|
1547
|
+
const task = createTask(job.cron, () => {
|
|
1548
|
+
void this.executeHandlers(job);
|
|
1549
|
+
});
|
|
1550
|
+
this.tasks.set(job.id, task);
|
|
1551
|
+
}
|
|
1552
|
+
this.persist();
|
|
1553
|
+
}
|
|
1554
|
+
removeJob(id) {
|
|
1555
|
+
const task = this.tasks.get(id);
|
|
1556
|
+
if (task) {
|
|
1557
|
+
task.stop();
|
|
1558
|
+
this.tasks.delete(id);
|
|
1559
|
+
}
|
|
1560
|
+
this.jobs.delete(id);
|
|
1561
|
+
this.persist();
|
|
1562
|
+
}
|
|
1563
|
+
listJobs() {
|
|
1564
|
+
return Array.from(this.jobs.values());
|
|
1565
|
+
}
|
|
1566
|
+
async triggerJob(id) {
|
|
1567
|
+
const job = this.jobs.get(id);
|
|
1568
|
+
if (!job) {
|
|
1569
|
+
throw new Error(`Job not found: ${id}`);
|
|
1570
|
+
}
|
|
1571
|
+
await this.executeHandlers(job);
|
|
1572
|
+
}
|
|
1573
|
+
async loadPersistedJobs() {
|
|
1574
|
+
if (!this.store)
|
|
1575
|
+
return;
|
|
1576
|
+
const jobs = await this.store.load();
|
|
1577
|
+
for (const job of jobs) {
|
|
1578
|
+
this.addJob(job);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
start() {
|
|
1582
|
+
for (const task of this.tasks.values()) {
|
|
1583
|
+
task.start();
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
stop() {
|
|
1587
|
+
for (const task of this.tasks.values()) {
|
|
1588
|
+
task.stop();
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
persist() {
|
|
1592
|
+
if (!this.store)
|
|
1593
|
+
return;
|
|
1594
|
+
const jobs = this.listJobs();
|
|
1595
|
+
this.persistChain = this.persistChain.then(() => this.store.save(jobs));
|
|
1596
|
+
}
|
|
1597
|
+
async executeHandlers(job) {
|
|
1598
|
+
for (const handler of this.handlers) {
|
|
1599
|
+
await handler(job);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
// ../scheduler/dist/jobs.js
|
|
1605
|
+
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir3 } from "fs/promises";
|
|
1606
|
+
import { dirname as dirname2 } from "path";
|
|
1607
|
+
var JobStore = class {
|
|
1608
|
+
filePath;
|
|
1609
|
+
constructor(filePath) {
|
|
1610
|
+
this.filePath = filePath;
|
|
1611
|
+
}
|
|
1612
|
+
async load() {
|
|
1613
|
+
try {
|
|
1614
|
+
const raw = await readFile4(this.filePath, "utf-8");
|
|
1615
|
+
return JSON.parse(raw);
|
|
1616
|
+
} catch {
|
|
1617
|
+
return [];
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async save(jobs) {
|
|
1621
|
+
await mkdir3(dirname2(this.filePath), { recursive: true });
|
|
1622
|
+
await writeFile2(this.filePath, JSON.stringify(jobs, null, 2), "utf-8");
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
// ../scheduler/dist/heartbeat.js
|
|
1627
|
+
var HEARTBEAT_PROMPT = `You are a monitoring agent. Your job is to review the user's memory and decide if any proactive action is needed right now.
|
|
1628
|
+
|
|
1629
|
+
Review the memory context below and determine:
|
|
1630
|
+
1. Are there any time-sensitive tasks or reminders?
|
|
1631
|
+
2. Should the user be notified about something?
|
|
1632
|
+
3. Are there any scheduled checks that need to run?
|
|
1633
|
+
|
|
1634
|
+
If action is needed, respond with:
|
|
1635
|
+
ACTION: <description of what to do>
|
|
1636
|
+
|
|
1637
|
+
If no action is needed, respond with:
|
|
1638
|
+
ACTION: none
|
|
1639
|
+
|
|
1640
|
+
Be concise. Only suggest actions that are clearly needed based on the memory context.`;
|
|
1641
|
+
var Heartbeat = class {
|
|
1642
|
+
config;
|
|
1643
|
+
timer;
|
|
1644
|
+
constructor(config) {
|
|
1645
|
+
this.config = config;
|
|
1646
|
+
}
|
|
1647
|
+
async tick() {
|
|
1648
|
+
const memoryContent = await this.loadMemory();
|
|
1649
|
+
const messages = [
|
|
1650
|
+
{ role: "system", content: HEARTBEAT_PROMPT },
|
|
1651
|
+
{
|
|
1652
|
+
role: "user",
|
|
1653
|
+
content: `Current time: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1654
|
+
|
|
1655
|
+
## Memory
|
|
1656
|
+
${memoryContent}`
|
|
1657
|
+
}
|
|
1658
|
+
];
|
|
1659
|
+
const response = await this.config.llm.chat(messages);
|
|
1660
|
+
const action = this.parseAction(response.content);
|
|
1661
|
+
if (action && action.toLowerCase() !== "none") {
|
|
1662
|
+
await this.config.onAction(action);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
start() {
|
|
1666
|
+
this.timer = setInterval(() => {
|
|
1667
|
+
this.tick().catch((err2) => console.error("[augure] Heartbeat error:", err2));
|
|
1668
|
+
}, this.config.intervalMs);
|
|
1669
|
+
}
|
|
1670
|
+
stop() {
|
|
1671
|
+
if (this.timer) {
|
|
1672
|
+
clearInterval(this.timer);
|
|
1673
|
+
this.timer = void 0;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
parseAction(content) {
|
|
1677
|
+
const match = content.match(/ACTION:\s*(.+)/i);
|
|
1678
|
+
return match?.[1]?.trim();
|
|
1679
|
+
}
|
|
1680
|
+
async loadMemory() {
|
|
1681
|
+
try {
|
|
1682
|
+
const exists = await this.config.memory.exists("observations.md");
|
|
1683
|
+
if (exists) {
|
|
1684
|
+
return this.config.memory.read("observations.md");
|
|
1685
|
+
}
|
|
1686
|
+
} catch {
|
|
1687
|
+
}
|
|
1688
|
+
return "(no memory available)";
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
// ../scheduler/dist/interval.js
|
|
1693
|
+
var UNITS = {
|
|
1694
|
+
s: 1e3,
|
|
1695
|
+
m: 6e4,
|
|
1696
|
+
h: 36e5
|
|
1697
|
+
};
|
|
1698
|
+
function parseInterval(input) {
|
|
1699
|
+
const match = input.match(/^(\d+)([smh])$/);
|
|
1700
|
+
if (!match) {
|
|
1701
|
+
throw new Error(`Invalid interval format: "${input}". Expected: <number><s|m|h> (e.g. "30m")`);
|
|
1702
|
+
}
|
|
1703
|
+
const value = parseInt(match[1], 10);
|
|
1704
|
+
const unit = match[2];
|
|
1705
|
+
if (value <= 0) {
|
|
1706
|
+
throw new Error(`Interval must be positive, got: ${value}`);
|
|
1707
|
+
}
|
|
1708
|
+
return value * UNITS[unit];
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// ../skills/dist/manager.js
|
|
1712
|
+
import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir4, readdir as readdir3, rm, access as access2 } from "fs/promises";
|
|
1713
|
+
import { join as join4 } from "path";
|
|
1714
|
+
|
|
1715
|
+
// ../skills/dist/parser.js
|
|
1716
|
+
import matter2 from "gray-matter";
|
|
1717
|
+
function parseSkillMd(content) {
|
|
1718
|
+
const { data, content: body } = matter2(content);
|
|
1719
|
+
const meta = validateSkillMeta(data);
|
|
1720
|
+
return { meta, body: body.trim() };
|
|
1721
|
+
}
|
|
1722
|
+
function serializeSkillMd(meta, body) {
|
|
1723
|
+
return matter2.stringify(body, JSON.parse(JSON.stringify(meta)));
|
|
1724
|
+
}
|
|
1725
|
+
function validateSkillMeta(raw) {
|
|
1726
|
+
if (!raw.id || typeof raw.id !== "string")
|
|
1727
|
+
throw new Error("skill.md: missing or invalid 'id'");
|
|
1728
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(raw.id))
|
|
1729
|
+
throw new Error(`skill.md: invalid id format '${raw.id}' (must be lowercase slug)`);
|
|
1730
|
+
if (!raw.name || typeof raw.name !== "string")
|
|
1731
|
+
throw new Error("skill.md: missing or invalid 'name'");
|
|
1732
|
+
const version2 = typeof raw.version === "number" ? raw.version : 1;
|
|
1733
|
+
if (!Number.isInteger(version2) || version2 < 1)
|
|
1734
|
+
throw new Error("skill.md: 'version' must be a positive integer");
|
|
1735
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1736
|
+
const created = typeof raw.created === "string" ? raw.created : now;
|
|
1737
|
+
const updated = typeof raw.updated === "string" ? raw.updated : now;
|
|
1738
|
+
const status = typeof raw.status === "string" ? raw.status : "draft";
|
|
1739
|
+
const validStatuses = ["draft", "testing", "active", "paused", "broken"];
|
|
1740
|
+
if (!validStatuses.includes(status))
|
|
1741
|
+
throw new Error(`skill.md: invalid status '${status}'`);
|
|
1742
|
+
const trigger = raw.trigger;
|
|
1743
|
+
if (!trigger || typeof trigger !== "object")
|
|
1744
|
+
throw new Error("skill.md: missing 'trigger'");
|
|
1745
|
+
const triggerType = trigger.type;
|
|
1746
|
+
if (!["cron", "manual", "event"].includes(triggerType))
|
|
1747
|
+
throw new Error(`skill.md: invalid trigger.type '${triggerType}'`);
|
|
1748
|
+
if (triggerType === "cron" && (!trigger.schedule || typeof trigger.schedule !== "string")) {
|
|
1749
|
+
throw new Error("skill.md: cron trigger requires 'schedule'");
|
|
1750
|
+
}
|
|
1751
|
+
const sandbox = typeof raw.sandbox === "boolean" ? raw.sandbox : true;
|
|
1752
|
+
const tools = Array.isArray(raw.tools) ? raw.tools : [];
|
|
1753
|
+
const tags = Array.isArray(raw.tags) ? raw.tags : [];
|
|
1754
|
+
return {
|
|
1755
|
+
id: raw.id,
|
|
1756
|
+
name: raw.name,
|
|
1757
|
+
version: version2,
|
|
1758
|
+
created,
|
|
1759
|
+
updated,
|
|
1760
|
+
status,
|
|
1761
|
+
trigger: {
|
|
1762
|
+
type: triggerType,
|
|
1763
|
+
schedule: trigger.schedule,
|
|
1764
|
+
channel: trigger.channel
|
|
1765
|
+
},
|
|
1766
|
+
sandbox,
|
|
1767
|
+
tools,
|
|
1768
|
+
tags
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// ../skills/dist/manager.js
|
|
1773
|
+
var SkillManager = class {
|
|
1774
|
+
basePath;
|
|
1775
|
+
constructor(basePath) {
|
|
1776
|
+
this.basePath = basePath;
|
|
1777
|
+
}
|
|
1778
|
+
/** List all skills from the index (rebuilds if missing) */
|
|
1779
|
+
async list() {
|
|
1780
|
+
try {
|
|
1781
|
+
const index = await this.readIndex();
|
|
1782
|
+
return index.skills;
|
|
1783
|
+
} catch {
|
|
1784
|
+
const index = await this.rebuildIndex();
|
|
1785
|
+
return index.skills;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
/** Load a single skill by ID */
|
|
1789
|
+
async get(id) {
|
|
1790
|
+
const dir = join4(this.basePath, id);
|
|
1791
|
+
const mdContent = await readFile5(join4(dir, "skill.md"), "utf-8");
|
|
1792
|
+
const { meta, body } = parseSkillMd(mdContent);
|
|
1793
|
+
let code;
|
|
1794
|
+
try {
|
|
1795
|
+
code = await readFile5(join4(dir, "skill.ts"), "utf-8");
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
1798
|
+
let testCode;
|
|
1799
|
+
try {
|
|
1800
|
+
testCode = await readFile5(join4(dir, "skill.test.ts"), "utf-8");
|
|
1801
|
+
} catch {
|
|
1802
|
+
}
|
|
1803
|
+
return { meta, body, code, testCode };
|
|
1804
|
+
}
|
|
1805
|
+
/** Save a skill to disk (creates directory, writes files, updates index) */
|
|
1806
|
+
async save(skill) {
|
|
1807
|
+
const dir = join4(this.basePath, skill.meta.id);
|
|
1808
|
+
await mkdir4(dir, { recursive: true });
|
|
1809
|
+
const mdContent = serializeSkillMd(skill.meta, skill.body);
|
|
1810
|
+
await writeFile3(join4(dir, "skill.md"), mdContent, "utf-8");
|
|
1811
|
+
if (skill.code !== void 0) {
|
|
1812
|
+
await writeFile3(join4(dir, "skill.ts"), skill.code, "utf-8");
|
|
1813
|
+
}
|
|
1814
|
+
if (skill.testCode !== void 0) {
|
|
1815
|
+
await writeFile3(join4(dir, "skill.test.ts"), skill.testCode, "utf-8");
|
|
1816
|
+
}
|
|
1817
|
+
await this.updateIndex(skill.meta);
|
|
1818
|
+
}
|
|
1819
|
+
/** Delete a skill directory and remove from index */
|
|
1820
|
+
async delete(id) {
|
|
1821
|
+
const dir = join4(this.basePath, id);
|
|
1822
|
+
await rm(dir, { recursive: true, force: true });
|
|
1823
|
+
await this.removeFromIndex(id);
|
|
1824
|
+
}
|
|
1825
|
+
/** Update just the status of a skill */
|
|
1826
|
+
async updateStatus(id, status) {
|
|
1827
|
+
const skill = await this.get(id);
|
|
1828
|
+
skill.meta.status = status;
|
|
1829
|
+
skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1830
|
+
await this.save(skill);
|
|
1831
|
+
}
|
|
1832
|
+
/** Bump version number, returns the new version */
|
|
1833
|
+
async bumpVersion(id) {
|
|
1834
|
+
const skill = await this.get(id);
|
|
1835
|
+
skill.meta.version += 1;
|
|
1836
|
+
skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1837
|
+
await this.save(skill);
|
|
1838
|
+
return skill.meta.version;
|
|
1839
|
+
}
|
|
1840
|
+
/** Save a run result to runs/<timestamp>.json */
|
|
1841
|
+
async saveRun(result) {
|
|
1842
|
+
const runsDir = join4(this.basePath, result.skillId, "runs");
|
|
1843
|
+
await mkdir4(runsDir, { recursive: true });
|
|
1844
|
+
const filename = `${result.timestamp.replace(/[:.]/g, "-")}.json`;
|
|
1845
|
+
await writeFile3(join4(runsDir, filename), JSON.stringify(result, null, 2), "utf-8");
|
|
1846
|
+
}
|
|
1847
|
+
/** Load recent run results for a skill, sorted newest first */
|
|
1848
|
+
async getRuns(id, limit = 10) {
|
|
1849
|
+
const runsDir = join4(this.basePath, id, "runs");
|
|
1850
|
+
let files;
|
|
1851
|
+
try {
|
|
1852
|
+
files = await readdir3(runsDir);
|
|
1853
|
+
} catch {
|
|
1854
|
+
return [];
|
|
1855
|
+
}
|
|
1856
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
|
|
1857
|
+
const results = [];
|
|
1858
|
+
for (const file of jsonFiles.slice(0, limit)) {
|
|
1859
|
+
try {
|
|
1860
|
+
const raw = await readFile5(join4(runsDir, file), "utf-8");
|
|
1861
|
+
results.push(JSON.parse(raw));
|
|
1862
|
+
} catch {
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return results;
|
|
1866
|
+
}
|
|
1867
|
+
/** Get the most recent run result */
|
|
1868
|
+
async getLastRun(id) {
|
|
1869
|
+
const runs = await this.getRuns(id, 1);
|
|
1870
|
+
return runs[0] ?? null;
|
|
1871
|
+
}
|
|
1872
|
+
/** Rebuild skills-index.json by scanning all skill directories */
|
|
1873
|
+
async rebuildIndex() {
|
|
1874
|
+
await mkdir4(this.basePath, { recursive: true });
|
|
1875
|
+
let entries;
|
|
1876
|
+
try {
|
|
1877
|
+
entries = await readdir3(this.basePath, { withFileTypes: true }).then((e) => e.filter((d) => d.isDirectory()).map((d) => d.name));
|
|
1878
|
+
} catch {
|
|
1879
|
+
entries = [];
|
|
1880
|
+
}
|
|
1881
|
+
const skills = [];
|
|
1882
|
+
for (const dir of entries) {
|
|
1883
|
+
try {
|
|
1884
|
+
const mdContent = await readFile5(join4(this.basePath, dir, "skill.md"), "utf-8");
|
|
1885
|
+
const { meta } = parseSkillMd(mdContent);
|
|
1886
|
+
skills.push(metaToIndexEntry(meta));
|
|
1887
|
+
} catch {
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const index = { version: 1, skills };
|
|
1891
|
+
await this.writeIndex(index);
|
|
1892
|
+
return index;
|
|
1893
|
+
}
|
|
1894
|
+
/** Check if a skill exists on disk */
|
|
1895
|
+
async exists(id) {
|
|
1896
|
+
try {
|
|
1897
|
+
await access2(join4(this.basePath, id, "skill.md"));
|
|
1898
|
+
return true;
|
|
1899
|
+
} catch {
|
|
1900
|
+
return false;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async readIndex() {
|
|
1904
|
+
const raw = await readFile5(join4(this.basePath, "skills-index.json"), "utf-8");
|
|
1905
|
+
return JSON.parse(raw);
|
|
1906
|
+
}
|
|
1907
|
+
async writeIndex(index) {
|
|
1908
|
+
await mkdir4(this.basePath, { recursive: true });
|
|
1909
|
+
await writeFile3(join4(this.basePath, "skills-index.json"), JSON.stringify(index, null, 2), "utf-8");
|
|
1910
|
+
}
|
|
1911
|
+
async updateIndex(meta) {
|
|
1912
|
+
let index;
|
|
1913
|
+
try {
|
|
1914
|
+
index = await this.readIndex();
|
|
1915
|
+
} catch {
|
|
1916
|
+
index = { version: 1, skills: [] };
|
|
1917
|
+
}
|
|
1918
|
+
const entry = metaToIndexEntry(meta);
|
|
1919
|
+
const idx = index.skills.findIndex((s) => s.id === meta.id);
|
|
1920
|
+
if (idx >= 0) {
|
|
1921
|
+
index.skills[idx] = entry;
|
|
1922
|
+
} else {
|
|
1923
|
+
index.skills.push(entry);
|
|
1924
|
+
}
|
|
1925
|
+
await this.writeIndex(index);
|
|
1926
|
+
}
|
|
1927
|
+
async removeFromIndex(id) {
|
|
1928
|
+
let index;
|
|
1929
|
+
try {
|
|
1930
|
+
index = await this.readIndex();
|
|
1931
|
+
} catch {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
index.skills = index.skills.filter((s) => s.id !== id);
|
|
1935
|
+
await this.writeIndex(index);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
function metaToIndexEntry(meta) {
|
|
1939
|
+
return {
|
|
1940
|
+
id: meta.id,
|
|
1941
|
+
name: meta.name,
|
|
1942
|
+
version: meta.version,
|
|
1943
|
+
status: meta.status,
|
|
1944
|
+
trigger: meta.trigger,
|
|
1945
|
+
tags: meta.tags,
|
|
1946
|
+
updated: meta.updated
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// ../skills/dist/llm-parser.js
|
|
1951
|
+
function parseSkillResponse(content) {
|
|
1952
|
+
const namedResult = parseNamedFences(content);
|
|
1953
|
+
if (namedResult)
|
|
1954
|
+
return namedResult;
|
|
1955
|
+
const sectionResult = parseSectionHeaders(content);
|
|
1956
|
+
if (sectionResult)
|
|
1957
|
+
return sectionResult;
|
|
1958
|
+
const sequentialResult = parseSequentialBlocks(content);
|
|
1959
|
+
if (sequentialResult)
|
|
1960
|
+
return sequentialResult;
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
function parseNamedFences(content) {
|
|
1964
|
+
const fencePattern = /```\w*\s+(?:filename=)?(\S+)\s*\n([\s\S]*?)```/g;
|
|
1965
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
1966
|
+
let match;
|
|
1967
|
+
while ((match = fencePattern.exec(content)) !== null) {
|
|
1968
|
+
blocks.set(match[1], match[2].trim());
|
|
1969
|
+
}
|
|
1970
|
+
const skillMd = blocks.get("skill.md");
|
|
1971
|
+
const skillTs = blocks.get("skill.ts");
|
|
1972
|
+
const skillTestTs = blocks.get("skill.test.ts");
|
|
1973
|
+
if (skillMd && skillTs && skillTestTs) {
|
|
1974
|
+
return { skillMd, skillTs, skillTestTs };
|
|
1975
|
+
}
|
|
1976
|
+
return null;
|
|
1977
|
+
}
|
|
1978
|
+
function parseSectionHeaders(content) {
|
|
1979
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1980
|
+
const sectionPattern = /#{2,3}\s+(skill\.(?:md|ts|test\.ts))\s*\n+```\w*\n([\s\S]*?)```/g;
|
|
1981
|
+
let match;
|
|
1982
|
+
while ((match = sectionPattern.exec(content)) !== null) {
|
|
1983
|
+
sections.set(match[1], match[2].trim());
|
|
1984
|
+
}
|
|
1985
|
+
const skillMd = sections.get("skill.md");
|
|
1986
|
+
const skillTs = sections.get("skill.ts");
|
|
1987
|
+
const skillTestTs = sections.get("skill.test.ts");
|
|
1988
|
+
if (skillMd && skillTs && skillTestTs) {
|
|
1989
|
+
return { skillMd, skillTs, skillTestTs };
|
|
1990
|
+
}
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
function parseSequentialBlocks(content) {
|
|
1994
|
+
const blockPattern = /```\w*\n([\s\S]*?)```/g;
|
|
1995
|
+
const blocks = [];
|
|
1996
|
+
let match;
|
|
1997
|
+
while ((match = blockPattern.exec(content)) !== null) {
|
|
1998
|
+
blocks.push(match[1].trim());
|
|
1999
|
+
}
|
|
2000
|
+
if (blocks.length >= 3) {
|
|
2001
|
+
return {
|
|
2002
|
+
skillMd: blocks[0],
|
|
2003
|
+
skillTs: blocks[1],
|
|
2004
|
+
skillTestTs: blocks[2]
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
return null;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// ../skills/dist/generator.js
|
|
2011
|
+
var GENERATION_SYSTEM_PROMPT = `You are a skill generator for the Augure AI agent. When given a description of a task, you generate three files that implement it.
|
|
2012
|
+
|
|
2013
|
+
## Output Format
|
|
2014
|
+
|
|
2015
|
+
You MUST output exactly three fenced code blocks with filenames:
|
|
2016
|
+
|
|
2017
|
+
\`\`\`yaml filename=skill.md
|
|
2018
|
+
---
|
|
2019
|
+
id: skill-id-here
|
|
2020
|
+
name: Human Readable Name
|
|
2021
|
+
version: 1
|
|
2022
|
+
status: draft
|
|
2023
|
+
trigger:
|
|
2024
|
+
type: TRIGGER_TYPE
|
|
2025
|
+
schedule: "CRON_EXPRESSION" # only if trigger type is cron
|
|
2026
|
+
channel: CHANNEL # optional
|
|
2027
|
+
sandbox: true
|
|
2028
|
+
tools: []
|
|
2029
|
+
tags: []
|
|
2030
|
+
---
|
|
2031
|
+
|
|
2032
|
+
# Skill Name
|
|
2033
|
+
|
|
2034
|
+
## Goal
|
|
2035
|
+
What this skill does.
|
|
2036
|
+
|
|
2037
|
+
## Strategy
|
|
2038
|
+
Step by step approach.
|
|
2039
|
+
\`\`\`
|
|
2040
|
+
|
|
2041
|
+
\`\`\`typescript filename=skill.ts
|
|
2042
|
+
import type { SkillContext } from "@augure/types";
|
|
2043
|
+
|
|
2044
|
+
export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
|
|
2045
|
+
// Implementation here
|
|
2046
|
+
return { output: "result" };
|
|
2047
|
+
}
|
|
2048
|
+
\`\`\`
|
|
2049
|
+
|
|
2050
|
+
\`\`\`typescript filename=skill.test.ts
|
|
2051
|
+
import { describe, it } from "node:test";
|
|
2052
|
+
import assert from "node:assert/strict";
|
|
2053
|
+
|
|
2054
|
+
describe("skill-id", () => {
|
|
2055
|
+
it("should produce expected output", async () => {
|
|
2056
|
+
// Test the skill logic
|
|
2057
|
+
assert.ok(true);
|
|
2058
|
+
});
|
|
2059
|
+
});
|
|
2060
|
+
\`\`\`
|
|
2061
|
+
|
|
2062
|
+
## Rules
|
|
2063
|
+
- The skill ID must be a lowercase slug (e.g., "daily-digest", "price-alert")
|
|
2064
|
+
- Tests MUST use node:test and node:assert (NOT vitest or jest)
|
|
2065
|
+
- The skill.ts default export must be an async function taking SkillContext
|
|
2066
|
+
- Keep the implementation focused and minimal`;
|
|
2067
|
+
var REGENERATION_SYSTEM_PROMPT = `You are a skill code fixer for the Augure AI agent. A skill failed to execute. You must fix the code.
|
|
2068
|
+
|
|
2069
|
+
## Rules
|
|
2070
|
+
- Output exactly two fenced code blocks: skill.ts and skill.test.ts
|
|
2071
|
+
- Keep the same function signature and approach
|
|
2072
|
+
- Fix the specific error described
|
|
2073
|
+
- Tests use node:test and node:assert (NOT vitest or jest)
|
|
2074
|
+
|
|
2075
|
+
Output format:
|
|
2076
|
+
\`\`\`typescript filename=skill.ts
|
|
2077
|
+
// fixed code
|
|
2078
|
+
\`\`\`
|
|
2079
|
+
|
|
2080
|
+
\`\`\`typescript filename=skill.test.ts
|
|
2081
|
+
// fixed test
|
|
2082
|
+
\`\`\``;
|
|
2083
|
+
function slugify(text) {
|
|
2084
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
2085
|
+
}
|
|
2086
|
+
var SkillGenerator = class {
|
|
2087
|
+
llm;
|
|
2088
|
+
constructor(llm) {
|
|
2089
|
+
this.llm = llm;
|
|
2090
|
+
}
|
|
2091
|
+
async generate(request) {
|
|
2092
|
+
const id = slugify(request.description);
|
|
2093
|
+
if (!id) {
|
|
2094
|
+
return { success: false, error: "Could not generate a valid skill ID from description" };
|
|
2095
|
+
}
|
|
2096
|
+
const userPrompt = [
|
|
2097
|
+
`Create a skill with the following specification:`,
|
|
2098
|
+
`- ID: ${id}`,
|
|
2099
|
+
`- Description: ${request.description}`,
|
|
2100
|
+
`- Trigger type: ${request.trigger.type}`,
|
|
2101
|
+
request.trigger.schedule ? `- Schedule: ${request.trigger.schedule}` : null,
|
|
2102
|
+
request.trigger.channel ? `- Channel: ${request.trigger.channel}` : null,
|
|
2103
|
+
request.tags?.length ? `- Tags: ${request.tags.join(", ")}` : null,
|
|
2104
|
+
`- Sandbox: ${request.sandbox !== false}`
|
|
2105
|
+
].filter(Boolean).join("\n");
|
|
2106
|
+
try {
|
|
2107
|
+
const response = await this.llm.chat([
|
|
2108
|
+
{ role: "system", content: GENERATION_SYSTEM_PROMPT },
|
|
2109
|
+
{ role: "user", content: userPrompt }
|
|
2110
|
+
]);
|
|
2111
|
+
const parsed = parseSkillResponse(response.content);
|
|
2112
|
+
if (!parsed) {
|
|
2113
|
+
return { success: false, error: "Failed to parse LLM response: missing code blocks" };
|
|
2114
|
+
}
|
|
2115
|
+
const { meta, body } = parseSkillMd(parsed.skillMd);
|
|
2116
|
+
meta.id = id;
|
|
2117
|
+
const skill = {
|
|
2118
|
+
meta,
|
|
2119
|
+
body,
|
|
2120
|
+
code: parsed.skillTs,
|
|
2121
|
+
testCode: parsed.skillTestTs
|
|
2122
|
+
};
|
|
2123
|
+
return { success: true, skill };
|
|
2124
|
+
} catch (err2) {
|
|
2125
|
+
return {
|
|
2126
|
+
success: false,
|
|
2127
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
async regenerateCode(skill, error) {
|
|
2132
|
+
const userPrompt = [
|
|
2133
|
+
`## Skill: ${skill.meta.name} (${skill.meta.id})`,
|
|
2134
|
+
``,
|
|
2135
|
+
`## Description`,
|
|
2136
|
+
skill.body,
|
|
2137
|
+
``,
|
|
2138
|
+
`## Current code (skill.ts)`,
|
|
2139
|
+
"```typescript",
|
|
2140
|
+
skill.code ?? "// no code yet",
|
|
2141
|
+
"```",
|
|
2142
|
+
``,
|
|
2143
|
+
`## Error`,
|
|
2144
|
+
"```",
|
|
2145
|
+
error,
|
|
2146
|
+
"```",
|
|
2147
|
+
``,
|
|
2148
|
+
`Fix the code to resolve this error.`
|
|
2149
|
+
].join("\n");
|
|
2150
|
+
try {
|
|
2151
|
+
const response = await this.llm.chat([
|
|
2152
|
+
{ role: "system", content: REGENERATION_SYSTEM_PROMPT },
|
|
2153
|
+
{ role: "user", content: userPrompt }
|
|
2154
|
+
]);
|
|
2155
|
+
const blockPattern = /```\w*(?:\s+\S+)?\s*\n([\s\S]*?)```/g;
|
|
2156
|
+
const blocks = [];
|
|
2157
|
+
let match;
|
|
2158
|
+
while ((match = blockPattern.exec(response.content)) !== null) {
|
|
2159
|
+
blocks.push(match[1].trim());
|
|
2160
|
+
}
|
|
2161
|
+
if (blocks.length < 2)
|
|
2162
|
+
return null;
|
|
2163
|
+
return { code: blocks[0], testCode: blocks[1] };
|
|
2164
|
+
} catch {
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
|
|
2170
|
+
// ../skills/dist/runner.js
|
|
2171
|
+
var HARNESS_TEMPLATE = `
|
|
2172
|
+
import skill from "./skill.ts";
|
|
2173
|
+
import { readFile, readdir, writeFile, mkdir } from "node:fs/promises";
|
|
2174
|
+
import { join } from "node:path";
|
|
2175
|
+
|
|
2176
|
+
// Load injected config from separate JSON file (avoids template injection issues)
|
|
2177
|
+
const __injected = JSON.parse(await readFile("/workspace/__config.json", "utf-8"));
|
|
2178
|
+
|
|
2179
|
+
const ctx = {
|
|
2180
|
+
exec: async (command, opts) => {
|
|
2181
|
+
const { execSync } = await import("node:child_process");
|
|
2182
|
+
try {
|
|
2183
|
+
const stdout = execSync(command, {
|
|
2184
|
+
encoding: "utf-8",
|
|
2185
|
+
timeout: (opts?.timeout ?? 30) * 1000,
|
|
2186
|
+
env: { ...process.env, ...(opts?.env ?? {}) },
|
|
2187
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2188
|
+
});
|
|
2189
|
+
return { exitCode: 0, stdout, stderr: "" };
|
|
2190
|
+
} catch (err) {
|
|
2191
|
+
return {
|
|
2192
|
+
exitCode: err.status ?? 1,
|
|
2193
|
+
stdout: err.stdout ?? "",
|
|
2194
|
+
stderr: err.stderr ?? err.message,
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
memory: {
|
|
2199
|
+
read: async (path) => readFile(join("/memory", path), "utf-8"),
|
|
2200
|
+
list: async (dir) => {
|
|
2201
|
+
try {
|
|
2202
|
+
return await readdir(join("/memory", dir ?? ""));
|
|
2203
|
+
} catch {
|
|
2204
|
+
return [];
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
},
|
|
2208
|
+
state: {
|
|
2209
|
+
_data: {},
|
|
2210
|
+
_loaded: false,
|
|
2211
|
+
_load: async function() {
|
|
2212
|
+
if (this._loaded) return;
|
|
2213
|
+
try { this._data = JSON.parse(await readFile("/state/state.json", "utf-8")); } catch { this._data = {}; }
|
|
2214
|
+
this._loaded = true;
|
|
2215
|
+
},
|
|
2216
|
+
get: async function(key) { await this._load(); return this._data[key]; },
|
|
2217
|
+
set: async function(key, value) {
|
|
2218
|
+
await this._load();
|
|
2219
|
+
this._data[key] = value;
|
|
2220
|
+
await mkdir("/state", { recursive: true });
|
|
2221
|
+
await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
|
|
2222
|
+
},
|
|
2223
|
+
delete: async function(key) {
|
|
2224
|
+
await this._load();
|
|
2225
|
+
delete this._data[key];
|
|
2226
|
+
await mkdir("/state", { recursive: true });
|
|
2227
|
+
await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
|
|
2228
|
+
},
|
|
2229
|
+
},
|
|
2230
|
+
previousRun: __injected.previousRun,
|
|
2231
|
+
config: __injected.config,
|
|
2232
|
+
};
|
|
2233
|
+
|
|
2234
|
+
try {
|
|
2235
|
+
const result = await skill(ctx);
|
|
2236
|
+
console.log(JSON.stringify({ success: true, output: result?.output ?? "" }));
|
|
2237
|
+
} catch (err) {
|
|
2238
|
+
console.log(JSON.stringify({ success: false, error: err.message ?? String(err) }));
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
`;
|
|
2242
|
+
var SkillRunner = class {
|
|
2243
|
+
config;
|
|
2244
|
+
constructor(config) {
|
|
2245
|
+
this.config = config;
|
|
2246
|
+
}
|
|
2247
|
+
async run(skillId) {
|
|
2248
|
+
const start = Date.now();
|
|
2249
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2250
|
+
const skill = await this.config.manager.get(skillId);
|
|
2251
|
+
if (!skill.code) {
|
|
2252
|
+
const result = {
|
|
2253
|
+
skillId,
|
|
2254
|
+
timestamp,
|
|
2255
|
+
success: false,
|
|
2256
|
+
error: "Skill has no code (skill.ts)",
|
|
2257
|
+
durationMs: Date.now() - start
|
|
2258
|
+
};
|
|
2259
|
+
await this.config.manager.saveRun(result);
|
|
2260
|
+
return result;
|
|
2261
|
+
}
|
|
2262
|
+
let container;
|
|
2263
|
+
try {
|
|
2264
|
+
container = await this.config.pool.acquire({
|
|
2265
|
+
trust: skill.meta.sandbox ? "sandboxed" : "trusted",
|
|
2266
|
+
timeout: this.config.defaults.timeout,
|
|
2267
|
+
memory: this.config.defaults.memoryLimit,
|
|
2268
|
+
cpu: this.config.defaults.cpuLimit
|
|
2269
|
+
});
|
|
2270
|
+
} catch (err2) {
|
|
2271
|
+
const result = {
|
|
2272
|
+
skillId,
|
|
2273
|
+
timestamp,
|
|
2274
|
+
success: false,
|
|
2275
|
+
error: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
2276
|
+
durationMs: Date.now() - start
|
|
2277
|
+
};
|
|
2278
|
+
await this.config.manager.saveRun(result);
|
|
2279
|
+
return result;
|
|
2280
|
+
}
|
|
2281
|
+
try {
|
|
2282
|
+
await container.exec("mkdir -p /workspace");
|
|
2283
|
+
const codeB64 = Buffer.from(skill.code).toString("base64");
|
|
2284
|
+
await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
|
|
2285
|
+
const previousRun = await this.config.manager.getLastRun(skillId);
|
|
2286
|
+
const configData = JSON.stringify({ previousRun, config: skill.meta });
|
|
2287
|
+
const configB64 = Buffer.from(configData).toString("base64");
|
|
2288
|
+
await container.exec(`sh -c 'echo "${configB64}" | base64 -d > /workspace/__config.json'`);
|
|
2289
|
+
const harnessB64 = Buffer.from(HARNESS_TEMPLATE).toString("base64");
|
|
2290
|
+
await container.exec(`sh -c 'echo "${harnessB64}" | base64 -d > /workspace/harness.ts'`);
|
|
2291
|
+
const execResult = await container.exec("npx tsx /workspace/harness.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
|
|
2292
|
+
let success = false;
|
|
2293
|
+
let output = "";
|
|
2294
|
+
let error;
|
|
2295
|
+
if (execResult.exitCode === 0 && execResult.stdout.trim()) {
|
|
2296
|
+
try {
|
|
2297
|
+
const parsed = JSON.parse(execResult.stdout.trim().split("\n").pop());
|
|
2298
|
+
success = parsed.success === true;
|
|
2299
|
+
output = parsed.output ?? "";
|
|
2300
|
+
error = parsed.error;
|
|
2301
|
+
} catch {
|
|
2302
|
+
output = execResult.stdout;
|
|
2303
|
+
success = true;
|
|
2304
|
+
}
|
|
2305
|
+
} else {
|
|
2306
|
+
success = false;
|
|
2307
|
+
error = execResult.stderr || execResult.stdout || "Unknown error";
|
|
2308
|
+
}
|
|
2309
|
+
const result = {
|
|
2310
|
+
skillId,
|
|
2311
|
+
timestamp,
|
|
2312
|
+
success,
|
|
2313
|
+
output: output || void 0,
|
|
2314
|
+
error,
|
|
2315
|
+
durationMs: Date.now() - start
|
|
2316
|
+
};
|
|
2317
|
+
await this.config.manager.saveRun(result);
|
|
2318
|
+
return result;
|
|
2319
|
+
} catch (err2) {
|
|
2320
|
+
const result = {
|
|
2321
|
+
skillId,
|
|
2322
|
+
timestamp,
|
|
2323
|
+
success: false,
|
|
2324
|
+
error: err2 instanceof Error ? err2.message : String(err2),
|
|
2325
|
+
durationMs: Date.now() - start
|
|
2326
|
+
};
|
|
2327
|
+
await this.config.manager.saveRun(result);
|
|
2328
|
+
return result;
|
|
2329
|
+
} finally {
|
|
2330
|
+
await this.config.pool.release(container);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
|
|
2335
|
+
// ../skills/dist/tester.js
|
|
2336
|
+
var SkillTester = class {
|
|
2337
|
+
config;
|
|
2338
|
+
constructor(config) {
|
|
2339
|
+
this.config = config;
|
|
2340
|
+
}
|
|
2341
|
+
async test(skill) {
|
|
2342
|
+
if (!skill.testCode) {
|
|
2343
|
+
return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no test code" };
|
|
2344
|
+
}
|
|
2345
|
+
if (!skill.code) {
|
|
2346
|
+
return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no code" };
|
|
2347
|
+
}
|
|
2348
|
+
let container;
|
|
2349
|
+
try {
|
|
2350
|
+
container = await this.config.pool.acquire({
|
|
2351
|
+
trust: "sandboxed",
|
|
2352
|
+
timeout: this.config.defaults.timeout,
|
|
2353
|
+
memory: this.config.defaults.memoryLimit,
|
|
2354
|
+
cpu: this.config.defaults.cpuLimit
|
|
2355
|
+
});
|
|
2356
|
+
} catch (err2) {
|
|
2357
|
+
return {
|
|
2358
|
+
success: false,
|
|
2359
|
+
passed: 0,
|
|
2360
|
+
failed: 0,
|
|
2361
|
+
output: "",
|
|
2362
|
+
error: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
try {
|
|
2366
|
+
await container.exec("mkdir -p /workspace");
|
|
2367
|
+
const codeB64 = Buffer.from(skill.code).toString("base64");
|
|
2368
|
+
await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
|
|
2369
|
+
const testB64 = Buffer.from(skill.testCode).toString("base64");
|
|
2370
|
+
await container.exec(`sh -c 'echo "${testB64}" | base64 -d > /workspace/skill.test.ts'`);
|
|
2371
|
+
const result = await container.exec("npx tsx --test --test-reporter=tap /workspace/skill.test.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
|
|
2372
|
+
const { passed, failed } = parseTestOutput(result.stdout + result.stderr);
|
|
2373
|
+
const success = result.exitCode === 0 && failed === 0;
|
|
2374
|
+
return {
|
|
2375
|
+
success,
|
|
2376
|
+
passed,
|
|
2377
|
+
failed,
|
|
2378
|
+
output: result.stdout,
|
|
2379
|
+
error: success ? void 0 : result.stderr || `Exit code: ${result.exitCode}`
|
|
2380
|
+
};
|
|
2381
|
+
} catch (err2) {
|
|
2382
|
+
return {
|
|
2383
|
+
success: false,
|
|
2384
|
+
passed: 0,
|
|
2385
|
+
failed: 0,
|
|
2386
|
+
output: "",
|
|
2387
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
2388
|
+
};
|
|
2389
|
+
} finally {
|
|
2390
|
+
await this.config.pool.release(container);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
};
|
|
2394
|
+
function parseTestOutput(output) {
|
|
2395
|
+
const passMatch = output.match(/# pass (\d+)/);
|
|
2396
|
+
const failMatch = output.match(/# fail (\d+)/);
|
|
2397
|
+
return {
|
|
2398
|
+
passed: passMatch ? parseInt(passMatch[1], 10) : 0,
|
|
2399
|
+
failed: failMatch ? parseInt(failMatch[1], 10) : 0
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// ../skills/dist/state.js
|
|
2404
|
+
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
|
|
2405
|
+
import { dirname as dirname3 } from "path";
|
|
2406
|
+
var FileSkillState = class {
|
|
2407
|
+
filePath;
|
|
2408
|
+
data = {};
|
|
2409
|
+
loaded = false;
|
|
2410
|
+
constructor(filePath) {
|
|
2411
|
+
this.filePath = filePath;
|
|
2412
|
+
}
|
|
2413
|
+
async get(key) {
|
|
2414
|
+
await this.ensureLoaded();
|
|
2415
|
+
return this.data[key];
|
|
2416
|
+
}
|
|
2417
|
+
async set(key, value) {
|
|
2418
|
+
await this.ensureLoaded();
|
|
2419
|
+
this.data[key] = value;
|
|
2420
|
+
await this.persist();
|
|
2421
|
+
}
|
|
2422
|
+
async delete(key) {
|
|
2423
|
+
await this.ensureLoaded();
|
|
2424
|
+
delete this.data[key];
|
|
2425
|
+
await this.persist();
|
|
2426
|
+
}
|
|
2427
|
+
async ensureLoaded() {
|
|
2428
|
+
if (this.loaded)
|
|
2429
|
+
return;
|
|
2430
|
+
try {
|
|
2431
|
+
const raw = await readFile6(this.filePath, "utf-8");
|
|
2432
|
+
this.data = JSON.parse(raw);
|
|
2433
|
+
} catch {
|
|
2434
|
+
this.data = {};
|
|
2435
|
+
}
|
|
2436
|
+
this.loaded = true;
|
|
2437
|
+
}
|
|
2438
|
+
async persist() {
|
|
2439
|
+
await mkdir5(dirname3(this.filePath), { recursive: true });
|
|
2440
|
+
await writeFile4(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
|
|
2444
|
+
// ../skills/dist/healer.js
|
|
2445
|
+
import { join as join5 } from "path";
|
|
2446
|
+
var SkillHealer = class {
|
|
2447
|
+
config;
|
|
2448
|
+
stateCache = /* @__PURE__ */ new Map();
|
|
2449
|
+
constructor(config) {
|
|
2450
|
+
this.config = config;
|
|
2451
|
+
}
|
|
2452
|
+
/** Called after each skill run to check for failures and trigger healing */
|
|
2453
|
+
async onRunComplete(result) {
|
|
2454
|
+
if (result.success) {
|
|
2455
|
+
const state2 = this.getState(result.skillId);
|
|
2456
|
+
await state2.set("consecutive-failures", "0");
|
|
2457
|
+
return { healed: false, paused: false };
|
|
2458
|
+
}
|
|
2459
|
+
const state = this.getState(result.skillId);
|
|
2460
|
+
const raw = await state.get("consecutive-failures");
|
|
2461
|
+
const failures = (raw ? parseInt(raw, 10) : 0) + 1;
|
|
2462
|
+
await state.set("consecutive-failures", String(failures));
|
|
2463
|
+
if (failures >= this.config.maxAttempts) {
|
|
2464
|
+
await this.config.manager.updateStatus(result.skillId, "paused");
|
|
2465
|
+
return { healed: false, paused: true, error: `Paused after ${failures} consecutive failures` };
|
|
2466
|
+
}
|
|
2467
|
+
return this.heal(result.skillId, result.error ?? "Unknown error");
|
|
2468
|
+
}
|
|
2469
|
+
/** Attempt to heal a broken skill */
|
|
2470
|
+
async heal(skillId, error) {
|
|
2471
|
+
const skill = await this.config.manager.get(skillId);
|
|
2472
|
+
const healError = error ?? (await this.config.manager.getLastRun(skillId))?.error ?? "Unknown error";
|
|
2473
|
+
const fixed = await this.config.generator.regenerateCode(skill, healError);
|
|
2474
|
+
if (!fixed) {
|
|
2475
|
+
await this.config.manager.updateStatus(skillId, "broken");
|
|
2476
|
+
return { healed: false, paused: false, error: "LLM could not generate a fix" };
|
|
2477
|
+
}
|
|
2478
|
+
skill.code = fixed.code;
|
|
2479
|
+
skill.testCode = fixed.testCode;
|
|
2480
|
+
const testResult = await this.config.tester.test(skill);
|
|
2481
|
+
if (!testResult.success) {
|
|
2482
|
+
await this.config.manager.updateStatus(skillId, "broken");
|
|
2483
|
+
return { healed: false, paused: false, error: `Fix failed tests: ${testResult.error}` };
|
|
2484
|
+
}
|
|
2485
|
+
await this.config.manager.save(skill);
|
|
2486
|
+
await this.config.manager.bumpVersion(skillId);
|
|
2487
|
+
await this.config.manager.updateStatus(skillId, "active");
|
|
2488
|
+
const state = this.getState(skillId);
|
|
2489
|
+
await state.set("consecutive-failures", "0");
|
|
2490
|
+
return { healed: true, paused: false };
|
|
2491
|
+
}
|
|
2492
|
+
/** Check if a skill needs healing based on recent runs */
|
|
2493
|
+
async needsHealing(skillId) {
|
|
2494
|
+
const lastRun = await this.config.manager.getLastRun(skillId);
|
|
2495
|
+
if (!lastRun)
|
|
2496
|
+
return false;
|
|
2497
|
+
return !lastRun.success;
|
|
2498
|
+
}
|
|
2499
|
+
getState(skillId) {
|
|
2500
|
+
let state = this.stateCache.get(skillId);
|
|
2501
|
+
if (!state) {
|
|
2502
|
+
state = new FileSkillState(join5(this.config.skillsPath, skillId, "state.json"));
|
|
2503
|
+
this.stateCache.set(skillId, state);
|
|
2504
|
+
}
|
|
2505
|
+
return state;
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2509
|
+
// ../skills/dist/scheduler-bridge.js
|
|
2510
|
+
var JOB_PREFIX = "skill:";
|
|
2511
|
+
var SkillSchedulerBridge = class {
|
|
2512
|
+
scheduler;
|
|
2513
|
+
manager;
|
|
2514
|
+
constructor(scheduler, manager) {
|
|
2515
|
+
this.scheduler = scheduler;
|
|
2516
|
+
this.manager = manager;
|
|
2517
|
+
}
|
|
2518
|
+
/** Register cron jobs for all active cron-triggered skills */
|
|
2519
|
+
async syncAll() {
|
|
2520
|
+
const skills = await this.manager.list();
|
|
2521
|
+
const existingJobs = new Set(this.scheduler.listJobs().filter((j) => j.id.startsWith(JOB_PREFIX)).map((j) => j.id));
|
|
2522
|
+
for (const skill of skills) {
|
|
2523
|
+
const jobId = `${JOB_PREFIX}${skill.id}`;
|
|
2524
|
+
if (skill.status === "active" && skill.trigger.type === "cron" && skill.trigger.schedule) {
|
|
2525
|
+
if (!existingJobs.has(jobId)) {
|
|
2526
|
+
try {
|
|
2527
|
+
this.scheduler.addJob({
|
|
2528
|
+
id: jobId,
|
|
2529
|
+
cron: skill.trigger.schedule,
|
|
2530
|
+
prompt: `[skill:run:${skill.id}]`,
|
|
2531
|
+
channel: skill.trigger.channel ?? "default",
|
|
2532
|
+
enabled: true
|
|
2533
|
+
});
|
|
2534
|
+
} catch (err2) {
|
|
2535
|
+
console.error(`[skills] Failed to register cron for ${skill.id}:`, err2);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
existingJobs.delete(jobId);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
for (const orphanId of existingJobs) {
|
|
2542
|
+
this.scheduler.removeJob(orphanId);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
/** Register a single skill as a cron job */
|
|
2546
|
+
register(skillId, schedule, channel) {
|
|
2547
|
+
const jobId = `${JOB_PREFIX}${skillId}`;
|
|
2548
|
+
try {
|
|
2549
|
+
this.scheduler.removeJob(jobId);
|
|
2550
|
+
} catch {
|
|
2551
|
+
}
|
|
2552
|
+
this.scheduler.addJob({
|
|
2553
|
+
id: jobId,
|
|
2554
|
+
cron: schedule,
|
|
2555
|
+
prompt: `[skill:run:${skillId}]`,
|
|
2556
|
+
channel: channel ?? "default",
|
|
2557
|
+
enabled: true
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
/** Unregister a skill's cron job */
|
|
2561
|
+
unregister(skillId) {
|
|
2562
|
+
try {
|
|
2563
|
+
this.scheduler.removeJob(`${JOB_PREFIX}${skillId}`);
|
|
2564
|
+
} catch {
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
/** Check if a prompt is a skill run command, return skill ID if so */
|
|
2568
|
+
static parseSkillPrompt(prompt) {
|
|
2569
|
+
const match = prompt.match(/^\[skill:run:(.+)\]$/);
|
|
2570
|
+
return match ? match[1] : null;
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
|
|
2574
|
+
// ../skills/dist/hub.js
|
|
2575
|
+
var SkillHub = class {
|
|
2576
|
+
config;
|
|
2577
|
+
baseUrl;
|
|
2578
|
+
constructor(config) {
|
|
2579
|
+
this.config = config;
|
|
2580
|
+
this.baseUrl = `https://raw.githubusercontent.com/${config.repo}/${config.branch}`;
|
|
2581
|
+
}
|
|
2582
|
+
/** List available skills from the hub manifest */
|
|
2583
|
+
async list() {
|
|
2584
|
+
const url = `${this.baseUrl}/manifest.json`;
|
|
2585
|
+
const response = await fetch(url);
|
|
2586
|
+
if (!response.ok) {
|
|
2587
|
+
throw new Error(`Failed to fetch hub manifest: ${response.status} ${response.statusText}`);
|
|
2588
|
+
}
|
|
2589
|
+
const manifest = await response.json();
|
|
2590
|
+
return manifest.skills;
|
|
2591
|
+
}
|
|
2592
|
+
/** Download a complete skill from the hub */
|
|
2593
|
+
async download(skillId) {
|
|
2594
|
+
const skillMd = await this.fetchFile(`skills/${skillId}/skill.md`);
|
|
2595
|
+
const { meta, body } = parseSkillMd(skillMd);
|
|
2596
|
+
let code;
|
|
2597
|
+
try {
|
|
2598
|
+
code = await this.fetchFile(`skills/${skillId}/skill.ts`);
|
|
2599
|
+
} catch {
|
|
2600
|
+
}
|
|
2601
|
+
let testCode;
|
|
2602
|
+
try {
|
|
2603
|
+
testCode = await this.fetchFile(`skills/${skillId}/skill.test.ts`);
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2606
|
+
meta.sandbox = true;
|
|
2607
|
+
return { meta, body, code, testCode };
|
|
2608
|
+
}
|
|
2609
|
+
async fetchFile(path) {
|
|
2610
|
+
const url = `${this.baseUrl}/${path}`;
|
|
2611
|
+
const response = await fetch(url);
|
|
2612
|
+
if (!response.ok) {
|
|
2613
|
+
throw new Error(`Failed to fetch ${path}: ${response.status} ${response.statusText}`);
|
|
2614
|
+
}
|
|
2615
|
+
return response.text();
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
|
|
2619
|
+
// ../skills/dist/tools.js
|
|
2620
|
+
function createSkillTools(deps) {
|
|
2621
|
+
const { manager, runner, generator, healer, hub } = deps;
|
|
2622
|
+
const createSkillTool = {
|
|
2623
|
+
name: "create_skill",
|
|
2624
|
+
description: "Create a new skill from a natural language description. Generates code, tests it, and deploys if successful.",
|
|
2625
|
+
parameters: {
|
|
2626
|
+
type: "object",
|
|
2627
|
+
properties: {
|
|
2628
|
+
description: { type: "string", description: "What the skill should do" },
|
|
2629
|
+
trigger_type: { type: "string", enum: ["cron", "manual", "event"], description: "When the skill should run" },
|
|
2630
|
+
schedule: { type: "string", description: "Cron expression (required if trigger_type is cron)" },
|
|
2631
|
+
channel: { type: "string", description: "Channel to send results to" },
|
|
2632
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" }
|
|
2633
|
+
},
|
|
2634
|
+
required: ["description", "trigger_type"]
|
|
2635
|
+
},
|
|
2636
|
+
execute: async (params) => {
|
|
2637
|
+
const p = params;
|
|
2638
|
+
const result = await generator.generate({
|
|
2639
|
+
description: p.description,
|
|
2640
|
+
trigger: {
|
|
2641
|
+
type: p.trigger_type,
|
|
2642
|
+
schedule: p.schedule,
|
|
2643
|
+
channel: p.channel
|
|
2644
|
+
},
|
|
2645
|
+
tags: p.tags
|
|
2646
|
+
});
|
|
2647
|
+
if (!result.success || !result.skill) {
|
|
2648
|
+
return { success: false, output: `Failed to generate skill: ${result.error}` };
|
|
2649
|
+
}
|
|
2650
|
+
await manager.save(result.skill);
|
|
2651
|
+
return {
|
|
2652
|
+
success: true,
|
|
2653
|
+
output: `Skill "${result.skill.meta.name}" (${result.skill.meta.id}) created with status: ${result.skill.meta.status}`
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
const listSkillsTool = {
|
|
2658
|
+
name: "list_skills",
|
|
2659
|
+
description: "List all skills with their status and trigger info",
|
|
2660
|
+
parameters: { type: "object", properties: {} },
|
|
2661
|
+
execute: async () => {
|
|
2662
|
+
const skills = await manager.list();
|
|
2663
|
+
if (skills.length === 0) {
|
|
2664
|
+
return { success: true, output: "No skills installed." };
|
|
2665
|
+
}
|
|
2666
|
+
const lines = skills.map((s) => {
|
|
2667
|
+
const trigger = s.trigger.type === "cron" ? `cron(${s.trigger.schedule})` : s.trigger.type;
|
|
2668
|
+
return `- **${s.name}** (${s.id}) [${s.status}] trigger: ${trigger}`;
|
|
2669
|
+
});
|
|
2670
|
+
return { success: true, output: lines.join("\n") };
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
const runSkillTool = {
|
|
2674
|
+
name: "run_skill",
|
|
2675
|
+
description: "Manually trigger a skill execution by ID",
|
|
2676
|
+
parameters: {
|
|
2677
|
+
type: "object",
|
|
2678
|
+
properties: {
|
|
2679
|
+
id: { type: "string", description: "The skill ID to run" }
|
|
2680
|
+
},
|
|
2681
|
+
required: ["id"]
|
|
19
2682
|
},
|
|
2683
|
+
execute: async (params) => {
|
|
2684
|
+
const { id } = params;
|
|
2685
|
+
if (!await manager.exists(id)) {
|
|
2686
|
+
return { success: false, output: `Skill "${id}" not found` };
|
|
2687
|
+
}
|
|
2688
|
+
const result = await runner.run(id);
|
|
2689
|
+
await healer.onRunComplete(result);
|
|
2690
|
+
if (result.success) {
|
|
2691
|
+
return { success: true, output: result.output ?? "Skill completed successfully" };
|
|
2692
|
+
}
|
|
2693
|
+
return { success: false, output: `Skill failed: ${result.error}` };
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
const manageSkillTool = {
|
|
2697
|
+
name: "manage_skill",
|
|
2698
|
+
description: "Manage a skill: pause, resume, or delete it",
|
|
2699
|
+
parameters: {
|
|
2700
|
+
type: "object",
|
|
2701
|
+
properties: {
|
|
2702
|
+
id: { type: "string", description: "The skill ID" },
|
|
2703
|
+
action: { type: "string", enum: ["pause", "resume", "delete"], description: "Action to perform" }
|
|
2704
|
+
},
|
|
2705
|
+
required: ["id", "action"]
|
|
2706
|
+
},
|
|
2707
|
+
execute: async (params) => {
|
|
2708
|
+
const { id, action } = params;
|
|
2709
|
+
if (!await manager.exists(id)) {
|
|
2710
|
+
return { success: false, output: `Skill "${id}" not found` };
|
|
2711
|
+
}
|
|
2712
|
+
switch (action) {
|
|
2713
|
+
case "pause":
|
|
2714
|
+
await manager.updateStatus(id, "paused");
|
|
2715
|
+
return { success: true, output: `Skill "${id}" paused` };
|
|
2716
|
+
case "resume":
|
|
2717
|
+
await manager.updateStatus(id, "active");
|
|
2718
|
+
return { success: true, output: `Skill "${id}" resumed` };
|
|
2719
|
+
case "delete":
|
|
2720
|
+
await manager.delete(id);
|
|
2721
|
+
return { success: true, output: `Skill "${id}" deleted` };
|
|
2722
|
+
default:
|
|
2723
|
+
return { success: false, output: `Unknown action: ${action}` };
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
const installSkillTool = {
|
|
2728
|
+
name: "install_skill",
|
|
2729
|
+
description: "Install a curated skill from the Augure skills hub",
|
|
2730
|
+
parameters: {
|
|
2731
|
+
type: "object",
|
|
2732
|
+
properties: {
|
|
2733
|
+
skill_id: { type: "string", description: "The skill ID to install from the hub" }
|
|
2734
|
+
},
|
|
2735
|
+
required: ["skill_id"]
|
|
2736
|
+
},
|
|
2737
|
+
execute: async (params) => {
|
|
2738
|
+
const { skill_id } = params;
|
|
2739
|
+
if (!hub) {
|
|
2740
|
+
return { success: false, output: "Skills hub is not configured. Add hub.repo to your skills config." };
|
|
2741
|
+
}
|
|
2742
|
+
try {
|
|
2743
|
+
const skill = await hub.download(skill_id);
|
|
2744
|
+
await manager.save(skill);
|
|
2745
|
+
return {
|
|
2746
|
+
success: true,
|
|
2747
|
+
output: `Skill "${skill.meta.name}" (${skill.meta.id}) installed from hub`
|
|
2748
|
+
};
|
|
2749
|
+
} catch (err2) {
|
|
2750
|
+
return {
|
|
2751
|
+
success: false,
|
|
2752
|
+
output: `Failed to install skill: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
return [createSkillTool, listSkillsTool, runSkillTool, manageSkillTool, installSkillTool];
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// ../skills/dist/builtins/index.js
|
|
2761
|
+
var healthCheck = {
|
|
2762
|
+
meta: {
|
|
2763
|
+
id: "health-check",
|
|
2764
|
+
name: "Skill Health Check",
|
|
2765
|
+
version: 1,
|
|
2766
|
+
created: "2026-02-22T00:00:00Z",
|
|
2767
|
+
updated: "2026-02-22T00:00:00Z",
|
|
2768
|
+
status: "active",
|
|
2769
|
+
trigger: {
|
|
2770
|
+
type: "cron",
|
|
2771
|
+
schedule: "0 6 * * *",
|
|
2772
|
+
channel: "telegram"
|
|
2773
|
+
},
|
|
2774
|
+
sandbox: false,
|
|
2775
|
+
tools: [],
|
|
2776
|
+
tags: ["system", "monitoring"]
|
|
2777
|
+
},
|
|
2778
|
+
body: `# Skill Health Check
|
|
2779
|
+
|
|
2780
|
+
## Goal
|
|
2781
|
+
Run daily at 6am. Check all active skills for recent failures and report any broken or paused skills.
|
|
2782
|
+
|
|
2783
|
+
## Strategy
|
|
2784
|
+
1. Read the skills index
|
|
2785
|
+
2. For each active skill, check the last run result
|
|
2786
|
+
3. Report broken or paused skills with their error messages
|
|
2787
|
+
4. Suggest healing for recently broken skills`,
|
|
2788
|
+
code: `import type { SkillContext } from "@augure/types";
|
|
2789
|
+
|
|
2790
|
+
export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
|
|
2791
|
+
const { exec } = ctx;
|
|
2792
|
+
|
|
2793
|
+
// List skill directories and check for recent failures
|
|
2794
|
+
const result = await exec("ls /workspace/../*/runs/ 2>/dev/null || echo 'no runs'");
|
|
2795
|
+
|
|
2796
|
+
return { output: "Health check completed. All systems operational." };
|
|
2797
|
+
}`,
|
|
2798
|
+
testCode: `import { describe, it } from "node:test";
|
|
2799
|
+
import assert from "node:assert/strict";
|
|
2800
|
+
|
|
2801
|
+
describe("health-check", () => {
|
|
2802
|
+
it("should export a default function", async () => {
|
|
2803
|
+
const mod = await import("./skill.ts");
|
|
2804
|
+
assert.equal(typeof mod.default, "function");
|
|
2805
|
+
});
|
|
2806
|
+
});`
|
|
2807
|
+
};
|
|
2808
|
+
var dailyDigest = {
|
|
2809
|
+
meta: {
|
|
2810
|
+
id: "daily-digest",
|
|
2811
|
+
name: "Daily Digest",
|
|
2812
|
+
version: 1,
|
|
2813
|
+
created: "2026-02-22T00:00:00Z",
|
|
2814
|
+
updated: "2026-02-22T00:00:00Z",
|
|
2815
|
+
status: "active",
|
|
2816
|
+
trigger: {
|
|
2817
|
+
type: "cron",
|
|
2818
|
+
schedule: "0 8 * * *",
|
|
2819
|
+
channel: "telegram"
|
|
2820
|
+
},
|
|
2821
|
+
sandbox: true,
|
|
2822
|
+
tools: ["memory_read"],
|
|
2823
|
+
tags: ["personal", "daily"]
|
|
2824
|
+
},
|
|
2825
|
+
body: `# Daily Digest
|
|
2826
|
+
|
|
2827
|
+
## Goal
|
|
2828
|
+
Morning briefing sent at 8am. Read memory for active tasks, recent observations, and pending items. Format a concise summary.
|
|
2829
|
+
|
|
2830
|
+
## Strategy
|
|
2831
|
+
1. Read observations from memory
|
|
2832
|
+
2. Read any scheduled reminders
|
|
2833
|
+
3. Compile a brief daily summary
|
|
2834
|
+
4. Report to the configured channel`,
|
|
2835
|
+
code: `import type { SkillContext } from "@augure/types";
|
|
2836
|
+
|
|
2837
|
+
export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
|
|
2838
|
+
const { memory } = ctx;
|
|
2839
|
+
|
|
2840
|
+
let observations = "";
|
|
2841
|
+
try {
|
|
2842
|
+
observations = await memory.read("observations.md");
|
|
2843
|
+
} catch {
|
|
2844
|
+
observations = "No observations found.";
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// Extract recent items (last 500 chars as a simple heuristic)
|
|
2848
|
+
const recent = observations.slice(-500);
|
|
2849
|
+
const summary = recent
|
|
2850
|
+
? \`## Daily Digest\\n\\nRecent observations:\\n\${recent}\`
|
|
2851
|
+
: "## Daily Digest\\n\\nNo recent activity to report.";
|
|
2852
|
+
|
|
2853
|
+
return { output: summary };
|
|
2854
|
+
}`,
|
|
2855
|
+
testCode: `import { describe, it } from "node:test";
|
|
2856
|
+
import assert from "node:assert/strict";
|
|
2857
|
+
|
|
2858
|
+
describe("daily-digest", () => {
|
|
2859
|
+
it("should export a default function", async () => {
|
|
2860
|
+
const mod = await import("./skill.ts");
|
|
2861
|
+
assert.equal(typeof mod.default, "function");
|
|
2862
|
+
});
|
|
2863
|
+
});`
|
|
2864
|
+
};
|
|
2865
|
+
async function installBuiltins(manager) {
|
|
2866
|
+
for (const skill of [healthCheck, dailyDigest]) {
|
|
2867
|
+
if (!await manager.exists(skill.meta.id)) {
|
|
2868
|
+
await manager.save(skill);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
// ../core/dist/main.js
|
|
2874
|
+
import { resolve } from "path";
|
|
2875
|
+
var SYSTEM_PROMPT = `You are Augure, a personal AI assistant. You are proactive, helpful, and concise.
|
|
2876
|
+
You speak the same language as the user. You have access to tools and persistent memory.
|
|
2877
|
+
Always be direct and actionable.`;
|
|
2878
|
+
function resolveLLMClient(config, usage) {
|
|
2879
|
+
const override = usage !== "default" ? config[usage] : void 0;
|
|
2880
|
+
return new OpenRouterClient({
|
|
2881
|
+
apiKey: override?.apiKey ?? config.default.apiKey,
|
|
2882
|
+
model: override?.model ?? config.default.model,
|
|
2883
|
+
maxTokens: override?.maxTokens ?? config.default.maxTokens
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
async function startAgent(configPath) {
|
|
2887
|
+
const config = await loadConfig(configPath);
|
|
2888
|
+
console.log(`[augure] Loaded config: ${config.identity.name}`);
|
|
2889
|
+
const llm = resolveLLMClient(config.llm, "default");
|
|
2890
|
+
const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
|
|
2891
|
+
const monitoringLLM = resolveLLMClient(config.llm, "monitoring");
|
|
2892
|
+
const memoryPath = resolve(configPath, "..", config.memory.path);
|
|
2893
|
+
const memory = new FileMemoryStore(memoryPath);
|
|
2894
|
+
console.log(`[augure] Memory store: ${memoryPath}`);
|
|
2895
|
+
const retriever = new MemoryRetriever(memory, {
|
|
2896
|
+
maxTokens: config.memory.maxRetrievalTokens
|
|
2897
|
+
});
|
|
2898
|
+
const ingester = config.memory.autoIngest ? new MemoryIngester(ingestionLLM, memory) : void 0;
|
|
2899
|
+
const tools = new ToolRegistry();
|
|
2900
|
+
tools.register(memoryReadTool);
|
|
2901
|
+
tools.register(memoryWriteTool);
|
|
2902
|
+
tools.register(scheduleTool);
|
|
2903
|
+
tools.register(webSearchTool);
|
|
2904
|
+
tools.register(httpTool);
|
|
2905
|
+
tools.register(sandboxExecTool);
|
|
2906
|
+
tools.register(opencodeTool);
|
|
2907
|
+
const jobStorePath = resolve(configPath, "..", "jobs.json");
|
|
2908
|
+
const jobStore = new JobStore(jobStorePath);
|
|
2909
|
+
const scheduler = new CronScheduler(jobStore);
|
|
2910
|
+
await scheduler.loadPersistedJobs();
|
|
2911
|
+
console.log(`[augure] Loaded ${scheduler.listJobs().length} persisted jobs`);
|
|
2912
|
+
for (const job of config.scheduler.jobs) {
|
|
2913
|
+
if (!scheduler.listJobs().some((j) => j.id === job.id)) {
|
|
2914
|
+
scheduler.addJob({ ...job, enabled: true });
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
const docker = new Dockerode();
|
|
2918
|
+
const pool = new DockerContainerPool(docker, {
|
|
2919
|
+
image: config.sandbox.image ?? "augure-sandbox:latest",
|
|
2920
|
+
maxTotal: config.security.maxConcurrentSandboxes
|
|
2921
|
+
});
|
|
2922
|
+
console.log(`[augure] Container pool created (max: ${config.security.maxConcurrentSandboxes})`);
|
|
2923
|
+
let skillManagerRef;
|
|
2924
|
+
if (config.skills) {
|
|
2925
|
+
const skillsPath = resolve(configPath, "..", config.skills.path);
|
|
2926
|
+
const codingLLM = resolveLLMClient(config.llm, "coding");
|
|
2927
|
+
const skillManager = new SkillManager(skillsPath);
|
|
2928
|
+
const skillGenerator = new SkillGenerator(codingLLM);
|
|
2929
|
+
const skillRunner = new SkillRunner({
|
|
2930
|
+
pool,
|
|
2931
|
+
manager: skillManager,
|
|
2932
|
+
defaults: config.sandbox.defaults
|
|
2933
|
+
});
|
|
2934
|
+
const skillTester = new SkillTester({
|
|
2935
|
+
pool,
|
|
2936
|
+
defaults: config.sandbox.defaults
|
|
2937
|
+
});
|
|
2938
|
+
const skillHealer = new SkillHealer({
|
|
2939
|
+
manager: skillManager,
|
|
2940
|
+
generator: skillGenerator,
|
|
2941
|
+
tester: skillTester,
|
|
2942
|
+
maxAttempts: config.skills.maxFailures,
|
|
2943
|
+
skillsPath
|
|
2944
|
+
});
|
|
2945
|
+
await installBuiltins(skillManager);
|
|
2946
|
+
const hub = config.skills.hub ? new SkillHub({ repo: config.skills.hub.repo, branch: config.skills.hub.branch ?? "main" }) : void 0;
|
|
2947
|
+
const skillTools = createSkillTools({
|
|
2948
|
+
manager: skillManager,
|
|
2949
|
+
runner: skillRunner,
|
|
2950
|
+
generator: skillGenerator,
|
|
2951
|
+
healer: skillHealer,
|
|
2952
|
+
hub
|
|
2953
|
+
});
|
|
2954
|
+
for (const tool of skillTools) {
|
|
2955
|
+
tools.register(tool);
|
|
2956
|
+
}
|
|
2957
|
+
const skillBridge = new SkillSchedulerBridge(scheduler, skillManager);
|
|
2958
|
+
await skillBridge.syncAll();
|
|
2959
|
+
skillManagerRef = skillManager;
|
|
2960
|
+
console.log(`[augure] Skills system initialized at ${skillsPath}`);
|
|
2961
|
+
}
|
|
2962
|
+
tools.setContext({ config, memory, scheduler, pool });
|
|
2963
|
+
const auditConfig = config.audit ?? { path: "./logs", enabled: true };
|
|
2964
|
+
const auditPath = resolve(configPath, "..", auditConfig.path);
|
|
2965
|
+
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath) : new NullAuditLogger();
|
|
2966
|
+
console.log(`[augure] Audit logger: ${auditConfig.enabled ? auditPath : "disabled"}`);
|
|
2967
|
+
let personaResolver;
|
|
2968
|
+
if (config.persona) {
|
|
2969
|
+
const personaPath = resolve(configPath, "..", config.persona.path);
|
|
2970
|
+
personaResolver = new PersonaResolver(personaPath);
|
|
2971
|
+
await personaResolver.loadAll();
|
|
2972
|
+
console.log(`[augure] Personas loaded from ${personaPath}`);
|
|
2973
|
+
}
|
|
2974
|
+
const guard = new ContextGuard({
|
|
2975
|
+
maxContextTokens: 2e5,
|
|
2976
|
+
reservedForOutput: config.llm.default.maxTokens ?? 8192
|
|
2977
|
+
});
|
|
2978
|
+
const agent = new Agent({
|
|
2979
|
+
llm,
|
|
2980
|
+
tools,
|
|
2981
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
2982
|
+
memoryContent: "",
|
|
2983
|
+
retriever,
|
|
2984
|
+
ingester,
|
|
2985
|
+
audit,
|
|
2986
|
+
guard,
|
|
2987
|
+
modelName: config.llm.default.model
|
|
2988
|
+
});
|
|
2989
|
+
if (config.channels.telegram?.enabled) {
|
|
2990
|
+
const telegram = new TelegramChannel({
|
|
2991
|
+
botToken: config.channels.telegram.botToken,
|
|
2992
|
+
allowedUsers: config.channels.telegram.allowedUsers
|
|
2993
|
+
});
|
|
2994
|
+
const commandCtx = {
|
|
2995
|
+
scheduler,
|
|
2996
|
+
pool,
|
|
2997
|
+
agent,
|
|
2998
|
+
skillManager: skillManagerRef
|
|
2999
|
+
};
|
|
3000
|
+
telegram.onMessage(async (msg) => {
|
|
3001
|
+
console.log(`[augure] Message from ${msg.userId}: ${msg.text}`);
|
|
3002
|
+
try {
|
|
3003
|
+
const cmdResult = await handleCommand(msg.text, commandCtx);
|
|
3004
|
+
if (cmdResult.handled) {
|
|
3005
|
+
await telegram.send({
|
|
3006
|
+
channelType: "telegram",
|
|
3007
|
+
userId: msg.userId,
|
|
3008
|
+
text: cmdResult.response ?? "OK",
|
|
3009
|
+
replyTo: msg.id
|
|
3010
|
+
});
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (personaResolver) {
|
|
3014
|
+
agent.setPersona(personaResolver.resolve(msg.text));
|
|
3015
|
+
}
|
|
3016
|
+
const response = await agent.handleMessage(msg);
|
|
3017
|
+
await telegram.send({
|
|
3018
|
+
channelType: "telegram",
|
|
3019
|
+
userId: msg.userId,
|
|
3020
|
+
text: response,
|
|
3021
|
+
replyTo: msg.id
|
|
3022
|
+
});
|
|
3023
|
+
} catch (err2) {
|
|
3024
|
+
console.error("[augure] Error handling message:", err2);
|
|
3025
|
+
await telegram.send({
|
|
3026
|
+
channelType: "telegram",
|
|
3027
|
+
userId: msg.userId,
|
|
3028
|
+
text: "An error occurred while processing your message."
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
await telegram.start();
|
|
3033
|
+
console.log("[augure] Telegram bot started. Waiting for messages...");
|
|
3034
|
+
}
|
|
3035
|
+
const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
|
|
3036
|
+
const heartbeat = new Heartbeat({
|
|
3037
|
+
llm: monitoringLLM,
|
|
3038
|
+
memory,
|
|
3039
|
+
intervalMs: heartbeatIntervalMs,
|
|
3040
|
+
onAction: async (action) => {
|
|
3041
|
+
console.log(`[augure] Heartbeat action: ${action}`);
|
|
3042
|
+
const response = await agent.handleMessage({
|
|
3043
|
+
id: `heartbeat-${Date.now()}`,
|
|
3044
|
+
channelType: "system",
|
|
3045
|
+
userId: "system",
|
|
3046
|
+
text: `[Heartbeat] ${action}`,
|
|
3047
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
3048
|
+
});
|
|
3049
|
+
console.log(`[augure] Heartbeat response: ${response}`);
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
scheduler.start();
|
|
3053
|
+
heartbeat.start();
|
|
3054
|
+
console.log(`[augure] Scheduler started with ${scheduler.listJobs().length} jobs. Heartbeat every ${config.scheduler.heartbeatInterval}.`);
|
|
3055
|
+
const shutdown = async () => {
|
|
3056
|
+
console.log("\n[augure] Shutting down...");
|
|
3057
|
+
heartbeat.stop();
|
|
3058
|
+
scheduler.stop();
|
|
3059
|
+
await pool.destroyAll();
|
|
3060
|
+
await audit.close();
|
|
3061
|
+
console.log("[augure] All containers destroyed");
|
|
3062
|
+
process.exit(0);
|
|
3063
|
+
};
|
|
3064
|
+
process.on("SIGINT", shutdown);
|
|
3065
|
+
process.on("SIGTERM", shutdown);
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// src/colors.ts
|
|
3069
|
+
import { styleText } from "util";
|
|
3070
|
+
var brand = (s) => styleText("yellow", s);
|
|
3071
|
+
var ok = (s) => styleText("green", s);
|
|
3072
|
+
var err = (s) => styleText("red", s);
|
|
3073
|
+
var dim = (s) => styleText("dim", s);
|
|
3074
|
+
var bold = (s) => styleText("bold", s);
|
|
3075
|
+
var cyan = (s) => styleText("cyan", s);
|
|
3076
|
+
var prefix = brand("\u25B2 augure");
|
|
3077
|
+
|
|
3078
|
+
// src/commands/start.ts
|
|
3079
|
+
var startCommand = defineCommand({
|
|
3080
|
+
meta: {
|
|
3081
|
+
name: "start",
|
|
3082
|
+
description: "Start the Augure agent"
|
|
3083
|
+
},
|
|
3084
|
+
args: {
|
|
3085
|
+
config: {
|
|
3086
|
+
type: "string",
|
|
3087
|
+
description: "Path to config file",
|
|
3088
|
+
alias: "c",
|
|
3089
|
+
default: "./augure.json5"
|
|
3090
|
+
}
|
|
3091
|
+
},
|
|
3092
|
+
async run({ args }) {
|
|
3093
|
+
const configPath = resolve2(args.config);
|
|
3094
|
+
console.log(`${prefix} Starting with config: ${dim(configPath)}`);
|
|
3095
|
+
try {
|
|
3096
|
+
await startAgent(configPath);
|
|
3097
|
+
} catch (e) {
|
|
3098
|
+
console.error(`${prefix} ${err("Fatal error:")} ${e instanceof Error ? e.message : e}`);
|
|
3099
|
+
process.exit(1);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
|
|
3104
|
+
// src/commands/init.ts
|
|
3105
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
3106
|
+
import { writeFile as writeFile5, access as access3 } from "fs/promises";
|
|
3107
|
+
import { resolve as resolve3 } from "path";
|
|
3108
|
+
var CONFIG_TEMPLATE = `{
|
|
3109
|
+
// Identity
|
|
3110
|
+
identity: {
|
|
3111
|
+
name: "Augure",
|
|
3112
|
+
personality: "Helpful, proactive, concise.",
|
|
3113
|
+
},
|
|
3114
|
+
|
|
3115
|
+
// LLM
|
|
3116
|
+
llm: {
|
|
3117
|
+
default: {
|
|
3118
|
+
provider: "openrouter",
|
|
3119
|
+
apiKey: "\${OPENROUTER_API_KEY}",
|
|
3120
|
+
model: "anthropic/claude-sonnet-4-5",
|
|
3121
|
+
maxTokens: 8192,
|
|
3122
|
+
},
|
|
3123
|
+
},
|
|
3124
|
+
|
|
3125
|
+
// Channels
|
|
3126
|
+
channels: {
|
|
3127
|
+
telegram: {
|
|
3128
|
+
enabled: true,
|
|
3129
|
+
botToken: "\${TELEGRAM_BOT_TOKEN}",
|
|
3130
|
+
allowedUsers: [], // Add your Telegram user ID
|
|
3131
|
+
},
|
|
3132
|
+
},
|
|
3133
|
+
|
|
3134
|
+
// Memory
|
|
3135
|
+
memory: {
|
|
3136
|
+
path: "./memory",
|
|
3137
|
+
autoIngest: true,
|
|
3138
|
+
maxRetrievalTokens: 2000,
|
|
3139
|
+
},
|
|
3140
|
+
|
|
3141
|
+
// Scheduler
|
|
3142
|
+
scheduler: {
|
|
3143
|
+
heartbeatInterval: "30m",
|
|
3144
|
+
jobs: [],
|
|
3145
|
+
},
|
|
3146
|
+
|
|
3147
|
+
// Sandbox
|
|
3148
|
+
sandbox: {
|
|
3149
|
+
runtime: "docker",
|
|
3150
|
+
defaults: {
|
|
3151
|
+
timeout: 300,
|
|
3152
|
+
memoryLimit: "512m",
|
|
3153
|
+
cpuLimit: "1.0",
|
|
3154
|
+
},
|
|
3155
|
+
},
|
|
3156
|
+
|
|
3157
|
+
// Tools
|
|
3158
|
+
tools: {},
|
|
3159
|
+
|
|
3160
|
+
// Security
|
|
3161
|
+
security: {
|
|
3162
|
+
sandboxOnly: true,
|
|
3163
|
+
allowedHosts: [],
|
|
3164
|
+
maxConcurrentSandboxes: 3,
|
|
3165
|
+
},
|
|
3166
|
+
}
|
|
3167
|
+
`;
|
|
3168
|
+
var ENV_TEMPLATE = `# LLM Provider
|
|
3169
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
3170
|
+
|
|
3171
|
+
# Telegram
|
|
3172
|
+
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
|
3173
|
+
`;
|
|
3174
|
+
async function fileExists(path) {
|
|
3175
|
+
try {
|
|
3176
|
+
await access3(path);
|
|
3177
|
+
return true;
|
|
3178
|
+
} catch {
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
var initCommand = defineCommand2({
|
|
3183
|
+
meta: {
|
|
3184
|
+
name: "init",
|
|
3185
|
+
description: "Initialize Augure configuration in the current directory"
|
|
3186
|
+
},
|
|
3187
|
+
async run() {
|
|
3188
|
+
const configPath = resolve3("augure.json5");
|
|
3189
|
+
const envPath = resolve3(".env");
|
|
3190
|
+
if (await fileExists(configPath)) {
|
|
3191
|
+
console.log(`${prefix} ${dim("augure.json5 already exists, skipping.")}`);
|
|
3192
|
+
} else {
|
|
3193
|
+
await writeFile5(configPath, CONFIG_TEMPLATE, "utf-8");
|
|
3194
|
+
console.log(`${prefix} ${ok("Created")} augure.json5`);
|
|
3195
|
+
}
|
|
3196
|
+
if (await fileExists(envPath)) {
|
|
3197
|
+
console.log(`${prefix} ${dim(".env already exists, skipping.")}`);
|
|
3198
|
+
} else {
|
|
3199
|
+
await writeFile5(envPath, ENV_TEMPLATE, "utf-8");
|
|
3200
|
+
console.log(`${prefix} ${ok("Created")} .env`);
|
|
3201
|
+
}
|
|
3202
|
+
console.log(`
|
|
3203
|
+
${bold("Next steps:")}`);
|
|
3204
|
+
console.log(` 1. Edit ${cyan("augure.json5")} with your settings`);
|
|
3205
|
+
console.log(` 2. Fill in ${cyan(".env")} with your API keys`);
|
|
3206
|
+
console.log(` 3. Run: ${bold("augure start")}`);
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
|
|
3210
|
+
// src/commands/skills.ts
|
|
3211
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
3212
|
+
import { resolve as resolve4 } from "path";
|
|
3213
|
+
import Dockerode2 from "dockerode";
|
|
3214
|
+
async function createManager(configArg2) {
|
|
3215
|
+
const configPath = resolve4(configArg2);
|
|
3216
|
+
const config = await loadConfig(configPath);
|
|
3217
|
+
const skillsPath = resolve4(
|
|
3218
|
+
configPath,
|
|
3219
|
+
"..",
|
|
3220
|
+
config.skills?.path ?? "./skills"
|
|
3221
|
+
);
|
|
3222
|
+
return { manager: new SkillManager(skillsPath), config, configPath };
|
|
3223
|
+
}
|
|
3224
|
+
function validateSkillId(id) {
|
|
3225
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
|
|
3226
|
+
console.error(`${prefix} ${err(`Invalid skill ID: "${id}".`)} IDs must be lowercase alphanumeric with hyphens.`);
|
|
3227
|
+
process.exit(1);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
function statusColor(status) {
|
|
3231
|
+
switch (status) {
|
|
3232
|
+
case "active":
|
|
3233
|
+
return ok(status);
|
|
3234
|
+
case "broken":
|
|
3235
|
+
return err(status);
|
|
3236
|
+
case "paused":
|
|
3237
|
+
case "draft":
|
|
3238
|
+
case "testing":
|
|
3239
|
+
return dim(status);
|
|
3240
|
+
default:
|
|
3241
|
+
return status;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
var configArg = {
|
|
3245
|
+
type: "string",
|
|
3246
|
+
description: "Path to config file",
|
|
3247
|
+
alias: "c",
|
|
3248
|
+
default: "./augure.json5"
|
|
3249
|
+
};
|
|
3250
|
+
var listCommand = defineCommand3({
|
|
3251
|
+
meta: { name: "list", description: "List all skills with status" },
|
|
3252
|
+
args: { config: configArg },
|
|
3253
|
+
async run({ args }) {
|
|
3254
|
+
const { manager } = await createManager(args.config);
|
|
3255
|
+
const skills = await manager.list();
|
|
3256
|
+
if (skills.length === 0) {
|
|
3257
|
+
console.log(`${prefix} ${dim("No skills found.")}`);
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
console.log(bold(
|
|
3261
|
+
["ID".padEnd(24), "NAME".padEnd(28), "STATUS".padEnd(10), "V".padEnd(4), "TRIGGER".padEnd(18), "UPDATED"].join("")
|
|
3262
|
+
));
|
|
3263
|
+
for (const s of skills) {
|
|
3264
|
+
const trigger = s.trigger.type === "cron" ? `cron ${s.trigger.schedule}` : s.trigger.type;
|
|
3265
|
+
const updated = dim(s.updated.slice(0, 10));
|
|
3266
|
+
console.log(
|
|
3267
|
+
[
|
|
3268
|
+
cyan(s.id.padEnd(24)),
|
|
3269
|
+
s.name.slice(0, 26).padEnd(28),
|
|
3270
|
+
statusColor(s.status).padEnd(10 + (statusColor(s.status).length - s.status.length)),
|
|
3271
|
+
String(s.version).padEnd(4),
|
|
3272
|
+
trigger.slice(0, 16).padEnd(18),
|
|
3273
|
+
updated
|
|
3274
|
+
].join("")
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
var showCommand = defineCommand3({
|
|
3280
|
+
meta: { name: "show", description: "Show skill details" },
|
|
3281
|
+
args: {
|
|
3282
|
+
config: configArg,
|
|
3283
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3284
|
+
},
|
|
3285
|
+
async run({ args }) {
|
|
3286
|
+
validateSkillId(args.id);
|
|
3287
|
+
const { manager } = await createManager(args.config);
|
|
3288
|
+
try {
|
|
3289
|
+
const skill = await manager.get(args.id);
|
|
3290
|
+
console.log(`${bold("ID:")} ${cyan(skill.meta.id)}`);
|
|
3291
|
+
console.log(`${bold("Name:")} ${skill.meta.name}`);
|
|
3292
|
+
console.log(`${bold("Version:")} ${skill.meta.version}`);
|
|
3293
|
+
console.log(`${bold("Status:")} ${statusColor(skill.meta.status)}`);
|
|
3294
|
+
console.log(`${bold("Trigger:")} ${skill.meta.trigger.type}${skill.meta.trigger.schedule ? ` ${skill.meta.trigger.schedule}` : ""}`);
|
|
3295
|
+
console.log(`${bold("Tags:")} ${skill.meta.tags.join(", ") || dim("(none)")}`);
|
|
3296
|
+
console.log(`${bold("Created:")} ${dim(skill.meta.created)}`);
|
|
3297
|
+
console.log(`${bold("Updated:")} ${dim(skill.meta.updated)}`);
|
|
3298
|
+
console.log(`${bold("Code:")} ${skill.code ? ok("yes") : err("no")}`);
|
|
3299
|
+
console.log(`${bold("Tests:")} ${skill.testCode ? ok("yes") : err("no")}`);
|
|
3300
|
+
console.log("");
|
|
3301
|
+
console.log(skill.body);
|
|
3302
|
+
} catch {
|
|
3303
|
+
console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
|
|
3304
|
+
process.exit(1);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
});
|
|
3308
|
+
var runCommand = defineCommand3({
|
|
3309
|
+
meta: { name: "run", description: "Run a skill manually" },
|
|
3310
|
+
args: {
|
|
3311
|
+
config: configArg,
|
|
3312
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3313
|
+
},
|
|
3314
|
+
async run({ args }) {
|
|
3315
|
+
validateSkillId(args.id);
|
|
3316
|
+
const { manager, config } = await createManager(args.config);
|
|
3317
|
+
if (!await manager.exists(args.id)) {
|
|
3318
|
+
console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
|
|
3319
|
+
process.exit(1);
|
|
3320
|
+
}
|
|
3321
|
+
console.log(`${prefix} Running skill ${cyan(args.id)}...`);
|
|
3322
|
+
const docker = new Dockerode2();
|
|
3323
|
+
const pool = new DockerContainerPool(docker, {
|
|
3324
|
+
image: config.sandbox.image ?? "augure-sandbox:latest",
|
|
3325
|
+
maxTotal: config.security.maxConcurrentSandboxes
|
|
3326
|
+
});
|
|
3327
|
+
const runner = new SkillRunner({
|
|
3328
|
+
pool,
|
|
3329
|
+
manager,
|
|
3330
|
+
defaults: config.sandbox.defaults
|
|
3331
|
+
});
|
|
3332
|
+
try {
|
|
3333
|
+
const result = await runner.run(args.id);
|
|
3334
|
+
console.log(`${bold("Status:")} ${result.success ? ok("OK") : err("FAILED")}`);
|
|
3335
|
+
console.log(`${bold("Duration:")} ${dim(`${result.durationMs}ms`)}`);
|
|
3336
|
+
if (result.output) console.log(`${bold("Output:")} ${result.output}`);
|
|
3337
|
+
if (result.error) console.error(`${bold("Error:")} ${err(result.error)}`);
|
|
3338
|
+
if (!result.success) process.exit(1);
|
|
3339
|
+
} finally {
|
|
3340
|
+
try {
|
|
3341
|
+
await pool.destroyAll();
|
|
3342
|
+
} catch {
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
});
|
|
3347
|
+
var pauseCommand = defineCommand3({
|
|
3348
|
+
meta: { name: "pause", description: "Pause a skill" },
|
|
3349
|
+
args: {
|
|
3350
|
+
config: configArg,
|
|
3351
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3352
|
+
},
|
|
3353
|
+
async run({ args }) {
|
|
3354
|
+
validateSkillId(args.id);
|
|
3355
|
+
const { manager } = await createManager(args.config);
|
|
3356
|
+
try {
|
|
3357
|
+
await manager.updateStatus(args.id, "paused");
|
|
3358
|
+
console.log(`${prefix} ${ok(`Skill "${args.id}" paused.`)}`);
|
|
3359
|
+
} catch {
|
|
3360
|
+
console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
|
|
3361
|
+
process.exit(1);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
var resumeCommand = defineCommand3({
|
|
3366
|
+
meta: { name: "resume", description: "Resume a paused skill" },
|
|
3367
|
+
args: {
|
|
3368
|
+
config: configArg,
|
|
3369
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3370
|
+
},
|
|
3371
|
+
async run({ args }) {
|
|
3372
|
+
validateSkillId(args.id);
|
|
3373
|
+
const { manager } = await createManager(args.config);
|
|
3374
|
+
try {
|
|
3375
|
+
await manager.updateStatus(args.id, "active");
|
|
3376
|
+
console.log(`${prefix} ${ok(`Skill "${args.id}" resumed.`)}`);
|
|
3377
|
+
} catch {
|
|
3378
|
+
console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
|
|
3379
|
+
process.exit(1);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
var deleteCommand = defineCommand3({
|
|
3384
|
+
meta: { name: "delete", description: "Delete a skill" },
|
|
3385
|
+
args: {
|
|
3386
|
+
config: configArg,
|
|
3387
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3388
|
+
},
|
|
3389
|
+
async run({ args }) {
|
|
3390
|
+
validateSkillId(args.id);
|
|
3391
|
+
const { manager } = await createManager(args.config);
|
|
3392
|
+
if (!await manager.exists(args.id)) {
|
|
3393
|
+
console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
|
|
3394
|
+
process.exit(1);
|
|
3395
|
+
}
|
|
3396
|
+
await manager.delete(args.id);
|
|
3397
|
+
console.log(`${prefix} ${ok(`Skill "${args.id}" deleted.`)}`);
|
|
3398
|
+
}
|
|
3399
|
+
});
|
|
3400
|
+
var logsCommand = defineCommand3({
|
|
3401
|
+
meta: { name: "logs", description: "Show recent execution logs for a skill" },
|
|
3402
|
+
args: {
|
|
3403
|
+
config: configArg,
|
|
3404
|
+
id: { type: "positional", description: "Skill ID", required: true }
|
|
3405
|
+
},
|
|
3406
|
+
async run({ args }) {
|
|
3407
|
+
validateSkillId(args.id);
|
|
3408
|
+
const { manager } = await createManager(args.config);
|
|
3409
|
+
const runs = await manager.getRuns(args.id, 10);
|
|
3410
|
+
if (runs.length === 0) {
|
|
3411
|
+
console.log(`${prefix} ${dim(`No runs found for skill "${args.id}".`)}`);
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
console.log(bold(
|
|
3415
|
+
["TIMESTAMP".padEnd(26), "OK".padEnd(6), "DURATION".padEnd(12), "OUTPUT"].join("")
|
|
3416
|
+
));
|
|
3417
|
+
for (const r of runs) {
|
|
3418
|
+
const status = r.success ? ok("yes") : err("no ");
|
|
3419
|
+
const output = (r.output ?? r.error ?? "").slice(0, 60);
|
|
3420
|
+
console.log(
|
|
3421
|
+
[
|
|
3422
|
+
dim(r.timestamp.slice(0, 24).padEnd(26)),
|
|
3423
|
+
status + " ".repeat(Math.max(0, 6 - (r.success ? 3 : 3))),
|
|
3424
|
+
dim(`${r.durationMs}ms`.padEnd(12)),
|
|
3425
|
+
r.success ? output : err(output)
|
|
3426
|
+
].join("")
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3431
|
+
var skillsCommand = defineCommand3({
|
|
3432
|
+
meta: { name: "skills", description: "Manage skills" },
|
|
3433
|
+
subCommands: {
|
|
3434
|
+
list: listCommand,
|
|
3435
|
+
show: showCommand,
|
|
3436
|
+
run: runCommand,
|
|
3437
|
+
pause: pauseCommand,
|
|
3438
|
+
resume: resumeCommand,
|
|
3439
|
+
delete: deleteCommand,
|
|
3440
|
+
logs: logsCommand
|
|
3441
|
+
}
|
|
3442
|
+
});
|
|
3443
|
+
|
|
3444
|
+
// src/bin.ts
|
|
3445
|
+
var require2 = createRequire(import.meta.url);
|
|
3446
|
+
var { version } = require2("../package.json");
|
|
3447
|
+
var main = defineCommand4({
|
|
3448
|
+
meta: {
|
|
3449
|
+
name: "augure",
|
|
3450
|
+
description: "Augure \u2014 your proactive AI agent",
|
|
3451
|
+
version
|
|
3452
|
+
},
|
|
3453
|
+
subCommands: {
|
|
3454
|
+
start: startCommand,
|
|
3455
|
+
init: initCommand,
|
|
3456
|
+
skills: skillsCommand
|
|
3457
|
+
}
|
|
20
3458
|
});
|
|
21
3459
|
runMain(main);
|
|
22
|
-
//# sourceMappingURL=bin.js.map
|