@cosmicstack/mercury-agent 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -193,4 +193,16 @@ Configure multiple LLM providers. Mercury tries them in order and falls back aut
193
193
 
194
194
  ## License
195
195
 
196
- MIT © [Cosmic Stack](https://github.com/cosmicstack-labs)
196
+ MIT © [Cosmic Stack](https://github.com/cosmicstack-labs)
197
+
198
+ ---
199
+
200
+ ## Disclaimer
201
+
202
+ **This is AI - it can break sometimes, please use this at your own risk.**
203
+
204
+ ---
205
+
206
+ ## Suggestions and Contributions
207
+
208
+ For suggestions, contributions, or any inquiries, please reach out to us at [support@cosmicstack.org](mailto:support@cosmicstack.org).
package/dist/index.js CHANGED
@@ -848,6 +848,7 @@ You can override this:
848
848
  });
849
849
  }
850
850
  this.capabilities.setChannelContext(msg.channelId, msg.channelType);
851
+ this.capabilities.permissions.setCurrentChannelType(msg.channelType);
851
852
  const fallbackIterator = this.providers.getFallbackIterator();
852
853
  let result = null;
853
854
  let usedProvider = null;
@@ -1720,7 +1721,7 @@ var CLIChannel = class extends BaseChannel {
1720
1721
  // src/channels/telegram.ts
1721
1722
  import fs2 from "fs";
1722
1723
  import path2 from "path";
1723
- import { Bot, InputFile } from "grammy";
1724
+ import { Bot, InputFile, InlineKeyboard } from "grammy";
1724
1725
  import { autoRetry } from "@grammyjs/auto-retry";
1725
1726
  var MAX_MESSAGE_LENGTH = 4096;
1726
1727
  var TelegramChannel = class extends BaseChannel {
@@ -1734,6 +1735,7 @@ var TelegramChannel = class extends BaseChannel {
1734
1735
  ownerChatId = null;
1735
1736
  typingInterval = null;
1736
1737
  chatCommandContext;
1738
+ pendingApprovals = /* @__PURE__ */ new Map();
1737
1739
  setChatCommandContext(ctx) {
1738
1740
  this.chatCommandContext = ctx;
1739
1741
  }
@@ -1762,6 +1764,18 @@ var TelegramChannel = class extends BaseChannel {
1762
1764
  };
1763
1765
  this.emit(msg);
1764
1766
  });
1767
+ bot.on("callback_query:data", async (ctx) => {
1768
+ const data = ctx.callbackQuery.data;
1769
+ const resolver = this.pendingApprovals.get(data);
1770
+ if (!resolver) {
1771
+ await ctx.answerCallbackQuery({ text: "Expired" });
1772
+ return;
1773
+ }
1774
+ this.pendingApprovals.delete(data);
1775
+ const action = data.split(":")[1];
1776
+ resolver(action);
1777
+ await ctx.answerCallbackQuery({ text: action === "no" ? "Denied" : "Approved" });
1778
+ });
1765
1779
  bot.catch((err) => {
1766
1780
  logger.error({ err: err.message }, "Telegram bot error");
1767
1781
  });
@@ -1945,6 +1959,34 @@ var TelegramChannel = class extends BaseChannel {
1945
1959
  this.stopTypingLoop();
1946
1960
  }
1947
1961
  }
