augure 0.5.0 → 0.5.1
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 +554 -76
- package/package.json +20 -20
package/dist/bin.js
CHANGED
|
@@ -153,19 +153,33 @@ async function loadConfig(path) {
|
|
|
153
153
|
return AppConfigSchema.parse(parsed);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// ../types/dist/logger.js
|
|
157
|
+
var noop = () => {
|
|
158
|
+
};
|
|
159
|
+
var noopLogger = {
|
|
160
|
+
debug: noop,
|
|
161
|
+
info: noop,
|
|
162
|
+
warn: noop,
|
|
163
|
+
error: noop,
|
|
164
|
+
child: () => noopLogger
|
|
165
|
+
};
|
|
166
|
+
|
|
156
167
|
// ../core/dist/llm.js
|
|
157
168
|
var OpenRouterClient = class {
|
|
158
169
|
apiKey;
|
|
159
170
|
model;
|
|
160
171
|
maxTokens;
|
|
161
172
|
baseUrl;
|
|
173
|
+
log;
|
|
162
174
|
constructor(config) {
|
|
163
175
|
this.apiKey = config.apiKey;
|
|
164
176
|
this.model = config.model;
|
|
165
177
|
this.maxTokens = config.maxTokens;
|
|
166
178
|
this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
179
|
+
this.log = config.logger ?? noopLogger;
|
|
167
180
|
}
|
|
168
181
|
async chat(messages, tools) {
|
|
182
|
+
this.log.debug(`Request: model=${this.model} messages=${messages.length} tools=${tools?.length ?? 0}`);
|
|
169
183
|
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
170
184
|
method: "POST",
|
|
171
185
|
headers: {
|
|
@@ -199,6 +213,7 @@ var OpenRouterClient = class {
|
|
|
199
213
|
}
|
|
200
214
|
const data = await response.json();
|
|
201
215
|
const choice = data.choices[0];
|
|
216
|
+
this.log.debug(`Response: ${response.status} ${data.usage.prompt_tokens}+${data.usage.completion_tokens} tokens`);
|
|
202
217
|
return {
|
|
203
218
|
content: choice.message.content ?? "",
|
|
204
219
|
toolCalls: (choice.message.tool_calls ?? []).map((tc) => {
|
|
@@ -206,7 +221,7 @@ var OpenRouterClient = class {
|
|
|
206
221
|
try {
|
|
207
222
|
args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
208
223
|
} catch {
|
|
209
|
-
|
|
224
|
+
this.log.warn(`Failed to parse tool call arguments for ${tc.function.name}`);
|
|
210
225
|
}
|
|
211
226
|
return { id: tc.id, name: tc.function.name, arguments: args };
|
|
212
227
|
}),
|
|
@@ -222,6 +237,19 @@ var OpenRouterClient = class {
|
|
|
222
237
|
function assembleContext(input) {
|
|
223
238
|
const { systemPrompt, memoryContent, conversationHistory, persona } = input;
|
|
224
239
|
let system = systemPrompt;
|
|
240
|
+
const now = /* @__PURE__ */ new Date();
|
|
241
|
+
const humanDate = new Intl.DateTimeFormat("en-US", {
|
|
242
|
+
weekday: "long",
|
|
243
|
+
year: "numeric",
|
|
244
|
+
month: "long",
|
|
245
|
+
day: "numeric",
|
|
246
|
+
hour: "2-digit",
|
|
247
|
+
minute: "2-digit",
|
|
248
|
+
timeZoneName: "short"
|
|
249
|
+
}).format(now);
|
|
250
|
+
system += `
|
|
251
|
+
|
|
252
|
+
Current date and time: ${now.toISOString()} (${humanDate})`;
|
|
225
253
|
if (persona) {
|
|
226
254
|
system += `
|
|
227
255
|
|
|
@@ -249,13 +277,15 @@ function summarize(text, maxLen = 200) {
|
|
|
249
277
|
}
|
|
250
278
|
var FileAuditLogger = class {
|
|
251
279
|
basePath;
|
|
280
|
+
logger;
|
|
252
281
|
pendingWrite = Promise.resolve();
|
|
253
282
|
initialized = false;
|
|
254
|
-
constructor(basePath) {
|
|
283
|
+
constructor(basePath, logger) {
|
|
255
284
|
this.basePath = basePath;
|
|
285
|
+
this.logger = logger ?? noopLogger;
|
|
256
286
|
}
|
|
257
287
|
log(entry) {
|
|
258
|
-
this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) =>
|
|
288
|
+
this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) => this.logger.error("Audit write error:", err2));
|
|
259
289
|
}
|
|
260
290
|
async close() {
|
|
261
291
|
await this.pendingWrite;
|
|
@@ -281,10 +311,12 @@ var NullAuditLogger = class {
|
|
|
281
311
|
// ../core/dist/agent.js
|
|
282
312
|
var Agent = class {
|
|
283
313
|
config;
|
|
314
|
+
log;
|
|
284
315
|
conversations = /* @__PURE__ */ new Map();
|
|
285
316
|
state = "running";
|
|
286
317
|
constructor(config) {
|
|
287
318
|
this.config = config;
|
|
319
|
+
this.log = config.logger ?? noopLogger;
|
|
288
320
|
}
|
|
289
321
|
getState() {
|
|
290
322
|
return this.state;
|
|
@@ -324,12 +356,14 @@ var Agent = class {
|
|
|
324
356
|
conversationHistory: history,
|
|
325
357
|
persona: this.config.persona
|
|
326
358
|
});
|
|
359
|
+
this.log.debug(`LLM call #${loopCount + 1} (${messages.length} messages)`);
|
|
327
360
|
const response = await this.config.llm.chat(messages, toolSchemas);
|
|
328
361
|
if (response.toolCalls.length === 0) {
|
|
329
362
|
history.push({
|
|
330
363
|
role: "assistant",
|
|
331
364
|
content: response.content
|
|
332
365
|
});
|
|
366
|
+
this.log.debug(`Response: ${response.usage.inputTokens}+${response.usage.outputTokens} tokens, ${Date.now() - start}ms`);
|
|
333
367
|
if (this.config.audit) {
|
|
334
368
|
this.config.audit.log({
|
|
335
369
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -347,7 +381,7 @@ var Agent = class {
|
|
|
347
381
|
});
|
|
348
382
|
}
|
|
349
383
|
if (this.config.ingester) {
|
|
350
|
-
this.config.ingester.ingest(history).catch((err2) =>
|
|
384
|
+
this.config.ingester.ingest(history).catch((err2) => this.log.error("Ingestion error:", err2));
|
|
351
385
|
}
|
|
352
386
|
return response.content;
|
|
353
387
|
}
|
|
@@ -358,7 +392,9 @@ var Agent = class {
|
|
|
358
392
|
});
|
|
359
393
|
for (const toolCall of response.toolCalls) {
|
|
360
394
|
const toolStart = Date.now();
|
|
395
|
+
this.log.debug(`Tool: ${toolCall.name}`);
|
|
361
396
|
const result = await this.config.tools.execute(toolCall.name, toolCall.arguments);
|
|
397
|
+
this.log.debug(`Tool ${toolCall.name}: ${result.success ? "ok" : "fail"} (${Date.now() - toolStart}ms)`);
|
|
362
398
|
history.push({
|
|
363
399
|
role: "tool",
|
|
364
400
|
content: result.output,
|
|
@@ -566,6 +602,56 @@ var ContextGuard = class {
|
|
|
566
602
|
}
|
|
567
603
|
};
|
|
568
604
|
|
|
605
|
+
// ../core/dist/logger.js
|
|
606
|
+
import { styleText } from "util";
|
|
607
|
+
var LEVELS = {
|
|
608
|
+
debug: 0,
|
|
609
|
+
info: 1,
|
|
610
|
+
warn: 2,
|
|
611
|
+
error: 3,
|
|
612
|
+
silent: 4
|
|
613
|
+
};
|
|
614
|
+
function tag(level) {
|
|
615
|
+
switch (level) {
|
|
616
|
+
case "debug":
|
|
617
|
+
return styleText("magenta", "DBG");
|
|
618
|
+
case "info":
|
|
619
|
+
return styleText("cyan", "INF");
|
|
620
|
+
case "warn":
|
|
621
|
+
return styleText("yellow", "WRN");
|
|
622
|
+
case "error":
|
|
623
|
+
return styleText("red", "ERR");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function createLogger(opts = {}) {
|
|
627
|
+
const min = LEVELS[opts.level ?? "info"];
|
|
628
|
+
const scope = opts.scope;
|
|
629
|
+
function emit(level, msg, args) {
|
|
630
|
+
if (LEVELS[level] < min)
|
|
631
|
+
return;
|
|
632
|
+
const ts = styleText("dim", (/* @__PURE__ */ new Date()).toISOString().slice(11, 23));
|
|
633
|
+
const lvl = tag(level);
|
|
634
|
+
const sc = scope ? ` ${styleText("dim", scope)}` : "";
|
|
635
|
+
const prefix2 = `${styleText("yellow", "\u25B2")} ${ts} ${lvl}${sc}`;
|
|
636
|
+
const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
637
|
+
if (args.length > 0) {
|
|
638
|
+
fn(prefix2, msg, ...args);
|
|
639
|
+
} else {
|
|
640
|
+
fn(prefix2, msg);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
debug: (msg, ...args) => emit("debug", msg, args),
|
|
645
|
+
info: (msg, ...args) => emit("info", msg, args),
|
|
646
|
+
warn: (msg, ...args) => emit("warn", msg, args),
|
|
647
|
+
error: (msg, ...args) => emit("error", msg, args),
|
|
648
|
+
child: (childScope) => createLogger({
|
|
649
|
+
level: opts.level,
|
|
650
|
+
scope: scope ? `${scope}:${childScope}` : childScope
|
|
651
|
+
})
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
569
655
|
// ../channels/dist/telegram/telegram.js
|
|
570
656
|
import { Bot } from "grammy";
|
|
571
657
|
|
|
@@ -786,11 +872,13 @@ var TelegramChannel = class {
|
|
|
786
872
|
allowedUsers;
|
|
787
873
|
handlers = [];
|
|
788
874
|
sendPipeline;
|
|
875
|
+
log;
|
|
789
876
|
constructor(config) {
|
|
877
|
+
this.log = config.logger ?? noopLogger;
|
|
790
878
|
this.bot = new Bot(config.botToken);
|
|
791
879
|
this.allowedUsers = new Set(config.allowedUsers);
|
|
792
880
|
this.bot.catch((err2) => {
|
|
793
|
-
|
|
881
|
+
this.log.error("Bot error:", err2.message ?? err2);
|
|
794
882
|
});
|
|
795
883
|
this.bot.on("message:text", async (ctx) => {
|
|
796
884
|
const userId = ctx.from.id;
|
|
@@ -819,7 +907,7 @@ var TelegramChannel = class {
|
|
|
819
907
|
...replyOpts
|
|
820
908
|
}), { maxRetries: 3, baseDelayMs: 500 }).catch(async () => {
|
|
821
909
|
await this.bot.api.sendMessage(Number(msg.userId), msg.text, replyOpts).catch((fallbackErr) => {
|
|
822
|
-
|
|
910
|
+
this.log.error("Fallback send also failed:", fallbackErr);
|
|
823
911
|
throw fallbackErr;
|
|
824
912
|
});
|
|
825
913
|
});
|
|
@@ -830,7 +918,7 @@ var TelegramChannel = class {
|
|
|
830
918
|
return this.allowedUsers.has(userId);
|
|
831
919
|
}
|
|
832
920
|
handleRejected(userId, unixTimestamp, rejectMessage) {
|
|
833
|
-
|
|
921
|
+
this.log.warn(`Rejected message from unauthorized user ${userId} at ${new Date(unixTimestamp * 1e3).toISOString()}`);
|
|
834
922
|
if (rejectMessage) {
|
|
835
923
|
this.bot.api.sendMessage(userId, rejectMessage).catch(() => {
|
|
836
924
|
});
|
|
@@ -864,14 +952,27 @@ var ToolRegistry = class {
|
|
|
864
952
|
return Array.from(this.tools.values());
|
|
865
953
|
}
|
|
866
954
|
toFunctionSchemas() {
|
|
867
|
-
return this.list().map((tool) =>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
955
|
+
return this.list().map((tool) => {
|
|
956
|
+
let description = tool.description;
|
|
957
|
+
if (tool.configCheck && this.context) {
|
|
958
|
+
try {
|
|
959
|
+
const warning = tool.configCheck(this.context);
|
|
960
|
+
if (warning) {
|
|
961
|
+
description += `
|
|
962
|
+
[NOT CONFIGURED] ${warning}`;
|
|
963
|
+
}
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
873
966
|
}
|
|
874
|
-
|
|
967
|
+
return {
|
|
968
|
+
type: "function",
|
|
969
|
+
function: {
|
|
970
|
+
name: tool.name,
|
|
971
|
+
description,
|
|
972
|
+
parameters: tool.parameters
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
});
|
|
875
976
|
}
|
|
876
977
|
async execute(name, params) {
|
|
877
978
|
const tool = this.tools.get(name);
|
|
@@ -1011,10 +1112,54 @@ var scheduleTool = {
|
|
|
1011
1112
|
}
|
|
1012
1113
|
};
|
|
1013
1114
|
|
|
1115
|
+
// ../tools/dist/datetime.js
|
|
1116
|
+
var datetimeTool = {
|
|
1117
|
+
name: "datetime",
|
|
1118
|
+
description: "Get the current date and time, optionally in a specific timezone",
|
|
1119
|
+
parameters: {
|
|
1120
|
+
type: "object",
|
|
1121
|
+
properties: {
|
|
1122
|
+
timezone: {
|
|
1123
|
+
type: "string",
|
|
1124
|
+
description: "IANA timezone (e.g. 'Europe/Paris', 'America/New_York'). Defaults to the system timezone."
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
execute: async (params) => {
|
|
1129
|
+
const { timezone } = params;
|
|
1130
|
+
const now = /* @__PURE__ */ new Date();
|
|
1131
|
+
const options = {
|
|
1132
|
+
weekday: "long",
|
|
1133
|
+
year: "numeric",
|
|
1134
|
+
month: "long",
|
|
1135
|
+
day: "numeric",
|
|
1136
|
+
hour: "2-digit",
|
|
1137
|
+
minute: "2-digit",
|
|
1138
|
+
second: "2-digit",
|
|
1139
|
+
timeZoneName: "longOffset"
|
|
1140
|
+
};
|
|
1141
|
+
if (timezone) {
|
|
1142
|
+
options.timeZone = timezone;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
const formatted = new Intl.DateTimeFormat("en-US", options).format(now);
|
|
1146
|
+
return {
|
|
1147
|
+
success: true,
|
|
1148
|
+
output: `${formatted}
|
|
1149
|
+
ISO 8601 (UTC): ${now.toISOString()}
|
|
1150
|
+
Unix timestamp: ${Math.floor(now.getTime() / 1e3)}`
|
|
1151
|
+
};
|
|
1152
|
+
} catch {
|
|
1153
|
+
return { success: false, output: `Invalid timezone: ${timezone}` };
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1014
1158
|
// ../tools/dist/web-search.js
|
|
1015
1159
|
var webSearchTool = {
|
|
1016
1160
|
name: "web_search",
|
|
1017
1161
|
description: "Search the web using the configured search provider (Tavily, Exa, or SearXNG)",
|
|
1162
|
+
configCheck: (ctx) => ctx.config.tools?.webSearch ? null : "This tool requires configuration. See https://augure.dev/docs/tools/web-search",
|
|
1018
1163
|
parameters: {
|
|
1019
1164
|
type: "object",
|
|
1020
1165
|
properties: {
|
|
@@ -1226,6 +1371,273 @@ ${text}`;
|
|
|
1226
1371
|
// ../tools/dist/email.js
|
|
1227
1372
|
import { ImapFlow } from "imapflow";
|
|
1228
1373
|
import { createTransport } from "nodemailer";
|
|
1374
|
+
var MAX_BODY_CHARS = 4e3;
|
|
1375
|
+
function stripHtml(html) {
|
|
1376
|
+
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();
|
|
1377
|
+
}
|
|
1378
|
+
function findTextPart(structure) {
|
|
1379
|
+
if (structure.type === "text/plain" && structure.part) {
|
|
1380
|
+
return { part: structure.part, isHtml: false };
|
|
1381
|
+
}
|
|
1382
|
+
if (structure.type === "text/html" && structure.part) {
|
|
1383
|
+
return { part: structure.part, isHtml: true };
|
|
1384
|
+
}
|
|
1385
|
+
if (structure.childNodes) {
|
|
1386
|
+
let htmlFallback = null;
|
|
1387
|
+
for (const child of structure.childNodes) {
|
|
1388
|
+
const found = findTextPart(child);
|
|
1389
|
+
if (found && !found.isHtml)
|
|
1390
|
+
return found;
|
|
1391
|
+
if (found && found.isHtml)
|
|
1392
|
+
htmlFallback = found;
|
|
1393
|
+
}
|
|
1394
|
+
return htmlFallback;
|
|
1395
|
+
}
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
async function withImapClient(config, fn) {
|
|
1399
|
+
const client = new ImapFlow({
|
|
1400
|
+
host: config.host,
|
|
1401
|
+
port: config.port,
|
|
1402
|
+
secure: config.port === 993,
|
|
1403
|
+
auth: { user: config.user, pass: config.password },
|
|
1404
|
+
logger: false
|
|
1405
|
+
});
|
|
1406
|
+
let connected = false;
|
|
1407
|
+
try {
|
|
1408
|
+
await client.connect();
|
|
1409
|
+
connected = true;
|
|
1410
|
+
return await fn(client);
|
|
1411
|
+
} finally {
|
|
1412
|
+
if (connected) {
|
|
1413
|
+
await client.logout();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function formatSummaries(emails) {
|
|
1418
|
+
if (emails.length === 0)
|
|
1419
|
+
return "No emails found.";
|
|
1420
|
+
return emails.map((e, i) => `${i + 1}. ${e.seen ? "" : "[UNREAD] "}UID:${e.uid} Subject: ${e.subject} | From: ${e.from} | Date: ${e.date}`).join("\n");
|
|
1421
|
+
}
|
|
1422
|
+
function extractAddress(addr) {
|
|
1423
|
+
if (!addr)
|
|
1424
|
+
return "unknown";
|
|
1425
|
+
if (Array.isArray(addr)) {
|
|
1426
|
+
const first = addr[0];
|
|
1427
|
+
if (first && typeof first === "object" && "address" in first) {
|
|
1428
|
+
return first.address;
|
|
1429
|
+
}
|
|
1430
|
+
return String(first ?? "unknown");
|
|
1431
|
+
}
|
|
1432
|
+
if (typeof addr === "object" && "address" in addr) {
|
|
1433
|
+
return addr.address;
|
|
1434
|
+
}
|
|
1435
|
+
return String(addr);
|
|
1436
|
+
}
|
|
1437
|
+
async function handleList(params, imapConfig) {
|
|
1438
|
+
const folder = params.folder ?? "INBOX";
|
|
1439
|
+
const limit = params.limit ?? 10;
|
|
1440
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1441
|
+
const lock = await client.getMailboxLock(folder);
|
|
1442
|
+
try {
|
|
1443
|
+
const status = client.mailbox;
|
|
1444
|
+
if (!status || status.exists === 0)
|
|
1445
|
+
return "Mailbox is empty.";
|
|
1446
|
+
const start = Math.max(1, status.exists - limit + 1);
|
|
1447
|
+
const emails = [];
|
|
1448
|
+
for await (const msg of client.fetch(`${start}:*`, {
|
|
1449
|
+
envelope: true,
|
|
1450
|
+
flags: true
|
|
1451
|
+
})) {
|
|
1452
|
+
emails.push({
|
|
1453
|
+
uid: msg.uid,
|
|
1454
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
1455
|
+
from: extractAddress(msg.envelope?.from),
|
|
1456
|
+
date: msg.envelope?.date?.toISOString() ?? "unknown",
|
|
1457
|
+
seen: msg.flags?.has("\\Seen") ?? false
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
return formatSummaries(emails.slice(-limit));
|
|
1461
|
+
} finally {
|
|
1462
|
+
lock.release();
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
async function handleRead(params, imapConfig) {
|
|
1467
|
+
const folder = params.folder ?? "INBOX";
|
|
1468
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1469
|
+
const lock = await client.getMailboxLock(folder);
|
|
1470
|
+
try {
|
|
1471
|
+
const msg = await client.fetchOne(String(params.uid), {
|
|
1472
|
+
envelope: true,
|
|
1473
|
+
bodyStructure: true,
|
|
1474
|
+
flags: true,
|
|
1475
|
+
uid: true
|
|
1476
|
+
});
|
|
1477
|
+
if (!msg)
|
|
1478
|
+
return `No email found with UID ${params.uid}.`;
|
|
1479
|
+
let bodyText = "";
|
|
1480
|
+
const textPart = msg.bodyStructure ? findTextPart(msg.bodyStructure) : null;
|
|
1481
|
+
if (textPart) {
|
|
1482
|
+
const { content } = await client.download(String(params.uid), textPart.part, {
|
|
1483
|
+
uid: true
|
|
1484
|
+
});
|
|
1485
|
+
const chunks = [];
|
|
1486
|
+
for await (const chunk of content) {
|
|
1487
|
+
chunks.push(Buffer.from(chunk));
|
|
1488
|
+
}
|
|
1489
|
+
bodyText = Buffer.concat(chunks).toString("utf-8");
|
|
1490
|
+
if (textPart.isHtml)
|
|
1491
|
+
bodyText = stripHtml(bodyText);
|
|
1492
|
+
}
|
|
1493
|
+
if (bodyText.length > MAX_BODY_CHARS) {
|
|
1494
|
+
bodyText = bodyText.slice(0, MAX_BODY_CHARS) + "\n[truncated]";
|
|
1495
|
+
}
|
|
1496
|
+
const wasSeen = msg.flags?.has("\\Seen") ?? false;
|
|
1497
|
+
await client.messageFlagsAdd(String(params.uid), ["\\Seen"], { uid: true });
|
|
1498
|
+
const subject = msg.envelope?.subject ?? "(no subject)";
|
|
1499
|
+
const from = extractAddress(msg.envelope?.from);
|
|
1500
|
+
const date = msg.envelope?.date?.toISOString() ?? "unknown";
|
|
1501
|
+
return `UID: ${params.uid}
|
|
1502
|
+
Subject: ${subject}
|
|
1503
|
+
From: ${from}
|
|
1504
|
+
Date: ${date}
|
|
1505
|
+
Status: ${wasSeen ? "was read" : "was unread, marked read"}
|
|
1506
|
+
|
|
1507
|
+
${bodyText}`;
|
|
1508
|
+
} finally {
|
|
1509
|
+
lock.release();
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
async function handleSearch(params, imapConfig) {
|
|
1514
|
+
const folder = params.folder ?? "INBOX";
|
|
1515
|
+
const limit = params.limit ?? 10;
|
|
1516
|
+
return withImapClient(imapConfig, async (client) => {
|
|
1517
|
+
const lock = await client.getMailboxLock(folder);
|
|
1518
|
+
try {
|
|
1519
|
+
const criteria = {};
|
|
1520
|
+
if (params.from)
|
|
1521
|
+
criteria.from = params.from;
|
|
1522
|
+
if (params.subject)
|
|
1523
|
+
criteria.subject = params.subject;
|
|
1524
|
+
if (params.since)
|
|
1525
|
+
criteria.since = new Date(params.since);
|
|
1526
|
+
if (params.unseen)
|
|
1527
|
+
criteria.seen = false;
|
|
1528
|
+
const result = await client.search(criteria, { uid: true });
|
|
1529
|
+
const uids = Array.isArray(result) ? result : [];
|
|
1530
|
+
if (uids.length === 0)
|
|
1531
|
+
return "No emails match the search criteria.";
|
|
1532
|
+
const selected = uids.slice(-limit);
|
|
1533
|
+
const emails = [];
|
|
1534
|
+
for await (const msg of client.fetch(selected, { envelope: true, flags: true }, { uid: true })) {
|
|
1535
|
+
emails.push({
|
|
1536
|
+
uid: msg.uid,
|
|
1537
|
+
subject: msg.envelope?.subject ?? "(no subject)",
|
|
1538
|
+
from: extractAddress(msg.envelope?.from),
|
|
1539
|
+
date: msg.envelope?.date?.toISOString() ?? "unknown",
|
|
1540
|
+
seen: msg.flags?.has("\\Seen") ?? false
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return formatSummaries(emails);
|
|
1544
|
+
} finally {
|
|
1545
|
+
lock.release();
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
async function handleSend(params, smtpConfig) {
|
|
1550
|
+
const transport = createTransport({
|
|
1551
|
+
host: smtpConfig.host,
|
|
1552
|
+
port: smtpConfig.port,
|
|
1553
|
+
secure: smtpConfig.port === 465,
|
|
1554
|
+
auth: { user: smtpConfig.user, pass: smtpConfig.password }
|
|
1555
|
+
});
|
|
1556
|
+
const info = await transport.sendMail({
|
|
1557
|
+
from: smtpConfig.user,
|
|
1558
|
+
to: params.to,
|
|
1559
|
+
subject: params.subject,
|
|
1560
|
+
text: params.body,
|
|
1561
|
+
cc: params.cc,
|
|
1562
|
+
bcc: params.bcc
|
|
1563
|
+
});
|
|
1564
|
+
return `Email sent. Message ID: ${info.messageId}`;
|
|
1565
|
+
}
|
|
1566
|
+
var emailTool = {
|
|
1567
|
+
name: "email",
|
|
1568
|
+
description: "Manage email: list recent messages, read by UID, search with criteria, or send an email via SMTP",
|
|
1569
|
+
configCheck: (ctx) => ctx.config.tools?.email ? null : "This tool requires configuration. See https://augure.dev/docs/tools/email",
|
|
1570
|
+
parameters: {
|
|
1571
|
+
type: "object",
|
|
1572
|
+
properties: {
|
|
1573
|
+
action: {
|
|
1574
|
+
type: "string",
|
|
1575
|
+
enum: ["list", "read", "search", "send"],
|
|
1576
|
+
description: "The email action to perform"
|
|
1577
|
+
},
|
|
1578
|
+
folder: {
|
|
1579
|
+
type: "string",
|
|
1580
|
+
description: 'IMAP folder (default: "INBOX"). Used by list, read, search.'
|
|
1581
|
+
},
|
|
1582
|
+
limit: {
|
|
1583
|
+
type: "number",
|
|
1584
|
+
description: "Max emails to return (default: 10). Used by list, search."
|
|
1585
|
+
},
|
|
1586
|
+
uid: { type: "number", description: "Email UID to read. Required for read." },
|
|
1587
|
+
from: { type: "string", description: "Filter by sender address. Used by search." },
|
|
1588
|
+
subject: {
|
|
1589
|
+
type: "string",
|
|
1590
|
+
description: "Filter by subject (search) or email subject (send)."
|
|
1591
|
+
},
|
|
1592
|
+
since: {
|
|
1593
|
+
type: "string",
|
|
1594
|
+
description: "ISO 8601 date \u2014 emails since this date. Used by search."
|
|
1595
|
+
},
|
|
1596
|
+
unseen: { type: "boolean", description: "Only unread emails. Used by search." },
|
|
1597
|
+
to: { type: "string", description: "Recipient address. Required for send." },
|
|
1598
|
+
body: { type: "string", description: "Email body text. Required for send." },
|
|
1599
|
+
cc: { type: "string", description: "CC recipients. Used by send." },
|
|
1600
|
+
bcc: { type: "string", description: "BCC recipients. Used by send." }
|
|
1601
|
+
},
|
|
1602
|
+
required: ["action"]
|
|
1603
|
+
},
|
|
1604
|
+
execute: async (params, ctx) => {
|
|
1605
|
+
const p = params;
|
|
1606
|
+
const emailConfig = ctx.config.tools?.email;
|
|
1607
|
+
if (!emailConfig) {
|
|
1608
|
+
return { success: false, output: "Email is not configured. Add tools.email to your config." };
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
switch (p.action) {
|
|
1612
|
+
case "list":
|
|
1613
|
+
return { success: true, output: await handleList(p, emailConfig.imap) };
|
|
1614
|
+
case "read":
|
|
1615
|
+
if (!p.uid)
|
|
1616
|
+
return { success: false, output: "Missing required field: uid" };
|
|
1617
|
+
return { success: true, output: await handleRead(p, emailConfig.imap) };
|
|
1618
|
+
case "search":
|
|
1619
|
+
if (!p.from && !p.subject && !p.since && p.unseen === void 0)
|
|
1620
|
+
return { success: false, output: "At least one search criterion is required (from, subject, since, or unseen)." };
|
|
1621
|
+
return { success: true, output: await handleSearch(p, emailConfig.imap) };
|
|
1622
|
+
case "send":
|
|
1623
|
+
if (!p.to)
|
|
1624
|
+
return { success: false, output: "Missing required field: to" };
|
|
1625
|
+
if (!p.subject)
|
|
1626
|
+
return { success: false, output: "Missing required field: subject" };
|
|
1627
|
+
if (!p.body)
|
|
1628
|
+
return { success: false, output: "Missing required field: body" };
|
|
1629
|
+
return { success: true, output: await handleSend(p, emailConfig.smtp) };
|
|
1630
|
+
default:
|
|
1631
|
+
return { success: false, output: `Unknown action: ${p.action}` };
|
|
1632
|
+
}
|
|
1633
|
+
} catch (err2) {
|
|
1634
|
+
return {
|
|
1635
|
+
success: false,
|
|
1636
|
+
output: err2 instanceof Error ? err2.message : String(err2)
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1229
1641
|
|
|
1230
1642
|
// ../tools/dist/sandbox-exec.js
|
|
1231
1643
|
var sandboxExecTool = {
|
|
@@ -1304,6 +1716,7 @@ function shellEscape(s) {
|
|
|
1304
1716
|
var opencodeTool = {
|
|
1305
1717
|
name: "opencode",
|
|
1306
1718
|
description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
|
|
1719
|
+
configCheck: (ctx) => ctx.config.sandbox.codeAgent ? null : "This tool requires sandbox.codeAgent configuration. See https://augure.dev/docs/sandbox",
|
|
1307
1720
|
parameters: {
|
|
1308
1721
|
type: "object",
|
|
1309
1722
|
properties: {
|
|
@@ -1515,6 +1928,10 @@ var DockerContainer = class {
|
|
|
1515
1928
|
}
|
|
1516
1929
|
});
|
|
1517
1930
|
this.demux(stream, stdoutPT, stderrPT);
|
|
1931
|
+
stream.on("end", () => {
|
|
1932
|
+
stdoutPT.end();
|
|
1933
|
+
stderrPT.end();
|
|
1934
|
+
});
|
|
1518
1935
|
});
|
|
1519
1936
|
}
|
|
1520
1937
|
};
|
|
@@ -1540,6 +1957,7 @@ var DockerContainerPool = class {
|
|
|
1540
1957
|
docker;
|
|
1541
1958
|
image;
|
|
1542
1959
|
maxTotal;
|
|
1960
|
+
log;
|
|
1543
1961
|
// C3: idle cache keyed by trust level to prevent cross-trust reuse
|
|
1544
1962
|
idle = /* @__PURE__ */ new Map([
|
|
1545
1963
|
["sandboxed", /* @__PURE__ */ new Set()],
|
|
@@ -1551,6 +1969,7 @@ var DockerContainerPool = class {
|
|
|
1551
1969
|
this.docker = docker;
|
|
1552
1970
|
this.image = config.image;
|
|
1553
1971
|
this.maxTotal = config.maxTotal;
|
|
1972
|
+
this.log = config.logger ?? noopLogger;
|
|
1554
1973
|
}
|
|
1555
1974
|
get idleCount() {
|
|
1556
1975
|
let count = 0;
|
|
@@ -1560,18 +1979,22 @@ var DockerContainerPool = class {
|
|
|
1560
1979
|
}
|
|
1561
1980
|
/* ---- acquire ---- */
|
|
1562
1981
|
async acquire(opts) {
|
|
1982
|
+
this.log.debug(`Acquiring container: trust=${opts.trust} memory=${opts.memory} cpu=${opts.cpu}`);
|
|
1563
1983
|
const trustIdle = this.idle.get(opts.trust);
|
|
1564
1984
|
const cached = trustIdle.values().next();
|
|
1565
1985
|
if (!cached.done) {
|
|
1566
1986
|
const container2 = cached.value;
|
|
1567
1987
|
trustIdle.delete(container2);
|
|
1568
1988
|
this.busy.add(container2);
|
|
1989
|
+
this.log.debug(`Reusing cached container: ${container2.id.slice(0, 12)}`);
|
|
1569
1990
|
return container2;
|
|
1570
1991
|
}
|
|
1571
1992
|
const total = this.idleCount + this.busy.size;
|
|
1572
1993
|
if (total >= this.maxTotal) {
|
|
1994
|
+
this.log.error(`Pool limit reached: ${total}/${this.maxTotal}`);
|
|
1573
1995
|
throw new Error("Pool limit reached");
|
|
1574
1996
|
}
|
|
1997
|
+
this.log.debug("Creating new container...");
|
|
1575
1998
|
const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
|
|
1576
1999
|
await raw.start();
|
|
1577
2000
|
const modem = this.docker.modem;
|
|
@@ -1579,6 +2002,7 @@ var DockerContainerPool = class {
|
|
|
1579
2002
|
const container = new DockerContainer(raw, demux);
|
|
1580
2003
|
this.containerTrust.set(container.id, opts.trust);
|
|
1581
2004
|
this.busy.add(container);
|
|
2005
|
+
this.log.debug(`Container created: ${container.id.slice(0, 12)}`);
|
|
1582
2006
|
return container;
|
|
1583
2007
|
}
|
|
1584
2008
|
/* ---- release ---- */
|
|
@@ -1590,6 +2014,7 @@ var DockerContainerPool = class {
|
|
|
1590
2014
|
}
|
|
1591
2015
|
const trust = this.containerTrust.get(container.id) ?? "sandboxed";
|
|
1592
2016
|
this.idle.get(trust).add(container);
|
|
2017
|
+
this.log.debug(`Container released: ${container.id.slice(0, 12)} \u2192 idle (${trust})`);
|
|
1593
2018
|
}
|
|
1594
2019
|
/* ---- destroy ---- */
|
|
1595
2020
|
async destroy(container) {
|
|
@@ -1678,16 +2103,19 @@ function buildTar(content) {
|
|
|
1678
2103
|
const end = Buffer.alloc(1024, 0);
|
|
1679
2104
|
return Buffer.concat([header, dataPadded, end]);
|
|
1680
2105
|
}
|
|
1681
|
-
async function ensureImage(docker, imageName) {
|
|
2106
|
+
async function ensureImage(docker, imageName, logger) {
|
|
2107
|
+
const log = logger ?? noopLogger;
|
|
2108
|
+
log.debug(`Checking image: ${imageName}`);
|
|
1682
2109
|
try {
|
|
1683
2110
|
await docker.getImage(imageName).inspect();
|
|
2111
|
+
log.debug("Image exists");
|
|
1684
2112
|
return;
|
|
1685
2113
|
} catch (err2) {
|
|
1686
2114
|
const statusCode = err2.statusCode;
|
|
1687
2115
|
if (statusCode !== void 0 && statusCode !== 404)
|
|
1688
2116
|
throw err2;
|
|
1689
2117
|
}
|
|
1690
|
-
|
|
2118
|
+
log.info(`Image "${imageName}" not found, building...`);
|
|
1691
2119
|
const tar = buildTar(DOCKERFILE);
|
|
1692
2120
|
const stream = await docker.buildImage(Readable.from(tar), {
|
|
1693
2121
|
t: imageName
|
|
@@ -1699,11 +2127,14 @@ async function ensureImage(docker, imageName) {
|
|
|
1699
2127
|
else
|
|
1700
2128
|
resolve5();
|
|
1701
2129
|
}, (event) => {
|
|
1702
|
-
if (event.stream)
|
|
1703
|
-
|
|
2130
|
+
if (event.stream) {
|
|
2131
|
+
const line = event.stream.trim();
|
|
2132
|
+
if (line)
|
|
2133
|
+
log.debug(line);
|
|
2134
|
+
}
|
|
1704
2135
|
});
|
|
1705
2136
|
});
|
|
1706
|
-
|
|
2137
|
+
log.info(`Image "${imageName}" built`);
|
|
1707
2138
|
}
|
|
1708
2139
|
|
|
1709
2140
|
// ../memory/dist/store.js
|
|
@@ -2016,10 +2447,13 @@ Be concise. Only suggest actions that are clearly needed based on the memory con
|
|
|
2016
2447
|
var Heartbeat = class {
|
|
2017
2448
|
config;
|
|
2018
2449
|
timer;
|
|
2450
|
+
log;
|
|
2019
2451
|
constructor(config) {
|
|
2020
2452
|
this.config = config;
|
|
2453
|
+
this.log = config.logger ?? noopLogger;
|
|
2021
2454
|
}
|
|
2022
2455
|
async tick() {
|
|
2456
|
+
this.log.debug("Heartbeat tick");
|
|
2023
2457
|
const memoryContent = await this.loadMemory();
|
|
2024
2458
|
const messages = [
|
|
2025
2459
|
{ role: "system", content: HEARTBEAT_PROMPT },
|
|
@@ -2034,12 +2468,15 @@ ${memoryContent}`
|
|
|
2034
2468
|
const response = await this.config.llm.chat(messages);
|
|
2035
2469
|
const action = this.parseAction(response.content);
|
|
2036
2470
|
if (action && action.toLowerCase() !== "none") {
|
|
2471
|
+
this.log.debug(`Heartbeat action: ${action}`);
|
|
2037
2472
|
await this.config.onAction(action);
|
|
2473
|
+
} else {
|
|
2474
|
+
this.log.debug("Heartbeat: no action needed");
|
|
2038
2475
|
}
|
|
2039
2476
|
}
|
|
2040
2477
|
start() {
|
|
2041
2478
|
this.timer = setInterval(() => {
|
|
2042
|
-
this.tick().catch((err2) =>
|
|
2479
|
+
this.tick().catch((err2) => this.log.error("Heartbeat error:", err2));
|
|
2043
2480
|
}, this.config.intervalMs);
|
|
2044
2481
|
}
|
|
2045
2482
|
stop() {
|
|
@@ -2886,9 +3323,11 @@ var JOB_PREFIX = "skill:";
|
|
|
2886
3323
|
var SkillSchedulerBridge = class {
|
|
2887
3324
|
scheduler;
|
|
2888
3325
|
manager;
|
|
2889
|
-
|
|
3326
|
+
log;
|
|
3327
|
+
constructor(scheduler, manager, logger) {
|
|
2890
3328
|
this.scheduler = scheduler;
|
|
2891
3329
|
this.manager = manager;
|
|
3330
|
+
this.log = logger ?? noopLogger;
|
|
2892
3331
|
}
|
|
2893
3332
|
/** Register cron jobs for all active cron-triggered skills */
|
|
2894
3333
|
async syncAll() {
|
|
@@ -2907,7 +3346,7 @@ var SkillSchedulerBridge = class {
|
|
|
2907
3346
|
enabled: true
|
|
2908
3347
|
});
|
|
2909
3348
|
} catch (err2) {
|
|
2910
|
-
|
|
3349
|
+
this.log.error(`Failed to register cron for ${skill.id}:`, err2);
|
|
2911
3350
|
}
|
|
2912
3351
|
}
|
|
2913
3352
|
existingJobs.delete(jobId);
|
|
@@ -3401,27 +3840,48 @@ var VersionChecker = class _VersionChecker {
|
|
|
3401
3840
|
};
|
|
3402
3841
|
|
|
3403
3842
|
// ../core/dist/main.js
|
|
3404
|
-
var
|
|
3843
|
+
var BASE_SYSTEM_PROMPT = `You are Augure, a personal AI assistant. You are proactive, helpful, and concise.
|
|
3405
3844
|
You speak the same language as the user. You have access to tools and persistent memory.
|
|
3406
|
-
Always be direct and actionable
|
|
3407
|
-
|
|
3845
|
+
Always be direct and actionable.
|
|
3846
|
+
|
|
3847
|
+
## Your capabilities
|
|
3848
|
+
|
|
3849
|
+
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.
|
|
3850
|
+
|
|
3851
|
+
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.`;
|
|
3852
|
+
var SKILLS_PROMPT = `
|
|
3853
|
+
## Skills
|
|
3854
|
+
|
|
3855
|
+
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.
|
|
3856
|
+
|
|
3857
|
+
- Use skill_list to see existing skills and their status
|
|
3858
|
+
- Use skill_generate to create a new skill from a natural language description
|
|
3859
|
+
- Use skill_run to execute a skill manually
|
|
3860
|
+
- Use skill_heal to fix a broken skill
|
|
3861
|
+
- Use skill_install to install a skill from the hub
|
|
3862
|
+
|
|
3863
|
+
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.`;
|
|
3864
|
+
function resolveLLMClient(config, usage, logger) {
|
|
3408
3865
|
const override = usage !== "default" ? config[usage] : void 0;
|
|
3409
3866
|
return new OpenRouterClient({
|
|
3410
3867
|
apiKey: override?.apiKey ?? config.default.apiKey,
|
|
3411
3868
|
model: override?.model ?? config.default.model,
|
|
3412
|
-
maxTokens: override?.maxTokens ?? config.default.maxTokens
|
|
3869
|
+
maxTokens: override?.maxTokens ?? config.default.maxTokens,
|
|
3870
|
+
logger: logger.child("llm")
|
|
3413
3871
|
});
|
|
3414
3872
|
}
|
|
3415
|
-
async function startAgent(configPath) {
|
|
3873
|
+
async function startAgent(configPath, opts) {
|
|
3874
|
+
const log = createLogger({ level: opts?.debug ? "debug" : "info" });
|
|
3416
3875
|
const config = await loadConfig(configPath);
|
|
3417
|
-
|
|
3876
|
+
log.info(`Loaded config: ${config.identity.name}`);
|
|
3877
|
+
log.debug(`Config path: ${configPath}`);
|
|
3418
3878
|
let telegram;
|
|
3419
|
-
const llm = resolveLLMClient(config.llm, "default");
|
|
3420
|
-
const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
|
|
3421
|
-
const monitoringLLM = resolveLLMClient(config.llm, "monitoring");
|
|
3879
|
+
const llm = resolveLLMClient(config.llm, "default", log);
|
|
3880
|
+
const ingestionLLM = resolveLLMClient(config.llm, "ingestion", log);
|
|
3881
|
+
const monitoringLLM = resolveLLMClient(config.llm, "monitoring", log);
|
|
3422
3882
|
const memoryPath = resolve(configPath, "..", config.memory.path);
|
|
3423
3883
|
const memory = new FileMemoryStore(memoryPath);
|
|
3424
|
-
|
|
3884
|
+
log.info(`Memory store: ${memoryPath}`);
|
|
3425
3885
|
const retriever = new MemoryRetriever(memory, {
|
|
3426
3886
|
maxTokens: config.memory.maxRetrievalTokens
|
|
3427
3887
|
});
|
|
@@ -3430,15 +3890,17 @@ async function startAgent(configPath) {
|
|
|
3430
3890
|
tools.register(memoryReadTool);
|
|
3431
3891
|
tools.register(memoryWriteTool);
|
|
3432
3892
|
tools.register(scheduleTool);
|
|
3893
|
+
tools.register(datetimeTool);
|
|
3433
3894
|
tools.register(webSearchTool);
|
|
3434
3895
|
tools.register(httpTool);
|
|
3896
|
+
tools.register(emailTool);
|
|
3435
3897
|
tools.register(sandboxExecTool);
|
|
3436
3898
|
tools.register(opencodeTool);
|
|
3437
3899
|
const jobStorePath = resolve(configPath, "..", "jobs.json");
|
|
3438
3900
|
const jobStore = new JobStore(jobStorePath);
|
|
3439
3901
|
const scheduler = new CronScheduler(jobStore);
|
|
3440
3902
|
await scheduler.loadPersistedJobs();
|
|
3441
|
-
|
|
3903
|
+
log.info(`Loaded ${scheduler.listJobs().length} persisted jobs`);
|
|
3442
3904
|
for (const job of config.scheduler.jobs) {
|
|
3443
3905
|
if (!scheduler.listJobs().some((j) => j.id === job.id)) {
|
|
3444
3906
|
scheduler.addJob({ ...job, enabled: true });
|
|
@@ -3446,17 +3908,19 @@ async function startAgent(configPath) {
|
|
|
3446
3908
|
}
|
|
3447
3909
|
const docker = new Dockerode();
|
|
3448
3910
|
const sandboxImage = config.sandbox.image ?? "augure-sandbox:latest";
|
|
3449
|
-
|
|
3911
|
+
const sandboxLog = log.child("sandbox");
|
|
3912
|
+
await ensureImage(docker, sandboxImage, sandboxLog);
|
|
3450
3913
|
const pool = new DockerContainerPool(docker, {
|
|
3451
3914
|
image: sandboxImage,
|
|
3452
|
-
maxTotal: config.security.maxConcurrentSandboxes
|
|
3915
|
+
maxTotal: config.security.maxConcurrentSandboxes,
|
|
3916
|
+
logger: sandboxLog
|
|
3453
3917
|
});
|
|
3454
|
-
|
|
3918
|
+
log.info(`Container pool: max=${config.security.maxConcurrentSandboxes}`);
|
|
3455
3919
|
let skillManagerRef;
|
|
3456
3920
|
let skillUpdater;
|
|
3457
3921
|
if (config.skills) {
|
|
3458
3922
|
const skillsPath = resolve(configPath, "..", config.skills.path);
|
|
3459
|
-
const codingLLM = resolveLLMClient(config.llm, "coding");
|
|
3923
|
+
const codingLLM = resolveLLMClient(config.llm, "coding", log);
|
|
3460
3924
|
const skillManager = new SkillManager(skillsPath);
|
|
3461
3925
|
const skillGenerator = new SkillGenerator(codingLLM);
|
|
3462
3926
|
const skillRunner = new SkillRunner({
|
|
@@ -3500,29 +3964,29 @@ async function startAgent(configPath) {
|
|
|
3500
3964
|
const updated = updateResults.filter((r) => r.success);
|
|
3501
3965
|
const failed = updateResults.filter((r) => !r.success);
|
|
3502
3966
|
if (updated.length > 0) {
|
|
3503
|
-
|
|
3967
|
+
log.info(`Skills updated: ${updated.map((r) => `${r.skillId} (v${r.fromVersion}\u2192v${r.toVersion})`).join(", ")}`);
|
|
3504
3968
|
}
|
|
3505
3969
|
if (failed.length > 0) {
|
|
3506
|
-
|
|
3970
|
+
log.warn(`Skill updates failed: ${failed.map((r) => `${r.skillId}: ${r.error}`).join(", ")}`);
|
|
3507
3971
|
}
|
|
3508
3972
|
} catch (err2) {
|
|
3509
|
-
|
|
3973
|
+
log.error("Skill update check failed:", err2);
|
|
3510
3974
|
}
|
|
3511
3975
|
}
|
|
3512
3976
|
skillManagerRef = skillManager;
|
|
3513
|
-
|
|
3977
|
+
log.info(`Skills initialized: ${skillsPath}`);
|
|
3514
3978
|
}
|
|
3515
3979
|
tools.setContext({ config, memory, scheduler, pool });
|
|
3516
3980
|
const auditConfig = config.audit ?? { path: "./logs", enabled: true };
|
|
3517
3981
|
const auditPath = resolve(configPath, "..", auditConfig.path);
|
|
3518
|
-
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath) : new NullAuditLogger();
|
|
3519
|
-
|
|
3982
|
+
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath, log.child("audit")) : new NullAuditLogger();
|
|
3983
|
+
log.info(`Audit: ${auditConfig.enabled ? auditPath : "disabled"}`);
|
|
3520
3984
|
let personaResolver;
|
|
3521
3985
|
if (config.persona) {
|
|
3522
3986
|
const personaPath = resolve(configPath, "..", config.persona.path);
|
|
3523
3987
|
personaResolver = new PersonaResolver(personaPath);
|
|
3524
3988
|
await personaResolver.loadAll();
|
|
3525
|
-
|
|
3989
|
+
log.info(`Personas: ${personaPath}`);
|
|
3526
3990
|
}
|
|
3527
3991
|
let cliVersion;
|
|
3528
3992
|
try {
|
|
@@ -3538,29 +4002,33 @@ async function startAgent(configPath) {
|
|
|
3538
4002
|
});
|
|
3539
4003
|
const versionResult = await versionChecker.check();
|
|
3540
4004
|
if (versionResult.updateAvailable) {
|
|
3541
|
-
|
|
4005
|
+
log.warn(`Update available: v${versionResult.latestVersion} (current: v${versionResult.currentVersion}). Run: npm update -g augure`);
|
|
3542
4006
|
}
|
|
3543
4007
|
}
|
|
3544
4008
|
const guard = new ContextGuard({
|
|
3545
4009
|
maxContextTokens: 2e5,
|
|
3546
4010
|
reservedForOutput: config.llm.default.maxTokens ?? 8192
|
|
3547
4011
|
});
|
|
4012
|
+
const systemPrompt = config.skills ? BASE_SYSTEM_PROMPT + SKILLS_PROMPT : BASE_SYSTEM_PROMPT;
|
|
3548
4013
|
const agent = new Agent({
|
|
3549
4014
|
llm,
|
|
3550
4015
|
tools,
|
|
3551
|
-
systemPrompt
|
|
4016
|
+
systemPrompt,
|
|
3552
4017
|
memoryContent: "",
|
|
3553
4018
|
retriever,
|
|
3554
4019
|
ingester,
|
|
3555
4020
|
audit,
|
|
3556
4021
|
guard,
|
|
3557
|
-
modelName: config.llm.default.model
|
|
4022
|
+
modelName: config.llm.default.model,
|
|
4023
|
+
logger: log.child("agent")
|
|
3558
4024
|
});
|
|
3559
4025
|
if (config.channels.telegram?.enabled) {
|
|
4026
|
+
const telegramLog = log.child("telegram");
|
|
3560
4027
|
telegram = new TelegramChannel({
|
|
3561
4028
|
botToken: config.channels.telegram.botToken,
|
|
3562
4029
|
allowedUsers: config.channels.telegram.allowedUsers,
|
|
3563
|
-
rejectMessage: config.channels.telegram.rejectMessage
|
|
4030
|
+
rejectMessage: config.channels.telegram.rejectMessage,
|
|
4031
|
+
logger: telegramLog
|
|
3564
4032
|
});
|
|
3565
4033
|
const tg = telegram;
|
|
3566
4034
|
const commandCtx = {
|
|
@@ -3570,7 +4038,7 @@ async function startAgent(configPath) {
|
|
|
3570
4038
|
skillManager: skillManagerRef
|
|
3571
4039
|
};
|
|
3572
4040
|
tg.onMessage(async (msg) => {
|
|
3573
|
-
|
|
4041
|
+
log.info(`Message from ${msg.userId}: ${msg.text}`);
|
|
3574
4042
|
try {
|
|
3575
4043
|
const cmdResult = await handleCommand(msg.text, commandCtx);
|
|
3576
4044
|
if (cmdResult.handled) {
|
|
@@ -3593,7 +4061,7 @@ async function startAgent(configPath) {
|
|
|
3593
4061
|
replyTo: msg.id
|
|
3594
4062
|
});
|
|
3595
4063
|
} catch (err2) {
|
|
3596
|
-
|
|
4064
|
+
log.error("Error handling message:", err2);
|
|
3597
4065
|
await tg.send({
|
|
3598
4066
|
channelType: "telegram",
|
|
3599
4067
|
userId: msg.userId,
|
|
@@ -3602,15 +4070,16 @@ async function startAgent(configPath) {
|
|
|
3602
4070
|
}
|
|
3603
4071
|
});
|
|
3604
4072
|
await tg.start();
|
|
3605
|
-
|
|
4073
|
+
log.info("Telegram bot started");
|
|
3606
4074
|
}
|
|
3607
4075
|
const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
|
|
3608
4076
|
const heartbeat = new Heartbeat({
|
|
3609
4077
|
llm: monitoringLLM,
|
|
3610
4078
|
memory,
|
|
3611
4079
|
intervalMs: heartbeatIntervalMs,
|
|
4080
|
+
logger: log.child("heartbeat"),
|
|
3612
4081
|
onAction: async (action) => {
|
|
3613
|
-
|
|
4082
|
+
log.info(`Heartbeat action: ${action}`);
|
|
3614
4083
|
const response = await agent.handleMessage({
|
|
3615
4084
|
id: `heartbeat-${Date.now()}`,
|
|
3616
4085
|
channelType: "system",
|
|
@@ -3618,11 +4087,11 @@ async function startAgent(configPath) {
|
|
|
3618
4087
|
text: `[Heartbeat] ${action}`,
|
|
3619
4088
|
timestamp: /* @__PURE__ */ new Date()
|
|
3620
4089
|
});
|
|
3621
|
-
|
|
4090
|
+
log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
|
|
3622
4091
|
}
|
|
3623
4092
|
});
|
|
3624
4093
|
scheduler.onJobTrigger(async (job) => {
|
|
3625
|
-
|
|
4094
|
+
log.info(`Job triggered: ${job.id}`);
|
|
3626
4095
|
const response = await agent.handleMessage({
|
|
3627
4096
|
id: `job-${job.id}-${Date.now()}`,
|
|
3628
4097
|
channelType: "system",
|
|
@@ -3640,11 +4109,11 @@ async function startAgent(configPath) {
|
|
|
3640
4109
|
});
|
|
3641
4110
|
}
|
|
3642
4111
|
}
|
|
3643
|
-
|
|
4112
|
+
log.debug(`Job ${job.id} completed`);
|
|
3644
4113
|
});
|
|
3645
4114
|
scheduler.start();
|
|
3646
4115
|
heartbeat.start();
|
|
3647
|
-
|
|
4116
|
+
log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
|
|
3648
4117
|
const updateTimers = [];
|
|
3649
4118
|
if (skillUpdater && config.updates?.skills?.checkInterval) {
|
|
3650
4119
|
const su = skillUpdater;
|
|
@@ -3654,13 +4123,13 @@ async function startAgent(configPath) {
|
|
|
3654
4123
|
const results = await su.checkAndApply();
|
|
3655
4124
|
for (const r of results) {
|
|
3656
4125
|
if (r.success) {
|
|
3657
|
-
|
|
4126
|
+
log.info(`Skill auto-updated: ${r.skillId} v${r.fromVersion}\u2192v${r.toVersion}`);
|
|
3658
4127
|
} else if (r.rolledBack) {
|
|
3659
|
-
|
|
4128
|
+
log.warn(`Skill update rolled back: ${r.skillId} - ${r.error}`);
|
|
3660
4129
|
}
|
|
3661
4130
|
}
|
|
3662
4131
|
} catch (err2) {
|
|
3663
|
-
|
|
4132
|
+
log.error("Periodic skill update check failed:", err2);
|
|
3664
4133
|
}
|
|
3665
4134
|
}, skillCheckMs));
|
|
3666
4135
|
}
|
|
@@ -3685,12 +4154,12 @@ Run: \`npm update -g augure\``
|
|
|
3685
4154
|
}
|
|
3686
4155
|
}
|
|
3687
4156
|
} catch (err2) {
|
|
3688
|
-
|
|
4157
|
+
log.error("CLI version check failed:", err2);
|
|
3689
4158
|
}
|
|
3690
4159
|
}, cliCheckMs));
|
|
3691
4160
|
}
|
|
3692
4161
|
const shutdown = async () => {
|
|
3693
|
-
|
|
4162
|
+
log.info("Shutting down...");
|
|
3694
4163
|
for (const timer of updateTimers)
|
|
3695
4164
|
clearInterval(timer);
|
|
3696
4165
|
heartbeat.stop();
|
|
@@ -3699,23 +4168,13 @@ Run: \`npm update -g augure\``
|
|
|
3699
4168
|
await telegram.stop();
|
|
3700
4169
|
await pool.destroyAll();
|
|
3701
4170
|
await audit.close();
|
|
3702
|
-
|
|
4171
|
+
log.info("All containers destroyed");
|
|
3703
4172
|
process.exit(0);
|
|
3704
4173
|
};
|
|
3705
4174
|
process.on("SIGINT", shutdown);
|
|
3706
4175
|
process.on("SIGTERM", shutdown);
|
|
3707
4176
|
}
|
|
3708
4177
|
|
|
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
4178
|
// src/commands/start.ts
|
|
3720
4179
|
var startCommand = defineCommand({
|
|
3721
4180
|
meta: {
|
|
@@ -3733,21 +4192,28 @@ var startCommand = defineCommand({
|
|
|
3733
4192
|
type: "string",
|
|
3734
4193
|
description: "Path to .env file",
|
|
3735
4194
|
alias: "e"
|
|
4195
|
+
},
|
|
4196
|
+
debug: {
|
|
4197
|
+
type: "boolean",
|
|
4198
|
+
description: "Enable debug logging",
|
|
4199
|
+
alias: "d",
|
|
4200
|
+
default: false
|
|
3736
4201
|
}
|
|
3737
4202
|
},
|
|
3738
4203
|
async run({ args }) {
|
|
3739
4204
|
const configPath = resolve2(args.config);
|
|
4205
|
+
const log = createLogger({ level: args.debug ? "debug" : "info" });
|
|
3740
4206
|
const envPath = args.env ? resolve2(args.env) : join6(dirname4(configPath), ".env");
|
|
3741
4207
|
try {
|
|
3742
4208
|
process.loadEnvFile(envPath);
|
|
3743
|
-
|
|
4209
|
+
log.debug(`Loaded env from ${envPath}`);
|
|
3744
4210
|
} catch {
|
|
3745
4211
|
}
|
|
3746
|
-
|
|
4212
|
+
log.info(`Starting with config: ${configPath}`);
|
|
3747
4213
|
try {
|
|
3748
|
-
await startAgent(configPath);
|
|
4214
|
+
await startAgent(configPath, { debug: args.debug });
|
|
3749
4215
|
} catch (e) {
|
|
3750
|
-
|
|
4216
|
+
log.error("Fatal:", e instanceof Error ? e.message : String(e));
|
|
3751
4217
|
process.exit(1);
|
|
3752
4218
|
}
|
|
3753
4219
|
}
|
|
@@ -3757,6 +4223,18 @@ var startCommand = defineCommand({
|
|
|
3757
4223
|
import { defineCommand as defineCommand2 } from "citty";
|
|
3758
4224
|
import { writeFile as writeFile5, access as access3 } from "fs/promises";
|
|
3759
4225
|
import { resolve as resolve3 } from "path";
|
|
4226
|
+
|
|
4227
|
+
// src/colors.ts
|
|
4228
|
+
import { styleText as styleText2 } from "util";
|
|
4229
|
+
var brand = (s) => styleText2("yellow", s);
|
|
4230
|
+
var ok = (s) => styleText2("green", s);
|
|
4231
|
+
var err = (s) => styleText2("red", s);
|
|
4232
|
+
var dim = (s) => styleText2("dim", s);
|
|
4233
|
+
var bold = (s) => styleText2("bold", s);
|
|
4234
|
+
var cyan = (s) => styleText2("cyan", s);
|
|
4235
|
+
var prefix = brand("\u25B2 augure");
|
|
4236
|
+
|
|
4237
|
+
// src/commands/init.ts
|
|
3760
4238
|
var CONFIG_TEMPLATE = `{
|
|
3761
4239
|
// Identity
|
|
3762
4240
|
identity: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "augure",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Augure — your proactive AI agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=22.0.0"
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:unit": "vitest run",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "eslint src/",
|
|
22
|
+
"clean": "rm -rf dist .turbo"
|
|
23
|
+
},
|
|
15
24
|
"dependencies": {
|
|
16
25
|
"citty": "^0.2.1",
|
|
17
26
|
"dockerode": "^4.0.9",
|
|
@@ -24,17 +33,17 @@
|
|
|
24
33
|
"zod": "^4.3.6"
|
|
25
34
|
},
|
|
26
35
|
"devDependencies": {
|
|
36
|
+
"@augure/channels": "workspace:*",
|
|
37
|
+
"@augure/core": "workspace:*",
|
|
38
|
+
"@augure/memory": "workspace:*",
|
|
39
|
+
"@augure/sandbox": "workspace:*",
|
|
40
|
+
"@augure/scheduler": "workspace:*",
|
|
41
|
+
"@augure/skills": "workspace:*",
|
|
42
|
+
"@augure/tools": "workspace:*",
|
|
43
|
+
"@augure/types": "workspace:*",
|
|
27
44
|
"@types/dockerode": "^4.0.1",
|
|
28
45
|
"@types/node-cron": "^3.0.11",
|
|
29
|
-
"tsup": "^8.5.1"
|
|
30
|
-
"@augure/channels": "0.1.1",
|
|
31
|
-
"@augure/core": "0.2.0",
|
|
32
|
-
"@augure/memory": "0.0.3",
|
|
33
|
-
"@augure/sandbox": "0.1.0",
|
|
34
|
-
"@augure/scheduler": "0.1.0",
|
|
35
|
-
"@augure/skills": "0.1.1",
|
|
36
|
-
"@augure/types": "0.1.1",
|
|
37
|
-
"@augure/tools": "0.0.3"
|
|
46
|
+
"tsup": "^8.5.1"
|
|
38
47
|
},
|
|
39
48
|
"keywords": [
|
|
40
49
|
"ai",
|
|
@@ -52,14 +61,5 @@
|
|
|
52
61
|
},
|
|
53
62
|
"publishConfig": {
|
|
54
63
|
"access": "public"
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"build": "tsup",
|
|
58
|
-
"dev": "tsup --watch",
|
|
59
|
-
"test": "vitest run",
|
|
60
|
-
"test:unit": "vitest run",
|
|
61
|
-
"typecheck": "tsc --noEmit",
|
|
62
|
-
"lint": "eslint src/",
|
|
63
|
-
"clean": "rm -rf dist .turbo"
|
|
64
64
|
}
|
|
65
|
-
}
|
|
65
|
+
}
|