@locusai/telegram 0.15.4 → 0.16.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 +26 -762
  2. package/package.json +3 -13
package/bin/telegram.js CHANGED
@@ -23113,32 +23113,6 @@ 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
-
23142
23116
  // ../shared/src/models/aws-instance.ts
23143
23117
  var InstanceAction, AwsCredentialsSchema, IntegrationSchema, AwsInstanceSchema, CreateAwsInstanceSchema, UpdateAwsInstanceSchema, SaveAwsCredentialsSchema, ProvisionAwsInstanceSchema, InstanceActionBodySchema, InstanceIdParamSchema, CIDR_REGEX, UpdateSecurityRulesSchema;
23144
23118
  var init_aws_instance = __esm(() => {
@@ -23457,49 +23431,6 @@ var init_sprint = __esm(() => {
23457
23431
  });
23458
23432
  });
23459
23433
 
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
-
23503
23434
  // ../shared/src/models/task.ts
23504
23435
  var AcceptanceItemSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, AddCommentSchema, DispatchTaskSchema, TaskIdParamSchema, TaskQuerySchema, TaskResponseSchema, TasksResponseSchema;
23505
23436
  var init_task = __esm(() => {
@@ -23639,7 +23570,6 @@ var init_models = __esm(() => {
23639
23570
  init_activity();
23640
23571
  init_agent();
23641
23572
  init_auth();
23642
- init_autonomy();
23643
23573
  init_aws_instance();
23644
23574
  init_ci();
23645
23575
  init_doc();
@@ -23647,7 +23577,6 @@ var init_models = __esm(() => {
23647
23577
  init_invitation();
23648
23578
  init_organization();
23649
23579
  init_sprint();
23650
- init_suggestion();
23651
23580
  init_task();
23652
23581
  init_user();
23653
23582
  init_workspace();
@@ -38691,29 +38620,6 @@ var init_sprints = __esm(() => {
38691
38620
  };
38692
38621
  });
38693
38622
 
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
-
38717
38623
  // ../sdk/src/modules/tasks.ts
38718
38624
  var TasksModule;
38719
38625
  var init_tasks = __esm(() => {
@@ -38898,7 +38804,6 @@ class LocusClient {
38898
38804
  docs;
38899
38805
  ci;
38900
38806
  instances;
38901
- suggestions;
38902
38807
  constructor(config2) {
38903
38808
  this.emitter = new LocusEmitter;
38904
38809
  this.api = axios_default.create({
@@ -38919,7 +38824,6 @@ class LocusClient {
38919
38824
  this.docs = new DocsModule(this.api, this.emitter);
38920
38825
  this.ci = new CiModule(this.api, this.emitter);
38921
38826
  this.instances = new InstancesModule(this.api, this.emitter);
38922
- this.suggestions = new SuggestionsModule(this.api, this.emitter);
38923
38827
  if (config2.retryOptions) {
38924
38828
  this.setupRetryInterceptor(config2.retryOptions);
38925
38829
  }
@@ -38989,7 +38893,6 @@ var init_src2 = __esm(() => {
38989
38893
  init_invitations();
38990
38894
  init_organizations();
38991
38895
  init_sprints();
38992
- init_suggestions();
38993
38896
  init_tasks();
38994
38897
  init_workspaces();
38995
38898
  init_discussion_types();
@@ -39001,7 +38904,6 @@ var init_src2 = __esm(() => {
39001
38904
  init_invitations();
39002
38905
  init_organizations();
39003
38906
  init_sprints();
39004
- init_suggestions();
39005
38907
  init_tasks();
39006
38908
  init_workspaces();
39007
38909
  });
@@ -39567,7 +39469,6 @@ var init_resolve_bin = __esm(() => {
39567
39469
  join3(homedir(), ".local", "bin"),
39568
39470
  join3(homedir(), ".npm", "bin"),
39569
39471
  join3(homedir(), ".npm-global", "bin"),
39570
- join3(homedir(), ".npm-packages", "bin"),
39571
39472
  join3(homedir(), ".yarn", "bin"),
39572
39473
  join3(homedir(), ".bun", "bin"),
39573
39474
  join3(homedir(), "Library", "pnpm"),
@@ -41205,7 +41106,7 @@ var init_worker = __esm(() => {
41205
41106
  var import_config16 = __toESM(require_config(), 1);
41206
41107
 
41207
41108
  // src/bot.ts
41208
- var import_telegraf6 = __toESM(require_lib3(), 1);
41109
+ var import_telegraf5 = __toESM(require_lib3(), 1);
41209
41110
 
41210
41111
  // src/callbacks.ts
41211
41112
  init_src();
@@ -43445,241 +43346,6 @@ var PlannerOutputSchema = exports_external.object({
43445
43346
  });
43446
43347
  // ../sdk/src/planning/planning-meeting.ts
43447
43348
  init_config();
43448
- // ../sdk/src/proposals/context-gatherer.ts
43449
- import { execFileSync as execFileSync4 } from "node:child_process";
43450
- import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "node:fs";
43451
- import { join as join9 } from "node:path";
43452
-
43453
- class ContextGatherer {
43454
- async gather(projectPath, client, workspaceId) {
43455
- const [activeSprint, allTasks, skippedSuggestions] = await Promise.all([
43456
- this.fetchActiveSprint(client, workspaceId),
43457
- this.fetchTasks(client, workspaceId),
43458
- this.fetchSkippedSuggestions(client, workspaceId)
43459
- ]);
43460
- const sprintTasks = activeSprint ? allTasks.filter((t) => t.sprintId === activeSprint.id) : [];
43461
- const backlogTasks = allTasks.filter((t) => !t.sprintId);
43462
- const gitLog = this.readGitLog(projectPath);
43463
- const artifactContents = this.readArtifacts(projectPath);
43464
- const locusInstructions = this.readLocusInstructions(projectPath);
43465
- return {
43466
- activeSprint,
43467
- sprintTasks,
43468
- backlogTasks,
43469
- gitLog,
43470
- artifactContents,
43471
- locusInstructions,
43472
- skippedSuggestions
43473
- };
43474
- }
43475
- async fetchActiveSprint(client, workspaceId) {
43476
- try {
43477
- return await client.sprints.getActive(workspaceId);
43478
- } catch {
43479
- return null;
43480
- }
43481
- }
43482
- async fetchTasks(client, workspaceId) {
43483
- try {
43484
- return await client.tasks.list(workspaceId);
43485
- } catch {
43486
- return [];
43487
- }
43488
- }
43489
- async fetchSkippedSuggestions(client, workspaceId) {
43490
- try {
43491
- return await client.suggestions.list(workspaceId, { status: "SKIPPED" });
43492
- } catch {
43493
- return [];
43494
- }
43495
- }
43496
- readGitLog(projectPath) {
43497
- try {
43498
- return execFileSync4("git", ["log", "--oneline", "--no-decorate", "-n", "20"], {
43499
- cwd: projectPath,
43500
- encoding: "utf-8",
43501
- timeout: 1e4,
43502
- stdio: ["pipe", "pipe", "pipe"]
43503
- }).trim();
43504
- } catch {
43505
- return "";
43506
- }
43507
- }
43508
- readArtifacts(projectPath) {
43509
- const artifactsDir = join9(projectPath, ".locus", "artifacts");
43510
- if (!existsSync9(artifactsDir))
43511
- return [];
43512
- try {
43513
- const files = readdirSync5(artifactsDir).filter((f) => f.endsWith(".md"));
43514
- return files.slice(0, 10).map((name) => ({
43515
- name,
43516
- content: readFileSync8(join9(artifactsDir, name), "utf-8").slice(0, 2000)
43517
- }));
43518
- } catch {
43519
- return [];
43520
- }
43521
- }
43522
- readLocusInstructions(projectPath) {
43523
- const locusPath = join9(projectPath, ".locus", "LOCUS.md");
43524
- if (!existsSync9(locusPath))
43525
- return null;
43526
- try {
43527
- return readFileSync8(locusPath, "utf-8").slice(0, 3000);
43528
- } catch {
43529
- return null;
43530
- }
43531
- }
43532
- }
43533
- // ../sdk/src/proposals/proposal-engine.ts
43534
- init_src();
43535
- init_factory();
43536
- class ProposalEngine {
43537
- contextGatherer;
43538
- constructor(contextGatherer) {
43539
- this.contextGatherer = contextGatherer ?? new ContextGatherer;
43540
- }
43541
- async runProposalCycle(projectPath, client, workspaceId) {
43542
- const context2 = await this.contextGatherer.gather(projectPath, client, workspaceId);
43543
- return this.generateProposals(context2, projectPath, client, workspaceId);
43544
- }
43545
- async generateProposals(context2, projectPath, client, workspaceId) {
43546
- const prompt = this.buildPrompt(context2);
43547
- const runner = createAiRunner(undefined, {
43548
- projectPath,
43549
- timeoutMs: 5 * 60 * 1000,
43550
- maxTurns: 1
43551
- });
43552
- let aiResponse;
43553
- try {
43554
- aiResponse = await runner.run(prompt);
43555
- } catch {
43556
- return [];
43557
- }
43558
- const proposals = this.parseResponse(aiResponse);
43559
- const created = [];
43560
- for (const proposal of proposals) {
43561
- if (this.isDuplicate(proposal.title, context2.skippedSuggestions)) {
43562
- continue;
43563
- }
43564
- try {
43565
- const suggestion2 = await client.suggestions.create(workspaceId, {
43566
- type: "NEXT_STEP" /* NEXT_STEP */,
43567
- title: proposal.title,
43568
- description: proposal.description,
43569
- metadata: {
43570
- complexity: proposal.complexity,
43571
- relatedBacklogItem: proposal.relatedBacklogItem,
43572
- source: "proposal-engine"
43573
- }
43574
- });
43575
- created.push(suggestion2);
43576
- } catch {}
43577
- }
43578
- return created;
43579
- }
43580
- buildPrompt(context2) {
43581
- const sections = [];
43582
- 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.");
43583
- if (context2.activeSprint) {
43584
- const sprintInfo = `Sprint: ${context2.activeSprint.name} (${context2.activeSprint.status})`;
43585
- const tasksByStatus = this.groupTasksByStatus(context2.sprintTasks);
43586
- sections.push(`## Current Sprint
43587
- ${sprintInfo}
43588
- ${tasksByStatus}`);
43589
- }
43590
- if (context2.backlogTasks.length > 0) {
43591
- const backlogList = context2.backlogTasks.slice(0, 15).map((t) => `- [${t.priority}] ${t.title}${t.description ? `: ${t.description.slice(0, 100)}` : ""}`).join(`
43592
- `);
43593
- sections.push(`## Backlog Items
43594
- ${backlogList}`);
43595
- }
43596
- if (context2.gitLog) {
43597
- sections.push(`## Recent Commits (last 20)
43598
- ${context2.gitLog}`);
43599
- }
43600
- if (context2.artifactContents.length > 0) {
43601
- const artifacts = context2.artifactContents.map((a) => `### ${a.name}
43602
- ${a.content}`).join(`
43603
-
43604
- `);
43605
- sections.push(`## Product Context
43606
- ${artifacts}`);
43607
- }
43608
- if (context2.locusInstructions) {
43609
- sections.push(`## Project Instructions
43610
- ${context2.locusInstructions}`);
43611
- }
43612
- if (context2.skippedSuggestions.length > 0) {
43613
- const skipped = context2.skippedSuggestions.map((s) => `- ${s.title}`).join(`
43614
- `);
43615
- sections.push(`## Previously Skipped Proposals (do NOT re-propose these)
43616
- ${skipped}`);
43617
- }
43618
- sections.push(`## Instructions
43619
- Propose 1-3 high-value next steps. For each, respond with exactly this format:
43620
-
43621
- PROPOSAL_START
43622
- Title: <clear, concise title>
43623
- Description: <what to do and why, 2-4 sentences>
43624
- Complexity: <low|medium|high>
43625
- Related Backlog: <title of related backlog item, or "none">
43626
- PROPOSAL_END
43627
-
43628
- Rules:
43629
- - Focus on what would deliver the most value right now
43630
- - Align with the current sprint goals when possible
43631
- - Don't propose things that are already in progress
43632
- - Don't re-propose previously skipped suggestions
43633
- - Keep proposals specific and actionable`);
43634
- return sections.join(`
43635
-
43636
- `);
43637
- }
43638
- parseResponse(response) {
43639
- const proposals = [];
43640
- const blocks = response.split("PROPOSAL_START");
43641
- for (const block of blocks) {
43642
- const endIdx = block.indexOf("PROPOSAL_END");
43643
- if (endIdx === -1)
43644
- continue;
43645
- const content = block.slice(0, endIdx).trim();
43646
- const title = this.extractField(content, "Title");
43647
- const description = this.extractField(content, "Description");
43648
- const complexity = this.extractField(content, "Complexity") ?? "medium";
43649
- const relatedRaw = this.extractField(content, "Related Backlog");
43650
- const relatedBacklogItem = relatedRaw && relatedRaw.toLowerCase() !== "none" ? relatedRaw : null;
43651
- if (title && description) {
43652
- proposals.push({
43653
- title: title.slice(0, 200),
43654
- description: description.slice(0, 2000),
43655
- complexity: complexity.toLowerCase(),
43656
- relatedBacklogItem
43657
- });
43658
- }
43659
- }
43660
- return proposals.slice(0, 3);
43661
- }
43662
- extractField(content, field) {
43663
- const regex = new RegExp(`^${field}:\\s*(.+)`, "im");
43664
- const match = content.match(regex);
43665
- return match ? match[1].trim() : null;
43666
- }
43667
- isDuplicate(title, skipped) {
43668
- const normalized = title.toLowerCase().trim();
43669
- return skipped.some((s) => {
43670
- const skippedNorm = s.title.toLowerCase().trim();
43671
- return skippedNorm === normalized || skippedNorm.includes(normalized) || normalized.includes(skippedNorm);
43672
- });
43673
- }
43674
- groupTasksByStatus(tasks2) {
43675
- const groups = {};
43676
- for (const t of tasks2) {
43677
- groups[t.status] = (groups[t.status] ?? 0) + 1;
43678
- }
43679
- return Object.entries(groups).map(([status, count]) => `- ${status}: ${count} task(s)`).join(`
43680
- `);
43681
- }
43682
- }
43683
43349
  // ../sdk/src/index-node.ts
43684
43350
  init_colors();
43685
43351
 
@@ -44143,218 +43809,6 @@ ${escapeHtml(desc)}
44143
43809
  const artifactName = ctx.match[1];
44144
43810
  await convertArtifactToPlan(ctx, config2, executor, artifactName);
44145
43811
  });
44146
- bot.action(/^proposal_start_(.+)$/, async (ctx) => {
44147
- await ctx.answerCbQuery("Starting proposal…");
44148
- const suggestionId = ctx.match[1];
44149
- if (!config2.apiKey) {
44150
- await ctx.reply(formatError2("API key is required to act on proposals."), {
44151
- parse_mode: "HTML"
44152
- });
44153
- return;
44154
- }
44155
- try {
44156
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44157
- const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "ACTED_ON" /* ACTED_ON */ });
44158
- try {
44159
- await ctx.editMessageText(`▶️ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" accepted. Starting planning…`, { parse_mode: "HTML" });
44160
- } catch {
44161
- await ctx.reply(`▶️ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" accepted. Starting planning…`, { parse_mode: "HTML" });
44162
- }
44163
- const planPrompt = `${suggestion2.title}
44164
-
44165
- ${suggestion2.description}`;
44166
- const args = executor.buildArgs(["plan", planPrompt], {
44167
- needsApiKey: true
44168
- });
44169
- const result = await executor.execute(args);
44170
- const output = (result.stdout + result.stderr).trim();
44171
- if (output) {
44172
- const parts = splitMessage(formatCommandOutput("locus plan", output, result.exitCode));
44173
- for (const part of parts) {
44174
- await ctx.reply(part, { parse_mode: "HTML" });
44175
- }
44176
- }
44177
- } catch (err) {
44178
- console.error("[callback:proposal_start] Failed:", err);
44179
- await ctx.reply(formatError2(`Failed to start proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44180
- }
44181
- });
44182
- bot.action(/^proposal_skip_(.+)$/, async (ctx) => {
44183
- await ctx.answerCbQuery("Skipping…");
44184
- const suggestionId = ctx.match[1];
44185
- if (!config2.apiKey) {
44186
- await ctx.reply(formatError2("API key is required to act on proposals."), {
44187
- parse_mode: "HTML"
44188
- });
44189
- return;
44190
- }
44191
- try {
44192
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44193
- const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "SKIPPED" /* SKIPPED */ });
44194
- try {
44195
- await ctx.editMessageText(`⏭ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" skipped. Won't suggest this again.`, { parse_mode: "HTML" });
44196
- } catch {
44197
- await ctx.reply(`⏭ Proposal "<b>${escapeHtml(suggestion2.title)}</b>" skipped. Won't suggest this again.`, { parse_mode: "HTML" });
44198
- }
44199
- } catch (err) {
44200
- console.error("[callback:proposal_skip] Failed:", err);
44201
- await ctx.reply(formatError2(`Failed to skip proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44202
- }
44203
- });
44204
- bot.action(/^proposal_details_(.+)$/, async (ctx) => {
44205
- await ctx.answerCbQuery();
44206
- const suggestionId = ctx.match[1];
44207
- if (!config2.apiKey) {
44208
- await ctx.reply(formatError2("API key is required to view proposal details."), { parse_mode: "HTML" });
44209
- return;
44210
- }
44211
- try {
44212
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44213
- const suggestion2 = await client.suggestions.get(workspaceId, suggestionId);
44214
- const complexityMap = {
44215
- low: "2/5",
44216
- medium: "3/5",
44217
- high: "4/5"
44218
- };
44219
- const complexity = complexityMap[String(suggestion2.metadata?.complexity).toLowerCase()] ?? "—";
44220
- const relatedTo = suggestion2.metadata?.relatedBacklogItem || "New initiative";
44221
- let msg = `\uD83D\uDCCB <b>Proposal Details</b>
44222
-
44223
- `;
44224
- msg += `<b>Title:</b> ${escapeHtml(suggestion2.title)}
44225
- `;
44226
- msg += `<b>Type:</b> ${escapeHtml(suggestion2.type)}
44227
- `;
44228
- msg += `<b>Status:</b> ${escapeHtml(suggestion2.status)}
44229
- `;
44230
- msg += `<b>Complexity:</b> ${escapeHtml(complexity)}
44231
- `;
44232
- msg += `<b>Related to:</b> ${escapeHtml(relatedTo)}
44233
- `;
44234
- msg += `<b>Created:</b> ${escapeHtml(suggestion2.createdAt)}
44235
- `;
44236
- msg += `<b>Expires:</b> ${escapeHtml(suggestion2.expiresAt)}
44237
- `;
44238
- if (suggestion2.jobRunId) {
44239
- msg += `<b>Job Run:</b> <code>${escapeHtml(suggestion2.jobRunId)}</code>
44240
- `;
44241
- }
44242
- msg += `
44243
- <b>Description:</b>
44244
- ${escapeHtml(truncateOutput(suggestion2.description, 2000))}`;
44245
- if (suggestion2.metadata && Object.keys(suggestion2.metadata).length > 0) {
44246
- const {
44247
- complexity: _c,
44248
- relatedBacklogItem: _r,
44249
- ...rest
44250
- } = suggestion2.metadata;
44251
- if (Object.keys(rest).length > 0) {
44252
- msg += `
44253
-
44254
- <b>Metadata:</b>
44255
- <pre>${escapeHtml(JSON.stringify(rest, null, 2).slice(0, 500))}</pre>`;
44256
- }
44257
- }
44258
- const parts = splitMessage(msg);
44259
- for (const part of parts) {
44260
- await ctx.reply(part, {
44261
- parse_mode: "HTML",
44262
- link_preview_options: { is_disabled: true }
44263
- });
44264
- }
44265
- } catch (err) {
44266
- console.error("[callback:proposal_details] Failed:", err);
44267
- await ctx.reply(formatError2(`Failed to fetch proposal: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44268
- }
44269
- });
44270
- bot.action(/^suggestion_fix_(.+)$/, async (ctx) => {
44271
- await ctx.answerCbQuery("Applying fix…");
44272
- const suggestionId = ctx.match[1];
44273
- if (!config2.apiKey) {
44274
- await ctx.reply(formatError2("API key is required to act on suggestions."), { parse_mode: "HTML" });
44275
- return;
44276
- }
44277
- try {
44278
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44279
- const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "ACTED_ON" /* ACTED_ON */ });
44280
- try {
44281
- await ctx.editMessageText(formatSuccess(`Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" marked as fixed.`), { parse_mode: "HTML" });
44282
- } catch {
44283
- await ctx.reply(formatSuccess(`Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" marked as fixed.`), { parse_mode: "HTML" });
44284
- }
44285
- } catch (err) {
44286
- console.error("[callback:suggestion_fix] Failed:", err);
44287
- await ctx.reply(formatError2(`Failed to update suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44288
- }
44289
- });
44290
- bot.action(/^suggestion_skip_(.+)$/, async (ctx) => {
44291
- await ctx.answerCbQuery("Skipping…");
44292
- const suggestionId = ctx.match[1];
44293
- if (!config2.apiKey) {
44294
- await ctx.reply(formatError2("API key is required to act on suggestions."), { parse_mode: "HTML" });
44295
- return;
44296
- }
44297
- try {
44298
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44299
- const suggestion2 = await client.suggestions.updateStatus(workspaceId, suggestionId, { status: "SKIPPED" /* SKIPPED */ });
44300
- try {
44301
- await ctx.editMessageText(`⏭ Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" skipped.`, { parse_mode: "HTML" });
44302
- } catch {
44303
- await ctx.reply(`⏭ Suggestion "<b>${escapeHtml(suggestion2.title)}</b>" skipped.`, { parse_mode: "HTML" });
44304
- }
44305
- } catch (err) {
44306
- console.error("[callback:suggestion_skip] Failed:", err);
44307
- await ctx.reply(formatError2(`Failed to update suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44308
- }
44309
- });
44310
- bot.action(/^suggestion_details_(.+)$/, async (ctx) => {
44311
- await ctx.answerCbQuery();
44312
- const suggestionId = ctx.match[1];
44313
- if (!config2.apiKey) {
44314
- await ctx.reply(formatError2("API key is required to view suggestion details."), { parse_mode: "HTML" });
44315
- return;
44316
- }
44317
- try {
44318
- const { client, workspaceId } = await getClientAndWorkspace(config2);
44319
- const suggestion2 = await client.suggestions.get(workspaceId, suggestionId);
44320
- let msg = `\uD83D\uDCCB <b>Suggestion Details</b>
44321
-
44322
- `;
44323
- msg += `<b>Title:</b> ${escapeHtml(suggestion2.title)}
44324
- `;
44325
- msg += `<b>Type:</b> ${escapeHtml(suggestion2.type)}
44326
- `;
44327
- msg += `<b>Status:</b> ${escapeHtml(suggestion2.status)}
44328
- `;
44329
- msg += `<b>Created:</b> ${escapeHtml(suggestion2.createdAt)}
44330
- `;
44331
- msg += `<b>Expires:</b> ${escapeHtml(suggestion2.expiresAt)}
44332
- `;
44333
- if (suggestion2.jobRunId) {
44334
- msg += `<b>Job Run:</b> <code>${escapeHtml(suggestion2.jobRunId)}</code>
44335
- `;
44336
- }
44337
- msg += `
44338
- <b>Description:</b>
44339
- ${escapeHtml(truncateOutput(suggestion2.description, 2000))}`;
44340
- if (suggestion2.metadata && Object.keys(suggestion2.metadata).length > 0) {
44341
- msg += `
44342
-
44343
- <b>Metadata:</b>
44344
- <pre>${escapeHtml(JSON.stringify(suggestion2.metadata, null, 2).slice(0, 500))}</pre>`;
44345
- }
44346
- const parts = splitMessage(msg);
44347
- for (const part of parts) {
44348
- await ctx.reply(part, {
44349
- parse_mode: "HTML",
44350
- link_preview_options: { is_disabled: true }
44351
- });
44352
- }
44353
- } catch (err) {
44354
- console.error("[callback:suggestion_details] Failed:", err);
44355
- await ctx.reply(formatError2(`Failed to fetch suggestion: ${err instanceof Error ? err.message : String(err)}`), { parse_mode: "HTML" });
44356
- }
44357
- });
44358
43812
  }
44359
43813
 
44360
43814
  // src/commands/activity.ts
@@ -44514,8 +43968,8 @@ Total: ${agents.length} active agent(s)`;
44514
43968
  }
44515
43969
  }
44516
43970
  // src/commands/config.ts
44517
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
44518
- import { join as join10 } from "node:path";
43971
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
43972
+ import { join as join9 } from "node:path";
44519
43973
 
44520
43974
  // src/command-whitelist.ts
44521
43975
  var SAFE_BRANCH = /^[a-zA-Z0-9_\-./]+$/;
@@ -44883,13 +44337,13 @@ var USAGE = `<b>Usage:</b>
44883
44337
  ${Object.entries(ALLOWED_KEYS).map(([k, v]) => ` \`${k}\` — ${v.description}`).join(`
44884
44338
  `)}`;
44885
44339
  function getSettingsPath(config2) {
44886
- return join10(config2.projectPath, CONFIG_DIR, SETTINGS_FILE);
44340
+ return join9(config2.projectPath, CONFIG_DIR, SETTINGS_FILE);
44887
44341
  }
44888
44342
  function loadSettings(config2) {
44889
44343
  const path = getSettingsPath(config2);
44890
- if (!existsSync10(path))
44344
+ if (!existsSync9(path))
44891
44345
  return null;
44892
- const raw = readFileSync9(path, "utf-8");
44346
+ const raw = readFileSync8(path, "utf-8");
44893
44347
  return JSON.parse(raw);
44894
44348
  }
44895
44349
  function saveSettings(config2, settings) {
@@ -45212,67 +44666,16 @@ async function dashboardCommand(ctx, config2) {
45212
44666
  import { spawn as spawn4 } from "node:child_process";
45213
44667
 
45214
44668
  // src/env.ts
45215
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "node:fs";
45216
44669
  import { homedir as homedir2 } from "node:os";
45217
- import { join as join11 } from "node:path";
45218
- function resolveNvmBinDir() {
45219
- const nvmDir = process.env.NVM_DIR || join11(homedir2(), ".nvm");
45220
- const versionsDir = join11(nvmDir, "versions", "node");
45221
- if (!existsSync11(versionsDir))
45222
- return null;
45223
- let versions2;
45224
- try {
45225
- versions2 = readdirSync6(versionsDir).filter((d) => d.startsWith("v"));
45226
- } catch {
45227
- return null;
45228
- }
45229
- if (versions2.length === 0)
45230
- return null;
45231
- const currentNodeVersion = `v${process.versions.node}`;
45232
- const currentBin = join11(versionsDir, currentNodeVersion, "bin");
45233
- if (versions2.includes(currentNodeVersion) && existsSync11(currentBin)) {
45234
- return currentBin;
45235
- }
45236
- const aliasPath = join11(nvmDir, "alias", "default");
45237
- if (existsSync11(aliasPath)) {
45238
- try {
45239
- const alias = readFileSync10(aliasPath, "utf-8").trim();
45240
- const match = versions2.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
45241
- if (match) {
45242
- const bin2 = join11(versionsDir, match, "bin");
45243
- if (existsSync11(bin2))
45244
- return bin2;
45245
- }
45246
- } catch {}
45247
- }
45248
- const sorted = versions2.sort((a, b) => {
45249
- const pa = a.slice(1).split(".").map(Number);
45250
- const pb = b.slice(1).split(".").map(Number);
45251
- for (let i = 0;i < 3; i++) {
45252
- if ((pa[i] || 0) !== (pb[i] || 0))
45253
- return (pb[i] || 0) - (pa[i] || 0);
45254
- }
45255
- return 0;
45256
- });
45257
- const bin = join11(versionsDir, sorted[0], "bin");
45258
- return existsSync11(bin) ? bin : null;
45259
- }
44670
+ import { join as join10 } from "node:path";
45260
44671
  function extraPathDirs() {
45261
44672
  const home = homedir2();
45262
- const dirs = [
45263
- join11(home, ".bun", "bin"),
45264
- join11(home, ".local", "bin"),
45265
- join11(home, ".npm", "bin"),
45266
- join11(home, ".npm-global", "bin"),
44673
+ return [
44674
+ join10(home, ".bun", "bin"),
44675
+ join10(home, ".nvm", "current", "bin"),
44676
+ join10(home, ".local", "bin"),
45267
44677
  "/usr/local/bin"
45268
44678
  ];
45269
- const nvmBin = resolveNvmBinDir();
45270
- if (nvmBin)
45271
- dirs.push(nvmBin);
45272
- const fnmCurrent = join11(home, ".fnm", "current", "bin");
45273
- if (existsSync11(fnmCurrent))
45274
- dirs.push(fnmCurrent);
45275
- return dirs;
45276
44679
  }
45277
44680
  function buildSpawnEnv() {
45278
44681
  const existing = process.env.PATH ?? "";
@@ -46193,7 +45596,7 @@ async function approveTaskCommand(ctx, config2) {
46193
45596
  // src/commands/upgrade.ts
46194
45597
  import { spawn as spawn5 } from "node:child_process";
46195
45598
  import { homedir as homedir3 } from "node:os";
46196
- import { join as join12 } from "node:path";
45599
+ import { join as join11 } from "node:path";
46197
45600
  function runCommand2(cmd, args, timeout) {
46198
45601
  return new Promise((resolve2) => {
46199
45602
  const proc = spawn5(cmd, args, {
@@ -46232,11 +45635,13 @@ function getRestartInfo() {
46232
45635
  if (platform === "linux") {
46233
45636
  return {
46234
45637
  label: "systemd (Linux)",
46235
- commands: [{ cmd: "sudo", args: ["systemctl", "restart", "locus"] }]
45638
+ commands: [
45639
+ { cmd: "sudo", args: ["systemctl", "restart", "locus-telegram"] }
45640
+ ]
46236
45641
  };
46237
45642
  }
46238
45643
  if (platform === "darwin") {
46239
- const plistPath = join12(homedir3(), "Library/LaunchAgents/com.locus.agent.plist");
45644
+ const plistPath = join11(homedir3(), "Library/LaunchAgents/com.locus.telegram.plist");
46240
45645
  return {
46241
45646
  label: "launchctl (macOS)",
46242
45647
  commands: [
@@ -46352,7 +45757,7 @@ async function workspaceCommand(ctx, config2) {
46352
45757
  }
46353
45758
  // src/executor.ts
46354
45759
  import { spawn as spawn6 } from "node:child_process";
46355
- import { join as join13 } from "node:path";
45760
+ import { join as join12 } from "node:path";
46356
45761
  function timestamp2() {
46357
45762
  return new Date().toLocaleTimeString("en-GB", { hour12: false });
46358
45763
  }
@@ -46368,7 +45773,7 @@ class CliExecutor {
46368
45773
  }
46369
45774
  resolveCommand(args) {
46370
45775
  if (this.config.testMode) {
46371
- const cliPath = join13(this.config.projectPath, "packages/cli/src/cli.ts");
45776
+ const cliPath = join12(this.config.projectPath, "packages/cli/src/cli.ts");
46372
45777
  return { cmd: "bun", cmdArgs: ["run", cliPath, ...args] };
46373
45778
  }
46374
45779
  return { cmd: "locus", cmdArgs: args };
@@ -46527,90 +45932,12 @@ class CliExecutor {
46527
45932
  }
46528
45933
  }
46529
45934
 
46530
- // src/notifications.ts
46531
- var import_telegraf5 = __toESM(require_lib3(), 1);
46532
- var PROPOSALS_GENERATED = "PROPOSALS_GENERATED";
46533
- var COMPLEXITY_DISPLAY = {
46534
- low: "2/5",
46535
- medium: "3/5",
46536
- high: "4/5"
46537
- };
46538
- var SUGGESTION_TYPE_ICONS = {
46539
- CODE_FIX: "\uD83D\uDD27",
46540
- DEPENDENCY_UPDATE: "\uD83D\uDCE6",
46541
- NEXT_STEP: "➡️",
46542
- REFACTOR: "♻️",
46543
- TEST_FIX: "\uD83E\uDDEA"
46544
- };
46545
-
46546
- class Notifier {
46547
- bot;
46548
- chatId;
46549
- constructor(bot, chatId) {
46550
- this.bot = bot;
46551
- this.chatId = chatId;
46552
- }
46553
- connect(emitter) {
46554
- emitter.on(PROPOSALS_GENERATED, (payload) => {
46555
- for (const suggestion2 of payload.suggestions) {
46556
- this.notifyProposal(suggestion2).catch((err) => console.error("[notifier] Failed to send proposal:", err));
46557
- }
46558
- });
46559
- }
46560
- async notifyProposal(suggestion2) {
46561
- const complexity = COMPLEXITY_DISPLAY[String(suggestion2.metadata?.complexity).toLowerCase()] ?? "3/5";
46562
- const relatedTo = suggestion2.metadata?.relatedBacklogItem || "New initiative";
46563
- let msg = `\uD83D\uDCA1 <b>Proposal:</b> ${escapeHtml(suggestion2.title)}
46564
-
46565
- `;
46566
- msg += `${escapeHtml(truncateOutput(suggestion2.description, 800))}
46567
-
46568
- `;
46569
- msg += `<b>Complexity:</b> ${escapeHtml(complexity)}
46570
- `;
46571
- msg += `<b>Related to:</b> ${escapeHtml(relatedTo)}`;
46572
- const buttons = [
46573
- [
46574
- import_telegraf5.Markup.button.callback("▶️ Start", `proposal_start_${suggestion2.id}`),
46575
- import_telegraf5.Markup.button.callback("⏭ Skip", `proposal_skip_${suggestion2.id}`),
46576
- import_telegraf5.Markup.button.callback("\uD83D\uDCCB Details", `proposal_details_${suggestion2.id}`)
46577
- ]
46578
- ];
46579
- await this.bot.telegram.sendMessage(this.chatId, msg, {
46580
- parse_mode: "HTML",
46581
- ...import_telegraf5.Markup.inlineKeyboard(buttons)
46582
- });
46583
- }
46584
- async notifySuggestion(suggestion2) {
46585
- const icon = SUGGESTION_TYPE_ICONS[suggestion2.type] ?? "\uD83D\uDCA1";
46586
- let msg = `${icon} <b>Suggestion:</b> ${escapeHtml(suggestion2.title)}
46587
-
46588
- `;
46589
- msg += `${escapeHtml(truncateOutput(suggestion2.description, 800))}
46590
- `;
46591
- msg += `
46592
- <b>Type:</b> ${escapeHtml(suggestion2.type)}`;
46593
- const buttons = [
46594
- [
46595
- import_telegraf5.Markup.button.callback("\uD83D\uDD27 Fix", `suggestion_fix_${suggestion2.id}`),
46596
- import_telegraf5.Markup.button.callback("⏭ Skip", `suggestion_skip_${suggestion2.id}`),
46597
- import_telegraf5.Markup.button.callback("\uD83D\uDCCB Details", `suggestion_details_${suggestion2.id}`)
46598
- ]
46599
- ];
46600
- await this.bot.telegram.sendMessage(this.chatId, msg, {
46601
- parse_mode: "HTML",
46602
- ...import_telegraf5.Markup.inlineKeyboard(buttons)
46603
- });
46604
- }
46605
- }
46606
-
46607
45935
  // src/bot.ts
46608
45936
  function createBot(config2) {
46609
- const bot = new import_telegraf6.Telegraf(config2.botToken, {
45937
+ const bot = new import_telegraf5.Telegraf(config2.botToken, {
46610
45938
  handlerTimeout: HANDLER_TIMEOUT
46611
45939
  });
46612
45940
  const executor = new CliExecutor(config2);
46613
- const notifier = new Notifier(bot, config2.chatId);
46614
45941
  bot.use(async (ctx, next) => {
46615
45942
  const chatId = ctx.chat?.id;
46616
45943
  if (chatId !== config2.chatId) {
@@ -46710,22 +46037,22 @@ function createBot(config2) {
46710
46037
  { command: "upgrade", description: "Upgrade Locus CLI & restart bot" },
46711
46038
  { command: "help", description: "Show all commands" }
46712
46039
  ]).catch((err) => console.error("Failed to set bot commands:", err));
46713
- return { bot, notifier };
46040
+ return bot;
46714
46041
  }
46715
46042
 
46716
46043
  // src/config.ts
46717
46044
  var import_dotenv = __toESM(require_main(), 1);
46718
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
46719
- import { join as join14 } from "node:path";
46045
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
46046
+ import { join as join13 } from "node:path";
46720
46047
  import_dotenv.default.config();
46721
46048
  var SETTINGS_FILE2 = "settings.json";
46722
46049
  var CONFIG_DIR2 = ".locus";
46723
46050
  function loadSettings2(projectPath) {
46724
- const settingsPath = join14(projectPath, CONFIG_DIR2, SETTINGS_FILE2);
46725
- if (!existsSync12(settingsPath)) {
46051
+ const settingsPath = join13(projectPath, CONFIG_DIR2, SETTINGS_FILE2);
46052
+ if (!existsSync10(settingsPath)) {
46726
46053
  return null;
46727
46054
  }
46728
- const raw = readFileSync11(settingsPath, "utf-8");
46055
+ const raw = readFileSync9(settingsPath, "utf-8");
46729
46056
  return JSON.parse(raw);
46730
46057
  }
46731
46058
  function resolveConfig() {
@@ -46762,66 +46089,6 @@ function resolveConfig() {
46762
46089
  };
46763
46090
  }
46764
46091
 
46765
- // src/scheduler.ts
46766
- var PROPOSAL_INTERVAL_MS = 6 * 60 * 60 * 1000;
46767
-
46768
- class ProposalScheduler {
46769
- config;
46770
- notifier;
46771
- timer = null;
46772
- running = false;
46773
- constructor(config2, notifier) {
46774
- this.config = config2;
46775
- this.notifier = notifier;
46776
- }
46777
- async start() {
46778
- if (!this.config.apiKey) {
46779
- console.log("[scheduler] API key not configured — proposal scheduling disabled");
46780
- return;
46781
- }
46782
- console.log("[scheduler] Starting proposal scheduler (every 6 hours)");
46783
- this.runCycle();
46784
- this.timer = setInterval(() => this.runCycle(), PROPOSAL_INTERVAL_MS);
46785
- }
46786
- stop() {
46787
- if (this.timer) {
46788
- clearInterval(this.timer);
46789
- this.timer = null;
46790
- }
46791
- console.log("[scheduler] Proposal scheduler stopped");
46792
- }
46793
- async runCycle() {
46794
- if (this.running) {
46795
- console.log("[scheduler] Proposal cycle already in progress, skipping");
46796
- return;
46797
- }
46798
- this.running = true;
46799
- const ts = new Date().toLocaleTimeString();
46800
- console.log(`[scheduler] Running proposal cycle at ${ts}`);
46801
- try {
46802
- const client = createClient(this.config);
46803
- const workspaceId = await resolveWorkspaceId(client, this.config);
46804
- const engine = new ProposalEngine;
46805
- const suggestions2 = await engine.runProposalCycle(this.config.projectPath, client, workspaceId);
46806
- if (suggestions2.length > 0) {
46807
- console.log(`[scheduler] ${suggestions2.length} proposal(s) generated`);
46808
- for (const s of suggestions2) {
46809
- console.log(`[scheduler] - ${s.title}`);
46810
- this.notifier.notifyProposal(s).catch((err) => {
46811
- console.error("[scheduler] Failed to notify proposal:", err);
46812
- });
46813
- }
46814
- } else {
46815
- console.log("[scheduler] No new proposals generated");
46816
- }
46817
- } catch (err) {
46818
- console.error(`[scheduler] Proposal cycle failed: ${err instanceof Error ? err.message : String(err)}`);
46819
- } finally {
46820
- this.running = false;
46821
- }
46822
- }
46823
- }
46824
-
46825
46092
  // src/index.ts
46826
46093
  async function main() {
46827
46094
  console.log("Locus Telegram Bot");
@@ -46833,13 +46100,10 @@ async function main() {
46833
46100
  console.log(`API Key: ${config2.apiKey ? "configured" : "not set"}`);
46834
46101
  console.log(`----------------------------------------------
46835
46102
  `);
46836
- const { bot, notifier } = createBot(config2);
46837
- const scheduler = new ProposalScheduler(config2, notifier);
46838
- await scheduler.start();
46103
+ const bot = createBot(config2);
46839
46104
  const shutdown = (signal) => {
46840
46105
  console.log(`
46841
46106
  Received ${signal}. Shutting down...`);
46842
- scheduler.stop();
46843
46107
  bot.stop(signal);
46844
46108
  process.exit(0);
46845
46109
  };
package/package.json CHANGED
@@ -1,18 +1,8 @@
1
1
  {
2
2
  "name": "@locusai/telegram",
3
- "version": "0.15.4",
3
+ "version": "0.16.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
- },
16
6
  "bin": {
17
7
  "locus-telegram": "./bin/telegram.js"
18
8
  },
@@ -41,8 +31,8 @@
41
31
  "author": "",
42
32
  "license": "MIT",
43
33
  "dependencies": {
44
- "@locusai/sdk": "^0.15.4",
45
- "@locusai/shared": "^0.15.4",
34
+ "@locusai/sdk": "^0.16.1",
35
+ "@locusai/shared": "^0.16.1",
46
36
  "dotenv": "^16.4.7",
47
37
  "telegraf": "^4.16.3"
48
38
  },