@sulala/agent-os 0.1.32 → 0.1.34

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.
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/png" href="/logo_dark.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Sulala Agent Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-Bv_g9PCc.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DX4XitXH.css">
8
+ <script type="module" crossorigin src="/assets/index-Dy8Huc2-.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DLAqOiRu.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/dist/cli.js CHANGED
@@ -154,6 +154,7 @@ async function readConfig() {
154
154
  const openrouter_api_key = o.openrouter_api_key;
155
155
  const telegram_bot_token = o.telegram_bot_token;
156
156
  const telegram_default_agent_id = o.telegram_default_agent_id;
157
+ const telegram_report_chat_id = o.telegram_report_chat_id;
157
158
  const slack_bot_token = o.slack_bot_token;
158
159
  const slack_signing_secret = o.slack_signing_secret;
159
160
  const slack_default_agent_id = o.slack_default_agent_id;
@@ -176,6 +177,7 @@ async function readConfig() {
176
177
  openrouter_api_key: typeof openrouter_api_key === "string" ? openrouter_api_key : undefined,
177
178
  telegram_bot_token: typeof telegram_bot_token === "string" ? telegram_bot_token : undefined,
178
179
  telegram_default_agent_id: typeof telegram_default_agent_id === "string" ? telegram_default_agent_id : undefined,
180
+ telegram_report_chat_id: typeof telegram_report_chat_id === "string" ? telegram_report_chat_id.trim() || undefined : undefined,
179
181
  slack_bot_token: typeof slack_bot_token === "string" ? slack_bot_token : undefined,
180
182
  slack_signing_secret: typeof slack_signing_secret === "string" ? slack_signing_secret : undefined,
181
183
  slack_default_agent_id: typeof slack_default_agent_id === "string" ? slack_default_agent_id : undefined,
@@ -225,6 +227,7 @@ async function writeConfig(updates) {
225
227
  openrouter_api_key: updates.openrouter_api_key !== undefined ? updates.openrouter_api_key : current.openrouter_api_key,
226
228
  telegram_bot_token: updates.telegram_bot_token !== undefined ? updates.telegram_bot_token : current.telegram_bot_token,
227
229
  telegram_default_agent_id: updates.telegram_default_agent_id !== undefined ? updates.telegram_default_agent_id : current.telegram_default_agent_id,
230
+ telegram_report_chat_id: updates.telegram_report_chat_id !== undefined ? updates.telegram_report_chat_id : current.telegram_report_chat_id,
228
231
  slack_bot_token: updates.slack_bot_token !== undefined ? updates.slack_bot_token : current.slack_bot_token,
229
232
  slack_signing_secret: updates.slack_signing_secret !== undefined ? updates.slack_signing_secret : current.slack_signing_secret,
230
233
  slack_default_agent_id: updates.slack_default_agent_id !== undefined ? updates.slack_default_agent_id : current.slack_default_agent_id,
@@ -251,6 +254,7 @@ async function writeConfig(updates) {
251
254
  openrouter_api_key: merged.openrouter_api_key ?? null,
252
255
  telegram_bot_token: merged.telegram_bot_token ?? null,
253
256
  telegram_default_agent_id: merged.telegram_default_agent_id ?? null,
257
+ telegram_report_chat_id: merged.telegram_report_chat_id ?? null,
254
258
  slack_bot_token: merged.slack_bot_token ?? null,
255
259
  slack_signing_secret: merged.slack_signing_secret ?? null,
256
260
  slack_default_agent_id: merged.slack_default_agent_id ?? null,
@@ -434,6 +438,7 @@ class MemoryStore {
434
438
  this.migrateMemoriesEmbedding();
435
439
  this.migrateAgentsScheduleEnabled();
436
440
  this.migrateConversationsGraphId();
441
+ this.migrateAgentsScheduleReportTargets();
437
442
  }
438
443
  migrateMemoriesEmbedding() {
439
444
  try {
@@ -450,6 +455,11 @@ class MemoryStore {
450
455
  this.db.exec("ALTER TABLE conversations ADD COLUMN graph_id TEXT");
451
456
  } catch {}
452
457
  }
458
+ migrateAgentsScheduleReportTargets() {
459
+ try {
460
+ this.db.exec("ALTER TABLE agents ADD COLUMN schedule_report_targets TEXT");
461
+ } catch {}
462
+ }
453
463
  insertMemory(args) {
454
464
  const embeddingJson = args.embedding && args.embedding.length > 0 ? JSON.stringify(args.embedding) : null;
455
465
  const stmt = this.db.prepare("INSERT INTO memories (user_id, agent_id, scope, text, tags, embedding) VALUES (?, ?, 'personal', ?, ?, ?)");
@@ -571,6 +581,7 @@ class MemoryStore {
571
581
  schedule: row.schedule ?? undefined,
572
582
  schedule_input: row.schedule_input ?? undefined,
573
583
  schedule_enabled: row.schedule_enabled !== undefined ? Number(row.schedule_enabled) === 1 : true,
584
+ schedule_report_targets: row.schedule_report_targets ? JSON.parse(String(row.schedule_report_targets)) : undefined,
574
585
  avatar: row.avatar ?? undefined,
575
586
  user_created: Number(row.user_created) === 1,
576
587
  limits: row.limits ? JSON.parse(String(row.limits)) : undefined
@@ -597,12 +608,13 @@ class MemoryStore {
597
608
  const schedule = config.schedule != null ? String(config.schedule) : null;
598
609
  const schedule_input = config.schedule_input != null ? String(config.schedule_input) : null;
599
610
  const schedule_enabled = config.schedule_enabled === false ? 0 : 1;
611
+ const schedule_report_targets = config.schedule_report_targets != null ? JSON.stringify(config.schedule_report_targets) : null;
600
612
  const avatar = config.avatar != null ? String(config.avatar) : null;
601
613
  const user_created = config.user_created === true ? 1 : 0;
602
614
  const limits = config.limits != null ? JSON.stringify(config.limits) : null;
603
- const stmt = this.db.prepare(`INSERT INTO agents (id, name, model, description, personality, skills, tools, schedule, schedule_input, schedule_enabled, avatar, user_created, limits)
604
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
605
- stmt.run(id, name, model, description, personality, skills, tools, schedule, schedule_input, schedule_enabled, avatar, user_created, limits);
615
+ const stmt = this.db.prepare(`INSERT INTO agents (id, name, model, description, personality, skills, tools, schedule, schedule_input, schedule_enabled, schedule_report_targets, avatar, user_created, limits)
616
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
617
+ stmt.run(id, name, model, description, personality, skills, tools, schedule, schedule_input, schedule_enabled, schedule_report_targets, avatar, user_created, limits);
606
618
  }
607
619
  updateAgent(id, updates) {
608
620
  const agent = this.getAgentById(id);
@@ -619,8 +631,9 @@ class MemoryStore {
619
631
  const schedule_input = updates.schedule_input !== undefined ? updates.schedule_input && String(updates.schedule_input).trim() ? String(updates.schedule_input).trim() : null : agent.schedule_input;
620
632
  const avatar = updates.avatar !== undefined ? updates.avatar && String(updates.avatar).trim() ? String(updates.avatar).trim() : null : agent.avatar;
621
633
  const schedule_enabled = updates.schedule_enabled !== undefined && updates.schedule_enabled !== null ? updates.schedule_enabled ? 1 : 0 : agent.schedule_enabled === true || agent.schedule_enabled === 1 ? 1 : 0;
622
- const stmt = this.db.prepare("UPDATE agents SET name = ?, model = ?, description = ?, personality = ?, skills = ?, tools = ?, limits = ?, schedule = ?, schedule_input = ?, avatar = ?, schedule_enabled = ?, updated_at = datetime('now') WHERE id = ?");
623
- stmt.run(name, model, description, personality, skills, tools, limits, schedule, schedule_input, avatar, schedule_enabled, id);
634
+ const schedule_report_targets = updates.schedule_report_targets !== undefined ? updates.schedule_report_targets?.length ? JSON.stringify(updates.schedule_report_targets) : null : agent.schedule_report_targets != null ? typeof agent.schedule_report_targets === "string" ? agent.schedule_report_targets : JSON.stringify(agent.schedule_report_targets) : null;
635
+ const stmt = this.db.prepare("UPDATE agents SET name = ?, model = ?, description = ?, personality = ?, skills = ?, tools = ?, limits = ?, schedule = ?, schedule_input = ?, avatar = ?, schedule_enabled = ?, schedule_report_targets = ?, updated_at = datetime('now') WHERE id = ?");
636
+ stmt.run(name, model, description, personality, skills, tools, limits, schedule, schedule_input, avatar, schedule_enabled, schedule_report_targets, id);
624
637
  }
625
638
  deleteAgent(id) {
626
639
  const agent = this.getAgentById(id);
@@ -682,6 +695,9 @@ function validateAgentConfig(raw) {
682
695
  } else if (obj.schedule_enabled === true) {
683
696
  config.schedule_enabled = true;
684
697
  }
698
+ if (Array.isArray(obj.schedule_report_targets)) {
699
+ config.schedule_report_targets = obj.schedule_report_targets.filter((t) => t && typeof t === "object" && t.channel === "telegram" && typeof t.address === "string" && String(t.address).trim() !== "").map((t) => ({ channel: "telegram", address: String(t.address).trim() }));
700
+ }
685
701
  if (obj.avatar != null && typeof obj.avatar === "string" && obj.avatar.trim()) {
686
702
  config.avatar = obj.avatar.trim();
687
703
  }
@@ -932,6 +948,9 @@ async function updateAgent(id, updates) {
932
948
  if (updates.schedule_enabled !== undefined && updates.schedule_enabled !== null) {
933
949
  parsed.schedule_enabled = updates.schedule_enabled;
934
950
  }
951
+ if (updates.schedule_report_targets !== undefined) {
952
+ parsed.schedule_report_targets = updates.schedule_report_targets ?? undefined;
953
+ }
935
954
  const agent = parseAgentConfig(parsed);
936
955
  await writeFile2(path, JSON.stringify(parsed, null, 2), "utf-8");
937
956
  return agent;
@@ -23739,21 +23758,25 @@ function buildSystemPrompt(agent, hasTools, skillDocContext, workspaceContext, d
23739
23758
  parts.push("Answer the user's task concisely.");
23740
23759
  const skillList = agent.skills?.length ? agent.skills.join(", ") : "none (built-in tools only)";
23741
23760
  parts.push(`Your skills: ${skillList}.`);
23742
- parts.push("If the user's task clearly requires a capability that only a skill can provide (e.g. post to Bluesky, send email, search the web, weather, run a specific integration) and that skill is NOT in your skills list above: reply with a single short message that you don't have that skill and they should install it from hub.sulala.ai (Dashboard \u2192 Skills \u2192 install from store) and add it to this agent in Edit agent. Do NOT attempt the task. Do NOT call any tools. Do NOT suggest workarounds. One sentence only.");
23761
+ parts.push("If a skill in your list above can fulfill the user's request, you MUST use it (via the exec tool or the skill's request tool as described in the skill documentation below). Do not claim you lack that capability.");
23762
+ parts.push("Only when no skill in your list can provide the requested capability: reply briefly that you don't have that skill and that they can install it from the addon store (Dashboard \u2192 Skills \u2192 install from store) and add it to this agent. Do not suggest installing when you already have a skill that can do it.");
23743
23763
  if (delegateableAgents?.length) {
23744
- parts.push(`You have the run_agent tool. When the user asks for something that another agent can do (e.g. post to Bluesky, search the web, send email), first tell the user in natural language that you are asking another agent to handle that part (for example: 'I\u2019ll ask our Social Media Agent to post that on Bluesky for you.' or 'I\u2019ll hand this off to our Research Agent to look that up.'). Then use run_agent with that agent's id and the task. After the other agent finishes, reply to the user with a short, friendly summary of what that agent did and the outcome (for example: 'The Social Media Agent has finished \u2014 your post "hi" is now live on Bluesky.').`);
23764
+ parts.push("You have the run_agent tool. When the user asks for something another agent can do, tell the user in natural language that you are delegating to another agent, then use run_agent with that agent's id and the task. After they finish, reply with a short, friendly summary of what was done and the outcome.");
23745
23765
  parts.push("Available agents to delegate to (use run_agent with agent_id and task): " + delegateableAgents.map((a) => `${a.id} (${a.name})`).join(", ") + ".");
23746
23766
  const memoryAgentIds = delegateableAgents.filter((a) => a.skills?.includes("memory")).map((a) => a.id);
23747
23767
  if (memoryAgentIds.length) {
23748
- parts.push(`Agents with long-term memory (use for remember/save requests): ${memoryAgentIds.join(", ")}. When the user asks to remember something, save a fact about themselves, or store information for later, use run_agent with one of these agents and a task like "Remember that [fact]" or "Save: [fact]". Do not say you cannot save\u2014delegate to an agent that has memory.`);
23768
+ parts.push(`Agents with long-term memory (use for remember/save requests): ${memoryAgentIds.join(", ")}. When the user asks to remember something or save a fact, use run_agent with one of these agents and a task like "Remember that [fact]" or "Save: [fact]". Do not say you cannot save\u2014delegate to an agent that has memory.`);
23749
23769
  }
23750
23770
  }
23751
23771
  if (hasTools) {
23752
23772
  parts.push("You have access to tools. Use them when helpful to answer the user.");
23753
- parts.push(`Your agent id is "${agent.id}". When a skill requires agent_id (e.g. memory_write), use this value in the request body.`);
23773
+ parts.push(`Your agent id is "${agent.id}". When a skill requires agent_id in the request body, use this value.`);
23754
23774
  parts.push("After using tools, always reply with a brief summary for the user; never end with only tool calls.");
23755
23775
  if (skillDocContext?.includes("_request")) {
23756
- parts.push("When the user asks for something a skill handles (e.g. list connections, send email, call an API), use that skill's request tool (e.g. skill_id_request) with the method, path, and body from the skill documentation below. Do not use echo, memory_search, or memory_write for skill-specific actions\u2014use the skill's request tool as described in the skill documentation.");
23776
+ parts.push("When the user asks for something a skill handles, use that skill's request tool (the one whose id ends with _request) with method, path, and body from the skill documentation below. Do not use echo, memory_search, or memory_write for skill-specific actions\u2014use the skill's request tool as described in the documentation.");
23777
+ }
23778
+ if (skillDocContext?.includes("exec") && skillDocContext?.includes("skill_id")) {
23779
+ parts.push("When the skill documentation below shows commands to run (e.g. scripts), use the exec tool with skill_id set to that skill and command set to the exact command from the doc.");
23757
23780
  }
23758
23781
  }
23759
23782
  if (skillDocContext) {
@@ -23761,7 +23784,7 @@ function buildSystemPrompt(agent, hasTools, skillDocContext, workspaceContext, d
23761
23784
 
23762
23785
  # Skill documentation
23763
23786
 
23764
- Use the following documentation to know how to call skill APIs. When you have a tool like "skill_id:request", use method, path, query, and body as described below.
23787
+ Use the documentation below to call skill APIs: for request-style tools use method, path, query, and body; for script-style skills use exec with skill_id and the command.
23765
23788
 
23766
23789
  ` + skillDocContext);
23767
23790
  }
@@ -26231,7 +26254,7 @@ async function getTaskById(id) {
26231
26254
  const task = await memoryTaskStore.getById(id);
26232
26255
  return task ?? undefined;
26233
26256
  }
26234
- async function enqueueTask(agent_id, input) {
26257
+ async function enqueueTask(agent_id, input, scheduled_run = false) {
26235
26258
  const id = randomUUID();
26236
26259
  const t = {
26237
26260
  id,
@@ -26239,12 +26262,13 @@ async function enqueueTask(agent_id, input) {
26239
26262
  input,
26240
26263
  status: "queued",
26241
26264
  created_at: nowIso(),
26242
- updated_at: nowIso()
26265
+ updated_at: nowIso(),
26266
+ ...scheduled_run && { scheduled_run: true }
26243
26267
  };
26244
26268
  await memoryTaskStore.enqueue(t);
26245
26269
  return t;
26246
26270
  }
26247
- async function enqueueGraphTask(graph_id, input) {
26271
+ async function enqueueGraphTask(graph_id, input, scheduled_run = false) {
26248
26272
  const id = randomUUID();
26249
26273
  const t = {
26250
26274
  id,
@@ -26252,7 +26276,8 @@ async function enqueueGraphTask(graph_id, input) {
26252
26276
  input,
26253
26277
  status: "queued",
26254
26278
  created_at: nowIso(),
26255
- updated_at: nowIso()
26279
+ updated_at: nowIso(),
26280
+ ...scheduled_run && { scheduled_run: true }
26256
26281
  };
26257
26282
  await memoryTaskStore.enqueue(t);
26258
26283
  return t;
@@ -26272,7 +26297,7 @@ function startScheduler(store = memoryTaskStore) {
26272
26297
  const current = await loadAgents();
26273
26298
  const a = current.find((x) => x.id === agentId);
26274
26299
  const input = a?.schedule_input?.trim() || "Scheduled run";
26275
- await enqueueTask(agentId, input);
26300
+ await enqueueTask(agentId, input, true);
26276
26301
  });
26277
26302
  }
26278
26303
  const graphSummaries = await listGraphs();
@@ -26284,7 +26309,7 @@ function startScheduler(store = memoryTaskStore) {
26284
26309
  import_node_cron.default.schedule(graph.schedule, async () => {
26285
26310
  const g = await loadGraph(gid);
26286
26311
  const input = g?.schedule_input?.trim() || "Scheduled run";
26287
- await enqueueGraphTask(gid, input);
26312
+ await enqueueGraphTask(gid, input, true);
26288
26313
  });
26289
26314
  }
26290
26315
  } catch (err) {
@@ -26770,6 +26795,7 @@ async function handleSettings(req) {
26770
26795
  has_openrouter_key: Boolean(config2.openrouter_api_key?.trim()),
26771
26796
  telegram_configured: Boolean(config2.telegram_bot_token?.trim()),
26772
26797
  telegram_default_agent_id: config2.telegram_default_agent_id ?? null,
26798
+ telegram_report_chat_id: config2.telegram_report_chat_id ?? null,
26773
26799
  slack_configured: Boolean(config2.slack_bot_token?.trim()),
26774
26800
  slack_default_agent_id: config2.slack_default_agent_id ?? null,
26775
26801
  discord_configured: Boolean(config2.discord_bot_token?.trim()),
@@ -26798,6 +26824,7 @@ async function handleSettings(req) {
26798
26824
  const openrouter_api_key = body.openrouter_api_key !== undefined ? typeof body.openrouter_api_key === "string" ? body.openrouter_api_key.trim() || undefined : undefined : current.openrouter_api_key;
26799
26825
  const telegram_bot_token = body.telegram_bot_token !== undefined ? typeof body.telegram_bot_token === "string" ? body.telegram_bot_token.trim() : undefined : current.telegram_bot_token;
26800
26826
  const telegram_default_agent_id = body.telegram_default_agent_id !== undefined ? typeof body.telegram_default_agent_id === "string" ? body.telegram_default_agent_id.trim() || undefined : undefined : current.telegram_default_agent_id;
26827
+ const telegram_report_chat_id = body.telegram_report_chat_id !== undefined ? typeof body.telegram_report_chat_id === "string" ? body.telegram_report_chat_id.trim() || undefined : undefined : current.telegram_report_chat_id;
26801
26828
  const slack_bot_token = body.slack_bot_token !== undefined ? typeof body.slack_bot_token === "string" ? body.slack_bot_token.trim() : undefined : current.slack_bot_token;
26802
26829
  const slack_signing_secret = body.slack_signing_secret !== undefined ? typeof body.slack_signing_secret === "string" ? body.slack_signing_secret.trim() : undefined : current.slack_signing_secret;
26803
26830
  const slack_default_agent_id = body.slack_default_agent_id !== undefined ? typeof body.slack_default_agent_id === "string" ? body.slack_default_agent_id.trim() || undefined : undefined : current.slack_default_agent_id;
@@ -26818,6 +26845,7 @@ async function handleSettings(req) {
26818
26845
  openrouter_api_key: openrouter_api_key ?? undefined,
26819
26846
  telegram_bot_token: telegram_bot_token || undefined,
26820
26847
  telegram_default_agent_id: telegram_default_agent_id || undefined,
26848
+ telegram_report_chat_id: telegram_report_chat_id ?? undefined,
26821
26849
  slack_bot_token: slack_bot_token || undefined,
26822
26850
  slack_signing_secret: slack_signing_secret ?? undefined,
26823
26851
  slack_default_agent_id: slack_default_agent_id || undefined,
@@ -27416,6 +27444,11 @@ async function processTelegramUpdate(memoryStore, body) {
27416
27444
  const botToken = config2.telegram_bot_token?.trim();
27417
27445
  if (!botToken)
27418
27446
  return;
27447
+ if (text === "/set_report_chat" || text === "/setreportchat") {
27448
+ await writeConfig({ telegram_report_chat_id: String(chatId) });
27449
+ await sendTelegramMessage(botToken, chatId, "\u2713 This chat will receive schedule reports. You can change it anytime by sending /set_report_chat from another chat.");
27450
+ return;
27451
+ }
27419
27452
  const agent = await getDefaultAgent(config2, "telegram_default_agent_id");
27420
27453
  if (!agent) {
27421
27454
  await sendTelegramMessage(botToken, chatId, "No agent configured. Set default agent in Settings.");
@@ -30291,6 +30324,100 @@ var init_viber = __esm(() => {
30291
30324
  init_channel_run();
30292
30325
  });
30293
30326
 
30327
+ // src/core/schedule-reports.ts
30328
+ function buildReportMessage(task, label) {
30329
+ const status = task.status === "completed" ? "\u2705 Completed" : "\u274C Failed";
30330
+ const output = task.result?.output?.trim() ?? "";
30331
+ const error2 = task.result?.error?.trim() ?? "";
30332
+ const body = task.status === "completed" ? output : error2 || output;
30333
+ const truncated = body.length > MAX_REPORT_LENGTH ? body.slice(0, MAX_REPORT_LENGTH) + "\u2026" : body;
30334
+ return [
30335
+ `[Schedule report] ${label}`,
30336
+ `Status: ${status}`,
30337
+ truncated ? `
30338
+ ${truncated}` : ""
30339
+ ].join(`
30340
+ `).trim();
30341
+ }
30342
+ async function sendTelegramReport(botToken, chatId, text) {
30343
+ console.log("[schedule-reports] Sending to Telegram chat_id:", chatId, "message length:", text.length);
30344
+ const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
30345
+ const res = await fetch(url, {
30346
+ method: "POST",
30347
+ headers: { "Content-Type": "application/json" },
30348
+ body: JSON.stringify({
30349
+ chat_id: chatId,
30350
+ text: text.slice(0, 4096)
30351
+ })
30352
+ });
30353
+ if (res.ok) {
30354
+ console.log("[schedule-reports] Telegram sendMessage OK for chat_id:", chatId);
30355
+ } else {
30356
+ const err = await res.text();
30357
+ console.error("[schedule-reports] Telegram sendMessage failed:", res.status, "chat_id:", chatId, "error:", err);
30358
+ }
30359
+ }
30360
+ async function handleTaskFinished(event) {
30361
+ const task = event.data.task;
30362
+ console.log("[schedule-reports] handleTaskFinished task_id:", task.id, "status:", task.status, "scheduled_run:", task.scheduled_run, "agent_id:", task.agent_id, "graph_id:", task.graph_id);
30363
+ const targets = [];
30364
+ let label;
30365
+ if (task.agent_id) {
30366
+ const agent = await getAgent(task.agent_id);
30367
+ if (!agent?.schedule_report_targets?.length) {
30368
+ console.log("[schedule-reports] Agent", task.agent_id, "has no schedule_report_targets; skipping.");
30369
+ return;
30370
+ }
30371
+ targets.push(...agent.schedule_report_targets.filter((t) => t.channel === "telegram"));
30372
+ label = `Agent "${agent.name}" (${task.agent_id})`;
30373
+ } else if (task.graph_id) {
30374
+ const graph = await loadGraph(task.graph_id);
30375
+ if (!graph?.schedule_report_targets?.length) {
30376
+ console.log("[schedule-reports] Graph", task.graph_id, "has no schedule_report_targets; skipping.");
30377
+ return;
30378
+ }
30379
+ targets.push(...graph.schedule_report_targets.filter((t) => t.channel === "telegram"));
30380
+ label = `Graph "${task.graph_id}"`;
30381
+ } else {
30382
+ return;
30383
+ }
30384
+ if (targets.length === 0)
30385
+ return;
30386
+ const config2 = await readConfig();
30387
+ const botToken = config2.telegram_bot_token?.trim();
30388
+ if (!botToken) {
30389
+ console.warn("[schedule-reports] Telegram bot token not configured; skipping report.");
30390
+ return;
30391
+ }
30392
+ const defaultChatId = config2.telegram_report_chat_id?.trim();
30393
+ console.log("[schedule-reports] Sending report for", label, "targets:", targets.length, "defaultChatId from config:", defaultChatId ?? "(not set)");
30394
+ const message = buildReportMessage(task, label);
30395
+ for (const t of targets) {
30396
+ const address = t.address.trim();
30397
+ if (!address)
30398
+ continue;
30399
+ const chatId = address === "__default__" ? defaultChatId : address;
30400
+ if (!chatId) {
30401
+ if (address === "__default__") {
30402
+ console.warn("[schedule-reports] Schedule report target is 'from Settings' but no Telegram report chat is set. Send /set_report_chat to your bot in Telegram, then set it in Settings > Telegram.");
30403
+ }
30404
+ continue;
30405
+ }
30406
+ await sendTelegramReport(botToken, chatId, message);
30407
+ }
30408
+ }
30409
+ function initScheduleReports() {
30410
+ subscribe("task.completed", (event) => handleTaskFinished(event));
30411
+ subscribe("task.failed", (event) => handleTaskFinished(event));
30412
+ }
30413
+ var MAX_REPORT_LENGTH = 4000;
30414
+ var init_schedule_reports = __esm(() => {
30415
+ init_events();
30416
+ init_config();
30417
+ init_agent_registry();
30418
+ init_graphs();
30419
+ });
30420
+
30294
30421
  // src/core/plugins.ts
30295
30422
  import { readdir as readdir5, stat as stat2 } from "fs/promises";
30296
30423
  import { join as join12 } from "path";
@@ -30497,6 +30624,7 @@ function createRoutes() {
30497
30624
  schedule: a.schedule ?? null,
30498
30625
  schedule_input: a.schedule_input ?? null,
30499
30626
  schedule_enabled: a.schedule_enabled ?? true,
30627
+ schedule_report_targets: a.schedule_report_targets ?? null,
30500
30628
  skills: a.skills ?? [],
30501
30629
  tools: a.tools ?? [],
30502
30630
  avatar: a.avatar ?? null,
@@ -30558,7 +30686,8 @@ function createRoutes() {
30558
30686
  schedule: body.schedule,
30559
30687
  schedule_input: body.schedule_input,
30560
30688
  avatar: body.avatar,
30561
- schedule_enabled: body.schedule_enabled
30689
+ schedule_enabled: body.schedule_enabled,
30690
+ schedule_report_targets: Array.isArray(body.schedule_report_targets) ? body.schedule_report_targets : undefined
30562
30691
  });
30563
30692
  return jsonResponse({
30564
30693
  ok: true,
@@ -30573,6 +30702,7 @@ function createRoutes() {
30573
30702
  schedule: agent.schedule ?? null,
30574
30703
  schedule_input: agent.schedule_input ?? null,
30575
30704
  schedule_enabled: agent.schedule_enabled ?? true,
30705
+ schedule_report_targets: agent.schedule_report_targets ?? null,
30576
30706
  avatar: agent.avatar ?? null,
30577
30707
  limits: agent.limits ?? null
30578
30708
  }
@@ -30664,7 +30794,8 @@ function createRoutes() {
30664
30794
  edges: Array.isArray(body.edges) ? body.edges : existing?.edges ?? [],
30665
30795
  schedule: body.schedule !== undefined ? body.schedule && String(body.schedule).trim() ? String(body.schedule).trim() : undefined : existing?.schedule,
30666
30796
  schedule_input: body.schedule_input !== undefined ? body.schedule_input && String(body.schedule_input).trim() ? String(body.schedule_input).trim() : undefined : existing?.schedule_input,
30667
- schedule_enabled: body.schedule_enabled !== undefined ? body.schedule_enabled : existing?.schedule_enabled !== false
30797
+ schedule_enabled: body.schedule_enabled !== undefined ? body.schedule_enabled : existing?.schedule_enabled !== false,
30798
+ schedule_report_targets: body.schedule_report_targets !== undefined ? Array.isArray(body.schedule_report_targets) ? body.schedule_report_targets.filter((t) => t?.channel === "telegram" && typeof t?.address === "string").map((t) => ({ channel: "telegram", address: String(t.address).trim() })).filter((t) => t.address.length > 0) : undefined : existing?.schedule_report_targets
30668
30799
  };
30669
30800
  if (!graph.nodes.length) {
30670
30801
  return jsonResponse({ error: "Graph must have at least one node" }, 400);
@@ -30989,6 +31120,7 @@ var init_server = __esm(() => {
30989
31120
  init_config();
30990
31121
  init_tasks();
30991
31122
  init_events();
31123
+ init_schedule_reports();
30992
31124
  init_graphs();
30993
31125
  init_plugins();
30994
31126
  init_loader();
@@ -31021,6 +31153,7 @@ var init_server = __esm(() => {
31021
31153
  for (const type of EVENT_TYPES) {
31022
31154
  subscribe(type, broadcastEvent);
31023
31155
  }
31156
+ initScheduleReports();
31024
31157
  });
31025
31158
 
31026
31159
  // src/cli.ts