@aaroncql/pim-agent 0.0.1 → 0.1.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.
Files changed (60) hide show
  1. package/README.md +19 -8
  2. package/bin/pim.ts +55 -3
  3. package/package.json +20 -5
  4. package/src/extensions/_init/index.ts +3 -2
  5. package/src/extensions/bash/capture.test.ts +0 -126
  6. package/src/extensions/bash/format.test.ts +0 -240
  7. package/src/extensions/bash/run.test.ts +0 -262
  8. package/src/extensions/command-picker/ranker.test.ts +0 -46
  9. package/src/extensions/edit/edit.test.ts +0 -285
  10. package/src/extensions/file-picker/catalog.test.ts +0 -263
  11. package/src/extensions/file-picker/index.test.ts +0 -168
  12. package/src/extensions/file-picker/ranker.test.ts +0 -94
  13. package/src/extensions/footer/git.test.ts +0 -76
  14. package/src/extensions/footer/index.test.ts +0 -161
  15. package/src/extensions/footer/segments.test.ts +0 -164
  16. package/src/extensions/glob/glob.test.ts +0 -171
  17. package/src/extensions/glob/index.test.ts +0 -68
  18. package/src/extensions/glob/render.test.ts +0 -126
  19. package/src/extensions/grep/grep.test.ts +0 -387
  20. package/src/extensions/grep/index.test.ts +0 -68
  21. package/src/extensions/grep/render.test.ts +0 -269
  22. package/src/extensions/read/read.test.ts +0 -177
  23. package/src/extensions/read/render.test.ts +0 -61
  24. package/src/extensions/subagent/index.test.ts +0 -44
  25. package/src/extensions/subagent/render.test.ts +0 -292
  26. package/src/extensions/subagent/subagent.test.ts +0 -315
  27. package/src/extensions/system-prompt/prompt.test.ts +0 -64
  28. package/src/extensions/todo/index.test.ts +0 -244
  29. package/src/extensions/todo/render.test.ts +0 -180
  30. package/src/extensions/todo/todo.test.ts +0 -222
  31. package/src/extensions/tps/index.test.ts +0 -254
  32. package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +0 -119
  33. package/src/extensions/web-fetch/fetch.test.ts +0 -244
  34. package/src/extensions/web-fetch/render.test.ts +0 -56
  35. package/src/extensions/web-search/ExaMcpClient.test.ts +0 -143
  36. package/src/extensions/web-search/render.test.ts +0 -21
  37. package/src/extensions/web-search/search.test.ts +0 -53
  38. package/src/extensions/working-indicator/index.test.ts +0 -21
  39. package/src/extensions/write/render.test.ts +0 -64
  40. package/src/extensions/write/write.test.ts +0 -108
  41. package/src/shared/DiffLines.test.ts +0 -193
  42. package/src/shared/DiffRenderer.test.ts +0 -206
  43. package/src/shared/EditMatcher.test.ts +0 -123
  44. package/src/shared/FileScanner.test.ts +0 -158
  45. package/src/shared/FuzzyMatcher.test.ts +0 -114
  46. package/src/shared/GitignoreFilter.test.ts +0 -64
  47. package/src/shared/Lines.test.ts +0 -25
  48. package/src/shared/McpClient.test.ts +0 -235
  49. package/src/shared/OutputBudget.test.ts +0 -99
  50. package/src/shared/Paths.test.ts +0 -51
  51. package/src/shared/PimSettings.test.ts +0 -90
  52. package/src/shared/Renderer.test.ts +0 -190
  53. package/src/shared/SpillCache.test.ts +0 -94
  54. package/src/shared/Tools.test.ts +0 -392
  55. package/src/telegram/Config.test.ts +0 -275
  56. package/src/telegram/Markdown.test.ts +0 -143
  57. package/src/telegram/Renderer.test.ts +0 -216
  58. package/src/telegram/SessionRegistry.test.ts +0 -89
  59. package/src/telegram/TaskScheduler.test.ts +0 -278
  60. package/src/telegram/TaskTool.test.ts +0 -179
