@locusai/telegram 0.10.2 → 0.10.5

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 +219 -60
  2. package/package.json +2 -2
package/bin/telegram.js CHANGED
@@ -38432,10 +38432,12 @@ class ClaudeRunner {
38432
38432
  currentToolName;
38433
38433
  activeTools = new Map;
38434
38434
  activeProcess = null;
38435
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log2) {
38435
+ timeoutMs;
38436
+ constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log2, timeoutMs) {
38436
38437
  this.model = model;
38437
38438
  this.log = log2;
38438
38439
  this.projectPath = resolve(projectPath);
38440
+ this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
38439
38441
  }
38440
38442
  setEventEmitter(emitter) {
38441
38443
  this.eventEmitter = emitter;
@@ -38451,11 +38453,14 @@ class ClaudeRunner {
38451
38453
  let lastError = null;
38452
38454
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
38453
38455
  try {
38454
- return await this.executeRun(prompt);
38456
+ return await this.withTimeout(this.executeRun(prompt));
38455
38457
  } catch (error48) {
38456
38458
  const err = error48;
38457
38459
  lastError = err;
38458
38460
  const isLastAttempt = attempt === maxRetries;
38461
+ if (err.message.includes("timed out")) {
38462
+ throw err;
38463
+ }
38459
38464
  if (!isLastAttempt) {
38460
38465
  const delay = Math.pow(2, attempt) * 1000;
38461
38466
  console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
@@ -38465,6 +38470,23 @@ class ClaudeRunner {
38465
38470
  }
38466
38471
  throw lastError || new Error("Claude CLI failed after multiple attempts");
38467
38472
  }
38473
+ withTimeout(promise2) {
38474
+ if (this.timeoutMs <= 0)
38475
+ return promise2;
38476
+ return new Promise((resolve2, reject) => {
38477
+ const timer = setTimeout(() => {
38478
+ this.abort();
38479
+ reject(new Error(`Claude CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
38480
+ }, this.timeoutMs);
38481
+ promise2.then((value) => {
38482
+ clearTimeout(timer);
38483
+ resolve2(value);
38484
+ }, (err) => {
38485
+ clearTimeout(timer);
38486
+ reject(err);
38487
+ });
38488
+ });
38489
+ }
38468
38490
  async* runStream(prompt) {
38469
38491
  const args = [
38470
38492
  "--dangerously-skip-permissions",
@@ -38829,18 +38851,22 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
38829
38851
  return new Error(message);
38830
38852
  }
38831
38853
  }
38832
- var SANDBOX_SETTINGS;
38854
+ var SANDBOX_SETTINGS, DEFAULT_TIMEOUT_MS;
38833
38855
  var init_claude_runner = __esm(() => {
38834
38856
  init_config();
38835
38857
  init_colors();
38836
38858
  init_resolve_bin();
38837
38859
  SANDBOX_SETTINGS = JSON.stringify({
38860
+ permissions: {
38861
+ deny: ["Read(../**)", "Edit(../**)"]
38862
+ },
38838
38863
  sandbox: {
38839
38864
  enabled: true,
38840
38865
  autoAllow: true,
38841
38866
  allowUnsandboxedCommands: false
38842
38867
  }
38843
38868
  });
38869
+ DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
38844
38870
  });
38845
38871
 
38846
38872
  // ../sdk/src/ai/codex-runner.ts
@@ -38855,10 +38881,17 @@ class CodexRunner {
38855
38881
  model;
38856
38882
  log;
38857
38883
  activeProcess = null;
38858
- constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log2) {
38884
+ eventEmitter;
38885
+ currentToolName;
38886
+ timeoutMs;
38887
+ constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log2, timeoutMs) {
38859
38888
  this.projectPath = projectPath;
38860
38889
  this.model = model;
38861
38890
  this.log = log2;
38891
+ this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS2;
38892
+ }
38893
+ setEventEmitter(emitter) {
38894
+ this.eventEmitter = emitter;
38862
38895
  }
38863
38896
  abort() {
38864
38897
  if (this.activeProcess && !this.activeProcess.killed) {
@@ -38871,9 +38904,12 @@ class CodexRunner {
38871
38904
  let lastError = null;
38872
38905
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
38873
38906
  try {
38874
- return await this.executeRun(prompt);
38907
+ return await this.withTimeout(this.executeRun(prompt));
38875
38908
  } catch (error48) {
38876
38909
  lastError = error48;
38910
+ if (lastError.message.includes("timed out")) {
38911
+ throw lastError;
38912
+ }
38877
38913
  if (attempt < maxRetries) {
38878
38914
  const delay = Math.pow(2, attempt) * 1000;
38879
38915
  console.warn(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`);
@@ -38883,9 +38919,31 @@ class CodexRunner {
38883
38919
  }
38884
38920
  throw lastError || new Error("Codex CLI failed after multiple attempts");
38885
38921
  }
38922
+ withTimeout(promise2) {
38923
+ if (this.timeoutMs <= 0)
38924
+ return promise2;
38925
+ return new Promise((resolve2, reject) => {
38926
+ const timer = setTimeout(() => {
38927
+ this.abort();
38928
+ reject(new Error(`Codex CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
38929
+ }, this.timeoutMs);
38930
+ promise2.then((value) => {
38931
+ clearTimeout(timer);
38932
+ resolve2(value);
38933
+ }, (err) => {
38934
+ clearTimeout(timer);
38935
+ reject(err);
38936
+ });
38937
+ });
38938
+ }
38886
38939
  async* runStream(prompt) {
38887
38940
  const outputPath = join5(tmpdir(), `locus-codex-${randomUUID()}.txt`);
38888
38941
  const args = this.buildArgs(outputPath);
38942
+ this.eventEmitter?.emitSessionStarted({
38943
+ model: this.model,
38944
+ provider: "codex"
38945
+ });
38946
+ this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
38889
38947
  const codex = spawn3("codex", args, {
38890
38948
  cwd: this.projectPath,
38891
38949
  stdio: ["pipe", "pipe", "pipe"],
@@ -38898,7 +38956,21 @@ class CodexRunner {
38898
38956
  let processEnded = false;
38899
38957
  let errorMessage = "";
38900
38958
  let finalOutput = "";
38959
+ let finalContent = "";
38960
+ let isThinking = false;
38901
38961
  const enqueueChunk = (chunk) => {
38962
+ this.emitEventForChunk(chunk, isThinking);
38963
+ if (chunk.type === "thinking") {
38964
+ isThinking = true;
38965
+ } else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
38966
+ if (isThinking) {
38967
+ this.eventEmitter?.emitThinkingStoped();
38968
+ isThinking = false;
38969
+ }
38970
+ }
38971
+ if (chunk.type === "text_delta") {
38972
+ finalContent += chunk.content;
38973
+ }
38902
38974
  if (resolveChunk) {
38903
38975
  const resolve2 = resolveChunk;
38904
38976
  resolveChunk = null;
@@ -38939,16 +39011,21 @@ class CodexRunner {
38939
39011
  codex.stderr.on("data", processOutput);
38940
39012
  codex.on("error", (err) => {
38941
39013
  errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
39014
+ this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
38942
39015
  signalEnd();
38943
39016
  });
38944
39017
  codex.on("close", (code) => {
38945
39018
  this.activeProcess = null;
38946
- this.cleanupTempFile(outputPath);
38947
39019
  if (code === 0) {
38948
39020
  const result = this.readOutput(outputPath, finalOutput);
39021
+ this.cleanupTempFile(outputPath);
38949
39022
  enqueueChunk({ type: "result", content: result });
38950
- } else if (!errorMessage) {
38951
- errorMessage = this.createErrorFromOutput(code, finalOutput).message;
39023
+ } else {
39024
+ this.cleanupTempFile(outputPath);
39025
+ if (!errorMessage) {
39026
+ errorMessage = this.createErrorFromOutput(code, finalOutput).message;
39027
+ this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
39028
+ }
38952
39029
  }
38953
39030
  signalEnd();
38954
39031
  });
@@ -38962,6 +39039,12 @@ class CodexRunner {
38962
39039
  } else if (processEnded) {
38963
39040
  if (errorMessage) {
38964
39041
  yield { type: "error", error: errorMessage };
39042
+ this.eventEmitter?.emitSessionEnded(false);
39043
+ } else {
39044
+ if (finalContent) {
39045
+ this.eventEmitter?.emitResponseCompleted(finalContent);
39046
+ }
39047
+ this.eventEmitter?.emitSessionEnded(true);
38965
39048
  }
38966
39049
  break;
38967
39050
  } else {
@@ -38971,6 +39054,12 @@ class CodexRunner {
38971
39054
  if (chunk === null) {
38972
39055
  if (errorMessage) {
38973
39056
  yield { type: "error", error: errorMessage };
39057
+ this.eventEmitter?.emitSessionEnded(false);
39058
+ } else {
39059
+ if (finalContent) {
39060
+ this.eventEmitter?.emitResponseCompleted(finalContent);
39061
+ }
39062
+ this.eventEmitter?.emitSessionEnded(true);
38974
39063
  }
38975
39064
  break;
38976
39065
  }
@@ -38978,6 +39067,36 @@ class CodexRunner {
38978
39067
  }
38979
39068
  }
38980
39069
  }
39070
+ emitEventForChunk(chunk, isThinking) {
39071
+ if (!this.eventEmitter)
39072
+ return;
39073
+ switch (chunk.type) {
39074
+ case "text_delta":
39075
+ this.eventEmitter.emitTextDelta(chunk.content);
39076
+ break;
39077
+ case "tool_use":
39078
+ if (this.currentToolName) {
39079
+ this.eventEmitter.emitToolCompleted(this.currentToolName);
39080
+ }
39081
+ this.currentToolName = chunk.tool;
39082
+ this.eventEmitter.emitToolStarted(chunk.tool);
39083
+ break;
39084
+ case "thinking":
39085
+ if (!isThinking) {
39086
+ this.eventEmitter.emitThinkingStarted(chunk.content);
39087
+ }
39088
+ break;
39089
+ case "result":
39090
+ if (this.currentToolName) {
39091
+ this.eventEmitter.emitToolCompleted(this.currentToolName);
39092
+ this.currentToolName = undefined;
39093
+ }
39094
+ break;
39095
+ case "error":
39096
+ this.eventEmitter.emitErrorOccurred(chunk.error);
39097
+ break;
39098
+ }
39099
+ }
38981
39100
  executeRun(prompt) {
38982
39101
  return new Promise((resolve2, reject) => {
38983
39102
  const outputPath = join5(tmpdir(), `locus-codex-${randomUUID()}.txt`);
@@ -39007,10 +39126,12 @@ class CodexRunner {
39007
39126
  });
39008
39127
  codex.on("close", (code) => {
39009
39128
  this.activeProcess = null;
39010
- this.cleanupTempFile(outputPath);
39011
39129
  if (code === 0) {
39012
- resolve2(this.readOutput(outputPath, output));
39130
+ const result = this.readOutput(outputPath, output);
39131
+ this.cleanupTempFile(outputPath);
39132
+ resolve2(result);
39013
39133
  } else {
39134
+ this.cleanupTempFile(outputPath);
39014
39135
  reject(this.createErrorFromOutput(code, errorOutput));
39015
39136
  }
39016
39137
  });
@@ -39020,12 +39141,18 @@ class CodexRunner {
39020
39141
  }
39021
39142
  buildArgs(outputPath) {
39022
39143
  const args = [
39144
+ "--ask-for-approval",
39145
+ "never",
39023
39146
  "exec",
39024
39147
  "--sandbox",
39025
39148
  "workspace-write",
39026
39149
  "--skip-git-repo-check",
39027
39150
  "--output-last-message",
39028
- outputPath
39151
+ outputPath,
39152
+ "-c",
39153
+ "sandbox_workspace_write.network_access=true",
39154
+ "-c",
39155
+ 'sandbox.excludedCommands=["git", "gh"]'
39029
39156
  ];
39030
39157
  if (this.model) {
39031
39158
  args.push("--model", this.model);
@@ -39076,9 +39203,11 @@ class CodexRunner {
39076
39203
  return new Promise((resolve2) => setTimeout(resolve2, ms));
39077
39204
  }
39078
39205
  }
39206
+ var DEFAULT_TIMEOUT_MS2;
39079
39207
  var init_codex_runner = __esm(() => {
39080
39208
  init_config();
39081
39209
  init_resolve_bin();
39210
+ DEFAULT_TIMEOUT_MS2 = 60 * 60 * 1000;
39082
39211
  });
39083
39212
 
39084
39213
  // ../sdk/src/ai/factory.ts
@@ -39087,9 +39216,9 @@ function createAiRunner(provider, config2) {
39087
39216
  const model = config2.model ?? DEFAULT_MODEL[resolvedProvider];
39088
39217
  switch (resolvedProvider) {
39089
39218
  case PROVIDER.CODEX:
39090
- return new CodexRunner(config2.projectPath, model, config2.log);
39219
+ return new CodexRunner(config2.projectPath, model, config2.log, config2.timeoutMs);
39091
39220
  default:
39092
- return new ClaudeRunner(config2.projectPath, model, config2.log);
39221
+ return new ClaudeRunner(config2.projectPath, model, config2.log, config2.timeoutMs);
39093
39222
  }
39094
39223
  }
39095
39224
  var init_factory = __esm(() => {
@@ -39516,8 +39645,9 @@ class WorktreeManager {
39516
39645
  this.ensureDirectory(this.rootPath, "Worktree root");
39517
39646
  addWorktree();
39518
39647
  }
39519
- this.log(`Worktree created at ${worktreePath}`, "success");
39520
- return { worktreePath, branch, baseBranch };
39648
+ const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
39649
+ this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
39650
+ return { worktreePath, branch, baseBranch, baseCommitHash };
39521
39651
  }
39522
39652
  list() {
39523
39653
  const output = this.git("worktree list --porcelain", this.projectPath);
@@ -39622,27 +39752,54 @@ class WorktreeManager {
39622
39752
  try {
39623
39753
  const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
39624
39754
  return Number.parseInt(count, 10) > 0;
39755
+ } catch (err) {
39756
+ this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
39757
+ return false;
39758
+ }
39759
+ }
39760
+ hasCommitsAheadOfHash(worktreePath, baseHash) {
39761
+ try {
39762
+ const headHash = this.git("rev-parse HEAD", worktreePath).trim();
39763
+ return headHash !== baseHash;
39625
39764
  } catch {
39626
39765
  return false;
39627
39766
  }
39628
39767
  }
39629
- commitChanges(worktreePath, message, baseBranch) {
39768
+ commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
39630
39769
  const hasUncommittedChanges = this.hasChanges(worktreePath);
39770
+ if (hasUncommittedChanges) {
39771
+ const statusOutput = this.git("status --porcelain", worktreePath).trim();
39772
+ this.log(`Detected uncommitted changes:
39773
+ ${statusOutput.split(`
39774
+ `).slice(0, 10).join(`
39775
+ `)}${statusOutput.split(`
39776
+ `).length > 10 ? `
39777
+ ... and ${statusOutput.split(`
39778
+ `).length - 10} more` : ""}`, "info");
39779
+ }
39631
39780
  if (!hasUncommittedChanges) {
39632
39781
  if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
39633
39782
  const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
39634
39783
  this.log(`Agent already committed changes (${hash3.slice(0, 8)}); skipping additional commit`, "info");
39635
39784
  return hash3;
39636
39785
  }
39637
- this.log("No changes to commit", "info");
39786
+ if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
39787
+ const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
39788
+ this.log(`Agent already committed changes (${hash3.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
39789
+ return hash3;
39790
+ }
39791
+ const branch = this.getBranch(worktreePath);
39792
+ this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
39638
39793
  return null;
39639
39794
  }
39640
39795
  this.git("add -A", worktreePath);
39641
39796
  const staged = this.git("diff --cached --name-only", worktreePath).trim();
39642
39797
  if (!staged) {
39643
- this.log("No changes to commit", "info");
39798
+ this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
39644
39799
  return null;
39645
39800
  }
39801
+ this.log(`Staging ${staged.split(`
39802
+ `).length} file(s) for commit`, "info");
39646
39803
  this.gitExec(["commit", "-m", message], worktreePath);
39647
39804
  const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
39648
39805
  this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
@@ -40060,15 +40217,27 @@ class TaskExecutor {
40060
40217
  const basePrompt = await this.promptBuilder.build(task2);
40061
40218
  try {
40062
40219
  this.deps.log("Starting Execution...", "info");
40063
- await this.deps.aiRunner.run(basePrompt);
40064
- return {
40065
- success: true,
40066
- summary: "Task completed by the agent"
40067
- };
40220
+ const output = await this.deps.aiRunner.run(basePrompt);
40221
+ const summary = this.extractSummary(output);
40222
+ return { success: true, summary };
40068
40223
  } catch (error48) {
40069
40224
  return { success: false, summary: `Error: ${error48}` };
40070
40225
  }
40071
40226
  }
40227
+ extractSummary(output) {
40228
+ if (!output || !output.trim()) {
40229
+ return "Task completed by the agent";
40230
+ }
40231
+ const paragraphs = output.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
40232
+ if (paragraphs.length === 0) {
40233
+ return "Task completed by the agent";
40234
+ }
40235
+ const last = paragraphs[paragraphs.length - 1];
40236
+ if (last.length > 500) {
40237
+ return `${last.slice(0, 497)}...`;
40238
+ }
40239
+ return last;
40240
+ }
40072
40241
  }
40073
40242
  var init_task_executor = __esm(() => {
40074
40243
  init_prompt_builder();
@@ -40086,7 +40255,7 @@ class GitWorkflow {
40086
40255
  this.log = log2;
40087
40256
  this.ghUsername = ghUsername;
40088
40257
  const projectPath = config2.projectPath || process.cwd();
40089
- this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }) : null;
40258
+ this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log2) : null;
40090
40259
  this.prService = config2.autoPush ? new PrService(projectPath, log2) : null;
40091
40260
  }
40092
40261
  createTaskWorktree(task2, defaultExecutor) {
@@ -40094,6 +40263,7 @@ class GitWorkflow {
40094
40263
  return {
40095
40264
  worktreePath: null,
40096
40265
  baseBranch: null,
40266
+ baseCommitHash: null,
40097
40267
  executor: defaultExecutor
40098
40268
  };
40099
40269
  }
@@ -40119,10 +40289,11 @@ class GitWorkflow {
40119
40289
  return {
40120
40290
  worktreePath: result.worktreePath,
40121
40291
  baseBranch: result.baseBranch,
40292
+ baseCommitHash: result.baseCommitHash,
40122
40293
  executor: taskExecutor
40123
40294
  };
40124
40295
  }
40125
- commitAndPush(worktreePath, task2, baseBranch) {
40296
+ commitAndPush(worktreePath, task2, baseBranch, baseCommitHash) {
40126
40297
  if (!this.worktreeManager) {
40127
40298
  return { branch: null, pushed: false, pushFailed: false };
40128
40299
  }
@@ -40139,7 +40310,7 @@ class GitWorkflow {
40139
40310
 
40140
40311
  ${trailers.join(`
40141
40312
  `)}`;
40142
- const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch);
40313
+ const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
40143
40314
  if (!hash2) {
40144
40315
  this.log("No changes to commit for this task", "info");
40145
40316
  return {
@@ -40179,7 +40350,12 @@ ${trailers.join(`
40179
40350
  } catch (err) {
40180
40351
  const errorMessage = err instanceof Error ? err.message : String(err);
40181
40352
  this.log(`Git commit failed: ${errorMessage}`, "error");
40182
- return { branch: null, pushed: false, pushFailed: false };
40353
+ return {
40354
+ branch: null,
40355
+ pushed: false,
40356
+ pushFailed: true,
40357
+ pushError: `Git commit/push failed: ${errorMessage}`
40358
+ };
40183
40359
  }
40184
40360
  }
40185
40361
  createPullRequest(task2, branch, summary, baseBranch) {
@@ -40505,7 +40681,7 @@ class AgentWorker {
40505
40681
  }
40506
40682
  async executeTask(task2) {
40507
40683
  const fullTask = await this.client.tasks.getById(task2.id, this.config.workspaceId);
40508
- const { worktreePath, baseBranch, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
40684
+ const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
40509
40685
  this.currentWorktreePath = worktreePath;
40510
40686
  let branchPushed = false;
40511
40687
  let keepBranch = false;
@@ -40517,7 +40693,7 @@ class AgentWorker {
40517
40693
  let prError = null;
40518
40694
  let noChanges = false;
40519
40695
  if (result.success && worktreePath) {
40520
- const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined);
40696
+ const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined, baseCommitHash ?? undefined);
40521
40697
  taskBranch = commitResult.branch;
40522
40698
  branchPushed = commitResult.pushed;
40523
40699
  keepBranch = taskBranch !== null;
@@ -41639,25 +41815,8 @@ function isStatusLine(line) {
41639
41815
  return STATUS_PATTERNS.some((pattern) => pattern.test(clean));
41640
41816
  }
41641
41817
  async function runCommand(ctx, executor, config) {
41642
- const text = (ctx.message && "text" in ctx.message ? ctx.message.text : "") || "";
41643
- const input = normalizeInput(text.replace(/^\/run\s*/, "").trim());
41644
- let parsedAgentCount;
41645
- const agentsMatch = input.match(/(?:--agents|-agents|-a)\s+(\d+)/);
41646
- if (agentsMatch) {
41647
- parsedAgentCount = Number.parseInt(agentsMatch[1], 10);
41648
- if (parsedAgentCount < 1 || parsedAgentCount > 5) {
41649
- await ctx.reply(formatError("Agent count must be between 1 and 5."), {
41650
- parse_mode: "HTML"
41651
- });
41652
- return;
41653
- }
41654
- }
41655
- const agentCount = parsedAgentCount ?? config.agentCount ?? 1;
41818
+ const agentCount = config.agentCount ?? 1;
41656
41819
  console.log(`[run] Starting agents (count: ${agentCount})`);
41657
- if (!config.apiKey) {
41658
- await ctx.reply(formatError("API key is not configured. Run: locus config setup --api-key <key>"), { parse_mode: "HTML" });
41659
- return;
41660
- }
41661
41820
  if (activeRunKill) {
41662
41821
  await ctx.reply(formatInfo("Agents are already running. Use /stop to stop them first."), { parse_mode: "HTML" });
41663
41822
  return;
@@ -41667,9 +41826,6 @@ async function runCommand(ctx, executor, config) {
41667
41826
  parse_mode: "HTML"
41668
41827
  });
41669
41828
  const baseArgs = ["run"];
41670
- if (agentCount > 1) {
41671
- baseArgs.push("--agents", String(agentCount));
41672
- }
41673
41829
  const args = executor.buildArgs(baseArgs, { needsApiKey: true });
41674
41830
  let lineBuffer = "";
41675
41831
  const pendingStatusLines = [];
@@ -41677,10 +41833,10 @@ async function runCommand(ctx, executor, config) {
41677
41833
  const sendInterval = setInterval(async () => {
41678
41834
  if (pendingStatusLines.length > 0) {
41679
41835
  const batch = pendingStatusLines.splice(0);
41680
- const text2 = batch.map((l) => escapeHtml(l)).join(`
41836
+ const text = batch.map((l) => escapeHtml(l)).join(`
41681
41837
  `);
41682
41838
  try {
41683
- await ctx.reply(`<pre>${text2}</pre>`, { parse_mode: "HTML" });
41839
+ await ctx.reply(`<pre>${text}</pre>`, { parse_mode: "HTML" });
41684
41840
  } catch {}
41685
41841
  }
41686
41842
  }, SEND_INTERVAL_MS);
@@ -41705,10 +41861,10 @@ async function runCommand(ctx, executor, config) {
41705
41861
  }
41706
41862
  if (pendingStatusLines.length > 0) {
41707
41863
  const batch = pendingStatusLines.splice(0);
41708
- const text2 = batch.map((l) => escapeHtml(l)).join(`
41864
+ const text = batch.map((l) => escapeHtml(l)).join(`
41709
41865
  `);
41710
41866
  try {
41711
- await ctx.reply(`<pre>${text2}</pre>`, { parse_mode: "HTML" });
41867
+ await ctx.reply(`<pre>${text}</pre>`, { parse_mode: "HTML" });
41712
41868
  } catch {}
41713
41869
  }
41714
41870
  if (result.exitCode === 0) {
@@ -43452,6 +43608,9 @@ class TierMergeService {
43452
43608
  }
43453
43609
  }
43454
43610
  findTierTaskBranches(tier) {
43611
+ const tierTaskIds = this.tierTaskIds.get(tier);
43612
+ if (!tierTaskIds || tierTaskIds.length === 0)
43613
+ return [];
43455
43614
  try {
43456
43615
  const output = execSync2('git branch -r --list "origin/agent/*" --format="%(refname:short)"', { cwd: this.projectPath, encoding: "utf-8" }).trim();
43457
43616
  if (!output)
@@ -43459,13 +43618,13 @@ class TierMergeService {
43459
43618
  const remoteBranches = output.split(`
43460
43619
  `).map((b) => b.replace("origin/", ""));
43461
43620
  return remoteBranches.filter((branch) => {
43462
- const match = branch.match(/^agent\/([^-]+)/);
43463
- if (!match)
43621
+ const branchSuffix = branch.replace(/^agent\//, "");
43622
+ if (!branchSuffix)
43464
43623
  return false;
43465
- const taskIdPrefix = match[1];
43466
- return this.tierTaskIds.get(tier)?.some((id) => id.startsWith(taskIdPrefix) || taskIdPrefix.startsWith(id.slice(0, 8))) ?? false;
43624
+ return tierTaskIds.some((id) => branchSuffix.startsWith(`${id}-`) || branchSuffix === id || branchSuffix.startsWith(id));
43467
43625
  });
43468
- } catch {
43626
+ } catch (err) {
43627
+ console.log(c.dim(` Could not list remote branches for tier ${tier}: ${err instanceof Error ? err.message : String(err)}`));
43469
43628
  return [];
43470
43629
  }
43471
43630
  }
@@ -44115,7 +44274,7 @@ function resolveConfig() {
44115
44274
  apiBase: isTestMode ? "http://localhost:8000/api" : apiBase,
44116
44275
  provider: process.env.LOCUS_PROVIDER || settings?.provider || undefined,
44117
44276
  model: process.env.LOCUS_MODEL || settings?.model || undefined,
44118
- agentCount: process.env.LOCUS_AGENT_COUNT ? Number.parseInt(process.env.LOCUS_AGENT_COUNT, 10) : settings?.agentCount,
44277
+ agentCount: settings?.agentCount ?? 1,
44119
44278
  testMode: isTestMode
44120
44279
  };
44121
44280
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/telegram",
3
- "version": "0.10.2",
3
+ "version": "0.10.5",
4
4
  "description": "Telegram bot for Locus - remote control your AI agents from Telegram",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "author": "",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@locusai/sdk": "^0.10.2",
35
+ "@locusai/sdk": "^0.10.5",
36
36
  "dotenv": "^16.4.7",
37
37
  "telegraf": "^4.16.3"
38
38
  },