@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 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(path20, args) {
3305
+ function runGit2(path17, args) {
3312
3306
  return execFileSync6("git", args, {
3313
- cwd: path20,
3307
+ cwd: path17,
3314
3308
  encoding: "utf8",
3315
3309
  stdio: ["ignore", "pipe", "pipe"]
3316
3310
  }).trim();
3317
3311
  }
3318
- function tryRunGit(path20, args) {
3312
+ function tryRunGit(path17, args) {
3319
3313
  try {
3320
- return { ok: true, output: runGit2(path20, args) };
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(path20, currentBranch) {
3432
- const upstream = tryRunGit(path20, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
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(path20, ["show-ref", "--verify", "--quiet", originBranch]);
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(path20, ref) {
3444
- return tryRunGit(path20, ["show-ref", "--verify", "--quiet", ref]).ok;
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, io3) {
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
- io3.emit("kanban:updated", { taskId: task.id, column: task.column });
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
- io3.emit("kanban:updated", {
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
- io3.emit("kanban:updated", {
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
- io3.emit("kanban:updated", { taskId: req.params.id });
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(io3, cols, rows) {
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 = io3.sockets.adapter.rooms.get("setup")?.size ?? 0;
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
- io3.to("setup").emit("setup:output", { data });
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
- io3.to("setup").emit("setup:clear-url", {});
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
- io3.to("setup").emit("setup:url", { url });
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
- io3.to("setup").emit("setup:complete", {});
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(io3) {
5472
- io3.on("connection", (socket) => {
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(io3, cols, rows);
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(io3, cols, rows);
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(io3) {
5515
+ function startKanbanAssigner(io) {
5609
5516
  console.log("Kanban assigner started (polling every 10s)");
5610
- timer2 = setInterval(() => tick(io3), POLL_INTERVAL);
5611
- void tick(io3);
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(io3) {
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(io3, task);
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(io3, task) {
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
- io3.emit("kanban:updated", {
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
- io3.emit("kanban:updated", {
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, io3) {
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
- io3.emit("kanban:updated", { taskId: result.taskId });
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
- io3.emit("kanban:updated", { taskId: result.taskId });
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
- io3.emit("kanban:updated", { taskId: result.taskId });
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
- io3.emit("kanban:updated", { taskId: result.taskId });
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/ollama-routes.ts
7043
- async function registerOllamaRoutes(app) {
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
- // ../pi/src/channels/whatsapp/whatsapp.ts
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
- var pollTimer = null;
7568
- var lastUpdateId = 0;
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 onTelegramMessage(cb) {
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
- console.error("[telegram] No bot token provided");
7604
- setStatus2("error");
7605
- return;
6849
+ connectionStatus = "error";
6850
+ throw new Error("No Telegram bot token configured");
7606
6851
  }
7607
6852
  botToken = resolvedToken;
7608
- setStatus2("connecting");
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
- console.log(`[telegram] Connected as @${botUsername}`);
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
- console.error("[telegram] Polling error:", error);
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
- lastUpdateId = 0;
7655
- setStatus2("disconnected");
6868
+ connectionStatus = "disconnected";
7656
6869
  }
7657
6870
  function getTelegramStatus() {
7658
- return { status: connectionStatus2, botUsername };
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
- // ../pi/src/channels/telegram/queue.ts
7680
- import {
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 = getToken?.() || void 0;
7951
- await startTelegram(io3, token);
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 io3 = new SocketServer(app.server, {
6919
+ const io = new SocketServer(app.server, {
8010
6920
  cors: { origin: "*" },
8011
6921
  transports: ["websocket", "polling"]
8012
6922
  });
8013
- io3.use((socket, next) => {
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: io3 });
6932
+ initTerminalManager({ io });
8023
6933
  await registerProjectRoutes(app);
8024
6934
  await registerTerminalRoutes(app);
8025
- await registerKanbanRoutes(app, io3);
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, io3);
6941
+ await registerWebhookRoutes(app, io);
8032
6942
  await registerSettingsRoutes(app);
8033
6943
  await registerMaestroUpdateRoutes(app);
8034
6944
  await registerSetupRoutes(app);
8035
- await registerWhatsAppRoutes(app, io3);
8036
- await registerTelegramRoutes(app, io3, () => getSettings().telegramBotToken);
8037
- await registerOllamaRoutes(app);
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(io3, savedTelegramToken || void 0);
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
- io3.close();
6979
+ io.close();
8086
6980
  await app.close();
8087
6981
  process.exit(0);
8088
6982
  };