augure 0.7.1 → 0.8.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 +218 -49
  2. package/package.json +9 -9
package/dist/bin.js CHANGED
@@ -276,6 +276,10 @@ var AppConfigSchema = z.object({
276
276
  runtime: z.enum(["vm", "docker", "auto"]).default("auto"),
277
277
  timeout: z.number().int().positive().default(30),
278
278
  memoryLimit: z.number().int().positive().default(128)
279
+ }).optional(),
280
+ approval: z.object({
281
+ enabled: z.boolean(),
282
+ timeoutMs: z.number().int().positive().default(12e4)
279
283
  }).optional()
280
284
  });
281
285
  function interpolateEnvVars(raw) {
@@ -622,7 +626,7 @@ var VmExecutor = class {
622
626
 
623
627
  // ../code-mode/dist/docker-sandbox.js
624
628
  var DOCKER_HARNESS = `
625
- import { readFile } from "node:fs/promises";
629
+ import { readFile, writeFile, unlink } from "node:fs/promises";
626
630
 
627
631
  const __logs = [];
628
632
  const __originalLog = console.log;
@@ -631,12 +635,28 @@ console.warn = (...args) => __logs.push("[warn] " + args.map(String).join(" "));
631
635
  console.error = (...args) => __logs.push("[error] " + args.map(String).join(" "));
632
636
 
633
637
  let __toolCalls = 0;
638
+ let __reqId = 0;
634
639
 
640
+ const __BRIDGE_TIMEOUT = 120_000;
635
641
  const api = new Proxy({}, {
636
642
  get: (_target, toolName) => {
637
- return async () => {
643
+ return async (args) => {
638
644
  __toolCalls++;
639
- return { success: false, output: "Tool calls not yet supported in Docker executor" };
645
+ const id = String(++__reqId);
646
+ const reqPath = \`/workspace/.bridge-req-\${id}.json\`;
647
+ const respPath = \`/workspace/.bridge-resp-\${id}.json\`;
648
+ await writeFile(reqPath, JSON.stringify({ id, tool: String(toolName), args }));
649
+ const deadline = Date.now() + __BRIDGE_TIMEOUT;
650
+ while (Date.now() < deadline) {
651
+ try {
652
+ const data = await readFile(respPath, "utf-8");
653
+ await unlink(respPath);
654
+ return JSON.parse(data);
655
+ } catch {
656
+ await new Promise(r => setTimeout(r, 50));
657
+ }
658
+ }
659
+ throw new Error(\`Bridge timeout: tool "\${String(toolName)}" did not respond within \${__BRIDGE_TIMEOUT}ms\`);
640
660
  };
641
661
  }
642
662
  });
@@ -663,11 +683,37 @@ try {
663
683
  }));
664
684
  }
665
685
  `;
686
+ function sleep(ms) {
687
+ return new Promise((r) => setTimeout(r, ms));
688
+ }
689
+ function parseFiles(stdout) {
690
+ return stdout.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("/workspace/.bridge-req-") && l.endsWith(".json"));
691
+ }
666
692
  var DockerExecutor = class {
667
693
  config;
668
694
  constructor(config) {
669
695
  this.config = config;
670
696
  }
697
+ async pollBridge(container, abortSignal) {
698
+ while (!abortSignal.aborted) {
699
+ try {
700
+ const ls = await container.exec("ls /workspace/.bridge-req-*.json 2>/dev/null || true");
701
+ const files = parseFiles(ls.stdout);
702
+ for (const reqFile of files) {
703
+ const reqJson = await container.exec(`cat ${reqFile}`);
704
+ const req = JSON.parse(reqJson.stdout);
705
+ const result = await this.config.registry.execute(req.tool, req.args);
706
+ const respFile = reqFile.replace("bridge-req", "bridge-resp");
707
+ const respB64 = Buffer.from(JSON.stringify(result)).toString("base64");
708
+ const tmpFile = `${respFile}.tmp`;
709
+ await container.exec(`sh -c 'echo "${respB64}" | base64 -d > ${tmpFile} && mv ${tmpFile} ${respFile}'`);
710
+ await container.exec(`rm ${reqFile}`);
711
+ }
712
+ } catch {
713
+ }
714
+ await sleep(100);
715
+ }
716
+ }
671
717
  async execute(code) {
672
718
  const start = Date.now();
673
719
  let container;
@@ -694,10 +740,14 @@ var DockerExecutor = class {
694
740
  await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/user-code.js'`);
695
741
  const harnessB64 = Buffer.from(DOCKER_HARNESS).toString("base64");
696
742
  await container.exec(`sh -c 'echo "${harnessB64}" | base64 -d > /workspace/harness.ts'`);
743
+ const abortController = new AbortController();
744
+ const bridgePromise = this.pollBridge(container, abortController.signal);
697
745
  const execResult = await container.exec("npx tsx /workspace/harness.ts", {
698
746
  timeout: this.config.timeout,
699
747
  cwd: "/workspace"
700
748
  });
749
+ abortController.abort();
750
+ await bridgePromise;
701
751
  if (execResult.exitCode === 0 && execResult.stdout.trim()) {
702
752
  try {
703
753
  const lastLine = execResult.stdout.trim().split("\n").pop();
@@ -952,6 +1002,18 @@ var Agent = class {
952
1002
  for (const toolCall of response.toolCalls) {
953
1003
  const toolStart = Date.now();
954
1004
  this.log.debug(`Tool: ${toolCall.name}`);
1005
+ const registeredTool = this.config.tools.get(toolCall.name);
1006
+ if (registeredTool?.riskLevel === "high" && this.config.approvalGate) {
1007
+ const approved = await this.config.approvalGate.request(incoming.userId, toolCall.name, toolCall.arguments);
1008
+ if (!approved) {
1009
+ history.push({
1010
+ role: "tool",
1011
+ content: "Tool call rejected by user.",
1012
+ toolCallId: toolCall.id
1013
+ });
1014
+ continue;
1015
+ }
1016
+ }
955
1017
  let result;
956
1018
  if (codeModeTool && toolCall.name === "execute_code") {
957
1019
  result = await codeModeTool.execute(toolCall.arguments, {});
@@ -1166,6 +1228,60 @@ var ContextGuard = class {
1166
1228
  }
1167
1229
  };
1168
1230
 
1231
+ // ../core/dist/approval.js
1232
+ import { randomUUID } from "crypto";
1233
+ var ApprovalGate = class {
1234
+ pending = /* @__PURE__ */ new Map();
1235
+ channel;
1236
+ timeoutMs;
1237
+ log;
1238
+ constructor(config) {
1239
+ this.channel = config.channel;
1240
+ this.timeoutMs = config.timeoutMs ?? 12e4;
1241
+ this.log = config.logger ?? noopLogger;
1242
+ if (this.channel.onApprovalResponse) {
1243
+ this.channel.onApprovalResponse((response) => {
1244
+ const entry = this.pending.get(response.requestId);
1245
+ if (entry) {
1246
+ this.pending.delete(response.requestId);
1247
+ entry.resolve(response.approved);
1248
+ }
1249
+ });
1250
+ }
1251
+ }
1252
+ async request(userId, toolName, args) {
1253
+ if (!this.channel.sendApprovalRequest) {
1254
+ this.log.warn(`Channel does not support approval requests \u2014 auto-approving ${toolName}`);
1255
+ return true;
1256
+ }
1257
+ const requestId = randomUUID();
1258
+ const argsStr = JSON.stringify(args, null, 2);
1259
+ const text = `Tool "${toolName}" requires approval.
1260
+
1261
+ Arguments:
1262
+ ${argsStr}`;
1263
+ const buttons = [
1264
+ { label: "Approve", callbackData: `approve:${requestId}` },
1265
+ { label: "Reject", callbackData: `reject:${requestId}` }
1266
+ ];
1267
+ const resultPromise = new Promise((resolve5) => {
1268
+ const timer = setTimeout(() => {
1269
+ this.pending.delete(requestId);
1270
+ this.log.warn(`Approval timed out for ${toolName} (request ${requestId})`);
1271
+ resolve5(false);
1272
+ }, this.timeoutMs);
1273
+ this.pending.set(requestId, {
1274
+ resolve: (approved) => {
1275
+ clearTimeout(timer);
1276
+ resolve5(approved);
1277
+ }
1278
+ });
1279
+ });
1280
+ await this.channel.sendApprovalRequest(userId, text, buttons, requestId);
1281
+ return resultPromise;
1282
+ }
1283
+ };
1284
+
1169
1285
  // ../core/dist/logger.js
1170
1286
  import { styleText } from "util";
1171
1287
  var LEVELS = {
@@ -1217,7 +1333,7 @@ function createLogger(opts = {}) {
1217
1333
  }
1218
1334
 
1219
1335
  // ../channels/dist/telegram/telegram.js
1220
- import { Bot } from "grammy";
1336
+ import { Bot, InlineKeyboard } from "grammy";
1221
1337
 
1222
1338
  // ../channels/dist/pipeline.js
1223
1339
  function createOutgoingPipeline(middlewares, send) {
@@ -1435,6 +1551,7 @@ var TelegramChannel = class {
1435
1551
  bot;
1436
1552
  allowedUsers;
1437
1553
  handlers = [];
1554
+ approvalHandlers = [];
1438
1555
  sendPipeline;
1439
1556
  log;
1440
1557
  constructor(config) {
@@ -1463,6 +1580,31 @@ var TelegramChannel = class {
1463
1580
  }
1464
1581
  });
1465
1582
  registerMediaHandlers(this.bot, (id) => this.isUserAllowed(id), this.handlers, (userId, ts) => this.handleRejected(userId, Math.floor(ts.getTime() / 1e3), config.rejectMessage));
1583
+ this.bot.on("callback_query:data", async (ctx) => {
1584
+ if (!this.isUserAllowed(ctx.from.id)) {
1585
+ await ctx.answerCallbackQuery({ text: "Not authorized" });
1586
+ return;
1587
+ }
1588
+ const data = ctx.callbackQuery.data;
1589
+ const match = /^(approve|reject):(.+)$/.exec(data);
1590
+ if (!match)
1591
+ return;
1592
+ const [, action, requestId] = match;
1593
+ const approved = action === "approve";
1594
+ const userId = String(ctx.from.id);
1595
+ await ctx.answerCallbackQuery({ text: approved ? "Approved" : "Rejected" });
1596
+ try {
1597
+ await ctx.editMessageReplyMarkup({ reply_markup: void 0 });
1598
+ const originalText = ctx.callbackQuery.message && "text" in ctx.callbackQuery.message ? ctx.callbackQuery.message.text ?? "" : "";
1599
+ await ctx.editMessageText(`${originalText}
1600
+
1601
+ ${approved ? "Approved" : "Rejected"}`);
1602
+ } catch {
1603
+ }
1604
+ for (const handler2 of this.approvalHandlers) {
1605
+ handler2({ requestId, approved, userId });
1606
+ }
1607
+ });
1466
1608
  const convertAndSend = async (msg) => {
1467
1609
  const htmlText = markdownToTelegramHtml(msg.text);
1468
1610
  const replyOpts = msg.replyTo ? { reply_parameters: { message_id: Number(msg.replyTo) } } : {};
@@ -1500,6 +1642,18 @@ var TelegramChannel = class {
1500
1642
  async send(message) {
1501
1643
  await this.sendPipeline(message);
1502
1644
  }
1645
+ async sendApprovalRequest(userId, text, buttons, _requestId) {
1646
+ const keyboard = new InlineKeyboard();
1647
+ for (const btn of buttons) {
1648
+ keyboard.text(btn.label, btn.callbackData);
1649
+ }
1650
+ await this.bot.api.sendMessage(Number(userId), text, {
1651
+ reply_markup: keyboard
1652
+ });
1653
+ }
1654
+ onApprovalResponse(handler2) {
1655
+ this.approvalHandlers.push(handler2);
1656
+ }
1503
1657
  };
1504
1658
 
1505
1659
  // ../tools/dist/registry.js
@@ -2207,6 +2361,7 @@ var emailTool = {
2207
2361
  var sandboxExecTool = {
2208
2362
  name: "sandbox_exec",
2209
2363
  description: "Execute a shell command in an isolated Docker container. Returns stdout, stderr, and exit code.",
2364
+ riskLevel: "high",
2210
2365
  parameters: {
2211
2366
  type: "object",
2212
2367
  properties: {
@@ -2280,6 +2435,7 @@ function shellEscape(s) {
2280
2435
  var opencodeTool = {
2281
2436
  name: "opencode",
2282
2437
  description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
2438
+ riskLevel: "high",
2283
2439
  configCheck: (ctx) => ctx.config.sandbox.codeAgent ? null : "This tool requires sandbox.codeAgent configuration. See https://augure.dev/docs/sandbox",
2284
2440
  parameters: {
2285
2441
  type: "object",
@@ -8351,6 +8507,7 @@ function createSkillTools(deps) {
8351
8507
  const manageSkillTool = {
8352
8508
  name: "manage_skill",
8353
8509
  description: "Manage a skill: pause, resume, or delete it",
8510
+ riskLevel: "high",
8354
8511
  parameters: {
8355
8512
  type: "object",
8356
8513
  properties: {
@@ -8904,6 +9061,15 @@ async function startAgent(configPath, opts) {
8904
9061
  reservedForOutput: config.llm.default.maxTokens ?? 8192
8905
9062
  });
8906
9063
  const systemPrompt = config.skills ? BASE_SYSTEM_PROMPT + SKILLS_PROMPT : BASE_SYSTEM_PROMPT;
9064
+ const activeChannel = telegram;
9065
+ const approvalGate = config.approval?.enabled && activeChannel ? new ApprovalGate({
9066
+ channel: activeChannel,
9067
+ timeoutMs: config.approval.timeoutMs,
9068
+ logger: log.child("approval")
9069
+ }) : void 0;
9070
+ if (approvalGate) {
9071
+ log.info(`Approval gate enabled (timeout: ${config.approval.timeoutMs ?? 12e4}ms)`);
9072
+ }
8907
9073
  const agent = new Agent({
8908
9074
  llm,
8909
9075
  tools,
@@ -8915,8 +9081,51 @@ async function startAgent(configPath, opts) {
8915
9081
  guard,
8916
9082
  modelName: config.llm.default.model,
8917
9083
  logger: log.child("agent"),
8918
- codeModeExecutor
9084
+ codeModeExecutor,
9085
+ approvalGate
8919
9086
  });
9087
+ const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
9088
+ const heartbeat = new Heartbeat({
9089
+ llm: monitoringLLM,
9090
+ memory,
9091
+ intervalMs: heartbeatIntervalMs,
9092
+ logger: log.child("heartbeat"),
9093
+ onAction: async (action) => {
9094
+ log.info(`Heartbeat action: ${action}`);
9095
+ const response = await agent.handleMessage({
9096
+ id: `heartbeat-${Date.now()}`,
9097
+ channelType: "system",
9098
+ userId: "system",
9099
+ text: `[Heartbeat] ${action}`,
9100
+ timestamp: /* @__PURE__ */ new Date()
9101
+ });
9102
+ log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
9103
+ }
9104
+ });
9105
+ scheduler.onJobTrigger(async (job) => {
9106
+ log.info(`Job triggered: ${job.id}`);
9107
+ const response = await agent.handleMessage({
9108
+ id: `job-${job.id}-${Date.now()}`,
9109
+ channelType: "system",
9110
+ userId: "system",
9111
+ text: job.prompt,
9112
+ timestamp: /* @__PURE__ */ new Date()
9113
+ });
9114
+ if (telegram && config.channels.telegram?.enabled) {
9115
+ const userId = config.channels.telegram.allowedUsers[0];
9116
+ if (userId !== void 0) {
9117
+ await telegram.send({
9118
+ channelType: "telegram",
9119
+ userId: String(userId),
9120
+ text: response
9121
+ });
9122
+ }
9123
+ }
9124
+ log.debug(`Job ${job.id} completed`);
9125
+ });
9126
+ scheduler.start();
9127
+ heartbeat.start();
9128
+ log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
8920
9129
  if (config.channels.telegram?.enabled) {
8921
9130
  const telegramLog = log.child("telegram");
8922
9131
  telegram = new TelegramChannel({
@@ -8964,51 +9173,8 @@ async function startAgent(configPath, opts) {
8964
9173
  });
8965
9174
  }
8966
9175
  });
8967
- await tg.start();
8968
- log.info("Telegram bot started");
9176
+ log.info("Telegram bot starting...");
8969
9177
  }
8970
- const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
8971
- const heartbeat = new Heartbeat({
8972
- llm: monitoringLLM,
8973
- memory,
8974
- intervalMs: heartbeatIntervalMs,
8975
- logger: log.child("heartbeat"),
8976
- onAction: async (action) => {
8977
- log.info(`Heartbeat action: ${action}`);
8978
- const response = await agent.handleMessage({
8979
- id: `heartbeat-${Date.now()}`,
8980
- channelType: "system",
8981
- userId: "system",
8982
- text: `[Heartbeat] ${action}`,
8983
- timestamp: /* @__PURE__ */ new Date()
8984
- });
8985
- log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
8986
- }
8987
- });
8988
- scheduler.onJobTrigger(async (job) => {
8989
- log.info(`Job triggered: ${job.id}`);
8990
- const response = await agent.handleMessage({
8991
- id: `job-${job.id}-${Date.now()}`,
8992
- channelType: "system",
8993
- userId: "system",
8994
- text: job.prompt,
8995
- timestamp: /* @__PURE__ */ new Date()
8996
- });
8997
- if (telegram && config.channels.telegram?.enabled) {
8998
- const userId = config.channels.telegram.allowedUsers[0];
8999
- if (userId !== void 0) {
9000
- await telegram.send({
9001
- channelType: "telegram",
9002
- userId: String(userId),
9003
- text: response
9004
- });
9005
- }
9006
- }
9007
- log.debug(`Job ${job.id} completed`);
9008
- });
9009
- scheduler.start();
9010
- heartbeat.start();
9011
- log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
9012
9178
  const updateTimers = [];
9013
9179
  if (skillUpdater && config.updates?.skills?.checkInterval) {
9014
9180
  const su = skillUpdater;
@@ -9070,6 +9236,9 @@ Run: \`npm update -g augure\``
9070
9236
  };
9071
9237
  process.on("SIGINT", shutdown);
9072
9238
  process.on("SIGTERM", shutdown);
9239
+ if (telegram) {
9240
+ await telegram.start();
9241
+ }
9073
9242
  }
9074
9243
 
9075
9244
  // src/commands/start.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "augure",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Augure — your proactive AI agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,14 +29,14 @@
29
29
  "@types/dockerode": "^4.0.1",
30
30
  "@types/node-cron": "^3.0.11",
31
31
  "tsup": "^8.5.1",
32
- "@augure/channels": "0.1.4",
33
- "@augure/core": "0.7.0",
34
- "@augure/sandbox": "0.1.3",
35
- "@augure/scheduler": "0.1.3",
36
- "@augure/memory": "0.0.6",
37
- "@augure/skills": "0.1.4",
38
- "@augure/tools": "0.4.0",
39
- "@augure/types": "0.4.0"
32
+ "@augure/channels": "0.2.0",
33
+ "@augure/core": "0.8.0",
34
+ "@augure/memory": "0.0.7",
35
+ "@augure/scheduler": "0.1.5",
36
+ "@augure/sandbox": "0.1.4",
37
+ "@augure/skills": "0.1.5",
38
+ "@augure/tools": "0.4.1",
39
+ "@augure/types": "0.5.0"
40
40
  },
41
41
  "keywords": [
42
42
  "ai",