@@ -1,89 +0,0 @@
1
- import type { Api } from "grammy";
2
- import { mkdtemp, rm } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
-
7
- import { Fs } from "../shared/Fs";
8
- import { type TelegramConfig } from "./Config";
9
- import { SessionRegistry } from "./SessionRegistry";
10
- import type { SessionSettings } from "./Session";
11
- import { TaskScheduler } from "./TaskScheduler";
12
-
13
- let tmp: string;
14
- let config: TelegramConfig;
15
- const stubApi = {} as Api;
16
- const stubScheduler = new TaskScheduler({
17
- configDir: "/tmp",
18
- runTask: async () => {},
19
- });
20
-
21
- beforeEach(async () => {
22
- tmp = await mkdtemp(join(tmpdir(), "pim-session-registry-test-"));
23
- config = {
24
- token: "token",
25
- allow: [],
26
- cwd: tmp,
27
- configDir: tmp,
28
- };
29
- });
30
-
31
- afterEach(async () => {
32
- await rm(tmp, { recursive: true, force: true });
33
- });
34
-
35
- async function readState(): Promise<Record<string, SessionSettings>> {
36
- return Fs.readJsonOrEmpty<Record<string, SessionSettings>>(
37
- join(tmp, "state.json"),
38
- {}
39
- );
40
- }
41
-
42
- async function writeState(
43
- state: Record<string, SessionSettings>
44
- ): Promise<void> {
45
- await Fs.writeAtomic(join(tmp, "state.json"), JSON.stringify(state, null, 2));
46
- }
47
-
48
- describe("SessionRegistry state", () => {
49
- test("loads persisted state and preserves it when mutating another session", async () => {
50
- await writeState({
51
- "1-main": {
52
- cwd: "/repo",
53
- cumulativeCost: 12.5,
54
- sessionPath: "/sessions/one.jsonl",
55
- },
56
- });
57
-
58
- const registry = new SessionRegistry(config, stubApi, stubScheduler);
59
- await registry.init();
60
- const session = registry.get({ chatId: 2, threadId: undefined });
61
- await session.setThinkingLevel("off");
62
-
63
- const loaded = await readState();
64
- expect(loaded["1-main"]).toEqual({
65
- cwd: "/repo",
66
- cumulativeCost: 12.5,
67
- sessionPath: "/sessions/one.jsonl",
68
- });
69
- expect(loaded["2-main"]?.thinkingLevel).toBe("off");
70
- });
71
-
72
- test("does not flush state when disposed before init", async () => {
73
- await writeState({
74
- "1-main": {
75
- cwd: "/repo",
76
- cumulativeCost: 12.5,
77
- },
78
- });
79
-
80
- const registry = new SessionRegistry(config, stubApi, stubScheduler);
81
- await registry.disposeAll();
82
-
83
- const loaded = await readState();
84
- expect(loaded["1-main"]).toEqual({
85
- cwd: "/repo",
86
- cumulativeCost: 12.5,
87
- });
88
- });
89
- });
@@ -1,278 +0,0 @@
1
- import { mkdtemp, readdir, rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import type { SessionId } from "./Session";
7
- import { TaskScheduler } from "./TaskScheduler";
8
- import type { ScheduledTask } from "./TaskSchema";
9
- import { TaskStore } from "./TaskStore";
10
-
11
- let tmp: string;
12
- const sessionId: SessionId = { chatId: 42, threadId: undefined };
13
-
14
- beforeEach(async () => {
15
- tmp = await mkdtemp(join(tmpdir(), "pim-scheduler-test-"));
16
- });
17
-
18
- afterEach(async () => {
19
- await rm(tmp, { recursive: true, force: true });
20
- });
21
-
22
- function makeScheduler(opts: {
23
- readonly now: () => number;
24
- readonly onFire?: (task: ScheduledTask) => Promise<void>;
25
- }): { readonly scheduler: TaskScheduler; readonly fired: ScheduledTask[] } {
26
- const fired: ScheduledTask[] = [];
27
- const scheduler = new TaskScheduler({
28
- configDir: tmp,
29
- runTask: async (task) => {
30
- fired.push(task);
31
- await opts.onFire?.(task);
32
- },
33
- now: opts.now,
34
- });
35
- return { scheduler, fired };
36
- }
37
-
38
- async function listTaskFiles(): Promise<string[]> {
39
- try {
40
- return await readdir(join(tmp, "tasks"));
41
- } catch {
42
- return [];
43
- }
44
- }
45
-
46
- describe("TaskScheduler.parseDuration", () => {
47
- test("parses simple units", () => {
48
- expect(TaskScheduler.parseDuration("30s")).toBe(30_000);
49
- expect(TaskScheduler.parseDuration("5m")).toBe(300_000);
50
- expect(TaskScheduler.parseDuration("2h")).toBe(7_200_000);
51
- expect(TaskScheduler.parseDuration("1d")).toBe(86_400_000);
52
- });
53
-
54
- test("parses compound durations", () => {
55
- expect(TaskScheduler.parseDuration("1h30m")).toBe(5_400_000);
56
- expect(TaskScheduler.parseDuration("2h15m30s")).toBe(8_130_000);
57
- });
58
-
59
- test("rejects garbage", () => {
60
- expect(() => TaskScheduler.parseDuration("")).toThrow();
61
- expect(() => TaskScheduler.parseDuration("forever")).toThrow();
62
- expect(() => TaskScheduler.parseDuration("10")).toThrow();
63
- });
64
- });
65
-
66
- describe("TaskScheduler.tick", () => {
67
- test("fires due active task and reschedules interval", async () => {
68
- const t0 = Date.parse("2026-05-14T12:00:00Z");
69
- const { scheduler, fired } = makeScheduler({ now: () => t0 });
70
- const task = await scheduler.create(sessionId, {
71
- prompt: "test",
72
- schedule: { type: "interval", every: "30m" },
73
- });
74
- expect(task.nextRun).toBe(new Date(t0 + 30 * 60_000).toISOString());
75
-
76
- // Not yet due
77
- await scheduler.tick();
78
- expect(fired).toHaveLength(0);
79
-
80
- // 30 min later, due
81
- const t1 = t0 + 30 * 60_000 + 5_000;
82
- const later = makeScheduler({ now: () => t1 });
83
- await later.scheduler.tick();
84
- expect(later.fired).toHaveLength(1);
85
- expect(later.fired[0]!.id).toBe(task.id);
86
-
87
- const reloaded = (await TaskStore.loadAll(tmp))[0]!;
88
- expect(reloaded.nextRun).toBe(new Date(t1 + 30 * 60_000).toISOString());
89
- });
90
-
91
- test("skips paused tasks", async () => {
92
- const t0 = Date.parse("2026-05-14T12:00:00Z");
93
- const { scheduler } = makeScheduler({ now: () => t0 });
94
- const task = await scheduler.create(sessionId, {
95
- prompt: "test",
96
- schedule: { type: "interval", every: "5m" },
97
- });
98
- await scheduler.setStatus(sessionId, task.id, "paused");
99
-
100
- const t1 = t0 + 6 * 60_000;
101
- const { scheduler: s2, fired } = makeScheduler({ now: () => t1 });
102
- await s2.tick();
103
- expect(fired).toHaveLength(0);
104
-
105
- // File still exists
106
- expect(await listTaskFiles()).toHaveLength(1);
107
- });
108
-
109
- test("once task is deleted after firing", async () => {
110
- const t0 = Date.parse("2026-05-14T12:00:00Z");
111
- const { scheduler } = makeScheduler({ now: () => t0 });
112
- const at = new Date(t0 + 60_000).toISOString();
113
- await scheduler.create(sessionId, {
114
- prompt: "test",
115
- schedule: { type: "once", at },
116
- });
117
-
118
- const t1 = t0 + 90_000;
119
- const { scheduler: s2, fired } = makeScheduler({ now: () => t1 });
120
- await s2.tick();
121
- expect(fired).toHaveLength(1);
122
- expect(await listTaskFiles()).toHaveLength(0);
123
- });
124
-
125
- test("missed >24h is advanced silently without firing", async () => {
126
- const t0 = Date.parse("2026-05-14T12:00:00Z");
127
- // Manually write a task whose nextRun is 26h in the past
128
- const stale: ScheduledTask = {
129
- id: "stale-task",
130
- prompt: "test",
131
- chatId: sessionId.chatId,
132
- threadId: sessionId.threadId,
133
- schedule: { type: "interval", every: "1h" },
134
- status: "active",
135
- nextRun: new Date(t0 - 26 * 3600_000).toISOString(),
136
- expires: null,
137
- isolatedSession: false,
138
- createdAt: new Date(t0 - 30 * 3600_000).toISOString(),
139
- };
140
- await TaskStore.save(tmp, stale);
141
-
142
- const { scheduler, fired } = makeScheduler({ now: () => t0 });
143
- await scheduler.tick();
144
- expect(fired).toHaveLength(0);
145
-
146
- const reloaded = (await TaskStore.loadAll(tmp))[0]!;
147
- expect(Date.parse(reloaded.nextRun)).toBeGreaterThan(t0);
148
- });
149
-
150
- test("missed <24h fires once", async () => {
151
- const t0 = Date.parse("2026-05-14T12:00:00Z");
152
- const stale: ScheduledTask = {
153
- id: "recent-miss",
154
- prompt: "test",
155
- chatId: sessionId.chatId,
156
- threadId: sessionId.threadId,
157
- schedule: { type: "interval", every: "1h" },
158
- status: "active",
159
- nextRun: new Date(t0 - 2 * 3600_000).toISOString(),
160
- expires: null,
161
- isolatedSession: false,
162
- createdAt: new Date(t0 - 5 * 3600_000).toISOString(),
163
- };
164
- await TaskStore.save(tmp, stale);
165
-
166
- const { scheduler, fired } = makeScheduler({ now: () => t0 });
167
- await scheduler.tick();
168
- expect(fired).toHaveLength(1);
169
- });
170
-
171
- test("expired task is deleted without firing", async () => {
172
- const t0 = Date.parse("2026-05-14T12:00:00Z");
173
- const expired: ScheduledTask = {
174
- id: "expired",
175
- prompt: "test",
176
- chatId: sessionId.chatId,
177
- threadId: sessionId.threadId,
178
- schedule: { type: "interval", every: "1h" },
179
- status: "active",
180
- nextRun: new Date(t0 - 60_000).toISOString(),
181
- expires: new Date(t0 - 30_000).toISOString(),
182
- isolatedSession: false,
183
- createdAt: new Date(t0 - 2 * 3600_000).toISOString(),
184
- };
185
- await TaskStore.save(tmp, expired);
186
-
187
- const { scheduler, fired } = makeScheduler({ now: () => t0 });
188
- await scheduler.tick();
189
- expect(fired).toHaveLength(0);
190
- expect(await listTaskFiles()).toHaveLength(0);
191
- });
192
-
193
- test("cron task next-run advances via Bun.cron.parse", async () => {
194
- const t0 = Date.parse("2026-05-14T12:00:00Z");
195
- const { scheduler } = makeScheduler({ now: () => t0 });
196
- const task = await scheduler.create(sessionId, {
197
- prompt: "test",
198
- schedule: { type: "cron", expr: "0 * * * *" },
199
- });
200
- expect(task.nextRun).toBe(new Date(t0 + 3600_000).toISOString());
201
-
202
- // Fire it
203
- const fireTime = t0 + 3600_000 + 30_000;
204
- const { scheduler: s2, fired } = makeScheduler({ now: () => fireTime });
205
- await s2.tick();
206
- expect(fired).toHaveLength(1);
207
-
208
- const reloaded = (await TaskStore.loadAll(tmp))[0]!;
209
- // Should advance to next top-of-hour after firing
210
- expect(Date.parse(reloaded.nextRun)).toBeGreaterThan(fireTime);
211
- });
212
- });
213
-
214
- describe("TaskScheduler.create validation", () => {
215
- test("rejects 'once' with past timestamp", async () => {
216
- const t0 = Date.parse("2026-05-14T12:00:00Z");
217
- const { scheduler } = makeScheduler({ now: () => t0 });
218
- await expect(
219
- scheduler.create(sessionId, {
220
- prompt: "test",
221
- schedule: { type: "once", at: new Date(t0 - 1000).toISOString() },
222
- })
223
- ).rejects.toThrow();
224
- });
225
-
226
- test("rejects sub-minute interval", async () => {
227
- const t0 = Date.parse("2026-05-14T12:00:00Z");
228
- const { scheduler } = makeScheduler({ now: () => t0 });
229
- await expect(
230
- scheduler.create(sessionId, {
231
- prompt: "test",
232
- schedule: { type: "interval", every: "30s" },
233
- })
234
- ).rejects.toThrow();
235
- });
236
-
237
- test("rejects expires before first nextRun", async () => {
238
- const t0 = Date.parse("2026-05-14T12:00:00Z");
239
- const { scheduler } = makeScheduler({ now: () => t0 });
240
- await expect(
241
- scheduler.create(sessionId, {
242
- prompt: "test",
243
- schedule: { type: "interval", every: "1h" },
244
- expires: new Date(t0 + 5 * 60_000).toISOString(),
245
- })
246
- ).rejects.toThrow();
247
- });
248
- });
249
-
250
- describe("TaskScheduler.list", () => {
251
- test("filters by chat/thread", async () => {
252
- const t0 = Date.parse("2026-05-14T12:00:00Z");
253
- const { scheduler } = makeScheduler({ now: () => t0 });
254
- await scheduler.create(
255
- { chatId: 1, threadId: undefined },
256
- { prompt: "a", schedule: { type: "interval", every: "1h" } }
257
- );
258
- await scheduler.create(
259
- { chatId: 1, threadId: 99 },
260
- { prompt: "b", schedule: { type: "interval", every: "1h" } }
261
- );
262
- await scheduler.create(
263
- { chatId: 2, threadId: undefined },
264
- { prompt: "c", schedule: { type: "interval", every: "1h" } }
265
- );
266
-
267
- const main = await scheduler.list({
268
- chatId: 1,
269
- threadId: undefined,
270
- });
271
- expect(main).toHaveLength(1);
272
- expect(main[0]!.prompt).toBe("a");
273
-
274
- const threaded = await scheduler.list({ chatId: 1, threadId: 99 });
275
- expect(threaded).toHaveLength(1);
276
- expect(threaded[0]!.prompt).toBe("b");
277
- });
278
- });
@@ -1,179 +0,0 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import type { SessionId } from "./Session";
7
- import { TaskScheduler } from "./TaskScheduler";
8
- import { TaskTool } from "./TaskTool";
9
- import type { ScheduledTask, TaskToolInput } from "./TaskSchema";
10
-
11
- let tmp: string;
12
- const sessionId: SessionId = { chatId: 7, threadId: undefined };
13
- const otherSessionId: SessionId = { chatId: 7, threadId: 99 };
14
-
15
- beforeEach(async () => {
16
- tmp = await mkdtemp(join(tmpdir(), "pim-task-tool-test-"));
17
- });
18
-
19
- afterEach(async () => {
20
- await rm(tmp, { recursive: true, force: true });
21
- });
22
-
23
- function makeTool(opts?: { readonly now?: () => number }): {
24
- readonly run: (
25
- input: TaskToolInput
26
- ) => Promise<{ readonly text: string; readonly details: unknown }>;
27
- readonly scheduler: TaskScheduler;
28
- } {
29
- const scheduler = new TaskScheduler({
30
- configDir: tmp,
31
- runTask: async () => {},
32
- now: opts?.now ?? ((): number => Date.now()),
33
- });
34
- const tool = TaskTool.build({ scheduler, sessionId });
35
- const run = async (
36
- input: TaskToolInput
37
- ): Promise<{ readonly text: string; readonly details: unknown }> => {
38
- const result = await tool.execute(
39
- "test-call-id",
40
- input,
41
- new AbortController().signal,
42
- undefined,
43
- // ExtensionContext stub — tool doesn't use it
44
- {} as never
45
- );
46
- return {
47
- text: result.content
48
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
49
- .map((c) => c.text)
50
- .join("\n"),
51
- details: result.details,
52
- };
53
- };
54
- return { run, scheduler };
55
- }
56
-
57
- describe("task tool: create", () => {
58
- test("creates an interval task and returns id + nextRun", async () => {
59
- const t0 = Date.parse("2026-05-14T12:00:00Z");
60
- const { run } = makeTool({ now: () => t0 });
61
- const result = await run({
62
- action: "create",
63
- prompt: "drink water",
64
- schedule: { type: "interval", every: "1h" },
65
- });
66
- expect(result.text).toMatch(/^Created task /);
67
- const details = result.details as ScheduledTask;
68
- expect(details.prompt).toBe("drink water");
69
- expect(details.nextRun).toBe(new Date(t0 + 3600_000).toISOString());
70
- });
71
-
72
- test("rejects sub-minute interval", async () => {
73
- const { run } = makeTool();
74
- await expect(
75
- run({
76
- action: "create",
77
- prompt: "spam",
78
- schedule: { type: "interval", every: "10s" },
79
- })
80
- ).rejects.toThrow(/at least 1 minute/);
81
- });
82
-
83
- test("rejects invalid cron", async () => {
84
- const { run } = makeTool();
85
- await expect(
86
- run({
87
- action: "create",
88
- prompt: "test",
89
- schedule: { type: "cron", expr: "not a cron" },
90
- })
91
- ).rejects.toThrow();
92
- });
93
-
94
- test("requires prompt and schedule", async () => {
95
- const { run } = makeTool();
96
- await expect(run({ action: "create" })).rejects.toThrow(/prompt/);
97
- await expect(run({ action: "create", prompt: "hi" })).rejects.toThrow(
98
- /schedule/
99
- );
100
- });
101
- });
102
-
103
- describe("task tool: list/delete/pause/resume", () => {
104
- test("list filters to current sessionId", async () => {
105
- const t0 = Date.parse("2026-05-14T12:00:00Z");
106
- const { run, scheduler } = makeTool({ now: () => t0 });
107
- await run({
108
- action: "create",
109
- prompt: "mine",
110
- schedule: { type: "interval", every: "1h" },
111
- });
112
- await scheduler.create(otherSessionId, {
113
- prompt: "not mine",
114
- schedule: { type: "interval", every: "1h" },
115
- });
116
- const result = await run({ action: "list" });
117
- const details = result.details as { readonly tasks: ScheduledTask[] };
118
- expect(details.tasks).toHaveLength(1);
119
- expect(details.tasks[0]!.prompt).toBe("mine");
120
- });
121
-
122
- test("delete removes by id", async () => {
123
- const t0 = Date.parse("2026-05-14T12:00:00Z");
124
- const { run } = makeTool({ now: () => t0 });
125
- const created = await run({
126
- action: "create",
127
- prompt: "soon-gone",
128
- schedule: { type: "interval", every: "1h" },
129
- });
130
- const id = (created.details as ScheduledTask).id;
131
- await run({ action: "delete", id });
132
- const list = await run({ action: "list" });
133
- expect((list.details as { tasks: ScheduledTask[] }).tasks).toHaveLength(0);
134
- });
135
-
136
- test("delete refuses cross-session id", async () => {
137
- const t0 = Date.parse("2026-05-14T12:00:00Z");
138
- const { run, scheduler } = makeTool({ now: () => t0 });
139
- const foreign = await scheduler.create(otherSessionId, {
140
- prompt: "not yours",
141
- schedule: { type: "interval", every: "1h" },
142
- });
143
- await expect(run({ action: "delete", id: foreign.id })).rejects.toThrow(
144
- /no task/
145
- );
146
- });
147
-
148
- test("pause flips status; resume undoes it", async () => {
149
- const t0 = Date.parse("2026-05-14T12:00:00Z");
150
- const { run } = makeTool({ now: () => t0 });
151
- const created = await run({
152
- action: "create",
153
- prompt: "p",
154
- schedule: { type: "interval", every: "1h" },
155
- });
156
- const id = (created.details as ScheduledTask).id;
157
-
158
- const paused = await run({ action: "pause", id });
159
- expect((paused.details as ScheduledTask).status).toBe("paused");
160
-
161
- const resumed = await run({ action: "resume", id });
162
- expect((resumed.details as ScheduledTask).status).toBe("active");
163
- });
164
- });
165
-
166
- describe("task tool: update_prompt", () => {
167
- test("changes prompt text", async () => {
168
- const t0 = Date.parse("2026-05-14T12:00:00Z");
169
- const { run } = makeTool({ now: () => t0 });
170
- const created = await run({
171
- action: "create",
172
- prompt: "old",
173
- schedule: { type: "interval", every: "1h" },
174
- });
175
- const id = (created.details as ScheduledTask).id;
176
- const updated = await run({ action: "update_prompt", id, prompt: "new" });
177
- expect((updated.details as ScheduledTask).prompt).toBe("new");
178
- });
179
- });