@locusai/telegram 0.14.5 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/telegram.js +720 -29
  2. package/package.json +13 -3
package/bin/telegram.js CHANGED
@@ -23113,6 +23113,32 @@ var init_auth = __esm(() => {
23113
23113
  });
23114
23114
  });
23115
23115
 
23116
+ // ../shared/src/models/autonomy.ts
23117
+ var RiskLevel, ChangeCategory, AutonomyRuleSchema;
23118
+ var init_autonomy = __esm(() => {
23119
+ init_zod();
23120
+ ((RiskLevel2) => {
23121
+ RiskLevel2["LOW"] = "LOW";
23122
+ RiskLevel2["HIGH"] = "HIGH";
23123
+ })(RiskLevel ||= {});
23124
+ ((ChangeCategory2) => {
23125
+ ChangeCategory2["FIX"] = "FIX";
23126
+ ChangeCategory2["REFACTOR"] = "REFACTOR";
23127
+ ChangeCategory2["STYLE"] = "STYLE";
23128
+ ChangeCategory2["DEPENDENCY"] = "DEPENDENCY";
23129
+ ChangeCategory2["FEATURE"] = "FEATURE";
23130
+ ChangeCategory2["ARCHITECTURE"] = "ARCHITECTURE";
23131
+ ChangeCategory2["DATABASE"] = "DATABASE";
23132
+ ChangeCategory2["AUTH"] = "AUTH";
23133
+ ChangeCategory2["API"] = "API";
23134
+ })(ChangeCategory ||= {});
23135
+ AutonomyRuleSchema = exports_external.object({
23136
+ category: exports_external.enum(ChangeCategory),
23137
+ riskLevel: exports_external.enum(RiskLevel),
23138
+ autoExecute: exports_external.boolean()
23139
+ });
23140
+ });
23141
+
23116
23142
  // ../shared/src/models/aws-instance.ts
23117
23143
  var InstanceAction, AwsCredentialsSchema, IntegrationSchema, AwsInstanceSchema, CreateAwsInstanceSchema, UpdateAwsInstanceSchema, SaveAwsCredentialsSchema, ProvisionAwsInstanceSchema, InstanceActionBodySchema, InstanceIdParamSchema, CIDR_REGEX, UpdateSecurityRulesSchema;
23118
23144
  var init_aws_instance = __esm(() => {
@@ -23431,6 +23457,49 @@ var init_sprint = __esm(() => {
23431
23457
  });
23432
23458
  });
23433
23459
 
23460
+ // ../shared/src/models/suggestion.ts
23461
+ var SuggestionStatus, SuggestionType, SuggestionSchema, CreateSuggestionSchema, UpdateSuggestionStatusSchema;
23462
+ var init_suggestion = __esm(() => {
23463
+ init_zod();
23464
+ ((SuggestionStatus2) => {
23465
+ SuggestionStatus2["NEW"] = "NEW";
23466
+ SuggestionStatus2["NOTIFIED"] = "NOTIFIED";
23467
+ SuggestionStatus2["ACTED_ON"] = "ACTED_ON";
23468
+ SuggestionStatus2["SKIPPED"] = "SKIPPED";
23469
+ SuggestionStatus2["EXPIRED"] = "EXPIRED";
23470
+ })(SuggestionStatus ||= {});
23471
+ ((SuggestionType2) => {
23472
+ SuggestionType2["CODE_FIX"] = "CODE_FIX";
23473
+ SuggestionType2["DEPENDENCY_UPDATE"] = "DEPENDENCY_UPDATE";
23474
+ SuggestionType2["NEXT_STEP"] = "NEXT_STEP";
23475
+ SuggestionType2["REFACTOR"] = "REFACTOR";
23476
+ SuggestionType2["TEST_FIX"] = "TEST_FIX";
23477
+ })(SuggestionType ||= {});
23478
+ SuggestionSchema = exports_external.object({
23479
+ id: exports_external.string(),
23480
+ type: exports_external.enum(SuggestionType),
23481
+ status: exports_external.enum(SuggestionStatus),
23482
+ title: exports_external.string(),
23483
+ description: exports_external.string(),
23484
+ jobRunId: exports_external.string().optional(),
23485
+ workspaceId: exports_external.string(),
23486
+ createdAt: exports_external.string(),
23487
+ expiresAt: exports_external.string(),
23488
+ metadata: exports_external.record(exports_external.string(), exports_external.any()).optional()
23489
+ });
23490
+ CreateSuggestionSchema = exports_external.object({
23491
+ type: exports_external.enum(SuggestionType),
23492
+ title: exports_external.string().min(1, "Title is required"),
23493
+ description: exports_external.string().min(1, "Description is required"),
23494
+ jobRunId: exports_external.string().uuid().optional(),
23495
+ metadata: exports_external.record(exports_external.string(), exports_external.any()).optional(),
23496
+ expiresAt: exports_external.string().optional()
23497
+ });
23498
+ UpdateSuggestionStatusSchema = exports_external.object({
23499
+ status: exports_external.enum(SuggestionStatus)
23500
+ });
23501
+ });
23502
+
23434
23503
  // ../shared/src/models/task.ts
23435
23504
  var AcceptanceItemSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, AddCommentSchema, DispatchTaskSchema, TaskIdParamSchema, TaskQuerySchema, TaskResponseSchema, TasksResponseSchema;
