macroclaw 0.44.0 → 0.46.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.44.0",
3
+ "version": "0.46.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -479,7 +479,7 @@ describe("Claude factory", () => {
479
479
  claude.newSession(textResult);
480
480
  const args = spawnArgs();
481
481
  expect(args).toContain("--disallowedTools");
482
- expect(args).toContain("CronList,CronDelete,CronCreate,AskUserQuestion");
482
+ expect(args).toContain("CronList,CronDelete,CronCreate,AskUserQuestion,RemoteTrigger");
483
483
  });
484
484
 
485
485
  it("spawns with stdin: pipe, stdout: pipe, stderr: pipe", () => {
package/src/claude.ts CHANGED
@@ -257,7 +257,7 @@ export class Claude {
257
257
  "--input-format", "stream-json",
258
258
  "--output-format", "stream-json",
259
259
  "--verbose",
260
- "--disallowedTools", "CronList,CronDelete,CronCreate,AskUserQuestion",
260
+ "--disallowedTools", "CronList,CronDelete,CronCreate,AskUserQuestion,RemoteTrigger",
261
261
  ];
262
262
 
263
263
  if (mode.kind === "resume") {
@@ -930,6 +930,32 @@ describe("Orchestrator", () => {
930
930
  expect(callCount).toBeGreaterThanOrEqual(2);
931
931
  });
932
932
 
933
+ it("passes action and actionReason through result XML for silent background agent", async () => {
934
+ saveSessions({ mainSessionId: "test-session" }, tmpSettingsDir);
935
+ const { process: bgProc, resolve: resolveBg } = pendingProcess("bg-sid");
936
+
937
+ let callCount = 0;
938
+ const claude = mockClaude((): ClaudeProcess<unknown> => {
939
+ callCount++;
940
+ if (callCount === 1) return bgProc;
941
+ return autoProcess({ action: "silent", actionReason: "nothing to report" }, `sid-${callCount}`);
942
+ });
943
+ const { orch } = makeOrchestrator(claude);
944
+
945
+ orch.handleBackgroundCommand("check something");
946
+ await waitForProcessing();
947
+
948
+ resolveBg({ action: "silent", actionReason: "no new results" });
949
+ await waitForProcessing(100);
950
+
951
+ const prompts = sentPrompts(claude);
952
+ const bgResultPrompt = prompts.find((p: string) => p?.includes("background-agent-result"));
953
+ expect(bgResultPrompt).toBeDefined();
954
+ expect(bgResultPrompt).toContain('action="silent"');
955
+ expect(bgResultPrompt).toContain('action-reason="no new results"');
956
+ expect(bgResultPrompt).not.toContain("<text>");
957
+ });
958
+
933
959
  it("feeds error back to queue on spawn failure", async () => {
934
960
  saveSessions({ mainSessionId: "test-session" }, tmpSettingsDir);
935
961
  const { process: bgProc, reject: rejectBg } = pendingProcess("bg-sid");
@@ -437,8 +437,12 @@ export class Orchestrator {
437
437
  return this.#prompts.backgroundAgentResult(
438
438
  name,
439
439
  request.name,
440
- { text: request.response.message || "[No output]", files: request.response.files },
441
- "Forward this result to the user (action=\"send\"). Summarize or add context from the conversation as appropriate.",
440
+ {
441
+ action: request.response.action,
442
+ actionReason: request.response.actionReason,
443
+ text: request.response.message,
444
+ files: request.response.files,
445
+ },
442
446
  { backgroundedEvent },
443
447
  );
444
448
  case "background-agent-progress":
@@ -170,12 +170,12 @@ describe("backgroundAgentResult", () => {
170
170
  const result = p.backgroundAgentResult(
171
171
  "bg-research",
172
172
  "research",
173
- { text: "found 3 papers" },
174
- "Forward to user.",
173
+ { action: "send", actionReason: "completed", text: "found 3 papers" },
175
174
  );
176
175
  expect(result).toContain('type="background-agent-result"');
177
176
  expect(result).toContain('<original-event name="research" />');
178
- expect(result).toContain("<result>");
177
+ expect(result).toContain('action="send"');
178
+ expect(result).toContain('action-reason="completed"');
179
179
  expect(result).toContain("<text>found 3 papers</text>");
180
180
  expect(result).toContain("</result>");
181
181
  expect(result).not.toContain("<files>");
@@ -185,27 +185,26 @@ describe("backgroundAgentResult", () => {
185
185
  const result = p.backgroundAgentResult(
186
186
  "bg-research",
187
187
  "research",
188
- { text: "here are the screenshots", files: ["/tmp/screenshot.png"] },
189
- "Forward to user.",
188
+ { action: "send", actionReason: "done", text: "here are the screenshots", files: ["/tmp/screenshot.png"] },
190
189
  );
191
- expect(result).toContain("<result>");
190
+ expect(result).toContain('action="send"');
192
191
  expect(result).toContain("<text>here are the screenshots</text>");
193
192
  expect(result).toContain('<file path="/tmp/screenshot.png" />');
194
193
  expect(result).toContain("</result>");
195
194
  });
196
195
 
197
- it("includes instructions after result", () => {
196
+ it("builds self-closing result for silent action", () => {
198
197
  const result = p.backgroundAgentResult(
199
- "bg-research",
200
- "research",
201
- { text: "done" },
202
- "Forward to user.",
198
+ "bg-heartbeat",
199
+ "cron-heartbeat",
200
+ { action: "silent", actionReason: "no new results" },
203
201
  );
204
- expect(result).toContain("<instructions>Forward to user.</instructions>");
205
- const instrIdx = result.indexOf("<instructions>");
206
- const closeIdx = result.indexOf("</event>");
207
- expect(instrIdx).toBeLessThan(closeIdx);
208
- expect(instrIdx).toBeGreaterThan(result.indexOf("</result>"));
202
+ expect(result).toContain('action="silent"');
203
+ expect(result).toContain('action-reason="no new results"');
204
+ expect(result).toContain("<result ");
205
+ expect(result).toContain("/>");
206
+ expect(result).not.toContain("<text>");
207
+ expect(result).not.toContain("</result>");
209
208
  });
210
209
  });
211
210
 
@@ -25,7 +25,7 @@ Event format: every incoming message is wrapped in an <event> XML block. Attribu
25
25
  - button-click — user tapped an inline button. Label in <button>.
26
26
  - schedule-trigger — automated scheduled task. Contains <schedule> with name and optional missed-by/scheduled-at attributes. Prefer action="silent" when nothing noteworthy.
27
27
  - background-agent-start — you are a background agent. Complete the task in <text> and return a result.
28
- - background-agent-result — a background agent has finished. Contains <original-event name="..." /> linking to the agent that produced it, and a <result> block with <text> and optional <files>. Always use action="send" — the user expects to see the outcome. Summarize, relay, or add additional context from the conversation as appropriate.
28
+ - background-agent-result — a background agent has finished. Contains <original-event name="..." /> linking to the agent that produced it, and a <result> block with action and action-reason attributes, plus optional <text> and <files>. If action="send", forward to the user (use action="send")summarize or add context as appropriate. If action="silent", the agent had nothing to report use action="silent" and do not send a message, but keep the context in mind.
29
29
  - background-agent-progress — interim progress update from a still-running background agent. Contains <original-event name="..." /> and a <progress> element. This is NOT a final result. Do not report to the user unless it contains exceptionally important information (errors, blockers, urgent findings). Keep this context in mind — if the user later asks about progress of a background task, use the latest progress update to answer.
30
30
  - peek — status check on a running session. Contains <target-event name="..." /> identifying the event being peeked at. Only consider progress since that event started. Respond with a brief status update (2-3 sentences): what has been done, what's happening now, what's remaining. Return plain text, not structured output.
31
31
  - health-check — automated status check on a background agent. Contains <target-event name="..." />. Report whether the task is complete or still in progress.
@@ -46,7 +46,7 @@ Inner elements:
46
46
  - <original-event name="..." /> — in background-agent-result, links to the agent that produced the result.
47
47
  - <target-event name="..." /> — in peek, identifies the event being checked on.
48
48
  - <progress> — interim status from a still-running background agent.
49
- - <result> — wraps the output from a completed background agent. Contains <text> and optional <files>.
49
+ - <result> — wraps the output from a completed background agent. Attributes: action ("send"|"silent"), action-reason. Contains optional <text> and <files>.
50
50
  - <instructions> — inline guidance for how to handle this specific event. Always follow these instructions.
51
51
 
52
52
  Background agents: spawn alongside any response via backgroundAgents array:
@@ -78,7 +78,7 @@ interface BuildXmlFields {
78
78
  targetEvent?: string;
79
79
  instructions?: string;
80
80
  progress?: string;
81
- result?: { text: string; files?: string[] };
81
+ result?: { action: string; actionReason: string; text?: string; files?: string[] };
82
82
  }
83
83
 
84
84
  export class PromptBuilder {
@@ -130,16 +130,23 @@ export class PromptBuilder {
130
130
  }
131
131
 
132
132
  if (fields.result) {
133
- lines.push("<result>");
134
- lines.push(`<text>${esc(fields.result.text)}</text>`);
135
- if (fields.result.files?.length) {
136
- lines.push("<files>");
137
- for (const f of fields.result.files) {
138
- lines.push(` <file path="${esc(f)}" />`);
133
+ const resultAttrs = [`action="${esc(fields.result.action)}" action-reason="${esc(fields.result.actionReason)}"`];
134
+ if (fields.result.text || fields.result.files?.length) {
135
+ lines.push(`<result ${resultAttrs.join(" ")}>`);
136
+ if (fields.result.text) {
137
+ lines.push(`<text>${esc(fields.result.text)}</text>`);
139
138
  }
140
- lines.push("</files>");
139
+ if (fields.result.files?.length) {
140
+ lines.push("<files>");
141
+ for (const f of fields.result.files) {
142
+ lines.push(` <file path="${esc(f)}" />`);
143
+ }
144
+ lines.push("</files>");
145
+ }
146
+ lines.push("</result>");
147
+ } else {
148
+ lines.push(`<result ${resultAttrs.join(" ")} />`);
141
149
  }
142
- lines.push("</result>");
143
150
  }
144
151
 
145
152
  if (fields.button) {
@@ -182,8 +189,8 @@ export class PromptBuilder {
182
189
  return PromptBuilder.#buildXml(name, "background-agent-start", "background", this.#localTime(), { text });
183
190
  }
184
191
 
185
- backgroundAgentResult(name: string, originalEvent: string, result: { text: string; files?: string[] }, instructions: string, opts?: { backgroundedEvent?: string }): string {
186
- return PromptBuilder.#buildXml(name, "background-agent-result", "main", this.#localTime(), { originalEvent, result, instructions, backgroundedEvent: opts?.backgroundedEvent });
192
+ backgroundAgentResult(name: string, originalEvent: string, result: { action: string; actionReason: string; text?: string; files?: string[] }, opts?: { backgroundedEvent?: string }): string {
193
+ return PromptBuilder.#buildXml(name, "background-agent-result", "main", this.#localTime(), { originalEvent, result, backgroundedEvent: opts?.backgroundedEvent });
187
194
  }
188
195
 
189
196
  backgroundAgentProgress(name: string, originalEvent: string, progress: string, instructions: string, opts?: { backgroundedEvent?: string }): string {
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: schedule
2
+ name: schedule-event
3
3
  description: "Schedule events, reminders, and recurring tasks. Use when the user wants to: set a reminder, schedule something for later, create a recurring task, set up a periodic check, automate a prompt on a schedule, or plan a one-time or repeating event at a specific time."
4
4
  ---
5
5
 
@@ -63,6 +63,8 @@ Skills live in `.claude/skills/`.
63
63
 
64
64
  When creating new skills, always put them in `.claude/skills/` within this workspace.
65
65
 
66
+ **Never use the built-in `/schedule` skill** — it manages cloud remote triggers which we don't use. Always use `/schedule-event` for scheduling.
67
+
66
68
  **Before using a skill**, check `TOOLS.md` for operational notes — custom instructions, overrides, workarounds, and tips that supplement the skill's own SKILL.md. Update `TOOLS.md` when you discover new issues or tricks.
67
69
 
68
70
  ## Workspace Structure — Keep It Clean!
@@ -79,7 +81,7 @@ When creating new skills, always put them in `.claude/skills/` within this works
79
81
  Structure:
80
82
  - `.claude/skills/` — local agent skills
81
83
  - `memory/` — daily logs (YYYY-MM-DD.md)
82
- - `data/schedule.json` — scheduled events and reminders (hot-reloaded, no restart needed) (use schedule skill to modify)
84
+ - `data/schedule.json` — scheduled events and reminders (hot-reloaded, no restart needed) (use schedule-event skill to modify)
83
85
 
84
86
  ## Safety
85
87