augure 0.5.0 → 0.6.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 +1035 -80
- package/package.json +10 -9
package/dist/bin.js
CHANGED
|
@@ -135,6 +135,11 @@ var AppConfigSchema = z.object({
|
|
|
135
135
|
checkInterval: z.string().min(1).default("24h"),
|
|
136
136
|
notifyChannel: z.string().min(1).default("telegram")
|
|
137
137
|
}).optional()
|
|
138
|
+
}).optional(),
|
|
139
|
+
codeMode: z.object({
|
|
140
|
+
runtime: z.enum(["vm", "docker", "auto"]).default("auto"),
|
|
141
|
+
timeout: z.number().int().positive().default(30),
|
|
142
|
+
memoryLimit: z.number().int().positive().default(128)
|
|
138
143
|
}).optional()
|
|
139
144
|
});
|
|
140
145
|
function interpolateEnvVars(raw) {
|
|
@@ -153,19 +158,33 @@ async function loadConfig(path) {
|
|
|
153
158
|
return AppConfigSchema.parse(parsed);
|
|
154
159
|
}
|
|
155
160
|
|
|
161
|
+
// ../types/dist/logger.js
|
|
162
|
+
var noop = () => {
|
|
163
|
+
};
|
|
164
|
+
var noopLogger = {
|
|
165
|
+
debug: noop,
|
|
166
|
+
info: noop,
|
|
167
|
+
warn: noop,
|
|
168
|
+
error: noop,
|
|
169
|
+
child: () => noopLogger
|
|
170
|
+
};
|
|
171
|
+
|
|
156
172
|
// ../core/dist/llm.js
|
|
157
173
|
var OpenRouterClient = class {
|
|
158
174
|
apiKey;
|
|
159
175
|
model;
|
|
160
176
|
maxTokens;
|
|
161
177
|
baseUrl;
|
|
178
|
+
log;
|
|
162
179
|
constructor(config) {
|
|
163
180
|
this.apiKey = config.apiKey;
|
|
164
181
|
this.model = config.model;
|
|
165
182
|
this.maxTokens = config.maxTokens;
|
|
166
183
|
this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
184
|
+
this.log = config.logger ?? noopLogger;
|
|
167
185
|
}
|
|
168
186
|
async chat(messages, tools) {
|
|
187
|
+
this.log.debug(`Request: model=${this.model} messages=${messages.length} tools=${tools?.length ?? 0}`);
|
|
169
188
|
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
170
189
|
method: "POST",
|
|
171
190
|
headers: {
|
|
@@ -199,6 +218,7 @@ var OpenRouterClient = class {
|
|
|
199
218
|
}
|
|
200
219
|
const data = await response.json();
|
|
201
220
|
const choice = data.choices[0];
|
|
221
|
+
this.log.debug(`Response: ${response.status} ${data.usage.prompt_tokens}+${data.usage.completion_tokens} tokens`);
|
|
202
222
|
return {
|
|
203
223
|
content: choice.message.content ?? "",
|
|
204
224
|
toolCalls: (choice.message.tool_calls ?? []).map((tc) => {
|
|
@@ -206,7 +226,7 @@ var OpenRouterClient = class {
|
|
|
206
226
|
try {
|
|
207
227
|
args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
208
228
|
} catch {
|
|
209
|
-
|
|
229
|
+
this.log.warn(`Failed to parse tool call arguments for ${tc.function.name}`);
|
|
210
230
|
}
|
|
211
231
|
return { id: tc.id, name: tc.function.name, arguments: args };
|
|
212
232
|
}),
|
|
@@ -222,6 +242,19 @@ var OpenRouterClient = class {
|
|
|
222
242
|
function assembleContext(input) {
|
|
223
243
|
const { systemPrompt, memoryContent, conversationHistory, persona } = input;
|
|
224
244
|
let system = systemPrompt;
|
|
245
|
+
const now = /* @__PURE__ */ new Date();
|
|
246
|
+
const humanDate = new Intl.DateTimeFormat("en-US", {
|
|
247
|
+
weekday: "long",
|
|
248
|
+
year: "numeric",
|
|
249
|
+
month: "long",
|
|
250
|
+
day: "numeric",
|
|
251
|
+
hour: "2-digit",
|
|
252
|
+
minute: "2-digit",
|
|
253
|
+
timeZoneName: "short"
|
|
254
|
+
}).format(now);
|
|
255
|
+
system += `
|
|
256
|
+
|
|
257
|
+
Current date and time: ${now.toISOString()} (${humanDate})`;
|
|
225
258
|
if (persona) {
|
|
226
259
|
system += `
|
|
227
260
|
|
|
@@ -239,6 +272,403 @@ ${memoryContent}`;
|
|
|
239
272
|
return messages;
|
|
240
273
|
}
|
|
241
274
|
|
|
275
|
+
// ../code-mode/dist/typegen.js
|
|
276
|
+
var JSON_TO_TS = {
|
|
277
|
+
string: "string",
|
|
278
|
+
number: "number",
|
|
279
|
+
integer: "number",
|
|
280
|
+
boolean: "boolean",
|
|
281
|
+
array: "unknown[]",
|
|
282
|
+
object: "Record<string, unknown>"
|
|
283
|
+
};
|
|
284
|
+
function sanitizeName(name) {
|
|
285
|
+
return name.replace(/[-. ]/g, "_");
|
|
286
|
+
}
|
|
287
|
+
function toPascalCase(name) {
|
|
288
|
+
return sanitizeName(name).split("_").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
289
|
+
}
|
|
290
|
+
function mapType(schema) {
|
|
291
|
+
const t = schema.type;
|
|
292
|
+
if (schema.enum) {
|
|
293
|
+
return schema.enum.map((v) => `"${v}"`).join(" | ");
|
|
294
|
+
}
|
|
295
|
+
return JSON_TO_TS[t ?? "string"] ?? "unknown";
|
|
296
|
+
}
|
|
297
|
+
function generateDeclarations(registry) {
|
|
298
|
+
const tools = registry.list();
|
|
299
|
+
const blocks = [];
|
|
300
|
+
const apiEntries = [];
|
|
301
|
+
for (const tool of tools) {
|
|
302
|
+
const safeName = sanitizeName(tool.name);
|
|
303
|
+
const interfaceName = `${toPascalCase(tool.name)}Input`;
|
|
304
|
+
const params = tool.parameters;
|
|
305
|
+
const properties = params.properties ?? {};
|
|
306
|
+
const required = new Set(params.required ?? []);
|
|
307
|
+
const fields = [];
|
|
308
|
+
for (const [key, schema] of Object.entries(properties)) {
|
|
309
|
+
const optional = required.has(key) ? "" : "?";
|
|
310
|
+
const tsType = mapType(schema);
|
|
311
|
+
const desc = schema.description;
|
|
312
|
+
if (desc) {
|
|
313
|
+
fields.push(` /** ${desc} */
|
|
314
|
+
${key}${optional}: ${tsType};`);
|
|
315
|
+
} else {
|
|
316
|
+
fields.push(` ${key}${optional}: ${tsType};`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
blocks.push(`interface ${interfaceName} {
|
|
320
|
+
${fields.join("\n")}
|
|
321
|
+
}`);
|
|
322
|
+
apiEntries.push(` /** ${tool.description} */
|
|
323
|
+
${safeName}: (input: ${interfaceName}) => Promise<{ success: boolean; output: string }>;`);
|
|
324
|
+
}
|
|
325
|
+
const apiBlock = `declare const api: {
|
|
326
|
+
${apiEntries.join("\n")}
|
|
327
|
+
};`;
|
|
328
|
+
return [...blocks, "", apiBlock].join("\n");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ../code-mode/dist/bridge.js
|
|
332
|
+
function createBridgeHandler(registry) {
|
|
333
|
+
return async (toolName, input) => {
|
|
334
|
+
try {
|
|
335
|
+
return await registry.execute(toolName, input);
|
|
336
|
+
} catch (err2) {
|
|
337
|
+
return {
|
|
338
|
+
success: false,
|
|
339
|
+
output: `Bridge error calling ${toolName}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function generateHarnessCode(userCode) {
|
|
345
|
+
return `
|
|
346
|
+
const __logs = [];
|
|
347
|
+
const __originalLog = console.log;
|
|
348
|
+
const __originalWarn = console.warn;
|
|
349
|
+
const __originalError = console.error;
|
|
350
|
+
console.log = (...args) => __logs.push(args.map(String).join(" "));
|
|
351
|
+
console.warn = (...args) => __logs.push("[warn] " + args.map(String).join(" "));
|
|
352
|
+
console.error = (...args) => __logs.push("[error] " + args.map(String).join(" "));
|
|
353
|
+
|
|
354
|
+
let __toolCalls = 0;
|
|
355
|
+
|
|
356
|
+
const api = new Proxy({}, {
|
|
357
|
+
get: (_target, toolName) => {
|
|
358
|
+
return async (input) => {
|
|
359
|
+
__toolCalls++;
|
|
360
|
+
return await __bridge(String(toolName), input);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
async function __run() {
|
|
366
|
+
${userCode}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const __result = await __run();
|
|
371
|
+
__originalLog(JSON.stringify({
|
|
372
|
+
success: true,
|
|
373
|
+
output: __result,
|
|
374
|
+
logs: __logs,
|
|
375
|
+
toolCalls: __toolCalls,
|
|
376
|
+
}));
|
|
377
|
+
} catch (err) {
|
|
378
|
+
__originalLog(JSON.stringify({
|
|
379
|
+
success: false,
|
|
380
|
+
error: err.message ?? String(err),
|
|
381
|
+
logs: __logs,
|
|
382
|
+
toolCalls: __toolCalls,
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ../code-mode/dist/vm-sandbox.js
|
|
389
|
+
import { createContext, runInContext } from "vm";
|
|
390
|
+
import { transform } from "esbuild";
|
|
391
|
+
var VmExecutor = class {
|
|
392
|
+
registry;
|
|
393
|
+
config;
|
|
394
|
+
constructor(registry, config) {
|
|
395
|
+
this.registry = registry;
|
|
396
|
+
this.config = config;
|
|
397
|
+
}
|
|
398
|
+
async execute(code) {
|
|
399
|
+
const start = performance.now();
|
|
400
|
+
try {
|
|
401
|
+
const harnessTs = generateHarnessCode(code);
|
|
402
|
+
const { code: harnessJs } = await transform(harnessTs, {
|
|
403
|
+
loader: "ts",
|
|
404
|
+
target: "es2024"
|
|
405
|
+
});
|
|
406
|
+
const bridgeHandler = createBridgeHandler(this.registry);
|
|
407
|
+
const consoleLogs = [];
|
|
408
|
+
const captureConsole = {
|
|
409
|
+
log: (...args) => consoleLogs.push(args.map(String).join(" ")),
|
|
410
|
+
warn: (...args) => consoleLogs.push("[warn] " + args.map(String).join(" ")),
|
|
411
|
+
error: (...args) => consoleLogs.push("[error] " + args.map(String).join(" "))
|
|
412
|
+
};
|
|
413
|
+
const context = createContext({
|
|
414
|
+
console: captureConsole,
|
|
415
|
+
__bridge: bridgeHandler,
|
|
416
|
+
JSON,
|
|
417
|
+
String,
|
|
418
|
+
Number,
|
|
419
|
+
Boolean,
|
|
420
|
+
Array,
|
|
421
|
+
Object,
|
|
422
|
+
Error,
|
|
423
|
+
Promise,
|
|
424
|
+
Map,
|
|
425
|
+
Set,
|
|
426
|
+
parseInt,
|
|
427
|
+
parseFloat,
|
|
428
|
+
isNaN,
|
|
429
|
+
isFinite,
|
|
430
|
+
setTimeout,
|
|
431
|
+
Date,
|
|
432
|
+
RegExp,
|
|
433
|
+
Math,
|
|
434
|
+
Symbol,
|
|
435
|
+
Uint8Array,
|
|
436
|
+
TextEncoder,
|
|
437
|
+
TextDecoder,
|
|
438
|
+
Buffer,
|
|
439
|
+
URL,
|
|
440
|
+
URLSearchParams
|
|
441
|
+
});
|
|
442
|
+
const wrappedCode = `(async () => { ${harnessJs} })()`;
|
|
443
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
444
|
+
const timer = setTimeout(() => reject(new Error("Timeout: code execution exceeded time limit")), this.config.timeout);
|
|
445
|
+
timer.unref?.();
|
|
446
|
+
});
|
|
447
|
+
const resultPromise = runInContext(wrappedCode, context, {
|
|
448
|
+
timeout: this.config.timeout
|
|
449
|
+
});
|
|
450
|
+
await Promise.race([resultPromise, timeoutPromise]);
|
|
451
|
+
const durationMs = performance.now() - start;
|
|
452
|
+
const lastLine = consoleLogs[consoleLogs.length - 1];
|
|
453
|
+
if (!lastLine) {
|
|
454
|
+
return {
|
|
455
|
+
success: false,
|
|
456
|
+
output: void 0,
|
|
457
|
+
logs: consoleLogs,
|
|
458
|
+
error: "No output produced by code execution",
|
|
459
|
+
durationMs,
|
|
460
|
+
toolCalls: 0
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const parsed = JSON.parse(lastLine);
|
|
464
|
+
return {
|
|
465
|
+
success: parsed.success,
|
|
466
|
+
output: parsed.output,
|
|
467
|
+
logs: parsed.logs,
|
|
468
|
+
error: parsed.error,
|
|
469
|
+
durationMs,
|
|
470
|
+
toolCalls: parsed.toolCalls
|
|
471
|
+
};
|
|
472
|
+
} catch (err2) {
|
|
473
|
+
const durationMs = performance.now() - start;
|
|
474
|
+
return {
|
|
475
|
+
success: false,
|
|
476
|
+
output: void 0,
|
|
477
|
+
logs: [],
|
|
478
|
+
error: err2 instanceof Error ? err2.message : String(err2),
|
|
479
|
+
durationMs,
|
|
480
|
+
toolCalls: 0
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// ../code-mode/dist/docker-sandbox.js
|
|
487
|
+
var DOCKER_HARNESS = `
|
|
488
|
+
import { readFile } from "node:fs/promises";
|
|
489
|
+
|
|
490
|
+
const __logs = [];
|
|
491
|
+
const __originalLog = console.log;
|
|
492
|
+
console.log = (...args) => __logs.push(args.map(String).join(" "));
|
|
493
|
+
console.warn = (...args) => __logs.push("[warn] " + args.map(String).join(" "));
|
|
494
|
+
console.error = (...args) => __logs.push("[error] " + args.map(String).join(" "));
|
|
495
|
+
|
|
496
|
+
let __toolCalls = 0;
|
|
497
|
+
|
|
498
|
+
const api = new Proxy({}, {
|
|
499
|
+
get: (_target, toolName) => {
|
|
500
|
+
return async () => {
|
|
501
|
+
__toolCalls++;
|
|
502
|
+
return { success: false, output: "Tool calls not yet supported in Docker executor" };
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const __userCode = await readFile("/workspace/user-code.js", "utf-8");
|
|
508
|
+
const __fn = new Function("api", "__logs",
|
|
509
|
+
"return (async () => { " + __userCode + " })();"
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const __result = await __fn(api, __logs);
|
|
514
|
+
__originalLog(JSON.stringify({
|
|
515
|
+
success: true,
|
|
516
|
+
output: __result,
|
|
517
|
+
logs: __logs,
|
|
518
|
+
toolCalls: __toolCalls,
|
|
519
|
+
}));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
__originalLog(JSON.stringify({
|
|
522
|
+
success: false,
|
|
523
|
+
error: err.message ?? String(err),
|
|
524
|
+
logs: __logs,
|
|
525
|
+
toolCalls: __toolCalls,
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
`;
|
|
529
|
+
var DockerExecutor = class {
|
|
530
|
+
config;
|
|
531
|
+
constructor(config) {
|
|
532
|
+
this.config = config;
|
|
533
|
+
}
|
|
534
|
+
async execute(code) {
|
|
535
|
+
const start = Date.now();
|
|
536
|
+
let container;
|
|
537
|
+
try {
|
|
538
|
+
container = await this.config.pool.acquire({
|
|
539
|
+
trust: "sandboxed",
|
|
540
|
+
timeout: this.config.timeout,
|
|
541
|
+
memory: this.config.memoryLimit,
|
|
542
|
+
cpu: this.config.cpuLimit
|
|
543
|
+
});
|
|
544
|
+
} catch (err2) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
output: void 0,
|
|
548
|
+
logs: [],
|
|
549
|
+
toolCalls: 0,
|
|
550
|
+
error: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
551
|
+
durationMs: Date.now() - start
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
await container.exec("mkdir -p /workspace");
|
|
556
|
+
const codeB64 = Buffer.from(code).toString("base64");
|
|
557
|
+
await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/user-code.js'`);
|
|
558
|
+
const harnessB64 = Buffer.from(DOCKER_HARNESS).toString("base64");
|
|
559
|
+
await container.exec(`sh -c 'echo "${harnessB64}" | base64 -d > /workspace/harness.ts'`);
|
|
560
|
+
const execResult = await container.exec("npx tsx /workspace/harness.ts", {
|
|
561
|
+
timeout: this.config.timeout,
|
|
562
|
+
cwd: "/workspace"
|
|
563
|
+
});
|
|
564
|
+
if (execResult.exitCode === 0 && execResult.stdout.trim()) {
|
|
565
|
+
try {
|
|
566
|
+
const lastLine = execResult.stdout.trim().split("\n").pop();
|
|
567
|
+
const parsed = JSON.parse(lastLine);
|
|
568
|
+
return {
|
|
569
|
+
success: parsed.success,
|
|
570
|
+
output: parsed.output,
|
|
571
|
+
logs: parsed.logs ?? [],
|
|
572
|
+
error: parsed.error,
|
|
573
|
+
durationMs: Date.now() - start,
|
|
574
|
+
toolCalls: parsed.toolCalls ?? 0
|
|
575
|
+
};
|
|
576
|
+
} catch {
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
output: execResult.stdout.trim(),
|
|
580
|
+
logs: [],
|
|
581
|
+
durationMs: Date.now() - start,
|
|
582
|
+
toolCalls: 0
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
success: false,
|
|
588
|
+
output: void 0,
|
|
589
|
+
logs: [],
|
|
590
|
+
toolCalls: 0,
|
|
591
|
+
error: execResult.stderr || execResult.stdout || "Unknown error",
|
|
592
|
+
durationMs: Date.now() - start
|
|
593
|
+
};
|
|
594
|
+
} catch (err2) {
|
|
595
|
+
return {
|
|
596
|
+
success: false,
|
|
597
|
+
output: void 0,
|
|
598
|
+
logs: [],
|
|
599
|
+
toolCalls: 0,
|
|
600
|
+
error: err2 instanceof Error ? err2.message : String(err2),
|
|
601
|
+
durationMs: Date.now() - start
|
|
602
|
+
};
|
|
603
|
+
} finally {
|
|
604
|
+
await this.config.pool.release(container);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// ../code-mode/dist/tool.js
|
|
610
|
+
function createCodeModeTool(registry, executor) {
|
|
611
|
+
const declarations = generateDeclarations(registry);
|
|
612
|
+
return {
|
|
613
|
+
name: "execute_code",
|
|
614
|
+
description: `Execute TypeScript code with access to the agent's APIs. Write the body of an async function.
|
|
615
|
+
|
|
616
|
+
Available APIs:
|
|
617
|
+
|
|
618
|
+
\`\`\`typescript
|
|
619
|
+
${declarations}
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
Each API call returns { success: boolean, output: string }.
|
|
623
|
+
Use console.log() for intermediate output. Return your final result.`,
|
|
624
|
+
parameters: {
|
|
625
|
+
type: "object",
|
|
626
|
+
properties: {
|
|
627
|
+
code: {
|
|
628
|
+
type: "string",
|
|
629
|
+
description: "The body of an async TypeScript function. Use the 'api' object to call tools."
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
required: ["code"]
|
|
633
|
+
},
|
|
634
|
+
execute: async (params) => {
|
|
635
|
+
const { code } = params;
|
|
636
|
+
const result = await executor.execute(code);
|
|
637
|
+
if (result.success) {
|
|
638
|
+
const parts = [];
|
|
639
|
+
if (result.logs.length > 0) {
|
|
640
|
+
parts.push(`[logs]
|
|
641
|
+
${result.logs.join("\n")}`);
|
|
642
|
+
}
|
|
643
|
+
parts.push(typeof result.output === "string" ? result.output : JSON.stringify(result.output));
|
|
644
|
+
return { success: true, output: parts.join("\n\n") };
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
success: false,
|
|
648
|
+
output: result.error ?? "Code execution failed"
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ../code-mode/dist/auto-executor.js
|
|
655
|
+
var AutoExecutor = class {
|
|
656
|
+
primary;
|
|
657
|
+
fallback;
|
|
658
|
+
constructor(primary, fallback) {
|
|
659
|
+
this.primary = primary;
|
|
660
|
+
this.fallback = fallback;
|
|
661
|
+
}
|
|
662
|
+
async execute(code) {
|
|
663
|
+
try {
|
|
664
|
+
const result = await this.primary.execute(code);
|
|
665
|
+
return result;
|
|
666
|
+
} catch {
|
|
667
|
+
return this.fallback.execute(code);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
|
|
242
672
|
// ../core/dist/audit.js
|
|
243
673
|
import { appendFile, mkdir } from "fs/promises";
|
|
244
674
|
import { join } from "path";
|
|
@@ -249,13 +679,15 @@ function summarize(text, maxLen = 200) {
|
|
|
249
679
|
}
|
|
250
680
|
var FileAuditLogger = class {
|
|
251
681
|
basePath;
|
|
682
|
+
logger;
|
|
252
683
|
pendingWrite = Promise.resolve();
|
|
253
684
|
initialized = false;
|
|
254
|
-
constructor(basePath) {
|
|
685
|
+
constructor(basePath, logger) {
|
|
255
686
|
this.basePath = basePath;
|
|
687
|
+
this.logger = logger ?? noopLogger;
|
|
256
688
|
}
|
|
257
689
|
log(entry) {
|
|
258
|
-
this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) =>
|
|
690
|
+
this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) => this.logger.error("Audit write error:", err2));
|
|
259
691
|
}
|
|
260
692
|
async close() {
|
|
261
693
|
await this.pendingWrite;
|
|
@@ -281,10 +713,12 @@ var NullAuditLogger = class {
|
|
|
281
713
|
// ../core/dist/agent.js
|
|
282
714
|
var Agent = class {
|
|
283
715
|
config;
|
|
716
|
+
log;
|
|
284
717
|
conversations = /* @__PURE__ */ new Map();
|
|
285
718
|
state = "running";
|
|
286
719
|
constructor(config) {
|
|
287
720
|
this.config = config;
|
|
721
|
+
this.log = config.logger ?? noopLogger;
|
|
288
722
|
}
|
|
289
723
|
getState() {
|
|
290
724
|
return this.state;
|
|
@@ -317,6 +751,19 @@ var Agent = class {
|
|
|
317
751
|
const maxLoops = this.config.maxToolLoops ?? 10;
|
|
318
752
|
let loopCount = 0;
|
|
319
753
|
const toolSchemas = this.config.tools.toFunctionSchemas();
|
|
754
|
+
let effectiveSchemas = toolSchemas;
|
|
755
|
+
let codeModeTool;
|
|
756
|
+
if (this.config.codeModeExecutor) {
|
|
757
|
+
codeModeTool = createCodeModeTool(this.config.tools, this.config.codeModeExecutor);
|
|
758
|
+
effectiveSchemas = [{
|
|
759
|
+
type: "function",
|
|
760
|
+
function: {
|
|
761
|
+
name: codeModeTool.name,
|
|
762
|
+
description: codeModeTool.description,
|
|
763
|
+
parameters: codeModeTool.parameters
|
|
764
|
+
}
|
|
765
|
+
}];
|
|
766
|
+
}
|
|
320
767
|
while (loopCount < maxLoops) {
|
|
321
768
|
const messages = assembleContext({
|
|
322
769
|
systemPrompt: this.config.systemPrompt,
|
|
@@ -324,12 +771,14 @@ var Agent = class {
|
|
|
324
771
|
conversationHistory: history,
|
|
325
772
|
persona: this.config.persona
|
|
326
773
|
});
|
|
327
|
-
|
|
774
|
+
this.log.debug(`LLM call #${loopCount + 1} (${messages.length} messages)`);
|
|
775
|
+
const response = await this.config.llm.chat(messages, effectiveSchemas);
|
|
328
776
|
if (response.toolCalls.length === 0) {
|
|
329
777
|
history.push({
|
|
330
778
|
role: "assistant",
|
|
331
779
|
content: response.content
|
|
332
780
|
});
|
|
781
|
+
this.log.debug(`Response: ${response.usage.inputTokens}+${response.usage.outputTokens} tokens, ${Date.now() - start}ms`);
|
|
333
782
|
if (this.config.audit) {
|
|
334
783
|
this.config.audit.log({
|
|
335
784
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -347,7 +796,7 @@ var Agent = class {
|
|
|
347
796
|
});
|
|
348
797
|
}
|
|
349
798
|
if (this.config.ingester) {
|
|
350
|
-
this.config.ingester.ingest(history).catch((err2) =>
|
|
799
|
+
this.config.ingester.ingest(history).catch((err2) => this.log.error("Ingestion error:", err2));
|
|
351
800
|
}
|
|
352
801
|
return response.content;
|
|
353
802
|
}
|
|
@@ -358,7 +807,14 @@ var Agent = class {
|
|
|
358
807
|
});
|
|
359
808
|
for (const toolCall of response.toolCalls) {
|
|
360
809
|
const toolStart = Date.now();
|
|
361
|
-
|
|
810
|
+
this.log.debug(`Tool: ${toolCall.name}`);
|
|
811
|
+
let result;
|
|
812
|
+
if (codeModeTool && toolCall.name === "execute_code") {
|
|
813
|
+
result = await codeModeTool.execute(toolCall.arguments, {});
|
|
814
|
+
} else {
|
|
815
|
+
result = await this.config.tools.execute(toolCall.name, toolCall.arguments);
|
|
816
|
+
}
|
|
817
|
+
this.log.debug(`Tool ${toolCall.name}: ${result.success ? "ok" : "fail"} (${Date.now() - toolStart}ms)`);
|
|
362
818
|
history.push({
|
|
363
819
|
role: "tool",
|
|
364
820
|
content: result.output,
|
|
@@ -566,6 +1022,56 @@ var ContextGuard = class {
|
|
|
566
1022
|
}
|
|
567
1023
|
};
|
|
568
1024
|
|
|
1025
|
+
// ../core/dist/logger.js
|
|
1026
|
+
import { styleText } from "util";
|
|
1027
|
+
var LEVELS = {
|
|
1028
|
+
debug: 0,
|
|
1029
|
+
info: 1,
|
|
1030
|
+
warn: 2,
|
|
1031
|
+
error: 3,
|
|
1032
|
+
silent: 4
|
|
1033
|
+
};
|
|
1034
|
+
function tag(level) {
|
|
1035
|
+
switch (level) {
|
|
1036
|
+
case "debug":
|
|
1037
|
+
return styleText("magenta", "DBG");
|
|
1038
|
+
case "info":
|
|
1039
|
+
return styleText("cyan", "INF");
|
|
1040
|
+
case "warn":
|
|
1041
|
+
return styleText("yellow", "WRN");
|
|
1042
|
+
case "error":
|
|
1043
|
+
return styleText("red", "ERR");
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function createLogger(opts = {}) {
|
|
1047
|
+
const min = LEVELS[opts.level ?? "info"];
|
|
1048
|
+
const scope = opts.scope;
|
|
1049
|
+
function emit(level, msg, args) {
|
|
1050
|
+
if (LEVELS[level] < min)
|
|
1051
|
+
return;
|
|
1052
|
+
const ts = styleText("dim", (/* @__PURE__ */ new Date()).toISOString().slice(11, 23));
|
|
1053
|
+
const lvl = tag(level);
|
|
1054
|
+
const sc = scope ? ` ${styleText("dim", scope)}` : "";
|
|
1055
|
+
const prefix2 = `${styleText("yellow", "\u25B2")} ${ts} ${lvl}${sc}`;
|
|
1056
|
+
const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
1057
|
+
if (args.length > 0) {
|
|
1058
|
+
fn(prefix2, msg, ...args);
|
|
1059
|
+
} else {
|
|
1060
|
+
fn(prefix2, msg);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return {
|
|
1064
|
+
debug: (msg, ...args) => emit("debug", msg, args),
|
|
1065
|
+
info: (msg, ...args) => emit("info", msg, args),
|
|
1066
|
+
warn: (msg, ...args) => emit("warn", msg, args),
|
|
1067
|
+
error: (msg, ...args) => emit("error", msg, args),
|
|
1068
|
+
child: (childScope) => createLogger({
|
|
1069
|
+
level: opts.level,
|
|
1070
|
+
scope: scope ? `${scope}:${childScope}` : childScope
|
|
1071
|
+
})
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
|
|
569
1075
|
// ../channels/dist/telegram/telegram.js
|
|
570
1076
|
import { Bot } from "grammy";
|
|
571
1077
|
|
|
@@ -786,11 +1292,13 @@ var TelegramChannel = class {
|
|
|
786
1292
|
allowedUsers;
|
|
787
1293
|
handlers = [];
|
|
788
1294
|
sendPipeline;
|
|
1295
|
+
log;
|
|
789
1296
|
constructor(config) {
|
|
1297
|
+
this.log = config.logger ?? noopLogger;
|
|
790
1298
|
this.bot = new Bot(config.botToken);
|
|
791
1299
|
this.allowedUsers = new Set(config.allowedUsers);
|
|
792
1300
|
this.bot.catch((err2) => {
|
|
793
|
-
|
|
1301
|
+
this.log.error("Bot error:", err2.message ?? err2);
|
|
794
1302
|
});
|
|
795
1303
|
this.bot.on("message:text", async (ctx) => {
|
|
796
1304
|
const userId = ctx.from.id;
|
|
@@ -819,7 +1327,7 @@ var TelegramChannel = class {
|
|
|
819
1327
|
...replyOpts
|
|
820
1328
|
}), { maxRetries: 3, baseDelayMs: 500 }).catch(async () => {
|
|
821
1329
|
await this.bot.api.sendMessage(Number(msg.userId), msg.text, replyOpts).catch((fallbackErr) => {
|
|
822
|
-
|
|
1330
|
+
this.log.error("Fallback send also failed:", fallbackErr);
|
|
823
1331
|
throw fallbackErr;
|
|
824
1332
|
});
|
|
825
1333
|
});
|
|
@@ -830,7 +1338,7 @@ var TelegramChannel = class {
|
|
|
830
1338
|
return this.allowedUsers.has(userId);
|
|
831
1339
|
}
|
|
832
1340
|
handleRejected(userId, unixTimestamp, rejectMessage) {
|
|
833
|
-
|
|
1341
|
+
this.log.warn(`Rejected message from unauthorized user ${userId} at ${new Date(unixTimestamp * 1e3).toISOString()}`);
|
|
834
1342
|
if (rejectMessage) {
|
|
835
1343
|
this.bot.api.sendMessage(userId, rejectMessage).catch(() => {
|
|
836
1344
|
});
|
|
@@ -864,14 +1372,27 @@ var ToolRegistry = class {
|
|
|
864
1372
|
return Array.from(this.tools.values());
|
|
865
1373
|
}
|
|
866
1374
|
toFunctionSchemas() {
|
|
867
|
-
return this.list().map((tool) =>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1375
|
+
return this.list().map((tool) => {
|
|
1376
|
+
let description = tool.description;
|
|
1377
|
+
if (tool.configCheck && this.context) {
|
|
1378
|
+
try {
|
|
1379
|
+
const warning = tool.configCheck(this.context);
|
|
1380
|
+
if (warning) {
|
|
1381
|
+
description += `
|
|
1382
|
+
[NOT CONFIGURED] ${warning}`;
|
|
1383
|
+
}
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
873
1386
|
}
|
|
874
|
-
|
|
1387
|
+
return {
|
|
1388
|
+
type: "function",
|
|
1389
|
+
function: {
|
|
1390
|
+
name: tool.name,
|
|
1391
|
+
description,
|
|
1392
|
+
parameters: tool.parameters
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
});
|
|
875
1396
|
}
|
|
876
1397
|
async execute(name, params) {
|
|
877
1398
|
const tool = this.tools.get(name);
|
|
@@ -1011,10 +1532,54 @@ var scheduleTool = {
|
|
|
1011
1532
|
}
|
|
1012
1533
|
};
|
|
1013
1534
|
|
|
1535
|
+
// ../tools/dist/datetime.js
|
|
1536
|
+
var datetimeTool = {
|
|
1537
|
+
name: "datetime",
|
|
1538
|
+
description: "Get the current date and time, optionally in a specific timezone",
|
|
1539
|
+
parameters: {
|
|
1540
|
+
type: "object",
|
|
1541
|
+
properties: {
|
|
1542
|
+
timezone: {
|
|
1543
|
+
type: "string",
|
|
1544
|
+
description: "IANA timezone (e.g. 'Europe/Paris', 'America/New_York'). Defaults to the system timezone."
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
execute: async (params) => {
|
|
1549
|
+
const { timezone } = params;
|
|
1550
|
+
const now = /* @__PURE__ */ new Date();
|
|
1551
|
+
const options = {
|
|
1552
|
+
weekday: "long",
|
|
1553
|
+
year: "numeric",
|
|
1554
|
+
month: "long",
|
|
1555
|
+
day: "numeric",
|
|
1556
|
+
hour: "2-digit",
|
|
1557
|
+
minute: "2-digit",
|
|
1558
|
+
second: "2-digit",
|
|
1559
|
+
timeZoneName: "longOffset"
|
|
1560
|
+
};
|
|
1561
|
+
if (timezone) {
|
|
1562
|
+
options.timeZone = timezone;
|
|
1563
|
+
}
|
|
1564
|
+
try {
|
|
1565
|
+
const formatted = new Intl.DateTimeFormat("en-US", options).format(now);
|
|
1566
|
+
return {
|
|
1567
|
+
success: true,
|
|
1568
|
+
output: `${formatted}
|
|
1569
|
+
ISO 8601 (UTC): ${now.toISOString()}
|
|
1570
|
+
Unix timestamp: ${Math.floor(now.getTime() / 1e3)}`
|
|
1571
|
+
};
|
|
1572
|
+
} catch {
|
|
1573
|
+
return { success: false, output: `Invalid timezone: ${timezone}` };
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1014
1578
|
// ../tools/dist/web-search.js
|
|
1015
1579
|
var webSearchTool = {
|
|
1016
1580
|
name: "web_search",
|
|
1017
1581
|
description: "Search the web using the configured search provider (Tavily, Exa, or SearXNG)",
|
|
1582
|
+
configCheck: (ctx) => ctx.config.tools?.webSearch ? null : "This tool requires configuration. See https://augure.dev/docs/tools/web-search",
|
|
1018
1583
|
parameters: {
|
|
1019
1584
|
type: "object",
|
|
1020
1585
|
properties: {
|
|
@@ -1226,6 +1791,273 @@ ${text}`;
|
|
|
1226
1791
|
// ../tools/dist/email.js
|
|
1227
1792
|
import { ImapFlow } from "imapflow";
|
|
1228
1793
|
import { createTransport } from "nodemailer";
|
|
1794
|
+
var MAX_BODY_CHARS = 4e3;
|
|
1795
|
+
function stripHtml(html) {
|
|
1796
|
+
return html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\n{3,}/g, "\n\n").trim();
|
|
1797
|
+
}
|
|
1798
|
+
function findTextPart(structure) {
|
|
1799
|
+
if (structure.type === "text/plain" && structure.part) {
|
|
1800
|
+
return { part: structure.part, isHtml: false };
|
|
1801
|
+
}
|
|
1802
|
+
if (structure.type === "text/html" && structure.part) {
|
|
1803
|
+
return { part: structure.part, isHtml: true };
|
|
1804
|
+
}
|
|
1805
|
+
if (structure.childNodes) {
|
|
1806
|
+
let htmlFallback = null;
|
|
1807
|
+
for (const child of structure.childNodes) {
|
|
1808
|
+
const found = findTextPart(child);
|
|
1809
|
+
if (found && !found.isHtml)
|
|
1810
|
+
return found;
|
|
1811
|
+
if (found && found.isHtml)
|
|
1812
|
+
htmlFallback = found;
|
|
1813
|
+
}
|
|
1814
|
+
return htmlFallback;
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
async function withImapClient(config, fn) {
|
|
1819
|
+
const client = new ImapFlow({
|
|
1820
|
+
host: config.host,
|
|
1821
|
+
port: config.port,
|
|
1822
|
+
secure: config.port === 993,
|
|
1823
|
+
auth: { user: config.user, pass: config.password },
|
|
1824
|
+
logger: false
|
|
1825
|
+
});
|
|
1826
|
+
let connected = false;
|
|
1827
|
+
try {
|
|
1828
|
+
await client.connect();
|
|
1829
|
+
connected = true;
|
|
1830
|
+
return await fn(client);
|
|
1831
|
+
} finally {
|
|
1832
|
+
if (connected) {
|
|
1833
|
+
await client.logout();
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
function formatSummaries(emails) {
|
|
1838
|
+
if (emails.length === 0)
|
|
1839
|
+
return "No emails found.";
|
|
1840
|
+
return emails.map((e, i) => `${i + 1}. ${e.seen ? "" : "[UNREAD] "}UID:${e.uid} Subject: ${e.subject} | From: ${e.from} | Date: ${e.date}`).join("\n");
|
|
1841
|
+
}
|
|
1842
|
+
function extractAddress(addr) {
|
|
1843
|
+
if (!addr)
|
|
1844
|
+
return "unknown";
|
|
1845
|
+
if (Array.isArray(addr)) {
|
|
1846
|
+
const first = addr[0];
|
|
1847
|
+
if (first && typeof first === "object" && "address" in first) {
|
|
1848
|
+
return first.address;
|
|
1849
|
+
}
|
|
1850
|
+
return String(first ?? "unknown");
|
|
1851
|
+
}
|
|
1852
|
+
if (typeof addr === "object" && "address" in addr) {
|
|
1853
|
+
return addr.address;
|
|
1854
|
+
}
|
|
1855
|
+
return String(addr);
|
|
1856
|
+
}
|
|
1857
|
+
async function handleList(params, imapConfig) {
|
|
1858
|
+
const folder = params.folder ?? "INBOX";
|
|
1859
|
+
const limit = params.limit ?? 10;
|
|
1860
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1861
|
+
const lock = await client.getMailboxLock(folder);
|
|
1862
|
+
try {
|
|
1863
|
+
const status = client.mailbox;
|
|
1864
|
+
if (!status || status.exists === 0)
|
|
1865
|
+
return "Mailbox is empty.";
|
|
1866
|
+
const start = Math.max(1, status.exists - limit + 1);
|
|
1867
|
+
const emails = [];
|
|
1868
|
+
for await (const msg of client.fetch(`${start}:*`, {
|
|
1869
|
+
envelope: true,
|
|
1870
|
+
flags: true
|
|
1871
|
+
})) {
|
|
1872
|
+
emails.push({
|
|
1873
|
+
uid: msg.uid,
|
|
1874
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
1875
|
+
from: extractAddress(msg.envelope?.from),
|
|
1876
|
+
date: msg.envelope?.date?.toISOString() ?? "unknown",
|
|
1877
|
+
seen: msg.flags?.has("\\Seen") ?? false
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
return formatSummaries(emails.slice(-limit));
|
|
1881
|
+
} finally {
|
|
1882
|
+
lock.release();
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
async function handleRead(params, imapConfig) {
|
|
1887
|
+
const folder = params.folder ?? "INBOX";
|
|
1888
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1889
|
+
const lock = await client.getMailboxLock(folder);
|
|
1890
|
+
try {
|
|
1891
|
+
const msg = await client.fetchOne(String(params.uid), {
|
|
1892
|
+
envelope: true,
|
|
1893
|
+
bodyStructure: true,
|
|
1894
|
+
flags: true,
|
|
1895
|
+
uid: true
|
|
1896
|
+
});
|
|
1897
|
+
if (!msg)
|
|
1898
|
+
return `No email found with UID ${params.uid}.`;
|
|
1899
|
+
let bodyText = "";
|
|
1900
|
+
const textPart = msg.bodyStructure ? findTextPart(msg.bodyStructure) : null;
|
|
1901
|
+
if (textPart) {
|
|
1902
|
+
const { content } = await client.download(String(params.uid), textPart.part, {
|
|
1903
|
+
uid: true
|
|
1904
|
+
});
|
|
1905
|
+
const chunks = [];
|
|
1906
|
+
for await (const chunk of content) {
|
|
1907
|
+
chunks.push(Buffer.from(chunk));
|
|
1908
|
+
}
|
|
1909
|
+
bodyText = Buffer.concat(chunks).toString("utf-8");
|
|
1910
|
+
if (textPart.isHtml)
|
|
1911
|
+
bodyText = stripHtml(bodyText);
|
|
1912
|
+
}
|
|
1913
|
+
if (bodyText.length > MAX_BODY_CHARS) {
|
|
1914
|
+
bodyText = bodyText.slice(0, MAX_BODY_CHARS) + "\n[truncated]";
|
|
1915
|
+
}
|
|
1916
|
+
const wasSeen = msg.flags?.has("\\Seen") ?? false;
|
|
1917
|
+
await client.messageFlagsAdd(String(params.uid), ["\\Seen"], { uid: true });
|
|
1918
|
+
const subject = msg.envelope?.subject ?? "(no subject)";
|
|
1919
|
+
const from = extractAddress(msg.envelope?.from);
|
|
1920
|
+
const date = msg.envelope?.date?.toISOString() ?? "unknown";
|
|
1921
|
+
return `UID: ${params.uid}
|
|
1922
|
+
Subject: ${subject}
|
|
1923
|
+
From: ${from}
|
|
1924
|
+
Date: ${date}
|
|
1925
|
+
Status: ${wasSeen ? "was read" : "was unread, marked read"}
|
|
1926
|
+
|
|
1927
|
+
${bodyText}`;
|
|
1928
|
+
} finally {
|
|
1929
|
+
lock.release();
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
async function handleSearch(params, imapConfig) {
|
|
1934
|
+
const folder = params.folder ?? "INBOX";
|
|
1935
|
+
const limit = params.limit ?? 10;
|
|
1936
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1937
|
+
const lock = await client.getMailboxLock(folder);
|
|
1938
|
+
try {
|
|
1939
|
+
const criteria = {};
|
|
1940
|
+
if (params.from)
|
|
1941
|
+
criteria.from = params.from;
|
|
1942
|
+
if (params.subject)
|
|
1943
|
+
criteria.subject = params.subject;
|
|
1944
|
+
if (params.since)
|
|
1945
|
+
criteria.since = new Date(params.since);
|
|
1946
|
+
if (params.unseen)
|
|
1947
|
+
criteria.seen = false;
|
|
1948
|
+
const result = await client.search(criteria, { uid: true });
|
|
1949
|
+
const uids = Array.isArray(result) ? result : [];
|
|
1950
|
+
if (uids.length === 0)
|
|
1951
|
+
return "No emails match the search criteria.";
|
|
1952
|
+
const selected = uids.slice(-limit);
|
|
1953
|
+
const emails = [];
|
|
1954
|
+
for await (const msg of client.fetch(selected, { envelope: true, flags: true }, { uid: true })) {
|
|
1955
|
+
emails.push({
|
|
1956
|
+
uid: msg.uid,
|
|
1957
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
1958
|
+
from: extractAddress(msg.envelope?.from),
|
|
1959
|
+
date: msg.envelope?.date?.toISOString() ?? "unknown",
|
|
1960
|
+
seen: msg.flags?.has("\\Seen") ?? false
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
return formatSummaries(emails);
|
|
1964
|
+
} finally {
|
|
1965
|
+
lock.release();
|
|
1966
|
+
}
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
async function handleSend(params, smtpConfig) {
|
|
1970
|
+
const transport = createTransport({
|
|
1971
|
+
host: smtpConfig.host,
|
|
1972
|
+
port: smtpConfig.port,
|
|
1973
|
+
secure: smtpConfig.port === 465,
|
|
1974
|
+
auth: { user: smtpConfig.user, pass: smtpConfig.password }
|
|
1975
|
+
});
|
|
1976
|
+
const info = await transport.sendMail({
|
|
1977
|
+
from: smtpConfig.user,
|
|
1978
|
+
to: params.to,
|
|
1979
|
+
subject: params.subject,
|
|
1980
|
+
text: params.body,
|
|
1981
|
+
cc: params.cc,
|
|
1982
|
+
bcc: params.bcc
|
|
1983
|
+
});
|
|
1984
|
+
return `Email sent. Message ID: ${info.messageId}`;
|
|
1985
|
+
}
|
|
1986
|
+
var emailTool = {
|
|
1987
|
+
name: "email",
|
|
1988
|
+
description: "Manage email: list recent messages, read by UID, search with criteria, or send an email via SMTP",
|
|
1989
|
+
configCheck: (ctx) => ctx.config.tools?.email ? null : "This tool requires configuration. See https://augure.dev/docs/tools/email",
|
|
1990
|
+
parameters: {
|
|
1991
|
+
type: "object",
|
|
1992
|
+
properties: {
|
|
1993
|
+
action: {
|
|
1994
|
+
type: "string",
|
|
1995
|
+
enum: ["list", "read", "search", "send"],
|
|
1996
|
+
description: "The email action to perform"
|
|
1997
|
+
},
|
|
1998
|
+
folder: {
|
|
1999
|
+
type: "string",
|
|
2000
|
+
description: 'IMAP folder (default: "INBOX"). Used by list, read, search.'
|
|
2001
|
+
},
|
|
2002
|
+
limit: {
|
|
2003
|
+
type: "number",
|
|
2004
|
+
description: "Max emails to return (default: 10). Used by list, search."
|
|
2005
|
+
},
|
|
2006
|
+
uid: { type: "number", description: "Email UID to read. Required for read." },
|
|
2007
|
+
from: { type: "string", description: "Filter by sender address. Used by search." },
|
|
2008
|
+
subject: {
|
|
2009
|
+
type: "string",
|
|
2010
|
+
description: "Filter by subject (search) or email subject (send)."
|
|
2011
|
+
},
|
|
2012
|
+
since: {
|
|
2013
|
+
type: "string",
|
|
2014
|
+
description: "ISO 8601 date \u2014 emails since this date. Used by search."
|
|
2015
|
+
},
|
|
2016
|
+
unseen: { type: "boolean", description: "Only unread emails. Used by search." },
|
|
2017
|
+
to: { type: "string", description: "Recipient address. Required for send." },
|
|
2018
|
+
body: { type: "string", description: "Email body text. Required for send." },
|
|
2019
|
+
cc: { type: "string", description: "CC recipients. Used by send." },
|
|
2020
|
+
bcc: { type: "string", description: "BCC recipients. Used by send." }
|
|
2021
|
+
},
|
|
2022
|
+
required: ["action"]
|
|
2023
|
+
},
|
|
2024
|
+
execute: async (params, ctx) => {
|
|
2025
|
+
const p = params;
|
|
2026
|
+
const emailConfig = ctx.config.tools?.email;
|
|
2027
|
+
if (!emailConfig) {
|
|
2028
|
+
return { success: false, output: "Email is not configured. Add tools.email to your config." };
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
switch (p.action) {
|
|
2032
|
+
case "list":
|
|
2033
|
+
return { success: true, output: await handleList(p, emailConfig.imap) };
|
|
2034
|
+
case "read":
|
|
2035
|
+
if (!p.uid)
|
|
2036
|
+
return { success: false, output: "Missing required field: uid" };
|
|
2037
|
+
return { success: true, output: await handleRead(p, emailConfig.imap) };
|
|
2038
|
+
case "search":
|
|
2039
|
+
if (!p.from && !p.subject && !p.since && p.unseen === void 0)
|
|
2040
|
+
return { success: false, output: "At least one search criterion is required (from, subject, since, or unseen)." };
|
|
2041
|
+
return { success: true, output: await handleSearch(p, emailConfig.imap) };
|
|
2042
|
+
case "send":
|
|
2043
|
+
if (!p.to)
|
|
2044
|
+
return { success: false, output: "Missing required field: to" };
|
|
2045
|
+
if (!p.subject)
|
|
2046
|
+
return { success: false, output: "Missing required field: subject" };
|
|
2047
|
+
if (!p.body)
|
|
2048
|
+
return { success: false, output: "Missing required field: body" };
|
|
2049
|
+
return { success: true, output: await handleSend(p, emailConfig.smtp) };
|
|
2050
|
+
default:
|
|
2051
|
+
return { success: false, output: `Unknown action: ${p.action}` };
|
|
2052
|
+
}
|
|
2053
|
+
} catch (err2) {
|
|
2054
|
+
return {
|
|
2055
|
+
success: false,
|
|
2056
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
1229
2061
|
|
|
1230
2062
|
// ../tools/dist/sandbox-exec.js
|
|
1231
2063
|
var sandboxExecTool = {
|
|
@@ -1304,6 +2136,7 @@ function shellEscape(s) {
|
|
|
1304
2136
|
var opencodeTool = {
|
|
1305
2137
|
name: "opencode",
|
|
1306
2138
|
description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
|
|
2139
|
+
configCheck: (ctx) => ctx.config.sandbox.codeAgent ? null : "This tool requires sandbox.codeAgent configuration. See https://augure.dev/docs/sandbox",
|
|
1307
2140
|
parameters: {
|
|
1308
2141
|
type: "object",
|
|
1309
2142
|
properties: {
|
|
@@ -1515,6 +2348,10 @@ var DockerContainer = class {
|
|
|
1515
2348
|
}
|
|
1516
2349
|
});
|
|
1517
2350
|
this.demux(stream, stdoutPT, stderrPT);
|
|
2351
|
+
stream.on("end", () => {
|
|
2352
|
+
stdoutPT.end();
|
|
2353
|
+
stderrPT.end();
|
|
2354
|
+
});
|
|
1518
2355
|
});
|
|
1519
2356
|
}
|
|
1520
2357
|
};
|
|
@@ -1540,6 +2377,7 @@ var DockerContainerPool = class {
|
|
|
1540
2377
|
docker;
|
|
1541
2378
|
image;
|
|
1542
2379
|
maxTotal;
|
|
2380
|
+
log;
|
|
1543
2381
|
// C3: idle cache keyed by trust level to prevent cross-trust reuse
|
|
1544
2382
|
idle = /* @__PURE__ */ new Map([
|
|
1545
2383
|
["sandboxed", /* @__PURE__ */ new Set()],
|
|
@@ -1551,6 +2389,7 @@ var DockerContainerPool = class {
|
|
|
1551
2389
|
this.docker = docker;
|
|
1552
2390
|
this.image = config.image;
|
|
1553
2391
|
this.maxTotal = config.maxTotal;
|
|
2392
|
+
this.log = config.logger ?? noopLogger;
|
|
1554
2393
|
}
|
|
1555
2394
|
get idleCount() {
|
|
1556
2395
|
let count = 0;
|
|
@@ -1560,18 +2399,22 @@ var DockerContainerPool = class {
|
|
|
1560
2399
|
}
|
|
1561
2400
|
/* ---- acquire ---- */
|
|
1562
2401
|
async acquire(opts) {
|
|
2402
|
+
this.log.debug(`Acquiring container: trust=${opts.trust} memory=${opts.memory} cpu=${opts.cpu}`);
|
|
1563
2403
|
const trustIdle = this.idle.get(opts.trust);
|
|
1564
2404
|
const cached = trustIdle.values().next();
|
|
1565
2405
|
if (!cached.done) {
|
|
1566
2406
|
const container2 = cached.value;
|
|
1567
2407
|
trustIdle.delete(container2);
|
|
1568
2408
|
this.busy.add(container2);
|
|
2409
|
+
this.log.debug(`Reusing cached container: ${container2.id.slice(0, 12)}`);
|
|
1569
2410
|
return container2;
|
|
1570
2411
|
}
|
|
1571
2412
|
const total = this.idleCount + this.busy.size;
|
|
1572
2413
|
if (total >= this.maxTotal) {
|
|
2414
|
+
this.log.error(`Pool limit reached: ${total}/${this.maxTotal}`);
|
|
1573
2415
|
throw new Error("Pool limit reached");
|
|
1574
2416
|
}
|
|
2417
|
+
this.log.debug("Creating new container...");
|
|
1575
2418
|
const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
|
|
1576
2419
|
await raw.start();
|
|
1577
2420
|
const modem = this.docker.modem;
|
|
@@ -1579,6 +2422,7 @@ var DockerContainerPool = class {
|
|
|
1579
2422
|
const container = new DockerContainer(raw, demux);
|
|
1580
2423
|
this.containerTrust.set(container.id, opts.trust);
|
|
1581
2424
|
this.busy.add(container);
|
|
2425
|
+
this.log.debug(`Container created: ${container.id.slice(0, 12)}`);
|
|
1582
2426
|
return container;
|
|
1583
2427
|
}
|
|
1584
2428
|
/* ---- release ---- */
|
|
@@ -1590,6 +2434,7 @@ var DockerContainerPool = class {
|
|
|
1590
2434
|
}
|
|
1591
2435
|
const trust = this.containerTrust.get(container.id) ?? "sandboxed";
|
|
1592
2436
|
this.idle.get(trust).add(container);
|
|
2437
|
+
this.log.debug(`Container released: ${container.id.slice(0, 12)} \u2192 idle (${trust})`);
|
|
1593
2438
|
}
|
|
1594
2439
|
/* ---- destroy ---- */
|
|
1595
2440
|
async destroy(container) {
|
|
@@ -1678,16 +2523,19 @@ function buildTar(content) {
|
|
|
1678
2523
|
const end = Buffer.alloc(1024, 0);
|
|
1679
2524
|
return Buffer.concat([header, dataPadded, end]);
|
|
1680
2525
|
}
|
|
1681
|
-
async function ensureImage(docker, imageName) {
|
|
2526
|
+
async function ensureImage(docker, imageName, logger) {
|
|
2527
|
+
const log = logger ?? noopLogger;
|
|
2528
|
+
log.debug(`Checking image: ${imageName}`);
|
|
1682
2529
|
try {
|
|
1683
2530
|
await docker.getImage(imageName).inspect();
|
|
2531
|
+
log.debug("Image exists");
|
|
1684
2532
|
return;
|
|
1685
2533
|
} catch (err2) {
|
|
1686
2534
|
const statusCode = err2.statusCode;
|
|
1687
2535
|
if (statusCode !== void 0 && statusCode !== 404)
|
|
1688
2536
|
throw err2;
|
|
1689
2537
|
}
|
|
1690
|
-
|
|
2538
|
+
log.info(`Image "${imageName}" not found, building...`);
|
|
1691
2539
|
const tar = buildTar(DOCKERFILE);
|
|
1692
2540
|
const stream = await docker.buildImage(Readable.from(tar), {
|
|
1693
2541
|
t: imageName
|
|
@@ -1699,11 +2547,14 @@ async function ensureImage(docker, imageName) {
|
|
|
1699
2547
|
else
|
|
1700
2548
|
resolve5();
|
|
1701
2549
|
}, (event) => {
|
|
1702
|
-
if (event.stream)
|
|
1703
|
-
|
|
2550
|
+
if (event.stream) {
|
|
2551
|
+
const line = event.stream.trim();
|
|
2552
|
+
if (line)
|
|
2553
|
+
log.debug(line);
|
|
2554
|
+
}
|
|
1704
2555
|
});
|
|
1705
2556
|
});
|
|
1706
|
-
|
|
2557
|
+
log.info(`Image "${imageName}" built`);
|
|
1707
2558
|
}
|
|
1708
2559
|
|
|
1709
2560
|
// ../memory/dist/store.js
|
|
@@ -1872,6 +2723,7 @@ var CronScheduler = class {
|
|
|
1872
2723
|
timers = /* @__PURE__ */ new Map();
|
|
1873
2724
|
handlers = [];
|
|
1874
2725
|
persistChain = Promise.resolve();
|
|
2726
|
+
running = false;
|
|
1875
2727
|
constructor(store) {
|
|
1876
2728
|
this.store = store;
|
|
1877
2729
|
}
|
|
@@ -1889,12 +2741,21 @@ var CronScheduler = class {
|
|
|
1889
2741
|
throw new Error(`Invalid runAt date: ${job.runAt}`);
|
|
1890
2742
|
}
|
|
1891
2743
|
this.jobs.set(job.id, job);
|
|
2744
|
+
console.log(`[scheduler] Added job ${job.id} (${job.cron ? `cron: ${job.cron}` : `runAt: ${job.runAt}`})`);
|
|
1892
2745
|
if (job.enabled && job.cron) {
|
|
1893
2746
|
const task = createTask(job.cron, () => {
|
|
2747
|
+
console.log(`[scheduler] Cron fired for job ${job.id}`);
|
|
1894
2748
|
void this.executeHandlers(job);
|
|
1895
2749
|
});
|
|
2750
|
+
if (this.running) {
|
|
2751
|
+
task.start();
|
|
2752
|
+
console.log(`[scheduler] Started cron task for ${job.id} immediately (scheduler already running)`);
|
|
2753
|
+
}
|
|
1896
2754
|
this.tasks.set(job.id, task);
|
|
1897
2755
|
}
|
|
2756
|
+
if (this.running && job.enabled && job.runAt && !job.cron) {
|
|
2757
|
+
this.scheduleOneShot(job);
|
|
2758
|
+
}
|
|
1898
2759
|
this.persist();
|
|
1899
2760
|
}
|
|
1900
2761
|
removeJob(id) {
|
|
@@ -1909,6 +2770,7 @@ var CronScheduler = class {
|
|
|
1909
2770
|
this.timers.delete(id);
|
|
1910
2771
|
}
|
|
1911
2772
|
this.jobs.delete(id);
|
|
2773
|
+
console.log(`[scheduler] Removed job ${id}`);
|
|
1912
2774
|
this.persist();
|
|
1913
2775
|
}
|
|
1914
2776
|
listJobs() {
|
|
@@ -1925,16 +2787,21 @@ var CronScheduler = class {
|
|
|
1925
2787
|
if (!this.store)
|
|
1926
2788
|
return;
|
|
1927
2789
|
const jobs = await this.store.load();
|
|
2790
|
+
console.log(`[scheduler] Loading ${jobs.length} persisted jobs`);
|
|
1928
2791
|
for (const job of jobs) {
|
|
1929
2792
|
if (job.runAt && Date.parse(job.runAt) <= Date.now()) {
|
|
2793
|
+
console.log(`[scheduler] Skipping expired one-shot job ${job.id} (runAt: ${job.runAt})`);
|
|
1930
2794
|
continue;
|
|
1931
2795
|
}
|
|
1932
2796
|
this.addJob(job);
|
|
1933
2797
|
}
|
|
1934
2798
|
}
|
|
1935
2799
|
start() {
|
|
1936
|
-
|
|
2800
|
+
this.running = true;
|
|
2801
|
+
console.log(`[scheduler] Starting with ${this.tasks.size} cron tasks and ${this.handlers.length} handlers`);
|
|
2802
|
+
for (const [id, task] of this.tasks) {
|
|
1937
2803
|
task.start();
|
|
2804
|
+
console.log(`[scheduler] Started cron task: ${id}`);
|
|
1938
2805
|
}
|
|
1939
2806
|
for (const job of this.jobs.values()) {
|
|
1940
2807
|
if (job.enabled && job.runAt && !job.cron) {
|
|
@@ -1943,6 +2810,7 @@ var CronScheduler = class {
|
|
|
1943
2810
|
}
|
|
1944
2811
|
}
|
|
1945
2812
|
stop() {
|
|
2813
|
+
this.running = false;
|
|
1946
2814
|
for (const task of this.tasks.values()) {
|
|
1947
2815
|
task.stop();
|
|
1948
2816
|
}
|
|
@@ -1953,9 +2821,13 @@ var CronScheduler = class {
|
|
|
1953
2821
|
}
|
|
1954
2822
|
scheduleOneShot(job) {
|
|
1955
2823
|
const delayMs = Date.parse(job.runAt) - Date.now();
|
|
1956
|
-
if (delayMs <= 0)
|
|
2824
|
+
if (delayMs <= 0) {
|
|
2825
|
+
console.log(`[scheduler] One-shot job ${job.id} already expired (delay: ${delayMs}ms), skipping`);
|
|
1957
2826
|
return;
|
|
2827
|
+
}
|
|
2828
|
+
console.log(`[scheduler] Scheduled one-shot job ${job.id} in ${Math.round(delayMs / 1e3)}s (${job.runAt})`);
|
|
1958
2829
|
const timer = setTimeout(() => {
|
|
2830
|
+
console.log(`[scheduler] One-shot job ${job.id} firing now`);
|
|
1959
2831
|
this.timers.delete(job.id);
|
|
1960
2832
|
void this.executeHandlers(job).then(() => {
|
|
1961
2833
|
this.removeJob(job.id);
|
|
@@ -1970,6 +2842,7 @@ var CronScheduler = class {
|
|
|
1970
2842
|
this.persistChain = this.persistChain.then(() => this.store.save(jobs));
|
|
1971
2843
|
}
|
|
1972
2844
|
async executeHandlers(job) {
|
|
2845
|
+
console.log(`[scheduler] Executing ${this.handlers.length} handlers for job ${job.id}`);
|
|
1973
2846
|
for (const handler of this.handlers) {
|
|
1974
2847
|
await handler(job);
|
|
1975
2848
|
}
|
|
@@ -2016,10 +2889,13 @@ Be concise. Only suggest actions that are clearly needed based on the memory con
|
|
|
2016
2889
|
var Heartbeat = class {
|
|
2017
2890
|
config;
|
|
2018
2891
|
timer;
|
|
2892
|
+
log;
|
|
2019
2893
|
constructor(config) {
|
|
2020
2894
|
this.config = config;
|
|
2895
|
+
this.log = config.logger ?? noopLogger;
|
|
2021
2896
|
}
|
|
2022
2897
|
async tick() {
|
|
2898
|
+
this.log.debug("Heartbeat tick");
|
|
2023
2899
|
const memoryContent = await this.loadMemory();
|
|
2024
2900
|
const messages = [
|
|
2025
2901
|
{ role: "system", content: HEARTBEAT_PROMPT },
|
|
@@ -2034,12 +2910,15 @@ ${memoryContent}`
|
|
|
2034
2910
|
const response = await this.config.llm.chat(messages);
|
|
2035
2911
|
const action = this.parseAction(response.content);
|
|
2036
2912
|
if (action && action.toLowerCase() !== "none") {
|
|
2913
|
+
this.log.debug(`Heartbeat action: ${action}`);
|
|
2037
2914
|
await this.config.onAction(action);
|
|
2915
|
+
} else {
|
|
2916
|
+
this.log.debug("Heartbeat: no action needed");
|
|
2038
2917
|
}
|
|
2039
2918
|
}
|
|
2040
2919
|
start() {
|
|
2041
2920
|
this.timer = setInterval(() => {
|
|
2042
|
-
this.tick().catch((err2) =>
|
|
2921
|
+
this.tick().catch((err2) => this.log.error("Heartbeat error:", err2));
|
|
2043
2922
|
}, this.config.intervalMs);
|
|
2044
2923
|
}
|
|
2045
2924
|
stop() {
|
|
@@ -2886,9 +3765,11 @@ var JOB_PREFIX = "skill:";
|
|
|
2886
3765
|
var SkillSchedulerBridge = class {
|
|
2887
3766
|
scheduler;
|
|
2888
3767
|
manager;
|
|
2889
|
-
|
|
3768
|
+
log;
|
|
3769
|
+
constructor(scheduler, manager, logger) {
|
|
2890
3770
|
this.scheduler = scheduler;
|
|
2891
3771
|
this.manager = manager;
|
|
3772
|
+
this.log = logger ?? noopLogger;
|
|
2892
3773
|
}
|
|
2893
3774
|
/** Register cron jobs for all active cron-triggered skills */
|
|
2894
3775
|
async syncAll() {
|
|
@@ -2907,7 +3788,7 @@ var SkillSchedulerBridge = class {
|
|
|
2907
3788
|
enabled: true
|
|
2908
3789
|
});
|
|
2909
3790
|
} catch (err2) {
|
|
2910
|
-
|
|
3791
|
+
this.log.error(`Failed to register cron for ${skill.id}:`, err2);
|
|
2911
3792
|
}
|
|
2912
3793
|
}
|
|
2913
3794
|
existingJobs.delete(jobId);
|
|
@@ -3401,27 +4282,48 @@ var VersionChecker = class _VersionChecker {
|
|
|
3401
4282
|
};
|
|
3402
4283
|
|
|
3403
4284
|
// ../core/dist/main.js
|
|
3404
|
-
var
|
|
4285
|
+
var BASE_SYSTEM_PROMPT = `You are Augure, a personal AI assistant. You are proactive, helpful, and concise.
|
|
3405
4286
|
You speak the same language as the user. You have access to tools and persistent memory.
|
|
3406
|
-
Always be direct and actionable
|
|
3407
|
-
|
|
4287
|
+
Always be direct and actionable.
|
|
4288
|
+
|
|
4289
|
+
## Your capabilities
|
|
4290
|
+
|
|
4291
|
+
You have access to tools that let you interact with the outside world. Use the datetime tool when the user needs precise time information beyond what is shown in the current date above. Use memory tools to remember and recall information across conversations. Use the schedule tool to create recurring or one-shot tasks.
|
|
4292
|
+
|
|
4293
|
+
If a tool is marked as [NOT CONFIGURED], let the user know it needs to be set up first and share the documentation link from the tool description.`;
|
|
4294
|
+
var SKILLS_PROMPT = `
|
|
4295
|
+
## Skills
|
|
4296
|
+
|
|
4297
|
+
You can create and manage "skills" \u2014 autonomous code units that run in isolated Docker containers. Skills are powerful: they let you automate tasks, run on a schedule, and self-heal when they break.
|
|
4298
|
+
|
|
4299
|
+
- Use skill_list to see existing skills and their status
|
|
4300
|
+
- Use skill_generate to create a new skill from a natural language description
|
|
4301
|
+
- Use skill_run to execute a skill manually
|
|
4302
|
+
- Use skill_heal to fix a broken skill
|
|
4303
|
+
- Use skill_install to install a skill from the hub
|
|
4304
|
+
|
|
4305
|
+
When a user asks to automate a recurring task (e.g. "check this every morning", "send me a summary daily"), suggest creating a skill with a cron trigger. Skills can also be triggered manually or by events.`;
|
|
4306
|
+
function resolveLLMClient(config, usage, logger) {
|
|
3408
4307
|
const override = usage !== "default" ? config[usage] : void 0;
|
|
3409
4308
|
return new OpenRouterClient({
|
|
3410
4309
|
apiKey: override?.apiKey ?? config.default.apiKey,
|
|
3411
4310
|
model: override?.model ?? config.default.model,
|
|
3412
|
-
maxTokens: override?.maxTokens ?? config.default.maxTokens
|
|
4311
|
+
maxTokens: override?.maxTokens ?? config.default.maxTokens,
|
|
4312
|
+
logger: logger.child("llm")
|
|
3413
4313
|
});
|
|
3414
4314
|
}
|
|
3415
|
-
async function startAgent(configPath) {
|
|
4315
|
+
async function startAgent(configPath, opts) {
|
|
4316
|
+
const log = createLogger({ level: opts?.debug ? "debug" : "info" });
|
|
3416
4317
|
const config = await loadConfig(configPath);
|
|
3417
|
-
|
|
4318
|
+
log.info(`Loaded config: ${config.identity.name}`);
|
|
4319
|
+
log.debug(`Config path: ${configPath}`);
|
|
3418
4320
|
let telegram;
|
|
3419
|
-
const llm = resolveLLMClient(config.llm, "default");
|
|
3420
|
-
const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
|
|
3421
|
-
const monitoringLLM = resolveLLMClient(config.llm, "monitoring");
|
|
4321
|
+
const llm = resolveLLMClient(config.llm, "default", log);
|
|
4322
|
+
const ingestionLLM = resolveLLMClient(config.llm, "ingestion", log);
|
|
4323
|
+
const monitoringLLM = resolveLLMClient(config.llm, "monitoring", log);
|
|
3422
4324
|
const memoryPath = resolve(configPath, "..", config.memory.path);
|
|
3423
4325
|
const memory = new FileMemoryStore(memoryPath);
|
|
3424
|
-
|
|
4326
|
+
log.info(`Memory store: ${memoryPath}`);
|
|
3425
4327
|
const retriever = new MemoryRetriever(memory, {
|
|
3426
4328
|
maxTokens: config.memory.maxRetrievalTokens
|
|
3427
4329
|
});
|
|
@@ -3430,15 +4332,17 @@ async function startAgent(configPath) {
|
|
|
3430
4332
|
tools.register(memoryReadTool);
|
|
3431
4333
|
tools.register(memoryWriteTool);
|
|
3432
4334
|
tools.register(scheduleTool);
|
|
4335
|
+
tools.register(datetimeTool);
|
|
3433
4336
|
tools.register(webSearchTool);
|
|
3434
4337
|
tools.register(httpTool);
|
|
4338
|
+
tools.register(emailTool);
|
|
3435
4339
|
tools.register(sandboxExecTool);
|
|
3436
4340
|
tools.register(opencodeTool);
|
|
3437
4341
|
const jobStorePath = resolve(configPath, "..", "jobs.json");
|
|
3438
4342
|
const jobStore = new JobStore(jobStorePath);
|
|
3439
4343
|
const scheduler = new CronScheduler(jobStore);
|
|
3440
4344
|
await scheduler.loadPersistedJobs();
|
|
3441
|
-
|
|
4345
|
+
log.info(`Loaded ${scheduler.listJobs().length} persisted jobs`);
|
|
3442
4346
|
for (const job of config.scheduler.jobs) {
|
|
3443
4347
|
if (!scheduler.listJobs().some((j) => j.id === job.id)) {
|
|
3444
4348
|
scheduler.addJob({ ...job, enabled: true });
|
|
@@ -3446,17 +4350,19 @@ async function startAgent(configPath) {
|
|
|
3446
4350
|
}
|
|
3447
4351
|
const docker = new Dockerode();
|
|
3448
4352
|
const sandboxImage = config.sandbox.image ?? "augure-sandbox:latest";
|
|
3449
|
-
|
|
4353
|
+
const sandboxLog = log.child("sandbox");
|
|
4354
|
+
await ensureImage(docker, sandboxImage, sandboxLog);
|
|
3450
4355
|
const pool = new DockerContainerPool(docker, {
|
|
3451
4356
|
image: sandboxImage,
|
|
3452
|
-
maxTotal: config.security.maxConcurrentSandboxes
|
|
4357
|
+
maxTotal: config.security.maxConcurrentSandboxes,
|
|
4358
|
+
logger: sandboxLog
|
|
3453
4359
|
});
|
|
3454
|
-
|
|
4360
|
+
log.info(`Container pool: max=${config.security.maxConcurrentSandboxes}`);
|
|
3455
4361
|
let skillManagerRef;
|
|
3456
4362
|
let skillUpdater;
|
|
3457
4363
|
if (config.skills) {
|
|
3458
4364
|
const skillsPath = resolve(configPath, "..", config.skills.path);
|
|
3459
|
-
const codingLLM = resolveLLMClient(config.llm, "coding");
|
|
4365
|
+
const codingLLM = resolveLLMClient(config.llm, "coding", log);
|
|
3460
4366
|
const skillManager = new SkillManager(skillsPath);
|
|
3461
4367
|
const skillGenerator = new SkillGenerator(codingLLM);
|
|
3462
4368
|
const skillRunner = new SkillRunner({
|
|
@@ -3500,29 +4406,63 @@ async function startAgent(configPath) {
|
|
|
3500
4406
|
const updated = updateResults.filter((r) => r.success);
|
|
3501
4407
|
const failed = updateResults.filter((r) => !r.success);
|
|
3502
4408
|
if (updated.length > 0) {
|
|
3503
|
-
|
|
4409
|
+
log.info(`Skills updated: ${updated.map((r) => `${r.skillId} (v${r.fromVersion}\u2192v${r.toVersion})`).join(", ")}`);
|
|
3504
4410
|
}
|
|
3505
4411
|
if (failed.length > 0) {
|
|
3506
|
-
|
|
4412
|
+
log.warn(`Skill updates failed: ${failed.map((r) => `${r.skillId}: ${r.error}`).join(", ")}`);
|
|
3507
4413
|
}
|
|
3508
4414
|
} catch (err2) {
|
|
3509
|
-
|
|
4415
|
+
log.error("Skill update check failed:", err2);
|
|
3510
4416
|
}
|
|
3511
4417
|
}
|
|
3512
4418
|
skillManagerRef = skillManager;
|
|
3513
|
-
|
|
4419
|
+
log.info(`Skills initialized: ${skillsPath}`);
|
|
3514
4420
|
}
|
|
3515
4421
|
tools.setContext({ config, memory, scheduler, pool });
|
|
4422
|
+
let codeModeExecutor;
|
|
4423
|
+
if (config.codeMode) {
|
|
4424
|
+
const cmConfig = config.codeMode;
|
|
4425
|
+
if (cmConfig.runtime === "vm") {
|
|
4426
|
+
codeModeExecutor = new VmExecutor(tools, {
|
|
4427
|
+
timeout: cmConfig.timeout * 1e3,
|
|
4428
|
+
// VmExecutor expects ms
|
|
4429
|
+
memoryLimit: cmConfig.memoryLimit
|
|
4430
|
+
});
|
|
4431
|
+
} else if (cmConfig.runtime === "docker") {
|
|
4432
|
+
codeModeExecutor = new DockerExecutor({
|
|
4433
|
+
registry: tools,
|
|
4434
|
+
pool,
|
|
4435
|
+
timeout: cmConfig.timeout,
|
|
4436
|
+
// DockerExecutor expects seconds
|
|
4437
|
+
memoryLimit: config.sandbox.defaults.memoryLimit,
|
|
4438
|
+
cpuLimit: config.sandbox.defaults.cpuLimit
|
|
4439
|
+
});
|
|
4440
|
+
} else {
|
|
4441
|
+
const vmExec = new VmExecutor(tools, {
|
|
4442
|
+
timeout: cmConfig.timeout * 1e3,
|
|
4443
|
+
memoryLimit: cmConfig.memoryLimit
|
|
4444
|
+
});
|
|
4445
|
+
const dockerExec = new DockerExecutor({
|
|
4446
|
+
registry: tools,
|
|
4447
|
+
pool,
|
|
4448
|
+
timeout: cmConfig.timeout,
|
|
4449
|
+
memoryLimit: config.sandbox.defaults.memoryLimit,
|
|
4450
|
+
cpuLimit: config.sandbox.defaults.cpuLimit
|
|
4451
|
+
});
|
|
4452
|
+
codeModeExecutor = new AutoExecutor(vmExec, dockerExec);
|
|
4453
|
+
}
|
|
4454
|
+
log.info(`Code Mode enabled: runtime=${cmConfig.runtime}, timeout=${cmConfig.timeout}s`);
|
|
4455
|
+
}
|
|
3516
4456
|
const auditConfig = config.audit ?? { path: "./logs", enabled: true };
|
|
3517
4457
|
const auditPath = resolve(configPath, "..", auditConfig.path);
|
|
3518
|
-
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath) : new NullAuditLogger();
|
|
3519
|
-
|
|
4458
|
+
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath, log.child("audit")) : new NullAuditLogger();
|
|
4459
|
+
log.info(`Audit: ${auditConfig.enabled ? auditPath : "disabled"}`);
|
|
3520
4460
|
let personaResolver;
|
|
3521
4461
|
if (config.persona) {
|
|
3522
4462
|
const personaPath = resolve(configPath, "..", config.persona.path);
|
|
3523
4463
|
personaResolver = new PersonaResolver(personaPath);
|
|
3524
4464
|
await personaResolver.loadAll();
|
|
3525
|
-
|
|
4465
|
+
log.info(`Personas: ${personaPath}`);
|
|
3526
4466
|
}
|
|
3527
4467
|
let cliVersion;
|
|
3528
4468
|
try {
|
|
@@ -3538,29 +4478,34 @@ async function startAgent(configPath) {
|
|
|
3538
4478
|
});
|
|
3539
4479
|
const versionResult = await versionChecker.check();
|
|
3540
4480
|
if (versionResult.updateAvailable) {
|
|
3541
|
-
|
|
4481
|
+
log.warn(`Update available: v${versionResult.latestVersion} (current: v${versionResult.currentVersion}). Run: npm update -g augure`);
|
|
3542
4482
|
}
|
|
3543
4483
|
}
|
|
3544
4484
|
const guard = new ContextGuard({
|
|
3545
4485
|
maxContextTokens: 2e5,
|
|
3546
4486
|
reservedForOutput: config.llm.default.maxTokens ?? 8192
|
|
3547
4487
|
});
|
|
4488
|
+
const systemPrompt = config.skills ? BASE_SYSTEM_PROMPT + SKILLS_PROMPT : BASE_SYSTEM_PROMPT;
|
|
3548
4489
|
const agent = new Agent({
|
|
3549
4490
|
llm,
|
|
3550
4491
|
tools,
|
|
3551
|
-
systemPrompt
|
|
4492
|
+
systemPrompt,
|
|
3552
4493
|
memoryContent: "",
|
|
3553
4494
|
retriever,
|
|
3554
4495
|
ingester,
|
|
3555
4496
|
audit,
|
|
3556
4497
|
guard,
|
|
3557
|
-
modelName: config.llm.default.model
|
|
4498
|
+
modelName: config.llm.default.model,
|
|
4499
|
+
logger: log.child("agent"),
|
|
4500
|
+
codeModeExecutor
|
|
3558
4501
|
});
|
|
3559
4502
|
if (config.channels.telegram?.enabled) {
|
|
4503
|
+
const telegramLog = log.child("telegram");
|
|
3560
4504
|
telegram = new TelegramChannel({
|
|
3561
4505
|
botToken: config.channels.telegram.botToken,
|
|
3562
4506
|
allowedUsers: config.channels.telegram.allowedUsers,
|
|
3563
|
-
rejectMessage: config.channels.telegram.rejectMessage
|
|
4507
|
+
rejectMessage: config.channels.telegram.rejectMessage,
|
|
4508
|
+
logger: telegramLog
|
|
3564
4509
|
});
|
|
3565
4510
|
const tg = telegram;
|
|
3566
4511
|
const commandCtx = {
|
|
@@ -3570,7 +4515,7 @@ async function startAgent(configPath) {
|
|
|
3570
4515
|
skillManager: skillManagerRef
|
|
3571
4516
|
};
|
|
3572
4517
|
tg.onMessage(async (msg) => {
|
|
3573
|
-
|
|
4518
|
+
log.info(`Message from ${msg.userId}: ${msg.text}`);
|
|
3574
4519
|
try {
|
|
3575
4520
|
const cmdResult = await handleCommand(msg.text, commandCtx);
|
|
3576
4521
|
if (cmdResult.handled) {
|
|
@@ -3593,7 +4538,7 @@ async function startAgent(configPath) {
|
|
|
3593
4538
|
replyTo: msg.id
|
|
3594
4539
|
});
|
|
3595
4540
|
} catch (err2) {
|
|
3596
|
-
|
|
4541
|
+
log.error("Error handling message:", err2);
|
|
3597
4542
|
await tg.send({
|
|
3598
4543
|
channelType: "telegram",
|
|
3599
4544
|
userId: msg.userId,
|
|
@@ -3602,15 +4547,16 @@ async function startAgent(configPath) {
|
|
|
3602
4547
|
}
|
|
3603
4548
|
});
|
|
3604
4549
|
await tg.start();
|
|
3605
|
-
|
|
4550
|
+
log.info("Telegram bot started");
|
|
3606
4551
|
}
|
|
3607
4552
|
const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
|
|
3608
4553
|
const heartbeat = new Heartbeat({
|
|
3609
4554
|
llm: monitoringLLM,
|
|
3610
4555
|
memory,
|
|
3611
4556
|
intervalMs: heartbeatIntervalMs,
|
|
4557
|
+
logger: log.child("heartbeat"),
|
|
3612
4558
|
onAction: async (action) => {
|
|
3613
|
-
|
|
4559
|
+
log.info(`Heartbeat action: ${action}`);
|
|
3614
4560
|
const response = await agent.handleMessage({
|
|
3615
4561
|
id: `heartbeat-${Date.now()}`,
|
|
3616
4562
|
channelType: "system",
|
|
@@ -3618,11 +4564,11 @@ async function startAgent(configPath) {
|
|
|
3618
4564
|
text: `[Heartbeat] ${action}`,
|
|
3619
4565
|
timestamp: /* @__PURE__ */ new Date()
|
|
3620
4566
|
});
|
|
3621
|
-
|
|
4567
|
+
log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
|
|
3622
4568
|
}
|
|
3623
4569
|
});
|
|
3624
4570
|
scheduler.onJobTrigger(async (job) => {
|
|
3625
|
-
|
|
4571
|
+
log.info(`Job triggered: ${job.id}`);
|
|
3626
4572
|
const response = await agent.handleMessage({
|
|
3627
4573
|
id: `job-${job.id}-${Date.now()}`,
|
|
3628
4574
|
channelType: "system",
|
|
@@ -3640,11 +4586,11 @@ async function startAgent(configPath) {
|
|
|
3640
4586
|
});
|
|
3641
4587
|
}
|
|
3642
4588
|
}
|
|
3643
|
-
|
|
4589
|
+
log.debug(`Job ${job.id} completed`);
|
|
3644
4590
|
});
|
|
3645
4591
|
scheduler.start();
|
|
3646
4592
|
heartbeat.start();
|
|
3647
|
-
|
|
4593
|
+
log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
|
|
3648
4594
|
const updateTimers = [];
|
|
3649
4595
|
if (skillUpdater && config.updates?.skills?.checkInterval) {
|
|
3650
4596
|
const su = skillUpdater;
|
|
@@ -3654,13 +4600,13 @@ async function startAgent(configPath) {
|
|
|
3654
4600
|
const results = await su.checkAndApply();
|
|
3655
4601
|
for (const r of results) {
|
|
3656
4602
|
if (r.success) {
|
|
3657
|
-
|
|
4603
|
+
log.info(`Skill auto-updated: ${r.skillId} v${r.fromVersion}\u2192v${r.toVersion}`);
|
|
3658
4604
|
} else if (r.rolledBack) {
|
|
3659
|
-
|
|
4605
|
+
log.warn(`Skill update rolled back: ${r.skillId} - ${r.error}`);
|
|
3660
4606
|
}
|
|
3661
4607
|
}
|
|
3662
4608
|
} catch (err2) {
|
|
3663
|
-
|
|
4609
|
+
log.error("Periodic skill update check failed:", err2);
|
|
3664
4610
|
}
|
|
3665
4611
|
}, skillCheckMs));
|
|
3666
4612
|
}
|
|
@@ -3685,12 +4631,12 @@ Run: \`npm update -g augure\``
|
|
|
3685
4631
|
}
|
|
3686
4632
|
}
|
|
3687
4633
|
} catch (err2) {
|
|
3688
|
-
|
|
4634
|
+
log.error("CLI version check failed:", err2);
|
|
3689
4635
|
}
|
|
3690
4636
|
}, cliCheckMs));
|
|
3691
4637
|
}
|
|
3692
4638
|
const shutdown = async () => {
|
|
3693
|
-
|
|
4639
|
+
log.info("Shutting down...");
|
|
3694
4640
|
for (const timer of updateTimers)
|
|
3695
4641
|
clearInterval(timer);
|
|
3696
4642
|
heartbeat.stop();
|
|
@@ -3699,23 +4645,13 @@ Run: \`npm update -g augure\``
|
|
|
3699
4645
|
await telegram.stop();
|
|
3700
4646
|
await pool.destroyAll();
|
|
3701
4647
|
await audit.close();
|
|
3702
|
-
|
|
4648
|
+
log.info("All containers destroyed");
|
|
3703
4649
|
process.exit(0);
|
|
3704
4650
|
};
|
|
3705
4651
|
process.on("SIGINT", shutdown);
|
|
3706
4652
|
process.on("SIGTERM", shutdown);
|
|
3707
4653
|
}
|
|
3708
4654
|
|
|
3709
|
-
// src/colors.ts
|
|
3710
|
-
import { styleText } from "util";
|
|
3711
|
-
var brand = (s) => styleText("yellow", s);
|
|
3712
|
-
var ok = (s) => styleText("green", s);
|
|
3713
|
-
var err = (s) => styleText("red", s);
|
|
3714
|
-
var dim = (s) => styleText("dim", s);
|
|
3715
|
-
var bold = (s) => styleText("bold", s);
|
|
3716
|
-
var cyan = (s) => styleText("cyan", s);
|
|
3717
|
-
var prefix = brand("\u25B2 augure");
|
|
3718
|
-
|
|
3719
4655
|
// src/commands/start.ts
|
|
3720
4656
|
var startCommand = defineCommand({
|
|
3721
4657
|
meta: {
|
|
@@ -3733,21 +4669,28 @@ var startCommand = defineCommand({
|
|
|
3733
4669
|
type: "string",
|
|
3734
4670
|
description: "Path to .env file",
|
|
3735
4671
|
alias: "e"
|
|
4672
|
+
},
|
|
4673
|
+
debug: {
|
|
4674
|
+
type: "boolean",
|
|
4675
|
+
description: "Enable debug logging",
|
|
4676
|
+
alias: "d",
|
|
4677
|
+
default: false
|
|
3736
4678
|
}
|
|
3737
4679
|
},
|
|
3738
4680
|
async run({ args }) {
|
|
3739
4681
|
const configPath = resolve2(args.config);
|
|
4682
|
+
const log = createLogger({ level: args.debug ? "debug" : "info" });
|
|
3740
4683
|
const envPath = args.env ? resolve2(args.env) : join6(dirname4(configPath), ".env");
|
|
3741
4684
|
try {
|
|
3742
4685
|
process.loadEnvFile(envPath);
|
|
3743
|
-
|
|
4686
|
+
log.debug(`Loaded env from ${envPath}`);
|
|
3744
4687
|
} catch {
|
|
3745
4688
|
}
|
|
3746
|
-
|
|
4689
|
+
log.info(`Starting with config: ${configPath}`);
|
|
3747
4690
|
try {
|
|
3748
|
-
await startAgent(configPath);
|
|
4691
|
+
await startAgent(configPath, { debug: args.debug });
|
|
3749
4692
|
} catch (e) {
|
|
3750
|
-
|
|
4693
|
+
log.error("Fatal:", e instanceof Error ? e.message : String(e));
|
|
3751
4694
|
process.exit(1);
|
|
3752
4695
|
}
|
|
3753
4696
|
}
|
|
@@ -3757,6 +4700,18 @@ var startCommand = defineCommand({
|
|
|
3757
4700
|
import { defineCommand as defineCommand2 } from "citty";
|
|
3758
4701
|
import { writeFile as writeFile5, access as access3 } from "fs/promises";
|
|
3759
4702
|
import { resolve as resolve3 } from "path";
|
|
4703
|
+
|
|
4704
|
+
// src/colors.ts
|
|
4705
|
+
import { styleText as styleText2 } from "util";
|
|
4706
|
+
var brand = (s) => styleText2("yellow", s);
|
|
4707
|
+
var ok = (s) => styleText2("green", s);
|
|
4708
|
+
var err = (s) => styleText2("red", s);
|
|
4709
|
+
var dim = (s) => styleText2("dim", s);
|
|
4710
|
+
var bold = (s) => styleText2("bold", s);
|
|
4711
|
+
var cyan = (s) => styleText2("cyan", s);
|
|
4712
|
+
var prefix = brand("\u25B2 augure");
|
|
4713
|
+
|
|
4714
|
+
// src/commands/init.ts
|
|
3760
4715
|
var CONFIG_TEMPLATE = `{
|
|
3761
4716
|
// Identity
|
|
3762
4717
|
identity: {
|