1962
+ async askPermission(prompt, targetId) {
1963
+ const chatId = this.parseChatId(targetId);
1964
+ if (!chatId || !this.bot) return "no";
1965
+ const id = `perm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1966
+ const keyboard = new InlineKeyboard().text("Allow", `${id}:yes`).text("Always", `${id}:always`).text("Deny", `${id}:no`);
1967
+ const html = mdToTelegram(prompt);
1968
+ try {
1969
+ await this.bot.api.sendMessage(chatId, html, {
1970
+ parse_mode: "HTML",
1971
+ reply_markup: keyboard
1972
+ });
1973
+ } catch {
1974
+ await this.bot.api.sendMessage(chatId, this.stripHtml(html), {
1975
+ reply_markup: keyboard
1976
+ });
1977
+ }
1978
+ return new Promise((resolve9) => {
1979
+ this.pendingApprovals.set(`${id}:yes`, () => resolve9("yes"));
1980
+ this.pendingApprovals.set(`${id}:always`, () => resolve9("always"));
1981
+ this.pendingApprovals.set(`${id}:no`, () => resolve9("no"));
1982
+ setTimeout(() => {
1983
+ this.pendingApprovals.delete(`${id}:yes`);
1984
+ this.pendingApprovals.delete(`${id}:always`);
1985
+ this.pendingApprovals.delete(`${id}:no`);
1986
+ resolve9("no");
1987
+ }, 12e4);
1988
+ });
1989
+ }
1948
1990
  escapeHtml(text) {
1949
1991
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1950
1992
  }
@@ -2165,7 +2207,7 @@ var TokenBudget = class {
2165
2207
 
2166
2208
  // src/capabilities/permissions.ts
2167
2209
  import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
2168
- import { join as join6, resolve } from "path";
2210
+ import { join as join6, resolve, sep } from "path";
2169
2211
  import { homedir as homedir2 } from "os";
2170
2212
  import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
2171
2213
  var DEFAULT_MANIFEST = {
@@ -2258,10 +2300,17 @@ var PermissionManager = class {
2258
2300
  autoApproveAll = false;
2259
2301
  elevatedCommands = /* @__PURE__ */ new Set();
2260
2302
  pendingApprovals = /* @__PURE__ */ new Set();
2303
+ currentChannelType = "cli";
2261
2304
  constructor() {
2262
2305
  this.cwd = process.cwd();
2263
2306
  this.manifest = this.load();
2264
2307
  }
2308
+ setCurrentChannelType(type) {
2309
+ this.currentChannelType = type;
2310
+ }
2311
+ getCurrentChannelType() {
2312
+ return this.currentChannelType;
2313
+ }
2265
2314
  onAsk(handler) {
2266
2315
  this.askHandler = handler;
2267
2316
  }
@@ -2390,9 +2439,31 @@ var PermissionManager = class {
2390
2439
  }
2391
2440
  for (const pattern of shell.needsApproval) {
2392
2441
  if (this.matchPattern(trimmed, pattern)) {
2442
+ if (this.currentChannelType === "telegram" && this.askHandler) {
2443
+ const result = await this.askHandler(`Run command: ${trimmed}`);
2444
+ if (result === "yes") {
2445
+ return { allowed: true, needsApproval: false };
2446
+ }
2447
+ if (result === "always") {
2448
+ this.addApprovedCommand(baseCmd);
2449
+ return { allowed: true, needsApproval: false };
2450
+ }
2451
+ return { allowed: false, reason: `User denied: ${trimmed}`, needsApproval: false };
2452
+ }
2393
2453
  return { allowed: false, reason: `Command requires approval: matches "${pattern}"`, needsApproval: true };
2394
2454
  }
2395
2455
  }
2456
+ if (this.currentChannelType === "telegram" && this.askHandler) {
2457
+ const result = await this.askHandler(`Run command: ${trimmed}`);
2458
+ if (result === "yes") {
2459
+ return { allowed: true, needsApproval: false };
2460
+ }
2461
+ if (result === "always") {
2462
+ this.addApprovedCommand(baseCmd);
2463
+ return { allowed: true, needsApproval: false };
2464
+ }
2465
+ return { allowed: false, reason: `User denied: ${trimmed}`, needsApproval: false };
2466
+ }
2396
2467
  return { allowed: false, reason: "Command not in auto-approve list \u2014 requires approval", needsApproval: true };
2397
2468
  }
2398
2469
  isGitReadAllowed() {
@@ -2421,7 +2492,7 @@ var PermissionManager = class {
2421
2492
  const scopes = this.manifest.capabilities.filesystem.scopes;
2422
2493
  for (const scope of scopes) {
2423
2494
  const scopeResolved = resolve(scope.path.replace(/^~/, homedir2()));
2424
- if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + "/")) {
2495
+ if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
2425
2496
  return scope;
2426
2497
  }
2427
2498
  }
@@ -2455,7 +2526,9 @@ var PermissionManager = class {
2455
2526
  const pathPatterns = [
2456
2527
  /(?:^|\s)(\/[^\s]+)/,
2457
2528
  /(?:^|\s)(~\/[^\s]+)/,
2458
- /(?:^|\s)\.\.\/([^\s]+)/
2529
+ /(?:^|\s)\.\.\/([^\s]+)/,
2530
+ /(?:^|\s)([A-Za-z]:\\[^\s]+)/,
2531
+ /(?:^|\s)(\\\\[^\s]+)/
2459
2532
  ];
2460
2533
  for (const p of pathPatterns) {
2461
2534
  const match = command.match(p);
@@ -4196,7 +4269,7 @@ function banner() {
4196
4269
  }
4197
4270
  console.log("");
4198
4271
  console.log(chalk6.white(" an AI agent for personal tasks"));
4199
- console.log(chalk6.dim(" v0.2.0 \xB7 by Cosmic Stack \xB7 mercury.cosmicstack.org"));
4272
+ console.log(chalk6.dim(" v0.2.1 \xB7 by Cosmic Stack \xB7 mercury.cosmicstack.org"));
4200
4273
  console.log("");
4201
4274
  }
4202
4275
  function splashScreen() {
@@ -4439,11 +4512,17 @@ async function runAgent(isDaemon = false) {
4439
4512
  await agent.birth();
4440
4513
  await agent.wake();
4441
4514
  const cliChannel = channels.get("cli");
4442
- if (cliChannel) {
4443
- capabilities.permissions.onAsk(async (prompt) => {
4515
+ const tgChannel = channels.get("telegram");
4516
+ capabilities.permissions.onAsk(async (prompt) => {
4517
+ const channelType = capabilities.permissions.getCurrentChannelType();
4518
+ if (channelType === "telegram" && tgChannel) {
4519
+ return tgChannel.askPermission(prompt);
4520
+ }
4521
+ if (cliChannel) {
4444
4522
  return cliChannel.askPermission(prompt);
4445
- });
4446
- }
4523
+ }
4524
+ return "no";
4525
+ });
4447
4526
  const activeCh = channels.getActiveChannels();
4448
4527
  const toolNames = capabilities.getToolNames();
4449
4528
  if (!isDaemon) {
@@ -4476,7 +4555,7 @@ async function runAgent(isDaemon = false) {
4476
4555
  process.on("SIGTERM", shutdown);
4477
4556
  }
4478
4557
  var program = new Command();
4479
- program.name("mercury").description("Mercury \u2014 Soul-driven AI agent with permission-hardened tools, token budgets, and multi-channel access.").version("0.2.0").option("-v, --verbose", "Show debug logs").action(async () => {
4558
+ program.name("mercury").description("Mercury \u2014 Soul-driven AI agent with permission-hardened tools, token budgets, and multi-channel access.").version("0.2.1").option("-v, --verbose", "Show debug logs").action(async () => {
4480
4559
  if (!isSetupComplete()) {
4481
4560
  await configure();
4482
4561
  autoDaemonize();