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 +1 -1
- package/src/app.test.ts +27 -65
- package/src/app.ts +13 -13
- package/src/orchestrator.test.ts +246 -331
- package/src/orchestrator.ts +141 -153
- package/src/prompts.test.ts +1 -1
- package/src/prompts.ts +10 -16
- package/src/sessions.test.ts +6 -3
- package/src/system-service.test.ts +3 -0
package/package.json
CHANGED
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,
|
|
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("
|
|
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("
|
|
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("
|
|
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("/
|
|
680
|
-
const
|
|
681
|
-
const
|
|
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
|
-
|
|
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("/
|
|
743
|
-
const
|
|
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
|
|
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
|
-
|
|
759
|
-
|
|
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
|
|
764
|
-
expect(
|
|
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("
|
|
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("
|
|
773
|
-
const ctx = { chat: { id:
|
|
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
|
-
|
|
779
|
-
|
|
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: "
|
|
807
|
-
{ command: "
|
|
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: "
|
|
52
|
-
{ command: "
|
|
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(
|
|
87
|
-
this.#
|
|
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
|
|
91
|
-
this.#orchestrator.
|
|
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) => {
|