augure 0.4.2 → 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 +760 -165
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -50,9 +50,12 @@ var AppConfigSchema = z.object({
|
|
|
50
50
|
heartbeatInterval: z.string().min(1),
|
|
51
51
|
jobs: z.array(z.object({
|
|
52
52
|
id: z.string().min(1),
|
|
53
|
-
cron: z.string().min(1),
|
|
53
|
+
cron: z.string().min(1).optional(),
|
|
54
|
+
runAt: z.string().min(1).optional(),
|
|
54
55
|
prompt: z.string().min(1),
|
|
55
56
|
channel: z.string().min(1)
|
|
57
|
+
}).refine((j) => j.cron || j.runAt, {
|
|
58
|
+
message: "Job must have either cron or runAt"
|
|
56
59
|
}))
|
|
57
60
|
}),
|
|
58
61
|
sandbox: z.object({
|
|
@@ -150,19 +153,33 @@ async function loadConfig(path) {
|
|
|
150
153
|
return AppConfigSchema.parse(parsed);
|
|
151
154
|
}
|
|
152
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
|
+
|
|
153
167
|
// ../core/dist/llm.js
|
|
154
168
|
var OpenRouterClient = class {
|
|
155
169
|
apiKey;
|
|
156
170
|
model;
|
|
157
171
|
maxTokens;
|
|
158
172
|
baseUrl;
|
|
173
|
+
log;
|
|
159
174
|
constructor(config) {
|
|
160
175
|
this.apiKey = config.apiKey;
|
|
161
176
|
this.model = config.model;
|
|
162
177
|
this.maxTokens = config.maxTokens;
|
|
163
178
|
this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
179
|
+
this.log = config.logger ?? noopLogger;
|
|
164
180
|
}
|
|
165
181
|
async chat(messages, tools) {
|
|
182
|
+
this.log.debug(`Request: model=${this.model} messages=${messages.length} tools=${tools?.length ?? 0}`);
|
|
166
183
|
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
167
184
|
method: "POST",
|
|
168
185
|
headers: {
|
|
@@ -196,6 +213,7 @@ var OpenRouterClient = class {
|
|
|
196
213
|
}
|
|
197
214
|
const data = await response.json();
|
|
198
215
|
const choice = data.choices[0];
|
|
216
|
+
this.log.debug(`Response: ${response.status} ${data.usage.prompt_tokens}+${data.usage.completion_tokens} tokens`);
|
|
199
217
|
return {
|
|
200
218
|
content: choice.message.content ?? "",
|
|
201
219
|
toolCalls: (choice.message.tool_calls ?? []).map((tc) => {
|
|
@@ -203,7 +221,7 @@ var OpenRouterClient = class {
|
|
|
203
221
|
try {
|
|
204
222
|
args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
205
223
|
} catch {
|
|
206
|
-
|
|
224
|
+
this.log.warn(`Failed to parse tool call arguments for ${tc.function.name}`);
|
|
207
225
|
}
|
|
208
226
|
return { id: tc.id, name: tc.function.name, arguments: args };
|
|
209
227
|
}),
|
|
@@ -219,6 +237,19 @@ var OpenRouterClient = class {
|
|
|
219
237
|
function assembleContext(input) {
|
|
220
238
|
const { systemPrompt, memoryContent, conversationHistory, persona } = input;
|
|
221
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})`;
|
|
222
253
|
if (persona) {
|
|
223
254
|
system += `
|
|
224
255
|
|
|
@@ -246,13 +277,15 @@ function summarize(text, maxLen = 200) {
|
|
|
246
277
|
}
|
|
247
278
|
var FileAuditLogger = class {
|
|
248
279
|
basePath;
|
|
280
|
+
logger;
|
|
249
281
|
pendingWrite = Promise.resolve();
|
|
250
282
|
initialized = false;
|
|
251
|
-
constructor(basePath) {
|
|
283
|
+
constructor(basePath, logger) {
|
|
252
284
|
this.basePath = basePath;
|
|
285
|
+
this.logger = logger ?? noopLogger;
|
|
253
286
|
}
|
|
254
287
|
log(entry) {
|
|
255
|
-
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));
|
|
256
289
|
}
|
|
257
290
|
async close() {
|
|
258
291
|
await this.pendingWrite;
|
|
@@ -278,10 +311,12 @@ var NullAuditLogger = class {
|
|
|
278
311
|
// ../core/dist/agent.js
|
|
279
312
|
var Agent = class {
|
|
280
313
|
config;
|
|
314
|
+
log;
|
|
281
315
|
conversations = /* @__PURE__ */ new Map();
|
|
282
316
|
state = "running";
|
|
283
317
|
constructor(config) {
|
|
284
318
|
this.config = config;
|
|
319
|
+
this.log = config.logger ?? noopLogger;
|
|
285
320
|
}
|
|
286
321
|
getState() {
|
|
287
322
|
return this.state;
|
|
@@ -321,12 +356,14 @@ var Agent = class {
|
|
|
321
356
|
conversationHistory: history,
|
|
322
357
|
persona: this.config.persona
|
|
323
358
|
});
|
|
359
|
+
this.log.debug(`LLM call #${loopCount + 1} (${messages.length} messages)`);
|
|
324
360
|
const response = await this.config.llm.chat(messages, toolSchemas);
|
|
325
361
|
if (response.toolCalls.length === 0) {
|
|
326
362
|
history.push({
|
|
327
363
|
role: "assistant",
|
|
328
364
|
content: response.content
|
|
329
365
|
});
|
|
366
|
+
this.log.debug(`Response: ${response.usage.inputTokens}+${response.usage.outputTokens} tokens, ${Date.now() - start}ms`);
|
|
330
367
|
if (this.config.audit) {
|
|
331
368
|
this.config.audit.log({
|
|
332
369
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -344,7 +381,7 @@ var Agent = class {
|
|
|
344
381
|
});
|
|
345
382
|
}
|
|
346
383
|
if (this.config.ingester) {
|
|
347
|
-
this.config.ingester.ingest(history).catch((err2) =>
|
|
384
|
+
this.config.ingester.ingest(history).catch((err2) => this.log.error("Ingestion error:", err2));
|
|
348
385
|
}
|
|
349
386
|
return response.content;
|
|
350
387
|
}
|
|
@@ -355,7 +392,9 @@ var Agent = class {
|
|
|
355
392
|
});
|
|
356
393
|
for (const toolCall of response.toolCalls) {
|
|
357
394
|
const toolStart = Date.now();
|
|
395
|
+
this.log.debug(`Tool: ${toolCall.name}`);
|
|
358
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)`);
|
|
359
398
|
history.push({
|
|
360
399
|
role: "tool",
|
|
361
400
|
content: result.output,
|
|
@@ -563,6 +602,56 @@ var ContextGuard = class {
|
|
|
563
602
|
}
|
|
564
603
|
};
|
|
565
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
|
+
|
|
566
655
|
// ../channels/dist/telegram/telegram.js
|
|
567
656
|
import { Bot } from "grammy";
|
|
568
657
|
|
|
@@ -582,62 +671,6 @@ function createOutgoingPipeline(middlewares, send) {
|
|
|
582
671
|
};
|
|
583
672
|
}
|
|
584
673
|
|
|
585
|
-
// ../channels/dist/middleware/escape-markdown.js
|
|
586
|
-
var SPECIAL_CHARS = /* @__PURE__ */ new Set([".", "!", ">", "#", "+", "-", "=", "|", "{", "}", "~"]);
|
|
587
|
-
function escapeMarkdownV2(text) {
|
|
588
|
-
if (!text)
|
|
589
|
-
return "";
|
|
590
|
-
const parts = [];
|
|
591
|
-
let i = 0;
|
|
592
|
-
while (i < text.length) {
|
|
593
|
-
if (text.startsWith("```", i)) {
|
|
594
|
-
const endIdx = text.indexOf("```", i + 3);
|
|
595
|
-
if (endIdx !== -1) {
|
|
596
|
-
parts.push(text.slice(i, endIdx + 3));
|
|
597
|
-
i = endIdx + 3;
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if (text[i] === "`") {
|
|
602
|
-
const endIdx = text.indexOf("`", i + 1);
|
|
603
|
-
if (endIdx !== -1) {
|
|
604
|
-
parts.push(text.slice(i, endIdx + 1));
|
|
605
|
-
i = endIdx + 1;
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
if (text[i] === "*" || text[i] === "_") {
|
|
610
|
-
parts.push(text[i]);
|
|
611
|
-
i++;
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
if (text[i] === "[" || text[i] === "(") {
|
|
615
|
-
parts.push(text[i]);
|
|
616
|
-
i++;
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
if (text[i] === "]") {
|
|
620
|
-
parts.push(text[i]);
|
|
621
|
-
i++;
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
const char = text[i];
|
|
625
|
-
if (SPECIAL_CHARS.has(char)) {
|
|
626
|
-
parts.push(`\\${char}`);
|
|
627
|
-
} else {
|
|
628
|
-
parts.push(char);
|
|
629
|
-
}
|
|
630
|
-
i++;
|
|
631
|
-
}
|
|
632
|
-
return parts.join("");
|
|
633
|
-
}
|
|
634
|
-
function createEscapeMarkdownMiddleware() {
|
|
635
|
-
return async (message, next) => {
|
|
636
|
-
message.text = escapeMarkdownV2(message.text);
|
|
637
|
-
await next();
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
|
|
641
674
|
// ../channels/dist/middleware/split-message.js
|
|
642
675
|
var TELEGRAM_MAX = 4096;
|
|
643
676
|
function splitText(text, maxLength) {
|
|
@@ -743,6 +776,38 @@ async function withRetry(fn, options) {
|
|
|
743
776
|
throw lastError;
|
|
744
777
|
}
|
|
745
778
|
|
|
779
|
+
// ../channels/dist/middleware/markdown-to-html.js
|
|
780
|
+
function markdownToTelegramHtml(text) {
|
|
781
|
+
if (!text)
|
|
782
|
+
return "";
|
|
783
|
+
const placeholders = [];
|
|
784
|
+
const PH_PREFIX = "\uFFFCPH";
|
|
785
|
+
const PH_SUFFIX = "\uFFFC";
|
|
786
|
+
function hold(html) {
|
|
787
|
+
const idx = placeholders.length;
|
|
788
|
+
placeholders.push(html);
|
|
789
|
+
return `${PH_PREFIX}${idx}${PH_SUFFIX}`;
|
|
790
|
+
}
|
|
791
|
+
let out = text;
|
|
792
|
+
out = out.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => hold(lang ? `<pre><code class="language-${escapeHtml(lang)}">${escapeHtml(code.trimEnd())}</code></pre>` : `<pre>${escapeHtml(code.trimEnd())}</pre>`));
|
|
793
|
+
out = out.replace(/`([^`\n]+)`/g, (_, code) => hold(`<code>${escapeHtml(code)}</code>`));
|
|
794
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, linkText, url) => hold(`<a href="${escapeHtml(url)}">${escapeHtml(linkText)}</a>`));
|
|
795
|
+
out = escapeHtml(out);
|
|
796
|
+
out = out.replace(/\*\*\*(.+?)\*\*\*/g, "<b><i>$1</i></b>");
|
|
797
|
+
out = out.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
798
|
+
out = out.replace(/\*([^*\n]+?)\*/g, "<i>$1</i>");
|
|
799
|
+
out = out.replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
800
|
+
out = out.replace(/^#{1,6}\s+(.+)$/gm, "<b>$1</b>");
|
|
801
|
+
out = out.replace(/^>\s?(.*)$/gm, "<blockquote>$1</blockquote>");
|
|
802
|
+
out = out.replace(/<\/blockquote>\n<blockquote>/g, "\n");
|
|
803
|
+
const phPattern = new RegExp(`${PH_PREFIX}(\\d+)${PH_SUFFIX}`, "g");
|
|
804
|
+
out = out.replace(phPattern, (_, idx) => placeholders[Number(idx)]);
|
|
805
|
+
return out;
|
|
806
|
+
}
|
|
807
|
+
function escapeHtml(text) {
|
|
808
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
809
|
+
}
|
|
810
|
+
|
|
746
811
|
// ../channels/dist/telegram/media.js
|
|
747
812
|
function registerMediaHandlers(bot, isAllowed, handlers, onRejected) {
|
|
748
813
|
bot.on("message:photo", async (ctx) => {
|
|
@@ -807,11 +872,13 @@ var TelegramChannel = class {
|
|
|
807
872
|
allowedUsers;
|
|
808
873
|
handlers = [];
|
|
809
874
|
sendPipeline;
|
|
875
|
+
log;
|
|
810
876
|
constructor(config) {
|
|
877
|
+
this.log = config.logger ?? noopLogger;
|
|
811
878
|
this.bot = new Bot(config.botToken);
|
|
812
879
|
this.allowedUsers = new Set(config.allowedUsers);
|
|
813
880
|
this.bot.catch((err2) => {
|
|
814
|
-
|
|
881
|
+
this.log.error("Bot error:", err2.message ?? err2);
|
|
815
882
|
});
|
|
816
883
|
this.bot.on("message:text", async (ctx) => {
|
|
817
884
|
const userId = ctx.from.id;
|
|
@@ -832,29 +899,26 @@ var TelegramChannel = class {
|
|
|
832
899
|
}
|
|
833
900
|
});
|
|
834
901
|
registerMediaHandlers(this.bot, (id) => this.isUserAllowed(id), this.handlers, (userId, ts) => this.handleRejected(userId, Math.floor(ts.getTime() / 1e3), config.rejectMessage));
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
902
|
+
const convertAndSend = async (msg) => {
|
|
903
|
+
const htmlText = markdownToTelegramHtml(msg.text);
|
|
904
|
+
const replyOpts = msg.replyTo ? { reply_parameters: { message_id: Number(msg.replyTo) } } : {};
|
|
905
|
+
await withRetry(() => this.bot.api.sendMessage(Number(msg.userId), htmlText, {
|
|
906
|
+
parse_mode: "HTML",
|
|
907
|
+
...replyOpts
|
|
839
908
|
}), { maxRetries: 3, baseDelayMs: 500 }).catch(async () => {
|
|
840
|
-
await this.bot.api.sendMessage(Number(msg.userId), msg.text, {
|
|
841
|
-
|
|
842
|
-
}).catch((fallbackErr) => {
|
|
843
|
-
console.error("[augure:telegram] Fallback send also failed:", fallbackErr);
|
|
909
|
+
await this.bot.api.sendMessage(Number(msg.userId), msg.text, replyOpts).catch((fallbackErr) => {
|
|
910
|
+
this.log.error("Fallback send also failed:", fallbackErr);
|
|
844
911
|
throw fallbackErr;
|
|
845
912
|
});
|
|
846
913
|
});
|
|
847
914
|
};
|
|
848
|
-
this.sendPipeline = createOutgoingPipeline([
|
|
849
|
-
createEscapeMarkdownMiddleware(),
|
|
850
|
-
createSplitMessageMiddleware(rawSend)
|
|
851
|
-
], rawSend);
|
|
915
|
+
this.sendPipeline = createOutgoingPipeline([createSplitMessageMiddleware(convertAndSend)], convertAndSend);
|
|
852
916
|
}
|
|
853
917
|
isUserAllowed(userId) {
|
|
854
918
|
return this.allowedUsers.has(userId);
|
|
855
919
|
}
|
|
856
920
|
handleRejected(userId, unixTimestamp, rejectMessage) {
|
|
857
|
-
|
|
921
|
+
this.log.warn(`Rejected message from unauthorized user ${userId} at ${new Date(unixTimestamp * 1e3).toISOString()}`);
|
|
858
922
|
if (rejectMessage) {
|
|
859
923
|
this.bot.api.sendMessage(userId, rejectMessage).catch(() => {
|
|
860
924
|
});
|
|
@@ -888,14 +952,27 @@ var ToolRegistry = class {
|
|
|
888
952
|
return Array.from(this.tools.values());
|
|
889
953
|
}
|
|
890
954
|
toFunctionSchemas() {
|
|
891
|
-
return this.list().map((tool) =>
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
+
}
|
|
897
966
|
}
|
|
898
|
-
|
|
967
|
+
return {
|
|
968
|
+
type: "function",
|
|
969
|
+
function: {
|
|
970
|
+
name: tool.name,
|
|
971
|
+
description,
|
|
972
|
+
parameters: tool.parameters
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
});
|
|
899
976
|
}
|
|
900
977
|
async execute(name, params) {
|
|
901
978
|
const tool = this.tools.get(name);
|
|
@@ -967,7 +1044,7 @@ var memoryWriteTool = {
|
|
|
967
1044
|
// ../tools/dist/schedule.js
|
|
968
1045
|
var scheduleTool = {
|
|
969
1046
|
name: "schedule",
|
|
970
|
-
description: "Manage scheduled tasks: create, delete, or list
|
|
1047
|
+
description: "Manage scheduled tasks: create recurring (cron) or one-shot (runAt) jobs, delete, or list them",
|
|
971
1048
|
parameters: {
|
|
972
1049
|
type: "object",
|
|
973
1050
|
properties: {
|
|
@@ -977,13 +1054,14 @@ var scheduleTool = {
|
|
|
977
1054
|
description: "The scheduling action to perform"
|
|
978
1055
|
},
|
|
979
1056
|
id: { type: "string", description: "The schedule ID (for create/delete)" },
|
|
980
|
-
cron: { type: "string", description: "Cron expression (for create)" },
|
|
1057
|
+
cron: { type: "string", description: "Cron expression for recurring jobs (for create)" },
|
|
1058
|
+
runAt: { type: "string", description: "ISO 8601 date for one-shot jobs, e.g. 2025-03-15T14:00:00Z (for create)" },
|
|
981
1059
|
prompt: { type: "string", description: "The prompt to execute on schedule (for create)" }
|
|
982
1060
|
},
|
|
983
1061
|
required: ["action"]
|
|
984
1062
|
},
|
|
985
1063
|
execute: async (params, ctx) => {
|
|
986
|
-
const { action, id, cron, prompt } = params;
|
|
1064
|
+
const { action, id, cron, runAt, prompt } = params;
|
|
987
1065
|
try {
|
|
988
1066
|
switch (action) {
|
|
989
1067
|
case "list": {
|
|
@@ -991,22 +1069,29 @@ var scheduleTool = {
|
|
|
991
1069
|
if (jobs.length === 0) {
|
|
992
1070
|
return { success: true, output: "No scheduled jobs." };
|
|
993
1071
|
}
|
|
994
|
-
const lines = jobs.map((j) =>
|
|
1072
|
+
const lines = jobs.map((j) => {
|
|
1073
|
+
const schedule = j.cron ? `cron: ${j.cron}` : `runAt: ${j.runAt}`;
|
|
1074
|
+
return `- ${j.id}: "${j.prompt}" @ ${schedule} (${j.enabled ? "enabled" : "disabled"})`;
|
|
1075
|
+
});
|
|
995
1076
|
return { success: true, output: lines.join("\n") };
|
|
996
1077
|
}
|
|
997
1078
|
case "create": {
|
|
998
|
-
if (!
|
|
999
|
-
return { success: false, output: "Missing required
|
|
1079
|
+
if (!prompt) {
|
|
1080
|
+
return { success: false, output: "Missing required field: prompt" };
|
|
1081
|
+
}
|
|
1082
|
+
if (!cron && !runAt) {
|
|
1083
|
+
return { success: false, output: "Must provide either cron (recurring) or runAt (one-shot)" };
|
|
1000
1084
|
}
|
|
1001
1085
|
const jobId = id ?? `job-${Date.now()}`;
|
|
1002
1086
|
ctx.scheduler.addJob({
|
|
1003
1087
|
id: jobId,
|
|
1004
1088
|
cron,
|
|
1089
|
+
runAt,
|
|
1005
1090
|
prompt,
|
|
1006
1091
|
channel: "default",
|
|
1007
1092
|
enabled: true
|
|
1008
1093
|
});
|
|
1009
|
-
return { success: true, output: `Created job ${jobId}` };
|
|
1094
|
+
return { success: true, output: `Created job ${jobId} (${cron ? "recurring" : `one-shot at ${runAt}`})` };
|
|
1010
1095
|
}
|
|
1011
1096
|
case "delete": {
|
|
1012
1097
|
if (!id) {
|
|
@@ -1027,10 +1112,54 @@ var scheduleTool = {
|
|
|
1027
1112
|
}
|
|
1028
1113
|
};
|
|
1029
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
|
+
|
|
1030
1158
|
// ../tools/dist/web-search.js
|
|
1031
1159
|
var webSearchTool = {
|
|
1032
1160
|
name: "web_search",
|
|
1033
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",
|
|
1034
1163
|
parameters: {
|
|
1035
1164
|
type: "object",
|
|
1036
1165
|
properties: {
|
|
@@ -1242,6 +1371,273 @@ ${text}`;
|
|
|
1242
1371
|
// ../tools/dist/email.js
|
|
1243
1372
|
import { ImapFlow } from "imapflow";
|
|
1244
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
|
+
};
|
|
1245
1641
|
|
|
1246
1642
|
// ../tools/dist/sandbox-exec.js
|
|
1247
1643
|
var sandboxExecTool = {
|
|
@@ -1320,6 +1716,7 @@ function shellEscape(s) {
|
|
|
1320
1716
|
var opencodeTool = {
|
|
1321
1717
|
name: "opencode",
|
|
1322
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",
|
|
1323
1720
|
parameters: {
|
|
1324
1721
|
type: "object",
|
|
1325
1722
|
properties: {
|
|
@@ -1531,6 +1928,10 @@ var DockerContainer = class {
|
|
|
1531
1928
|
}
|
|
1532
1929
|
});
|
|
1533
1930
|
this.demux(stream, stdoutPT, stderrPT);
|
|
1931
|
+
stream.on("end", () => {
|
|
1932
|
+
stdoutPT.end();
|
|
1933
|
+
stderrPT.end();
|
|
1934
|
+
});
|
|
1534
1935
|
});
|
|
1535
1936
|
}
|
|
1536
1937
|
};
|
|
@@ -1556,6 +1957,7 @@ var DockerContainerPool = class {
|
|
|
1556
1957
|
docker;
|
|
1557
1958
|
image;
|
|
1558
1959
|
maxTotal;
|
|
1960
|
+
log;
|
|
1559
1961
|
// C3: idle cache keyed by trust level to prevent cross-trust reuse
|
|
1560
1962
|
idle = /* @__PURE__ */ new Map([
|
|
1561
1963
|
["sandboxed", /* @__PURE__ */ new Set()],
|
|
@@ -1567,6 +1969,7 @@ var DockerContainerPool = class {
|
|
|
1567
1969
|
this.docker = docker;
|
|
1568
1970
|
this.image = config.image;
|
|
1569
1971
|
this.maxTotal = config.maxTotal;
|
|
1972
|
+
this.log = config.logger ?? noopLogger;
|
|
1570
1973
|
}
|
|
1571
1974
|
get idleCount() {
|
|
1572
1975
|
let count = 0;
|
|
@@ -1576,18 +1979,22 @@ var DockerContainerPool = class {
|
|
|
1576
1979
|
}
|
|
1577
1980
|
/* ---- acquire ---- */
|
|
1578
1981
|
async acquire(opts) {
|
|
1982
|
+
this.log.debug(`Acquiring container: trust=${opts.trust} memory=${opts.memory} cpu=${opts.cpu}`);
|
|
1579
1983
|
const trustIdle = this.idle.get(opts.trust);
|
|
1580
1984
|
const cached = trustIdle.values().next();
|
|
1581
1985
|
if (!cached.done) {
|
|
1582
1986
|
const container2 = cached.value;
|
|
1583
1987
|
trustIdle.delete(container2);
|
|
1584
1988
|
this.busy.add(container2);
|
|
1989
|
+
this.log.debug(`Reusing cached container: ${container2.id.slice(0, 12)}`);
|
|
1585
1990
|
return container2;
|
|
1586
1991
|
}
|
|
1587
1992
|
const total = this.idleCount + this.busy.size;
|
|
1588
1993
|
if (total >= this.maxTotal) {
|
|
1994
|
+
this.log.error(`Pool limit reached: ${total}/${this.maxTotal}`);
|
|
1589
1995
|
throw new Error("Pool limit reached");
|
|
1590
1996
|
}
|
|
1997
|
+
this.log.debug("Creating new container...");
|
|
1591
1998
|
const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
|
|
1592
1999
|
await raw.start();
|
|
1593
2000
|
const modem = this.docker.modem;
|
|
@@ -1595,6 +2002,7 @@ var DockerContainerPool = class {
|
|
|
1595
2002
|
const container = new DockerContainer(raw, demux);
|
|
1596
2003
|
this.containerTrust.set(container.id, opts.trust);
|
|
1597
2004
|
this.busy.add(container);
|
|
2005
|
+
this.log.debug(`Container created: ${container.id.slice(0, 12)}`);
|
|
1598
2006
|
return container;
|
|
1599
2007
|
}
|
|
1600
2008
|
/* ---- release ---- */
|
|
@@ -1606,6 +2014,7 @@ var DockerContainerPool = class {
|
|
|
1606
2014
|
}
|
|
1607
2015
|
const trust = this.containerTrust.get(container.id) ?? "sandboxed";
|
|
1608
2016
|
this.idle.get(trust).add(container);
|
|
2017
|
+
this.log.debug(`Container released: ${container.id.slice(0, 12)} \u2192 idle (${trust})`);
|
|
1609
2018
|
}
|
|
1610
2019
|
/* ---- destroy ---- */
|
|
1611
2020
|
async destroy(container) {
|
|
@@ -1665,6 +2074,69 @@ var DockerContainerPool = class {
|
|
|
1665
2074
|
}
|
|
1666
2075
|
};
|
|
1667
2076
|
|
|
2077
|
+
// ../sandbox/dist/ensure-image.js
|
|
2078
|
+
import { Readable } from "stream";
|
|
2079
|
+
var DOCKERFILE = `FROM node:22-slim
|
|
2080
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
2081
|
+
python3 curl jq git \\
|
|
2082
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
2083
|
+
WORKDIR /workspace
|
|
2084
|
+
`;
|
|
2085
|
+
function buildTar(content) {
|
|
2086
|
+
const data = Buffer.from(content, "utf-8");
|
|
2087
|
+
const name = "Dockerfile";
|
|
2088
|
+
const header = Buffer.alloc(512, 0);
|
|
2089
|
+
header.write(name, 0, 100, "utf-8");
|
|
2090
|
+
header.write("0000644\0", 100, 8, "utf-8");
|
|
2091
|
+
header.write("0000000\0", 108, 8, "utf-8");
|
|
2092
|
+
header.write("0000000\0", 116, 8, "utf-8");
|
|
2093
|
+
header.write(data.length.toString(8).padStart(11, "0") + "\0", 124, 12, "utf-8");
|
|
2094
|
+
header.write(Math.floor(Date.now() / 1e3).toString(8).padStart(11, "0") + "\0", 136, 12, "utf-8");
|
|
2095
|
+
header.write("0", 156, 1, "utf-8");
|
|
2096
|
+
header.fill(32, 148, 156);
|
|
2097
|
+
let checksum = 0;
|
|
2098
|
+
for (let i = 0; i < 512; i++)
|
|
2099
|
+
checksum += header[i];
|
|
2100
|
+
header.write(checksum.toString(8).padStart(6, "0") + "\0 ", 148, 8, "utf-8");
|
|
2101
|
+
const padding = 512 - (data.length % 512 || 512);
|
|
2102
|
+
const dataPadded = padding > 0 && padding < 512 ? Buffer.concat([data, Buffer.alloc(padding, 0)]) : data;
|
|
2103
|
+
const end = Buffer.alloc(1024, 0);
|
|
2104
|
+
return Buffer.concat([header, dataPadded, end]);
|
|
2105
|
+
}
|
|
2106
|
+
async function ensureImage(docker, imageName, logger) {
|
|
2107
|
+
const log = logger ?? noopLogger;
|
|
2108
|
+
log.debug(`Checking image: ${imageName}`);
|
|
2109
|
+
try {
|
|
2110
|
+
await docker.getImage(imageName).inspect();
|
|
2111
|
+
log.debug("Image exists");
|
|
2112
|
+
return;
|
|
2113
|
+
} catch (err2) {
|
|
2114
|
+
const statusCode = err2.statusCode;
|
|
2115
|
+
if (statusCode !== void 0 && statusCode !== 404)
|
|
2116
|
+
throw err2;
|
|
2117
|
+
}
|
|
2118
|
+
log.info(`Image "${imageName}" not found, building...`);
|
|
2119
|
+
const tar = buildTar(DOCKERFILE);
|
|
2120
|
+
const stream = await docker.buildImage(Readable.from(tar), {
|
|
2121
|
+
t: imageName
|
|
2122
|
+
});
|
|
2123
|
+
await new Promise((resolve5, reject) => {
|
|
2124
|
+
docker.modem.followProgress(stream, (err2) => {
|
|
2125
|
+
if (err2)
|
|
2126
|
+
reject(err2);
|
|
2127
|
+
else
|
|
2128
|
+
resolve5();
|
|
2129
|
+
}, (event) => {
|
|
2130
|
+
if (event.stream) {
|
|
2131
|
+
const line = event.stream.trim();
|
|
2132
|
+
if (line)
|
|
2133
|
+
log.debug(line);
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
log.info(`Image "${imageName}" built`);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
1668
2140
|
// ../memory/dist/store.js
|
|
1669
2141
|
import { readFile as readFile3, writeFile, mkdir as mkdir2, readdir as readdir2, access } from "fs/promises";
|
|
1670
2142
|
import { join as join3, dirname, relative } from "path";
|
|
@@ -1828,6 +2300,7 @@ var CronScheduler = class {
|
|
|
1828
2300
|
store;
|
|
1829
2301
|
jobs = /* @__PURE__ */ new Map();
|
|
1830
2302
|
tasks = /* @__PURE__ */ new Map();
|
|
2303
|
+
timers = /* @__PURE__ */ new Map();
|
|
1831
2304
|
handlers = [];
|
|
1832
2305
|
persistChain = Promise.resolve();
|
|
1833
2306
|
constructor(store) {
|
|
@@ -1837,11 +2310,17 @@ var CronScheduler = class {
|
|
|
1837
2310
|
this.handlers.push(handler);
|
|
1838
2311
|
}
|
|
1839
2312
|
addJob(job) {
|
|
1840
|
-
if (!
|
|
2313
|
+
if (!job.cron && !job.runAt) {
|
|
2314
|
+
throw new Error(`Job ${job.id} must have either cron or runAt`);
|
|
2315
|
+
}
|
|
2316
|
+
if (job.cron && !validate(job.cron)) {
|
|
1841
2317
|
throw new Error(`Invalid cron expression: ${job.cron}`);
|
|
1842
2318
|
}
|
|
2319
|
+
if (job.runAt && isNaN(Date.parse(job.runAt))) {
|
|
2320
|
+
throw new Error(`Invalid runAt date: ${job.runAt}`);
|
|
2321
|
+
}
|
|
1843
2322
|
this.jobs.set(job.id, job);
|
|
1844
|
-
if (job.enabled) {
|
|
2323
|
+
if (job.enabled && job.cron) {
|
|
1845
2324
|
const task = createTask(job.cron, () => {
|
|
1846
2325
|
void this.executeHandlers(job);
|
|
1847
2326
|
});
|
|
@@ -1855,6 +2334,11 @@ var CronScheduler = class {
|
|
|
1855
2334
|
task.stop();
|
|
1856
2335
|
this.tasks.delete(id);
|
|
1857
2336
|
}
|
|
2337
|
+
const timer = this.timers.get(id);
|
|
2338
|
+
if (timer) {
|
|
2339
|
+
clearTimeout(timer);
|
|
2340
|
+
this.timers.delete(id);
|
|
2341
|
+
}
|
|
1858
2342
|
this.jobs.delete(id);
|
|
1859
2343
|
this.persist();
|
|
1860
2344
|
}
|
|
@@ -1873,6 +2357,9 @@ var CronScheduler = class {
|
|
|
1873
2357
|
return;
|
|
1874
2358
|
const jobs = await this.store.load();
|
|
1875
2359
|
for (const job of jobs) {
|
|
2360
|
+
if (job.runAt && Date.parse(job.runAt) <= Date.now()) {
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
1876
2363
|
this.addJob(job);
|
|
1877
2364
|
}
|
|
1878
2365
|
}
|
|
@@ -1880,11 +2367,32 @@ var CronScheduler = class {
|
|
|
1880
2367
|
for (const task of this.tasks.values()) {
|
|
1881
2368
|
task.start();
|
|
1882
2369
|
}
|
|
2370
|
+
for (const job of this.jobs.values()) {
|
|
2371
|
+
if (job.enabled && job.runAt && !job.cron) {
|
|
2372
|
+
this.scheduleOneShot(job);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
1883
2375
|
}
|
|
1884
2376
|
stop() {
|
|
1885
2377
|
for (const task of this.tasks.values()) {
|
|
1886
2378
|
task.stop();
|
|
1887
2379
|
}
|
|
2380
|
+
for (const timer of this.timers.values()) {
|
|
2381
|
+
clearTimeout(timer);
|
|
2382
|
+
}
|
|
2383
|
+
this.timers.clear();
|
|
2384
|
+
}
|
|
2385
|
+
scheduleOneShot(job) {
|
|
2386
|
+
const delayMs = Date.parse(job.runAt) - Date.now();
|
|
2387
|
+
if (delayMs <= 0)
|
|
2388
|
+
return;
|
|
2389
|
+
const timer = setTimeout(() => {
|
|
2390
|
+
this.timers.delete(job.id);
|
|
2391
|
+
void this.executeHandlers(job).then(() => {
|
|
2392
|
+
this.removeJob(job.id);
|
|
2393
|
+
});
|
|
2394
|
+
}, delayMs);
|
|
2395
|
+
this.timers.set(job.id, timer);
|
|
1888
2396
|
}
|
|
1889
2397
|
persist() {
|
|
1890
2398
|
if (!this.store)
|
|
@@ -1939,10 +2447,13 @@ Be concise. Only suggest actions that are clearly needed based on the memory con
|
|
|
1939
2447
|
var Heartbeat = class {
|
|
1940
2448
|
config;
|
|
1941
2449
|
timer;
|
|
2450
|
+
log;
|
|
1942
2451
|
constructor(config) {
|
|
1943
2452
|
this.config = config;
|
|
2453
|
+
this.log = config.logger ?? noopLogger;
|
|
1944
2454
|
}
|
|
1945
2455
|
async tick() {
|
|
2456
|
+
this.log.debug("Heartbeat tick");
|
|
1946
2457
|
const memoryContent = await this.loadMemory();
|
|
1947
2458
|
const messages = [
|
|
1948
2459
|
{ role: "system", content: HEARTBEAT_PROMPT },
|
|
@@ -1957,12 +2468,15 @@ ${memoryContent}`
|
|
|
1957
2468
|
const response = await this.config.llm.chat(messages);
|
|
1958
2469
|
const action = this.parseAction(response.content);
|
|
1959
2470
|
if (action && action.toLowerCase() !== "none") {
|
|
2471
|
+
this.log.debug(`Heartbeat action: ${action}`);
|
|
1960
2472
|
await this.config.onAction(action);
|
|
2473
|
+
} else {
|
|
2474
|
+
this.log.debug("Heartbeat: no action needed");
|
|
1961
2475
|
}
|
|
1962
2476
|
}
|
|
1963
2477
|
start() {
|
|
1964
2478
|
this.timer = setInterval(() => {
|
|
1965
|
-
this.tick().catch((err2) =>
|
|
2479
|
+
this.tick().catch((err2) => this.log.error("Heartbeat error:", err2));
|
|
1966
2480
|
}, this.config.intervalMs);
|
|
1967
2481
|
}
|
|
1968
2482
|
stop() {
|
|
@@ -2809,9 +3323,11 @@ var JOB_PREFIX = "skill:";
|
|
|
2809
3323
|
var SkillSchedulerBridge = class {
|
|
2810
3324
|
scheduler;
|
|
2811
3325
|
manager;
|
|
2812
|
-
|
|
3326
|
+
log;
|
|
3327
|
+
constructor(scheduler, manager, logger) {
|
|
2813
3328
|
this.scheduler = scheduler;
|
|
2814
3329
|
this.manager = manager;
|
|
3330
|
+
this.log = logger ?? noopLogger;
|
|
2815
3331
|
}
|
|
2816
3332
|
/** Register cron jobs for all active cron-triggered skills */
|
|
2817
3333
|
async syncAll() {
|
|
@@ -2830,7 +3346,7 @@ var SkillSchedulerBridge = class {
|
|
|
2830
3346
|
enabled: true
|
|
2831
3347
|
});
|
|
2832
3348
|
} catch (err2) {
|
|
2833
|
-
|
|
3349
|
+
this.log.error(`Failed to register cron for ${skill.id}:`, err2);
|
|
2834
3350
|
}
|
|
2835
3351
|
}
|
|
2836
3352
|
existingJobs.delete(jobId);
|
|
@@ -3222,6 +3738,17 @@ var SkillUpdater = class {
|
|
|
3222
3738
|
};
|
|
3223
3739
|
}
|
|
3224
3740
|
const toVersion = newSkill.meta.version;
|
|
3741
|
+
if (!newSkill.meta.sandbox) {
|
|
3742
|
+
await this.config.manager.save(backup);
|
|
3743
|
+
return {
|
|
3744
|
+
skillId,
|
|
3745
|
+
success: false,
|
|
3746
|
+
rolledBack: true,
|
|
3747
|
+
fromVersion,
|
|
3748
|
+
toVersion,
|
|
3749
|
+
error: "Downloaded skill has sandbox disabled \u2014 rejected for security"
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3225
3752
|
const testResult = await this.config.tester.test(newSkill);
|
|
3226
3753
|
if (testResult.success) {
|
|
3227
3754
|
await this.config.manager.save(newSkill);
|
|
@@ -3295,10 +3822,11 @@ var VersionChecker = class _VersionChecker {
|
|
|
3295
3822
|
};
|
|
3296
3823
|
}
|
|
3297
3824
|
}
|
|
3298
|
-
/** Compare two semver strings. Returns -1 if a < b, 0 if equal, 1 if a > b */
|
|
3825
|
+
/** Compare two semver strings (MAJOR.MINOR.PATCH only, pre-release suffixes stripped). Returns -1 if a < b, 0 if equal, 1 if a > b */
|
|
3299
3826
|
static compareVersions(a, b) {
|
|
3300
|
-
const
|
|
3301
|
-
const
|
|
3827
|
+
const clean = (v) => v.replace(/^v/, "").split("-")[0];
|
|
3828
|
+
const pa = clean(a).split(".").map(Number);
|
|
3829
|
+
const pb = clean(b).split(".").map(Number);
|
|
3302
3830
|
for (let i = 0; i < 3; i++) {
|
|
3303
3831
|
const va = pa[i] ?? 0;
|
|
3304
3832
|
const vb = pb[i] ?? 0;
|
|
@@ -3312,27 +3840,48 @@ var VersionChecker = class _VersionChecker {
|
|
|
3312
3840
|
};
|
|
3313
3841
|
|
|
3314
3842
|
// ../core/dist/main.js
|
|
3315
|
-
var
|
|
3843
|
+
var BASE_SYSTEM_PROMPT = `You are Augure, a personal AI assistant. You are proactive, helpful, and concise.
|
|
3316
3844
|
You speak the same language as the user. You have access to tools and persistent memory.
|
|
3317
|
-
Always be direct and actionable
|
|
3318
|
-
|
|
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) {
|
|
3319
3865
|
const override = usage !== "default" ? config[usage] : void 0;
|
|
3320
3866
|
return new OpenRouterClient({
|
|
3321
3867
|
apiKey: override?.apiKey ?? config.default.apiKey,
|
|
3322
3868
|
model: override?.model ?? config.default.model,
|
|
3323
|
-
maxTokens: override?.maxTokens ?? config.default.maxTokens
|
|
3869
|
+
maxTokens: override?.maxTokens ?? config.default.maxTokens,
|
|
3870
|
+
logger: logger.child("llm")
|
|
3324
3871
|
});
|
|
3325
3872
|
}
|
|
3326
|
-
async function startAgent(configPath) {
|
|
3873
|
+
async function startAgent(configPath, opts) {
|
|
3874
|
+
const log = createLogger({ level: opts?.debug ? "debug" : "info" });
|
|
3327
3875
|
const config = await loadConfig(configPath);
|
|
3328
|
-
|
|
3876
|
+
log.info(`Loaded config: ${config.identity.name}`);
|
|
3877
|
+
log.debug(`Config path: ${configPath}`);
|
|
3329
3878
|
let telegram;
|
|
3330
|
-
const llm = resolveLLMClient(config.llm, "default");
|
|
3331
|
-
const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
|
|
3332
|
-
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);
|
|
3333
3882
|
const memoryPath = resolve(configPath, "..", config.memory.path);
|
|
3334
3883
|
const memory = new FileMemoryStore(memoryPath);
|
|
3335
|
-
|
|
3884
|
+
log.info(`Memory store: ${memoryPath}`);
|
|
3336
3885
|
const retriever = new MemoryRetriever(memory, {
|
|
3337
3886
|
maxTokens: config.memory.maxRetrievalTokens
|
|
3338
3887
|
});
|
|
@@ -3341,31 +3890,37 @@ async function startAgent(configPath) {
|
|
|
3341
3890
|
tools.register(memoryReadTool);
|
|
3342
3891
|
tools.register(memoryWriteTool);
|
|
3343
3892
|
tools.register(scheduleTool);
|
|
3893
|
+
tools.register(datetimeTool);
|
|
3344
3894
|
tools.register(webSearchTool);
|
|
3345
3895
|
tools.register(httpTool);
|
|
3896
|
+
tools.register(emailTool);
|
|
3346
3897
|
tools.register(sandboxExecTool);
|
|
3347
3898
|
tools.register(opencodeTool);
|
|
3348
3899
|
const jobStorePath = resolve(configPath, "..", "jobs.json");
|
|
3349
3900
|
const jobStore = new JobStore(jobStorePath);
|
|
3350
3901
|
const scheduler = new CronScheduler(jobStore);
|
|
3351
3902
|
await scheduler.loadPersistedJobs();
|
|
3352
|
-
|
|
3903
|
+
log.info(`Loaded ${scheduler.listJobs().length} persisted jobs`);
|
|
3353
3904
|
for (const job of config.scheduler.jobs) {
|
|
3354
3905
|
if (!scheduler.listJobs().some((j) => j.id === job.id)) {
|
|
3355
3906
|
scheduler.addJob({ ...job, enabled: true });
|
|
3356
3907
|
}
|
|
3357
3908
|
}
|
|
3358
3909
|
const docker = new Dockerode();
|
|
3910
|
+
const sandboxImage = config.sandbox.image ?? "augure-sandbox:latest";
|
|
3911
|
+
const sandboxLog = log.child("sandbox");
|
|
3912
|
+
await ensureImage(docker, sandboxImage, sandboxLog);
|
|
3359
3913
|
const pool = new DockerContainerPool(docker, {
|
|
3360
|
-
image:
|
|
3361
|
-
maxTotal: config.security.maxConcurrentSandboxes
|
|
3914
|
+
image: sandboxImage,
|
|
3915
|
+
maxTotal: config.security.maxConcurrentSandboxes,
|
|
3916
|
+
logger: sandboxLog
|
|
3362
3917
|
});
|
|
3363
|
-
|
|
3918
|
+
log.info(`Container pool: max=${config.security.maxConcurrentSandboxes}`);
|
|
3364
3919
|
let skillManagerRef;
|
|
3365
3920
|
let skillUpdater;
|
|
3366
3921
|
if (config.skills) {
|
|
3367
3922
|
const skillsPath = resolve(configPath, "..", config.skills.path);
|
|
3368
|
-
const codingLLM = resolveLLMClient(config.llm, "coding");
|
|
3923
|
+
const codingLLM = resolveLLMClient(config.llm, "coding", log);
|
|
3369
3924
|
const skillManager = new SkillManager(skillsPath);
|
|
3370
3925
|
const skillGenerator = new SkillGenerator(codingLLM);
|
|
3371
3926
|
const skillRunner = new SkillRunner({
|
|
@@ -3409,63 +3964,71 @@ async function startAgent(configPath) {
|
|
|
3409
3964
|
const updated = updateResults.filter((r) => r.success);
|
|
3410
3965
|
const failed = updateResults.filter((r) => !r.success);
|
|
3411
3966
|
if (updated.length > 0) {
|
|
3412
|
-
|
|
3967
|
+
log.info(`Skills updated: ${updated.map((r) => `${r.skillId} (v${r.fromVersion}\u2192v${r.toVersion})`).join(", ")}`);
|
|
3413
3968
|
}
|
|
3414
3969
|
if (failed.length > 0) {
|
|
3415
|
-
|
|
3970
|
+
log.warn(`Skill updates failed: ${failed.map((r) => `${r.skillId}: ${r.error}`).join(", ")}`);
|
|
3416
3971
|
}
|
|
3417
3972
|
} catch (err2) {
|
|
3418
|
-
|
|
3973
|
+
log.error("Skill update check failed:", err2);
|
|
3419
3974
|
}
|
|
3420
3975
|
}
|
|
3421
3976
|
skillManagerRef = skillManager;
|
|
3422
|
-
|
|
3977
|
+
log.info(`Skills initialized: ${skillsPath}`);
|
|
3423
3978
|
}
|
|
3424
3979
|
tools.setContext({ config, memory, scheduler, pool });
|
|
3425
3980
|
const auditConfig = config.audit ?? { path: "./logs", enabled: true };
|
|
3426
3981
|
const auditPath = resolve(configPath, "..", auditConfig.path);
|
|
3427
|
-
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath) : new NullAuditLogger();
|
|
3428
|
-
|
|
3982
|
+
const audit = auditConfig.enabled ? new FileAuditLogger(auditPath, log.child("audit")) : new NullAuditLogger();
|
|
3983
|
+
log.info(`Audit: ${auditConfig.enabled ? auditPath : "disabled"}`);
|
|
3429
3984
|
let personaResolver;
|
|
3430
3985
|
if (config.persona) {
|
|
3431
3986
|
const personaPath = resolve(configPath, "..", config.persona.path);
|
|
3432
3987
|
personaResolver = new PersonaResolver(personaPath);
|
|
3433
3988
|
await personaResolver.loadAll();
|
|
3434
|
-
|
|
3989
|
+
log.info(`Personas: ${personaPath}`);
|
|
3435
3990
|
}
|
|
3436
|
-
|
|
3991
|
+
let cliVersion;
|
|
3992
|
+
try {
|
|
3437
3993
|
const require3 = createRequire(import.meta.url);
|
|
3438
|
-
const
|
|
3994
|
+
const pkg = require3("augure/package.json");
|
|
3995
|
+
cliVersion = pkg.version;
|
|
3996
|
+
} catch {
|
|
3997
|
+
}
|
|
3998
|
+
if (cliVersion && config.updates?.cli?.enabled !== false) {
|
|
3439
3999
|
const versionChecker = new VersionChecker({
|
|
3440
|
-
currentVersion:
|
|
3441
|
-
packageName: "augure"
|
|
3442
|
-
githubRepo: "FaureAlexis/augure"
|
|
4000
|
+
currentVersion: cliVersion,
|
|
4001
|
+
packageName: "augure"
|
|
3443
4002
|
});
|
|
3444
4003
|
const versionResult = await versionChecker.check();
|
|
3445
4004
|
if (versionResult.updateAvailable) {
|
|
3446
|
-
|
|
4005
|
+
log.warn(`Update available: v${versionResult.latestVersion} (current: v${versionResult.currentVersion}). Run: npm update -g augure`);
|
|
3447
4006
|
}
|
|
3448
4007
|
}
|
|
3449
4008
|
const guard = new ContextGuard({
|
|
3450
4009
|
maxContextTokens: 2e5,
|
|
3451
4010
|
reservedForOutput: config.llm.default.maxTokens ?? 8192
|
|
3452
4011
|
});
|
|
4012
|
+
const systemPrompt = config.skills ? BASE_SYSTEM_PROMPT + SKILLS_PROMPT : BASE_SYSTEM_PROMPT;
|
|
3453
4013
|
const agent = new Agent({
|
|
3454
4014
|
llm,
|
|
3455
4015
|
tools,
|
|
3456
|
-
systemPrompt
|
|
4016
|
+
systemPrompt,
|
|
3457
4017
|
memoryContent: "",
|
|
3458
4018
|
retriever,
|
|
3459
4019
|
ingester,
|
|
3460
4020
|
audit,
|
|
3461
4021
|
guard,
|
|
3462
|
-
modelName: config.llm.default.model
|
|
4022
|
+
modelName: config.llm.default.model,
|
|
4023
|
+
logger: log.child("agent")
|
|
3463
4024
|
});
|
|
3464
4025
|
if (config.channels.telegram?.enabled) {
|
|
4026
|
+
const telegramLog = log.child("telegram");
|
|
3465
4027
|
telegram = new TelegramChannel({
|
|
3466
4028
|
botToken: config.channels.telegram.botToken,
|
|
3467
4029
|
allowedUsers: config.channels.telegram.allowedUsers,
|
|
3468
|
-
rejectMessage: config.channels.telegram.rejectMessage
|
|
4030
|
+
rejectMessage: config.channels.telegram.rejectMessage,
|
|
4031
|
+
logger: telegramLog
|
|
3469
4032
|
});
|
|
3470
4033
|
const tg = telegram;
|
|
3471
4034
|
const commandCtx = {
|
|
@@ -3475,7 +4038,7 @@ async function startAgent(configPath) {
|
|
|
3475
4038
|
skillManager: skillManagerRef
|
|
3476
4039
|
};
|
|
3477
4040
|
tg.onMessage(async (msg) => {
|
|
3478
|
-
|
|
4041
|
+
log.info(`Message from ${msg.userId}: ${msg.text}`);
|
|
3479
4042
|
try {
|
|
3480
4043
|
const cmdResult = await handleCommand(msg.text, commandCtx);
|
|
3481
4044
|
if (cmdResult.handled) {
|
|
@@ -3498,7 +4061,7 @@ async function startAgent(configPath) {
|
|
|
3498
4061
|
replyTo: msg.id
|
|
3499
4062
|
});
|
|
3500
4063
|
} catch (err2) {
|
|
3501
|
-
|
|
4064
|
+
log.error("Error handling message:", err2);
|
|
3502
4065
|
await tg.send({
|
|
3503
4066
|
channelType: "telegram",
|
|
3504
4067
|
userId: msg.userId,
|
|
@@ -3507,15 +4070,16 @@ async function startAgent(configPath) {
|
|
|
3507
4070
|
}
|
|
3508
4071
|
});
|
|
3509
4072
|
await tg.start();
|
|
3510
|
-
|
|
4073
|
+
log.info("Telegram bot started");
|
|
3511
4074
|
}
|
|
3512
4075
|
const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
|
|
3513
4076
|
const heartbeat = new Heartbeat({
|
|
3514
4077
|
llm: monitoringLLM,
|
|
3515
4078
|
memory,
|
|
3516
4079
|
intervalMs: heartbeatIntervalMs,
|
|
4080
|
+
logger: log.child("heartbeat"),
|
|
3517
4081
|
onAction: async (action) => {
|
|
3518
|
-
|
|
4082
|
+
log.info(`Heartbeat action: ${action}`);
|
|
3519
4083
|
const response = await agent.handleMessage({
|
|
3520
4084
|
id: `heartbeat-${Date.now()}`,
|
|
3521
4085
|
channelType: "system",
|
|
@@ -3523,39 +4087,59 @@ async function startAgent(configPath) {
|
|
|
3523
4087
|
text: `[Heartbeat] ${action}`,
|
|
3524
4088
|
timestamp: /* @__PURE__ */ new Date()
|
|
3525
4089
|
});
|
|
3526
|
-
|
|
4090
|
+
log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
|
|
3527
4091
|
}
|
|
3528
4092
|
});
|
|
4093
|
+
scheduler.onJobTrigger(async (job) => {
|
|
4094
|
+
log.info(`Job triggered: ${job.id}`);
|
|
4095
|
+
const response = await agent.handleMessage({
|
|
4096
|
+
id: `job-${job.id}-${Date.now()}`,
|
|
4097
|
+
channelType: "system",
|
|
4098
|
+
userId: "system",
|
|
4099
|
+
text: job.prompt,
|
|
4100
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
4101
|
+
});
|
|
4102
|
+
if (telegram && config.channels.telegram?.enabled) {
|
|
4103
|
+
const userId = config.channels.telegram.allowedUsers[0];
|
|
4104
|
+
if (userId !== void 0) {
|
|
4105
|
+
await telegram.send({
|
|
4106
|
+
channelType: "telegram",
|
|
4107
|
+
userId: String(userId),
|
|
4108
|
+
text: response
|
|
4109
|
+
});
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
log.debug(`Job ${job.id} completed`);
|
|
4113
|
+
});
|
|
3529
4114
|
scheduler.start();
|
|
3530
4115
|
heartbeat.start();
|
|
3531
|
-
|
|
4116
|
+
log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
|
|
4117
|
+
const updateTimers = [];
|
|
3532
4118
|
if (skillUpdater && config.updates?.skills?.checkInterval) {
|
|
4119
|
+
const su = skillUpdater;
|
|
3533
4120
|
const skillCheckMs = parseInterval(config.updates.skills.checkInterval);
|
|
3534
|
-
setInterval(async () => {
|
|
4121
|
+
updateTimers.push(setInterval(async () => {
|
|
3535
4122
|
try {
|
|
3536
|
-
const results = await
|
|
4123
|
+
const results = await su.checkAndApply();
|
|
3537
4124
|
for (const r of results) {
|
|
3538
4125
|
if (r.success) {
|
|
3539
|
-
|
|
4126
|
+
log.info(`Skill auto-updated: ${r.skillId} v${r.fromVersion}\u2192v${r.toVersion}`);
|
|
3540
4127
|
} else if (r.rolledBack) {
|
|
3541
|
-
|
|
4128
|
+
log.warn(`Skill update rolled back: ${r.skillId} - ${r.error}`);
|
|
3542
4129
|
}
|
|
3543
4130
|
}
|
|
3544
4131
|
} catch (err2) {
|
|
3545
|
-
|
|
4132
|
+
log.error("Periodic skill update check failed:", err2);
|
|
3546
4133
|
}
|
|
3547
|
-
}, skillCheckMs);
|
|
4134
|
+
}, skillCheckMs));
|
|
3548
4135
|
}
|
|
3549
|
-
if (config.updates?.cli?.enabled !== false && config.channels.telegram?.enabled) {
|
|
4136
|
+
if (cliVersion && config.updates?.cli?.enabled !== false && config.channels.telegram?.enabled) {
|
|
3550
4137
|
const cliCheckMs = parseInterval(config.updates?.cli?.checkInterval ?? "24h");
|
|
3551
|
-
const require3 = createRequire(import.meta.url);
|
|
3552
|
-
const { version: version2 } = require3("augure/package.json");
|
|
3553
4138
|
const versionChecker = new VersionChecker({
|
|
3554
|
-
currentVersion:
|
|
3555
|
-
packageName: "augure"
|
|
3556
|
-
githubRepo: "FaureAlexis/augure"
|
|
4139
|
+
currentVersion: cliVersion,
|
|
4140
|
+
packageName: "augure"
|
|
3557
4141
|
});
|
|
3558
|
-
setInterval(async () => {
|
|
4142
|
+
updateTimers.push(setInterval(async () => {
|
|
3559
4143
|
try {
|
|
3560
4144
|
const result = await versionChecker.check();
|
|
3561
4145
|
if (result.updateAvailable && telegram) {
|
|
@@ -3570,35 +4154,27 @@ Run: \`npm update -g augure\``
|
|
|
3570
4154
|
}
|
|
3571
4155
|
}
|
|
3572
4156
|
} catch (err2) {
|
|
3573
|
-
|
|
4157
|
+
log.error("CLI version check failed:", err2);
|
|
3574
4158
|
}
|
|
3575
|
-
}, cliCheckMs);
|
|
4159
|
+
}, cliCheckMs));
|
|
3576
4160
|
}
|
|
3577
4161
|
const shutdown = async () => {
|
|
3578
|
-
|
|
4162
|
+
log.info("Shutting down...");
|
|
4163
|
+
for (const timer of updateTimers)
|
|
4164
|
+
clearInterval(timer);
|
|
3579
4165
|
heartbeat.stop();
|
|
3580
4166
|
scheduler.stop();
|
|
3581
4167
|
if (telegram)
|
|
3582
4168
|
await telegram.stop();
|
|
3583
4169
|
await pool.destroyAll();
|
|
3584
4170
|
await audit.close();
|
|
3585
|
-
|
|
4171
|
+
log.info("All containers destroyed");
|
|
3586
4172
|
process.exit(0);
|
|
3587
4173
|
};
|
|
3588
4174
|
process.on("SIGINT", shutdown);
|
|
3589
4175
|
process.on("SIGTERM", shutdown);
|
|
3590
4176
|
}
|
|
3591
4177
|
|
|
3592
|
-
// src/colors.ts
|
|
3593
|
-
import { styleText } from "util";
|
|
3594
|
-
var brand = (s) => styleText("yellow", s);
|
|
3595
|
-
var ok = (s) => styleText("green", s);
|
|
3596
|
-
var err = (s) => styleText("red", s);
|
|
3597
|
-
var dim = (s) => styleText("dim", s);
|
|
3598
|
-
var bold = (s) => styleText("bold", s);
|
|
3599
|
-
var cyan = (s) => styleText("cyan", s);
|
|
3600
|
-
var prefix = brand("\u25B2 augure");
|
|
3601
|
-
|
|
3602
4178
|
// src/commands/start.ts
|
|
3603
4179
|
var startCommand = defineCommand({
|
|
3604
4180
|
meta: {
|
|
@@ -3616,21 +4192,28 @@ var startCommand = defineCommand({
|
|
|
3616
4192
|
type: "string",
|
|
3617
4193
|
description: "Path to .env file",
|
|
3618
4194
|
alias: "e"
|
|
4195
|
+
},
|
|
4196
|
+
debug: {
|
|
4197
|
+
type: "boolean",
|
|
4198
|
+
description: "Enable debug logging",
|
|
4199
|
+
alias: "d",
|
|
4200
|
+
default: false
|
|
3619
4201
|
}
|
|
3620
4202
|
},
|
|
3621
4203
|
async run({ args }) {
|
|
3622
4204
|
const configPath = resolve2(args.config);
|
|
4205
|
+
const log = createLogger({ level: args.debug ? "debug" : "info" });
|
|
3623
4206
|
const envPath = args.env ? resolve2(args.env) : join6(dirname4(configPath), ".env");
|
|
3624
4207
|
try {
|
|
3625
4208
|
process.loadEnvFile(envPath);
|
|
3626
|
-
|
|
4209
|
+
log.debug(`Loaded env from ${envPath}`);
|
|
3627
4210
|
} catch {
|
|
3628
4211
|
}
|
|
3629
|
-
|
|
4212
|
+
log.info(`Starting with config: ${configPath}`);
|
|
3630
4213
|
try {
|
|
3631
|
-
await startAgent(configPath);
|
|
4214
|
+
await startAgent(configPath, { debug: args.debug });
|
|
3632
4215
|
} catch (e) {
|
|
3633
|
-
|
|
4216
|
+
log.error("Fatal:", e instanceof Error ? e.message : String(e));
|
|
3634
4217
|
process.exit(1);
|
|
3635
4218
|
}
|
|
3636
4219
|
}
|
|
@@ -3640,6 +4223,18 @@ var startCommand = defineCommand({
|
|
|
3640
4223
|
import { defineCommand as defineCommand2 } from "citty";
|
|
3641
4224
|
import { writeFile as writeFile5, access as access3 } from "fs/promises";
|
|
3642
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
|
|
3643
4238
|
var CONFIG_TEMPLATE = `{
|
|
3644
4239
|
// Identity
|
|
3645
4240
|
identity: {
|