augure 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +343 -34
  2. package/package.json +9 -9
package/dist/bin.js CHANGED
@@ -6,7 +6,7 @@ import { defineCommand as defineCommand4, runMain } from "citty";
6
6
 
7
7
  // src/commands/start.ts
8
8
  import { defineCommand } from "citty";
9
- import { resolve as resolve2 } from "path";
9
+ import { resolve as resolve2, dirname as dirname4, join as join6 } from "path";
10
10
 
11
11
  // ../core/dist/config.js
12
12
  import { readFile } from "fs/promises";
@@ -35,7 +35,8 @@ var AppConfigSchema = z.object({
35
35
  telegram: z.object({
36
36
  enabled: z.boolean(),
37
37
  botToken: z.string(),
38
- allowedUsers: z.array(z.number())
38
+ allowedUsers: z.array(z.number()),
39
+ rejectMessage: z.string().optional()
39
40
  }).optional(),
40
41
  whatsapp: z.object({ enabled: z.boolean() }).optional(),
41
42
  web: z.object({ enabled: z.boolean(), port: z.number() }).optional()
@@ -120,6 +121,17 @@ var AppConfigSchema = z.object({
120
121
  }).optional(),
121
122
  persona: z.object({
122
123
  path: z.string().min(1).default("./config/personas")
124
+ }).optional(),
125
+ updates: z.object({
126
+ skills: z.object({
127
+ enabled: z.boolean().default(true),
128
+ checkInterval: z.string().min(1).default("6h")
129
+ }).optional(),
130
+ cli: z.object({
131
+ enabled: z.boolean().default(true),
132
+ checkInterval: z.string().min(1).default("24h"),
133
+ notifyChannel: z.string().min(1).default("telegram")
134
+ }).optional()
123
135
  }).optional()
124
136
  });
125
137
  function interpolateEnvVars(raw) {
@@ -150,7 +162,7 @@ var OpenRouterClient = class {
150
162
  this.maxTokens = config.maxTokens;
151
163
  this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
152
164
  }
153
- async chat(messages) {
165
+ async chat(messages, tools) {
154
166
  const response = await fetch(`${this.baseUrl}/chat/completions`, {
155
167
  method: "POST",
156
168
  headers: {
@@ -163,8 +175,19 @@ var OpenRouterClient = class {
163
175
  messages: messages.map((m) => ({
164
176
  role: m.role,
165
177
  content: m.content,
166
- ...m.toolCallId ? { tool_call_id: m.toolCallId } : {}
167
- }))
178
+ ...m.toolCallId ? { tool_call_id: m.toolCallId } : {},
179
+ ...m.toolCalls?.length ? {
180
+ tool_calls: m.toolCalls.map((tc) => ({
181
+ id: tc.id,
182
+ type: "function",
183
+ function: {
184
+ name: tc.name,
185
+ arguments: JSON.stringify(tc.arguments)
186
+ }
187
+ }))
188
+ } : {}
189
+ })),
190
+ ...tools?.length ? { tools } : {}
168
191
  })
169
192
  });
