macroclaw 0.34.0 → 0.36.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.34.0",
3
+ "version": "0.36.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@
32
32
  "citty": "^0.2.1",
33
33
  "cron-parser": "^5.5.0",
34
34
  "grammy": "^1.39.3",
35
+ "luxon": "^3.7.2",
35
36
  "openai": "^6.27.0",
36
37
  "pino": "^10.3.1",
37
38
  "pino-pretty": "^13.1.3",
@@ -41,6 +42,7 @@
41
42
  },
42
43
  "devDependencies": {
43
44
  "@biomejs/biome": "^2.4.6",
45
+ "@types/luxon": "^3.7.1",
44
46
  "bun-types": "^1.3.10",
45
47
  "dependency-cruiser": "^17.3.8",
46
48
  "typescript": "^5.9.3"
package/src/app.test.ts CHANGED
@@ -132,6 +132,8 @@ function makeConfig(overrides?: Partial<AppConfig>): AppConfig {
132
132
  botToken: "test-token",
133
133
  authorizedChatId: "12345",
134
134
  workspace: "/tmp/macroclaw-test-workspace",
135
+ model: "sonnet",
136
+ timezone: "UTC",
135
137
  settingsDir: tmpSettingsDir,
136
138
  claude: defaultMockClaude(),
137
139
  stt: mockStt(),
package/src/app.ts CHANGED
@@ -11,7 +11,8 @@ export interface AppConfig {
11
11
  botToken: string;
12
12
  authorizedChatId: string;
13
13
  workspace: string;
14
- model?: string;
14
+ model: string;
15
+ timezone: string;
15
16
  settingsDir?: string;
16
17
  claude?: Claude;
17
18
  stt?: SpeechToText;
@@ -29,6 +30,7 @@ export class App {
29
30
  this.#orchestrator = new Orchestrator({
30
31
  model: config.model,
31
32
  workspace: config.workspace,
33
+ timezone: config.timezone,
32
34
  settingsDir: config.settingsDir,
33
35
  claude: config.claude,
34
36
  healthCheckInterval: config.healthCheckInterval,
@@ -49,6 +51,7 @@ export class App {
49
51
  start() {
50
52
  log.info("Starting macroclaw...");
51
53
  const scheduler = new Scheduler(this.#config.workspace, {
54
+ timezone: this.#config.timezone,
52
55
  onJob: (name, prompt, model, missed) => this.#orchestrator.handleCron(name, prompt, model, missed),
53
56
  });
54
57
  scheduler.start();
@@ -491,5 +491,13 @@ describe("Claude factory", () => {
491
491
  expect(opts.stdout).toBe("pipe");
492
492
  expect(opts.stderr).toBe("pipe");
493
493
  });
494
+
495
+ it("merges envVars into the process environment", () => {
496
+ mockSpawn();
497
+ const claude = new Claude({ workspace: TEST_WORKSPACE, envVars: { TZ: "Europe/Prague" } });
498
+ claude.newSession(textResult);
499
+ const opts = spawnOpts();
500
+ expect((opts.env as Record<string, string>).TZ).toBe("Europe/Prague");
501
+ });
494
502
  });
495
503
  });
package/src/claude.ts CHANGED
@@ -18,6 +18,8 @@ export interface ClaudeConfig {
18
18
  workspace: string;
19
19
  model?: string;
20
20
  systemPrompt?: string;
21
+ /** Extra environment variables to set when spawning the Claude process */
22
+ envVars?: Record<string, string>;
21
23
  }
22
24
 
23
25
  /** Per-call overrides */
@@ -221,11 +223,13 @@ export class Claude {
221
223
  readonly #workspace: string;
222
224
  readonly #model?: string;
223
225
  readonly #systemPrompt?: string;
226
+ readonly #envVars: Record<string, string>;
224
227
 
225
228
  constructor(config: ClaudeConfig) {
226
229
  this.#workspace = config.workspace;
227
230
  this.#model = config.model;
228
231
  this.#systemPrompt = config.systemPrompt;
232
+ this.#envVars = config.envVars ?? {};
229
233
  }
230
234
 
231
235
  newSession<R extends ResultType>(resultType: R, options?: QueryOptions): ClaudeProcess<InferResult<R>> {
@@ -241,7 +245,7 @@ export class Claude {
241
245
  }
242
246
 
243
247
  #spawn<R extends ResultType>(mode: SessionMode, resultType: R, options?: QueryOptions): ClaudeProcess<InferResult<R>> {
244
- const env = { ...process.env };
248
+ const env = { ...process.env, ...this.#envVars };
245
249
  delete env.CLAUDECODE;
246
250
 
247
251
  const model = options?.model ?? this.#model;
package/src/cli.test.ts CHANGED
@@ -133,6 +133,7 @@ function mockService(overrides?: Record<string, unknown>): SystemServiceManager
133
133
  uninstall: mock(() => {}),
134
134
  start: mock(() => ""),
135
135
  stop: mock(() => {}),
136
+ restart: mock(() => ""),
136
137
  update: mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" })),
137
138
  isRunning: false,
138
139
  status: mock(() => ({ installed: false, running: false, platform: "systemd" as const })),
@@ -170,6 +171,13 @@ describe("Cli.service", () => {
170
171
  expect(stop).toHaveBeenCalled();
171
172
  });
172
173
 
174
+ it("runs restart action", () => {
175
+ const restart = mock(() => "tail -f /logs");
176
+ const cli = new Cli({ systemService: mockService({ restart }) });
177
+ cli.service("restart");
178
+ expect(restart).toHaveBeenCalled();
179
+ });
180
+
173
181
  it("runs update action — stops and starts when running", () => {
174
182
  const stop = mock(() => {});
175
183
  const start = mock(() => "tail -f /logs");
package/src/cli.ts CHANGED
@@ -75,6 +75,11 @@ export class Cli {
75
75
  console.log(`Service started. Check logs:\n ${logCmd}`);
76
76
  break;
77
77
  }
78
+ case "restart": {
79
+ const logCmd = this.#serviceManager.restart();
80
+ console.log(`Service restarted. Check logs:\n ${logCmd}`);
81
+ break;
82
+ }
78
83
  case "status": {
79
84
  const s = this.#serviceManager.status();
80
85
  const lines = [
@@ -173,6 +178,11 @@ const serviceStatusCommand = defineCommand({
173
178
  run: () => { try { defaultCli.service("status"); } catch (err) { handleError(err); } },
174
179
  });
175
180
 
181
+ const serviceRestartCommand = defineCommand({
182
+ meta: { name: "restart", description: "Restart the service" },
183
+ run: () => { try { defaultCli.service("restart"); } catch (err) { handleError(err); } },
184
+ });
185
+
176
186
  const serviceLogsCommand = defineCommand({
177
187
  meta: { name: "logs", description: "Print the command to view service logs" },
178
188
  args: {
@@ -188,6 +198,7 @@ const serviceCommand = defineCommand({
188
198
  uninstall: serviceUninstallCommand,
189
199
  start: serviceStartCommand,
190
200
  stop: serviceStopCommand,
201
+ restart: serviceRestartCommand,
191
202
  update: serviceUpdateCommand,
192
203
  status: serviceStatusCommand,
193
204
  logs: serviceLogsCommand,
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ export async function start(): Promise<void> {
40
40
  authorizedChatId: resolved.chatId,
41
41
  workspace,
42
42
  model: resolved.model,
43
+ timezone: resolved.timezone,
43
44
  stt: resolved.openaiApiKey ? new SpeechToText(resolved.openaiApiKey) : undefined,
44
45
  };
45
46
 
@@ -116,6 +116,8 @@ function makeOrchestrator(claude: Claude, extraConfig?: Partial<OrchestratorConf
116
116
  const onResponse = mock(async (r: OrchestratorResponse) => { responses.push(r); });
117
117
  const orch = new Orchestrator({
118
118
  workspace: TEST_WORKSPACE,
119
+ model: "sonnet",
120
+ timezone: "UTC",
119
121
  settingsDir: tmpSettingsDir,
120
122
  onResponse,
121
123
  claude,
@@ -737,7 +739,7 @@ describe("Orchestrator", () => {
737
739
  const detailResponse = responses[responses.length - 1];
738
740
  expect(detailResponse.message).toContain("research-pricing");
739
741
  expect(detailResponse.message).toContain("research pricing");
740
- expect(detailResponse.message).toContain("default");
742
+ expect(detailResponse.message).toContain("sonnet");
741
743
  expect(detailResponse.message).toContain("Status: running");
742
744
  expect(detailResponse.buttons).toHaveLength(3);
743
745
  expect(detailResponse.buttons![0]).toEqual({ text: "Peek", data: `peek:${sessionId}` });
@@ -1153,6 +1155,8 @@ describe("Orchestrator", () => {
1153
1155
  const failingOnResponse = mock(async (_r: OrchestratorResponse) => { throw new Error("send failed"); });
1154
1156
  const orch = new Orchestrator({
1155
1157
  workspace: TEST_WORKSPACE,
1158
+ model: "sonnet",
1159
+ timezone: "UTC",
1156
1160
  settingsDir: tmpSettingsDir,
1157
1161
  onResponse: failingOnResponse,
1158
1162
  claude,
@@ -10,17 +10,7 @@ import {
10
10
  import { writeHistoryPrompt, writeHistoryResult } from "./history";
11
11
  import { createLogger } from "./logger";
12
12
  import { generateName } from "./naming";
13
- import {
14
- backgroundAgentProgressEvent,
15
- backgroundAgentResultEvent,
16
- backgroundAgentStartEvent,
17
- buttonClickEvent,
18
- healthCheckEvent,
19
- peekEvent,
20
- SYSTEM_PROMPT,
21
- scheduleTriggerEvent,
22
- userMessageEvent,
23
- } from "./prompts";
13
+ import { PromptBuilder } from "./prompt-builder";
24
14
  import { Queue } from "./queue";
25
15
  import { loadSessions, saveSessions } from "./sessions";
26
16
 
@@ -100,8 +90,9 @@ interface SessionInfo {
100
90
  }
101
91
 
102
92
  export interface OrchestratorConfig {
103
- model?: string;
93
+ model: string;
104
94
  workspace: string;
95
+ timezone: string;
105
96
  settingsDir?: string;
106
97
  onResponse: (response: OrchestratorResponse) => Promise<void>;
107
98
  claude?: Claude;
@@ -116,6 +107,7 @@ export interface OrchestratorConfig {
116
107
  export class Orchestrator {
117
108
  #config: Omit<OrchestratorConfig , 'claude'>;
118
109
  #claude: Claude;
110
+ #prompts: PromptBuilder;
119
111
  #waitThreshold: number;
120
112
  #healthCheckInterval: number;
121
113
  #healthCheckTimeout: number;
@@ -127,7 +119,9 @@ export class Orchestrator {
127
119
 
128
120
  constructor(config: OrchestratorConfig) {
129
121
  this.#config = config;
130
- this.#claude = config.claude ?? new Claude({ workspace: config.workspace, systemPrompt: SYSTEM_PROMPT });
122
+ this.#prompts = new PromptBuilder(config.timezone);
123
+ const envVars: Record<string, string> = { TZ: config.timezone };
124
+ this.#claude = config.claude ?? new Claude({ workspace: config.workspace, systemPrompt: this.#prompts.systemPrompt, envVars });
131
125
  this.#waitThreshold = config.waitThreshold ?? WAIT_THRESHOLD;
132
126
  this.#healthCheckInterval = config.healthCheckInterval ?? HEALTH_CHECK_INTERVAL_MS;
133
127
  this.#healthCheckTimeout = config.healthCheckTimeout ?? HEALTH_CHECK_TIMEOUT_MS;
@@ -149,7 +143,7 @@ export class Orchestrator {
149
143
 
150
144
  handleCron(name: string, prompt: string, model?: string, missed?: { missedBy: string; scheduledAt: string }): void {
151
145
  const cronName = `cron-${name}`;
152
- const formatted = scheduleTriggerEvent(
146
+ const formatted = this.#prompts.scheduleTrigger(
153
147
  cronName,
154
148
  { name, missedBy: missed?.missedBy, scheduledAt: missed?.scheduledAt },
155
149
  prompt,
@@ -220,7 +214,7 @@ export class Orchestrator {
220
214
  this.#callOnResponse({ message: `Peeking at <b>${escapeHtml(session.name)}</b>...` });
221
215
 
222
216
  try {
223
- const prompt = peekEvent(
217
+ const prompt = this.#prompts.peek(
224
218
  `peek-${session.name}`,
225
219
  session.name,
226
220
  `Only consider progress since the "${session.name}" event. Brief status update: done, in progress, remaining. 2-3 sentences max, plain text.`,
@@ -438,9 +432,9 @@ export class Orchestrator {
438
432
  #formatPrompt(request: OrchestratorRequest, name: string, backgroundedEvent?: string): string {
439
433
  switch (request.type) {
440
434
  case "user":
441
- return userMessageEvent(name, request.message || "", { files: request.files, backgroundedEvent });
435
+ return this.#prompts.userMessage(name, request.message || "", { files: request.files, backgroundedEvent });
442
436
  case "background-agent-result":
443
- return backgroundAgentResultEvent(
437
+ return this.#prompts.backgroundAgentResult(
444
438
  name,
445
439
  request.name,
446
440
  { text: request.response.message || "[No output]", files: request.response.files },
@@ -448,7 +442,7 @@ export class Orchestrator {
448
442
  { backgroundedEvent },
449
443
  );
450
444
  case "background-agent-progress":
451
- return backgroundAgentProgressEvent(
445
+ return this.#prompts.backgroundAgentProgress(
452
446
  name,
453
447
  request.name,
454
448
  request.progress,
@@ -456,7 +450,7 @@ export class Orchestrator {
456
450
  { backgroundedEvent },
457
451
  );
458
452
  case "button":
459
- return buttonClickEvent(name, request.label, { backgroundedEvent });
453
+ return this.#prompts.buttonClick(name, request.label, { backgroundedEvent });
460
454
  }
461
455
  }
462
456
 
@@ -491,7 +485,7 @@ export class Orchestrator {
491
485
  // --- Background management ---
492
486
 
493
487
  #spawnBackground(name: string, prompt: string, model: string | undefined) {
494
- const formatted = backgroundAgentStartEvent(name, prompt);
488
+ const formatted = this.#prompts.backgroundAgentStart(name, prompt);
495
489
  this.#spawnBackgroundRaw(name, prompt, formatted, model);
496
490
  }
497
491
 
@@ -555,7 +549,7 @@ export class Orchestrator {
555
549
 
556
550
  log.debug({ name: info.name, sessionId }, "Running health check");
557
551
 
558
- const prompt = healthCheckEvent(
552
+ const prompt = this.#prompts.healthCheck(
559
553
  `health-check-${info.name}`,
560
554
  info.name,
561
555
  "Report your current status. If your task is complete, set finished=true and provide the full output. If still working, set finished=false and describe current progress in one sentence.",
@@ -1,75 +1,74 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import {
3
- backgroundAgentProgressEvent,
4
- backgroundAgentResultEvent,
5
- backgroundAgentStartEvent,
6
- buttonClickEvent,
7
- healthCheckEvent,
8
- peekEvent,
9
- SYSTEM_PROMPT,
10
- scheduleTriggerEvent,
11
- userMessageEvent,
12
- } from "./prompts";
13
-
14
- describe("SYSTEM_PROMPT", () => {
2
+ import { PromptBuilder } from "./prompt-builder";
3
+
4
+ const p = new PromptBuilder("UTC");
5
+
6
+ describe("systemPrompt", () => {
15
7
  it("contains key sections", () => {
16
- expect(SYSTEM_PROMPT).toContain("macroclaw");
17
- expect(SYSTEM_PROMPT).toContain("Structured output");
18
- expect(SYSTEM_PROMPT).toContain("Event format");
19
- expect(SYSTEM_PROMPT).toContain("Background agents");
20
- expect(SYSTEM_PROMPT).toContain("Cron");
21
- expect(SYSTEM_PROMPT).toContain("Buttons");
22
- expect(SYSTEM_PROMPT).toContain("Files");
23
- expect(SYSTEM_PROMPT).toContain("Session routing");
8
+ expect(p.systemPrompt).toContain("macroclaw");
9
+ expect(p.systemPrompt).toContain("Structured output");
10
+ expect(p.systemPrompt).toContain("Event format");
11
+ expect(p.systemPrompt).toContain("Background agents");
12
+ expect(p.systemPrompt).toContain("Cron");
13
+ expect(p.systemPrompt).toContain("Buttons");
14
+ expect(p.systemPrompt).toContain("Files");
15
+ expect(p.systemPrompt).toContain("Session routing");
24
16
  });
25
17
 
26
18
  it("contains HTML formatting instructions", () => {
27
- expect(SYSTEM_PROMPT).toContain("HTML parse mode");
28
- expect(SYSTEM_PROMPT).toContain("<b>");
19
+ expect(p.systemPrompt).toContain("HTML parse mode");
20
+ expect(p.systemPrompt).toContain("<b>");
29
21
  });
30
22
 
31
23
  it("documents all event types", () => {
32
- expect(SYSTEM_PROMPT).toContain("user-message");
33
- expect(SYSTEM_PROMPT).toContain("button-click");
34
- expect(SYSTEM_PROMPT).toContain("schedule-trigger");
35
- expect(SYSTEM_PROMPT).toContain("background-agent-start");
36
- expect(SYSTEM_PROMPT).toContain("background-agent-result");
37
- expect(SYSTEM_PROMPT).toContain("peek");
24
+ expect(p.systemPrompt).toContain("user-message");
25
+ expect(p.systemPrompt).toContain("button-click");
26
+ expect(p.systemPrompt).toContain("schedule-trigger");
27
+ expect(p.systemPrompt).toContain("background-agent-start");
28
+ expect(p.systemPrompt).toContain("background-agent-result");
29
+ expect(p.systemPrompt).toContain("peek");
38
30
  });
39
31
 
40
32
  it("documents backgrounded events", () => {
41
- expect(SYSTEM_PROMPT).toContain("backgrounded-event");
42
- expect(SYSTEM_PROMPT).toContain("moved to background");
43
- expect(SYSTEM_PROMPT).toContain("Do not re-execute");
33
+ expect(p.systemPrompt).toContain("backgrounded-event");
34
+ expect(p.systemPrompt).toContain("moved to background");
35
+ expect(p.systemPrompt).toContain("Do not re-execute");
44
36
  });
45
37
 
46
38
  it("contains structured output reinforcement", () => {
47
- expect(SYSTEM_PROMPT).toContain("StructuredOutput tool");
48
- expect(SYSTEM_PROMPT).toContain("actionReason");
39
+ expect(p.systemPrompt).toContain("StructuredOutput tool");
40
+ expect(p.systemPrompt).toContain("actionReason");
49
41
  });
50
42
 
51
43
  it("contains no personal names", () => {
52
- expect(SYSTEM_PROMPT).not.toContain("Alfread");
53
- expect(SYSTEM_PROMPT).not.toContain("Michal");
44
+ expect(p.systemPrompt).not.toContain("Alfread");
45
+ expect(p.systemPrompt).not.toContain("Michal");
54
46
  });
55
47
 
56
48
  it("documents background agent model options", () => {
57
- expect(SYSTEM_PROMPT).toContain("haiku");
58
- expect(SYSTEM_PROMPT).toContain("sonnet");
59
- expect(SYSTEM_PROMPT).toContain("opus");
49
+ expect(p.systemPrompt).toContain("haiku");
50
+ expect(p.systemPrompt).toContain("sonnet");
51
+ expect(p.systemPrompt).toContain("opus");
52
+ });
53
+
54
+ it("includes timezone but not a fixed date", () => {
55
+ const prague = new PromptBuilder("Europe/Prague");
56
+ expect(prague.systemPrompt).not.toContain("Current date:");
57
+ expect(prague.systemPrompt).toContain("Timezone: Europe/Prague");
58
+ expect(prague.systemPrompt).toContain("TZ env var is set");
60
59
  });
61
60
  });
62
61
 
63
- describe("userMessageEvent", () => {
64
- it("builds user message event", () => {
65
- const result = userMessageEvent("check-logs", "hello");
66
- expect(result).toStartWith('<event name="check-logs" type="user-message" session="main">');
62
+ describe("userMessage", () => {
63
+ it("builds user message event with time attribute", () => {
64
+ const result = p.userMessage("check-logs", "hello");
65
+ expect(result).toMatch(/^<event time="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}" name="check-logs" type="user-message" session="main">/);
67
66
  expect(result).toContain("<text>hello</text>");
68
67
  expect(result).toEndWith("</event>");
69
68
  });
70
69
 
71
70
  it("builds user message with files", () => {
72
- const result = userMessageEvent("analyze-photo", "what's in this image?", {
71
+ const result = p.userMessage("analyze-photo", "what's in this image?", {
73
72
  files: ["/tmp/photo.jpg", "/tmp/doc.pdf"],
74
73
  });
75
74
  expect(result).toContain("<text>what's in this image?</text>");
@@ -80,7 +79,7 @@ describe("userMessageEvent", () => {
80
79
  });
81
80
 
82
81
  it("builds user message with backgrounded event", () => {
83
- const result = userMessageEvent("check-logs", "check the logs", {
82
+ const result = p.userMessage("check-logs", "check the logs", {
84
83
  backgroundedEvent: "deploy-cluster",
85
84
  });
86
85
  expect(result).toContain('<backgrounded-event name="deploy-cluster" />');
@@ -88,7 +87,7 @@ describe("userMessageEvent", () => {
88
87
  });
89
88
 
90
89
  it("places backgrounded-event before text", () => {
91
- const result = userMessageEvent("check-logs", "hello", {
90
+ const result = p.userMessage("check-logs", "hello", {
92
91
  backgroundedEvent: "deploy",
93
92
  });
94
93
  const bgIdx = result.indexOf("backgrounded-event");
@@ -97,33 +96,33 @@ describe("userMessageEvent", () => {
97
96
  });
98
97
 
99
98
  it("escapes XML in text content", () => {
100
- const result = userMessageEvent("test", "a < b & c > d");
99
+ const result = p.userMessage("test", "a < b & c > d");
101
100
  expect(result).toContain("<text>a &lt; b &amp; c &gt; d</text>");
102
101
  });
103
102
 
104
103
  it("escapes XML in name attribute", () => {
105
- const result = userMessageEvent('a & "b"', "test");
104
+ const result = p.userMessage('a & "b"', "test");
106
105
  expect(result).toContain('name="a &amp; &quot;b&quot;"');
107
106
  });
108
107
 
109
108
  it("escapes XML in backgrounded event name", () => {
110
- const result = userMessageEvent("test", "hello", {
109
+ const result = p.userMessage("test", "hello", {
111
110
  backgroundedEvent: 'task & "stuff"',
112
111
  });
113
112
  expect(result).toContain('backgrounded-event name="task &amp; &quot;stuff&quot;"');
114
113
  });
115
114
  });
116
115
 
117
- describe("buttonClickEvent", () => {
116
+ describe("buttonClick", () => {
118
117
  it("builds button click event", () => {
119
- const result = buttonClickEvent("btn-yes", "Yes");
118
+ const result = p.buttonClick("btn-yes", "Yes");
120
119
  expect(result).toContain('type="button-click"');
121
120
  expect(result).toContain("<button>Yes</button>");
122
121
  expect(result).not.toContain("<text>");
123
122
  });
124
123
 
125
124
  it("builds button click with backgrounded event", () => {
126
- const result = buttonClickEvent("btn-yes", "Yes", {
125
+ const result = p.buttonClick("btn-yes", "Yes", {
127
126
  backgroundedEvent: "deploy-cluster",
128
127
  });
129
128
  expect(result).toContain('<backgrounded-event name="deploy-cluster" />');
@@ -131,14 +130,14 @@ describe("buttonClickEvent", () => {
131
130
  });
132
131
 
133
132
  it("escapes XML in button label", () => {
134
- const result = buttonClickEvent("btn", 'a & "b"');
133
+ const result = p.buttonClick("btn", 'a & "b"');
135
134
  expect(result).toContain("<button>a &amp; &quot;b&quot;</button>");
136
135
  });
137
136
  });
138
137
 
139
- describe("scheduleTriggerEvent", () => {
138
+ describe("scheduleTrigger", () => {
140
139
  it("builds schedule trigger event", () => {
141
- const result = scheduleTriggerEvent("cron-daily", { name: "daily" }, "check updates");
140
+ const result = p.scheduleTrigger("cron-daily", { name: "daily" }, "check updates");
142
141
  expect(result).toContain('type="schedule-trigger"');
143
142
  expect(result).toContain('session="background"');
144
143
  expect(result).toContain('<schedule name="daily" />');
@@ -146,7 +145,7 @@ describe("scheduleTriggerEvent", () => {
146
145
  });
147
146
 
148
147
  it("builds missed schedule trigger with attributes", () => {
149
- const result = scheduleTriggerEvent(
148
+ const result = p.scheduleTrigger(
150
149
  "cron-reminder",
151
150
  { name: "reminder", missedBy: "15m", scheduledAt: "2026-03-20T06:00:00Z" },
152
151
  "buy milk",
@@ -157,18 +156,18 @@ describe("scheduleTriggerEvent", () => {
157
156
  });
158
157
  });
159
158
 
160
- describe("backgroundAgentStartEvent", () => {
159
+ describe("backgroundAgentStart", () => {
161
160
  it("builds background agent start event", () => {
162
- const result = backgroundAgentStartEvent("research", "find papers about transformers");
161
+ const result = p.backgroundAgentStart("research", "find papers about transformers");
163
162
  expect(result).toContain('type="background-agent-start"');
164
163
  expect(result).toContain('session="background"');
165
164
  expect(result).toContain("<text>find papers about transformers</text>");
166
165
  });
167
166
  });
168
167
 
169
- describe("backgroundAgentResultEvent", () => {
168
+ describe("backgroundAgentResult", () => {
170
169
  it("builds background agent result (text only)", () => {
171
- const result = backgroundAgentResultEvent(
170
+ const result = p.backgroundAgentResult(
172
171
  "bg-research",
173
172
  "research",
174
173
  { text: "found 3 papers" },
@@ -183,7 +182,7 @@ describe("backgroundAgentResultEvent", () => {
183
182
  });
184
183
 
185
184
  it("builds background agent result with files", () => {
186
- const result = backgroundAgentResultEvent(
185
+ const result = p.backgroundAgentResult(
187
186
  "bg-research",
188
187
  "research",
189
188
  { text: "here are the screenshots", files: ["/tmp/screenshot.png"] },
@@ -196,7 +195,7 @@ describe("backgroundAgentResultEvent", () => {
196
195
  });
197
196
 
198
197
  it("includes instructions after result", () => {
199
- const result = backgroundAgentResultEvent(
198
+ const result = p.backgroundAgentResult(
200
199
  "bg-research",
201
200
  "research",
202
201
  { text: "done" },
@@ -210,9 +209,9 @@ describe("backgroundAgentResultEvent", () => {
210
209
  });
211
210
  });
212
211
 
213
- describe("backgroundAgentProgressEvent", () => {
212
+ describe("backgroundAgentProgress", () => {
214
213
  it("builds progress event with progress tag", () => {
215
- const result = backgroundAgentProgressEvent(
214
+ const result = p.backgroundAgentProgress(
216
215
  "progress-research",
217
216
  "research",
218
217
  "indexing 500 documents",
@@ -225,9 +224,9 @@ describe("backgroundAgentProgressEvent", () => {
225
224
  });
226
225
  });
227
226
 
228
- describe("peekEvent", () => {
227
+ describe("peek", () => {
229
228
  it("builds peek event with instructions", () => {
230
- const result = peekEvent("peek-deploy", "deploy", "Brief status update.");
229
+ const result = p.peek("peek-deploy", "deploy", "Brief status update.");
231
230
  expect(result).toContain('type="peek"');
232
231
  expect(result).toContain('<target-event name="deploy" />');
233
232
  expect(result).toContain("<instructions>Brief status update.</instructions>");
@@ -235,9 +234,9 @@ describe("peekEvent", () => {
235
234
  });
236
235
  });
237
236
 
238
- describe("healthCheckEvent", () => {
237
+ describe("healthCheck", () => {
239
238
  it("builds health check event with instructions", () => {
240
- const result = healthCheckEvent("health-check-deploy", "deploy", "Report status.");
239
+ const result = p.healthCheck("health-check-deploy", "deploy", "Report status.");
241
240
  expect(result).toContain('type="health-check"');
242
241
  expect(result).toContain('<target-event name="deploy" />');
243
242
  expect(result).toContain("<instructions>Report status.</instructions>");