@aaroncql/pim-agent 0.0.1
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/LICENSE +21 -0
- package/README.md +212 -0
- package/bin/pim.ts +109 -0
- package/package.json +49 -0
- package/src/extensions/_init/index.ts +109 -0
- package/src/extensions/bash/capture.test.ts +126 -0
- package/src/extensions/bash/capture.ts +80 -0
- package/src/extensions/bash/format.test.ts +240 -0
- package/src/extensions/bash/format.ts +76 -0
- package/src/extensions/bash/index.ts +86 -0
- package/src/extensions/bash/run.test.ts +262 -0
- package/src/extensions/bash/run.ts +207 -0
- package/src/extensions/bash/schema.ts +54 -0
- package/src/extensions/command-picker/index.ts +52 -0
- package/src/extensions/command-picker/ranker.test.ts +46 -0
- package/src/extensions/command-picker/ranker.ts +17 -0
- package/src/extensions/edit/edit.test.ts +285 -0
- package/src/extensions/edit/edit.ts +382 -0
- package/src/extensions/edit/index.ts +54 -0
- package/src/extensions/edit/schema.ts +37 -0
- package/src/extensions/file-picker/catalog.test.ts +263 -0
- package/src/extensions/file-picker/catalog.ts +219 -0
- package/src/extensions/file-picker/index.test.ts +168 -0
- package/src/extensions/file-picker/index.ts +119 -0
- package/src/extensions/file-picker/ranker.test.ts +94 -0
- package/src/extensions/file-picker/ranker.ts +76 -0
- package/src/extensions/footer/git.test.ts +76 -0
- package/src/extensions/footer/git.ts +87 -0
- package/src/extensions/footer/index.test.ts +161 -0
- package/src/extensions/footer/index.ts +148 -0
- package/src/extensions/footer/powerline.ts +87 -0
- package/src/extensions/footer/segments.test.ts +164 -0
- package/src/extensions/footer/segments.ts +234 -0
- package/src/extensions/glob/glob.test.ts +171 -0
- package/src/extensions/glob/glob.ts +34 -0
- package/src/extensions/glob/index.test.ts +68 -0
- package/src/extensions/glob/index.ts +136 -0
- package/src/extensions/glob/render.test.ts +126 -0
- package/src/extensions/glob/render.ts +74 -0
- package/src/extensions/glob/schema.ts +52 -0
- package/src/extensions/grep/grep.test.ts +387 -0
- package/src/extensions/grep/grep.ts +215 -0
- package/src/extensions/grep/index.test.ts +68 -0
- package/src/extensions/grep/index.ts +158 -0
- package/src/extensions/grep/render.test.ts +269 -0
- package/src/extensions/grep/render.ts +243 -0
- package/src/extensions/grep/schema.ts +92 -0
- package/src/extensions/read/index.ts +84 -0
- package/src/extensions/read/read.test.ts +177 -0
- package/src/extensions/read/read.ts +206 -0
- package/src/extensions/read/render.test.ts +61 -0
- package/src/extensions/read/render.ts +33 -0
- package/src/extensions/read/schema.ts +27 -0
- package/src/extensions/subagent/index.test.ts +44 -0
- package/src/extensions/subagent/index.ts +30 -0
- package/src/extensions/subagent/render.test.ts +292 -0
- package/src/extensions/subagent/render.ts +359 -0
- package/src/extensions/subagent/schema.ts +9 -0
- package/src/extensions/subagent/subagent.test.ts +315 -0
- package/src/extensions/subagent/subagent.ts +418 -0
- package/src/extensions/system-prompt/index.ts +28 -0
- package/src/extensions/system-prompt/prompt.test.ts +64 -0
- package/src/extensions/system-prompt/prompt.ts +213 -0
- package/src/extensions/todo/index.test.ts +244 -0
- package/src/extensions/todo/index.ts +122 -0
- package/src/extensions/todo/render.test.ts +180 -0
- package/src/extensions/todo/render.ts +172 -0
- package/src/extensions/todo/schema.ts +24 -0
- package/src/extensions/todo/todo.test.ts +222 -0
- package/src/extensions/todo/todo.ts +188 -0
- package/src/extensions/tps/index.test.ts +254 -0
- package/src/extensions/tps/index.ts +136 -0
- package/src/extensions/web-fetch/JinaReaderClient.ts +230 -0
- package/src/extensions/web-fetch/WebViewFetchClient.ts +186 -0
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +119 -0
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +511 -0
- package/src/extensions/web-fetch/fetch.test.ts +244 -0
- package/src/extensions/web-fetch/fetch.ts +249 -0
- package/src/extensions/web-fetch/index.ts +107 -0
- package/src/extensions/web-fetch/render.test.ts +56 -0
- package/src/extensions/web-fetch/render.ts +39 -0
- package/src/extensions/web-fetch/schema.ts +23 -0
- package/src/extensions/web-search/ExaMcpClient.test.ts +143 -0
- package/src/extensions/web-search/ExaMcpClient.ts +258 -0
- package/src/extensions/web-search/index.ts +118 -0
- package/src/extensions/web-search/render.test.ts +21 -0
- package/src/extensions/web-search/render.ts +9 -0
- package/src/extensions/web-search/schema.ts +21 -0
- package/src/extensions/web-search/search.test.ts +53 -0
- package/src/extensions/web-search/search.ts +23 -0
- package/src/extensions/working-indicator/index.test.ts +21 -0
- package/src/extensions/working-indicator/index.ts +77 -0
- package/src/extensions/write/index.ts +76 -0
- package/src/extensions/write/render.test.ts +64 -0
- package/src/extensions/write/schema.ts +14 -0
- package/src/extensions/write/write.test.ts +108 -0
- package/src/extensions/write/write.ts +104 -0
- package/src/shared/DiffLines.test.ts +193 -0
- package/src/shared/DiffLines.ts +307 -0
- package/src/shared/DiffRenderer.test.ts +206 -0
- package/src/shared/DiffRenderer.ts +396 -0
- package/src/shared/DiffView.ts +199 -0
- package/src/shared/EditMatcher.test.ts +123 -0
- package/src/shared/EditMatcher.ts +826 -0
- package/src/shared/FileScanner.test.ts +158 -0
- package/src/shared/FileScanner.ts +41 -0
- package/src/shared/Fs.ts +46 -0
- package/src/shared/FsErrors.ts +72 -0
- package/src/shared/FuzzyMatcher.test.ts +114 -0
- package/src/shared/FuzzyMatcher.ts +73 -0
- package/src/shared/GitignoreFilter.test.ts +64 -0
- package/src/shared/GitignoreFilter.ts +142 -0
- package/src/shared/GlobExclusions.ts +23 -0
- package/src/shared/Levenshtein.ts +33 -0
- package/src/shared/Lines.test.ts +25 -0
- package/src/shared/Lines.ts +77 -0
- package/src/shared/McpClient.test.ts +235 -0
- package/src/shared/McpClient.ts +406 -0
- package/src/shared/OutputBudget.test.ts +99 -0
- package/src/shared/OutputBudget.ts +79 -0
- package/src/shared/Paths.test.ts +51 -0
- package/src/shared/Paths.ts +52 -0
- package/src/shared/PimSettings.test.ts +90 -0
- package/src/shared/PimSettings.ts +124 -0
- package/src/shared/Renderer.test.ts +190 -0
- package/src/shared/Renderer.ts +256 -0
- package/src/shared/SpillCache.test.ts +94 -0
- package/src/shared/SpillCache.ts +89 -0
- package/src/shared/Tools.test.ts +392 -0
- package/src/shared/Tools.ts +636 -0
- package/src/telegram/Bot.ts +198 -0
- package/src/telegram/Commands.ts +721 -0
- package/src/telegram/Config.test.ts +275 -0
- package/src/telegram/Config.ts +162 -0
- package/src/telegram/Markdown.test.ts +143 -0
- package/src/telegram/Markdown.ts +177 -0
- package/src/telegram/Message.ts +211 -0
- package/src/telegram/Renderer.test.ts +216 -0
- package/src/telegram/Renderer.ts +713 -0
- package/src/telegram/SendFileSchema.ts +19 -0
- package/src/telegram/SendFileTool.ts +94 -0
- package/src/telegram/Session.ts +579 -0
- package/src/telegram/SessionRegistry.test.ts +89 -0
- package/src/telegram/SessionRegistry.ts +170 -0
- package/src/telegram/Supervisor.ts +357 -0
- package/src/telegram/TaskScheduler.test.ts +278 -0
- package/src/telegram/TaskScheduler.ts +293 -0
- package/src/telegram/TaskSchema.ts +88 -0
- package/src/telegram/TaskStore.ts +73 -0
- package/src/telegram/TaskTool.test.ts +179 -0
- package/src/telegram/TaskTool.ts +159 -0
- package/src/telegram/TypingIndicator.ts +43 -0
- package/src/telegram/index.ts +32 -0
- package/src/themes/pim-dark.json +84 -0
- package/src/themes/pim-light.json +84 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readdir, unlink } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { Fs } from "../shared/Fs";
|
|
5
|
+
import type { ScheduledTask } from "./TaskSchema";
|
|
6
|
+
|
|
7
|
+
export class TaskStore {
|
|
8
|
+
public static async loadAll(
|
|
9
|
+
configDir: string
|
|
10
|
+
): Promise<ReadonlyArray<ScheduledTask>> {
|
|
11
|
+
let entries: string[];
|
|
12
|
+
try {
|
|
13
|
+
entries = await readdir(TaskStore.dir(configDir));
|
|
14
|
+
} catch (err) {
|
|
15
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
const loaded = await Promise.all(
|
|
21
|
+
entries
|
|
22
|
+
.filter((name) => name.endsWith(".json"))
|
|
23
|
+
.map((name) =>
|
|
24
|
+
Fs.readJsonOrEmpty<ScheduledTask | undefined>(
|
|
25
|
+
join(TaskStore.dir(configDir), name),
|
|
26
|
+
undefined
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
return loaded.filter(
|
|
31
|
+
(data): data is ScheduledTask =>
|
|
32
|
+
!!data && typeof data === "object" && "id" in data
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static async save(
|
|
37
|
+
configDir: string,
|
|
38
|
+
task: ScheduledTask
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
await Fs.writeAtomic(
|
|
41
|
+
TaskStore.path(configDir, task.id),
|
|
42
|
+
JSON.stringify(task, null, 2)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static async delete(configDir: string, id: string): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
await unlink(TaskStore.path(configDir, id));
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static makeId(prompt: string): string {
|
|
57
|
+
const slug = prompt
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
60
|
+
.replace(/^-+|-+$/g, "")
|
|
61
|
+
.slice(0, 40);
|
|
62
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
63
|
+
return slug ? `${slug}-${rand}` : rand;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static dir(configDir: string): string {
|
|
67
|
+
return join(configDir, "tasks");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static path(configDir: string, id: string): string {
|
|
71
|
+
return join(TaskStore.dir(configDir), `${id}.json`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineTool,
|
|
3
|
+
type AgentToolResult,
|
|
4
|
+
type ToolDefinition,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
import type { SessionId } from "./Session";
|
|
8
|
+
import type { TaskScheduler } from "./TaskScheduler";
|
|
9
|
+
import { taskToolSchema, type TaskToolInput } from "./TaskSchema";
|
|
10
|
+
|
|
11
|
+
export type TaskToolDeps = {
|
|
12
|
+
readonly scheduler: TaskScheduler;
|
|
13
|
+
readonly sessionId: SessionId;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class TaskTool {
|
|
17
|
+
public static build(deps: TaskToolDeps): ToolDefinition {
|
|
18
|
+
return defineTool({
|
|
19
|
+
name: "task",
|
|
20
|
+
label: "task",
|
|
21
|
+
description:
|
|
22
|
+
"Manage scheduled/recurring tasks for this Telegram chat/thread.",
|
|
23
|
+
parameters: taskToolSchema,
|
|
24
|
+
async execute(_id, params) {
|
|
25
|
+
const input = params as TaskToolInput;
|
|
26
|
+
switch (input.action) {
|
|
27
|
+
case "create":
|
|
28
|
+
return await TaskTool.create(deps, input);
|
|
29
|
+
case "list":
|
|
30
|
+
return await TaskTool.list(deps);
|
|
31
|
+
case "delete":
|
|
32
|
+
return await TaskTool.delete(deps, input);
|
|
33
|
+
case "pause":
|
|
34
|
+
case "resume":
|
|
35
|
+
return await TaskTool.setStatus(deps, input);
|
|
36
|
+
case "update_prompt":
|
|
37
|
+
return await TaskTool.updatePrompt(deps, input);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static async create(
|
|
44
|
+
deps: TaskToolDeps,
|
|
45
|
+
input: TaskToolInput
|
|
46
|
+
): Promise<AgentToolResult<unknown>> {
|
|
47
|
+
if (!input.prompt) {
|
|
48
|
+
throw new Error("'prompt' is required for action=create");
|
|
49
|
+
}
|
|
50
|
+
if (!input.schedule) {
|
|
51
|
+
throw new Error("'schedule' is required for action=create");
|
|
52
|
+
}
|
|
53
|
+
const task = await deps.scheduler.create(deps.sessionId, {
|
|
54
|
+
prompt: input.prompt,
|
|
55
|
+
schedule: input.schedule,
|
|
56
|
+
expires: input.expires,
|
|
57
|
+
isolatedSession: input.isolatedSession,
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text" as const,
|
|
63
|
+
text: `Created task ${task.id}. Next run: ${task.nextRun}.`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
details: task,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static async list(
|
|
71
|
+
deps: TaskToolDeps
|
|
72
|
+
): Promise<AgentToolResult<unknown>> {
|
|
73
|
+
const tasks = await deps.scheduler.list(deps.sessionId);
|
|
74
|
+
if (tasks.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: "text" as const,
|
|
79
|
+
text: "No tasks scheduled for this thread.",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
details: { tasks: [] },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text" as const, text: JSON.stringify(tasks) }],
|
|
87
|
+
details: { tasks },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private static async delete(
|
|
92
|
+
deps: TaskToolDeps,
|
|
93
|
+
input: TaskToolInput
|
|
94
|
+
): Promise<AgentToolResult<unknown>> {
|
|
95
|
+
if (!input.id) {
|
|
96
|
+
throw new Error("'id' is required for action=delete");
|
|
97
|
+
}
|
|
98
|
+
const ok = await deps.scheduler.delete(deps.sessionId, input.id);
|
|
99
|
+
if (!ok) {
|
|
100
|
+
throw new Error(`no task with id=${input.id} in this thread`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text" as const, text: `Deleted task ${input.id}.` }],
|
|
104
|
+
details: { id: input.id },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private static async setStatus(
|
|
109
|
+
deps: TaskToolDeps,
|
|
110
|
+
input: TaskToolInput
|
|
111
|
+
): Promise<AgentToolResult<unknown>> {
|
|
112
|
+
if (!input.id) {
|
|
113
|
+
throw new Error(`'id' is required for action=${input.action}`);
|
|
114
|
+
}
|
|
115
|
+
const target = input.action === "pause" ? "paused" : "active";
|
|
116
|
+
const task = await deps.scheduler.setStatus(
|
|
117
|
+
deps.sessionId,
|
|
118
|
+
input.id,
|
|
119
|
+
target
|
|
120
|
+
);
|
|
121
|
+
if (!task) {
|
|
122
|
+
throw new Error(`no task with id=${input.id} in this thread`);
|
|
123
|
+
}
|
|
124
|
+
const text =
|
|
125
|
+
input.action === "pause"
|
|
126
|
+
? `Paused task ${task.id}. Will not fire until resumed.`
|
|
127
|
+
: `Resumed task ${task.id}. Next run: ${task.nextRun}.`;
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text" as const, text }],
|
|
130
|
+
details: task,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private static async updatePrompt(
|
|
135
|
+
deps: TaskToolDeps,
|
|
136
|
+
input: TaskToolInput
|
|
137
|
+
): Promise<AgentToolResult<unknown>> {
|
|
138
|
+
if (!input.id) {
|
|
139
|
+
throw new Error("'id' is required for action=update_prompt");
|
|
140
|
+
}
|
|
141
|
+
if (!input.prompt) {
|
|
142
|
+
throw new Error("'prompt' is required for action=update_prompt");
|
|
143
|
+
}
|
|
144
|
+
const task = await deps.scheduler.updatePrompt(
|
|
145
|
+
deps.sessionId,
|
|
146
|
+
input.id,
|
|
147
|
+
input.prompt
|
|
148
|
+
);
|
|
149
|
+
if (!task) {
|
|
150
|
+
throw new Error(`no task with id=${input.id} in this thread`);
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{ type: "text" as const, text: `Updated prompt for task ${task.id}.` },
|
|
155
|
+
],
|
|
156
|
+
details: task,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Api } from "grammy";
|
|
2
|
+
|
|
3
|
+
import type { SessionId } from "./Session";
|
|
4
|
+
|
|
5
|
+
export class TypingIndicator {
|
|
6
|
+
private readonly api: Api;
|
|
7
|
+
private readonly sessionId: SessionId;
|
|
8
|
+
private timer: Timer | undefined;
|
|
9
|
+
private stopped = true;
|
|
10
|
+
|
|
11
|
+
public constructor(api: Api, sessionId: SessionId) {
|
|
12
|
+
this.api = api;
|
|
13
|
+
this.sessionId = sessionId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public start(): void {
|
|
17
|
+
if (this.timer) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.stopped = false;
|
|
21
|
+
void this.send();
|
|
22
|
+
this.timer = setInterval(() => void this.send(), 4_000);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public stop(): void {
|
|
26
|
+
this.stopped = true;
|
|
27
|
+
if (this.timer) {
|
|
28
|
+
clearInterval(this.timer);
|
|
29
|
+
this.timer = undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async send(): Promise<void> {
|
|
34
|
+
if (this.stopped) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await this.api
|
|
38
|
+
.sendChatAction(this.sessionId.chatId, "typing", {
|
|
39
|
+
message_thread_id: this.sessionId.threadId,
|
|
40
|
+
})
|
|
41
|
+
.catch(() => {});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Bot } from "./Bot";
|
|
2
|
+
import { Config } from "./Config";
|
|
3
|
+
|
|
4
|
+
export async function start(args: ReadonlyArray<string>): Promise<void> {
|
|
5
|
+
const cli = Config.parseArgs(args);
|
|
6
|
+
const config = await Config.load(cli);
|
|
7
|
+
|
|
8
|
+
if (cli.printConfig) {
|
|
9
|
+
console.log(
|
|
10
|
+
JSON.stringify(
|
|
11
|
+
{ ...config, token: config.token ? "***" : undefined },
|
|
12
|
+
null,
|
|
13
|
+
2
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const bot = new Bot(config);
|
|
20
|
+
let stopped = false;
|
|
21
|
+
const stop = (): void => {
|
|
22
|
+
if (stopped) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
stopped = true;
|
|
26
|
+
void bot.stop();
|
|
27
|
+
};
|
|
28
|
+
process.once("SIGINT", stop);
|
|
29
|
+
process.once("SIGTERM", stop);
|
|
30
|
+
|
|
31
|
+
await bot.start();
|
|
32
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "pim-dark",
|
|
4
|
+
"vars": {
|
|
5
|
+
"black": 0,
|
|
6
|
+
"red": 1,
|
|
7
|
+
"green": 2,
|
|
8
|
+
"yellow": 3,
|
|
9
|
+
"blue": 4,
|
|
10
|
+
"magenta": 5,
|
|
11
|
+
"cyan": 6,
|
|
12
|
+
"white": 7,
|
|
13
|
+
"gray": 8,
|
|
14
|
+
"brightRed": 9,
|
|
15
|
+
"brightGreen": 10,
|
|
16
|
+
"brightYellow": 11,
|
|
17
|
+
"brightBlue": 12,
|
|
18
|
+
"brightMagenta": 13,
|
|
19
|
+
"brightCyan": 14,
|
|
20
|
+
"brightWhite": 15,
|
|
21
|
+
|
|
22
|
+
"panelBg": "#343541"
|
|
23
|
+
},
|
|
24
|
+
"colors": {
|
|
25
|
+
"accent": "cyan",
|
|
26
|
+
"border": "gray",
|
|
27
|
+
"borderAccent": "cyan",
|
|
28
|
+
"borderMuted": "gray",
|
|
29
|
+
"success": "green",
|
|
30
|
+
"error": "red",
|
|
31
|
+
"warning": "yellow",
|
|
32
|
+
"muted": "gray",
|
|
33
|
+
"dim": "gray",
|
|
34
|
+
"text": "white",
|
|
35
|
+
"thinkingText": "gray",
|
|
36
|
+
|
|
37
|
+
"selectedBg": "panelBg",
|
|
38
|
+
"userMessageBg": "panelBg",
|
|
39
|
+
"userMessageText": "",
|
|
40
|
+
"customMessageBg": "panelBg",
|
|
41
|
+
"customMessageText": "",
|
|
42
|
+
"customMessageLabel": "magenta",
|
|
43
|
+
|
|
44
|
+
"toolPendingBg": "",
|
|
45
|
+
"toolSuccessBg": "",
|
|
46
|
+
"toolErrorBg": "",
|
|
47
|
+
"toolTitle": "",
|
|
48
|
+
"toolOutput": "gray",
|
|
49
|
+
|
|
50
|
+
"mdHeading": "brightBlue",
|
|
51
|
+
"mdLink": "",
|
|
52
|
+
"mdLinkUrl": "brightCyan",
|
|
53
|
+
"mdCode": "brightMagenta",
|
|
54
|
+
"mdCodeBlock": "",
|
|
55
|
+
"mdCodeBlockBorder": "gray",
|
|
56
|
+
"mdQuote": "brightYellow",
|
|
57
|
+
"mdQuoteBorder": "brightYellow",
|
|
58
|
+
"mdHr": "gray",
|
|
59
|
+
"mdListBullet": "gray",
|
|
60
|
+
|
|
61
|
+
"toolDiffAdded": "green",
|
|
62
|
+
"toolDiffRemoved": "red",
|
|
63
|
+
"toolDiffContext": "gray",
|
|
64
|
+
|
|
65
|
+
"syntaxComment": "gray",
|
|
66
|
+
"syntaxKeyword": "magenta",
|
|
67
|
+
"syntaxFunction": "green",
|
|
68
|
+
"syntaxVariable": "magenta",
|
|
69
|
+
"syntaxString": "yellow",
|
|
70
|
+
"syntaxNumber": "blue",
|
|
71
|
+
"syntaxType": "cyan",
|
|
72
|
+
"syntaxOperator": "",
|
|
73
|
+
"syntaxPunctuation": "",
|
|
74
|
+
|
|
75
|
+
"thinkingOff": "gray",
|
|
76
|
+
"thinkingMinimal": "gray",
|
|
77
|
+
"thinkingLow": "gray",
|
|
78
|
+
"thinkingMedium": "gray",
|
|
79
|
+
"thinkingHigh": "gray",
|
|
80
|
+
"thinkingXhigh": "gray",
|
|
81
|
+
|
|
82
|
+
"bashMode": "yellow"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "pim-light",
|
|
4
|
+
"vars": {
|
|
5
|
+
"black": 0,
|
|
6
|
+
"red": 1,
|
|
7
|
+
"green": 2,
|
|
8
|
+
"yellow": 3,
|
|
9
|
+
"blue": 4,
|
|
10
|
+
"magenta": 5,
|
|
11
|
+
"cyan": 6,
|
|
12
|
+
"white": 7,
|
|
13
|
+
"gray": 8,
|
|
14
|
+
"brightRed": 9,
|
|
15
|
+
"brightGreen": 10,
|
|
16
|
+
"brightYellow": 11,
|
|
17
|
+
"brightBlue": 12,
|
|
18
|
+
"brightMagenta": 13,
|
|
19
|
+
"brightCyan": 14,
|
|
20
|
+
"brightWhite": 15,
|
|
21
|
+
|
|
22
|
+
"panelBg": "#e8e8e8"
|
|
23
|
+
},
|
|
24
|
+
"colors": {
|
|
25
|
+
"accent": "blue",
|
|
26
|
+
"border": "gray",
|
|
27
|
+
"borderAccent": "blue",
|
|
28
|
+
"borderMuted": "gray",
|
|
29
|
+
"success": "green",
|
|
30
|
+
"error": "red",
|
|
31
|
+
"warning": "yellow",
|
|
32
|
+
"muted": "gray",
|
|
33
|
+
"dim": "gray",
|
|
34
|
+
"text": "black",
|
|
35
|
+
"thinkingText": "gray",
|
|
36
|
+
|
|
37
|
+
"selectedBg": "panelBg",
|
|
38
|
+
"userMessageBg": "panelBg",
|
|
39
|
+
"userMessageText": "",
|
|
40
|
+
"customMessageBg": "panelBg",
|
|
41
|
+
"customMessageText": "",
|
|
42
|
+
"customMessageLabel": "magenta",
|
|
43
|
+
|
|
44
|
+
"toolPendingBg": "",
|
|
45
|
+
"toolSuccessBg": "",
|
|
46
|
+
"toolErrorBg": "",
|
|
47
|
+
"toolTitle": "",
|
|
48
|
+
"toolOutput": "gray",
|
|
49
|
+
|
|
50
|
+
"mdHeading": "blue",
|
|
51
|
+
"mdLink": "",
|
|
52
|
+
"mdLinkUrl": "cyan",
|
|
53
|
+
"mdCode": "magenta",
|
|
54
|
+
"mdCodeBlock": "",
|
|
55
|
+
"mdCodeBlockBorder": "gray",
|
|
56
|
+
"mdQuote": "yellow",
|
|
57
|
+
"mdQuoteBorder": "yellow",
|
|
58
|
+
"mdHr": "gray",
|
|
59
|
+
"mdListBullet": "gray",
|
|
60
|
+
|
|
61
|
+
"toolDiffAdded": "green",
|
|
62
|
+
"toolDiffRemoved": "red",
|
|
63
|
+
"toolDiffContext": "gray",
|
|
64
|
+
|
|
65
|
+
"syntaxComment": "gray",
|
|
66
|
+
"syntaxKeyword": "magenta",
|
|
67
|
+
"syntaxFunction": "green",
|
|
68
|
+
"syntaxVariable": "magenta",
|
|
69
|
+
"syntaxString": "yellow",
|
|
70
|
+
"syntaxNumber": "blue",
|
|
71
|
+
"syntaxType": "cyan",
|
|
72
|
+
"syntaxOperator": "",
|
|
73
|
+
"syntaxPunctuation": "",
|
|
74
|
+
|
|
75
|
+
"thinkingOff": "gray",
|
|
76
|
+
"thinkingMinimal": "gray",
|
|
77
|
+
"thinkingLow": "gray",
|
|
78
|
+
"thinkingMedium": "gray",
|
|
79
|
+
"thinkingHigh": "gray",
|
|
80
|
+
"thinkingXhigh": "gray",
|
|
81
|
+
|
|
82
|
+
"bashMode": "yellow"
|
|
83
|
+
}
|
|
84
|
+
}
|