@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.
- package/bin/telegram.js +720 -29
- 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
|
|
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
|
|
43965
|
-
import { join as
|
|
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
|
|
44885
|
+
return join10(config2.projectPath, CONFIG_DIR, SETTINGS_FILE);
|
|
44334
44886
|
}
|
|
44335
44887
|
function loadSettings(config2) {
|
|
44336
44888
|
const path = getSettingsPath(config2);
|
|
44337
|
-
if (!
|
|
44889
|
+
if (!existsSync10(path))
|
|
44338
44890
|
return null;
|
|
44339
|
-
const raw =
|
|
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
|
|
45215
|
+
import { join as join11 } from "node:path";
|
|
44664
45216
|
function extraPathDirs() {
|
|
44665
45217
|
const home = homedir2();
|
|
44666
45218
|
return [
|
|
44667
|
-
|
|
44668
|
-
|
|
44669
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
46039
|
-
import { join as
|
|
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 =
|
|
46045
|
-
if (!
|
|
46672
|
+
const settingsPath = join14(projectPath, CONFIG_DIR2, SETTINGS_FILE2);
|
|
46673
|
+
if (!existsSync11(settingsPath)) {
|
|
46046
46674
|
return null;
|
|
46047
46675
|
}
|
|
46048
|
-
const raw =
|
|
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.
|
|
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.
|
|
35
|
-
"@locusai/shared": "^0.
|
|
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
|
},
|