macroclaw 0.32.0 → 0.34.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 +159 -103
- package/src/app.ts +13 -0
- package/src/claude.integration-test.ts +65 -27
- package/src/claude.test.ts +369 -189
- package/src/claude.ts +171 -71
- package/src/orchestrator.test.ts +301 -249
- package/src/orchestrator.ts +157 -96
- package/workspace-template/.claude/skills/self-update/SKILL.md +1 -1
- package/workspace-template/.claude/skills/self-update/scripts/update.sh +3 -4
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
|
-
|
|
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("
|
|
32
|
-
it("
|
|
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
|
|
37
|
+
const process = claude.newSession(objectResultType(simpleSchema), { model: "haiku" });
|
|
35
38
|
|
|
36
|
-
|
|
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("
|
|
48
|
+
it("receives text output via stream-json", async () => {
|
|
41
49
|
const claude = new Claude({ workspace: WORKSPACE });
|
|
42
|
-
const
|
|
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
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
)
|
|
86
|
+
);
|
|
59
87
|
|
|
60
|
-
|
|
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("
|
|
65
|
-
const
|
|
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
|
-
|
|
74
|
-
|
|
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
|
});
|