@locusai/cli 0.10.2 → 0.10.4
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/agent/worker.js +205 -29
- package/bin/locus.js +249 -67
- package/package.json +2 -2
package/bin/agent/worker.js
CHANGED
|
@@ -14963,10 +14963,12 @@ class ClaudeRunner {
|
|
|
14963
14963
|
currentToolName;
|
|
14964
14964
|
activeTools = new Map;
|
|
14965
14965
|
activeProcess = null;
|
|
14966
|
-
|
|
14966
|
+
timeoutMs;
|
|
14967
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log, timeoutMs) {
|
|
14967
14968
|
this.model = model;
|
|
14968
14969
|
this.log = log;
|
|
14969
14970
|
this.projectPath = resolve(projectPath);
|
|
14971
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
14970
14972
|
}
|
|
14971
14973
|
setEventEmitter(emitter) {
|
|
14972
14974
|
this.eventEmitter = emitter;
|
|
@@ -14982,11 +14984,14 @@ class ClaudeRunner {
|
|
|
14982
14984
|
let lastError = null;
|
|
14983
14985
|
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
14984
14986
|
try {
|
|
14985
|
-
return await this.executeRun(prompt);
|
|
14987
|
+
return await this.withTimeout(this.executeRun(prompt));
|
|
14986
14988
|
} catch (error48) {
|
|
14987
14989
|
const err = error48;
|
|
14988
14990
|
lastError = err;
|
|
14989
14991
|
const isLastAttempt = attempt === maxRetries;
|
|
14992
|
+
if (err.message.includes("timed out")) {
|
|
14993
|
+
throw err;
|
|
14994
|
+
}
|
|
14990
14995
|
if (!isLastAttempt) {
|
|
14991
14996
|
const delay = Math.pow(2, attempt) * 1000;
|
|
14992
14997
|
console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
@@ -14996,6 +15001,23 @@ class ClaudeRunner {
|
|
|
14996
15001
|
}
|
|
14997
15002
|
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
14998
15003
|
}
|
|
15004
|
+
withTimeout(promise2) {
|
|
15005
|
+
if (this.timeoutMs <= 0)
|
|
15006
|
+
return promise2;
|
|
15007
|
+
return new Promise((resolve2, reject) => {
|
|
15008
|
+
const timer = setTimeout(() => {
|
|
15009
|
+
this.abort();
|
|
15010
|
+
reject(new Error(`Claude CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
|
|
15011
|
+
}, this.timeoutMs);
|
|
15012
|
+
promise2.then((value) => {
|
|
15013
|
+
clearTimeout(timer);
|
|
15014
|
+
resolve2(value);
|
|
15015
|
+
}, (err) => {
|
|
15016
|
+
clearTimeout(timer);
|
|
15017
|
+
reject(err);
|
|
15018
|
+
});
|
|
15019
|
+
});
|
|
15020
|
+
}
|
|
14999
15021
|
async* runStream(prompt) {
|
|
15000
15022
|
const args = [
|
|
15001
15023
|
"--dangerously-skip-permissions",
|
|
@@ -15360,18 +15382,22 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
|
15360
15382
|
return new Error(message);
|
|
15361
15383
|
}
|
|
15362
15384
|
}
|
|
15363
|
-
var SANDBOX_SETTINGS;
|
|
15385
|
+
var SANDBOX_SETTINGS, DEFAULT_TIMEOUT_MS;
|
|
15364
15386
|
var init_claude_runner = __esm(() => {
|
|
15365
15387
|
init_config();
|
|
15366
15388
|
init_colors();
|
|
15367
15389
|
init_resolve_bin();
|
|
15368
15390
|
SANDBOX_SETTINGS = JSON.stringify({
|
|
15391
|
+
permissions: {
|
|
15392
|
+
deny: ["Read(../**)", "Edit(../**)"]
|
|
15393
|
+
},
|
|
15369
15394
|
sandbox: {
|
|
15370
15395
|
enabled: true,
|
|
15371
15396
|
autoAllow: true,
|
|
15372
15397
|
allowUnsandboxedCommands: false
|
|
15373
15398
|
}
|
|
15374
15399
|
});
|
|
15400
|
+
DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
15375
15401
|
});
|
|
15376
15402
|
|
|
15377
15403
|
// ../sdk/src/ai/codex-runner.ts
|
|
@@ -15386,10 +15412,17 @@ class CodexRunner {
|
|
|
15386
15412
|
model;
|
|
15387
15413
|
log;
|
|
15388
15414
|
activeProcess = null;
|
|
15389
|
-
|
|
15415
|
+
eventEmitter;
|
|
15416
|
+
currentToolName;
|
|
15417
|
+
timeoutMs;
|
|
15418
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, timeoutMs) {
|
|
15390
15419
|
this.projectPath = projectPath;
|
|
15391
15420
|
this.model = model;
|
|
15392
15421
|
this.log = log;
|
|
15422
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
15423
|
+
}
|
|
15424
|
+
setEventEmitter(emitter) {
|
|
15425
|
+
this.eventEmitter = emitter;
|
|
15393
15426
|
}
|
|
15394
15427
|
abort() {
|
|
15395
15428
|
if (this.activeProcess && !this.activeProcess.killed) {
|
|
@@ -15402,9 +15435,12 @@ class CodexRunner {
|
|
|
15402
15435
|
let lastError = null;
|
|
15403
15436
|
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
15404
15437
|
try {
|
|
15405
|
-
return await this.executeRun(prompt);
|
|
15438
|
+
return await this.withTimeout(this.executeRun(prompt));
|
|
15406
15439
|
} catch (error48) {
|
|
15407
15440
|
lastError = error48;
|
|
15441
|
+
if (lastError.message.includes("timed out")) {
|
|
15442
|
+
throw lastError;
|
|
15443
|
+
}
|
|
15408
15444
|
if (attempt < maxRetries) {
|
|
15409
15445
|
const delay = Math.pow(2, attempt) * 1000;
|
|
15410
15446
|
console.warn(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`);
|
|
@@ -15414,9 +15450,31 @@ class CodexRunner {
|
|
|
15414
15450
|
}
|
|
15415
15451
|
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
15416
15452
|
}
|
|
15453
|
+
withTimeout(promise2) {
|
|
15454
|
+
if (this.timeoutMs <= 0)
|
|
15455
|
+
return promise2;
|
|
15456
|
+
return new Promise((resolve2, reject) => {
|
|
15457
|
+
const timer = setTimeout(() => {
|
|
15458
|
+
this.abort();
|
|
15459
|
+
reject(new Error(`Codex CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
|
|
15460
|
+
}, this.timeoutMs);
|
|
15461
|
+
promise2.then((value) => {
|
|
15462
|
+
clearTimeout(timer);
|
|
15463
|
+
resolve2(value);
|
|
15464
|
+
}, (err) => {
|
|
15465
|
+
clearTimeout(timer);
|
|
15466
|
+
reject(err);
|
|
15467
|
+
});
|
|
15468
|
+
});
|
|
15469
|
+
}
|
|
15417
15470
|
async* runStream(prompt) {
|
|
15418
15471
|
const outputPath = join3(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
15419
15472
|
const args = this.buildArgs(outputPath);
|
|
15473
|
+
this.eventEmitter?.emitSessionStarted({
|
|
15474
|
+
model: this.model,
|
|
15475
|
+
provider: "codex"
|
|
15476
|
+
});
|
|
15477
|
+
this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
|
|
15420
15478
|
const codex = spawn2("codex", args, {
|
|
15421
15479
|
cwd: this.projectPath,
|
|
15422
15480
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -15429,7 +15487,21 @@ class CodexRunner {
|
|
|
15429
15487
|
let processEnded = false;
|
|
15430
15488
|
let errorMessage = "";
|
|
15431
15489
|
let finalOutput = "";
|
|
15490
|
+
let finalContent = "";
|
|
15491
|
+
let isThinking = false;
|
|
15432
15492
|
const enqueueChunk = (chunk) => {
|
|
15493
|
+
this.emitEventForChunk(chunk, isThinking);
|
|
15494
|
+
if (chunk.type === "thinking") {
|
|
15495
|
+
isThinking = true;
|
|
15496
|
+
} else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
|
|
15497
|
+
if (isThinking) {
|
|
15498
|
+
this.eventEmitter?.emitThinkingStoped();
|
|
15499
|
+
isThinking = false;
|
|
15500
|
+
}
|
|
15501
|
+
}
|
|
15502
|
+
if (chunk.type === "text_delta") {
|
|
15503
|
+
finalContent += chunk.content;
|
|
15504
|
+
}
|
|
15433
15505
|
if (resolveChunk) {
|
|
15434
15506
|
const resolve2 = resolveChunk;
|
|
15435
15507
|
resolveChunk = null;
|
|
@@ -15470,16 +15542,21 @@ class CodexRunner {
|
|
|
15470
15542
|
codex.stderr.on("data", processOutput);
|
|
15471
15543
|
codex.on("error", (err) => {
|
|
15472
15544
|
errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
|
|
15545
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
|
|
15473
15546
|
signalEnd();
|
|
15474
15547
|
});
|
|
15475
15548
|
codex.on("close", (code) => {
|
|
15476
15549
|
this.activeProcess = null;
|
|
15477
|
-
this.cleanupTempFile(outputPath);
|
|
15478
15550
|
if (code === 0) {
|
|
15479
15551
|
const result = this.readOutput(outputPath, finalOutput);
|
|
15552
|
+
this.cleanupTempFile(outputPath);
|
|
15480
15553
|
enqueueChunk({ type: "result", content: result });
|
|
15481
|
-
} else
|
|
15482
|
-
|
|
15554
|
+
} else {
|
|
15555
|
+
this.cleanupTempFile(outputPath);
|
|
15556
|
+
if (!errorMessage) {
|
|
15557
|
+
errorMessage = this.createErrorFromOutput(code, finalOutput).message;
|
|
15558
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
|
|
15559
|
+
}
|
|
15483
15560
|
}
|
|
15484
15561
|
signalEnd();
|
|
15485
15562
|
});
|
|
@@ -15493,6 +15570,12 @@ class CodexRunner {
|
|
|
15493
15570
|
} else if (processEnded) {
|
|
15494
15571
|
if (errorMessage) {
|
|
15495
15572
|
yield { type: "error", error: errorMessage };
|
|
15573
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
15574
|
+
} else {
|
|
15575
|
+
if (finalContent) {
|
|
15576
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
15577
|
+
}
|
|
15578
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
15496
15579
|
}
|
|
15497
15580
|
break;
|
|
15498
15581
|
} else {
|
|
@@ -15502,6 +15585,12 @@ class CodexRunner {
|
|
|
15502
15585
|
if (chunk === null) {
|
|
15503
15586
|
if (errorMessage) {
|
|
15504
15587
|
yield { type: "error", error: errorMessage };
|
|
15588
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
15589
|
+
} else {
|
|
15590
|
+
if (finalContent) {
|
|
15591
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
15592
|
+
}
|
|
15593
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
15505
15594
|
}
|
|
15506
15595
|
break;
|
|
15507
15596
|
}
|
|
@@ -15509,6 +15598,36 @@ class CodexRunner {
|
|
|
15509
15598
|
}
|
|
15510
15599
|
}
|
|
15511
15600
|
}
|
|
15601
|
+
emitEventForChunk(chunk, isThinking) {
|
|
15602
|
+
if (!this.eventEmitter)
|
|
15603
|
+
return;
|
|
15604
|
+
switch (chunk.type) {
|
|
15605
|
+
case "text_delta":
|
|
15606
|
+
this.eventEmitter.emitTextDelta(chunk.content);
|
|
15607
|
+
break;
|
|
15608
|
+
case "tool_use":
|
|
15609
|
+
if (this.currentToolName) {
|
|
15610
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
15611
|
+
}
|
|
15612
|
+
this.currentToolName = chunk.tool;
|
|
15613
|
+
this.eventEmitter.emitToolStarted(chunk.tool);
|
|
15614
|
+
break;
|
|
15615
|
+
case "thinking":
|
|
15616
|
+
if (!isThinking) {
|
|
15617
|
+
this.eventEmitter.emitThinkingStarted(chunk.content);
|
|
15618
|
+
}
|
|
15619
|
+
break;
|
|
15620
|
+
case "result":
|
|
15621
|
+
if (this.currentToolName) {
|
|
15622
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
15623
|
+
this.currentToolName = undefined;
|
|
15624
|
+
}
|
|
15625
|
+
break;
|
|
15626
|
+
case "error":
|
|
15627
|
+
this.eventEmitter.emitErrorOccurred(chunk.error);
|
|
15628
|
+
break;
|
|
15629
|
+
}
|
|
15630
|
+
}
|
|
15512
15631
|
executeRun(prompt) {
|
|
15513
15632
|
return new Promise((resolve2, reject) => {
|
|
15514
15633
|
const outputPath = join3(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
@@ -15538,10 +15657,12 @@ class CodexRunner {
|
|
|
15538
15657
|
});
|
|
15539
15658
|
codex.on("close", (code) => {
|
|
15540
15659
|
this.activeProcess = null;
|
|
15541
|
-
this.cleanupTempFile(outputPath);
|
|
15542
15660
|
if (code === 0) {
|
|
15543
|
-
|
|
15661
|
+
const result = this.readOutput(outputPath, output);
|
|
15662
|
+
this.cleanupTempFile(outputPath);
|
|
15663
|
+
resolve2(result);
|
|
15544
15664
|
} else {
|
|
15665
|
+
this.cleanupTempFile(outputPath);
|
|
15545
15666
|
reject(this.createErrorFromOutput(code, errorOutput));
|
|
15546
15667
|
}
|
|
15547
15668
|
});
|
|
@@ -15551,12 +15672,18 @@ class CodexRunner {
|
|
|
15551
15672
|
}
|
|
15552
15673
|
buildArgs(outputPath) {
|
|
15553
15674
|
const args = [
|
|
15675
|
+
"--ask-for-approval",
|
|
15676
|
+
"never",
|
|
15554
15677
|
"exec",
|
|
15555
15678
|
"--sandbox",
|
|
15556
15679
|
"workspace-write",
|
|
15557
15680
|
"--skip-git-repo-check",
|
|
15558
15681
|
"--output-last-message",
|
|
15559
|
-
outputPath
|
|
15682
|
+
outputPath,
|
|
15683
|
+
"-c",
|
|
15684
|
+
"sandbox_workspace_write.network_access=true",
|
|
15685
|
+
"-c",
|
|
15686
|
+
'sandbox.excludedCommands=["git", "gh"]'
|
|
15560
15687
|
];
|
|
15561
15688
|
if (this.model) {
|
|
15562
15689
|
args.push("--model", this.model);
|
|
@@ -15607,9 +15734,11 @@ class CodexRunner {
|
|
|
15607
15734
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
15608
15735
|
}
|
|
15609
15736
|
}
|
|
15737
|
+
var DEFAULT_TIMEOUT_MS2;
|
|
15610
15738
|
var init_codex_runner = __esm(() => {
|
|
15611
15739
|
init_config();
|
|
15612
15740
|
init_resolve_bin();
|
|
15741
|
+
DEFAULT_TIMEOUT_MS2 = 60 * 60 * 1000;
|
|
15613
15742
|
});
|
|
15614
15743
|
|
|
15615
15744
|
// ../sdk/src/ai/factory.ts
|
|
@@ -15618,9 +15747,9 @@ function createAiRunner(provider, config2) {
|
|
|
15618
15747
|
const model = config2.model ?? DEFAULT_MODEL[resolvedProvider];
|
|
15619
15748
|
switch (resolvedProvider) {
|
|
15620
15749
|
case PROVIDER.CODEX:
|
|
15621
|
-
return new CodexRunner(config2.projectPath, model, config2.log);
|
|
15750
|
+
return new CodexRunner(config2.projectPath, model, config2.log, config2.timeoutMs);
|
|
15622
15751
|
default:
|
|
15623
|
-
return new ClaudeRunner(config2.projectPath, model, config2.log);
|
|
15752
|
+
return new ClaudeRunner(config2.projectPath, model, config2.log, config2.timeoutMs);
|
|
15624
15753
|
}
|
|
15625
15754
|
}
|
|
15626
15755
|
var init_factory = __esm(() => {
|
|
@@ -31580,8 +31709,9 @@ class WorktreeManager {
|
|
|
31580
31709
|
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
31581
31710
|
addWorktree();
|
|
31582
31711
|
}
|
|
31583
|
-
this.
|
|
31584
|
-
|
|
31712
|
+
const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31713
|
+
this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
|
|
31714
|
+
return { worktreePath, branch, baseBranch, baseCommitHash };
|
|
31585
31715
|
}
|
|
31586
31716
|
list() {
|
|
31587
31717
|
const output = this.git("worktree list --porcelain", this.projectPath);
|
|
@@ -31686,27 +31816,54 @@ class WorktreeManager {
|
|
|
31686
31816
|
try {
|
|
31687
31817
|
const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
|
|
31688
31818
|
return Number.parseInt(count, 10) > 0;
|
|
31819
|
+
} catch (err) {
|
|
31820
|
+
this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
31821
|
+
return false;
|
|
31822
|
+
}
|
|
31823
|
+
}
|
|
31824
|
+
hasCommitsAheadOfHash(worktreePath, baseHash) {
|
|
31825
|
+
try {
|
|
31826
|
+
const headHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31827
|
+
return headHash !== baseHash;
|
|
31689
31828
|
} catch {
|
|
31690
31829
|
return false;
|
|
31691
31830
|
}
|
|
31692
31831
|
}
|
|
31693
|
-
commitChanges(worktreePath, message, baseBranch) {
|
|
31832
|
+
commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
|
|
31694
31833
|
const hasUncommittedChanges = this.hasChanges(worktreePath);
|
|
31834
|
+
if (hasUncommittedChanges) {
|
|
31835
|
+
const statusOutput = this.git("status --porcelain", worktreePath).trim();
|
|
31836
|
+
this.log(`Detected uncommitted changes:
|
|
31837
|
+
${statusOutput.split(`
|
|
31838
|
+
`).slice(0, 10).join(`
|
|
31839
|
+
`)}${statusOutput.split(`
|
|
31840
|
+
`).length > 10 ? `
|
|
31841
|
+
... and ${statusOutput.split(`
|
|
31842
|
+
`).length - 10} more` : ""}`, "info");
|
|
31843
|
+
}
|
|
31695
31844
|
if (!hasUncommittedChanges) {
|
|
31696
31845
|
if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
|
|
31697
31846
|
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31698
31847
|
this.log(`Agent already committed changes (${hash3.slice(0, 8)}); skipping additional commit`, "info");
|
|
31699
31848
|
return hash3;
|
|
31700
31849
|
}
|
|
31701
|
-
this.
|
|
31850
|
+
if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
|
|
31851
|
+
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31852
|
+
this.log(`Agent already committed changes (${hash3.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
|
|
31853
|
+
return hash3;
|
|
31854
|
+
}
|
|
31855
|
+
const branch = this.getBranch(worktreePath);
|
|
31856
|
+
this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
|
|
31702
31857
|
return null;
|
|
31703
31858
|
}
|
|
31704
31859
|
this.git("add -A", worktreePath);
|
|
31705
31860
|
const staged = this.git("diff --cached --name-only", worktreePath).trim();
|
|
31706
31861
|
if (!staged) {
|
|
31707
|
-
this.log("
|
|
31862
|
+
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
31708
31863
|
return null;
|
|
31709
31864
|
}
|
|
31865
|
+
this.log(`Staging ${staged.split(`
|
|
31866
|
+
`).length} file(s) for commit`, "info");
|
|
31710
31867
|
this.gitExec(["commit", "-m", message], worktreePath);
|
|
31711
31868
|
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31712
31869
|
this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
|
|
@@ -32124,15 +32281,27 @@ class TaskExecutor {
|
|
|
32124
32281
|
const basePrompt = await this.promptBuilder.build(task2);
|
|
32125
32282
|
try {
|
|
32126
32283
|
this.deps.log("Starting Execution...", "info");
|
|
32127
|
-
await this.deps.aiRunner.run(basePrompt);
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
summary: "Task completed by the agent"
|
|
32131
|
-
};
|
|
32284
|
+
const output = await this.deps.aiRunner.run(basePrompt);
|
|
32285
|
+
const summary = this.extractSummary(output);
|
|
32286
|
+
return { success: true, summary };
|
|
32132
32287
|
} catch (error48) {
|
|
32133
32288
|
return { success: false, summary: `Error: ${error48}` };
|
|
32134
32289
|
}
|
|
32135
32290
|
}
|
|
32291
|
+
extractSummary(output) {
|
|
32292
|
+
if (!output || !output.trim()) {
|
|
32293
|
+
return "Task completed by the agent";
|
|
32294
|
+
}
|
|
32295
|
+
const paragraphs = output.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
32296
|
+
if (paragraphs.length === 0) {
|
|
32297
|
+
return "Task completed by the agent";
|
|
32298
|
+
}
|
|
32299
|
+
const last = paragraphs[paragraphs.length - 1];
|
|
32300
|
+
if (last.length > 500) {
|
|
32301
|
+
return `${last.slice(0, 497)}...`;
|
|
32302
|
+
}
|
|
32303
|
+
return last;
|
|
32304
|
+
}
|
|
32136
32305
|
}
|
|
32137
32306
|
var init_task_executor = __esm(() => {
|
|
32138
32307
|
init_prompt_builder();
|
|
@@ -32150,7 +32319,7 @@ class GitWorkflow {
|
|
|
32150
32319
|
this.log = log;
|
|
32151
32320
|
this.ghUsername = ghUsername;
|
|
32152
32321
|
const projectPath = config2.projectPath || process.cwd();
|
|
32153
|
-
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }) : null;
|
|
32322
|
+
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log) : null;
|
|
32154
32323
|
this.prService = config2.autoPush ? new PrService(projectPath, log) : null;
|
|
32155
32324
|
}
|
|
32156
32325
|
createTaskWorktree(task2, defaultExecutor) {
|
|
@@ -32158,6 +32327,7 @@ class GitWorkflow {
|
|
|
32158
32327
|
return {
|
|
32159
32328
|
worktreePath: null,
|
|
32160
32329
|
baseBranch: null,
|
|
32330
|
+
baseCommitHash: null,
|
|
32161
32331
|
executor: defaultExecutor
|
|
32162
32332
|
};
|
|
32163
32333
|
}
|
|
@@ -32183,10 +32353,11 @@ class GitWorkflow {
|
|
|
32183
32353
|
return {
|
|
32184
32354
|
worktreePath: result.worktreePath,
|
|
32185
32355
|
baseBranch: result.baseBranch,
|
|
32356
|
+
baseCommitHash: result.baseCommitHash,
|
|
32186
32357
|
executor: taskExecutor
|
|
32187
32358
|
};
|
|
32188
32359
|
}
|
|
32189
|
-
commitAndPush(worktreePath, task2, baseBranch) {
|
|
32360
|
+
commitAndPush(worktreePath, task2, baseBranch, baseCommitHash) {
|
|
32190
32361
|
if (!this.worktreeManager) {
|
|
32191
32362
|
return { branch: null, pushed: false, pushFailed: false };
|
|
32192
32363
|
}
|
|
@@ -32203,7 +32374,7 @@ class GitWorkflow {
|
|
|
32203
32374
|
|
|
32204
32375
|
${trailers.join(`
|
|
32205
32376
|
`)}`;
|
|
32206
|
-
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch);
|
|
32377
|
+
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
|
|
32207
32378
|
if (!hash2) {
|
|
32208
32379
|
this.log("No changes to commit for this task", "info");
|
|
32209
32380
|
return {
|
|
@@ -32243,7 +32414,12 @@ ${trailers.join(`
|
|
|
32243
32414
|
} catch (err) {
|
|
32244
32415
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32245
32416
|
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
32246
|
-
return {
|
|
32417
|
+
return {
|
|
32418
|
+
branch: null,
|
|
32419
|
+
pushed: false,
|
|
32420
|
+
pushFailed: true,
|
|
32421
|
+
pushError: `Git commit/push failed: ${errorMessage}`
|
|
32422
|
+
};
|
|
32247
32423
|
}
|
|
32248
32424
|
}
|
|
32249
32425
|
createPullRequest(task2, branch, summary, baseBranch) {
|
|
@@ -32469,7 +32645,7 @@ class AgentWorker {
|
|
|
32469
32645
|
}
|
|
32470
32646
|
async executeTask(task2) {
|
|
32471
32647
|
const fullTask = await this.client.tasks.getById(task2.id, this.config.workspaceId);
|
|
32472
|
-
const { worktreePath, baseBranch, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
32648
|
+
const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
32473
32649
|
this.currentWorktreePath = worktreePath;
|
|
32474
32650
|
let branchPushed = false;
|
|
32475
32651
|
let keepBranch = false;
|
|
@@ -32481,7 +32657,7 @@ class AgentWorker {
|
|
|
32481
32657
|
let prError = null;
|
|
32482
32658
|
let noChanges = false;
|
|
32483
32659
|
if (result.success && worktreePath) {
|
|
32484
|
-
const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined);
|
|
32660
|
+
const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined, baseCommitHash ?? undefined);
|
|
32485
32661
|
taskBranch = commitResult.branch;
|
|
32486
32662
|
branchPushed = commitResult.pushed;
|
|
32487
32663
|
keepBranch = taskBranch !== null;
|
package/bin/locus.js
CHANGED
|
@@ -6663,10 +6663,12 @@ class ClaudeRunner {
|
|
|
6663
6663
|
currentToolName;
|
|
6664
6664
|
activeTools = new Map;
|
|
6665
6665
|
activeProcess = null;
|
|
6666
|
-
|
|
6666
|
+
timeoutMs;
|
|
6667
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log, timeoutMs) {
|
|
6667
6668
|
this.model = model;
|
|
6668
6669
|
this.log = log;
|
|
6669
6670
|
this.projectPath = resolve(projectPath);
|
|
6671
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6670
6672
|
}
|
|
6671
6673
|
setEventEmitter(emitter) {
|
|
6672
6674
|
this.eventEmitter = emitter;
|
|
@@ -6682,11 +6684,14 @@ class ClaudeRunner {
|
|
|
6682
6684
|
let lastError = null;
|
|
6683
6685
|
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
6684
6686
|
try {
|
|
6685
|
-
return await this.executeRun(prompt);
|
|
6687
|
+
return await this.withTimeout(this.executeRun(prompt));
|
|
6686
6688
|
} catch (error) {
|
|
6687
6689
|
const err = error;
|
|
6688
6690
|
lastError = err;
|
|
6689
6691
|
const isLastAttempt = attempt === maxRetries;
|
|
6692
|
+
if (err.message.includes("timed out")) {
|
|
6693
|
+
throw err;
|
|
6694
|
+
}
|
|
6690
6695
|
if (!isLastAttempt) {
|
|
6691
6696
|
const delay = Math.pow(2, attempt) * 1000;
|
|
6692
6697
|
console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
@@ -6696,6 +6701,23 @@ class ClaudeRunner {
|
|
|
6696
6701
|
}
|
|
6697
6702
|
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
6698
6703
|
}
|
|
6704
|
+
withTimeout(promise) {
|
|
6705
|
+
if (this.timeoutMs <= 0)
|
|
6706
|
+
return promise;
|
|
6707
|
+
return new Promise((resolve2, reject) => {
|
|
6708
|
+
const timer = setTimeout(() => {
|
|
6709
|
+
this.abort();
|
|
6710
|
+
reject(new Error(`Claude CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
|
|
6711
|
+
}, this.timeoutMs);
|
|
6712
|
+
promise.then((value) => {
|
|
6713
|
+
clearTimeout(timer);
|
|
6714
|
+
resolve2(value);
|
|
6715
|
+
}, (err) => {
|
|
6716
|
+
clearTimeout(timer);
|
|
6717
|
+
reject(err);
|
|
6718
|
+
});
|
|
6719
|
+
});
|
|
6720
|
+
}
|
|
6699
6721
|
async* runStream(prompt) {
|
|
6700
6722
|
const args = [
|
|
6701
6723
|
"--dangerously-skip-permissions",
|
|
@@ -7060,18 +7082,22 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
|
7060
7082
|
return new Error(message);
|
|
7061
7083
|
}
|
|
7062
7084
|
}
|
|
7063
|
-
var SANDBOX_SETTINGS;
|
|
7085
|
+
var SANDBOX_SETTINGS, DEFAULT_TIMEOUT_MS;
|
|
7064
7086
|
var init_claude_runner = __esm(() => {
|
|
7065
7087
|
init_config();
|
|
7066
7088
|
init_colors();
|
|
7067
7089
|
init_resolve_bin();
|
|
7068
7090
|
SANDBOX_SETTINGS = JSON.stringify({
|
|
7091
|
+
permissions: {
|
|
7092
|
+
deny: ["Read(../**)", "Edit(../**)"]
|
|
7093
|
+
},
|
|
7069
7094
|
sandbox: {
|
|
7070
7095
|
enabled: true,
|
|
7071
7096
|
autoAllow: true,
|
|
7072
7097
|
allowUnsandboxedCommands: false
|
|
7073
7098
|
}
|
|
7074
7099
|
});
|
|
7100
|
+
DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
7075
7101
|
});
|
|
7076
7102
|
|
|
7077
7103
|
// ../sdk/src/ai/codex-runner.ts
|
|
@@ -7086,10 +7112,17 @@ class CodexRunner {
|
|
|
7086
7112
|
model;
|
|
7087
7113
|
log;
|
|
7088
7114
|
activeProcess = null;
|
|
7089
|
-
|
|
7115
|
+
eventEmitter;
|
|
7116
|
+
currentToolName;
|
|
7117
|
+
timeoutMs;
|
|
7118
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, timeoutMs) {
|
|
7090
7119
|
this.projectPath = projectPath;
|
|
7091
7120
|
this.model = model;
|
|
7092
7121
|
this.log = log;
|
|
7122
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
7123
|
+
}
|
|
7124
|
+
setEventEmitter(emitter) {
|
|
7125
|
+
this.eventEmitter = emitter;
|
|
7093
7126
|
}
|
|
7094
7127
|
abort() {
|
|
7095
7128
|
if (this.activeProcess && !this.activeProcess.killed) {
|
|
@@ -7102,9 +7135,12 @@ class CodexRunner {
|
|
|
7102
7135
|
let lastError = null;
|
|
7103
7136
|
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
7104
7137
|
try {
|
|
7105
|
-
return await this.executeRun(prompt);
|
|
7138
|
+
return await this.withTimeout(this.executeRun(prompt));
|
|
7106
7139
|
} catch (error) {
|
|
7107
7140
|
lastError = error;
|
|
7141
|
+
if (lastError.message.includes("timed out")) {
|
|
7142
|
+
throw lastError;
|
|
7143
|
+
}
|
|
7108
7144
|
if (attempt < maxRetries) {
|
|
7109
7145
|
const delay = Math.pow(2, attempt) * 1000;
|
|
7110
7146
|
console.warn(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`);
|
|
@@ -7114,9 +7150,31 @@ class CodexRunner {
|
|
|
7114
7150
|
}
|
|
7115
7151
|
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
7116
7152
|
}
|
|
7153
|
+
withTimeout(promise) {
|
|
7154
|
+
if (this.timeoutMs <= 0)
|
|
7155
|
+
return promise;
|
|
7156
|
+
return new Promise((resolve2, reject) => {
|
|
7157
|
+
const timer = setTimeout(() => {
|
|
7158
|
+
this.abort();
|
|
7159
|
+
reject(new Error(`Codex CLI execution timed out after ${Math.round(this.timeoutMs / 60000)} minutes`));
|
|
7160
|
+
}, this.timeoutMs);
|
|
7161
|
+
promise.then((value) => {
|
|
7162
|
+
clearTimeout(timer);
|
|
7163
|
+
resolve2(value);
|
|
7164
|
+
}, (err) => {
|
|
7165
|
+
clearTimeout(timer);
|
|
7166
|
+
reject(err);
|
|
7167
|
+
});
|
|
7168
|
+
});
|
|
7169
|
+
}
|
|
7117
7170
|
async* runStream(prompt) {
|
|
7118
7171
|
const outputPath = join5(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
7119
7172
|
const args = this.buildArgs(outputPath);
|
|
7173
|
+
this.eventEmitter?.emitSessionStarted({
|
|
7174
|
+
model: this.model,
|
|
7175
|
+
provider: "codex"
|
|
7176
|
+
});
|
|
7177
|
+
this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
|
|
7120
7178
|
const codex = spawn2("codex", args, {
|
|
7121
7179
|
cwd: this.projectPath,
|
|
7122
7180
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -7129,7 +7187,21 @@ class CodexRunner {
|
|
|
7129
7187
|
let processEnded = false;
|
|
7130
7188
|
let errorMessage = "";
|
|
7131
7189
|
let finalOutput = "";
|
|
7190
|
+
let finalContent = "";
|
|
7191
|
+
let isThinking = false;
|
|
7132
7192
|
const enqueueChunk = (chunk) => {
|
|
7193
|
+
this.emitEventForChunk(chunk, isThinking);
|
|
7194
|
+
if (chunk.type === "thinking") {
|
|
7195
|
+
isThinking = true;
|
|
7196
|
+
} else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
|
|
7197
|
+
if (isThinking) {
|
|
7198
|
+
this.eventEmitter?.emitThinkingStoped();
|
|
7199
|
+
isThinking = false;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
if (chunk.type === "text_delta") {
|
|
7203
|
+
finalContent += chunk.content;
|
|
7204
|
+
}
|
|
7133
7205
|
if (resolveChunk) {
|
|
7134
7206
|
const resolve2 = resolveChunk;
|
|
7135
7207
|
resolveChunk = null;
|
|
@@ -7170,16 +7242,21 @@ class CodexRunner {
|
|
|
7170
7242
|
codex.stderr.on("data", processOutput);
|
|
7171
7243
|
codex.on("error", (err) => {
|
|
7172
7244
|
errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
|
|
7245
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
|
|
7173
7246
|
signalEnd();
|
|
7174
7247
|
});
|
|
7175
7248
|
codex.on("close", (code) => {
|
|
7176
7249
|
this.activeProcess = null;
|
|
7177
|
-
this.cleanupTempFile(outputPath);
|
|
7178
7250
|
if (code === 0) {
|
|
7179
7251
|
const result = this.readOutput(outputPath, finalOutput);
|
|
7252
|
+
this.cleanupTempFile(outputPath);
|
|
7180
7253
|
enqueueChunk({ type: "result", content: result });
|
|
7181
|
-
} else
|
|
7182
|
-
|
|
7254
|
+
} else {
|
|
7255
|
+
this.cleanupTempFile(outputPath);
|
|
7256
|
+
if (!errorMessage) {
|
|
7257
|
+
errorMessage = this.createErrorFromOutput(code, finalOutput).message;
|
|
7258
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
|
|
7259
|
+
}
|
|
7183
7260
|
}
|
|
7184
7261
|
signalEnd();
|
|
7185
7262
|
});
|
|
@@ -7193,6 +7270,12 @@ class CodexRunner {
|
|
|
7193
7270
|
} else if (processEnded) {
|
|
7194
7271
|
if (errorMessage) {
|
|
7195
7272
|
yield { type: "error", error: errorMessage };
|
|
7273
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
7274
|
+
} else {
|
|
7275
|
+
if (finalContent) {
|
|
7276
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
7277
|
+
}
|
|
7278
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
7196
7279
|
}
|
|
7197
7280
|
break;
|
|
7198
7281
|
} else {
|
|
@@ -7202,6 +7285,12 @@ class CodexRunner {
|
|
|
7202
7285
|
if (chunk === null) {
|
|
7203
7286
|
if (errorMessage) {
|
|
7204
7287
|
yield { type: "error", error: errorMessage };
|
|
7288
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
7289
|
+
} else {
|
|
7290
|
+
if (finalContent) {
|
|
7291
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
7292
|
+
}
|
|
7293
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
7205
7294
|
}
|
|
7206
7295
|
break;
|
|
7207
7296
|
}
|
|
@@ -7209,6 +7298,36 @@ class CodexRunner {
|
|
|
7209
7298
|
}
|
|
7210
7299
|
}
|
|
7211
7300
|
}
|
|
7301
|
+
emitEventForChunk(chunk, isThinking) {
|
|
7302
|
+
if (!this.eventEmitter)
|
|
7303
|
+
return;
|
|
7304
|
+
switch (chunk.type) {
|
|
7305
|
+
case "text_delta":
|
|
7306
|
+
this.eventEmitter.emitTextDelta(chunk.content);
|
|
7307
|
+
break;
|
|
7308
|
+
case "tool_use":
|
|
7309
|
+
if (this.currentToolName) {
|
|
7310
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
7311
|
+
}
|
|
7312
|
+
this.currentToolName = chunk.tool;
|
|
7313
|
+
this.eventEmitter.emitToolStarted(chunk.tool);
|
|
7314
|
+
break;
|
|
7315
|
+
case "thinking":
|
|
7316
|
+
if (!isThinking) {
|
|
7317
|
+
this.eventEmitter.emitThinkingStarted(chunk.content);
|
|
7318
|
+
}
|
|
7319
|
+
break;
|
|
7320
|
+
case "result":
|
|
7321
|
+
if (this.currentToolName) {
|
|
7322
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
7323
|
+
this.currentToolName = undefined;
|
|
7324
|
+
}
|
|
7325
|
+
break;
|
|
7326
|
+
case "error":
|
|
7327
|
+
this.eventEmitter.emitErrorOccurred(chunk.error);
|
|
7328
|
+
break;
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7212
7331
|
executeRun(prompt) {
|
|
7213
7332
|
return new Promise((resolve2, reject) => {
|
|
7214
7333
|
const outputPath = join5(tmpdir(), `locus-codex-${randomUUID()}.txt`);
|
|
@@ -7238,10 +7357,12 @@ class CodexRunner {
|
|
|
7238
7357
|
});
|
|
7239
7358
|
codex.on("close", (code) => {
|
|
7240
7359
|
this.activeProcess = null;
|
|
7241
|
-
this.cleanupTempFile(outputPath);
|
|
7242
7360
|
if (code === 0) {
|
|
7243
|
-
|
|
7361
|
+
const result = this.readOutput(outputPath, output);
|
|
7362
|
+
this.cleanupTempFile(outputPath);
|
|
7363
|
+
resolve2(result);
|
|
7244
7364
|
} else {
|
|
7365
|
+
this.cleanupTempFile(outputPath);
|
|
7245
7366
|
reject(this.createErrorFromOutput(code, errorOutput));
|
|
7246
7367
|
}
|
|
7247
7368
|
});
|
|
@@ -7251,12 +7372,18 @@ class CodexRunner {
|
|
|
7251
7372
|
}
|
|
7252
7373
|
buildArgs(outputPath) {
|
|
7253
7374
|
const args = [
|
|
7375
|
+
"--ask-for-approval",
|
|
7376
|
+
"never",
|
|
7254
7377
|
"exec",
|
|
7255
7378
|
"--sandbox",
|
|
7256
7379
|
"workspace-write",
|
|
7257
7380
|
"--skip-git-repo-check",
|
|
7258
7381
|
"--output-last-message",
|
|
7259
|
-
outputPath
|
|
7382
|
+
outputPath,
|
|
7383
|
+
"-c",
|
|
7384
|
+
"sandbox_workspace_write.network_access=true",
|
|
7385
|
+
"-c",
|
|
7386
|
+
'sandbox.excludedCommands=["git", "gh"]'
|
|
7260
7387
|
];
|
|
7261
7388
|
if (this.model) {
|
|
7262
7389
|
args.push("--model", this.model);
|
|
@@ -7307,9 +7434,11 @@ class CodexRunner {
|
|
|
7307
7434
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
7308
7435
|
}
|
|
7309
7436
|
}
|
|
7437
|
+
var DEFAULT_TIMEOUT_MS2;
|
|
7310
7438
|
var init_codex_runner = __esm(() => {
|
|
7311
7439
|
init_config();
|
|
7312
7440
|
init_resolve_bin();
|
|
7441
|
+
DEFAULT_TIMEOUT_MS2 = 60 * 60 * 1000;
|
|
7313
7442
|
});
|
|
7314
7443
|
|
|
7315
7444
|
// ../sdk/src/ai/factory.ts
|
|
@@ -7318,9 +7447,9 @@ function createAiRunner(provider, config) {
|
|
|
7318
7447
|
const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
|
|
7319
7448
|
switch (resolvedProvider) {
|
|
7320
7449
|
case PROVIDER.CODEX:
|
|
7321
|
-
return new CodexRunner(config.projectPath, model, config.log);
|
|
7450
|
+
return new CodexRunner(config.projectPath, model, config.log, config.timeoutMs);
|
|
7322
7451
|
default:
|
|
7323
|
-
return new ClaudeRunner(config.projectPath, model, config.log);
|
|
7452
|
+
return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
|
|
7324
7453
|
}
|
|
7325
7454
|
}
|
|
7326
7455
|
var init_factory = __esm(() => {
|
|
@@ -7747,8 +7876,9 @@ class WorktreeManager {
|
|
|
7747
7876
|
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
7748
7877
|
addWorktree();
|
|
7749
7878
|
}
|
|
7750
|
-
this.
|
|
7751
|
-
|
|
7879
|
+
const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
7880
|
+
this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
|
|
7881
|
+
return { worktreePath, branch, baseBranch, baseCommitHash };
|
|
7752
7882
|
}
|
|
7753
7883
|
list() {
|
|
7754
7884
|
const output = this.git("worktree list --porcelain", this.projectPath);
|
|
@@ -7853,27 +7983,54 @@ class WorktreeManager {
|
|
|
7853
7983
|
try {
|
|
7854
7984
|
const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
|
|
7855
7985
|
return Number.parseInt(count, 10) > 0;
|
|
7986
|
+
} catch (err) {
|
|
7987
|
+
this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
7988
|
+
return false;
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
hasCommitsAheadOfHash(worktreePath, baseHash) {
|
|
7992
|
+
try {
|
|
7993
|
+
const headHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
7994
|
+
return headHash !== baseHash;
|
|
7856
7995
|
} catch {
|
|
7857
7996
|
return false;
|
|
7858
7997
|
}
|
|
7859
7998
|
}
|
|
7860
|
-
commitChanges(worktreePath, message, baseBranch) {
|
|
7999
|
+
commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
|
|
7861
8000
|
const hasUncommittedChanges = this.hasChanges(worktreePath);
|
|
8001
|
+
if (hasUncommittedChanges) {
|
|
8002
|
+
const statusOutput = this.git("status --porcelain", worktreePath).trim();
|
|
8003
|
+
this.log(`Detected uncommitted changes:
|
|
8004
|
+
${statusOutput.split(`
|
|
8005
|
+
`).slice(0, 10).join(`
|
|
8006
|
+
`)}${statusOutput.split(`
|
|
8007
|
+
`).length > 10 ? `
|
|
8008
|
+
... and ${statusOutput.split(`
|
|
8009
|
+
`).length - 10} more` : ""}`, "info");
|
|
8010
|
+
}
|
|
7862
8011
|
if (!hasUncommittedChanges) {
|
|
7863
8012
|
if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
|
|
7864
8013
|
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
7865
8014
|
this.log(`Agent already committed changes (${hash2.slice(0, 8)}); skipping additional commit`, "info");
|
|
7866
8015
|
return hash2;
|
|
7867
8016
|
}
|
|
7868
|
-
this.
|
|
8017
|
+
if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
|
|
8018
|
+
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
8019
|
+
this.log(`Agent already committed changes (${hash2.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
|
|
8020
|
+
return hash2;
|
|
8021
|
+
}
|
|
8022
|
+
const branch = this.getBranch(worktreePath);
|
|
8023
|
+
this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
|
|
7869
8024
|
return null;
|
|
7870
8025
|
}
|
|
7871
8026
|
this.git("add -A", worktreePath);
|
|
7872
8027
|
const staged = this.git("diff --cached --name-only", worktreePath).trim();
|
|
7873
8028
|
if (!staged) {
|
|
7874
|
-
this.log("
|
|
8029
|
+
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
7875
8030
|
return null;
|
|
7876
8031
|
}
|
|
8032
|
+
this.log(`Staging ${staged.split(`
|
|
8033
|
+
`).length} file(s) for commit`, "info");
|
|
7877
8034
|
this.gitExec(["commit", "-m", message], worktreePath);
|
|
7878
8035
|
const hash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
7879
8036
|
this.log(`Committed: ${hash.slice(0, 8)}`, "success");
|
|
@@ -23004,15 +23161,27 @@ class TaskExecutor {
|
|
|
23004
23161
|
const basePrompt = await this.promptBuilder.build(task2);
|
|
23005
23162
|
try {
|
|
23006
23163
|
this.deps.log("Starting Execution...", "info");
|
|
23007
|
-
await this.deps.aiRunner.run(basePrompt);
|
|
23008
|
-
|
|
23009
|
-
|
|
23010
|
-
summary: "Task completed by the agent"
|
|
23011
|
-
};
|
|
23164
|
+
const output = await this.deps.aiRunner.run(basePrompt);
|
|
23165
|
+
const summary = this.extractSummary(output);
|
|
23166
|
+
return { success: true, summary };
|
|
23012
23167
|
} catch (error48) {
|
|
23013
23168
|
return { success: false, summary: `Error: ${error48}` };
|
|
23014
23169
|
}
|
|
23015
23170
|
}
|
|
23171
|
+
extractSummary(output) {
|
|
23172
|
+
if (!output || !output.trim()) {
|
|
23173
|
+
return "Task completed by the agent";
|
|
23174
|
+
}
|
|
23175
|
+
const paragraphs = output.split(/\n\n+/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
23176
|
+
if (paragraphs.length === 0) {
|
|
23177
|
+
return "Task completed by the agent";
|
|
23178
|
+
}
|
|
23179
|
+
const last = paragraphs[paragraphs.length - 1];
|
|
23180
|
+
if (last.length > 500) {
|
|
23181
|
+
return `${last.slice(0, 497)}...`;
|
|
23182
|
+
}
|
|
23183
|
+
return last;
|
|
23184
|
+
}
|
|
23016
23185
|
}
|
|
23017
23186
|
var init_task_executor = __esm(() => {
|
|
23018
23187
|
init_prompt_builder();
|
|
@@ -23030,7 +23199,7 @@ class GitWorkflow {
|
|
|
23030
23199
|
this.log = log;
|
|
23031
23200
|
this.ghUsername = ghUsername;
|
|
23032
23201
|
const projectPath = config2.projectPath || process.cwd();
|
|
23033
|
-
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }) : null;
|
|
23202
|
+
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log) : null;
|
|
23034
23203
|
this.prService = config2.autoPush ? new PrService(projectPath, log) : null;
|
|
23035
23204
|
}
|
|
23036
23205
|
createTaskWorktree(task2, defaultExecutor) {
|
|
@@ -23038,6 +23207,7 @@ class GitWorkflow {
|
|
|
23038
23207
|
return {
|
|
23039
23208
|
worktreePath: null,
|
|
23040
23209
|
baseBranch: null,
|
|
23210
|
+
baseCommitHash: null,
|
|
23041
23211
|
executor: defaultExecutor
|
|
23042
23212
|
};
|
|
23043
23213
|
}
|
|
@@ -23063,10 +23233,11 @@ class GitWorkflow {
|
|
|
23063
23233
|
return {
|
|
23064
23234
|
worktreePath: result.worktreePath,
|
|
23065
23235
|
baseBranch: result.baseBranch,
|
|
23236
|
+
baseCommitHash: result.baseCommitHash,
|
|
23066
23237
|
executor: taskExecutor
|
|
23067
23238
|
};
|
|
23068
23239
|
}
|
|
23069
|
-
commitAndPush(worktreePath, task2, baseBranch) {
|
|
23240
|
+
commitAndPush(worktreePath, task2, baseBranch, baseCommitHash) {
|
|
23070
23241
|
if (!this.worktreeManager) {
|
|
23071
23242
|
return { branch: null, pushed: false, pushFailed: false };
|
|
23072
23243
|
}
|
|
@@ -23083,7 +23254,7 @@ class GitWorkflow {
|
|
|
23083
23254
|
|
|
23084
23255
|
${trailers.join(`
|
|
23085
23256
|
`)}`;
|
|
23086
|
-
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch);
|
|
23257
|
+
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
|
|
23087
23258
|
if (!hash2) {
|
|
23088
23259
|
this.log("No changes to commit for this task", "info");
|
|
23089
23260
|
return {
|
|
@@ -23123,7 +23294,12 @@ ${trailers.join(`
|
|
|
23123
23294
|
} catch (err) {
|
|
23124
23295
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
23125
23296
|
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
23126
|
-
return {
|
|
23297
|
+
return {
|
|
23298
|
+
branch: null,
|
|
23299
|
+
pushed: false,
|
|
23300
|
+
pushFailed: true,
|
|
23301
|
+
pushError: `Git commit/push failed: ${errorMessage}`
|
|
23302
|
+
};
|
|
23127
23303
|
}
|
|
23128
23304
|
}
|
|
23129
23305
|
createPullRequest(task2, branch, summary, baseBranch) {
|
|
@@ -39169,7 +39345,7 @@ class AgentWorker {
|
|
|
39169
39345
|
}
|
|
39170
39346
|
async executeTask(task2) {
|
|
39171
39347
|
const fullTask = await this.client.tasks.getById(task2.id, this.config.workspaceId);
|
|
39172
|
-
const { worktreePath, baseBranch, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
39348
|
+
const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
39173
39349
|
this.currentWorktreePath = worktreePath;
|
|
39174
39350
|
let branchPushed = false;
|
|
39175
39351
|
let keepBranch = false;
|
|
@@ -39181,7 +39357,7 @@ class AgentWorker {
|
|
|
39181
39357
|
let prError = null;
|
|
39182
39358
|
let noChanges = false;
|
|
39183
39359
|
if (result.success && worktreePath) {
|
|
39184
|
-
const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined);
|
|
39360
|
+
const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined, baseCommitHash ?? undefined);
|
|
39185
39361
|
taskBranch = commitResult.branch;
|
|
39186
39362
|
branchPushed = commitResult.pushed;
|
|
39187
39363
|
keepBranch = taskBranch !== null;
|
|
@@ -40622,6 +40798,9 @@ class TierMergeService {
|
|
|
40622
40798
|
}
|
|
40623
40799
|
}
|
|
40624
40800
|
findTierTaskBranches(tier) {
|
|
40801
|
+
const tierTaskIds = this.tierTaskIds.get(tier);
|
|
40802
|
+
if (!tierTaskIds || tierTaskIds.length === 0)
|
|
40803
|
+
return [];
|
|
40625
40804
|
try {
|
|
40626
40805
|
const output = execSync3('git branch -r --list "origin/agent/*" --format="%(refname:short)"', { cwd: this.projectPath, encoding: "utf-8" }).trim();
|
|
40627
40806
|
if (!output)
|
|
@@ -40629,13 +40808,13 @@ class TierMergeService {
|
|
|
40629
40808
|
const remoteBranches = output.split(`
|
|
40630
40809
|
`).map((b) => b.replace("origin/", ""));
|
|
40631
40810
|
return remoteBranches.filter((branch) => {
|
|
40632
|
-
const
|
|
40633
|
-
if (!
|
|
40811
|
+
const branchSuffix = branch.replace(/^agent\//, "");
|
|
40812
|
+
if (!branchSuffix)
|
|
40634
40813
|
return false;
|
|
40635
|
-
|
|
40636
|
-
return this.tierTaskIds.get(tier)?.some((id) => id.startsWith(taskIdPrefix) || taskIdPrefix.startsWith(id.slice(0, 8))) ?? false;
|
|
40814
|
+
return tierTaskIds.some((id) => branchSuffix.startsWith(`${id}-`) || branchSuffix === id || branchSuffix.startsWith(id));
|
|
40637
40815
|
});
|
|
40638
|
-
} catch {
|
|
40816
|
+
} catch (err) {
|
|
40817
|
+
console.log(c.dim(` Could not list remote branches for tier ${tier}: ${err instanceof Error ? err.message : String(err)}`));
|
|
40639
40818
|
return [];
|
|
40640
40819
|
}
|
|
40641
40820
|
}
|
|
@@ -41210,6 +41389,7 @@ Review and refine the Tech Lead's breakdown:
|
|
|
41210
41389
|
3. **Task Merging** — If two tasks are trivially small and related, merge them.
|
|
41211
41390
|
4. **Complexity Scoring** — Rate each task 1-5 (1=trivial, 5=very complex).
|
|
41212
41391
|
5. **Missing Tasks** — Add any tasks the Tech Lead missed (database migrations, configuration, testing, etc.).
|
|
41392
|
+
6. **Description Quality** — Review and improve each task description to be a clear, actionable implementation guide. Each description must tell the executing agent exactly what to do, where to do it (specific files/modules), how to do it (patterns, utilities, data flow), and what is NOT in scope. Vague descriptions like "Add authentication" must be rewritten with specific file paths, implementation approach, and boundaries.
|
|
41213
41393
|
|
|
41214
41394
|
## CRITICAL: Task Isolation & Overlap Detection
|
|
41215
41395
|
|
|
@@ -41235,7 +41415,7 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
41235
41415
|
"tasks": [
|
|
41236
41416
|
{
|
|
41237
41417
|
"title": "string",
|
|
41238
|
-
"description": "string (
|
|
41418
|
+
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
|
|
41239
41419
|
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
41240
41420
|
"priority": "HIGH | MEDIUM | LOW | CRITICAL",
|
|
41241
41421
|
"labels": ["string"],
|
|
@@ -41328,6 +41508,15 @@ Verify tier assignments are correct:
|
|
|
41328
41508
|
### 5. Merge Conflict Risk Zones
|
|
41329
41509
|
Identify the highest-risk files (files that multiple same-tier tasks might touch) and ensure only ONE task per tier modifies each.
|
|
41330
41510
|
|
|
41511
|
+
### 6. Description Quality Validation
|
|
41512
|
+
For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
|
|
41513
|
+
- **What to do** — the specific goal and expected behavior/outcome
|
|
41514
|
+
- **Where to do it** — specific files, modules, or directories to modify or create
|
|
41515
|
+
- **How to do it** — implementation approach, patterns to follow, existing utilities to use
|
|
41516
|
+
- **Boundaries** — what is NOT in scope for this task
|
|
41517
|
+
|
|
41518
|
+
If any description is vague (e.g., "Add authentication", "Update the API", "Fix the frontend"), rewrite it with concrete implementation details. The executing agent receives ONLY the task title, description, and acceptance criteria as its instructions.
|
|
41519
|
+
|
|
41331
41520
|
## Output Format
|
|
41332
41521
|
|
|
41333
41522
|
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
@@ -41336,7 +41525,7 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
41336
41525
|
"hasIssues": true | false,
|
|
41337
41526
|
"issues": [
|
|
41338
41527
|
{
|
|
41339
|
-
"type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk" | "wrong_tier",
|
|
41528
|
+
"type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk" | "wrong_tier" | "vague_description",
|
|
41340
41529
|
"description": "string describing the specific issue",
|
|
41341
41530
|
"affectedTasks": ["Task Title 1", "Task Title 2"],
|
|
41342
41531
|
"resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
|
|
@@ -41349,7 +41538,7 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
41349
41538
|
"tasks": [
|
|
41350
41539
|
{
|
|
41351
41540
|
"title": "string",
|
|
41352
|
-
"description": "string",
|
|
41541
|
+
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
|
|
41353
41542
|
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
41354
41543
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
41355
41544
|
"labels": ["string"],
|
|
@@ -41373,6 +41562,7 @@ IMPORTANT:
|
|
|
41373
41562
|
- If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
|
|
41374
41563
|
- The revisedPlan is ALWAYS required — it becomes the final plan
|
|
41375
41564
|
- When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
|
|
41565
|
+
- Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
|
|
41376
41566
|
- Prefer fewer, larger, self-contained tasks over many small conflicting ones
|
|
41377
41567
|
- Every task MUST have a "tier" field (integer >= 0)
|
|
41378
41568
|
- tier 0 = foundational (runs first), tier 1 = depends on tier 0, tier 2 = depends on tier 1, etc.
|
|
@@ -41411,6 +41601,7 @@ Produce the final sprint plan:
|
|
|
41411
41601
|
4. **Tier Assignment** — Assign each task an execution tier (integer, starting at 0). Tasks within the same tier run IN PARALLEL on separate git branches. Tasks in tier N+1 only start AFTER all tier N tasks are complete and merged. Tier 0 = foundational tasks (config, schemas, shared code). Higher tiers build on lower tier outputs.
|
|
41412
41602
|
5. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
|
|
41413
41603
|
6. **Final Task List** — Each task with all fields filled in, ordered by execution priority
|
|
41604
|
+
7. **Description Quality Check** — Ensure every task description is a clear, actionable implementation guide. Each description must specify: what to do, where to do it (specific files/modules/directories), how to do it (implementation approach, patterns to follow, existing utilities to use), and what is NOT in scope. If any description is vague or generic, rewrite it with specifics. Remember: an independent agent will receive ONLY the task title, description, and acceptance criteria — the description is its primary instruction.
|
|
41414
41605
|
|
|
41415
41606
|
Guidelines:
|
|
41416
41607
|
- The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
|
|
@@ -41420,6 +41611,7 @@ Guidelines:
|
|
|
41420
41611
|
- Group related independent tasks in the same tier for maximum parallelism
|
|
41421
41612
|
- Ensure acceptance criteria are specific and testable
|
|
41422
41613
|
- Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
|
|
41614
|
+
- Ensure every task description reads as a standalone implementation brief — not a summary
|
|
41423
41615
|
|
|
41424
41616
|
## CRITICAL: Task Isolation Validation
|
|
41425
41617
|
|
|
@@ -41441,7 +41633,7 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
41441
41633
|
"tasks": [
|
|
41442
41634
|
{
|
|
41443
41635
|
"title": "string",
|
|
41444
|
-
"description": "string",
|
|
41636
|
+
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
|
|
41445
41637
|
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
41446
41638
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
41447
41639
|
"labels": ["string"],
|
|
@@ -41497,7 +41689,7 @@ ${input.codebaseIndex || "No codebase index available."}
|
|
|
41497
41689
|
Analyze the CEO's directive and produce a detailed task breakdown. For each task:
|
|
41498
41690
|
|
|
41499
41691
|
1. **Title** — Clear, action-oriented (e.g., "Implement user registration API endpoint")
|
|
41500
|
-
2. **Description** —
|
|
41692
|
+
2. **Description** — A detailed, actionable implementation guide (see description requirements below)
|
|
41501
41693
|
3. **Assignee Role** — Who should work on this: BACKEND, FRONTEND, QA, PM, or DESIGN
|
|
41502
41694
|
4. **Priority** — HIGH, MEDIUM, or LOW based on business impact
|
|
41503
41695
|
5. **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
|
|
@@ -41509,6 +41701,19 @@ Think about:
|
|
|
41509
41701
|
- What the right granularity is (not too big, not too small)
|
|
41510
41702
|
- What risks or unknowns exist
|
|
41511
41703
|
|
|
41704
|
+
## CRITICAL: Task Description Requirements
|
|
41705
|
+
|
|
41706
|
+
Each task description will be handed to an INDEPENDENT agent as its primary instruction. The agent will have access to the codebase but NO context about the planning meeting. Descriptions must be clear enough for the agent to execute the task without ambiguity.
|
|
41707
|
+
|
|
41708
|
+
Each description MUST include:
|
|
41709
|
+
1. **What to do** — Clearly state the goal and what needs to be implemented, changed, or created. Be specific about the expected behavior or outcome.
|
|
41710
|
+
2. **Where to do it** — List the specific files, modules, or directories that need to be modified or created. Reference existing code paths when extending functionality.
|
|
41711
|
+
3. **How to do it** — Provide key implementation details: which patterns to follow, which existing utilities or services to use, what the data flow looks like.
|
|
41712
|
+
4. **Boundaries** — Clarify what is NOT in scope for this task to prevent overlap with other tasks.
|
|
41713
|
+
|
|
41714
|
+
Bad example: "Add authentication to the API."
|
|
41715
|
+
Good example: "Implement JWT-based authentication middleware in src/middleware/auth.ts. Create a verifyToken middleware that extracts the Bearer token from the Authorization header, validates it using the existing JWT_SECRET from env config, and attaches the decoded user payload to req.user. Apply this middleware to all routes in src/routes/protected/. Add a POST /auth/login endpoint in src/routes/auth.ts that accepts {email, password}, validates credentials against the users table, and returns a signed JWT. This task does NOT include user registration or password reset — those are handled separately."
|
|
41716
|
+
|
|
41512
41717
|
## CRITICAL: Task Isolation Rules
|
|
41513
41718
|
|
|
41514
41719
|
Tasks will be executed by INDEPENDENT agents on SEPARATE git branches that get merged together. Each agent has NO knowledge of what other agents are doing. Therefore:
|
|
@@ -41527,7 +41732,7 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
41527
41732
|
"tasks": [
|
|
41528
41733
|
{
|
|
41529
41734
|
"title": "string",
|
|
41530
|
-
"description": "string (
|
|
41735
|
+
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries — see description requirements above)",
|
|
41531
41736
|
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
41532
41737
|
"priority": "HIGH | MEDIUM | LOW",
|
|
41533
41738
|
"labels": ["string"],
|
|
@@ -44735,10 +44940,7 @@ function showTelegramHelp() {
|
|
|
44735
44940
|
|
|
44736
44941
|
${c.header(" SUBCOMMANDS ")}
|
|
44737
44942
|
${c.success("run")} Start the Telegram bot
|
|
44738
|
-
${c.dim("--agents <N> Override agent count (1-5)")}
|
|
44739
44943
|
${c.success("setup")} Interactive Telegram bot setup (or pass flags below)
|
|
44740
|
-
${c.dim("--token <TOKEN> Bot token from @BotFather (required)")}
|
|
44741
|
-
${c.dim("--chat-id <ID> Your Telegram chat ID (required)")}
|
|
44742
44944
|
${c.success("config")} Show current Telegram configuration
|
|
44743
44945
|
${c.success("set")} Set a config value
|
|
44744
44946
|
${c.dim("locus telegram set <key> <value>")}
|
|
@@ -44747,7 +44949,6 @@ function showTelegramHelp() {
|
|
|
44747
44949
|
|
|
44748
44950
|
${c.header(" EXAMPLES ")}
|
|
44749
44951
|
${c.dim("$")} ${c.primary("locus telegram run")}
|
|
44750
|
-
${c.dim("$")} ${c.primary("locus telegram run --agents 3")}
|
|
44751
44952
|
${c.dim("$")} ${c.primary('locus telegram setup --token "123:ABC" --chat-id 987654')}
|
|
44752
44953
|
${c.dim("$")} ${c.primary("locus telegram config")}
|
|
44753
44954
|
${c.dim("$")} ${c.primary("locus telegram remove")}
|
|
@@ -44936,22 +45137,7 @@ function removeCommand2(projectPath) {
|
|
|
44936
45137
|
${c.success("✔")} ${c.bold("Telegram configuration removed.")}
|
|
44937
45138
|
`);
|
|
44938
45139
|
}
|
|
44939
|
-
function runBotCommand(
|
|
44940
|
-
let agentCountOverride;
|
|
44941
|
-
for (let i = 0;i < subArgs.length; i++) {
|
|
44942
|
-
if (subArgs[i] === "--agents" && subArgs[i + 1]) {
|
|
44943
|
-
agentCountOverride = subArgs[++i]?.trim();
|
|
44944
|
-
}
|
|
44945
|
-
}
|
|
44946
|
-
if (agentCountOverride) {
|
|
44947
|
-
const parsed = Number.parseInt(agentCountOverride, 10);
|
|
44948
|
-
if (Number.isNaN(parsed) || parsed < 1 || parsed > 5) {
|
|
44949
|
-
console.error(`
|
|
44950
|
-
${c.error("✖")} ${c.bold("Agent count must be a number between 1 and 5.")}
|
|
44951
|
-
`);
|
|
44952
|
-
process.exit(1);
|
|
44953
|
-
}
|
|
44954
|
-
}
|
|
45140
|
+
function runBotCommand(projectPath) {
|
|
44955
45141
|
const manager = new SettingsManager(projectPath);
|
|
44956
45142
|
const settings = manager.load();
|
|
44957
45143
|
if (!settings.telegram?.botToken || !settings.telegram?.chatId) {
|
|
@@ -44973,9 +45159,6 @@ function runBotCommand(subArgs, projectPath) {
|
|
|
44973
45159
|
args = [];
|
|
44974
45160
|
}
|
|
44975
45161
|
const env = { ...process.env };
|
|
44976
|
-
if (agentCountOverride) {
|
|
44977
|
-
env.LOCUS_AGENT_COUNT = agentCountOverride;
|
|
44978
|
-
}
|
|
44979
45162
|
const child = spawn4(cmd, args, {
|
|
44980
45163
|
cwd: projectPath,
|
|
44981
45164
|
stdio: "inherit",
|
|
@@ -45001,19 +45184,18 @@ function runBotCommand(subArgs, projectPath) {
|
|
|
45001
45184
|
async function telegramCommand(args) {
|
|
45002
45185
|
const projectPath = process.cwd();
|
|
45003
45186
|
const subcommand = args[0];
|
|
45004
|
-
const subArgs = args.slice(1);
|
|
45005
45187
|
switch (subcommand) {
|
|
45006
45188
|
case "run":
|
|
45007
|
-
runBotCommand(
|
|
45189
|
+
runBotCommand(projectPath);
|
|
45008
45190
|
break;
|
|
45009
45191
|
case "setup":
|
|
45010
|
-
await setupCommand2(
|
|
45192
|
+
await setupCommand2(args, projectPath);
|
|
45011
45193
|
break;
|
|
45012
45194
|
case "config":
|
|
45013
45195
|
configCommand2(projectPath);
|
|
45014
45196
|
break;
|
|
45015
45197
|
case "set":
|
|
45016
|
-
setCommand2(
|
|
45198
|
+
setCommand2(args, projectPath);
|
|
45017
45199
|
break;
|
|
45018
45200
|
case "remove":
|
|
45019
45201
|
removeCommand2(projectPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "CLI for Locus - AI-native project management platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"author": "",
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@locusai/sdk": "^0.10.
|
|
36
|
+
"@locusai/sdk": "^0.10.4"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {}
|
|
39
39
|
}
|