augure 0.7.0 → 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 +532 -73
  2. package/package.json +11 -10
package/dist/bin.js CHANGED
@@ -223,7 +223,22 @@ var AppConfigSchema = z.object({
223
223
  password: z.string()
224
224
  })
225
225
  }).optional(),
226
- github: z.object({ token: z.string() }).optional()
226
+ github: z.object({ token: z.string() }).optional(),
227
+ browser: z.object({
228
+ provider: z.enum(["local", "browserbase"]),
229
+ browserbase: z.object({
230
+ apiKey: z.string().min(1),
231
+ projectId: z.string().optional()
232
+ }).optional(),
233
+ defaults: z.object({
234
+ timeout: z.number().int().positive().optional(),
235
+ headless: z.boolean().optional(),
236
+ viewport: z.object({
237
+ width: z.number().int().positive(),
238
+ height: z.number().int().positive()
239
+ }).optional()
240
+ }).optional()
241
+ }).optional()
227
242
  }),
228
243
  security: z.object({
229
244
  sandboxOnly: z.boolean(),
@@ -261,6 +276,10 @@ var AppConfigSchema = z.object({
261
276
  runtime: z.enum(["vm", "docker", "auto"]).default("auto"),
262
277
  timeout: z.number().int().positive().default(30),
263
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)
264
283
  }).optional()
265
284
  });
266
285
  function interpolateEnvVars(raw) {
@@ -607,7 +626,7 @@ var VmExecutor = class {
607
626
 
608
627
  // ../code-mode/dist/docker-sandbox.js
609
628
  var DOCKER_HARNESS = `
610
- import { readFile } from "node:fs/promises";
629
+ import { readFile, writeFile, unlink } from "node:fs/promises";
611
630
 
612
631
  const __logs = [];
613
632
  const __originalLog = console.log;
@@ -616,12 +635,28 @@ console.warn = (...args) => __logs.push("[warn] " + args.map(String).join(" "));
616
635
  console.error = (...args) => __logs.push("[error] " + args.map(String).join(" "));
617
636
 
618
637
  let __toolCalls = 0;
638
+ let __reqId = 0;
619
639
 
640
+ const __BRIDGE_TIMEOUT = 120_000;
620
641
  const api = new Proxy({}, {
621
642
  get: (_target, toolName) => {
622
- return async () => {
643
+ return async (args) => {
623
644
  __toolCalls++;
624
- 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\`);
625
660
  };
626
661
  }
627
662
  });
@@ -648,11 +683,37 @@ try {
648
683
  }));
649
684
  }
650
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
+ }
651
692
  var DockerExecutor = class {
652
693
  config;
653
694
  constructor(config) {
654
695
  this.config = config;
655
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
+ }
656
717
  async execute(code) {
657
718
  const start = Date.now();
658
719
  let container;
@@ -679,10 +740,14 @@ var DockerExecutor = class {
679
740
  await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/user-code.js'`);
680
741
  const harnessB64 = Buffer.from(DOCKER_HARNESS).toString("base64");
681
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);
682
745
  const execResult = await container.exec("npx tsx /workspace/harness.ts", {
683
746
  timeout: this.config.timeout,
684
747
  cwd: "/workspace"
685
748
  });
