macroclaw 0.31.0 → 0.33.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/src/app.ts CHANGED
@@ -15,6 +15,7 @@ export interface AppConfig {
15
15
  settingsDir?: string;
16
16
  claude?: Claude;
17
17
  stt?: SpeechToText;
18
+ healthCheckInterval?: number;
18
19
  }
19
20
 
20
21
  export class App {
@@ -30,6 +31,7 @@ export class App {
30
31
  workspace: config.workspace,
31
32
  settingsDir: config.settingsDir,
32
33
  claude: config.claude,
34
+ healthCheckInterval: config.healthCheckInterval,
33
35
  onResponse: (r) => this.#deliverResponse(r),
34
36
  });
35
37
 
@@ -40,6 +42,10 @@ export class App {
40
42
  return this.#bot;
41
43
  }
42
44
 
45
+ async dispose(): Promise<void> {
46
+ await this.#orchestrator.dispose();
47
+ }
48
+
43
49
  start() {
44
50
  log.info("Starting macroclaw...");
45
51
  const scheduler = new Scheduler(this.#config.workspace, {
@@ -50,6 +56,7 @@ export class App {
50
56
  { command: "chatid", description: "Show current chat ID" },
51
57
  { command: "bg", description: "Spawn a background agent" },
52
58
  { command: "sessions", description: "List running sessions" },
59
+ { command: "clear", description: "Clear session and start fresh" },
53
60
  ]).catch((err) => log.error({ err }, "Failed to set commands"));
54
61
  this.#bot.start({
55
62
  onStart: (botInfo) => {
@@ -91,6 +98,12 @@ export class App {
91
98
  this.#orchestrator.handleSessions();
92
99
  });
93
100
 
101
+ this.#bot.command("clear", (ctx) => {
102
+ if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
103
+ log.debug("Command /clear");
104
+ this.#orchestrator.handleClear();
105
+ });
106
+
94
107
  this.#bot.on("message:photo", async (ctx) => {
95
108
  if (ctx.chat.id.toString() !== this.#config.authorizedChatId) return;
96
109
  const photos = ctx.message.photo;
@@ -26,51 +26,89 @@ const fullSchema = z.object({
26
26
  })).optional(),
27
27
  });
28
28
 
29
- const objectResultType = (schema: z.ZodType) => ({ type: "object" as const, schema });
29
+ function objectResultType<T>(schema: z.ZodType<T>) {
30
+ return { type: "object" as const, schema };
31
+ }
32
+ const textResultType = { type: "text" } as const;
30
33
 
31
- describe("claude CLI structured output", () => {
32
- it("simple schema without system prompt", async () => {
34
+ describe("stream-json persistent process", () => {
35
+ it("receives structured output via stream-json with single send", async () => {
33
36
  const claude = new Claude({ workspace: WORKSPACE });
34
- const { value } = await claude.newSession("Say hello", objectResultType(simpleSchema), { model: "haiku" }).result;
37
+ const process = claude.newSession(objectResultType(simpleSchema), { model: "haiku" });
35
38
 
36
- console.log("Simple (no sysprompt):", JSON.stringify(value, null, 2));
39
+ const { value } = await process.send("Say hello briefly");
40
+
41
+ console.log("Stream-json structured output:", JSON.stringify(value, null, 2));
37
42
  expect(value).not.toBeNull();
43
+ expect(value.action).toBeDefined();
44
+
45
+ await process.kill();
38
46
  }, 60_000);
39
47
 
40
- it("simple schema with system prompt", async () => {
48
+ it("receives text output via stream-json", async () => {
41
49
  const claude = new Claude({ workspace: WORKSPACE });
42
- const { value } = await claude.newSession(
43
- "Say hello",
44
- objectResultType(simpleSchema),
45
- { model: "haiku", systemPrompt: "You are a helpful assistant. This is a direct message from the user." },
46
- ).result;
50
+ const process = claude.newSession(textResultType, { model: "haiku" });
47
51
 
48
- console.log("Simple (with sysprompt):", JSON.stringify(value, null, 2));
49
- expect(value).not.toBeNull();
52
+ const { value } = await process.send("Say hello in one word");
53
+
54
+ console.log("Stream-json text output:", value);
55
+ expect(typeof value).toBe("string");
56
+ expect(value.length).toBeGreaterThan(0);
57
+
58
+ await process.kill();
50
59
  }, 60_000);
51
60
 
61
+ it("supports multiple sends to the same process (persistent)", async () => {
62
+ const claude = new Claude({ workspace: WORKSPACE });
63
+ const process = claude.newSession(objectResultType(simpleSchema), { model: "haiku" });
64
+
65
+ // First message
66
+ const r1 = await process.send("Say hello");
67
+ console.log("First response:", JSON.stringify(r1.value, null, 2));
68
+ expect(r1.value.action).toBeDefined();
69
+
70
+ // Second message — same process, same session
71
+ const r2 = await process.send("Say goodbye");
72
+ console.log("Second response:", JSON.stringify(r2.value, null, 2));
73
+ expect(r2.value.action).toBeDefined();
74
+
75
+ // Session IDs should match (same session)
76
+ expect(r1.sessionId).toBe(r2.sessionId);
77
+
78
+ await process.kill();
79
+ }, 120_000);
80
+
52
81
  it("full schema with system prompt", async () => {
53
82
  const claude = new Claude({ workspace: WORKSPACE });
54
- const { value } = await claude.newSession(
55
- "Say hello",
83
+ const process = claude.newSession(
56
84
  objectResultType(fullSchema),
57
85
  { model: "haiku", systemPrompt: "You are a helpful assistant. This is a direct message from the user." },
58
- ).result;
86
+ );
59
87
 
60
- console.log("Full (with sysprompt):", JSON.stringify(value, null, 2));
88
+ const { value } = await process.send("Say hello");
89
+
90
+ console.log("Full schema (with sysprompt):", JSON.stringify(value, null, 2));
61
91
  expect(value).not.toBeNull();
92
+
93
+ await process.kill();
62
94
  }, 60_000);
63
95
 
64
- it("full schema with real system prompt and workspace", async () => {
65
- const workspace = process.env.MACROCLAW_WORKSPACE ?? WORKSPACE;
66
- const claude = new Claude({ workspace });
67
- const { value } = await claude.newSession(
68
- "Say hello",
69
- objectResultType(fullSchema),
70
- { model: "sonnet", systemPrompt: "You are an AI assistant running inside macroclaw. This is a direct message from the user." },
71
- ).result;
96
+ it("resume session works with stream-json", async () => {
97
+ const claude = new Claude({ workspace: WORKSPACE });
72
98
 
73
- console.log("Full (real workspace):", JSON.stringify(value, null, 2));
74
- expect(value).not.toBeNull();
99
+ // Create a session
100
+ const p1 = claude.newSession(objectResultType(simpleSchema), { model: "haiku" });
101
+ const r1 = await p1.send("Remember the word 'banana'");
102
+ const sessionId = r1.sessionId;
103
+ console.log("Created session:", sessionId);
104
+ await p1.kill();
105
+
106
+ // Resume the session in a new process
107
+ const p2 = claude.resumeSession(sessionId, objectResultType(simpleSchema), { model: "haiku" });
108
+ const r2 = await p2.send("What word did I ask you to remember?");
109
+ console.log("Resumed response:", JSON.stringify(r2.value, null, 2));
110
+ expect(r2.value.action).toBeDefined();
111
+
112
+ await p2.kill();
75
113
  }, 120_000);
76
114
  });