170
193
  if (!response.ok) {
@@ -190,7 +213,7 @@ var OpenRouterClient = class {
190
213
 
191
214
  // ../core/dist/context.js
192
215
  function assembleContext(input) {
193
- const { systemPrompt, memoryContent, toolSchemas, conversationHistory, persona } = input;
216
+ const { systemPrompt, memoryContent, conversationHistory, persona } = input;
194
217
  let system = systemPrompt;
195
218
  if (persona) {
196
219
  system += `
@@ -203,13 +226,6 @@ ${persona}`;
203
226
 
204
227
  ## Memory
205
228
  ${memoryContent}`;
206
- }
207
- if (toolSchemas.length > 0) {
208
- const toolList = toolSchemas.map((s) => `- **${s.function.name}**: ${s.function.description}`).join("\n");
209
- system += `
210
-
211
- ## Available Tools
212
- ${toolList}`;
213
229
  }
214
230
  const messages = [{ role: "system", content: system }];
215
231
  messages.push(...conversationHistory);
@@ -258,7 +274,7 @@ var NullAuditLogger = class {
258
274
  // ../core/dist/agent.js
259
275
  var Agent = class {
260
276
  config;
261
- conversationHistory = [];
277
+ conversations = /* @__PURE__ */ new Map();
262
278
  state = "running";
263
279
  constructor(config) {
264
280
  this.config = config;
@@ -277,30 +293,33 @@ var Agent = class {
277
293
  return "Agent is in emergency stop mode. Send /resume to reactivate.";
278
294
  }
279
295
  const start = Date.now();
280
- this.conversationHistory.push({
296
+ const userId = incoming.userId;
297
+ let history = this.conversations.get(userId) ?? [];
298
+ history.push({
281
299
  role: "user",
282
300
  content: incoming.text
283
301
  });
284
302
  if (this.config.guard) {
285
- this.conversationHistory = this.config.guard.compact(this.conversationHistory);
303
+ history = this.config.guard.compact(history);
286
304
  }
305
+ this.conversations.set(userId, history);
287
306
  let memoryContent = this.config.memoryContent;
288
307
  if (this.config.retriever) {
289
308
  memoryContent = await this.config.retriever.retrieve();
290
309
  }
291
310
  const maxLoops = this.config.maxToolLoops ?? 10;
292
311
  let loopCount = 0;
312
+ const toolSchemas = this.config.tools.toFunctionSchemas();
293
313
  while (loopCount < maxLoops) {
294
314
  const messages = assembleContext({
295
315
  systemPrompt: this.config.systemPrompt,
296
316
  memoryContent,
297
- toolSchemas: this.config.tools.toFunctionSchemas(),
298
- conversationHistory: this.conversationHistory,
317
+ conversationHistory: history,
299
318
  persona: this.config.persona
300
319
  });
301
- const response = await this.config.llm.chat(messages);
320
+ const response = await this.config.llm.chat(messages, toolSchemas);
302
321
  if (response.toolCalls.length === 0) {
303
- this.conversationHistory.push({
322
+ history.push({
304
323
  role: "assistant",
305
324
  content: response.content
306
325
  });
@@ -321,18 +340,19 @@ var Agent = class {
321
340
  });
322
341
  }
323
342
  if (this.config.ingester) {
324
- this.config.ingester.ingest(this.conversationHistory).catch((err2) => console.error("[augure] Ingestion error:", err2));
343
+ this.config.ingester.ingest(history).catch((err2) => console.error("[augure] Ingestion error:", err2));
325
344
  }
326
345
  return response.content;
327
346
  }
328
- this.conversationHistory.push({
347
+ history.push({
329
348
  role: "assistant",
330
- content: response.content || ""
349
+ content: response.content || "",
350
+ toolCalls: response.toolCalls
331
351
  });
332
352
  for (const toolCall of response.toolCalls) {
333
353
  const toolStart = Date.now();
334
354
  const result = await this.config.tools.execute(toolCall.name, toolCall.arguments);
335
- this.conversationHistory.push({
355
+ history.push({
336
356
  role: "tool",
337
357
  content: result.output,
338
358
  toolCallId: toolCall.id
@@ -354,11 +374,22 @@ var Agent = class {
354
374
  }
355
375
  return "Max tool call loops reached. Please try again.";
356
376
  }
357
- getConversationHistory() {
358
- return [...this.conversationHistory];
377
+ getConversationHistory(userId) {
378
+ if (userId) {
379
+ return [...this.conversations.get(userId) ?? []];
380
+ }
381
+ const all = [];
382
+ for (const msgs of this.conversations.values()) {
383
+ all.push(...msgs);
384
+ }
385
+ return all;
359
386
  }
360
- clearHistory() {
361
- this.conversationHistory = [];
387
+ clearHistory(userId) {
388
+ if (userId) {
389
+ this.conversations.delete(userId);
390
+ } else {
391
+ this.conversations.clear();
392
+ }
362
393
  }
363
394
  };
364
395
 
@@ -528,19 +559,259 @@ var ContextGuard = class {
528
559
  }
529
560
  };
530
561
 
531
- // ../channels/dist/telegram.js
562
+ // ../channels/dist/telegram/telegram.js
532
563
  import { Bot } from "grammy";
564
+
565
+ // ../channels/dist/pipeline.js
566
+ function createOutgoingPipeline(middlewares, send) {
567
+ return async (message) => {
568
+ let index = 0;
569
+ const next = async () => {
570
+ if (index < middlewares.length) {
571
+ const mw = middlewares[index++];
572
+ await mw(message, next);
573
+ } else {
574
+ await send(message);
575
+ }
576
+ };
577
+ await next();
578
+ };
579
+ }
580
+
581
+ // ../channels/dist/middleware/escape-markdown.js
582
+ var SPECIAL_CHARS = /* @__PURE__ */ new Set([".", "!", ">", "#", "+", "-", "=", "|", "{", "}", "~"]);
583
+ function escapeMarkdownV2(text) {
584
+ if (!text)
585
+ return "";
586
+ const parts = [];
587
+ let i = 0;
588
+ while (i < text.length) {
589
+ if (text.startsWith("```", i)) {
590
+ const endIdx = text.indexOf("```", i + 3);
591
+ if (endIdx !== -1) {
592
+ parts.push(text.slice(i, endIdx + 3));
593
+ i = endIdx + 3;
594
+ continue;
595
+ }
596
+ }
597
+ if (text[i] === "`") {
598
+ const endIdx = text.indexOf("`", i + 1);
599
+ if (endIdx !== -1) {
600
+ parts.push(text.slice(i, endIdx + 1));
601
+ i = endIdx + 1;
602
+ continue;
603
+ }
604
+ }
605
+ if (text[i] === "*" || text[i] === "_") {
606
+ parts.push(text[i]);
607
+ i++;
608
+ continue;
609
+ }
610
+ if (text[i] === "[" || text[i] === "(") {
611
+ parts.push(text[i]);
612
+ i++;
613
+ continue;
614
+ }
615
+ if (text[i] === "]") {
616
+ parts.push(text[i]);
617
+ i++;
618
+ continue;
619
+ }
620
+ const char = text[i];
621
+ if (SPECIAL_CHARS.has(char)) {
622
+ parts.push(`\\${char}`);
623
+ } else {
624
+ parts.push(char);
625
+ }
626
+ i++;
627
+ }
628
+ return parts.join("");
629
+ }
630
+ function createEscapeMarkdownMiddleware() {
631
+ return async (message, next) => {
632
+ message.text = escapeMarkdownV2(message.text);
633
+ await next();
634
+ };
635
+ }
636
+
637
+ // ../channels/dist/middleware/split-message.js
638
+ var TELEGRAM_MAX = 4096;
639
+ function splitText(text, maxLength) {
640
+ if (text.length <= maxLength)
641
+ return [text];
642
+ const chunks = [];
643
+ let remaining = text;
644
+ let openCodeBlock = null;
645
+ while (remaining.length > 0) {
646
+ const prefix2 = openCodeBlock ? `${openCodeBlock}
647
+ ` : "";
648
+ const effectiveMax = maxLength - prefix2.length;
649
+ if (remaining.length <= effectiveMax) {
650
+ chunks.push(prefix2 + remaining);
651
+ break;
652
+ }
653
+ let splitAt = -1;
654
+ const searchArea = remaining.slice(0, effectiveMax);
655
+ const paraIdx = searchArea.lastIndexOf("\n\n");
656
+ if (paraIdx > effectiveMax * 0.3) {
657
+ splitAt = paraIdx;
658
+ }
659
+ if (splitAt === -1) {
660
+ const newlineIdx = searchArea.lastIndexOf("\n");
661
+ if (newlineIdx > effectiveMax * 0.3) {
662
+ splitAt = newlineIdx;
663
+ }
664
+ }
665
+ if (splitAt === -1) {
666
+ splitAt = effectiveMax;
667
+ }
668
+ let chunk = remaining.slice(0, splitAt);
669
+ remaining = remaining.slice(splitAt).replace(/^\n+/, "");
670
+ const fenceMatches = chunk.match(/```/g);
671
+ const fenceCount = fenceMatches ? fenceMatches.length : 0;
672
+ if (openCodeBlock) {
673
+ chunk = prefix2 + chunk;
674
+ if (fenceCount % 2 === 1) {
675
+ openCodeBlock = null;
676
+ } else {
677
+ chunk += "\n```";
678
+ }
679
+ } else {
680
+ if (fenceCount % 2 === 1) {
681
+ const lastFenceIdx = chunk.lastIndexOf("```");
682
+ const afterFence = chunk.slice(lastFenceIdx + 3);
683
+ const langMatch = afterFence.match(/^(\w*)/);
684
+ openCodeBlock = "```" + (langMatch?.[1] ?? "");
685
+ chunk += "\n```";
686
+ }
687
+ }
688
+ chunks.push(chunk);
689
+ }
690
+ return chunks.length === 0 ? [""] : chunks;
691
+ }
692
+ function createSplitMessageMiddleware(sendFn, maxLength = TELEGRAM_MAX) {
693
+ return async (message, next) => {
694
+ const chunks = splitText(message.text, maxLength);
695
+ if (chunks.length <= 1) {
696
+ await next();
697
+ return;
698
+ }
699
+ for (let i = 0; i < chunks.length; i++) {
700
+ await sendFn({
701
+ ...message,
702
+ text: chunks[i],
703
+ replyTo: i === 0 ? message.replyTo : void 0
704
+ });
705
+ if (i < chunks.length - 1) {
706
+ await new Promise((r) => setTimeout(r, 50));
707
+ }
708
+ }
709
+ };
710
+ }
711
+
712
+ // ../channels/dist/middleware/error-handler.js
713
+ function isRetryable(error) {
714
+ if (error instanceof Error) {
715
+ const status = error.status;
716
+ if (status === 429 || status !== void 0 && status >= 500)
717
+ return true;
718
+ if (status !== void 0 && status >= 400 && status < 500)
719
+ return false;
720
+ return true;
721
+ }
722
+ return false;
723
+ }
724
+ async function withRetry(fn, options) {
725
+ let lastError;
726
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
727
+ try {
728
+ return await fn();
729
+ } catch (err2) {
730
+ lastError = err2;
731
+ if (!isRetryable(err2) || attempt === options.maxRetries) {
732
+ throw err2;
733
+ }
734
+ const delay = options.baseDelayMs * Math.pow(2, attempt);
735
+ await new Promise((r) => setTimeout(r, delay));
736
+ }
737
+ }
738
+ throw lastError;
739
+ }
740
+
741
+ // ../channels/dist/telegram/media.js
742
+ function registerMediaHandlers(bot, isAllowed, handlers, onRejected) {
743
+ bot.on("message:photo", async (ctx) => {
744
+ const userId = ctx.from.id;
745
+ if (!isAllowed(userId)) {
746
+ onRejected?.(userId, new Date(ctx.message.date * 1e3));
747
+ return;
748
+ }
749
+ const photos = ctx.message.photo;
750
+ const largest = photos[photos.length - 1];
751
+ const attachment = {
752
+ type: "photo",
753
+ fileId: largest.file_id,
754
+ caption: ctx.message.caption ?? void 0
755
+ };
756
+ const incoming = {
757
+ id: String(ctx.message.message_id),
758
+ channelType: "telegram",
759
+ userId: String(userId),
760
+ text: ctx.message.caption ?? "[Photo]",
761
+ timestamp: new Date(ctx.message.date * 1e3),
762
+ replyTo: ctx.message.reply_to_message ? String(ctx.message.reply_to_message.message_id) : void 0,
763
+ attachments: [attachment]
764
+ };
765
+ for (const handler of handlers) {
766
+ await handler(incoming);
767
+ }
768
+ });
769
+ bot.on("message:document", async (ctx) => {
770
+ const userId = ctx.from.id;
771
+ if (!isAllowed(userId)) {
772
+ onRejected?.(userId, new Date(ctx.message.date * 1e3));
773
+ return;
774
+ }
775
+ const doc = ctx.message.document;
776
+ const attachment = {
777
+ type: "document",
778
+ fileId: doc.file_id,
779
+ fileName: doc.file_name ?? void 0,
780
+ mimeType: doc.mime_type ?? void 0,
781
+ caption: ctx.message.caption ?? void 0
782
+ };
783
+ const incoming = {
784
+ id: String(ctx.message.message_id),
785
+ channelType: "telegram",
786
+ userId: String(userId),
787
+ text: ctx.message.caption ?? `[Document: ${doc.file_name ?? "unknown"}]`,
788
+ timestamp: new Date(ctx.message.date * 1e3),
789
+ replyTo: ctx.message.reply_to_message ? String(ctx.message.reply_to_message.message_id) : void 0,
790
+ attachments: [attachment]
791
+ };
792
+ for (const handler of handlers) {
793
+ await handler(incoming);
794
+ }
795
+ });
796
+ }
797
+
798
+ // ../channels/dist/telegram/telegram.js
533
799
  var TelegramChannel = class {
534
800
  type = "telegram";
535
801
  bot;
536
802
  allowedUsers;
537
803
  handlers = [];
804
+ sendPipeline;
538
805
  constructor(config) {
539
806
  this.bot = new Bot(config.botToken);
540
807
  this.allowedUsers = new Set(config.allowedUsers);
808
+ this.bot.catch((err2) => {
809
+ console.error("[augure:telegram] Bot error:", err2.message ?? err2);
810
+ });
541
811
  this.bot.on("message:text", async (ctx) => {
542
812
  const userId = ctx.from.id;
543
813
  if (!this.isUserAllowed(userId)) {
814
+ this.handleRejected(userId, ctx.message.date, config.rejectMessage);
544
815
  return;
545
816
  }
546
817
  const incoming = {
@@ -555,10 +826,35 @@ var TelegramChannel = class {
555
826
  await handler(incoming);
556
827
  }
557
828
  });
829
+ registerMediaHandlers(this.bot, (id) => this.isUserAllowed(id), this.handlers, (userId, ts) => this.handleRejected(userId, Math.floor(ts.getTime() / 1e3), config.rejectMessage));
830
+ const rawSend = async (msg) => {
831
+ await withRetry(() => this.bot.api.sendMessage(Number(msg.userId), msg.text, {
832
+ parse_mode: "MarkdownV2",
833
+ ...msg.replyTo ? { reply_parameters: { message_id: Number(msg.replyTo) } } : {}
834
+ }), { maxRetries: 3, baseDelayMs: 500 }).catch(async () => {
835
+ await this.bot.api.sendMessage(Number(msg.userId), msg.text, {
836
+ ...msg.replyTo ? { reply_parameters: { message_id: Number(msg.replyTo) } } : {}
837
+ }).catch((fallbackErr) => {
838
+ console.error("[augure:telegram] Fallback send also failed:", fallbackErr);
839
+ throw fallbackErr;
840
+ });
841
+ });
842
+ };
843
+ this.sendPipeline = createOutgoingPipeline([
844
+ createEscapeMarkdownMiddleware(),
845
+ createSplitMessageMiddleware(rawSend)
846
+ ], rawSend);
558
847
  }
559
848
  isUserAllowed(userId) {
560
849
  return this.allowedUsers.has(userId);
561
850
  }
851
+ handleRejected(userId, unixTimestamp, rejectMessage) {
852
+ console.warn(`[augure:telegram] Rejected message from unauthorized user ${userId} at ${new Date(unixTimestamp * 1e3).toISOString()}`);
853
+ if (rejectMessage) {
854
+ this.bot.api.sendMessage(userId, rejectMessage).catch(() => {
855
+ });
856
+ }
857
+ }
562
858
  onMessage(handler) {
563
859
  this.handlers.push(handler);
564
860
  }
@@ -569,10 +865,7 @@ var TelegramChannel = class {
569
865
  await this.bot.stop();
570
866
  }
571
867
  async send(message) {
572
- await this.bot.api.sendMessage(Number(message.userId), message.text, {
573
- parse_mode: "Markdown",
574
- ...message.replyTo ? { reply_parameters: { message_id: Number(message.replyTo) } } : {}
575
- });
868
+ await this.sendPipeline(message);
576
869
  }
577
870
  };
578
871
 
@@ -2886,6 +3179,7 @@ function resolveLLMClient(config, usage) {
2886
3179
  async function startAgent(configPath) {
2887
3180
  const config = await loadConfig(configPath);
2888
3181
  console.log(`[augure] Loaded config: ${config.identity.name}`);
3182
+ let telegramChannel;
2889
3183
  const llm = resolveLLMClient(config.llm, "default");
2890
3184
  const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
2891
3185
  const monitoringLLM = resolveLLMClient(config.llm, "monitoring");
@@ -2989,7 +3283,8 @@ async function startAgent(configPath) {
2989
3283
  if (config.channels.telegram?.enabled) {
2990
3284
  const telegram = new TelegramChannel({
2991
3285
  botToken: config.channels.telegram.botToken,
2992
- allowedUsers: config.channels.telegram.allowedUsers
3286
+ allowedUsers: config.channels.telegram.allowedUsers,
3287
+ rejectMessage: config.channels.telegram.rejectMessage
2993
3288
  });
2994
3289
  const commandCtx = {
2995
3290
  scheduler,
@@ -3030,6 +3325,7 @@ async function startAgent(configPath) {
3030
3325
  }
3031
3326
  });
3032
3327
  await telegram.start();
3328
+ telegramChannel = telegram;
3033
3329
  console.log("[augure] Telegram bot started. Waiting for messages...");
3034
3330
  }
3035
3331
  const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
@@ -3056,6 +3352,8 @@ async function startAgent(configPath) {
3056
3352
  console.log("\n[augure] Shutting down...");
3057
3353
  heartbeat.stop();
3058
3354
  scheduler.stop();
3355
+ if (telegramChannel)
3356
+ await telegramChannel.stop();
3059
3357
  await pool.destroyAll();
3060
3358
  await audit.close();
3061
3359
  console.log("[augure] All containers destroyed");
@@ -3087,10 +3385,21 @@ var startCommand = defineCommand({
3087
3385
  description: "Path to config file",
3088
3386
  alias: "c",
3089
3387
  default: "./augure.json5"
3388
+ },
3389
+ env: {
3390
+ type: "string",
3391
+ description: "Path to .env file",
3392
+ alias: "e"
3090
3393
  }
3091
3394
  },
3092
3395
  async run({ args }) {
3093
3396
  const configPath = resolve2(args.config);
3397
+ const envPath = args.env ? resolve2(args.env) : join6(dirname4(configPath), ".env");
3398
+ try {
3399
+ process.loadEnvFile(envPath);
3400
+ console.log(`${prefix} Loaded env from ${dim(envPath)}`);
3401
+ } catch {
3402
+ }
3094
3403
  console.log(`${prefix} Starting with config: ${dim(configPath)}`);
3095
3404
  try {
3096
3405
  await startAgent(configPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "augure",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Augure — your proactive AI agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,14 +27,14 @@
27
27
  "@types/dockerode": "^4.0.1",
28
28
  "@types/node-cron": "^3.0.11",
29
29
  "tsup": "^8.5.1",
30
- "@augure/channels": "0.0.1",
31
- "@augure/memory": "0.0.1",
32
- "@augure/sandbox": "0.0.1",
33
- "@augure/core": "0.0.1",
34
- "@augure/scheduler": "0.0.1",
35
- "@augure/skills": "0.0.1",
36
- "@augure/types": "0.0.1",
37
- "@augure/tools": "0.0.1"
30
+ "@augure/core": "0.1.0",
31
+ "@augure/memory": "0.0.2",
32
+ "@augure/sandbox": "0.0.2",
33
+ "@augure/channels": "0.1.0",
34
+ "@augure/scheduler": "0.0.2",
35
+ "@augure/tools": "0.0.2",
36
+ "@augure/types": "0.1.0",
37
+ "@augure/skills": "0.1.0"
38
38
  },
39
39
  "keywords": [
40
40
  "ai",