macroclaw 0.25.0 → 0.27.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macroclaw",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/app.test.ts CHANGED
@@ -141,12 +141,12 @@ describe("App", () => {
141
141
  expect(bot.filterHandlers.has("callback_query:data")).toBe(true);
142
142
  });
143
143
 
144
- it("registers chatid, session, and bg commands", () => {
144
+ it("registers chatid, bg, and sessions commands", () => {
145
145
  const app = new App(makeConfig());
146
146
  const bot = app.bot as any;
147
147
  expect(bot.commandHandlers.has("chatid")).toBe(true);
148
- expect(bot.commandHandlers.has("session")).toBe(true);
149
148
  expect(bot.commandHandlers.has("bg")).toBe(true);
149
+ expect(bot.commandHandlers.has("sessions")).toBe(true);
150
150
  });
151
151
 
152
152
  it("registers error handler", () => {
@@ -576,7 +576,7 @@ describe("App", () => {
576
576
  expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Opened", callback_data: "_noop" }]] } });
577
577
  const calls = (bot.api.sendMessage as any).mock.calls;
578
578
  const text = calls[calls.length - 1][1];
579
- expect(text).toBe("Agent not found or already finished.");
579
+ expect(text).toBe("Session not found or already finished.");
580
580
  });
581
581
 
582
582
  it("handles peek: callback by routing to orchestrator.handlePeek", async () => {
@@ -599,7 +599,7 @@ describe("App", () => {
599
599
  expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Peeked", callback_data: "_noop" }]] } });
600
600
  const calls = (bot.api.sendMessage as any).mock.calls;
601
601
  const text = calls[calls.length - 1][1];
602
- expect(text).toBe("Agent not found or already finished.");
602
+ expect(text).toBe("Session not found or already finished.");
603
603
  });
604
604
 
605
605
  it("handles kill: callback by routing to orchestrator.handleKill", async () => {
@@ -622,7 +622,7 @@ describe("App", () => {
622
622
  expect(ctx.editMessageReplyMarkup).toHaveBeenCalledWith({ reply_markup: { inline_keyboard: [[{ text: "✓ Killed", callback_data: "_noop" }]] } });
623
623
  const calls = (bot.api.sendMessage as any).mock.calls;
624
624
  const text = calls[calls.length - 1][1];
625
- expect(text).toBe("Agent not found or already finished.");
625
+ expect(text).toBe("Session not found or already finished.");
626
626
  });
627
627
 
628
628
  it("ignores callback_query from unauthorized chats", async () => {
@@ -676,35 +676,9 @@ describe("App", () => {
676
676
  expect(ctx.reply).toHaveBeenCalledWith("Chat ID: `12345`", { parse_mode: "Markdown" });
677
677
  });
678
678
 
679
- it("/session sends session ID via sendMessage", async () => {
680
- const app = new App(makeConfig());
681
- const bot = app.bot as any;
682
- const handler = bot.commandHandlers.get("session")!;
683
- const ctx = { chat: { id: 12345 } };
684
-
685
- handler(ctx);
686
- await new Promise((r) => setTimeout(r, 50));
687
-
688
- const calls = (bot.api.sendMessage as any).mock.calls;
689
- expect(calls.length).toBeGreaterThan(0);
690
- const text = calls[calls.length - 1][1];
691
- expect(text).toBe("Session: <code>test-session</code>");
692
- });
693
-
694
- it("/session is ignored for unauthorized chats", async () => {
695
- const app = new App(makeConfig());
696
- const bot = app.bot as any;
697
- const handler = bot.commandHandlers.get("session")!;
698
- const ctx = { chat: { id: 99999 } };
699
-
700
- handler(ctx);
701
- await new Promise((r) => setTimeout(r, 50));
702
-
703
- expect((bot.api.sendMessage as any).mock.calls.length).toBe(0);
704
- });
705
-
706
- it("/bg shows no agents via sendMessage when none are running", async () => {
707
- const app = new App(makeConfig());
679
+ it("/bg without prompt sends usage hint", async () => {
680
+ const config = makeConfig();
681
+ const app = new App(config);
708
682
  const bot = app.bot as any;
709
683
  const handler = bot.commandHandlers.get("bg")!;
710
684
  const ctx = { chat: { id: 12345 }, match: "" };
@@ -712,9 +686,9 @@ describe("App", () => {
712
686
  handler(ctx);
713
687
  await new Promise((r) => setTimeout(r, 50));
714
688
 
689
+ expect((config.claude as Claude & { calls: CallInfo[] }).calls).toHaveLength(0);
715
690
  const calls = (bot.api.sendMessage as any).mock.calls;
716
- const text = calls[calls.length - 1][1];
717
- expect(text).toBe("No background agents running.");
691
+ expect(calls[calls.length - 1][1]).toBe("Usage: /bg <prompt>");
718
692
  });
719
693
 
720
694
  it("/bg with prompt spawns a background agent via sendMessage", async () => {
@@ -739,46 +713,34 @@ describe("App", () => {
739
713
  expect(claude.calls).toHaveLength(1);
740
714
  });
741
715
 
742
- it("/bg lists active background agents via sendMessage", async () => {
743
- const claude = mockClaude((): RunningQuery<unknown> => ({
744
- sessionId: `bg-${Date.now()}`,
745
- startedAt: new Date(),
746
- result: new Promise(() => {}),
747
- kill: mock(async () => {}),
748
- }));
749
- const config = makeConfig({ claude });
750
- const app = new App(config);
716
+ it("/sessions lists running sessions via sendMessage", async () => {
717
+ const app = new App(makeConfig());
751
718
  const bot = app.bot as any;
752
- const bgHandler = bot.commandHandlers.get("bg")!;
753
-
754
- const spawnCtx = { chat: { id: 12345 }, match: "long task" };
755
- bgHandler(spawnCtx);
756
- await new Promise((r) => setTimeout(r, 10));
719
+ const handler = bot.commandHandlers.get("sessions")!;
720
+ const ctx = { chat: { id: 12345 } };
757
721
 
758
- const listCtx = { chat: { id: 12345 }, match: "" };
759
- bgHandler(listCtx);
760
- await new Promise((r) => setTimeout(r, 10));
722
+ handler(ctx);
723
+ await new Promise((r) => setTimeout(r, 50));
761
724
 
762
725
  const calls = (bot.api.sendMessage as any).mock.calls;
763
- const lastText = calls[calls.length - 1][1];
764
- expect(lastText).toContain("long-task");
765
- expect(lastText).toMatch(/\d+s/);
726
+ const text = calls[calls.length - 1][1];
727
+ expect(text).toBe("No running sessions.");
766
728
  });
767
729
 
768
- it("shows 'none' when no session exists yet", async () => {
769
- if (existsSync(tmpSettingsDir)) rmSync(tmpSettingsDir, { recursive: true });
730
+ it("/sessions is ignored for unauthorized chats", async () => {
770
731
  const app = new App(makeConfig());
771
732
  const bot = app.bot as any;
772
- const handler = bot.commandHandlers.get("session")!;
773
- const ctx = { chat: { id: 12345 } };
733
+ const handler = bot.commandHandlers.get("sessions")!;
734
+ const ctx = { chat: { id: 99999 } };
774
735
 
775
736
  handler(ctx);
776
737
  await new Promise((r) => setTimeout(r, 50));
777
738
 
778
- const calls = (bot.api.sendMessage as any).mock.calls;
779
- const text = calls[calls.length - 1][1];
780
- expect(text).toBe("Session: <code>none</code>");
739
+ // No sendMessage calls for unauthorized
740
+ expect((bot.api.sendMessage as any).mock.calls.length).toBe(0);
781
741
  });
742
+
743
+
782
744
  });
783
745
 
784
746
  describe("error handler", () => {
@@ -803,8 +765,8 @@ describe("App", () => {
803
765
  app.start();
804
766
  expect(bot.api.setMyCommands).toHaveBeenCalledWith([
805
767
  { command: "chatid", description: "Show current chat ID" },
806
- { command: "session", description: "Show current session ID" },
807
- { command: "bg", description: "List or spawn background agents" },
768
+ { command: "bg", description: "Spawn a background agent" },
769
+ { command: "sessions", description: "List running sessions" },
808
770
  ]);
809
771
  });
810
772
  });
package/src/app.ts CHANGED
@@ -48,8 +48,8 @@ export class App {
48
48
  scheduler.start();
49
49
  this.#bot.api.setMyCommands([
50
50
  { command: "chatid", description: "Show current chat ID" },
51
- { command: "session", description: "Show current session ID" },
52
- { command: "bg", description: "List or spawn background agents" },
51
+ { command: "bg", description: "Spawn a background agent" },
52
+ { command: "sessions", description: "List running sessions" },
53
53
  ]).catch((err) => log.error({ err }, "Failed to set commands"));
54
54
  this.#bot.start({
55
55
  onStart: (botInfo) => {
@@ -73,22 +73,22 @@ export class App {
73
73
  ctx.reply(`Chat ID: \`${ctx.chat.id}\``, { parse_mode: "Markdown" });
74
74
  });
75
75
 
76
- this.#bot.command("session", (ctx) => {
77
- if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
78
- log.debug("Command /session");
79
- this.#orchestrator.handleSessionCommand();
80
- });
81
-
82
76
  this.#bot.command("bg", (ctx) => {
83
77
  if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
84
78
  const prompt = ctx.match?.trim();
85
- if (prompt) {
86
- log.debug({ prompt }, "Command /bg spawn");
87
- this.#orchestrator.handleBackgroundCommand(prompt);
79
+ if (!prompt) {
80
+ log.debug("Command /bg without prompt");
81
+ sendResponse(this.#bot, this.#config.authorizedChatId, "Usage: /bg <prompt>");
88
82
  return;
89
83
  }
90
- log.debug("Command /bg list");
91
- this.#orchestrator.handleBackgroundList();
84
+ log.debug({ prompt }, "Command /bg spawn");
85
+ this.#orchestrator.handleBackgroundCommand(prompt);
86
+ });
87
+
88
+ this.#bot.command("sessions", (ctx) => {
89
+ if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
90
+ log.debug("Command /sessions");
91
+ this.#orchestrator.handleSessions();
92
92
  });
93
93
 
94
94
  this.#bot.on("message:photo", async (ctx) => {