@isarai/maestro 0.1.5 → 0.1.7
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/dist/server.js +68 -1174
- package/dist/server.js.map +4 -4
- package/package.json +1 -5
package/dist/server.js
CHANGED
|
@@ -2205,7 +2205,6 @@ var init_sqlite = __esm({
|
|
|
2205
2205
|
function getSettings() {
|
|
2206
2206
|
const enabled = getSetting("autoUpdateEnabled");
|
|
2207
2207
|
const interval = getSetting("autoUpdateIntervalHours");
|
|
2208
|
-
const piModel = getSetting("piOllamaModel");
|
|
2209
2208
|
const telegramToken = getSetting("telegramBotToken");
|
|
2210
2209
|
const sandbox = getSetting("sandboxEnabled");
|
|
2211
2210
|
const sandboxProvider = getSetting("sandboxProvider");
|
|
@@ -2218,7 +2217,6 @@ function getSettings() {
|
|
|
2218
2217
|
return {
|
|
2219
2218
|
autoUpdateEnabled: enabled !== null ? enabled === "true" : DEFAULTS.autoUpdateEnabled,
|
|
2220
2219
|
autoUpdateIntervalHours: interval !== null ? Number(interval) : DEFAULTS.autoUpdateIntervalHours,
|
|
2221
|
-
piOllamaModel: piModel !== null ? piModel : DEFAULTS.piOllamaModel,
|
|
2222
2220
|
telegramBotToken: telegramToken !== null ? telegramToken : DEFAULTS.telegramBotToken,
|
|
2223
2221
|
sandboxEnabled: resolvedSandboxProvider !== "none",
|
|
2224
2222
|
sandboxProvider: resolvedSandboxProvider,
|
|
@@ -2236,9 +2234,6 @@ function updateSettings(patch) {
|
|
|
2236
2234
|
if (patch.autoUpdateIntervalHours !== void 0) {
|
|
2237
2235
|
setSetting("autoUpdateIntervalHours", String(patch.autoUpdateIntervalHours));
|
|
2238
2236
|
}
|
|
2239
|
-
if (patch.piOllamaModel !== void 0) {
|
|
2240
|
-
setSetting("piOllamaModel", patch.piOllamaModel);
|
|
2241
|
-
}
|
|
2242
2237
|
if (patch.telegramBotToken !== void 0) {
|
|
2243
2238
|
setSetting("telegramBotToken", patch.telegramBotToken);
|
|
2244
2239
|
}
|
|
@@ -2278,7 +2273,6 @@ var init_settings = __esm({
|
|
|
2278
2273
|
DEFAULTS = {
|
|
2279
2274
|
autoUpdateEnabled: false,
|
|
2280
2275
|
autoUpdateIntervalHours: 24,
|
|
2281
|
-
piOllamaModel: "",
|
|
2282
2276
|
telegramBotToken: "",
|
|
2283
2277
|
sandboxEnabled: false,
|
|
2284
2278
|
sandboxProvider: "none",
|
|
@@ -3308,16 +3302,16 @@ function readCommandError(error, fallback) {
|
|
|
3308
3302
|
}
|
|
3309
3303
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
3310
3304
|
}
|
|
3311
|
-
function runGit2(
|
|
3305
|
+
function runGit2(path17, args) {
|
|
3312
3306
|
return execFileSync6("git", args, {
|
|
3313
|
-
cwd:
|
|
3307
|
+
cwd: path17,
|
|
3314
3308
|
encoding: "utf8",
|
|
3315
3309
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3316
3310
|
}).trim();
|
|
3317
3311
|
}
|
|
3318
|
-
function tryRunGit(
|
|
3312
|
+
function tryRunGit(path17, args) {
|
|
3319
3313
|
try {
|
|
3320
|
-
return { ok: true, output: runGit2(
|
|
3314
|
+
return { ok: true, output: runGit2(path17, args) };
|
|
3321
3315
|
} catch (error) {
|
|
3322
3316
|
return {
|
|
3323
3317
|
ok: false,
|
|
@@ -3428,20 +3422,20 @@ function decideAutoWorktreeStartPoint(input) {
|
|
|
3428
3422
|
reason: "Using local HEAD in a fresh worktree because no remote tracking branch was available."
|
|
3429
3423
|
};
|
|
3430
3424
|
}
|
|
3431
|
-
function resolveUpstreamRef(
|
|
3432
|
-
const upstream = tryRunGit(
|
|
3425
|
+
function resolveUpstreamRef(path17, currentBranch) {
|
|
3426
|
+
const upstream = tryRunGit(path17, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
|
3433
3427
|
if (upstream.ok && upstream.output) {
|
|
3434
3428
|
return upstream.output;
|
|
3435
3429
|
}
|
|
3436
3430
|
const originBranch = `refs/remotes/origin/${currentBranch}`;
|
|
3437
|
-
const remoteBranch = tryRunGit(
|
|
3431
|
+
const remoteBranch = tryRunGit(path17, ["show-ref", "--verify", "--quiet", originBranch]);
|
|
3438
3432
|
if (remoteBranch.ok) {
|
|
3439
3433
|
return `origin/${currentBranch}`;
|
|
3440
3434
|
}
|
|
3441
3435
|
return null;
|
|
3442
3436
|
}
|
|
3443
|
-
function hasGitRef(
|
|
3444
|
-
return tryRunGit(
|
|
3437
|
+
function hasGitRef(path17, ref) {
|
|
3438
|
+
return tryRunGit(path17, ["show-ref", "--verify", "--quiet", ref]).ok;
|
|
3445
3439
|
}
|
|
3446
3440
|
async function resolveAutoWorktreeStartPoint(input) {
|
|
3447
3441
|
const project = resolveProjectRecord(input);
|
|
@@ -4317,7 +4311,6 @@ var SandboxProviderSchema = z.enum(["none", "docker"]);
|
|
|
4317
4311
|
var SettingsSchema = z.object({
|
|
4318
4312
|
autoUpdateEnabled: z.boolean().default(false),
|
|
4319
4313
|
autoUpdateIntervalHours: z.number().min(1).max(168).default(24),
|
|
4320
|
-
piOllamaModel: z.string().default(""),
|
|
4321
4314
|
telegramBotToken: z.string().default(""),
|
|
4322
4315
|
/** Legacy boolean retained for backwards compatibility with older clients */
|
|
4323
4316
|
sandboxEnabled: z.boolean().default(false),
|
|
@@ -4335,18 +4328,6 @@ var SettingsSchema = z.object({
|
|
|
4335
4328
|
agentDefaultWorktreeMode: AutoSpawnAgentWorktreeModeSchema.default("none")
|
|
4336
4329
|
});
|
|
4337
4330
|
var SettingsUpdateSchema = SettingsSchema.partial();
|
|
4338
|
-
var OllamaModelInfo = z.object({
|
|
4339
|
-
name: z.string(),
|
|
4340
|
-
size: z.number(),
|
|
4341
|
-
digest: z.string(),
|
|
4342
|
-
modifiedAt: z.string()
|
|
4343
|
-
});
|
|
4344
|
-
var OllamaPullProgress = z.object({
|
|
4345
|
-
status: z.string(),
|
|
4346
|
-
digest: z.string().optional(),
|
|
4347
|
-
total: z.number().optional(),
|
|
4348
|
-
completed: z.number().optional()
|
|
4349
|
-
});
|
|
4350
4331
|
var UpdateStatus = z.object({
|
|
4351
4332
|
lastCheckAt: z.string().nullable(),
|
|
4352
4333
|
lastUpdateAt: z.string().nullable(),
|
|
@@ -4650,73 +4631,6 @@ var ProjectInfo = z5.object({
|
|
|
4650
4631
|
updatedAt: z5.string()
|
|
4651
4632
|
});
|
|
4652
4633
|
|
|
4653
|
-
// ../wire/src/whatsapp.ts
|
|
4654
|
-
import { z as z6 } from "zod";
|
|
4655
|
-
var WhatsAppConnectionStatus = z6.enum([
|
|
4656
|
-
"disconnected",
|
|
4657
|
-
"connecting",
|
|
4658
|
-
"qr_pending",
|
|
4659
|
-
"connected"
|
|
4660
|
-
]);
|
|
4661
|
-
var WhatsAppStatusResponse = z6.object({
|
|
4662
|
-
status: WhatsAppConnectionStatus,
|
|
4663
|
-
qrCode: z6.string().nullable().optional(),
|
|
4664
|
-
phoneNumber: z6.string().nullable().optional()
|
|
4665
|
-
});
|
|
4666
|
-
var WhatsAppMessageDirection = z6.enum(["incoming", "outgoing"]);
|
|
4667
|
-
var WhatsAppMessageStatus = z6.enum([
|
|
4668
|
-
"queued",
|
|
4669
|
-
"processing",
|
|
4670
|
-
"completed",
|
|
4671
|
-
"failed"
|
|
4672
|
-
]);
|
|
4673
|
-
var WhatsAppMessage = z6.object({
|
|
4674
|
-
id: z6.string(),
|
|
4675
|
-
chatJid: z6.string(),
|
|
4676
|
-
messageId: z6.string(),
|
|
4677
|
-
direction: WhatsAppMessageDirection,
|
|
4678
|
-
senderName: z6.string().nullable(),
|
|
4679
|
-
body: z6.string(),
|
|
4680
|
-
status: WhatsAppMessageStatus,
|
|
4681
|
-
responseText: z6.string().nullable(),
|
|
4682
|
-
error: z6.string().nullable(),
|
|
4683
|
-
createdAt: z6.string(),
|
|
4684
|
-
processedAt: z6.string().nullable()
|
|
4685
|
-
});
|
|
4686
|
-
|
|
4687
|
-
// ../wire/src/telegram.ts
|
|
4688
|
-
import { z as z7 } from "zod";
|
|
4689
|
-
var TelegramConnectionStatus = z7.enum([
|
|
4690
|
-
"disconnected",
|
|
4691
|
-
"connecting",
|
|
4692
|
-
"connected",
|
|
4693
|
-
"error"
|
|
4694
|
-
]);
|
|
4695
|
-
var TelegramStatusResponse = z7.object({
|
|
4696
|
-
status: TelegramConnectionStatus,
|
|
4697
|
-
botUsername: z7.string().nullable().optional()
|
|
4698
|
-
});
|
|
4699
|
-
var TelegramMessageDirection = z7.enum(["incoming", "outgoing"]);
|
|
4700
|
-
var TelegramMessageStatus = z7.enum([
|
|
4701
|
-
"queued",
|
|
4702
|
-
"processing",
|
|
4703
|
-
"completed",
|
|
4704
|
-
"failed"
|
|
4705
|
-
]);
|
|
4706
|
-
var TelegramMessage = z7.object({
|
|
4707
|
-
id: z7.string(),
|
|
4708
|
-
chatId: z7.string(),
|
|
4709
|
-
messageId: z7.string(),
|
|
4710
|
-
direction: TelegramMessageDirection,
|
|
4711
|
-
senderName: z7.string().nullable(),
|
|
4712
|
-
body: z7.string(),
|
|
4713
|
-
status: TelegramMessageStatus,
|
|
4714
|
-
responseText: z7.string().nullable(),
|
|
4715
|
-
error: z7.string().nullable(),
|
|
4716
|
-
createdAt: z7.string(),
|
|
4717
|
-
processedAt: z7.string().nullable()
|
|
4718
|
-
});
|
|
4719
|
-
|
|
4720
4634
|
// ../server/src/routes/terminal-routes.ts
|
|
4721
4635
|
init_terminal_manager();
|
|
4722
4636
|
init_projects();
|
|
@@ -5248,7 +5162,7 @@ async function registerProjectRoutes(app) {
|
|
|
5248
5162
|
init_kanban();
|
|
5249
5163
|
init_terminal_manager();
|
|
5250
5164
|
init_auto_spawn_provider();
|
|
5251
|
-
async function registerKanbanRoutes(app,
|
|
5165
|
+
async function registerKanbanRoutes(app, io) {
|
|
5252
5166
|
app.get(
|
|
5253
5167
|
"/api/kanban/tasks",
|
|
5254
5168
|
async (req) => {
|
|
@@ -5271,7 +5185,7 @@ async function registerKanbanRoutes(app, io3) {
|
|
|
5271
5185
|
try {
|
|
5272
5186
|
const input = KanbanTaskCreate.parse(req.body);
|
|
5273
5187
|
const task = await createKanbanTask(input);
|
|
5274
|
-
|
|
5188
|
+
io.emit("kanban:updated", { taskId: task.id, column: task.column });
|
|
5275
5189
|
return reply.status(201).send({ task });
|
|
5276
5190
|
} catch (err) {
|
|
5277
5191
|
return reply.status(400).send({ error: err instanceof Error ? err.message : "Invalid input" });
|
|
@@ -5283,7 +5197,7 @@ async function registerKanbanRoutes(app, io3) {
|
|
|
5283
5197
|
try {
|
|
5284
5198
|
const input = KanbanTaskUpdate.parse(req.body);
|
|
5285
5199
|
const task = await updateKanbanTaskRecord(req.params.id, input);
|
|
5286
|
-
|
|
5200
|
+
io.emit("kanban:updated", {
|
|
5287
5201
|
taskId: task.id,
|
|
5288
5202
|
column: task.column,
|
|
5289
5203
|
assignedTerminalId: task.assignedTerminalId
|
|
@@ -5322,7 +5236,7 @@ async function registerKanbanRoutes(app, io3) {
|
|
|
5322
5236
|
}
|
|
5323
5237
|
}
|
|
5324
5238
|
const task = await moveKanbanTaskRecord(req.params.id, column);
|
|
5325
|
-
|
|
5239
|
+
io.emit("kanban:updated", {
|
|
5326
5240
|
taskId: task.id,
|
|
5327
5241
|
column: task.column,
|
|
5328
5242
|
assignedTerminalId: task.assignedTerminalId
|
|
@@ -5340,7 +5254,7 @@ async function registerKanbanRoutes(app, io3) {
|
|
|
5340
5254
|
async (req, reply) => {
|
|
5341
5255
|
try {
|
|
5342
5256
|
await deleteKanbanTaskRecord(req.params.id);
|
|
5343
|
-
|
|
5257
|
+
io.emit("kanban:updated", { taskId: req.params.id });
|
|
5344
5258
|
return { ok: true };
|
|
5345
5259
|
} catch (err) {
|
|
5346
5260
|
return reply.status(400).send({
|
|
@@ -5388,7 +5302,7 @@ function resetSetup() {
|
|
|
5388
5302
|
function isSetupRunning() {
|
|
5389
5303
|
return running;
|
|
5390
5304
|
}
|
|
5391
|
-
function startSetupPty(
|
|
5305
|
+
function startSetupPty(io, cols, rows) {
|
|
5392
5306
|
if (activePty) {
|
|
5393
5307
|
console.log("[setup] Killing stale PTY before starting new one");
|
|
5394
5308
|
cleanup();
|
|
@@ -5412,26 +5326,26 @@ function startSetupPty(io3, cols, rows) {
|
|
|
5412
5326
|
});
|
|
5413
5327
|
console.log(`[setup] PTY spawned, pid: ${activePty.pid}`);
|
|
5414
5328
|
activePty.onData((data) => {
|
|
5415
|
-
const roomSize =
|
|
5329
|
+
const roomSize = io.sockets.adapter.rooms.get("setup")?.size ?? 0;
|
|
5416
5330
|
console.log(`[setup] PTY output (${data.length} bytes, ${roomSize} subscribers)`);
|
|
5417
5331
|
outputBuffer.push(data);
|
|
5418
|
-
|
|
5332
|
+
io.to("setup").emit("setup:output", { data });
|
|
5419
5333
|
const clean = data.replace(ANSI_RE2, "");
|
|
5420
5334
|
const hasUrl = URL_RE.test(clean);
|
|
5421
5335
|
URL_RE.lastIndex = 0;
|
|
5422
5336
|
if (!hasUrl && STEP_BOUNDARY_RE.test(clean)) {
|
|
5423
|
-
|
|
5337
|
+
io.to("setup").emit("setup:clear-url", {});
|
|
5424
5338
|
}
|
|
5425
5339
|
const urls = clean.match(URL_RE);
|
|
5426
5340
|
if (urls) {
|
|
5427
5341
|
for (const url of urls) {
|
|
5428
|
-
|
|
5342
|
+
io.to("setup").emit("setup:url", { url });
|
|
5429
5343
|
}
|
|
5430
5344
|
}
|
|
5431
5345
|
if (data.includes(SENTINEL)) {
|
|
5432
5346
|
setupComplete = true;
|
|
5433
5347
|
markSetupComplete();
|
|
5434
|
-
|
|
5348
|
+
io.to("setup").emit("setup:complete", {});
|
|
5435
5349
|
cleanup();
|
|
5436
5350
|
}
|
|
5437
5351
|
});
|
|
@@ -5468,8 +5382,8 @@ function resetOutputBuffer2() {
|
|
|
5468
5382
|
}
|
|
5469
5383
|
|
|
5470
5384
|
// ../server/src/socket/handlers.ts
|
|
5471
|
-
function registerSocketHandlers(
|
|
5472
|
-
|
|
5385
|
+
function registerSocketHandlers(io) {
|
|
5386
|
+
io.on("connection", (socket) => {
|
|
5473
5387
|
console.log(`Client connected: ${socket.id}`);
|
|
5474
5388
|
socket.on("terminal:attach", async (data, respond) => {
|
|
5475
5389
|
try {
|
|
@@ -5554,7 +5468,7 @@ function registerSocketHandlers(io3) {
|
|
|
5554
5468
|
if (!isSetupRunning() && !isSetupDone()) {
|
|
5555
5469
|
const cols = data?.cols;
|
|
5556
5470
|
const rows = data?.rows;
|
|
5557
|
-
startSetupPty(
|
|
5471
|
+
startSetupPty(io, cols, rows);
|
|
5558
5472
|
}
|
|
5559
5473
|
});
|
|
5560
5474
|
socket.on("setup:unsubscribe", () => {
|
|
@@ -5581,18 +5495,11 @@ function registerSocketHandlers(io3) {
|
|
|
5581
5495
|
const { cols, rows } = ClientEvents["setup:restart"].parse(data);
|
|
5582
5496
|
console.log(`[setup] Restart requested by ${socket.id}`);
|
|
5583
5497
|
resetSetup();
|
|
5584
|
-
startSetupPty(
|
|
5498
|
+
startSetupPty(io, cols, rows);
|
|
5585
5499
|
} catch {
|
|
5586
5500
|
socket.emit("error", { message: "Invalid setup restart payload" });
|
|
5587
5501
|
}
|
|
5588
5502
|
});
|
|
5589
|
-
socket.on("whatsapp:subscribe", () => {
|
|
5590
|
-
socket.join("whatsapp");
|
|
5591
|
-
console.log(`Client ${socket.id} subscribed to whatsapp`);
|
|
5592
|
-
});
|
|
5593
|
-
socket.on("whatsapp:unsubscribe", () => {
|
|
5594
|
-
socket.leave("whatsapp");
|
|
5595
|
-
});
|
|
5596
5503
|
socket.on("disconnect", () => {
|
|
5597
5504
|
console.log(`Client disconnected: ${socket.id}`);
|
|
5598
5505
|
});
|
|
@@ -5605,10 +5512,10 @@ init_kanban();
|
|
|
5605
5512
|
init_terminals();
|
|
5606
5513
|
var POLL_INTERVAL = 1e4;
|
|
5607
5514
|
var timer2 = null;
|
|
5608
|
-
function startKanbanAssigner(
|
|
5515
|
+
function startKanbanAssigner(io) {
|
|
5609
5516
|
console.log("Kanban assigner started (polling every 10s)");
|
|
5610
|
-
timer2 = setInterval(() => tick(
|
|
5611
|
-
void tick(
|
|
5517
|
+
timer2 = setInterval(() => tick(io), POLL_INTERVAL);
|
|
5518
|
+
void tick(io);
|
|
5612
5519
|
}
|
|
5613
5520
|
function stopKanbanAssigner() {
|
|
5614
5521
|
if (timer2) {
|
|
@@ -5616,7 +5523,7 @@ function stopKanbanAssigner() {
|
|
|
5616
5523
|
timer2 = null;
|
|
5617
5524
|
}
|
|
5618
5525
|
}
|
|
5619
|
-
async function tick(
|
|
5526
|
+
async function tick(io) {
|
|
5620
5527
|
try {
|
|
5621
5528
|
const allTasks = await listKanbanTasks();
|
|
5622
5529
|
const doneTaskIds = new Set(
|
|
@@ -5628,13 +5535,13 @@ async function tick(io3) {
|
|
|
5628
5535
|
);
|
|
5629
5536
|
if (assignableTasks.length === 0) return;
|
|
5630
5537
|
for (const task of assignableTasks) {
|
|
5631
|
-
await assignTaskToTerminal(
|
|
5538
|
+
await assignTaskToTerminal(io, task);
|
|
5632
5539
|
}
|
|
5633
5540
|
} catch (err) {
|
|
5634
5541
|
console.error("Kanban assigner error:", err);
|
|
5635
5542
|
}
|
|
5636
5543
|
}
|
|
5637
|
-
async function assignTaskToTerminal(
|
|
5544
|
+
async function assignTaskToTerminal(io, task) {
|
|
5638
5545
|
let terminalId = null;
|
|
5639
5546
|
try {
|
|
5640
5547
|
const terminal = await createAutoSpawnTerminal({
|
|
@@ -5654,7 +5561,7 @@ async function assignTaskToTerminal(io3, task) {
|
|
|
5654
5561
|
|
|
5655
5562
|
${task.description}`;
|
|
5656
5563
|
await startTerminal(terminalId, prompt);
|
|
5657
|
-
|
|
5564
|
+
io.emit("kanban:updated", {
|
|
5658
5565
|
taskId: task.id,
|
|
5659
5566
|
column: "ongoing",
|
|
5660
5567
|
assignedTerminalId: terminalId
|
|
@@ -5671,7 +5578,7 @@ ${task.description}`;
|
|
|
5671
5578
|
column: "planned",
|
|
5672
5579
|
assignedTerminalId: null
|
|
5673
5580
|
});
|
|
5674
|
-
|
|
5581
|
+
io.emit("kanban:updated", {
|
|
5675
5582
|
taskId: task.id,
|
|
5676
5583
|
column: "planned",
|
|
5677
5584
|
assignedTerminalId: null
|
|
@@ -6366,7 +6273,7 @@ function verifySignature(rawBody, signature, secret) {
|
|
|
6366
6273
|
}
|
|
6367
6274
|
return crypto3.timingSafeEqual(expectedBuffer, providedBuffer);
|
|
6368
6275
|
}
|
|
6369
|
-
async function registerWebhookRoutes(app,
|
|
6276
|
+
async function registerWebhookRoutes(app, io) {
|
|
6370
6277
|
app.post("/api/webhooks/github", async (req, reply) => {
|
|
6371
6278
|
const secret = process.env.GITHUB_WEBHOOK_SECRET?.trim();
|
|
6372
6279
|
if (!secret) {
|
|
@@ -6401,7 +6308,7 @@ async function registerWebhookRoutes(app, io3) {
|
|
|
6401
6308
|
body.action ?? "",
|
|
6402
6309
|
body.issue.number
|
|
6403
6310
|
);
|
|
6404
|
-
|
|
6311
|
+
io.emit("kanban:updated", { taskId: result.taskId });
|
|
6405
6312
|
return { ok: true, handled: true, projectId: project.id, taskId: result.taskId };
|
|
6406
6313
|
}
|
|
6407
6314
|
if (event === "pull_request" && body.pull_request) {
|
|
@@ -6412,7 +6319,7 @@ async function registerWebhookRoutes(app, io3) {
|
|
|
6412
6319
|
merged: body.pull_request.merged
|
|
6413
6320
|
});
|
|
6414
6321
|
if (result.taskId) {
|
|
6415
|
-
|
|
6322
|
+
io.emit("kanban:updated", { taskId: result.taskId });
|
|
6416
6323
|
}
|
|
6417
6324
|
return {
|
|
6418
6325
|
ok: true,
|
|
@@ -6433,7 +6340,7 @@ async function registerWebhookRoutes(app, io3) {
|
|
|
6433
6340
|
}
|
|
6434
6341
|
);
|
|
6435
6342
|
if (result.taskId) {
|
|
6436
|
-
|
|
6343
|
+
io.emit("kanban:updated", { taskId: result.taskId });
|
|
6437
6344
|
}
|
|
6438
6345
|
return {
|
|
6439
6346
|
ok: true,
|
|
@@ -6451,7 +6358,7 @@ async function registerWebhookRoutes(app, io3) {
|
|
|
6451
6358
|
}
|
|
6452
6359
|
);
|
|
6453
6360
|
if (result.taskId) {
|
|
6454
|
-
|
|
6361
|
+
io.emit("kanban:updated", { taskId: result.taskId });
|
|
6455
6362
|
}
|
|
6456
6363
|
return {
|
|
6457
6364
|
ok: true,
|
|
@@ -6645,125 +6552,6 @@ function stopAutoUpdater() {
|
|
|
6645
6552
|
}
|
|
6646
6553
|
}
|
|
6647
6554
|
|
|
6648
|
-
// ../server/src/services/ollama.ts
|
|
6649
|
-
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, readFileSync as readFileSync5, existsSync as existsSync15 } from "node:fs";
|
|
6650
|
-
import { join as join16 } from "node:path";
|
|
6651
|
-
import { homedir as homedir9 } from "node:os";
|
|
6652
|
-
var OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434";
|
|
6653
|
-
var PI_MODELS_PATH = join16(homedir9(), ".pi", "agent", "models.json");
|
|
6654
|
-
async function getOllamaStatus() {
|
|
6655
|
-
try {
|
|
6656
|
-
const res = await fetch(`${OLLAMA_HOST}/api/version`);
|
|
6657
|
-
return { running: res.ok, host: OLLAMA_HOST };
|
|
6658
|
-
} catch {
|
|
6659
|
-
return { running: false, host: OLLAMA_HOST };
|
|
6660
|
-
}
|
|
6661
|
-
}
|
|
6662
|
-
async function listOllamaModels() {
|
|
6663
|
-
const res = await fetch(`${OLLAMA_HOST}/api/tags`);
|
|
6664
|
-
if (!res.ok) throw new Error(`Ollama returned ${res.status}`);
|
|
6665
|
-
const data = await res.json();
|
|
6666
|
-
return (data.models || []).map((m) => ({
|
|
6667
|
-
name: m.name,
|
|
6668
|
-
size: m.size,
|
|
6669
|
-
digest: m.digest,
|
|
6670
|
-
modifiedAt: m.modified_at
|
|
6671
|
-
}));
|
|
6672
|
-
}
|
|
6673
|
-
var RECOMMENDED_MODELS = [
|
|
6674
|
-
"qwen3:4b",
|
|
6675
|
-
"qwen3:2b",
|
|
6676
|
-
"qwen3:9b-q4_K_M",
|
|
6677
|
-
"llama3.1:8b-q4_K_M",
|
|
6678
|
-
"llama3.2:3b"
|
|
6679
|
-
];
|
|
6680
|
-
var activePull = null;
|
|
6681
|
-
function getPullStatus() {
|
|
6682
|
-
return activePull;
|
|
6683
|
-
}
|
|
6684
|
-
async function pullOllamaModel(modelName) {
|
|
6685
|
-
if (activePull && !activePull.done) {
|
|
6686
|
-
throw new Error(`Already pulling model: ${activePull.model}`);
|
|
6687
|
-
}
|
|
6688
|
-
activePull = { model: modelName, status: "starting", progress: 0, error: null, done: false };
|
|
6689
|
-
try {
|
|
6690
|
-
const res = await fetch(`${OLLAMA_HOST}/api/pull`, {
|
|
6691
|
-
method: "POST",
|
|
6692
|
-
headers: { "Content-Type": "application/json" },
|
|
6693
|
-
body: JSON.stringify({ name: modelName, stream: true })
|
|
6694
|
-
});
|
|
6695
|
-
if (!res.ok) {
|
|
6696
|
-
const body = await res.text();
|
|
6697
|
-
throw new Error(`Ollama pull failed: ${res.status} ${body}`);
|
|
6698
|
-
}
|
|
6699
|
-
const reader = res.body?.getReader();
|
|
6700
|
-
if (!reader) throw new Error("No response body");
|
|
6701
|
-
const decoder = new TextDecoder();
|
|
6702
|
-
let buffer = "";
|
|
6703
|
-
while (true) {
|
|
6704
|
-
const { done, value } = await reader.read();
|
|
6705
|
-
if (done) break;
|
|
6706
|
-
buffer += decoder.decode(value, { stream: true });
|
|
6707
|
-
const lines = buffer.split("\n");
|
|
6708
|
-
buffer = lines.pop() || "";
|
|
6709
|
-
for (const line of lines) {
|
|
6710
|
-
if (!line.trim()) continue;
|
|
6711
|
-
try {
|
|
6712
|
-
const msg = JSON.parse(line);
|
|
6713
|
-
if (msg.error) {
|
|
6714
|
-
activePull.error = msg.error;
|
|
6715
|
-
activePull.status = "error";
|
|
6716
|
-
activePull.done = true;
|
|
6717
|
-
return;
|
|
6718
|
-
}
|
|
6719
|
-
activePull.status = msg.status;
|
|
6720
|
-
if (msg.total && msg.completed) {
|
|
6721
|
-
activePull.progress = Math.round(msg.completed / msg.total * 100);
|
|
6722
|
-
}
|
|
6723
|
-
if (msg.status === "success") {
|
|
6724
|
-
activePull.progress = 100;
|
|
6725
|
-
activePull.done = true;
|
|
6726
|
-
}
|
|
6727
|
-
} catch {
|
|
6728
|
-
}
|
|
6729
|
-
}
|
|
6730
|
-
}
|
|
6731
|
-
if (!activePull.done) {
|
|
6732
|
-
activePull.done = true;
|
|
6733
|
-
activePull.progress = 100;
|
|
6734
|
-
activePull.status = "success";
|
|
6735
|
-
}
|
|
6736
|
-
} catch (err) {
|
|
6737
|
-
activePull.error = err instanceof Error ? err.message : String(err);
|
|
6738
|
-
activePull.status = "error";
|
|
6739
|
-
activePull.done = true;
|
|
6740
|
-
}
|
|
6741
|
-
}
|
|
6742
|
-
function writePiModelsConfig(modelId) {
|
|
6743
|
-
const dir = join16(homedir9(), ".pi", "agent");
|
|
6744
|
-
mkdirSync6(dir, { recursive: true });
|
|
6745
|
-
let existing = {};
|
|
6746
|
-
if (existsSync15(PI_MODELS_PATH)) {
|
|
6747
|
-
try {
|
|
6748
|
-
existing = JSON.parse(readFileSync5(PI_MODELS_PATH, "utf-8"));
|
|
6749
|
-
} catch {
|
|
6750
|
-
}
|
|
6751
|
-
}
|
|
6752
|
-
const providers2 = existing.providers ?? {};
|
|
6753
|
-
providers2.ollama = {
|
|
6754
|
-
baseUrl: `${OLLAMA_HOST}/v1`,
|
|
6755
|
-
api: "openai-completions",
|
|
6756
|
-
apiKey: "ollama",
|
|
6757
|
-
compat: {
|
|
6758
|
-
supportsDeveloperRole: false,
|
|
6759
|
-
supportsReasoningEffort: false
|
|
6760
|
-
},
|
|
6761
|
-
models: [{ id: modelId }]
|
|
6762
|
-
};
|
|
6763
|
-
const config = { ...existing, providers: providers2 };
|
|
6764
|
-
writeFileSync5(PI_MODELS_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6765
|
-
}
|
|
6766
|
-
|
|
6767
6555
|
// ../server/src/routes/settings-routes.ts
|
|
6768
6556
|
async function registerSettingsRoutes(app) {
|
|
6769
6557
|
app.get("/api/settings", async () => {
|
|
@@ -6776,9 +6564,6 @@ async function registerSettingsRoutes(app) {
|
|
|
6776
6564
|
}
|
|
6777
6565
|
const settings = updateSettings(parsed.data);
|
|
6778
6566
|
restartAutoUpdater();
|
|
6779
|
-
if (parsed.data.piOllamaModel) {
|
|
6780
|
-
writePiModelsConfig(parsed.data.piOllamaModel);
|
|
6781
|
-
}
|
|
6782
6567
|
return settings;
|
|
6783
6568
|
});
|
|
6784
6569
|
app.get("/api/settings/deepgram-key", async (_req, reply) => {
|
|
@@ -7039,550 +6824,15 @@ async function registerMaestroUpdateRoutes(app) {
|
|
|
7039
6824
|
});
|
|
7040
6825
|
}
|
|
7041
6826
|
|
|
7042
|
-
// ../server/src/routes/
|
|
7043
|
-
|
|
7044
|
-
app.get("/api/ollama/status", async () => {
|
|
7045
|
-
return getOllamaStatus();
|
|
7046
|
-
});
|
|
7047
|
-
app.get("/api/ollama/models", async (_req, reply) => {
|
|
7048
|
-
try {
|
|
7049
|
-
const models = await listOllamaModels();
|
|
7050
|
-
return { models };
|
|
7051
|
-
} catch (err) {
|
|
7052
|
-
return reply.status(502).send({
|
|
7053
|
-
error: err instanceof Error ? err.message : "Cannot reach Ollama"
|
|
7054
|
-
});
|
|
7055
|
-
}
|
|
7056
|
-
});
|
|
7057
|
-
app.get("/api/ollama/recommended", async () => {
|
|
7058
|
-
return { models: RECOMMENDED_MODELS };
|
|
7059
|
-
});
|
|
7060
|
-
app.post("/api/ollama/pull", async (req, reply) => {
|
|
7061
|
-
const { model } = req.body;
|
|
7062
|
-
if (!model || typeof model !== "string") {
|
|
7063
|
-
return reply.status(400).send({ error: "model is required" });
|
|
7064
|
-
}
|
|
7065
|
-
try {
|
|
7066
|
-
void pullOllamaModel(model);
|
|
7067
|
-
return { ok: true, model };
|
|
7068
|
-
} catch (err) {
|
|
7069
|
-
return reply.status(409).send({
|
|
7070
|
-
error: err instanceof Error ? err.message : "Pull failed"
|
|
7071
|
-
});
|
|
7072
|
-
}
|
|
7073
|
-
});
|
|
7074
|
-
app.get("/api/ollama/pull/status", async () => {
|
|
7075
|
-
return getPullStatus() ?? { model: null, status: "idle", progress: 0, error: null, done: true };
|
|
7076
|
-
});
|
|
7077
|
-
}
|
|
7078
|
-
|
|
7079
|
-
// ../pi/src/system-prompt.ts
|
|
7080
|
-
function buildAppendSections(base) {
|
|
7081
|
-
return [
|
|
7082
|
-
`## Personality & Behavior
|
|
7083
|
-
|
|
7084
|
-
You are a friendly, conversational AI assistant. You communicate via messaging (Telegram, WhatsApp, etc.).
|
|
7085
|
-
|
|
7086
|
-
### Conversation style
|
|
7087
|
-
- Be warm, natural, and concise. Write like a helpful friend, not a manual.
|
|
7088
|
-
- Greet users back naturally when they greet you.
|
|
7089
|
-
- Use short paragraphs. Avoid walls of text \u2014 this is chat, not a document.
|
|
7090
|
-
- If you don't know something, say so honestly.
|
|
7091
|
-
- Ask for clarification when the request is ambiguous.
|
|
7092
|
-
|
|
7093
|
-
### When to use tools
|
|
7094
|
-
- For casual chat (greetings, questions, advice), just respond with text. No tool calls needed.
|
|
7095
|
-
- For coding, technical, or file-related requests, use your tools to explore and help.
|
|
7096
|
-
- State your intent before making tool calls, but never predict or claim results before receiving them.
|
|
7097
|
-
- Before modifying a file, always read it first. Do not assume files or directories exist.
|
|
7098
|
-
- If a tool call fails, analyze the error before retrying with a different approach.`,
|
|
7099
|
-
...base
|
|
7100
|
-
];
|
|
7101
|
-
}
|
|
6827
|
+
// ../server/src/routes/telegram-routes.ts
|
|
6828
|
+
init_settings();
|
|
7102
6829
|
|
|
7103
|
-
// ../
|
|
7104
|
-
import * as os10 from "os";
|
|
7105
|
-
import * as path17 from "path";
|
|
7106
|
-
import makeWASocket, {
|
|
7107
|
-
useMultiFileAuthState,
|
|
7108
|
-
DisconnectReason,
|
|
7109
|
-
fetchLatestBaileysVersion,
|
|
7110
|
-
makeCacheableSignalKeyStore
|
|
7111
|
-
} from "@whiskeysockets/baileys";
|
|
7112
|
-
import * as QRCode from "qrcode";
|
|
7113
|
-
var DATA_DIR = path17.join(os10.homedir(), ".maestro");
|
|
7114
|
-
var AUTH_DIR = path17.join(DATA_DIR, "whatsapp-auth");
|
|
7115
|
-
var sock = null;
|
|
7116
|
-
var io = null;
|
|
6830
|
+
// ../server/src/services/telegram.ts
|
|
7117
6831
|
var connectionStatus = "disconnected";
|
|
7118
|
-
var currentQrCode = null;
|
|
7119
|
-
var currentQrRaw = null;
|
|
7120
|
-
var reconnectAttempts = 0;
|
|
7121
|
-
var reconnectTimer = null;
|
|
7122
|
-
var messageCallback = null;
|
|
7123
|
-
function getAllowedJids() {
|
|
7124
|
-
const raw = process.env.WHATSAPP_ALLOWED_JIDS || "";
|
|
7125
|
-
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7126
|
-
}
|
|
7127
|
-
function isJidAllowed(jid) {
|
|
7128
|
-
const allowed = getAllowedJids();
|
|
7129
|
-
if (allowed.length === 0) return true;
|
|
7130
|
-
return allowed.some((a) => jid.includes(a));
|
|
7131
|
-
}
|
|
7132
|
-
function setStatus(status) {
|
|
7133
|
-
connectionStatus = status;
|
|
7134
|
-
io?.emit("whatsapp:status", { status });
|
|
7135
|
-
}
|
|
7136
|
-
function onWhatsAppMessage(cb) {
|
|
7137
|
-
messageCallback = cb;
|
|
7138
|
-
}
|
|
7139
|
-
async function startWhatsApp(socketIo) {
|
|
7140
|
-
io = socketIo;
|
|
7141
|
-
if (sock) {
|
|
7142
|
-
console.log("[whatsapp] Already connected, skipping start");
|
|
7143
|
-
return;
|
|
7144
|
-
}
|
|
7145
|
-
setStatus("connecting");
|
|
7146
|
-
reconnectAttempts = 0;
|
|
7147
|
-
await connectBaileys();
|
|
7148
|
-
}
|
|
7149
|
-
async function connectBaileys() {
|
|
7150
|
-
try {
|
|
7151
|
-
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
|
|
7152
|
-
const { version } = await fetchLatestBaileysVersion();
|
|
7153
|
-
sock = makeWASocket({
|
|
7154
|
-
version,
|
|
7155
|
-
auth: {
|
|
7156
|
-
creds: state.creds,
|
|
7157
|
-
keys: makeCacheableSignalKeyStore(state.keys, void 0)
|
|
7158
|
-
},
|
|
7159
|
-
generateHighQualityLinkPreview: false
|
|
7160
|
-
});
|
|
7161
|
-
sock.ev.on("creds.update", saveCreds);
|
|
7162
|
-
sock.ev.on("connection.update", (update) => {
|
|
7163
|
-
const { connection, lastDisconnect, qr } = update;
|
|
7164
|
-
if (qr) {
|
|
7165
|
-
setStatus("qr_pending");
|
|
7166
|
-
currentQrRaw = qr;
|
|
7167
|
-
QRCode.toDataURL(qr).then((url) => {
|
|
7168
|
-
currentQrCode = url;
|
|
7169
|
-
io?.emit("whatsapp:qr", { qrCode: url });
|
|
7170
|
-
}).catch((err) => {
|
|
7171
|
-
console.error("[whatsapp] Failed to generate QR code:", err);
|
|
7172
|
-
});
|
|
7173
|
-
QRCode.toString(qr, { type: "terminal", small: true, margin: 1 }).then((ascii) => {
|
|
7174
|
-
console.log("[whatsapp] Scan this QR code with WhatsApp:\n" + ascii);
|
|
7175
|
-
console.log("[whatsapp] If the QR code above doesn't work, use this code manually:\n" + qr);
|
|
7176
|
-
}).catch(() => {
|
|
7177
|
-
});
|
|
7178
|
-
}
|
|
7179
|
-
if (connection === "close") {
|
|
7180
|
-
sock = null;
|
|
7181
|
-
currentQrCode = null;
|
|
7182
|
-
currentQrRaw = null;
|
|
7183
|
-
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
7184
|
-
const loggedOut = statusCode === DisconnectReason.loggedOut;
|
|
7185
|
-
if (loggedOut) {
|
|
7186
|
-
console.log("[whatsapp] Logged out \u2014 clearing auth state");
|
|
7187
|
-
setStatus("disconnected");
|
|
7188
|
-
reconnectAttempts = 0;
|
|
7189
|
-
return;
|
|
7190
|
-
}
|
|
7191
|
-
const isQrTimeout = statusCode === DisconnectReason.timedOut || statusCode === 515;
|
|
7192
|
-
if (isQrTimeout) {
|
|
7193
|
-
console.log("[whatsapp] QR expired, reconnecting immediately for a fresh code...");
|
|
7194
|
-
setStatus("connecting");
|
|
7195
|
-
reconnectTimer = setTimeout(() => connectBaileys(), 1e3);
|
|
7196
|
-
return;
|
|
7197
|
-
}
|
|
7198
|
-
reconnectAttempts++;
|
|
7199
|
-
const delay = Math.min(1e3 * Math.pow(2, reconnectAttempts), 6e4);
|
|
7200
|
-
console.log(`[whatsapp] Connection closed (code=${statusCode}), reconnecting in ${delay}ms...`);
|
|
7201
|
-
setStatus("connecting");
|
|
7202
|
-
reconnectTimer = setTimeout(() => connectBaileys(), delay);
|
|
7203
|
-
}
|
|
7204
|
-
if (connection === "open") {
|
|
7205
|
-
console.log("[whatsapp] Connected successfully");
|
|
7206
|
-
setStatus("connected");
|
|
7207
|
-
currentQrCode = null;
|
|
7208
|
-
currentQrRaw = null;
|
|
7209
|
-
reconnectAttempts = 0;
|
|
7210
|
-
}
|
|
7211
|
-
});
|
|
7212
|
-
sock.ev.on("messages.upsert", (m) => {
|
|
7213
|
-
if (m.type !== "notify") return;
|
|
7214
|
-
for (const msg of m.messages) {
|
|
7215
|
-
const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text;
|
|
7216
|
-
if (!text) continue;
|
|
7217
|
-
const chatJid = msg.key.remoteJid;
|
|
7218
|
-
if (!chatJid) continue;
|
|
7219
|
-
if (!isJidAllowed(chatJid)) {
|
|
7220
|
-
console.log(`[whatsapp] Ignoring message from non-allowed JID: ${chatJid}`);
|
|
7221
|
-
continue;
|
|
7222
|
-
}
|
|
7223
|
-
const messageId = msg.key.id || "";
|
|
7224
|
-
const senderName = msg.pushName || chatJid.split("@")[0];
|
|
7225
|
-
console.log(`[whatsapp] Incoming message from ${senderName} (${chatJid}): ${text.slice(0, 80)}`);
|
|
7226
|
-
messageCallback?.(chatJid, messageId, text, senderName);
|
|
7227
|
-
}
|
|
7228
|
-
});
|
|
7229
|
-
} catch (error) {
|
|
7230
|
-
console.error("[whatsapp] Failed to connect:", error);
|
|
7231
|
-
setStatus("disconnected");
|
|
7232
|
-
}
|
|
7233
|
-
}
|
|
7234
|
-
async function stopWhatsApp() {
|
|
7235
|
-
if (reconnectTimer) {
|
|
7236
|
-
clearTimeout(reconnectTimer);
|
|
7237
|
-
reconnectTimer = null;
|
|
7238
|
-
}
|
|
7239
|
-
if (sock) {
|
|
7240
|
-
sock.end(void 0);
|
|
7241
|
-
sock = null;
|
|
7242
|
-
}
|
|
7243
|
-
currentQrCode = null;
|
|
7244
|
-
currentQrRaw = null;
|
|
7245
|
-
setStatus("disconnected");
|
|
7246
|
-
}
|
|
7247
|
-
function getWhatsAppStatus() {
|
|
7248
|
-
return { status: connectionStatus, qrCode: currentQrCode, qrRaw: currentQrRaw };
|
|
7249
|
-
}
|
|
7250
|
-
async function sendWhatsAppMessage(jid, text) {
|
|
7251
|
-
if (!sock) {
|
|
7252
|
-
throw new Error("WhatsApp is not connected");
|
|
7253
|
-
}
|
|
7254
|
-
await sock.sendMessage(jid, { text });
|
|
7255
|
-
}
|
|
7256
|
-
|
|
7257
|
-
// ../pi/src/channels/whatsapp/queue.ts
|
|
7258
|
-
import {
|
|
7259
|
-
AuthStorage,
|
|
7260
|
-
createAgentSession,
|
|
7261
|
-
createCodingTools,
|
|
7262
|
-
DefaultResourceLoader,
|
|
7263
|
-
ModelRegistry,
|
|
7264
|
-
SessionManager
|
|
7265
|
-
} from "@mariozechner/pi-coding-agent";
|
|
7266
|
-
|
|
7267
|
-
// ../pi/src/channels/whatsapp/store.ts
|
|
7268
|
-
import * as fs16 from "fs";
|
|
7269
|
-
import * as os11 from "os";
|
|
7270
|
-
import * as path18 from "path";
|
|
7271
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
7272
|
-
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
7273
|
-
var DATA_DIR2 = path18.join(os11.homedir(), ".maestro");
|
|
7274
|
-
fs16.mkdirSync(DATA_DIR2, { recursive: true });
|
|
7275
|
-
var dbPath2 = path18.join(DATA_DIR2, "pi.sqlite");
|
|
7276
|
-
var db2 = new DatabaseSync2(dbPath2);
|
|
7277
|
-
db2.exec(`
|
|
7278
|
-
CREATE TABLE IF NOT EXISTS whatsapp_messages (
|
|
7279
|
-
id TEXT PRIMARY KEY,
|
|
7280
|
-
chat_jid TEXT NOT NULL,
|
|
7281
|
-
message_id TEXT NOT NULL UNIQUE,
|
|
7282
|
-
direction TEXT NOT NULL,
|
|
7283
|
-
sender_name TEXT,
|
|
7284
|
-
body TEXT NOT NULL,
|
|
7285
|
-
status TEXT NOT NULL DEFAULT 'queued',
|
|
7286
|
-
response_text TEXT,
|
|
7287
|
-
error TEXT,
|
|
7288
|
-
created_at TEXT NOT NULL,
|
|
7289
|
-
processed_at TEXT
|
|
7290
|
-
);
|
|
7291
|
-
CREATE INDEX IF NOT EXISTS idx_wa_status ON whatsapp_messages(status);
|
|
7292
|
-
CREATE INDEX IF NOT EXISTS idx_wa_chat ON whatsapp_messages(chat_jid, created_at);
|
|
7293
|
-
`);
|
|
7294
|
-
function nowIso3() {
|
|
7295
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7296
|
-
}
|
|
7297
|
-
function toWhatsAppMessage(row) {
|
|
7298
|
-
return {
|
|
7299
|
-
id: String(row.id),
|
|
7300
|
-
chatJid: String(row.chat_jid),
|
|
7301
|
-
messageId: String(row.message_id),
|
|
7302
|
-
direction: String(row.direction),
|
|
7303
|
-
senderName: row.sender_name ? String(row.sender_name) : null,
|
|
7304
|
-
body: String(row.body),
|
|
7305
|
-
status: String(row.status),
|
|
7306
|
-
responseText: row.response_text ? String(row.response_text) : null,
|
|
7307
|
-
error: row.error ? String(row.error) : null,
|
|
7308
|
-
createdAt: String(row.created_at),
|
|
7309
|
-
processedAt: row.processed_at ? String(row.processed_at) : null
|
|
7310
|
-
};
|
|
7311
|
-
}
|
|
7312
|
-
function insertWhatsAppMessage(input) {
|
|
7313
|
-
const id = randomUUID5();
|
|
7314
|
-
const now = nowIso3();
|
|
7315
|
-
const status = input.status ?? "queued";
|
|
7316
|
-
db2.prepare(`
|
|
7317
|
-
INSERT INTO whatsapp_messages (id, chat_jid, message_id, direction, sender_name, body, status, created_at)
|
|
7318
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
7319
|
-
`).run(id, input.chatJid, input.messageId, input.direction, input.senderName, input.body, status, now);
|
|
7320
|
-
return {
|
|
7321
|
-
id,
|
|
7322
|
-
chatJid: input.chatJid,
|
|
7323
|
-
messageId: input.messageId,
|
|
7324
|
-
direction: input.direction,
|
|
7325
|
-
senderName: input.senderName,
|
|
7326
|
-
body: input.body,
|
|
7327
|
-
status,
|
|
7328
|
-
responseText: null,
|
|
7329
|
-
error: null,
|
|
7330
|
-
createdAt: now,
|
|
7331
|
-
processedAt: null
|
|
7332
|
-
};
|
|
7333
|
-
}
|
|
7334
|
-
function getNextQueuedMessage() {
|
|
7335
|
-
const row = db2.prepare("SELECT * FROM whatsapp_messages WHERE status = 'queued' AND direction = 'incoming' ORDER BY created_at ASC LIMIT 1").get();
|
|
7336
|
-
return row ? toWhatsAppMessage(row) : null;
|
|
7337
|
-
}
|
|
7338
|
-
function updateWhatsAppMessage(id, patch) {
|
|
7339
|
-
const sets = [];
|
|
7340
|
-
const values = [];
|
|
7341
|
-
if (patch.status !== void 0) {
|
|
7342
|
-
sets.push("status = ?");
|
|
7343
|
-
values.push(patch.status ?? null);
|
|
7344
|
-
}
|
|
7345
|
-
if (patch.responseText !== void 0) {
|
|
7346
|
-
sets.push("response_text = ?");
|
|
7347
|
-
values.push(patch.responseText ?? null);
|
|
7348
|
-
}
|
|
7349
|
-
if (patch.error !== void 0) {
|
|
7350
|
-
sets.push("error = ?");
|
|
7351
|
-
values.push(patch.error ?? null);
|
|
7352
|
-
}
|
|
7353
|
-
if (patch.processedAt !== void 0) {
|
|
7354
|
-
sets.push("processed_at = ?");
|
|
7355
|
-
values.push(patch.processedAt ?? null);
|
|
7356
|
-
}
|
|
7357
|
-
if (sets.length === 0) return;
|
|
7358
|
-
values.push(id);
|
|
7359
|
-
db2.prepare(`UPDATE whatsapp_messages SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
7360
|
-
}
|
|
7361
|
-
function listWhatsAppMessages(chatJid, limit = 50) {
|
|
7362
|
-
if (chatJid) {
|
|
7363
|
-
const rows2 = db2.prepare("SELECT * FROM whatsapp_messages WHERE chat_jid = ? ORDER BY created_at DESC LIMIT ?").all(chatJid, limit);
|
|
7364
|
-
return rows2.map(toWhatsAppMessage);
|
|
7365
|
-
}
|
|
7366
|
-
const rows = db2.prepare("SELECT * FROM whatsapp_messages ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
7367
|
-
return rows.map(toWhatsAppMessage);
|
|
7368
|
-
}
|
|
7369
|
-
function whatsAppMessageExists(messageId) {
|
|
7370
|
-
const row = db2.prepare("SELECT 1 FROM whatsapp_messages WHERE message_id = ?").get(messageId);
|
|
7371
|
-
return !!row;
|
|
7372
|
-
}
|
|
7373
|
-
|
|
7374
|
-
// ../pi/src/channels/whatsapp/queue.ts
|
|
7375
|
-
var session = null;
|
|
7376
|
-
var isProcessing = false;
|
|
7377
|
-
var PI_PROJECT_PATH = process.env.PI_PROJECT_PATH || "/tmp/maestro-whatsapp";
|
|
7378
|
-
var PI_TIMEOUT_MS = parseInt(process.env.PI_TIMEOUT_MS || "300000", 10);
|
|
7379
|
-
function nowIso4() {
|
|
7380
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7381
|
-
}
|
|
7382
|
-
async function ensureSession() {
|
|
7383
|
-
if (session) return session;
|
|
7384
|
-
const authStorage = AuthStorage.create();
|
|
7385
|
-
const modelRegistry = new ModelRegistry(authStorage);
|
|
7386
|
-
const available = await modelRegistry.getAvailable();
|
|
7387
|
-
const model = available[0];
|
|
7388
|
-
if (!model) {
|
|
7389
|
-
throw new Error(
|
|
7390
|
-
"No model available. Configure an Ollama model in Settings > Pi Agent."
|
|
7391
|
-
);
|
|
7392
|
-
}
|
|
7393
|
-
console.log(`[whatsapp-queue] Using model: ${model.id} (provider: ${model.provider})`);
|
|
7394
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
7395
|
-
cwd: PI_PROJECT_PATH,
|
|
7396
|
-
appendSystemPromptOverride: (base) => buildAppendSections(base)
|
|
7397
|
-
});
|
|
7398
|
-
await resourceLoader.reload();
|
|
7399
|
-
const { session: s } = await createAgentSession({
|
|
7400
|
-
cwd: PI_PROJECT_PATH,
|
|
7401
|
-
tools: createCodingTools(PI_PROJECT_PATH),
|
|
7402
|
-
sessionManager: SessionManager.continueRecent(PI_PROJECT_PATH),
|
|
7403
|
-
resourceLoader,
|
|
7404
|
-
authStorage,
|
|
7405
|
-
modelRegistry,
|
|
7406
|
-
model
|
|
7407
|
-
});
|
|
7408
|
-
session = s;
|
|
7409
|
-
console.log("[whatsapp-queue] Pi agent session created");
|
|
7410
|
-
return s;
|
|
7411
|
-
}
|
|
7412
|
-
function sendPrompt(s, message) {
|
|
7413
|
-
return new Promise((resolve5, reject) => {
|
|
7414
|
-
let responseText = "";
|
|
7415
|
-
let done = false;
|
|
7416
|
-
const timer4 = setTimeout(() => {
|
|
7417
|
-
if (!done) {
|
|
7418
|
-
done = true;
|
|
7419
|
-
unsubscribe();
|
|
7420
|
-
reject(new Error("Pi agent timed out"));
|
|
7421
|
-
}
|
|
7422
|
-
}, PI_TIMEOUT_MS);
|
|
7423
|
-
const unsubscribe = s.subscribe((event) => {
|
|
7424
|
-
if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
7425
|
-
responseText += event.assistantMessageEvent.delta;
|
|
7426
|
-
}
|
|
7427
|
-
if (event.type === "agent_end") {
|
|
7428
|
-
done = true;
|
|
7429
|
-
clearTimeout(timer4);
|
|
7430
|
-
unsubscribe();
|
|
7431
|
-
const messages = event.messages || [];
|
|
7432
|
-
const lastAssistant = messages.findLast((m) => m.role === "assistant");
|
|
7433
|
-
if (lastAssistant?.stopReason === "error" && lastAssistant?.errorMessage) {
|
|
7434
|
-
reject(new Error(lastAssistant.errorMessage));
|
|
7435
|
-
return;
|
|
7436
|
-
}
|
|
7437
|
-
resolve5(responseText.trim() || "Agent completed without text output.");
|
|
7438
|
-
}
|
|
7439
|
-
});
|
|
7440
|
-
s.prompt(message).catch((err) => {
|
|
7441
|
-
if (!done) {
|
|
7442
|
-
done = true;
|
|
7443
|
-
clearTimeout(timer4);
|
|
7444
|
-
unsubscribe();
|
|
7445
|
-
reject(err);
|
|
7446
|
-
}
|
|
7447
|
-
});
|
|
7448
|
-
});
|
|
7449
|
-
}
|
|
7450
|
-
function startWhatsAppQueue() {
|
|
7451
|
-
onWhatsAppMessage((chatJid, messageId, body, senderName) => {
|
|
7452
|
-
onMessage(chatJid, messageId, body, senderName);
|
|
7453
|
-
});
|
|
7454
|
-
console.log("[whatsapp-queue] Queue processor started");
|
|
7455
|
-
}
|
|
7456
|
-
function stopWhatsAppQueue() {
|
|
7457
|
-
session = null;
|
|
7458
|
-
console.log("[whatsapp-queue] Queue processor stopped");
|
|
7459
|
-
}
|
|
7460
|
-
function onMessage(chatJid, messageId, body, senderName) {
|
|
7461
|
-
if (whatsAppMessageExists(messageId)) {
|
|
7462
|
-
console.log(`[whatsapp-queue] Duplicate message ${messageId}, skipping`);
|
|
7463
|
-
return;
|
|
7464
|
-
}
|
|
7465
|
-
insertWhatsAppMessage({
|
|
7466
|
-
chatJid,
|
|
7467
|
-
messageId,
|
|
7468
|
-
direction: "incoming",
|
|
7469
|
-
senderName,
|
|
7470
|
-
body,
|
|
7471
|
-
status: "queued"
|
|
7472
|
-
});
|
|
7473
|
-
console.log(`[whatsapp-queue] Queued message from ${senderName}`);
|
|
7474
|
-
processNext();
|
|
7475
|
-
}
|
|
7476
|
-
async function processNext() {
|
|
7477
|
-
if (isProcessing) return;
|
|
7478
|
-
const msg = getNextQueuedMessage();
|
|
7479
|
-
if (!msg) return;
|
|
7480
|
-
isProcessing = true;
|
|
7481
|
-
try {
|
|
7482
|
-
updateWhatsAppMessage(msg.id, { status: "processing" });
|
|
7483
|
-
const s = await ensureSession();
|
|
7484
|
-
if (msg.body.trim().toLowerCase() === "reset") {
|
|
7485
|
-
await s.newSession();
|
|
7486
|
-
await sendWhatsAppMessage(msg.chatJid, "Session reset.");
|
|
7487
|
-
updateWhatsAppMessage(msg.id, { status: "completed", responseText: "Session reset.", processedAt: nowIso4() });
|
|
7488
|
-
console.log("[whatsapp-queue] Session reset by user");
|
|
7489
|
-
return;
|
|
7490
|
-
}
|
|
7491
|
-
const responseText = await sendPrompt(s, msg.body);
|
|
7492
|
-
await sendWhatsAppMessage(msg.chatJid, responseText);
|
|
7493
|
-
insertWhatsAppMessage({
|
|
7494
|
-
chatJid: msg.chatJid,
|
|
7495
|
-
messageId: `out_${msg.id}`,
|
|
7496
|
-
direction: "outgoing",
|
|
7497
|
-
senderName: null,
|
|
7498
|
-
body: responseText,
|
|
7499
|
-
status: "completed"
|
|
7500
|
-
});
|
|
7501
|
-
updateWhatsAppMessage(msg.id, {
|
|
7502
|
-
status: "completed",
|
|
7503
|
-
responseText,
|
|
7504
|
-
processedAt: nowIso4()
|
|
7505
|
-
});
|
|
7506
|
-
console.log(`[whatsapp-queue] Completed message ${msg.id}`);
|
|
7507
|
-
} catch (error) {
|
|
7508
|
-
const errMsg = error instanceof Error ? error.message : String(error);
|
|
7509
|
-
console.error(`[whatsapp-queue] Failed to process message ${msg.id}:`, errMsg);
|
|
7510
|
-
updateWhatsAppMessage(msg.id, {
|
|
7511
|
-
status: "failed",
|
|
7512
|
-
error: errMsg,
|
|
7513
|
-
processedAt: nowIso4()
|
|
7514
|
-
});
|
|
7515
|
-
} finally {
|
|
7516
|
-
isProcessing = false;
|
|
7517
|
-
processNext();
|
|
7518
|
-
}
|
|
7519
|
-
}
|
|
7520
|
-
|
|
7521
|
-
// ../pi/src/channels/whatsapp/routes.ts
|
|
7522
|
-
async function registerWhatsAppRoutes(app, io3) {
|
|
7523
|
-
app.get("/api/integrations/whatsapp", async () => {
|
|
7524
|
-
return getWhatsAppStatus();
|
|
7525
|
-
});
|
|
7526
|
-
app.post("/api/integrations/whatsapp/connect", async (_req, reply) => {
|
|
7527
|
-
try {
|
|
7528
|
-
await startWhatsApp(io3);
|
|
7529
|
-
return getWhatsAppStatus();
|
|
7530
|
-
} catch (error) {
|
|
7531
|
-
return reply.status(500).send({
|
|
7532
|
-
error: error instanceof Error ? error.message : "Failed to start WhatsApp"
|
|
7533
|
-
});
|
|
7534
|
-
}
|
|
7535
|
-
});
|
|
7536
|
-
app.delete("/api/integrations/whatsapp/connect", async () => {
|
|
7537
|
-
await stopWhatsApp();
|
|
7538
|
-
return { ok: true };
|
|
7539
|
-
});
|
|
7540
|
-
app.get("/api/integrations/whatsapp/messages", async (req) => {
|
|
7541
|
-
const query = req.query;
|
|
7542
|
-
const chatJid = query.chatJid || void 0;
|
|
7543
|
-
const limit = query.limit ? parseInt(query.limit, 10) : 50;
|
|
7544
|
-
return listWhatsAppMessages(chatJid, limit);
|
|
7545
|
-
});
|
|
7546
|
-
app.post("/api/integrations/whatsapp/send", async (req, reply) => {
|
|
7547
|
-
try {
|
|
7548
|
-
const body = req.body;
|
|
7549
|
-
if (!body.jid || !body.text) {
|
|
7550
|
-
return reply.status(400).send({ error: "jid and text are required" });
|
|
7551
|
-
}
|
|
7552
|
-
await sendWhatsAppMessage(body.jid, body.text);
|
|
7553
|
-
return { ok: true };
|
|
7554
|
-
} catch (error) {
|
|
7555
|
-
return reply.status(500).send({
|
|
7556
|
-
error: error instanceof Error ? error.message : "Failed to send message"
|
|
7557
|
-
});
|
|
7558
|
-
}
|
|
7559
|
-
});
|
|
7560
|
-
}
|
|
7561
|
-
|
|
7562
|
-
// ../pi/src/channels/telegram/telegram.ts
|
|
7563
|
-
var io2 = null;
|
|
7564
|
-
var connectionStatus2 = "disconnected";
|
|
7565
6832
|
var botToken = null;
|
|
7566
6833
|
var botUsername = null;
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
var messageCallback2 = null;
|
|
7570
|
-
function getAllowedChatIds() {
|
|
7571
|
-
const raw = process.env.TELEGRAM_ALLOWED_CHAT_IDS || "";
|
|
7572
|
-
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7573
|
-
}
|
|
7574
|
-
function isChatAllowed(chatId) {
|
|
7575
|
-
const allowed = getAllowedChatIds();
|
|
7576
|
-
if (allowed.length === 0) return true;
|
|
7577
|
-
return allowed.includes(chatId);
|
|
7578
|
-
}
|
|
7579
|
-
function setStatus2(status) {
|
|
7580
|
-
connectionStatus2 = status;
|
|
7581
|
-
io2?.emit("telegram:status", { status });
|
|
7582
|
-
}
|
|
7583
|
-
async function telegramApi(method, body) {
|
|
7584
|
-
const url = `https://api.telegram.org/bot${botToken}/${method}`;
|
|
7585
|
-
const res = await fetch(url, {
|
|
6834
|
+
async function telegramApi(token, method, body) {
|
|
6835
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/${method}`, {
|
|
7586
6836
|
method: "POST",
|
|
7587
6837
|
headers: { "Content-Type": "application/json" },
|
|
7588
6838
|
body: body ? JSON.stringify(body) : void 0
|
|
@@ -7593,363 +6843,43 @@ async function telegramApi(method, body) {
|
|
|
7593
6843
|
}
|
|
7594
6844
|
return json.result;
|
|
7595
6845
|
}
|
|
7596
|
-
function
|
|
7597
|
-
messageCallback2 = cb;
|
|
7598
|
-
}
|
|
7599
|
-
async function startTelegram(socketIo, token) {
|
|
7600
|
-
io2 = socketIo;
|
|
6846
|
+
async function startTelegram(token) {
|
|
7601
6847
|
const resolvedToken = token || process.env.TELEGRAM_BOT_TOKEN;
|
|
7602
6848
|
if (!resolvedToken) {
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
return;
|
|
6849
|
+
connectionStatus = "error";
|
|
6850
|
+
throw new Error("No Telegram bot token configured");
|
|
7606
6851
|
}
|
|
7607
6852
|
botToken = resolvedToken;
|
|
7608
|
-
|
|
6853
|
+
connectionStatus = "connecting";
|
|
7609
6854
|
try {
|
|
7610
|
-
const me = await telegramApi("getMe");
|
|
6855
|
+
const me = await telegramApi(resolvedToken, "getMe");
|
|
7611
6856
|
botUsername = me.username || null;
|
|
7612
|
-
|
|
7613
|
-
setStatus2("connected");
|
|
7614
|
-
lastUpdateId = 0;
|
|
7615
|
-
pollUpdates();
|
|
7616
|
-
} catch (error) {
|
|
7617
|
-
console.error("[telegram] Failed to connect:", error);
|
|
7618
|
-
setStatus2("error");
|
|
7619
|
-
}
|
|
7620
|
-
}
|
|
7621
|
-
async function pollUpdates() {
|
|
7622
|
-
if (connectionStatus2 !== "connected" || !botToken) return;
|
|
7623
|
-
try {
|
|
7624
|
-
const updates = await telegramApi("getUpdates", {
|
|
7625
|
-
offset: lastUpdateId + 1,
|
|
7626
|
-
timeout: 30
|
|
7627
|
-
});
|
|
7628
|
-
for (const update of updates) {
|
|
7629
|
-
lastUpdateId = update.update_id;
|
|
7630
|
-
if (!update.message?.text) continue;
|
|
7631
|
-
const chatId = String(update.message.chat.id);
|
|
7632
|
-
if (!isChatAllowed(chatId)) {
|
|
7633
|
-
console.log(`[telegram] Ignoring message from non-allowed chat: ${chatId}`);
|
|
7634
|
-
continue;
|
|
7635
|
-
}
|
|
7636
|
-
const messageId = String(update.message.message_id);
|
|
7637
|
-
const from = update.message.from;
|
|
7638
|
-
const senderName = from ? [from.first_name, from.last_name].filter(Boolean).join(" ") || from.username || chatId : chatId;
|
|
7639
|
-
console.log(`[telegram] Incoming message from ${senderName} (${chatId}): ${update.message.text.slice(0, 80)}`);
|
|
7640
|
-
messageCallback2?.(chatId, messageId, update.message.text, senderName);
|
|
7641
|
-
}
|
|
6857
|
+
connectionStatus = "connected";
|
|
7642
6858
|
} catch (error) {
|
|
7643
|
-
|
|
6859
|
+
botToken = null;
|
|
6860
|
+
botUsername = null;
|
|
6861
|
+
connectionStatus = "error";
|
|
6862
|
+
throw error;
|
|
7644
6863
|
}
|
|
7645
|
-
pollTimer = setTimeout(() => pollUpdates(), 500);
|
|
7646
6864
|
}
|
|
7647
6865
|
async function stopTelegram() {
|
|
7648
|
-
if (pollTimer) {
|
|
7649
|
-
clearTimeout(pollTimer);
|
|
7650
|
-
pollTimer = null;
|
|
7651
|
-
}
|
|
7652
6866
|
botToken = null;
|
|
7653
6867
|
botUsername = null;
|
|
7654
|
-
|
|
7655
|
-
setStatus2("disconnected");
|
|
6868
|
+
connectionStatus = "disconnected";
|
|
7656
6869
|
}
|
|
7657
6870
|
function getTelegramStatus() {
|
|
7658
|
-
return { status:
|
|
7659
|
-
}
|
|
7660
|
-
async function sendTelegramMessage(chatId, text) {
|
|
7661
|
-
if (!botToken) {
|
|
7662
|
-
throw new Error("Telegram bot is not connected");
|
|
7663
|
-
}
|
|
7664
|
-
if (text.length <= 4096) {
|
|
7665
|
-
await telegramApi("sendMessage", { chat_id: chatId, text });
|
|
7666
|
-
return;
|
|
7667
|
-
}
|
|
7668
|
-
const chunks = [];
|
|
7669
|
-
let remaining = text;
|
|
7670
|
-
while (remaining.length > 0) {
|
|
7671
|
-
chunks.push(remaining.slice(0, 4096));
|
|
7672
|
-
remaining = remaining.slice(4096);
|
|
7673
|
-
}
|
|
7674
|
-
for (const chunk of chunks) {
|
|
7675
|
-
await telegramApi("sendMessage", { chat_id: chatId, text: chunk });
|
|
7676
|
-
}
|
|
6871
|
+
return { status: connectionStatus, botUsername };
|
|
7677
6872
|
}
|
|
7678
6873
|
|
|
7679
|
-
// ../
|
|
7680
|
-
|
|
7681
|
-
AuthStorage as AuthStorage2,
|
|
7682
|
-
createAgentSession as createAgentSession2,
|
|
7683
|
-
createCodingTools as createCodingTools2,
|
|
7684
|
-
DefaultResourceLoader as DefaultResourceLoader2,
|
|
7685
|
-
ModelRegistry as ModelRegistry2,
|
|
7686
|
-
SessionManager as SessionManager2
|
|
7687
|
-
} from "@mariozechner/pi-coding-agent";
|
|
7688
|
-
|
|
7689
|
-
// ../pi/src/channels/telegram/store.ts
|
|
7690
|
-
import * as fs17 from "fs";
|
|
7691
|
-
import * as os12 from "os";
|
|
7692
|
-
import * as path19 from "path";
|
|
7693
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
7694
|
-
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
7695
|
-
var DATA_DIR3 = path19.join(os12.homedir(), ".maestro");
|
|
7696
|
-
fs17.mkdirSync(DATA_DIR3, { recursive: true });
|
|
7697
|
-
var dbPath3 = path19.join(DATA_DIR3, "pi.sqlite");
|
|
7698
|
-
var db3 = new DatabaseSync3(dbPath3);
|
|
7699
|
-
db3.exec(`
|
|
7700
|
-
CREATE TABLE IF NOT EXISTS telegram_messages (
|
|
7701
|
-
id TEXT PRIMARY KEY,
|
|
7702
|
-
chat_id TEXT NOT NULL,
|
|
7703
|
-
message_id TEXT NOT NULL UNIQUE,
|
|
7704
|
-
direction TEXT NOT NULL,
|
|
7705
|
-
sender_name TEXT,
|
|
7706
|
-
body TEXT NOT NULL,
|
|
7707
|
-
status TEXT NOT NULL DEFAULT 'queued',
|
|
7708
|
-
response_text TEXT,
|
|
7709
|
-
error TEXT,
|
|
7710
|
-
created_at TEXT NOT NULL,
|
|
7711
|
-
processed_at TEXT
|
|
7712
|
-
);
|
|
7713
|
-
CREATE INDEX IF NOT EXISTS idx_tg_status ON telegram_messages(status);
|
|
7714
|
-
CREATE INDEX IF NOT EXISTS idx_tg_chat ON telegram_messages(chat_id, created_at);
|
|
7715
|
-
`);
|
|
7716
|
-
function nowIso5() {
|
|
7717
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7718
|
-
}
|
|
7719
|
-
function toTelegramMessage(row) {
|
|
7720
|
-
return {
|
|
7721
|
-
id: String(row.id),
|
|
7722
|
-
chatId: String(row.chat_id),
|
|
7723
|
-
messageId: String(row.message_id),
|
|
7724
|
-
direction: String(row.direction),
|
|
7725
|
-
senderName: row.sender_name ? String(row.sender_name) : null,
|
|
7726
|
-
body: String(row.body),
|
|
7727
|
-
status: String(row.status),
|
|
7728
|
-
responseText: row.response_text ? String(row.response_text) : null,
|
|
7729
|
-
error: row.error ? String(row.error) : null,
|
|
7730
|
-
createdAt: String(row.created_at),
|
|
7731
|
-
processedAt: row.processed_at ? String(row.processed_at) : null
|
|
7732
|
-
};
|
|
7733
|
-
}
|
|
7734
|
-
function insertTelegramMessage(input) {
|
|
7735
|
-
const id = randomUUID6();
|
|
7736
|
-
const now = nowIso5();
|
|
7737
|
-
const status = input.status ?? "queued";
|
|
7738
|
-
db3.prepare(`
|
|
7739
|
-
INSERT INTO telegram_messages (id, chat_id, message_id, direction, sender_name, body, status, created_at)
|
|
7740
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
7741
|
-
`).run(id, input.chatId, input.messageId, input.direction, input.senderName, input.body, status, now);
|
|
7742
|
-
return {
|
|
7743
|
-
id,
|
|
7744
|
-
chatId: input.chatId,
|
|
7745
|
-
messageId: input.messageId,
|
|
7746
|
-
direction: input.direction,
|
|
7747
|
-
senderName: input.senderName,
|
|
7748
|
-
body: input.body,
|
|
7749
|
-
status,
|
|
7750
|
-
responseText: null,
|
|
7751
|
-
error: null,
|
|
7752
|
-
createdAt: now,
|
|
7753
|
-
processedAt: null
|
|
7754
|
-
};
|
|
7755
|
-
}
|
|
7756
|
-
function getNextQueuedTelegramMessage() {
|
|
7757
|
-
const row = db3.prepare("SELECT * FROM telegram_messages WHERE status = 'queued' AND direction = 'incoming' ORDER BY created_at ASC LIMIT 1").get();
|
|
7758
|
-
return row ? toTelegramMessage(row) : null;
|
|
7759
|
-
}
|
|
7760
|
-
function updateTelegramMessage(id, patch) {
|
|
7761
|
-
const sets = [];
|
|
7762
|
-
const values = [];
|
|
7763
|
-
if (patch.status !== void 0) {
|
|
7764
|
-
sets.push("status = ?");
|
|
7765
|
-
values.push(patch.status ?? null);
|
|
7766
|
-
}
|
|
7767
|
-
if (patch.responseText !== void 0) {
|
|
7768
|
-
sets.push("response_text = ?");
|
|
7769
|
-
values.push(patch.responseText ?? null);
|
|
7770
|
-
}
|
|
7771
|
-
if (patch.error !== void 0) {
|
|
7772
|
-
sets.push("error = ?");
|
|
7773
|
-
values.push(patch.error ?? null);
|
|
7774
|
-
}
|
|
7775
|
-
if (patch.processedAt !== void 0) {
|
|
7776
|
-
sets.push("processed_at = ?");
|
|
7777
|
-
values.push(patch.processedAt ?? null);
|
|
7778
|
-
}
|
|
7779
|
-
if (sets.length === 0) return;
|
|
7780
|
-
values.push(id);
|
|
7781
|
-
db3.prepare(`UPDATE telegram_messages SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
7782
|
-
}
|
|
7783
|
-
function listTelegramMessages(chatId, limit = 50) {
|
|
7784
|
-
if (chatId) {
|
|
7785
|
-
const rows2 = db3.prepare("SELECT * FROM telegram_messages WHERE chat_id = ? ORDER BY created_at DESC LIMIT ?").all(chatId, limit);
|
|
7786
|
-
return rows2.map(toTelegramMessage);
|
|
7787
|
-
}
|
|
7788
|
-
const rows = db3.prepare("SELECT * FROM telegram_messages ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
7789
|
-
return rows.map(toTelegramMessage);
|
|
7790
|
-
}
|
|
7791
|
-
function telegramMessageExists(messageId) {
|
|
7792
|
-
const row = db3.prepare("SELECT 1 FROM telegram_messages WHERE message_id = ?").get(messageId);
|
|
7793
|
-
return !!row;
|
|
7794
|
-
}
|
|
7795
|
-
|
|
7796
|
-
// ../pi/src/channels/telegram/queue.ts
|
|
7797
|
-
var session2 = null;
|
|
7798
|
-
var isProcessing2 = false;
|
|
7799
|
-
var PI_PROJECT_PATH2 = process.env.PI_PROJECT_PATH || "/tmp/maestro-telegram";
|
|
7800
|
-
var PI_TIMEOUT_MS2 = parseInt(process.env.PI_TIMEOUT_MS || "300000", 10);
|
|
7801
|
-
function nowIso6() {
|
|
7802
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
7803
|
-
}
|
|
7804
|
-
async function ensureSession2() {
|
|
7805
|
-
if (session2) return session2;
|
|
7806
|
-
const authStorage = AuthStorage2.create();
|
|
7807
|
-
const modelRegistry = new ModelRegistry2(authStorage);
|
|
7808
|
-
const available = await modelRegistry.getAvailable();
|
|
7809
|
-
const model = available[0];
|
|
7810
|
-
if (!model) {
|
|
7811
|
-
throw new Error(
|
|
7812
|
-
"No model available. Configure an Ollama model in Settings > Pi Agent."
|
|
7813
|
-
);
|
|
7814
|
-
}
|
|
7815
|
-
console.log(`[telegram-queue] Using model: ${model.id} (provider: ${model.provider})`);
|
|
7816
|
-
const resourceLoader = new DefaultResourceLoader2({
|
|
7817
|
-
cwd: PI_PROJECT_PATH2,
|
|
7818
|
-
appendSystemPromptOverride: (base) => buildAppendSections(base)
|
|
7819
|
-
});
|
|
7820
|
-
await resourceLoader.reload();
|
|
7821
|
-
const { session: s } = await createAgentSession2({
|
|
7822
|
-
cwd: PI_PROJECT_PATH2,
|
|
7823
|
-
tools: createCodingTools2(PI_PROJECT_PATH2),
|
|
7824
|
-
sessionManager: SessionManager2.continueRecent(PI_PROJECT_PATH2),
|
|
7825
|
-
resourceLoader,
|
|
7826
|
-
authStorage,
|
|
7827
|
-
modelRegistry,
|
|
7828
|
-
model
|
|
7829
|
-
});
|
|
7830
|
-
session2 = s;
|
|
7831
|
-
console.log("[telegram-queue] Pi agent session created");
|
|
7832
|
-
return s;
|
|
7833
|
-
}
|
|
7834
|
-
function sendPrompt2(s, message) {
|
|
7835
|
-
return new Promise((resolve5, reject) => {
|
|
7836
|
-
let responseText = "";
|
|
7837
|
-
let done = false;
|
|
7838
|
-
const timer4 = setTimeout(() => {
|
|
7839
|
-
if (!done) {
|
|
7840
|
-
done = true;
|
|
7841
|
-
unsubscribe();
|
|
7842
|
-
reject(new Error("Pi agent timed out"));
|
|
7843
|
-
}
|
|
7844
|
-
}, PI_TIMEOUT_MS2);
|
|
7845
|
-
const unsubscribe = s.subscribe((event) => {
|
|
7846
|
-
if (event.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
7847
|
-
responseText += event.assistantMessageEvent.delta;
|
|
7848
|
-
}
|
|
7849
|
-
if (event.type === "agent_end") {
|
|
7850
|
-
done = true;
|
|
7851
|
-
clearTimeout(timer4);
|
|
7852
|
-
unsubscribe();
|
|
7853
|
-
const messages = event.messages || [];
|
|
7854
|
-
const lastAssistant = messages.findLast((m) => m.role === "assistant");
|
|
7855
|
-
if (lastAssistant?.stopReason === "error" && lastAssistant?.errorMessage) {
|
|
7856
|
-
reject(new Error(lastAssistant.errorMessage));
|
|
7857
|
-
return;
|
|
7858
|
-
}
|
|
7859
|
-
resolve5(responseText.trim() || "Agent completed without text output.");
|
|
7860
|
-
}
|
|
7861
|
-
});
|
|
7862
|
-
s.prompt(message).catch((err) => {
|
|
7863
|
-
if (!done) {
|
|
7864
|
-
done = true;
|
|
7865
|
-
clearTimeout(timer4);
|
|
7866
|
-
unsubscribe();
|
|
7867
|
-
reject(err);
|
|
7868
|
-
}
|
|
7869
|
-
});
|
|
7870
|
-
});
|
|
7871
|
-
}
|
|
7872
|
-
function startTelegramQueue() {
|
|
7873
|
-
onTelegramMessage((chatId, messageId, body, senderName) => {
|
|
7874
|
-
onMessage2(chatId, messageId, body, senderName);
|
|
7875
|
-
});
|
|
7876
|
-
console.log("[telegram-queue] Queue processor started");
|
|
7877
|
-
}
|
|
7878
|
-
function stopTelegramQueue() {
|
|
7879
|
-
session2 = null;
|
|
7880
|
-
console.log("[telegram-queue] Queue processor stopped");
|
|
7881
|
-
}
|
|
7882
|
-
function onMessage2(chatId, messageId, body, senderName) {
|
|
7883
|
-
if (telegramMessageExists(messageId)) {
|
|
7884
|
-
console.log(`[telegram-queue] Duplicate message ${messageId}, skipping`);
|
|
7885
|
-
return;
|
|
7886
|
-
}
|
|
7887
|
-
insertTelegramMessage({
|
|
7888
|
-
chatId,
|
|
7889
|
-
messageId,
|
|
7890
|
-
direction: "incoming",
|
|
7891
|
-
senderName,
|
|
7892
|
-
body,
|
|
7893
|
-
status: "queued"
|
|
7894
|
-
});
|
|
7895
|
-
console.log(`[telegram-queue] Queued message from ${senderName}`);
|
|
7896
|
-
processNext2();
|
|
7897
|
-
}
|
|
7898
|
-
async function processNext2() {
|
|
7899
|
-
if (isProcessing2) return;
|
|
7900
|
-
const msg = getNextQueuedTelegramMessage();
|
|
7901
|
-
if (!msg) return;
|
|
7902
|
-
isProcessing2 = true;
|
|
7903
|
-
try {
|
|
7904
|
-
updateTelegramMessage(msg.id, { status: "processing" });
|
|
7905
|
-
const s = await ensureSession2();
|
|
7906
|
-
if (msg.body.trim().toLowerCase() === "reset") {
|
|
7907
|
-
await s.newSession();
|
|
7908
|
-
await sendTelegramMessage(msg.chatId, "Session reset.");
|
|
7909
|
-
updateTelegramMessage(msg.id, { status: "completed", responseText: "Session reset.", processedAt: nowIso6() });
|
|
7910
|
-
console.log("[telegram-queue] Session reset by user");
|
|
7911
|
-
return;
|
|
7912
|
-
}
|
|
7913
|
-
const responseText = await sendPrompt2(s, msg.body);
|
|
7914
|
-
await sendTelegramMessage(msg.chatId, responseText);
|
|
7915
|
-
insertTelegramMessage({
|
|
7916
|
-
chatId: msg.chatId,
|
|
7917
|
-
messageId: `out_${msg.id}`,
|
|
7918
|
-
direction: "outgoing",
|
|
7919
|
-
senderName: null,
|
|
7920
|
-
body: responseText,
|
|
7921
|
-
status: "completed"
|
|
7922
|
-
});
|
|
7923
|
-
updateTelegramMessage(msg.id, {
|
|
7924
|
-
status: "completed",
|
|
7925
|
-
responseText,
|
|
7926
|
-
processedAt: nowIso6()
|
|
7927
|
-
});
|
|
7928
|
-
console.log(`[telegram-queue] Completed message ${msg.id}`);
|
|
7929
|
-
} catch (error) {
|
|
7930
|
-
const errMsg = error instanceof Error ? error.message : String(error);
|
|
7931
|
-
console.error(`[telegram-queue] Failed to process message ${msg.id}:`, errMsg);
|
|
7932
|
-
updateTelegramMessage(msg.id, {
|
|
7933
|
-
status: "failed",
|
|
7934
|
-
error: errMsg,
|
|
7935
|
-
processedAt: nowIso6()
|
|
7936
|
-
});
|
|
7937
|
-
} finally {
|
|
7938
|
-
isProcessing2 = false;
|
|
7939
|
-
processNext2();
|
|
7940
|
-
}
|
|
7941
|
-
}
|
|
7942
|
-
|
|
7943
|
-
// ../pi/src/channels/telegram/routes.ts
|
|
7944
|
-
async function registerTelegramRoutes(app, io3, getToken) {
|
|
6874
|
+
// ../server/src/routes/telegram-routes.ts
|
|
6875
|
+
async function registerTelegramRoutes(app) {
|
|
7945
6876
|
app.get("/api/integrations/telegram", async () => {
|
|
7946
6877
|
return getTelegramStatus();
|
|
7947
6878
|
});
|
|
7948
6879
|
app.post("/api/integrations/telegram/connect", async (_req, reply) => {
|
|
7949
6880
|
try {
|
|
7950
|
-
const token =
|
|
7951
|
-
await startTelegram(
|
|
7952
|
-
startTelegramQueue();
|
|
6881
|
+
const token = getSettings().telegramBotToken || void 0;
|
|
6882
|
+
await startTelegram(token);
|
|
7953
6883
|
return getTelegramStatus();
|
|
7954
6884
|
} catch (error) {
|
|
7955
6885
|
return reply.status(500).send({
|
|
@@ -7961,26 +6891,6 @@ async function registerTelegramRoutes(app, io3, getToken) {
|
|
|
7961
6891
|
await stopTelegram();
|
|
7962
6892
|
return { ok: true };
|
|
7963
6893
|
});
|
|
7964
|
-
app.get("/api/integrations/telegram/messages", async (req) => {
|
|
7965
|
-
const query = req.query;
|
|
7966
|
-
const chatId = query.chatId || void 0;
|
|
7967
|
-
const limit = query.limit ? parseInt(query.limit, 10) : 50;
|
|
7968
|
-
return listTelegramMessages(chatId, limit);
|
|
7969
|
-
});
|
|
7970
|
-
app.post("/api/integrations/telegram/send", async (req, reply) => {
|
|
7971
|
-
try {
|
|
7972
|
-
const body = req.body;
|
|
7973
|
-
if (!body.chatId || !body.text) {
|
|
7974
|
-
return reply.status(400).send({ error: "chatId and text are required" });
|
|
7975
|
-
}
|
|
7976
|
-
await sendTelegramMessage(body.chatId, body.text);
|
|
7977
|
-
return { ok: true };
|
|
7978
|
-
} catch (error) {
|
|
7979
|
-
return reply.status(500).send({
|
|
7980
|
-
error: error instanceof Error ? error.message : "Failed to send message"
|
|
7981
|
-
});
|
|
7982
|
-
}
|
|
7983
|
-
});
|
|
7984
6894
|
}
|
|
7985
6895
|
|
|
7986
6896
|
// ../server/src/main.ts
|
|
@@ -8006,11 +6916,11 @@ async function main() {
|
|
|
8006
6916
|
await app.register(cors, { origin: true });
|
|
8007
6917
|
registerAuthHook(app);
|
|
8008
6918
|
await registerAuthRoutes(app);
|
|
8009
|
-
const
|
|
6919
|
+
const io = new SocketServer(app.server, {
|
|
8010
6920
|
cors: { origin: "*" },
|
|
8011
6921
|
transports: ["websocket", "polling"]
|
|
8012
6922
|
});
|
|
8013
|
-
|
|
6923
|
+
io.use((socket, next) => {
|
|
8014
6924
|
if (process.env.AUTH_DISABLED === "1") return next();
|
|
8015
6925
|
const token = socket.handshake.auth?.token;
|
|
8016
6926
|
if (!token) return next(new Error("Missing auth token"));
|
|
@@ -8019,42 +6929,29 @@ async function main() {
|
|
|
8019
6929
|
if (payload) return next();
|
|
8020
6930
|
return next(new Error("Invalid auth token"));
|
|
8021
6931
|
});
|
|
8022
|
-
initTerminalManager({ io
|
|
6932
|
+
initTerminalManager({ io });
|
|
8023
6933
|
await registerProjectRoutes(app);
|
|
8024
6934
|
await registerTerminalRoutes(app);
|
|
8025
|
-
await registerKanbanRoutes(app,
|
|
6935
|
+
await registerKanbanRoutes(app, io);
|
|
8026
6936
|
await registerSchedulerRoutes(app);
|
|
8027
6937
|
await registerAutomationRoutes(app);
|
|
8028
6938
|
await registerSystemRoutes(app);
|
|
8029
6939
|
await registerCliAuthRoutes(app);
|
|
8030
6940
|
await registerGitHubIntegrationRoutes(app);
|
|
8031
|
-
await registerWebhookRoutes(app,
|
|
6941
|
+
await registerWebhookRoutes(app, io);
|
|
8032
6942
|
await registerSettingsRoutes(app);
|
|
8033
6943
|
await registerMaestroUpdateRoutes(app);
|
|
8034
6944
|
await registerSetupRoutes(app);
|
|
8035
|
-
await
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
registerSocketHandlers(io3);
|
|
8039
|
-
startKanbanAssigner(io3);
|
|
6945
|
+
await registerTelegramRoutes(app);
|
|
6946
|
+
registerSocketHandlers(io);
|
|
6947
|
+
startKanbanAssigner(io);
|
|
8040
6948
|
await startScheduler();
|
|
8041
6949
|
startAutomationRunner();
|
|
8042
6950
|
startAutoUpdater();
|
|
8043
6951
|
startAuthStatusChecker();
|
|
8044
|
-
const savedPiModel = getSettings().piOllamaModel;
|
|
8045
|
-
if (savedPiModel) {
|
|
8046
|
-
writePiModelsConfig(savedPiModel);
|
|
8047
|
-
console.log(`[startup] Pi models.json written for model: ${savedPiModel}`);
|
|
8048
|
-
}
|
|
8049
|
-
if (process.env.WHATSAPP_ENABLED === "1") {
|
|
8050
|
-
await startWhatsApp(io3);
|
|
8051
|
-
startWhatsAppQueue();
|
|
8052
|
-
console.log("[startup] WhatsApp integration enabled");
|
|
8053
|
-
}
|
|
8054
6952
|
const savedTelegramToken = getSettings().telegramBotToken;
|
|
8055
6953
|
if (process.env.TELEGRAM_ENABLED === "1" || savedTelegramToken) {
|
|
8056
|
-
await startTelegram(
|
|
8057
|
-
startTelegramQueue();
|
|
6954
|
+
await startTelegram(savedTelegramToken || void 0);
|
|
8058
6955
|
console.log("[startup] Telegram integration enabled");
|
|
8059
6956
|
}
|
|
8060
6957
|
app.get("/health", async () => ({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
@@ -8077,12 +6974,9 @@ Received ${signal}, shutting down...`);
|
|
|
8077
6974
|
stopAutomationRunner();
|
|
8078
6975
|
stopAutoUpdater();
|
|
8079
6976
|
stopAuthStatusChecker();
|
|
8080
|
-
stopWhatsAppQueue();
|
|
8081
|
-
await stopWhatsApp();
|
|
8082
|
-
stopTelegramQueue();
|
|
8083
6977
|
await stopTelegram();
|
|
8084
6978
|
await shutdownTerminalManager();
|
|
8085
|
-
|
|
6979
|
+
io.close();
|
|
8086
6980
|
await app.close();
|
|
8087
6981
|
process.exit(0);
|
|
8088
6982
|
};
|