arisa 3.0.12 → 3.1.2
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/AGENTS.md +11 -0
- package/README.md +37 -0
- package/docs/async-event-queue-flow.md +68 -0
- package/package.json +9 -10
- package/pnpm-workspace.yaml +12 -0
- package/src/core/agent/agent-manager.js +137 -28
- package/src/core/agent/runtime-context.js +2 -2
- package/src/core/artifacts/artifact-store.js +50 -25
- package/src/core/artifacts/normalize-for-reasoning.js +90 -0
- package/src/core/tasks/task-store.js +169 -0
- package/src/core/tools/daemon-runtime.js +167 -0
- package/src/core/tools/tool-config.js +15 -7
- package/src/core/tools/tool-registry.js +25 -10
- package/src/index.js +105 -12
- package/src/runtime/bootstrap.js +211 -19
- package/src/runtime/create-app.js +48 -4
- package/src/runtime/paths.js +27 -3
- package/src/runtime/service-manager.js +2 -2
- package/src/transport/telegram/bot.js +190 -77
- package/src/transport/telegram/media.js +17 -11
- package/tools/openai-transcribe/index.js +1 -1
- package/tools/schedule-agent-task/config.js +1 -0
- package/tools/schedule-agent-task/index.js +68 -0
- package/tools/schedule-agent-task/package.json +6 -0
- package/tools/schedule-agent-task/tool.manifest.json +8 -0
- package/tools/web-browser/index.js +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Bot, InputFile } from "grammy";
|
|
1
|
+
import { Bot, InputFile, webhookCallback } from "grammy";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { authorizeChat } from "./auth.js";
|
|
4
4
|
import { captureIncomingArtifact } from "./media.js";
|
|
5
5
|
import { renderTelegramHtml } from "./text-format.js";
|
|
6
|
+
import { normalizeArtifactForReasoning } from "../../core/artifacts/normalize-for-reasoning.js";
|
|
6
7
|
|
|
7
8
|
function quotedMessageSummary(message) {
|
|
8
9
|
if (!message) return [];
|
|
@@ -32,17 +33,28 @@ function quotedMessageSummary(message) {
|
|
|
32
33
|
return parts;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function getTelegramCommand(ctx) {
|
|
37
|
+
const text = ctx.message?.text || "";
|
|
38
|
+
const entity = ctx.message?.entities?.[0];
|
|
39
|
+
if (entity?.type !== "bot_command" || entity.offset !== 0 || !text.startsWith("/")) return "";
|
|
40
|
+
return text.slice(1, entity.length).split("@")[0].trim().toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getIncomingMessageText(message) {
|
|
44
|
+
return message?.text || message?.caption || "";
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
function buildPrompt({ ctx, artifact, transcript, toolResult }) {
|
|
36
48
|
const parts = [
|
|
37
|
-
`
|
|
49
|
+
`Incoming Telegram message.`,
|
|
38
50
|
`chatId: ${ctx.chat.id}`,
|
|
39
51
|
`userId: ${ctx.from.id}`,
|
|
40
52
|
`username: ${ctx.from.username || "(no username)"}`,
|
|
41
|
-
`messageId: ${ctx.msg.message_id}
|
|
42
|
-
`preferredTelegramLanguageCode: ${ctx.from?.language_code || "unknown"}`
|
|
53
|
+
`messageId: ${ctx.msg.message_id}`
|
|
43
54
|
];
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
const messageText = getIncomingMessageText(ctx.message);
|
|
57
|
+
if (messageText) parts.push(`text: ${messageText}`);
|
|
46
58
|
parts.push(...quotedMessageSummary(ctx.message?.reply_to_message));
|
|
47
59
|
if (artifact?.path) parts.push(`artifactPath: ${artifact.path}`);
|
|
48
60
|
if (artifact?.id) parts.push(`artifactId: ${artifact.id}`);
|
|
@@ -64,32 +76,70 @@ function buildPrompt({ ctx, artifact, transcript, toolResult }) {
|
|
|
64
76
|
return parts.join("\n");
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
function buildNewSessionPrompt(ctx) {
|
|
80
|
+
return [
|
|
81
|
+
"System event: /new requested.",
|
|
82
|
+
"Session was reset.",
|
|
83
|
+
`preferredTelegramLanguageCode: ${ctx.from?.language_code || "unknown"}`,
|
|
84
|
+
"Reply with a brief, warm confirmation in the user's language."
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
88
|
+
async function buildAsyncTaskPrompt({ task, artifactStore, toolRegistry, logger }) {
|
|
89
|
+
const parts = [
|
|
90
|
+
"Scheduled task fired.",
|
|
91
|
+
`taskId: ${task.id}`,
|
|
92
|
+
`chatId: ${task.payload.chatId}`,
|
|
93
|
+
task.payload.prompt ? `text: ${task.payload.prompt}` : null
|
|
94
|
+
];
|
|
77
95
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
if (task.payload.artifactId) {
|
|
97
|
+
const chatArtifactStore = artifactStore.forChat(task.payload.chatId);
|
|
98
|
+
const artifact = await chatArtifactStore.get(task.payload.artifactId);
|
|
99
|
+
if (artifact) {
|
|
100
|
+
parts.push(`artifactPath: ${artifact.path || ""}`);
|
|
101
|
+
parts.push(`artifactId: ${artifact.id}`);
|
|
102
|
+
parts.push(`mimeType: ${artifact.mimeType}`);
|
|
103
|
+
parts.push(`kind: ${artifact.kind}`);
|
|
104
|
+
|
|
105
|
+
const { normalizedArtifact, toolResult } = await normalizeArtifactForReasoning({
|
|
106
|
+
artifact,
|
|
107
|
+
desiredMimeType: "text/plain",
|
|
108
|
+
toolRegistry,
|
|
109
|
+
chatArtifactStore,
|
|
110
|
+
chatId: task.payload.chatId
|
|
111
|
+
});
|
|
81
112
|
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
if (normalizedArtifact) {
|
|
114
|
+
logger?.log("tasks", `artifact ${artifact.id} normalized to ${normalizedArtifact.id}`);
|
|
115
|
+
parts.push(`transcriptArtifactId: ${normalizedArtifact.id}`);
|
|
116
|
+
parts.push(`transcriptText: ${normalizedArtifact.text}`);
|
|
117
|
+
parts.push("Important: the attached audio artifact has already been normalized for reasoning. Use the transcript as the message content.");
|
|
118
|
+
} else if (artifact.kind === "audio" && toolResult) {
|
|
119
|
+
parts.push(`audioNormalizationResult: ${JSON.stringify(toolResult)}`);
|
|
120
|
+
parts.push("Important: pre-reasoning audio normalization could not be completed, so you do not have a transcript for this audio artifact.");
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
parts.push(`artifactId: ${task.payload.artifactId}`);
|
|
124
|
+
parts.push("Important: referenced artifact was not found.");
|
|
125
|
+
}
|
|
84
126
|
}
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
128
|
+
parts.push("Treat this as a new request for the chat and fulfill it now.");
|
|
129
|
+
parts.push("If needed, use tools.");
|
|
130
|
+
return parts.filter(Boolean).join("\n");
|
|
131
|
+
}
|
|
91
132
|
|
|
92
|
-
|
|
133
|
+
async function normalizeIncomingArtifact({ artifact, toolRegistry, chatArtifactStore, chatId }) {
|
|
134
|
+
if (!artifact) return { transcript: null, toolResult: null };
|
|
135
|
+
const { normalizedArtifact, toolResult } = await normalizeArtifactForReasoning({
|
|
136
|
+
artifact,
|
|
137
|
+
desiredMimeType: "text/plain",
|
|
138
|
+
toolRegistry,
|
|
139
|
+
chatArtifactStore,
|
|
140
|
+
chatId
|
|
141
|
+
});
|
|
142
|
+
return { transcript: normalizedArtifact, toolResult };
|
|
93
143
|
}
|
|
94
144
|
|
|
95
145
|
async function collectText(session, prompt) {
|
|
@@ -117,8 +167,8 @@ async function withTyping(ctx, work) {
|
|
|
117
167
|
}
|
|
118
168
|
}
|
|
119
169
|
|
|
120
|
-
export async function createTelegramBot({ config, artifactStore, toolRegistry, agentManager, saveConfig, updateConfig, logger }) {
|
|
121
|
-
const bot = new Bot(config.telegram.
|
|
170
|
+
export async function createTelegramBot({ config, artifactStore, toolRegistry, taskStore, agentManager, saveConfig, updateConfig, logger, webhookUrl, setHttpRequestHandler }) {
|
|
171
|
+
const bot = new Bot(config.telegram.token);
|
|
122
172
|
const perChatState = new Map();
|
|
123
173
|
|
|
124
174
|
function getIncomingChatMeta(ctx) {
|
|
@@ -138,10 +188,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
138
188
|
}
|
|
139
189
|
|
|
140
190
|
async function buildIncomingPrompt(ctx) {
|
|
141
|
-
|
|
191
|
+
const chatId = ctx.chat.id;
|
|
192
|
+
logger?.log("telegram", `message ${ctx.msg.message_id} in chat ${chatId}`);
|
|
193
|
+
const chatArtifactStore = artifactStore.forChat(chatId);
|
|
142
194
|
const artifact = await captureIncomingArtifact(ctx, artifactStore);
|
|
143
195
|
if (artifact) logger?.log("telegram", `captured artifact ${artifact.kind}${artifact.id ? ` ${artifact.id}` : ""}`);
|
|
144
|
-
const { transcript, toolResult } = await
|
|
196
|
+
const { transcript, toolResult } = await normalizeIncomingArtifact({ artifact, toolRegistry, chatArtifactStore, chatId });
|
|
145
197
|
if (transcript) logger?.log("telegram", `audio transcribed to artifact ${transcript.id}`);
|
|
146
198
|
if (artifact?.kind === "audio" && !transcript) {
|
|
147
199
|
logger?.log("telegram", `audio normalization unavailable for chat ${ctx.chat.id}: ${toolResult?.error || toolResult?.missingConfig?.join(", ") || "unknown error"}`);
|
|
@@ -154,7 +206,8 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
154
206
|
|
|
155
207
|
if (text.length > maxInlineReplyLength) {
|
|
156
208
|
logger?.log("telegram", `sending long reply as markdown attachment for chat ${chatId}`);
|
|
157
|
-
const
|
|
209
|
+
const chatArtifactStore = artifactStore.forChat(chatId);
|
|
210
|
+
const artifact = await chatArtifactStore.createGeneratedFile({
|
|
158
211
|
fileName: `reply-${Date.now()}.md`,
|
|
159
212
|
content: text,
|
|
160
213
|
kind: "document",
|
|
@@ -172,51 +225,58 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
172
225
|
await sendText(renderTelegramHtml(text), { parse_mode: "HTML" });
|
|
173
226
|
}
|
|
174
227
|
|
|
175
|
-
|
|
176
|
-
|
|
228
|
+
function createTelegramSessionBridge(chatId) {
|
|
229
|
+
return {
|
|
177
230
|
sendMedia: async (filePath, { method = "audio", caption } = {}) => {
|
|
178
|
-
logger?.log("telegram", `sending ${method} reply for chat ${
|
|
231
|
+
logger?.log("telegram", `sending ${method} reply for chat ${chatId}`);
|
|
179
232
|
const input = new InputFile(filePath);
|
|
180
|
-
if (method === "voice") return
|
|
181
|
-
if (method === "document") return
|
|
182
|
-
return
|
|
233
|
+
if (method === "voice") return bot.api.sendVoice(chatId, input, { caption });
|
|
234
|
+
if (method === "document") return bot.api.sendDocument(chatId, input, { caption });
|
|
235
|
+
return bot.api.sendAudio(chatId, input, { caption });
|
|
183
236
|
}
|
|
184
237
|
};
|
|
185
|
-
|
|
186
|
-
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function processPromptForChat({ chatId, prompt, ctx = null }) {
|
|
241
|
+
const work = async () => {
|
|
242
|
+
const { session } = await agentManager.getSessionContext(chatId, createTelegramSessionBridge(chatId));
|
|
187
243
|
const text = await collectText(session, prompt);
|
|
188
244
|
if (text) {
|
|
189
245
|
await sendTextReply({
|
|
190
|
-
sendText: (message, extra) =>
|
|
191
|
-
sendDocument: (file, extra) =>
|
|
192
|
-
chatId
|
|
246
|
+
sendText: (message, extra) => bot.api.sendMessage(chatId, message, extra),
|
|
247
|
+
sendDocument: (file, extra) => bot.api.sendDocument(chatId, file, extra),
|
|
248
|
+
chatId,
|
|
193
249
|
text
|
|
194
250
|
});
|
|
195
251
|
}
|
|
196
|
-
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (ctx) return withTyping(ctx, work);
|
|
255
|
+
return work();
|
|
197
256
|
}
|
|
198
257
|
|
|
199
|
-
async function
|
|
200
|
-
const chatState = getChatState(
|
|
201
|
-
const incomingPrompt = await buildIncomingPrompt(ctx);
|
|
258
|
+
async function enqueuePrompt({ chatId, prompt, label, ctx = null }) {
|
|
259
|
+
const chatState = getChatState(chatId);
|
|
202
260
|
|
|
203
261
|
if (chatState.processing) {
|
|
204
|
-
logger?.log("telegram", `chat ${
|
|
262
|
+
logger?.log("telegram", `chat ${chatId} busy, queueing ${label}`);
|
|
205
263
|
chatState.nextPrompt = chatState.nextPrompt
|
|
206
|
-
? `${chatState.nextPrompt}\n\n${
|
|
207
|
-
:
|
|
208
|
-
return
|
|
264
|
+
? `${chatState.nextPrompt}\n\n${prompt}`
|
|
265
|
+
: prompt;
|
|
266
|
+
return;
|
|
209
267
|
}
|
|
210
268
|
|
|
211
269
|
chatState.processing = true;
|
|
212
|
-
logger?.log("telegram", `processing
|
|
213
|
-
let currentPrompt =
|
|
270
|
+
logger?.log("telegram", `processing ${label} in chat ${chatId}`);
|
|
271
|
+
let currentPrompt = prompt;
|
|
272
|
+
let currentCtx = ctx;
|
|
214
273
|
|
|
215
274
|
while (currentPrompt) {
|
|
216
275
|
try {
|
|
217
|
-
logger?.log("telegram", `prompt dispatch for chat ${
|
|
218
|
-
await
|
|
276
|
+
logger?.log("telegram", `prompt dispatch for chat ${chatId}`);
|
|
277
|
+
await processPromptForChat({ chatId, prompt: currentPrompt, ctx: currentCtx });
|
|
219
278
|
} finally {
|
|
279
|
+
currentCtx = null;
|
|
220
280
|
if (chatState.nextPrompt) {
|
|
221
281
|
currentPrompt = chatState.nextPrompt;
|
|
222
282
|
chatState.nextPrompt = "";
|
|
@@ -229,6 +289,38 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
229
289
|
chatState.processing = false;
|
|
230
290
|
}
|
|
231
291
|
|
|
292
|
+
async function enqueueOrProcess(ctx) {
|
|
293
|
+
const chatState = getChatState(ctx.chat.id);
|
|
294
|
+
|
|
295
|
+
if (chatState.processing) {
|
|
296
|
+
const incomingPrompt = await buildIncomingPrompt(ctx);
|
|
297
|
+
return enqueuePrompt({
|
|
298
|
+
chatId: ctx.chat.id,
|
|
299
|
+
prompt: incomingPrompt,
|
|
300
|
+
label: `message ${ctx.msg.message_id}`
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const incomingPrompt = await buildIncomingPrompt(ctx);
|
|
305
|
+
return enqueuePrompt({
|
|
306
|
+
chatId: ctx.chat.id,
|
|
307
|
+
prompt: incomingPrompt,
|
|
308
|
+
label: `message ${ctx.msg.message_id}`,
|
|
309
|
+
ctx
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function handleNewCommand(ctx) {
|
|
314
|
+
agentManager.resetSession(ctx.chat.id);
|
|
315
|
+
perChatState.set(ctx.chat.id, { processing: false, nextPrompt: "" });
|
|
316
|
+
await enqueuePrompt({
|
|
317
|
+
chatId: ctx.chat.id,
|
|
318
|
+
prompt: buildNewSessionPrompt(ctx),
|
|
319
|
+
label: "new-session command",
|
|
320
|
+
ctx
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
232
324
|
bot.catch((error) => {
|
|
233
325
|
logger?.error("telegram", `bot error: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
326
|
console.error("Telegram bot error:", error);
|
|
@@ -243,15 +335,16 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
243
335
|
bot.command("new", async (ctx) => {
|
|
244
336
|
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
245
337
|
if (!auth.ok) return;
|
|
246
|
-
|
|
247
|
-
perChatState.set(ctx.chat.id, { processing: false, nextPrompt: "" });
|
|
248
|
-
return ctx.reply("Started a new chat context.");
|
|
338
|
+
await handleNewCommand(ctx);
|
|
249
339
|
});
|
|
250
340
|
|
|
251
341
|
bot.on("message", async (ctx) => {
|
|
252
342
|
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
253
343
|
if (!auth.ok) return;
|
|
254
344
|
|
|
345
|
+
const command = getTelegramCommand(ctx);
|
|
346
|
+
if (command) return;
|
|
347
|
+
|
|
255
348
|
try {
|
|
256
349
|
await enqueueOrProcess(ctx);
|
|
257
350
|
} catch (error) {
|
|
@@ -269,16 +362,6 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
269
362
|
try {
|
|
270
363
|
logger?.log("telegram", `generating startup message for chat ${chatId}`);
|
|
271
364
|
const chatMeta = config.telegram.chatMeta[chatId] || {};
|
|
272
|
-
const telegram = {
|
|
273
|
-
sendMedia: async (filePath, { method = "audio", caption } = {}) => {
|
|
274
|
-
logger?.log("telegram", `sending ${method} reply for chat ${chatId}`);
|
|
275
|
-
const input = new InputFile(filePath);
|
|
276
|
-
if (method === "voice") return bot.api.sendVoice(chatId, input, { caption });
|
|
277
|
-
if (method === "document") return bot.api.sendDocument(chatId, input, { caption });
|
|
278
|
-
return bot.api.sendAudio(chatId, input, { caption });
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
const { session } = await agentManager.getSessionContext(chatId, telegram);
|
|
282
365
|
const welcomePrompt = [
|
|
283
366
|
"System event: Arisa has just started.",
|
|
284
367
|
`chatId: ${chatId}`,
|
|
@@ -290,15 +373,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
290
373
|
"Use the user's Telegram language when possible.",
|
|
291
374
|
"Do not mention internal implementation details."
|
|
292
375
|
].filter(Boolean).join("\n");
|
|
293
|
-
|
|
294
|
-
if (text) {
|
|
295
|
-
await sendTextReply({
|
|
296
|
-
sendText: (message, extra) => bot.api.sendMessage(chatId, message, extra),
|
|
297
|
-
sendDocument: (file, extra) => bot.api.sendDocument(chatId, file, extra),
|
|
298
|
-
chatId,
|
|
299
|
-
text
|
|
300
|
-
});
|
|
301
|
-
}
|
|
376
|
+
await enqueuePrompt({ chatId, prompt: welcomePrompt, label: "startup message" });
|
|
302
377
|
} catch (error) {
|
|
303
378
|
logger?.log("telegram", `startup message failed for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
304
379
|
}
|
|
@@ -306,8 +381,46 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
306
381
|
await bot.api.setMyCommands([
|
|
307
382
|
{ command: "new", description: "Start a new chat context" }
|
|
308
383
|
]);
|
|
309
|
-
|
|
310
|
-
|
|
384
|
+
setInterval(async () => {
|
|
385
|
+
const tasks = await taskStore.claimDue(10);
|
|
386
|
+
for (const task of tasks) {
|
|
387
|
+
try {
|
|
388
|
+
if (task.kind !== "agent_task" || !task.payload?.chatId || !task.payload?.prompt) {
|
|
389
|
+
await taskStore.fail(task.id, `Unsupported task: ${task.kind}`);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
logger?.log("tasks", `running task ${task.id} for chat ${task.payload.chatId}`);
|
|
393
|
+
await enqueuePrompt({
|
|
394
|
+
chatId: task.payload.chatId,
|
|
395
|
+
prompt: await buildAsyncTaskPrompt({ task, artifactStore, toolRegistry, logger }),
|
|
396
|
+
label: `scheduled task ${task.id}`
|
|
397
|
+
});
|
|
398
|
+
await taskStore.complete(task.id);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
await taskStore.fail(task.id, error instanceof Error ? error.message : String(error));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}, 1000).unref();
|
|
404
|
+
if (webhookUrl && setHttpRequestHandler) {
|
|
405
|
+
const webhookPath = `/telegram-${config.telegram.token.slice(-8)}`;
|
|
406
|
+
const handleUpdate = webhookCallback(bot, "http", {
|
|
407
|
+
timeoutMilliseconds: 60_000,
|
|
408
|
+
onTimeout: "return",
|
|
409
|
+
});
|
|
410
|
+
setHttpRequestHandler((req, res) => {
|
|
411
|
+
const parsed = new URL(req.url, "http://localhost");
|
|
412
|
+
if (req.method === "POST" && parsed.pathname === webhookPath) {
|
|
413
|
+
return handleUpdate(req, res);
|
|
414
|
+
}
|
|
415
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
416
|
+
res.end("ok");
|
|
417
|
+
});
|
|
418
|
+
await bot.api.setWebhook(`${webhookUrl}${webhookPath}`);
|
|
419
|
+
logger?.log("telegram", `webhook mode: ${webhookUrl}${webhookPath}`);
|
|
420
|
+
} else {
|
|
421
|
+
logger?.log("telegram", "bot polling started");
|
|
422
|
+
await bot.start({ drop_pending_updates: true });
|
|
423
|
+
}
|
|
311
424
|
}
|
|
312
425
|
};
|
|
313
426
|
}
|
|
@@ -6,56 +6,62 @@ async function downloadToBuffer(ctx, fileId) {
|
|
|
6
6
|
return Buffer.from(await response.arrayBuffer());
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function incomingCaptionMetadata(ctx) {
|
|
10
|
+
return ctx.message?.caption ? { caption: ctx.message.caption } : {};
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
export async function captureIncomingArtifact(ctx, artifactStore) {
|
|
14
|
+
const chatId = ctx.chat.id;
|
|
15
|
+
const store = artifactStore.forChat(chatId);
|
|
10
16
|
const baseSource = {
|
|
11
17
|
type: "telegram",
|
|
12
|
-
chatId
|
|
18
|
+
chatId,
|
|
13
19
|
messageId: ctx.msg.message_id,
|
|
14
20
|
userId: ctx.from.id
|
|
15
21
|
};
|
|
16
22
|
|
|
17
23
|
if (ctx.message?.voice) {
|
|
18
|
-
const fileName = `${
|
|
24
|
+
const fileName = `${chatId}-${ctx.msg.message_id}.ogg`;
|
|
19
25
|
const content = await downloadToBuffer(ctx, ctx.message.voice.file_id);
|
|
20
|
-
return
|
|
26
|
+
return store.createGeneratedFile({
|
|
21
27
|
fileName,
|
|
22
28
|
content,
|
|
23
29
|
kind: "audio",
|
|
24
30
|
mimeType: "audio/ogg",
|
|
25
31
|
source: baseSource,
|
|
26
|
-
metadata: { duration: ctx.message.voice.duration }
|
|
32
|
+
metadata: { duration: ctx.message.voice.duration, ...incomingCaptionMetadata(ctx) }
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
if (ctx.message?.document) {
|
|
31
|
-
const fileName = ctx.message.document.file_name || `${
|
|
37
|
+
const fileName = ctx.message.document.file_name || `${chatId}-${ctx.msg.message_id}`;
|
|
32
38
|
const content = await downloadToBuffer(ctx, ctx.message.document.file_id);
|
|
33
|
-
return
|
|
39
|
+
return store.createGeneratedFile({
|
|
34
40
|
fileName,
|
|
35
41
|
content,
|
|
36
42
|
kind: "document",
|
|
37
43
|
mimeType: ctx.message.document.mime_type || "application/octet-stream",
|
|
38
44
|
source: baseSource,
|
|
39
|
-
metadata:
|
|
45
|
+
metadata: incomingCaptionMetadata(ctx)
|
|
40
46
|
});
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
if (ctx.message?.photo?.length) {
|
|
44
50
|
const photo = ctx.message.photo.at(-1);
|
|
45
|
-
const fileName = `${
|
|
51
|
+
const fileName = `${chatId}-${ctx.msg.message_id}.jpg`;
|
|
46
52
|
const content = await downloadToBuffer(ctx, photo.file_id);
|
|
47
|
-
return
|
|
53
|
+
return store.createGeneratedFile({
|
|
48
54
|
fileName,
|
|
49
55
|
content,
|
|
50
56
|
kind: "image",
|
|
51
57
|
mimeType: "image/jpeg",
|
|
52
58
|
source: baseSource,
|
|
53
|
-
metadata: { width: photo.width, height: photo.height }
|
|
59
|
+
metadata: { width: photo.width, height: photo.height, ...incomingCaptionMetadata(ctx) }
|
|
54
60
|
});
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
if (ctx.message?.text) {
|
|
58
|
-
return
|
|
64
|
+
return store.createText({
|
|
59
65
|
text: ctx.message.text,
|
|
60
66
|
source: baseSource,
|
|
61
67
|
metadata: {}
|
|
@@ -9,7 +9,7 @@ const toolName = "openai-transcribe";
|
|
|
9
9
|
const config = await loadToolConfig(toolName, defaults);
|
|
10
10
|
|
|
11
11
|
function printHelp() {
|
|
12
|
-
console.log(`openai-transcribe\n\nUsage:\n node index.js --help\n node index.js run --request-file <json>\n\nExpected input:\n {\n
|
|
12
|
+
console.log(`openai-transcribe\n\nUsage:\n node index.js --help\n node index.js run --request-file <json>\n\nExpected input:\n {\n "artifact": { "path": "/abs/audio.ogg", "mimeType": "audio/ogg" },\n "args": {}\n }\n\nConfig at ${getToolConfigPath(toolName)}:\n OPENAI_API_KEY\n MODEL\n`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
async function run(requestFile) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { toolError, toolOk } from "../../src/core/tools/tool-result.js";
|
|
3
|
+
|
|
4
|
+
function printHelp() {
|
|
5
|
+
console.log(`schedule-agent-task\n\nUsage:\n node index.js --help\n node index.js run --request-file <json>\n\nExpected input:\n {\n "text": "tell me the temperature in Toronto",\n "artifact": { "text": "tell me the temperature in Toronto" },\n "args": {\n "prompt": "tell me the temperature in Toronto",\n "runAt": "2026-04-07T14:00:00.000Z",\n "delaySeconds": "30",\n "intervalSeconds": "3600"\n }\n }\n\nBehavior:\n - schedules a future agent task for the current chat\n - provide either args.runAt or args.delaySeconds\n - optional args.intervalSeconds makes the task recurring\n`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function firstNonEmpty(...values) {
|
|
9
|
+
return values.find((value) => String(value || "").trim()) || "";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function buildRunAt(args = {}) {
|
|
13
|
+
const runAtValue = firstNonEmpty(args.runAt, args.at, args.when);
|
|
14
|
+
if (runAtValue) {
|
|
15
|
+
const parsed = Date.parse(runAtValue);
|
|
16
|
+
if (Number.isNaN(parsed)) return "";
|
|
17
|
+
return new Date(parsed).toISOString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const delaySeconds = Number(firstNonEmpty(args.delaySeconds, args.delay, args.seconds));
|
|
21
|
+
if (Number.isFinite(delaySeconds) && delaySeconds > 0) {
|
|
22
|
+
return new Date(Date.now() + (delaySeconds * 1000)).toISOString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function run(requestFile) {
|
|
29
|
+
const request = JSON.parse(await readFile(requestFile, "utf8"));
|
|
30
|
+
const args = request.args || {};
|
|
31
|
+
const prompt = firstNonEmpty(args.prompt, args.message, args.task, request.text, request.artifact?.text);
|
|
32
|
+
const runAt = buildRunAt(args);
|
|
33
|
+
const intervalSeconds = Number(firstNonEmpty(args.intervalSeconds, args.interval, args.everySeconds));
|
|
34
|
+
|
|
35
|
+
if (!prompt.trim()) {
|
|
36
|
+
console.log(JSON.stringify(toolError("prompt/message/task, text, or artifact.text is required")));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!runAt) {
|
|
41
|
+
console.log(JSON.stringify(toolError("args.runAt/at/when or args.delaySeconds/delay/seconds is required")));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const asyncTask = {
|
|
46
|
+
kind: "agent_task",
|
|
47
|
+
runAt,
|
|
48
|
+
payload: { prompt },
|
|
49
|
+
recurrence: Number.isFinite(intervalSeconds) && intervalSeconds > 0
|
|
50
|
+
? { type: "interval", everySeconds: intervalSeconds }
|
|
51
|
+
: null
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
console.log(JSON.stringify(toolOk({ runAt }, {
|
|
55
|
+
status: "scheduled",
|
|
56
|
+
asyncTask
|
|
57
|
+
})));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
if (!args.length || args.includes("--help") || args[0] === "help") {
|
|
62
|
+
printHelp();
|
|
63
|
+
} else if (args[0] === "run") {
|
|
64
|
+
const fileIndex = args.indexOf("--request-file");
|
|
65
|
+
await run(args[fileIndex + 1]);
|
|
66
|
+
} else {
|
|
67
|
+
printHelp();
|
|
68
|
+
}
|