@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,198 @@
|
|
|
1
|
+
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Bot as Grammy } from "grammy";
|
|
3
|
+
|
|
4
|
+
import { Commands } from "./Commands";
|
|
5
|
+
import type { TelegramConfig } from "./Config";
|
|
6
|
+
import { Message, type Prompt } from "./Message";
|
|
7
|
+
import { Renderer, type TurnEndState } from "./Renderer";
|
|
8
|
+
import { Session, type SessionId } from "./Session";
|
|
9
|
+
import { SessionRegistry } from "./SessionRegistry";
|
|
10
|
+
import { Supervisor } from "./Supervisor";
|
|
11
|
+
import type { ScheduledTask } from "./TaskSchema";
|
|
12
|
+
import { TaskScheduler } from "./TaskScheduler";
|
|
13
|
+
|
|
14
|
+
export class Bot {
|
|
15
|
+
private readonly grammy: Grammy;
|
|
16
|
+
private readonly allowSet: ReadonlySet<number>;
|
|
17
|
+
private readonly registry: SessionRegistry;
|
|
18
|
+
private readonly scheduler: TaskScheduler;
|
|
19
|
+
private readonly config: TelegramConfig;
|
|
20
|
+
private readonly commands: Commands;
|
|
21
|
+
|
|
22
|
+
public constructor(config: TelegramConfig) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.grammy = new Grammy(config.token);
|
|
25
|
+
this.allowSet = new Set(config.allow);
|
|
26
|
+
this.scheduler = new TaskScheduler({
|
|
27
|
+
configDir: config.configDir,
|
|
28
|
+
runTask: (task) => this.runScheduledTask(task),
|
|
29
|
+
});
|
|
30
|
+
this.registry = new SessionRegistry(
|
|
31
|
+
config,
|
|
32
|
+
this.grammy.api,
|
|
33
|
+
this.scheduler
|
|
34
|
+
);
|
|
35
|
+
this.commands = new Commands(config, this.grammy.api, this.registry);
|
|
36
|
+
|
|
37
|
+
this.grammy.on("callback_query:data", async (ctx) => {
|
|
38
|
+
if (!this.allowSet.has(ctx.chat?.id ?? -1)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await this.commands.handleCallback(ctx);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.grammy.on("message", async (ctx) => {
|
|
45
|
+
const chatId = ctx.chat.id;
|
|
46
|
+
if (!this.allowSet.has(chatId)) {
|
|
47
|
+
console.log(`[recv] reject chatId=${chatId} (not in allow-list)`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const sessionId: SessionId = {
|
|
51
|
+
chatId,
|
|
52
|
+
threadId: ctx.message.message_thread_id,
|
|
53
|
+
};
|
|
54
|
+
let prompt: Prompt | undefined;
|
|
55
|
+
try {
|
|
56
|
+
prompt = await Message.toPrompt(
|
|
57
|
+
ctx,
|
|
58
|
+
this.config.token,
|
|
59
|
+
this.config.configDir,
|
|
60
|
+
sessionId
|
|
61
|
+
);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const msg = (err as Error).message ?? String(err);
|
|
64
|
+
console.error(`[recv] attachment download failed:`, err);
|
|
65
|
+
await this.grammy.api
|
|
66
|
+
.sendMessage(sessionId.chatId, `⚠️ ${msg}`, {
|
|
67
|
+
message_thread_id: sessionId.threadId,
|
|
68
|
+
link_preview_options: { is_disabled: true },
|
|
69
|
+
})
|
|
70
|
+
.catch((e) => console.error(`[send] plain failed:`, e));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!prompt) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const preview = prompt.text.slice(0, 120).replace(/\s+/g, " ");
|
|
77
|
+
console.log(
|
|
78
|
+
`[recv] chatId=${chatId} threadId=${sessionId.threadId ?? "main"} ${preview}`
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const session = this.registry.get(sessionId);
|
|
82
|
+
if (prompt.text.startsWith("/") && !prompt.options.images?.length) {
|
|
83
|
+
await this.commands.handleCommand(ctx, session, prompt.text);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void session.run(
|
|
88
|
+
(agent) => this.handleTurn(session, agent, prompt),
|
|
89
|
+
session.temporary ? { isolated: true } : undefined
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.grammy.catch((err) => {
|
|
94
|
+
console.error("[bot] handler error:", err.error);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async start(): Promise<void> {
|
|
99
|
+
await this.registry.init();
|
|
100
|
+
await this.grammy.init();
|
|
101
|
+
await this.processBootUpdateConfirm();
|
|
102
|
+
await this.grammy.api.deleteWebhook({ drop_pending_updates: true });
|
|
103
|
+
const username = this.grammy.botInfo.username;
|
|
104
|
+
this.registry.setBotUsername(username);
|
|
105
|
+
console.log(`bot @${username} ready`);
|
|
106
|
+
await this.scheduler.start();
|
|
107
|
+
await this.grammy.start();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public async stop(): Promise<void> {
|
|
111
|
+
await this.scheduler.stop();
|
|
112
|
+
await this.grammy.stop();
|
|
113
|
+
await this.registry.disposeAll();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async runScheduledTask(task: ScheduledTask): Promise<void> {
|
|
117
|
+
const sessionId: SessionId = {
|
|
118
|
+
chatId: task.chatId,
|
|
119
|
+
threadId: task.threadId,
|
|
120
|
+
};
|
|
121
|
+
const prompt: Prompt = { text: task.prompt, options: {} };
|
|
122
|
+
const session = this.registry.get(sessionId);
|
|
123
|
+
await session.run(
|
|
124
|
+
(agent) => this.handleTurn(session, agent, prompt),
|
|
125
|
+
task.isolatedSession ? { isolated: true } : undefined
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async processBootUpdateConfirm(): Promise<void> {
|
|
130
|
+
const entries = await Supervisor.readUpdateConfirm(this.config.configDir);
|
|
131
|
+
if (entries.length === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const version = await Supervisor.readVersion();
|
|
135
|
+
const text = `✅ Pim Agent updated to v${version}!`;
|
|
136
|
+
await Promise.all(
|
|
137
|
+
entries.map((e) =>
|
|
138
|
+
this.grammy.api
|
|
139
|
+
.editMessageText(e.chatId, e.messageId, text, {
|
|
140
|
+
link_preview_options: { is_disabled: true },
|
|
141
|
+
})
|
|
142
|
+
.catch((err) => console.warn(`[update-confirm] edit failed:`, err))
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
await Supervisor.clearUpdateConfirm(this.config.configDir);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async handleTurn(
|
|
149
|
+
session: Session,
|
|
150
|
+
agent: AgentSession,
|
|
151
|
+
prompt: Prompt
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
const renderer = new Renderer(session, this.grammy.api);
|
|
154
|
+
const unsubscribe = agent.subscribe((event) => renderer.handleEvent(event));
|
|
155
|
+
await renderer.start();
|
|
156
|
+
try {
|
|
157
|
+
await agent.prompt(prompt.text, {
|
|
158
|
+
...prompt.options,
|
|
159
|
+
source: "rpc",
|
|
160
|
+
});
|
|
161
|
+
const final = Bot.extractFinalResult(agent);
|
|
162
|
+
await renderer.finish(final.text, final.state);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const msg = (err as Error).message ?? String(err);
|
|
165
|
+
console.error(`[bot] turn failed:`, err);
|
|
166
|
+
await renderer.finish(`⚠️ ${msg}`, "error");
|
|
167
|
+
} finally {
|
|
168
|
+
unsubscribe();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static extractFinalResult(agent: AgentSession): {
|
|
173
|
+
readonly text: string;
|
|
174
|
+
readonly state: TurnEndState;
|
|
175
|
+
} {
|
|
176
|
+
const messages = agent.messages;
|
|
177
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
178
|
+
const msg = messages[i]!;
|
|
179
|
+
if (msg.role !== "assistant") {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (msg.stopReason === "error") {
|
|
183
|
+
return { text: msg.errorMessage ?? "", state: "error" };
|
|
184
|
+
}
|
|
185
|
+
if (msg.stopReason === "aborted") {
|
|
186
|
+
return { text: msg.errorMessage ?? "", state: "cancelled" };
|
|
187
|
+
}
|
|
188
|
+
const parts: string[] = [];
|
|
189
|
+
for (const block of msg.content) {
|
|
190
|
+
if (block.type === "text") {
|
|
191
|
+
parts.push(block.text);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { text: parts.join("").trim(), state: "ok" };
|
|
195
|
+
}
|
|
196
|
+
return { text: "", state: "ok" };
|
|
197
|
+
}
|
|
198
|
+
}
|