749
+ abortController.abort();
750
+ await bridgePromise;
686
751
  if (execResult.exitCode === 0 && execResult.stdout.trim()) {
687
752
  try {
688
753
  const lastLine = execResult.stdout.trim().split("\n").pop();
@@ -937,6 +1002,18 @@ var Agent = class {
937
1002
  for (const toolCall of response.toolCalls) {
938
1003
  const toolStart = Date.now();
939
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
+ }
940
1017
  let result;
941
1018
  if (codeModeTool && toolCall.name === "execute_code") {
942
1019
  result = await codeModeTool.execute(toolCall.arguments, {});
@@ -1151,6 +1228,60 @@ var ContextGuard = class {
1151
1228
  }
1152
1229
  };
1153
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
+
1154
1285
  // ../core/dist/logger.js
1155
1286
  import { styleText } from "util";
1156
1287
  var LEVELS = {
@@ -1202,7 +1333,7 @@ function createLogger(opts = {}) {
1202
1333
  }
1203
1334
 
1204
1335
  // ../channels/dist/telegram/telegram.js
1205
- import { Bot } from "grammy";
1336
+ import { Bot, InlineKeyboard } from "grammy";
1206
1337
 
1207
1338
  // ../channels/dist/pipeline.js
1208
1339
  function createOutgoingPipeline(middlewares, send) {
@@ -1420,6 +1551,7 @@ var TelegramChannel = class {
1420
1551
  bot;
1421
1552
  allowedUsers;
1422
1553
  handlers = [];
1554
+ approvalHandlers = [];
1423
1555
  sendPipeline;
1424
1556
  log;
1425
1557
  constructor(config) {
@@ -1448,6 +1580,31 @@ var TelegramChannel = class {
1448
1580
  }
1449
1581
  });
1450
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
+ });
1451
1608
  const convertAndSend = async (msg) => {
1452
1609
  const htmlText = markdownToTelegramHtml(msg.text);
1453
1610
  const replyOpts = msg.replyTo ? { reply_parameters: { message_id: Number(msg.replyTo) } } : {};
@@ -1485,6 +1642,18 @@ var TelegramChannel = class {
1485
1642
  async send(message) {
1486
1643
  await this.sendPipeline(message);
1487
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
+ }
1488
1657
  };
1489
1658
 
1490
1659
  // ../tools/dist/registry.js
@@ -2192,6 +2361,7 @@ var emailTool = {
2192
2361
  var sandboxExecTool = {
2193
2362
  name: "sandbox_exec",
2194
2363
  description: "Execute a shell command in an isolated Docker container. Returns stdout, stderr, and exit code.",
2364
+ riskLevel: "high",
2195
2365
  parameters: {
2196
2366
  type: "object",
2197
2367
  properties: {
@@ -2265,6 +2435,7 @@ function shellEscape(s) {
2265
2435
  var opencodeTool = {
2266
2436
  name: "opencode",
2267
2437
  description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
2438
+ riskLevel: "high",
2268
2439
  configCheck: (ctx) => ctx.config.sandbox.codeAgent ? null : "This tool requires sandbox.codeAgent configuration. See https://augure.dev/docs/sandbox",
2269
2440
  parameters: {
2270
2441
  type: "object",
@@ -6345,6 +6516,248 @@ var githubTool = {
6345
6516
  }
6346
6517
  };
6347
6518
 
6519
+ // ../tools/dist/browser.js
6520
+ function createBrowserTool(manager) {
6521
+ return {
6522
+ name: "browser",
6523
+ description: "AI-powered browser automation. Open a session, then use natural language to interact with web pages. Actions: open (creates session), navigate, act (click/type/interact), extract (get structured data), observe (discover elements), screenshot, close. Use 'act' with natural language instructions instead of CSS selectors. Use 'extract' with an instruction describing what data to get. Always close sessions when done.",
6524
+ parameters: {
6525
+ type: "object",
6526
+ properties: {
6527
+ action: {
6528
+ type: "string",
6529
+ enum: ["open", "navigate", "act", "extract", "observe", "screenshot", "close"],
6530
+ description: "The browser action to perform"
6531
+ },
6532
+ session: {
6533
+ type: "string",
6534
+ description: "Session ID from 'open'. Required for all actions except 'open'."
6535
+ },
6536
+ url: {
6537
+ type: "string",
6538
+ description: "URL to navigate to. Used with 'open' and 'navigate'."
6539
+ },
6540
+ instruction: {
6541
+ type: "string",
6542
+ description: "Natural language instruction for act/extract/observe. Examples: 'click the search button', 'extract all product prices and titles', 'find the login form'."
6543
+ },
6544
+ schema: {
6545
+ type: "object",
6546
+ description: "JSON schema for structured extraction with 'extract'. Optional."
6547
+ },
6548
+ variables: {
6549
+ type: "object",
6550
+ description: "Variables for sensitive data in 'act'. Use %varName% in instruction. Example: instruction='type %password%', variables={password: 'secret'}"
6551
+ }
6552
+ },
6553
+ required: ["action"]
6554
+ },
6555
+ configCheck: (ctx) => {
6556
+ if (!ctx.config.tools?.browser) {
6557
+ return "Browser tool requires tools.browser config in augure.json5. Set provider to 'local' for Playwright or 'browserbase' for cloud.";
6558
+ }
6559
+ if (ctx.config.tools.browser.provider === "browserbase" && !ctx.config.tools.browser.browserbase?.apiKey) {
6560
+ return "Browserbase provider requires tools.browser.browserbase.apiKey";
6561
+ }
6562
+ return null;
6563
+ },
6564
+ execute: async (params, _ctx) => {
6565
+ const p = params;
6566
+ if (p.action !== "open" && !p.session) {
6567
+ return { success: false, output: "Missing 'session' \u2014 open a session first with action: 'open'" };
6568
+ }
6569
+ if (["act", "extract", "observe"].includes(p.action) && !p.instruction) {
6570
+ return { success: false, output: `Missing 'instruction' for action '${p.action}'` };
6571
+ }
6572
+ try {
6573
+ switch (p.action) {
6574
+ case "open": {
6575
+ const sessionId = await manager.open(p.url);
6576
+ return { success: true, output: `Session ${sessionId} opened.${p.url ? ` Navigated to ${p.url}` : ""}` };
6577
+ }
6578
+ case "navigate": {
6579
+ if (!p.url)
6580
+ return { success: false, output: "Missing 'url' for navigate" };
6581
+ await manager.navigate(p.session, p.url);
6582
+ return { success: true, output: `Navigated to ${p.url}` };
6583
+ }
6584
+ case "act": {
6585
+ const result = await manager.act(p.session, p.instruction, p.variables);
6586
+ return { success: result.success, output: result.message || "Action completed" };
6587
+ }
6588
+ case "extract": {
6589
+ const data = await manager.extract(p.session, p.instruction, p.schema);
6590
+ const output = typeof data === "string" ? data : JSON.stringify(data, null, 2);
6591
+ return { success: true, output };
6592
+ }
6593
+ case "observe": {
6594
+ const elements = await manager.observe(p.session, p.instruction);
6595
+ return {
6596
+ success: true,
6597
+ output: elements.length > 0 ? elements.map((e) => `- ${e.description} (${e.selector})`).join("\n") : "No matching elements found"
6598
+ };
6599
+ }
6600
+ case "screenshot": {
6601
+ const base64 = await manager.screenshot(p.session);
6602
+ return {
6603
+ success: true,
6604
+ output: "Screenshot captured",
6605
+ artifacts: [{ type: "image", name: "screenshot.png", content: base64 }]
6606
+ };
6607
+ }
6608
+ case "close": {
6609
+ await manager.close(p.session);
6610
+ return { success: true, output: `Session ${p.session} closed` };
6611
+ }
6612
+ default:
6613
+ return { success: false, output: `Unknown action: ${p.action}` };
6614
+ }
6615
+ } catch (err2) {
6616
+ return {
6617
+ success: false,
6618
+ output: `Browser error: ${err2 instanceof Error ? err2.message : String(err2)}`
6619
+ };
6620
+ }
6621
+ }
6622
+ };
6623
+ }
6624
+
6625
+ // ../browser/dist/session-manager.js
6626
+ import { Stagehand } from "@browserbasehq/stagehand";
6627
+
6628
+ // ../browser/dist/provider.js
6629
+ var PROVIDER_BASE_URLS = {
6630
+ openrouter: "https://openrouter.ai/api/v1",
6631
+ anthropic: "https://api.anthropic.com/v1",
6632
+ openai: "https://api.openai.com/v1"
6633
+ };
6634
+ function createStagehandConfig(config, llm) {
6635
+ const baseURL = PROVIDER_BASE_URLS[llm.provider];
6636
+ return {
6637
+ env: config.provider === "local" ? "LOCAL" : "BROWSERBASE",
6638
+ apiKey: config.browserbase?.apiKey,
6639
+ projectId: config.browserbase?.projectId,
6640
+ model: {
6641
+ modelName: llm.model,
6642
+ apiKey: llm.apiKey,
6643
+ ...baseURL ? { baseURL } : {}
6644
+ },
6645
+ localBrowserLaunchOptions: config.provider === "local" ? {
6646
+ headless: config.defaults?.headless ?? true,
6647
+ viewport: config.defaults?.viewport ?? { width: 1280, height: 720 }
6648
+ } : void 0,
6649
+ domSettleTimeout: (config.defaults?.timeout ?? 30) * 1e3,
6650
+ verbose: 0,
6651
+ disablePino: true
6652
+ };
6653
+ }
6654
+
6655
+ // ../browser/dist/session-manager.js
6656
+ var BrowserSessionManager = class {
6657
+ sessions = /* @__PURE__ */ new Map();
6658
+ config;
6659
+ llm;
6660
+ ttlMs;
6661
+ log;
6662
+ counter = 0;
6663
+ constructor(opts) {
6664
+ this.config = opts.config;
6665
+ this.llm = opts.llm;
6666
+ this.ttlMs = opts.ttlMs ?? 12e4;
6667
+ this.log = opts.logger ?? noopLogger;
6668
+ }
6669
+ async open(url) {
6670
+ const id = `s_${Date.now()}_${++this.counter}`;
6671
+ const stagehandConfig = createStagehandConfig(this.config, this.llm);
6672
+ const stagehand = new Stagehand(stagehandConfig);
6673
+ await stagehand.init();
6674
+ if (url) {
6675
+ const page = stagehand.context.activePage();
6676
+ if (!page)
6677
+ throw new Error(`No active page after init for session ${id}`);
6678
+ await page.goto(url, { waitUntil: "domcontentloaded" });
6679
+ }
6680
+ const timer = setTimeout(() => {
6681
+ this.log.warn(`Browser session ${id} expired (TTL ${this.ttlMs}ms)`);
6682
+ this.close(id).catch(() => {
6683
+ });
6684
+ }, this.ttlMs);
6685
+ this.sessions.set(id, { stagehand, timer });
6686
+ this.log.info(`Browser session ${id} opened`);
6687
+ return id;
6688
+ }
6689
+ async navigate(sessionId, url) {
6690
+ const entry = this.getSession(sessionId);
6691
+ this.resetTtl(sessionId, entry);
6692
+ const page = entry.stagehand.context.activePage();
6693
+ if (!page)
6694
+ throw new Error(`No active page for session ${sessionId}`);
6695
+ await page.goto(url, { waitUntil: "domcontentloaded" });
6696
+ }
6697
+ async act(sessionId, instruction, variables) {
6698
+ const entry = this.getSession(sessionId);
6699
+ this.resetTtl(sessionId, entry);
6700
+ const result = await entry.stagehand.act(instruction, variables ? { variables } : void 0);
6701
+ return { success: result.success, message: result.message ?? result.actionDescription ?? "" };
6702
+ }
6703
+ async extract(sessionId, instruction, schema) {
6704
+ const entry = this.getSession(sessionId);
6705
+ this.resetTtl(sessionId, entry);
6706
+ if (schema) {
6707
+ return entry.stagehand.extract(instruction, schema);
6708
+ }
6709
+ return entry.stagehand.extract(instruction);
6710
+ }
6711
+ async observe(sessionId, instruction) {
6712
+ const entry = this.getSession(sessionId);
6713
+ this.resetTtl(sessionId, entry);
6714
+ const actions2 = await entry.stagehand.observe(instruction);
6715
+ return actions2.map((a) => ({
6716
+ description: a.description ?? "",
6717
+ selector: a.selector ?? ""
6718
+ }));
6719
+ }
6720
+ async screenshot(sessionId) {
6721
+ const entry = this.getSession(sessionId);
6722
+ this.resetTtl(sessionId, entry);
6723
+ const page = entry.stagehand.context.activePage();
6724
+ if (!page)
6725
+ throw new Error(`No active page for session ${sessionId}`);
6726
+ const buffer = await page.screenshot();
6727
+ return Buffer.from(buffer).toString("base64");
6728
+ }
6729
+ async close(sessionId) {
6730
+ const entry = this.sessions.get(sessionId);
6731
+ if (!entry)
6732
+ return;
6733
+ clearTimeout(entry.timer);
6734
+ this.sessions.delete(sessionId);
6735
+ try {
6736
+ await entry.stagehand.close();
6737
+ } catch {
6738
+ }
6739
+ this.log.info(`Browser session ${sessionId} closed`);
6740
+ }
6741
+ async closeAll() {
6742
+ const ids = [...this.sessions.keys()];
6743
+ await Promise.all(ids.map((id) => this.close(id)));
6744
+ }
6745
+ getSession(sessionId) {
6746
+ const entry = this.sessions.get(sessionId);
6747
+ if (!entry)
6748
+ throw new Error(`Unknown or expired: no browser session ${sessionId}`);
6749
+ return entry;
6750
+ }
6751
+ resetTtl(sessionId, entry) {
6752
+ clearTimeout(entry.timer);
6753
+ entry.timer = setTimeout(() => {
6754
+ this.log.warn(`Browser session ${sessionId} expired (TTL ${this.ttlMs}ms)`);
6755
+ this.close(sessionId).catch(() => {
6756
+ });
6757
+ }, this.ttlMs);
6758
+ }
6759
+ };
6760
+
6348
6761
  // ../core/dist/main.js
6349
6762
  import Dockerode from "dockerode";
6350
6763
 
@@ -6846,15 +7259,25 @@ ${content}`;
6846
7259
  // ../scheduler/dist/cron.js
6847
7260
  import { createTask, validate } from "node-cron";
6848
7261
  var CronScheduler = class {
6849
- store;
6850
7262
  jobs = /* @__PURE__ */ new Map();
6851
7263
  tasks = /* @__PURE__ */ new Map();
6852
7264
  timers = /* @__PURE__ */ new Map();
6853
7265
  handlers = [];
6854
7266
  persistChain = Promise.resolve();
6855
7267
  running = false;
6856
- constructor(store) {
6857
- this.store = store;
7268
+ store;
7269
+ log;
7270
+ constructor(storeOrOpts) {
7271
+ if (storeOrOpts && "save" in storeOrOpts) {
7272
+ this.store = storeOrOpts;
7273
+ this.log = noopLogger;
7274
+ } else if (storeOrOpts) {
7275
+ const opts = storeOrOpts;
7276
+ this.store = opts.store;
7277
+ this.log = opts.logger ?? noopLogger;
7278
+ } else {
7279
+ this.log = noopLogger;
7280
+ }
6858
7281
  }
6859
7282
  onJobTrigger(handler2) {
6860
7283
  this.handlers.push(handler2);
@@ -6870,20 +7293,24 @@ var CronScheduler = class {
6870
7293
  throw new Error(`Invalid runAt date: ${job.runAt}`);
6871
7294
  }
6872
7295
  this.jobs.set(job.id, job);
6873
- console.log(`[scheduler] Added job ${job.id} (${job.cron ? `cron: ${job.cron}` : `runAt: ${job.runAt}`})`);
7296
+ this.log.info(`Added job ${job.id} (${job.cron ? `cron: ${job.cron}` : `runAt: ${job.runAt}`})`);
6874
7297
  if (job.enabled && job.cron) {
6875
7298
  const task = createTask(job.cron, () => {
6876
- console.log(`[scheduler] Cron fired for job ${job.id}`);
6877
- void this.executeHandlers(job);
7299
+ this.log.info(`Cron fired for job ${job.id}`);
7300
+ this.executeHandlers(job).catch((err2) => this.log.error(`Cron job ${job.id} handler failed:`, err2));
6878
7301
  });
6879
7302
  if (this.running) {
6880
7303
  task.start();
6881
- console.log(`[scheduler] Started cron task for ${job.id} immediately (scheduler already running)`);
7304
+ this.log.debug(`Started cron task for ${job.id} immediately (scheduler already running)`);
6882
7305
  }
6883
7306
  this.tasks.set(job.id, task);
6884
7307
  }
6885
- if (this.running && job.enabled && job.runAt && !job.cron) {
6886
- this.scheduleOneShot(job);
7308
+ if (job.enabled && job.runAt && !job.cron) {
7309
+ if (this.running) {
7310
+ this.scheduleOneShot(job);
7311
+ } else {
7312
+ this.log.warn(`Scheduler not running \u2014 one-shot job ${job.id} will be scheduled on start()`);
7313
+ }
6887
7314
  }
6888
7315
  this.persist();
6889
7316
  }
@@ -6899,7 +7326,7 @@ var CronScheduler = class {
6899
7326
  this.timers.delete(id);
6900
7327
  }
6901
7328
  this.jobs.delete(id);
6902
- console.log(`[scheduler] Removed job ${id}`);
7329
+ this.log.debug(`Removed job ${id}`);
6903
7330
  this.persist();
6904
7331
  }
6905
7332
  listJobs() {
@@ -6916,10 +7343,10 @@ var CronScheduler = class {
6916
7343
  if (!this.store)
6917
7344
  return;
6918
7345
  const jobs = await this.store.load();
6919
- console.log(`[scheduler] Loading ${jobs.length} persisted jobs`);
7346
+ this.log.info(`Loading ${jobs.length} persisted jobs`);
6920
7347
  for (const job of jobs) {
6921
7348
  if (job.runAt && Date.parse(job.runAt) <= Date.now()) {
6922
- console.log(`[scheduler] Skipping expired one-shot job ${job.id} (runAt: ${job.runAt})`);
7349
+ this.log.debug(`Skipping expired one-shot job ${job.id} (runAt: ${job.runAt})`);
6923
7350
  continue;
6924
7351
  }
6925
7352
  this.addJob(job);
@@ -6927,10 +7354,10 @@ var CronScheduler = class {
6927
7354
  }
6928
7355
  start() {
6929
7356
  this.running = true;
6930
- console.log(`[scheduler] Starting with ${this.tasks.size} cron tasks and ${this.handlers.length} handlers`);
7357
+ this.log.info(`Starting with ${this.tasks.size} cron tasks and ${this.handlers.length} handlers`);
6931
7358
  for (const [id, task] of this.tasks) {
6932
7359
  task.start();
6933
- console.log(`[scheduler] Started cron task: ${id}`);
7360
+ this.log.debug(`Started cron task: ${id}`);
6934
7361
  }
6935
7362
  for (const job of this.jobs.values()) {
6936
7363
  if (job.enabled && job.runAt && !job.cron) {
@@ -6939,6 +7366,7 @@ var CronScheduler = class {
6939
7366
  }
6940
7367
  }
6941
7368
  stop() {
7369
+ this.log.info("Scheduler stopped");
6942
7370
  this.running = false;
6943
7371
  for (const task of this.tasks.values()) {
6944
7372
  task.stop();
@@ -6951,16 +7379,14 @@ var CronScheduler = class {
6951
7379
  scheduleOneShot(job) {
6952
7380
  const delayMs = Date.parse(job.runAt) - Date.now();
6953
7381
  if (delayMs <= 0) {
6954
- console.log(`[scheduler] One-shot job ${job.id} already expired (delay: ${delayMs}ms), skipping`);
7382
+ this.log.warn(`One-shot job ${job.id} already expired (delay: ${delayMs}ms), skipping`);
6955
7383
  return;
6956
7384
  }
6957
- console.log(`[scheduler] Scheduled one-shot job ${job.id} in ${Math.round(delayMs / 1e3)}s (${job.runAt})`);
7385
+ this.log.info(`Scheduled one-shot job ${job.id} in ${Math.round(delayMs / 1e3)}s (${job.runAt})`);
6958
7386
  const timer = setTimeout(() => {
6959
- console.log(`[scheduler] One-shot job ${job.id} firing now`);
7387
+ this.log.info(`One-shot job ${job.id} firing now`);
6960
7388
  this.timers.delete(job.id);
6961
- void this.executeHandlers(job).then(() => {
6962
- this.removeJob(job.id);
6963
- });
7389
+ this.executeHandlers(job).then(() => this.removeJob(job.id)).catch((err2) => this.log.error(`One-shot job ${job.id} handler failed:`, err2));
6964
7390
  }, delayMs);
6965
7391
  this.timers.set(job.id, timer);
6966
7392
  }
@@ -6971,7 +7397,7 @@ var CronScheduler = class {
6971
7397
  this.persistChain = this.persistChain.then(() => this.store.save(jobs));
6972
7398
  }
6973
7399
  async executeHandlers(job) {
6974
- console.log(`[scheduler] Executing ${this.handlers.length} handlers for job ${job.id}`);
7400
+ this.log.debug(`Executing ${this.handlers.length} handlers for job ${job.id}`);
6975
7401
  for (const handler2 of this.handlers) {
6976
7402
  await handler2(job);
6977
7403
  }
@@ -8081,6 +8507,7 @@ function createSkillTools(deps) {
8081
8507
  const manageSkillTool = {
8082
8508
  name: "manage_skill",
8083
8509
  description: "Manage a skill: pause, resume, or delete it",
8510
+ riskLevel: "high",
8084
8511
  parameters: {
8085
8512
  type: "object",
8086
8513
  properties: {
@@ -8468,9 +8895,26 @@ async function startAgent(configPath, opts) {
8468
8895
  tools.register(sandboxExecTool);
8469
8896
  tools.register(opencodeTool);
8470
8897
  tools.register(githubTool);
8898
+ let browserManager;
8899
+ if (config.tools?.browser) {
8900
+ const browserLlm = config.llm.coding ?? config.llm.default;
8901
+ browserManager = new BrowserSessionManager({
8902
+ config: config.tools.browser,
8903
+ llm: {
8904
+ provider: browserLlm.provider ?? config.llm.default.provider,
8905
+ apiKey: browserLlm.apiKey ?? config.llm.default.apiKey,
8906
+ model: browserLlm.model ?? config.llm.default.model,
8907
+ maxTokens: browserLlm.maxTokens ?? config.llm.default.maxTokens
8908
+ },
8909
+ ttlMs: 12e4,
8910
+ logger: log.child("browser")
8911
+ });
8912
+ tools.register(createBrowserTool(browserManager));
8913
+ log.info("Browser tool registered", { provider: config.tools.browser.provider });
8914
+ }
8471
8915
  const jobStorePath = resolve(configPath, "..", "jobs.json");
8472
8916
  const jobStore = new JobStore(jobStorePath);
8473
- const scheduler = new CronScheduler(jobStore);
8917
+ const scheduler = new CronScheduler({ store: jobStore, logger: log.child("scheduler") });
8474
8918
  await scheduler.loadPersistedJobs();
8475
8919
  log.info(`Loaded ${scheduler.listJobs().length} persisted jobs`);
8476
8920
  for (const job of config.scheduler.jobs) {
@@ -8498,7 +8942,8 @@ async function startAgent(configPath, opts) {
8498
8942
  const skillRunner = new SkillRunner({
8499
8943
  pool,
8500
8944
  manager: skillManager,
8501
- defaults: config.sandbox.defaults
8945
+ defaults: config.sandbox.defaults,
8946
+ browserManager
8502
8947
  });
8503
8948
  const skillTester = new SkillTester({
8504
8949
  pool,
@@ -8616,6 +9061,15 @@ async function startAgent(configPath, opts) {
8616
9061
  reservedForOutput: config.llm.default.maxTokens ?? 8192
8617
9062
  });
8618
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
+ }
8619
9073
  const agent = new Agent({
8620
9074
  llm,
8621
9075
  tools,
@@ -8627,8 +9081,51 @@ async function startAgent(configPath, opts) {
8627
9081
  guard,
8628
9082
  modelName: config.llm.default.model,
8629
9083
  logger: log.child("agent"),
8630
- codeModeExecutor
9084
+ codeModeExecutor,
9085
+ approvalGate
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
+ }
8631
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}`);
8632
9129
  if (config.channels.telegram?.enabled) {
8633
9130
  const telegramLog = log.child("telegram");
8634
9131
  telegram = new TelegramChannel({
@@ -8676,51 +9173,8 @@ async function startAgent(configPath, opts) {
8676
9173
  });
8677
9174
  }
8678
9175
  });
8679
- await tg.start();
8680
- log.info("Telegram bot started");
9176
+ log.info("Telegram bot starting...");
8681
9177
  }
8682
- const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
8683
- const heartbeat = new Heartbeat({
8684
- llm: monitoringLLM,
8685
- memory,
8686
- intervalMs: heartbeatIntervalMs,
8687
- logger: log.child("heartbeat"),
8688
- onAction: async (action) => {
8689
- log.info(`Heartbeat action: ${action}`);
8690
- const response = await agent.handleMessage({
8691
- id: `heartbeat-${Date.now()}`,
8692
- channelType: "system",
8693
- userId: "system",
8694
- text: `[Heartbeat] ${action}`,
8695
- timestamp: /* @__PURE__ */ new Date()
8696
- });
8697
- log.debug(`Heartbeat response: ${response.slice(0, 200)}`);
8698
- }
8699
- });
8700
- scheduler.onJobTrigger(async (job) => {
8701
- log.info(`Job triggered: ${job.id}`);
8702
- const response = await agent.handleMessage({
8703
- id: `job-${job.id}-${Date.now()}`,
8704
- channelType: "system",
8705
- userId: "system",
8706
- text: job.prompt,
8707
- timestamp: /* @__PURE__ */ new Date()
8708
- });
8709
- if (telegram && config.channels.telegram?.enabled) {
8710
- const userId = config.channels.telegram.allowedUsers[0];
8711
- if (userId !== void 0) {
8712
- await telegram.send({
8713
- channelType: "telegram",
8714
- userId: String(userId),
8715
- text: response
8716
- });
8717
- }
8718
- }
8719
- log.debug(`Job ${job.id} completed`);
8720
- });
8721
- scheduler.start();
8722
- heartbeat.start();
8723
- log.info(`Scheduler started: ${scheduler.listJobs().length} jobs, heartbeat every ${config.scheduler.heartbeatInterval}`);
8724
9178
  const updateTimers = [];
8725
9179
  if (skillUpdater && config.updates?.skills?.checkInterval) {
8726
9180
  const su = skillUpdater;
@@ -8773,6 +9227,8 @@ Run: \`npm update -g augure\``
8773
9227
  scheduler.stop();
8774
9228
  if (telegram)
8775
9229
  await telegram.stop();
9230
+ if (browserManager)
9231
+ await browserManager.closeAll();
8776
9232
  await pool.destroyAll();
8777
9233
  await audit.close();
8778
9234
  log.info("All containers destroyed");
@@ -8780,6 +9236,9 @@ Run: \`npm update -g augure\``
8780
9236
  };
8781
9237
  process.on("SIGINT", shutdown);
8782
9238
  process.on("SIGTERM", shutdown);
9239
+ if (telegram) {
9240
+ await telegram.start();
9241
+ }
8783
9242
  }
8784
9243
 
8785
9244
  // src/commands/start.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "augure",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Augure — your proactive AI agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,20 +22,21 @@
22
22
  "gray-matter": "^4.0.3",
23
23
  "json5": "^2.2.3",
24
24
  "node-cron": "^4.2.1",
25
- "zod": "^4.3.6"
25
+ "zod": "^4.3.6",
26
+ "@browserbasehq/stagehand": "^3.0.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/dockerode": "^4.0.1",
29
30
  "@types/node-cron": "^3.0.11",
30
31
  "tsup": "^8.5.1",
31
- "@augure/channels": "0.1.3",
32
- "@augure/core": "0.6.0",
33
- "@augure/sandbox": "0.1.2",
34
- "@augure/scheduler": "0.1.2",
35
- "@augure/memory": "0.0.5",
36
- "@augure/tools": "0.3.0",
37
- "@augure/types": "0.3.0",
38
- "@augure/skills": "0.1.3"
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"
39
40
  },
40
41
  "keywords": [
41
42
  "ai",