23436
23505
  var init_task = __esm(() => {
@@ -23570,6 +23639,7 @@ var init_models = __esm(() => {
23570
23639
  init_activity();
23571
23640
  init_agent();
23572
23641
  init_auth();
23642
+ init_autonomy();
23573
23643
  init_aws_instance();
23574
23644
  init_ci();
23575
23645
  init_doc();
@@ -23577,6 +23647,7 @@ var init_models = __esm(() => {
23577
23647
  init_invitation();
23578
23648
  init_organization();
23579
23649
  init_sprint();
23650
+ init_suggestion();
23580
23651
  init_task();
23581
23652
  init_user();
23582
23653
  init_workspace();
@@ -38620,6 +38691,29 @@ var init_sprints = __esm(() => {
38620
38691
  };
38621
38692
  });
38622
38693
 
38694
+ // ../sdk/src/modules/suggestions.ts
38695
+ var SuggestionsModule;
38696
+ var init_suggestions = __esm(() => {
38697
+ SuggestionsModule = class SuggestionsModule extends BaseModule {
38698
+ async create(workspaceId, data) {
38699
+ const { data: res } = await this.api.post(`/workspaces/${workspaceId}/suggestions`, data);
38700
+ return res.suggestion;
38701
+ }
38702
+ async list(workspaceId, params) {
38703
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/suggestions`, { params });
38704
+ return data.suggestions;
38705
+ }
38706
+ async get(workspaceId, id) {
38707
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/suggestions/${id}`);
38708
+ return data.suggestion;
38709
+ }
38710
+ async updateStatus(workspaceId, id, status) {
38711
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/suggestions/${id}/status`, status);
38712
+ return data.suggestion;
38713
+ }
38714
+ };
38715
+ });
38716
+
38623
38717
  // ../sdk/src/modules/tasks.ts
38624
38718
  var TasksModule;
38625
38719
  var init_tasks = __esm(() => {
@@ -38804,6 +38898,7 @@ class LocusClient {
38804
38898
  docs;
38805
38899
  ci;
38806
38900
  instances;
38901
+ suggestions;
38807
38902
  constructor(config2) {
38808
38903
  this.emitter = new LocusEmitter;
38809
38904
  this.api = axios_default.create({
@@ -38824,6 +38919,7 @@ class LocusClient {
38824
38919
  this.docs = new DocsModule(this.api, this.emitter);
38825
38920
  this.ci = new CiModule(this.api, this.emitter);
38826
38921
  this.instances = new InstancesModule(this.api, this.emitter);
38922
+ this.suggestions = new SuggestionsModule(this.api, this.emitter);
38827
38923
  if (config2.retryOptions) {
38828
38924
  this.setupRetryInterceptor(config2.retryOptions);
38829
38925
  }
@@ -38893,6 +38989,7 @@ var init_src2 = __esm(() => {
38893
38989
  init_invitations();
38894
38990
  init_organizations();
38895
38991
  init_sprints();
38992
+ init_suggestions();
38896
38993
  init_tasks();
38897
38994
  init_workspaces();
38898
38995
  init_discussion_types();
@@ -38904,6 +39001,7 @@ var init_src2 = __esm(() => {
38904
39001
  init_invitations();
38905
39002
  init_organizations();
38906
39003
  init_sprints();
39004
+ init_suggestions();
38907
39005
  init_tasks();
38908
39006
  init_workspaces();
38909
39007
  });
@@ -39491,6 +39589,7 @@ class ClaudeRunner {
39491
39589
  currentToolName;
39492
39590
  activeTools = new Map;
39493
39591
  activeProcess = null;
39592
+ aborted = false;
39494
39593
  timeoutMs;
39495
39594
  constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log, timeoutMs) {
39496
39595
  this.model = model;
@@ -39503,6 +39602,7 @@ class ClaudeRunner {
39503
39602
  }
39504
39603
  abort() {
39505
39604
  if (this.activeProcess && !this.activeProcess.killed) {
39605
+ this.aborted = true;
39506
39606
  this.activeProcess.kill("SIGTERM");
39507
39607
  this.activeProcess = null;
39508
39608
  }
@@ -39561,6 +39661,7 @@ class ClaudeRunner {
39561
39661
  return args;
39562
39662
  }
39563
39663
  async* runStream(prompt) {
39664
+ this.aborted = false;
39564
39665
  const args = this.buildCliArgs();
39565
39666
  const env = getAugmentedEnv({
39566
39667
  FORCE_COLOR: "1",
@@ -39655,7 +39756,7 @@ class ClaudeRunner {
39655
39756
  process.stderr.write(`${stderrBuffer}
39656
39757
  `);
39657
39758
  }
39658
- if (code !== 0 && !errorMessage) {
39759
+ if (code !== 0 && !errorMessage && !this.aborted) {
39659
39760
  const detail = stderrFull.trim() || lastResultContent.trim();
39660
39761
  errorMessage = this.createExecutionError(code, detail).message;
39661
39762
  this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
@@ -39800,6 +39901,7 @@ class ClaudeRunner {
39800
39901
  return null;
39801
39902
  }
39802
39903
  executeRun(prompt) {
39904
+ this.aborted = false;
39803
39905
  return new Promise((resolve2, reject) => {
39804
39906
  const args = this.buildCliArgs();
39805
39907
  const env = getAugmentedEnv({
@@ -39852,7 +39954,7 @@ class ClaudeRunner {
39852
39954
  }
39853
39955
  process.stdout.write(`
39854
39956
  `);
39855
- if (code === 0) {
39957
+ if (code === 0 || this.aborted) {
39856
39958
  resolve2(finalResult);
39857
39959
  } else {
39858
39960
  const detail = errorOutput.trim() || finalResult.trim();
@@ -39923,6 +40025,7 @@ class CodexRunner {
39923
40025
  log;
39924
40026
  reasoningEffort;
39925
40027
  activeProcess = null;
40028
+ aborted = false;
39926
40029
  eventEmitter;
39927
40030
  currentToolName;
39928
40031
  timeoutMs;
@@ -39938,11 +40041,13 @@ class CodexRunner {
39938
40041
  }
39939
40042
  abort() {
39940
40043
  if (this.activeProcess && !this.activeProcess.killed) {
40044
+ this.aborted = true;
39941
40045
  this.activeProcess.kill("SIGTERM");
39942
40046
  this.activeProcess = null;
39943
40047
  }
39944
40048
  }
39945
40049
  async run(prompt) {
40050
+ this.aborted = false;
39946
40051
  const maxRetries = 3;
39947
40052
  let lastError = null;
39948
40053
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -40058,7 +40163,7 @@ class CodexRunner {
40058
40163
  });
40059
40164
  codex.on("close", (code) => {
40060
40165
  this.activeProcess = null;
40061
- if (code === 0) {
40166
+ if (code === 0 || this.aborted) {
40062
40167
  const result = this.readOutput(outputPath, finalOutput);
40063
40168
  this.cleanupTempFile(outputPath);
40064
40169
  if (result && finalContent.trim().length === 0) {
@@ -40171,7 +40276,7 @@ class CodexRunner {
40171
40276
  });
40172
40277
  codex.on("close", (code) => {
40173
40278
  this.activeProcess = null;
40174
- if (code === 0) {
40279
+ if (code === 0 || this.aborted) {
40175
40280
  const result = this.readOutput(outputPath, output);
40176
40281
  this.cleanupTempFile(outputPath);
40177
40282
  resolve2(result);
@@ -41099,7 +41204,7 @@ var init_worker = __esm(() => {
41099
41204
  var import_config16 = __toESM(require_config(), 1);
41100
41205
 
41101
41206
  // src/bot.ts
41102
- var import_telegraf5 = __toESM(require_lib3(), 1);
41207
+ var import_telegraf6 = __toESM(require_lib3(), 1);
41103
41208
 
41104
41209
  // src/callbacks.ts
41105
41210
  init_src();
@@ -43339,6 +43444,241 @@ var PlannerOutputSchema = exports_external.object({
43339
43444
  });
43340
43445
  // ../sdk/src/planning/planning-meeting.ts
43341
43446
  init_config();
43447
+ // ../sdk/src/proposals/context-gatherer.ts
43448
+ import { execFileSync as execFileSync4 } from "node:child_process";
43449
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "node:fs";
43450
+ import { join as join9 } from "node:path";
43451
+
43452
+ class ContextGatherer {
43453
+ async gather(projectPath, client, workspaceId) {
43454
+ const [activeSprint, allTasks, skippedSuggestions] = await Promise.all([
43455
+ this.fetchActiveSprint(client, workspaceId),
43456
+ this.fetchTasks(client, workspaceId),
43457
+ this.fetchSkippedSuggestions(client, workspaceId)
43458
+ ]);
43459
+ const sprintTasks = activeSprint ? allTasks.filter((t) => t.sprintId === activeSprint.id) : [];
43460
+ const backlogTasks = allTasks.filter((t) => !t.sprintId);
43461
+ const gitLog = this.readGitLog(projectPath);
43462
+ const artifactContents = this.readArtifacts(projectPath);
43463
+ const locusInstructions = this.readLocusInstructions(projectPath);
43464
+ return {
43465
+ activeSprint,
43466
+ sprintTasks,
43467
+ backlogTasks,
43468
+ gitLog,
43469
+ artifactContents,
43470
+ locusInstructions,
43471
+ skippedSuggestions
43472
+ };
43473
+ }
43474
+ async fetchActiveSprint(client, workspaceId) {
43475
+ try {
43476
+ return await client.sprints.getActive(workspaceId);
43477
+ } catch {
43478
+ return null;
43479
+ }
43480
+ }
43481
+ async fetchTasks(client, workspaceId) {
43482
+ try {
43483
+ return await client.tasks.list(workspaceId);
43484
+ } catch {
43485
+ return [];
43486
+ }
43487
+ }
43488
+ async fetchSkippedSuggestions(client, workspaceId) {
43489
+ try {
43490
+ return await client.suggestions.list(workspaceId, { status: "SKIPPED" });
43491
+ } catch {
43492
+ return [];
43493
+ }
43494
+ }
43495
+ readGitLog(projectPath) {
43496
+ try {
43497
+ return execFileSync4("git", ["log", "--oneline", "--no-decorate", "-n", "20"], {
43498
+ cwd: projectPath,
43499
+ encoding: "utf-8",
43500
+ timeout: 1e4,
43501
+ stdio: ["pipe", "pipe", "pipe"]
43502
+ }).trim();
43503
+ } catch {
43504
+ return "";
43505
+ }
43506
+ }
43507
+ readArtifacts(projectPath) {
43508
+ const artifactsDir = join9(projectPath, ".locus", "artifacts");
43509
+ if (!existsSync9(artifactsDir))
43510
+ return [];
43511
+ try {
43512
+ const files = readdirSync5(artifactsDir).filter((f) => f.endsWith(".md"));
43513
+ return files.slice(0, 10).map((name) => ({
43514
+ name,
43515
+ content: readFileSync8(join9(artifactsDir, name), "utf-8").slice(0, 2000)
43516
+ }));
43517
+ } catch {
43518
+ return [];
43519
+ }
43520
+ }
43521
+ readLocusInstructions(projectPath) {
43522
+ const locusPath = join9(projectPath, ".locus", "LOCUS.md");
43523
+ if (!existsSync9(locusPath))
43524
+ return null;
43525
+ try {
43526
+ return readFileSync8(locusPath, "utf-8").slice(0, 3000);
43527
+ } catch {
43528
+ return null;
43529
+ }
43530
+ }
43531
+ }
43532
+ // ../sdk/src/proposals/proposal-engine.ts
43533
+ init_src();
43534
+ init_factory();
43535
+ class ProposalEngine {
43536
+ contextGatherer;
43537
+ constructor(contextGatherer) {
43538
+ this.contextGatherer = contextGatherer ?? new ContextGatherer;
43539
+ }
43540
+ async runProposalCycle(projectPath, client, workspaceId) {
43541
+ const context2 = await this.contextGatherer.gather(projectPath, client, workspaceId);
43542
+ return this.generateProposals(context2, projectPath, client, workspaceId);
43543
+ }
43544
+ async generateProposals(context2, projectPath, client, workspaceId) {
43545
+ const prompt = this.buildPrompt(context2);
43546
+ const runner = createAiRunner(undefined, {
43547
+ projectPath,
43548
+ timeoutMs: 5 * 60 * 1000,
43549
+ maxTurns: 1
43550
+ });
43551
+ let aiResponse;
43552
+ try {
43553
+ aiResponse = await runner.run(prompt);
43554
+ } catch {
43555
+ return [];
43556
+ }
43557
+ const proposals = this.parseResponse(aiResponse);
43558
+ const created = [];
43559
+ for (const proposal of proposals) {
43560
+ if (this.isDuplicate(proposal.title, context2.skippedSuggestions)) {
43561
+ continue;
43562
+ }
43563
+ try {
43564
+ const suggestion2 = await client.suggestions.create(workspaceId, {
43565
+ type: "NEXT_STEP" /* NEXT_STEP */,
43566
+ title: proposal.title,
43567
+ description: proposal.description,
43568
+ metadata: {
43569
+ complexity: proposal.complexity,
43570
+ relatedBacklogItem: proposal.relatedBacklogItem,
43571
+ source: "proposal-engine"
43572
+ }
43573
+ });
43574
+ created.push(suggestion2);
43575
+ } catch {}
43576
+ }
43577
+ return created;
43578
+ }
43579
+ buildPrompt(context2) {
43580
+ const sections = [];
43581
+ sections.push("You are a proactive software engineering advisor. Based on the project context below, propose 1-3 high-value next steps the team should take. Focus on actionable, impactful work.");
43582
+ if (context2.activeSprint) {
43583
+ const sprintInfo = `Sprint: ${context2.activeSprint.name} (${context2.activeSprint.status})`;
43584
+ const tasksByStatus = this.groupTasksByStatus(context2.sprintTasks);
43585
+ sections.push(`## Current Sprint
43586
+ ${sprintInfo}
43587
+ ${tasksByStatus}`);
43588
+ }
43589
+ if (context2.backlogTasks.length > 0) {
43590
+ const backlogList = context2.backlogTasks.slice(0, 15).map((t) => `- [${t.priority}] ${t.title}${t.description ? `: ${t.description.slice(0, 100)}` : ""}`).join(`
43591
+ `);
43592
+ sections.push(`## Backlog Items
43593
+ ${backlogList}`);
43594
+ }
43595
+ if (context2.gitLog) {
43596
+ sections.push(`## Recent Commits (last 20)
43597
+ ${context2.gitLog}`);
43598
+ }
43599
+ if (context2.artifactContents.length > 0) {
43600
+ const artifacts = context2.artifactContents.map((a) => `### ${a.name}
43601
+ ${a.content}`).join(`
43602
+
43603
+ `);
43604
+ sections.push(`## Product Context
43605
+ ${artifacts}`);
43606
+ }
43607
+ if (context2.locusInstructions) {
43608
+ sections.push(`## Project Instructions
43609
+ ${context2.locusInstructions}`);
43610
+ }
43611
+ if (context2.skippedSuggestions.length > 0) {
43612
+ const skipped = context2.skippedSuggestions.map((s) => `- ${s.title}`).join(`
43613
+ `);
43614
+ sections.push(`## Previously Skipped Proposals (do NOT re-propose these)
43615
+ ${skipped}`);
43616
+ }
43617
+ sections.push(`## Instructions
43618
+ Propose 1-3 high-value next steps. For each, respond with exactly this format:
43619
+
43620
+ PROPOSAL_START
43621
+ Title: <clear, concise title>
43622
+ Description: <what to do and why, 2-4 sentences>
43623
+ Complexity: <low|medium|high>
43624
+ Related Backlog: <title of related backlog item, or "none">
43625
+ PROPOSAL_END
43626
+
43627
+ Rules:
43628
+ - Focus on what would deliver the most value right now
43629
+ - Align with the current sprint goals when possible
43630
+ - Don't propose things that are already in progress
43631
+ - Don't re-propose previously skipped suggestions
43632
+ - Keep proposals specific and actionable`);
43633
+ return sections.join(`
43634
+
43635
+ `);
43636
+ }
43637
+ parseResponse(response) {
43638
+ const proposals = [];
43639
+ const blocks = response.split("PROPOSAL_START");
43640
+ for (const block of blocks) {
43641
+ const endIdx = block.indexOf("PROPOSAL_END");
43642
+ if (endIdx === -1)
43643
+ continue;
43644
+ const content = block.slice(0, endIdx).trim();
43645
+ const title = this.extractField(content, "Title");
43646
+ const description = this.extractField(content, "Description");
43647
+ const complexity = this.extractField(content, "Complexity") ?? "medium";
43648
+ const relatedRaw = this.extractField(content, "Related Backlog");
43649
+ const relatedBacklogItem = relatedRaw && relatedRaw.toLowerCase() !== "none" ? relatedRaw : null;
43650
+ if (title && description) {
43651
+ proposals.push({
43652
+ title: title.slice(0, 200),
43653
+ description: description.slice(0, 2000),
43654
+ complexity: complexity.toLowerCase(),
43655
+ relatedBacklogItem
43656
+ });
43657
+ }
43658
+ }
43659
+ return proposals.slice(0, 3);
43660
+ }
43661
+ extractField(content, field) {
43662
+ const regex = new RegExp(`^${field}:\\s*(.+)`, "im");
43663
+ const match = content.match(regex);
43664
+ return match ? match[1].trim() : null;
43665
+ }
43666
+ isDuplicate(title, skipped) {
43667
+ const normalized = title.toLowerCase().trim();
43668
+ return skipped.some((s) => {
43669
+ const skippedNorm = s.title.toLowerCase().trim();
43670
+ return skippedNorm === normalized || skippedNorm.includes(normalized) || normalized.includes(skippedNorm);
43671
+ });
43672
+ }
43673
+ groupTasksByStatus(tasks2) {
43674
+ const groups = {};
43675
+ for (const t of tasks2) {
43676
+ groups[t.status] = (groups[t.status] ?? 0) + 1;
43677
+ }
43678
+ return Object.entries(groups).map(([status, count]) => `- ${status}: ${count} task(s)`).join(`
43679
+ `);
43680
+ }
43681
+ }
43342
43682
  // ../sdk/src/index-node.ts
43343
43683
  init_colors();
43344
43684
 
@@ -43802,6 +44142,218 @@ ${escapeHtml(desc)}
43802
44142
  const artifactName = ctx.match[1];
43803
44143
  await convertArtifactToPlan(ctx, config2, executor, artifactName);
43804
44144
  });
44145
+ bot.action(/^proposal_start_(.+)$/, async (ctx) => {
44146
+ await ctx.answerCbQuery("Starting proposal…");
44147
+ const suggestionId = ctx.match[1];
44148
+ if (!config2.apiKey) {
44149
+ await ctx.reply(formatError2("API key is required to act on proposals."), {
44150
+ parse_mode: "HTML"
44151
+ });
44152
+ return;
44153
+ }
44154
+ try {
44155
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44156
+ const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "ACTED_ON" /* ACTED_ON */ });
44157
+ try {
44158
+ await ctx.editMessageText(`▶️ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" accepted. Starting planning…`, { parse_mode: "HTML" });
44159
+ } catch {
44160
+ await ctx.reply(`▶️ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" accepted. Starting planning…`, { parse_mode: "HTML" });
44161
+ }
44162
+ const planPrompt = `${suggestion2.title}
44163
+
44164
+ ${suggestion2.description}`;
44165
+ const args = executor.buildArgs(["plan", planPrompt], {
44166
+ needsApiKey: true
44167
+ });
44168
+ const result = await executor.execute(args);
44169
+ const output = (result.stdout + result.stderr).trim();
44170
+ if (output) {
44171
+ const parts = splitMessage(formatCommandOutput("locus plan", output, result.exitCode));
44172
+ for (const part of parts) {
44173
+ await ctx.reply(part, { parse_mode: "HTML" });
44174
+ }
44175
+ }
44176
+ } catch (err) {
44177
+ console.error("[callback:proposal_start] Failed:", err);
44178
+ await ctx.reply(formatError2(`Failed to start proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44179
+ }
44180
+ });
44181
+ bot.action(/^proposal_skip_(.+)$/, async (ctx) => {
44182
+ await ctx.answerCbQuery("Skipping…");
44183
+ const suggestionId = ctx.match[1];
44184
+ if (!config2.apiKey) {
44185
+ await ctx.reply(formatError2("API key is required to act on proposals."), {
44186
+ parse_mode: "HTML"
44187
+ });
44188
+ return;
44189
+ }
44190
+ try {
44191
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44192
+ const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "SKIPPED" /* SKIPPED */ });
44193
+ try {
44194
+ await ctx.editMessageText(`⏭ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" skipped. Won't suggest this again.`, { parse_mode: "HTML" });
44195
+ } catch {
44196
+ await ctx.reply(`⏭ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" skipped. Won't suggest this again.`, { parse_mode: "HTML" });
44197
+ }
44198
+ } catch (err) {
44199
+ console.error("[callback:proposal_skip] Failed:", err);
44200
+ await ctx.reply(formatError2(`Failed to skip proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44201
+ }
44202
+ });
44203
+ bot.action(/^proposal_details_(.+)$/, async (ctx) => {
44204
+ await ctx.answerCbQuery();
44205
+ const suggestionId = ctx.match[1];
44206
+ if (!config2.apiKey) {
44207
+ await ctx.reply(formatError2("API key is required to view proposal details."), { parse_mode: "HTML" });
44208
+ return;
44209
+ }
44210
+ try {
44211
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44212
+ const suggestion2 = await client.suggestions.get(workspaceId, suggestionId);
44213
+ const complexityMap = {
44214
+ low: "2/5",
44215
+ medium: "3/5",
44216
+ high: "4/5"
44217
+ };
44218
+ const complexity = complexityMap[String(suggestion2.metadata?.complexity).toLowerCase()] ?? "—";
44219
+ const relatedTo = suggestion2.metadata?.relatedBacklogItem || "New initiative";
44220
+ let msg = `\uD83D\uDCCB <b>Proposal Details</b>
44221
+
44222
+ `;
44223
+ msg += `<b>Title:</b> ${escapeHtml(suggestion2.title)}
44224
+ `;
44225
+ msg += `<b>Type:</b> ${escapeHtml(suggestion2.type)}
44226
+ `;
44227
+ msg += `<b>Status:</b> ${escapeHtml(suggestion2.status)}
44228
+ `;
44229
+ msg += `<b>Complexity:</b> ${escapeHtml(complexity)}
44230
+ `;
44231
+ msg += `<b>Related to:</b> ${escapeHtml(relatedTo)}
44232
+ `;
44233
+ msg += `<b>Created:</b> ${escapeHtml(suggestion2.createdAt)}
44234
+ `;
44235
+ msg += `<b>Expires:</b> ${escapeHtml(suggestion2.expiresAt)}
44236
+ `;
44237
+ if (suggestion2.jobRunId) {
44238
+ msg += `<b>Job Run:</b> <code>${escapeHtml(suggestion2.jobRunId)}</code>
44239
+ `;
44240
+ }
44241
+ msg += `
44242
+ <b>Description:</b>
44243
+ ${escapeHtml(truncateOutput(suggestion2.description, 2000))}`;
44244
+ if (suggestion2.metadata && Object.keys(suggestion2.metadata).length > 0) {
44245
+ const {
44246
+ complexity: _c,
44247
+ relatedBacklogItem: _r,
44248
+ ...rest
44249
+ } = suggestion2.metadata;
44250
+ if (Object.keys(rest).length > 0) {
44251
+ msg += `
44252
+
44253
+ <b>Metadata:</b>
44254
+ <pre>${escapeHtml(JSON.stringify(rest, null, 2).slice(0, 500))}</pre>`;
44255
+ }
44256
+ }
44257
+ const parts = splitMessage(msg);
44258
+ for (const part of parts) {
44259
+ await ctx.reply(part, {
44260
+ parse_mode: "HTML",
44261
+ link_preview_options: { is_disabled: true }
44262
+ });
44263
+ }
44264
+ } catch (err) {
44265
+ console.error("[callback:proposal_details] Failed:", err);
44266
+ await ctx.reply(formatError2(`Failed to fetch proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44267
+ }
44268
+ });
44269
+ bot.action(/^suggestion_fix_(.+)$/, async (ctx) => {
44270
+ await ctx.answerCbQuery("Applying fix…");
44271
+ const suggestionId = ctx.match[1];
44272
+ if (!config2.apiKey) {
44273
+ await ctx.reply(formatError2("API key is required to act on suggestions."), { parse_mode: "HTML" });
44274
+ return;
44275
+ }
44276
+ try {
44277
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44278
+ const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "ACTED_ON" /* ACTED_ON */ });
44279
+ try {
44280
+ await ctx.editMessageText(formatSuccess(`Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" marked as fixed.`), { parse_mode: "HTML" });
44281
+ } catch {
44282
+ await ctx.reply(formatSuccess(`Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" marked as fixed.`), { parse_mode: "HTML" });
44283
+ }
44284
+ } catch (err) {
44285
+ console.error("[callback:suggestion_fix] Failed:", err);
44286
+ await ctx.reply(formatError2(`Failed to update suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44287
+ }
44288
+ });
44289
+ bot.action(/^suggestion_skip_(.+)$/, async (ctx) => {
44290
+ await ctx.answerCbQuery("Skipping…");
44291
+ const suggestionId = ctx.match[1];
44292
+ if (!config2.apiKey) {
44293
+ await ctx.reply(formatError2("API key is required to act on suggestions."), { parse_mode: "HTML" });
44294
+ return;
44295
+ }
44296
+ try {
44297
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44298
+ const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "SKIPPED" /* SKIPPED */ });
44299
+ try {
44300
+ await ctx.editMessageText(`⏭ Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" skipped.`, { parse_mode: "HTML" });
44301
+ } catch {
44302
+ await ctx.reply(`⏭ Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" skipped.`, { parse_mode: "HTML" });
44303
+ }
44304
+ } catch (err) {
44305
+ console.error("[callback:suggestion_skip] Failed:", err);
44306
+ await ctx.reply(formatError2(`Failed to update suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44307
+ }
44308
+ });
44309
+ bot.action(/^suggestion_details_(.+)$/, async (ctx) => {
44310
+ await ctx.answerCbQuery();
44311
+ const suggestionId = ctx.match[1];
44312
+ if (!config2.apiKey) {
44313
+ await ctx.reply(formatError2("API key is required to view suggestion details."), { parse_mode: "HTML" });
44314
+ return;
44315
+ }
44316
+ try {
44317
+ const { client, workspaceId } = await getClientAndWorkspace(config2);
44318
+ const suggestion2 = await client.suggestions.get(workspaceId, suggestionId);
44319
+ let msg = `\uD83D\uDCCB <b>Suggestion Details</b>
44320
+
44321
+ `;
44322
+ msg += `<b>Title:</b> ${escapeHtml(suggestion2.title)}
44323
+ `;
44324
+ msg += `<b>Type:</b> ${escapeHtml(suggestion2.type)}
44325
+ `;
44326
+ msg += `<b>Status:</b> ${escapeHtml(suggestion2.status)}
44327
+ `;
44328
+ msg += `<b>Created:</b> ${escapeHtml(suggestion2.createdAt)}
44329
+ `;
44330
+ msg += `<b>Expires:</b> ${escapeHtml(suggestion2.expiresAt)}
44331
+ `;
44332
+ if (suggestion2.jobRunId) {
44333
+ msg += `<b>Job Run:</b> <code>${escapeHtml(suggestion2.jobRunId)}</code>
44334
+ `;
44335
+ }
44336
+ msg += `
44337
+ <b>Description:</b>
44338
+ ${escapeHtml(truncateOutput(suggestion2.description, 2000))}`;
44339
+ if (suggestion2.metadata && Object.keys(suggestion2.metadata).length > 0) {
44340
+ msg += `
44341
+
44342
+ <b>Metadata:</b>
44343
+ <pre>${escapeHtml(JSON.stringify(suggestion2.metadata, null, 2).slice(0, 500))}</pre>`;
44344
+ }
44345
+ const parts = splitMessage(msg);
44346
+ for (const part of parts) {
44347
+ await ctx.reply(part, {
44348
+ parse_mode: "HTML",
44349
+ link_preview_options: { is_disabled: true }
44350
+ });
44351
+ }
44352
+ } catch (err) {
44353
+ console.error("[callback:suggestion_details] Failed:", err);
44354
+ await ctx.reply(formatError2(`Failed to fetch suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44355
+ }
44356
+ });
43805
44357
  }
43806
44358
 
43807
44359
  // src/commands/activity.ts
@@ -43961,8 +44513,8 @@ Total: ${agents.length} active agent(s)`;
43961
44513
  }
43962
44514
  }
43963
44515
  // src/commands/config.ts
43964
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
43965
- import { join as join9 } from "node:path";
44516
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
44517
+ import { join as join10 } from "node:path";
43966
44518
 
43967
44519
  // src/command-whitelist.ts
43968
44520
  var SAFE_BRANCH = /^[a-zA-Z0-9_\-./]+$/;
@@ -44330,13 +44882,13 @@ var USAGE = `<b>Usage:</b>
44330
44882
  ${Object.entries(ALLOWED_KEYS).map(([k, v]) => ` \`${k}\` — ${v.description}`).join(`
44331
44883
  `)}`;
44332
44884
  function getSettingsPath(config2) {
44333
- return join9(config2.projectPath, CONFIG_DIR, SETTINGS_FILE);
44885
+ return join10(config2.projectPath, CONFIG_DIR, SETTINGS_FILE);
44334
44886
  }
44335
44887
  function loadSettings(config2) {
44336
44888
  const path = getSettingsPath(config2);
44337
- if (!existsSync9(path))
44889
+ if (!existsSync10(path))
44338
44890
  return null;
44339
- const raw = readFileSync8(path, "utf-8");
44891
+ const raw = readFileSync9(path, "utf-8");
44340
44892
  return JSON.parse(raw);
44341
44893
  }
44342
44894
  function saveSettings(config2, settings) {
@@ -44660,13 +45212,13 @@ import { spawn as spawn4 } from "node:child_process";
44660
45212
 
44661
45213
  // src/env.ts
44662
45214
  import { homedir as homedir2 } from "node:os";
44663
- import { join as join10 } from "node:path";
45215
+ import { join as join11 } from "node:path";
44664
45216
  function extraPathDirs() {
44665
45217
  const home = homedir2();
44666
45218
  return [
44667
- join10(home, ".bun", "bin"),
44668
- join10(home, ".nvm", "current", "bin"),
44669
- join10(home, ".local", "bin"),
45219
+ join11(home, ".bun", "bin"),
45220
+ join11(home, ".nvm", "current", "bin"),
45221
+ join11(home, ".local", "bin"),
44670
45222
  "/usr/local/bin"
44671
45223
  ];
44672
45224
  }
@@ -45589,7 +46141,7 @@ async function approveTaskCommand(ctx, config2) {
45589
46141
  // src/commands/upgrade.ts
45590
46142
  import { spawn as spawn5 } from "node:child_process";
45591
46143
  import { homedir as homedir3 } from "node:os";
45592
- import { join as join11 } from "node:path";
46144
+ import { join as join12 } from "node:path";
45593
46145
  function runCommand2(cmd, args, timeout) {
45594
46146
  return new Promise((resolve2) => {
45595
46147
  const proc = spawn5(cmd, args, {
@@ -45628,13 +46180,11 @@ function getRestartInfo() {
45628
46180
  if (platform === "linux") {
45629
46181
  return {
45630
46182
  label: "systemd (Linux)",
45631
- commands: [
45632
- { cmd: "sudo", args: ["systemctl", "restart", "locus-telegram"] }
45633
- ]
46183
+ commands: [{ cmd: "sudo", args: ["systemctl", "restart", "locus"] }]
45634
46184
  };
45635
46185
  }
45636
46186
  if (platform === "darwin") {
45637
- const plistPath = join11(homedir3(), "Library/LaunchAgents/com.locus.telegram.plist");
46187
+ const plistPath = join12(homedir3(), "Library/LaunchAgents/com.locus.agent.plist");
45638
46188
  return {
45639
46189
  label: "launchctl (macOS)",
45640
46190
  commands: [
@@ -45750,7 +46300,7 @@ async function workspaceCommand(ctx, config2) {
45750
46300
  }
45751
46301
  // src/executor.ts
45752
46302
  import { spawn as spawn6 } from "node:child_process";
45753
- import { join as join12 } from "node:path";
46303
+ import { join as join13 } from "node:path";
45754
46304
  function timestamp2() {
45755
46305
  return new Date().toLocaleTimeString("en-GB", { hour12: false });
45756
46306
  }
@@ -45766,7 +46316,7 @@ class CliExecutor {
45766
46316
  }
45767
46317
  resolveCommand(args) {
45768
46318
  if (this.config.testMode) {
45769
- const cliPath = join12(this.config.projectPath, "packages/cli/src/cli.ts");
46319
+ const cliPath = join13(this.config.projectPath, "packages/cli/src/cli.ts");
45770
46320
  return { cmd: "bun", cmdArgs: ["run", cliPath, ...args] };
45771
46321
  }
45772
46322
  return { cmd: "locus", cmdArgs: args };
@@ -45925,12 +46475,90 @@ class CliExecutor {
45925
46475
  }
45926
46476
  }
45927
46477
 
46478
+ // src/notifications.ts
46479
+ var import_telegraf5 = __toESM(require_lib3(), 1);
46480
+ var PROPOSALS_GENERATED = "PROPOSALS_GENERATED";
46481
+ var COMPLEXITY_DISPLAY = {
46482
+ low: "2/5",
46483
+ medium: "3/5",
46484
+ high: "4/5"
46485
+ };
46486
+ var SUGGESTION_TYPE_ICONS = {
46487
+ CODE_FIX: "\uD83D\uDD27",
46488
+ DEPENDENCY_UPDATE: "\uD83D\uDCE6",
46489
+ NEXT_STEP: "➡️",
46490
+ REFACTOR: "♻️",
46491
+ TEST_FIX: "\uD83E\uDDEA"
46492
+ };
46493
+
46494
+ class Notifier {
46495
+ bot;
46496
+ chatId;
46497
+ constructor(bot, chatId) {
46498
+ this.bot = bot;
46499
+ this.chatId = chatId;
46500
+ }
46501
+ connect(emitter) {
46502
+ emitter.on(PROPOSALS_GENERATED, (payload) => {
46503
+ for (const suggestion2 of payload.suggestions) {
46504
+ this.notifyProposal(suggestion2).catch((err) => console.error("[notifier] Failed to send proposal:", err));
46505
+ }
46506
+ });
46507
+ }
46508
+ async notifyProposal(suggestion2) {
46509
+ const complexity = COMPLEXITY_DISPLAY[String(suggestion2.metadata?.complexity).toLowerCase()] ?? "3/5";
46510
+ const relatedTo = suggestion2.metadata?.relatedBacklogItem || "New initiative";
46511
+ let msg = `\uD83D\uDCA1 <b>Proposal:</b> ${escapeHtml(suggestion2.title)}
46512
+
46513
+ `;
46514
+ msg += `${escapeHtml(truncateOutput(suggestion2.description, 800))}
46515
+
46516
+ `;
46517
+ msg += `<b>Complexity:</b> ${escapeHtml(complexity)}
46518
+ `;
46519
+ msg += `<b>Related to:</b> ${escapeHtml(relatedTo)}`;
46520
+ const buttons = [
46521
+ [
46522
+ import_telegraf5.Markup.button.callback("▶️ Start", `proposal_start_${suggestion2.id}`),
46523
+ import_telegraf5.Markup.button.callback("⏭ Skip", `proposal_skip_${suggestion2.id}`),
46524
+ import_telegraf5.Markup.button.callback("\uD83D\uDCCB Details", `proposal_details_${suggestion2.id}`)
46525
+ ]
46526
+ ];
46527
+ await this.bot.telegram.sendMessage(this.chatId, msg, {
46528
+ parse_mode: "HTML",
46529
+ ...import_telegraf5.Markup.inlineKeyboard(buttons)
46530
+ });
46531
+ }
46532
+ async notifySuggestion(suggestion2) {
46533
+ const icon = SUGGESTION_TYPE_ICONS[suggestion2.type] ?? "\uD83D\uDCA1";
46534
+ let msg = `${icon} <b>Suggestion:</b> ${escapeHtml(suggestion2.title)}
46535
+
46536
+ `;
46537
+ msg += `${escapeHtml(truncateOutput(suggestion2.description, 800))}
46538
+ `;
46539
+ msg += `
46540
+ <b>Type:</b> ${escapeHtml(suggestion2.type)}`;
46541
+ const buttons = [
46542
+ [
46543
+ import_telegraf5.Markup.button.callback("\uD83D\uDD27 Fix", `suggestion_fix_${suggestion2.id}`),
46544
+ import_telegraf5.Markup.button.callback("⏭ Skip", `suggestion_skip_${suggestion2.id}`),
46545
+ import_telegraf5.Markup.button.callback("\uD83D\uDCCB Details", `suggestion_details_${suggestion2.id}`)
46546
+ ]
46547
+ ];
46548
+ await this.bot.telegram.sendMessage(this.chatId, msg, {
46549
+ parse_mode: "HTML",
46550
+ ...import_telegraf5.Markup.inlineKeyboard(buttons)
46551
+ });
46552
+ }
46553
+ }
46554
+
45928
46555
  // src/bot.ts
45929
46556
  function createBot(config2) {
45930
- const bot = new import_telegraf5.Telegraf(config2.botToken, {
46557
+ const bot = new import_telegraf6.Telegraf(config2.botToken, {
45931
46558
  handlerTimeout: HANDLER_TIMEOUT
45932
46559
  });
45933
46560
  const executor = new CliExecutor(config2);
46561
+ const notifier = new Notifier(bot, config2.chatId);
45934
46562
  bot.use(async (ctx, next) => {
45935
46563
  const chatId = ctx.chat?.id;
45936
46564
  if (chatId !== config2.chatId) {
@@ -46030,22 +46658,22 @@ function createBot(config2) {
46030
46658
  { command: "upgrade", description: "Upgrade Locus CLI & restart bot" },
46031
46659
  { command: "help", description: "Show all commands" }
46032
46660
  ]).catch((err) => console.error("Failed to set bot commands:", err));
46033
- return bot;
46661
+ return { bot, notifier };
46034
46662
  }
46035
46663
 
46036
46664
  // src/config.ts
46037
46665
  var import_dotenv = __toESM(require_main(), 1);
46038
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
46039
- import { join as join13 } from "node:path";
46666
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
46667
+ import { join as join14 } from "node:path";
46040
46668
  import_dotenv.default.config();
46041
46669
  var SETTINGS_FILE2 = "settings.json";
46042
46670
  var CONFIG_DIR2 = ".locus";
46043
46671
  function loadSettings2(projectPath) {
46044
- const settingsPath = join13(projectPath, CONFIG_DIR2, SETTINGS_FILE2);
46045
- if (!existsSync10(settingsPath)) {
46672
+ const settingsPath = join14(projectPath, CONFIG_DIR2, SETTINGS_FILE2);
46673
+ if (!existsSync11(settingsPath)) {
46046
46674
  return null;
46047
46675
  }
46048
- const raw = readFileSync9(settingsPath, "utf-8");
46676
+ const raw = readFileSync10(settingsPath, "utf-8");
46049
46677
  return JSON.parse(raw);
46050
46678
  }
46051
46679
  function resolveConfig() {
@@ -46082,6 +46710,66 @@ function resolveConfig() {
46082
46710
  };
46083
46711
  }
46084
46712
 
46713
+ // src/scheduler.ts
46714
+ var PROPOSAL_INTERVAL_MS = 6 * 60 * 60 * 1000;
46715
+
46716
+ class ProposalScheduler {
46717
+ config;
46718
+ notifier;
46719
+ timer = null;
46720
+ running = false;
46721
+ constructor(config2, notifier) {
46722
+ this.config = config2;
46723
+ this.notifier = notifier;
46724
+ }
46725
+ async start() {
46726
+ if (!this.config.apiKey) {
46727
+ console.log("[scheduler] API key not configured — proposal scheduling disabled");
46728
+ return;
46729
+ }
46730
+ console.log("[scheduler] Starting proposal scheduler (every 6 hours)");
46731
+ this.runCycle();
46732
+ this.timer = setInterval(() => this.runCycle(), PROPOSAL_INTERVAL_MS);
46733
+ }
46734
+ stop() {
46735
+ if (this.timer) {
46736
+ clearInterval(this.timer);
46737
+ this.timer = null;
46738
+ }
46739
+ console.log("[scheduler] Proposal scheduler stopped");
46740
+ }
46741
+ async runCycle() {
46742
+ if (this.running) {
46743
+ console.log("[scheduler] Proposal cycle already in progress, skipping");
46744
+ return;
46745
+ }
46746
+ this.running = true;
46747
+ const ts = new Date().toLocaleTimeString();
46748
+ console.log(`[scheduler] Running proposal cycle at ${ts}`);
46749
+ try {
46750
+ const client = createClient(this.config);
46751
+ const workspaceId = await resolveWorkspaceId(client, this.config);
46752
+ const engine = new ProposalEngine;
46753
+ const suggestions2 = await engine.runProposalCycle(this.config.projectPath, client, workspaceId);
46754
+ if (suggestions2.length > 0) {
46755
+ console.log(`[scheduler] ${suggestions2.length} proposal(s) generated`);
46756
+ for (const s of suggestions2) {
46757
+ console.log(`[scheduler] - ${s.title}`);
46758
+ this.notifier.notifyProposal(s).catch((err) => {
46759
+ console.error("[scheduler] Failed to notify proposal:", err);
46760
+ });
46761
+ }
46762
+ } else {
46763
+ console.log("[scheduler] No new proposals generated");
46764
+ }
46765
+ } catch (err) {
46766
+ console.error(`[scheduler] Proposal cycle failed: ${err instanceof Error ? err.message : String(err)}`);
46767
+ } finally {
46768
+ this.running = false;
46769
+ }
46770
+ }
46771
+ }
46772
+
46085
46773
  // src/index.ts
46086
46774
  async function main() {
46087
46775
  console.log("Locus Telegram Bot");
@@ -46093,10 +46781,13 @@ async function main() {
46093
46781
  console.log(`API Key: ${config2.apiKey ? "configured" : "not set"}`);
46094
46782
  console.log(`----------------------------------------------
46095
46783
  `);
46096
- const bot = createBot(config2);
46784
+ const { bot, notifier } = createBot(config2);
46785
+ const scheduler = new ProposalScheduler(config2, notifier);
46786
+ await scheduler.start();
46097
46787
  const shutdown = (signal) => {
46098
46788
  console.log(`
46099
46789
  Received ${signal}. Shutting down...`);
46790
+ scheduler.stop();
46100
46791
  bot.stop(signal);
46101
46792
  process.exit(0);
46102
46793
  };
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@locusai/telegram",
3
- "version": "0.14.5",
3
+ "version": "0.15.1",
4
4
  "description": "Telegram bot for Locus - remote control your AI agents from Telegram",
5
5
  "type": "module",
6
+ "main": "./src/lib.ts",
7
+ "types": "./src/lib.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/lib.ts",
11
+ "development": "./src/lib.ts",
12
+ "import": "./src/lib.ts",
13
+ "default": "./src/lib.ts"
14
+ }
15
+ },
6
16
  "bin": {
7
17
  "locus-telegram": "./bin/telegram.js"
8
18
  },
@@ -31,8 +41,8 @@
31
41
  "author": "",
32
42
  "license": "MIT",
33
43
  "dependencies": {
34
- "@locusai/sdk": "^0.14.5",
35
- "@locusai/shared": "^0.14.5",
44
+ "@locusai/sdk": "^0.15.1",
45
+ "@locusai/shared": "^0.15.1",
36
46
  "dotenv": "^16.4.7",
37
47
  "telegraf": "^4.16.3"
38
48
  },