@locusai/sdk 0.4.16 → 0.5.0
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/dist/agent/artifact-syncer.d.ts +2 -1
- package/dist/agent/artifact-syncer.d.ts.map +1 -1
- package/dist/agent/codebase-indexer-service.d.ts +4 -5
- package/dist/agent/codebase-indexer-service.d.ts.map +1 -1
- package/dist/agent/sprint-planner.d.ts +4 -5
- package/dist/agent/sprint-planner.d.ts.map +1 -1
- package/dist/agent/task-executor.d.ts +5 -5
- package/dist/agent/task-executor.d.ts.map +1 -1
- package/dist/agent/worker.d.ts +4 -3
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +360 -224
- package/dist/ai/claude-runner.d.ts +10 -3
- package/dist/ai/claude-runner.d.ts.map +1 -1
- package/dist/ai/codex-runner.d.ts +22 -0
- package/dist/ai/codex-runner.d.ts.map +1 -0
- package/dist/ai/factory.d.ts +9 -0
- package/dist/ai/factory.d.ts.map +1 -0
- package/dist/ai/index.d.ts +4 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/runner.d.ts +6 -0
- package/dist/ai/runner.d.ts.map +1 -0
- package/dist/core/config.d.ts +6 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/index-node.js +392 -244
- package/dist/orchestrator.d.ts +3 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/package.json +7 -6
- package/dist/ai/anthropic-client.d.ts +0 -33
- package/dist/ai/anthropic-client.d.ts.map +0 -1
package/dist/index-node.js
CHANGED
|
@@ -439,13 +439,18 @@ __export(exports_worker, {
|
|
|
439
439
|
AgentWorker: () => AgentWorker
|
|
440
440
|
});
|
|
441
441
|
module.exports = __toCommonJS(exports_worker);
|
|
442
|
-
|
|
443
|
-
// src/ai/anthropic-client.ts
|
|
444
|
-
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
442
|
+
var import_shared3 = require("@locusai/shared");
|
|
445
443
|
|
|
446
444
|
// src/core/config.ts
|
|
447
445
|
var import_node_path = require("node:path");
|
|
448
|
-
var
|
|
446
|
+
var PROVIDER = {
|
|
447
|
+
CLAUDE: "claude",
|
|
448
|
+
CODEX: "codex"
|
|
449
|
+
};
|
|
450
|
+
var DEFAULT_MODEL = {
|
|
451
|
+
[PROVIDER.CLAUDE]: "sonnet",
|
|
452
|
+
[PROVIDER.CODEX]: "gpt-5.1-codex-mini"
|
|
453
|
+
};
|
|
449
454
|
var LOCUS_CONFIG = {
|
|
450
455
|
dir: ".locus",
|
|
451
456
|
configFile: "config.json",
|
|
@@ -460,122 +465,9 @@ function getLocusPath(projectPath, fileName) {
|
|
|
460
465
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
|
|
461
466
|
}
|
|
462
467
|
|
|
463
|
-
// src/ai/anthropic-client.ts
|
|
464
|
-
class AnthropicClient {
|
|
465
|
-
client;
|
|
466
|
-
model;
|
|
467
|
-
constructor(config) {
|
|
468
|
-
this.client = new import_sdk.default({
|
|
469
|
-
apiKey: config.apiKey
|
|
470
|
-
});
|
|
471
|
-
this.model = config.model || DEFAULT_MODEL;
|
|
472
|
-
}
|
|
473
|
-
async run(options) {
|
|
474
|
-
const { systemPrompt, cacheableContext = [], userPrompt } = options;
|
|
475
|
-
const systemContent = [];
|
|
476
|
-
if (systemPrompt) {
|
|
477
|
-
systemContent.push({
|
|
478
|
-
type: "text",
|
|
479
|
-
text: systemPrompt
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
for (let i = 0;i < cacheableContext.length; i++) {
|
|
483
|
-
const isLast = i === cacheableContext.length - 1;
|
|
484
|
-
systemContent.push({
|
|
485
|
-
type: "text",
|
|
486
|
-
text: cacheableContext[i],
|
|
487
|
-
...isLast && {
|
|
488
|
-
cache_control: { type: "ephemeral" }
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
const response = await this.client.messages.create({
|
|
493
|
-
model: this.model,
|
|
494
|
-
max_tokens: 8000,
|
|
495
|
-
system: systemContent,
|
|
496
|
-
messages: [
|
|
497
|
-
{
|
|
498
|
-
role: "user",
|
|
499
|
-
content: userPrompt
|
|
500
|
-
}
|
|
501
|
-
]
|
|
502
|
-
});
|
|
503
|
-
const textBlocks = response.content.filter((block) => block.type === "text");
|
|
504
|
-
return textBlocks.map((block) => block.text).join(`
|
|
505
|
-
`);
|
|
506
|
-
}
|
|
507
|
-
async runSimple(prompt) {
|
|
508
|
-
return this.run({
|
|
509
|
-
userPrompt: prompt
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
468
|
// src/ai/claude-runner.ts
|
|
515
469
|
var import_node_child_process = require("node:child_process");
|
|
516
|
-
|
|
517
|
-
projectPath;
|
|
518
|
-
model;
|
|
519
|
-
constructor(projectPath, model = DEFAULT_MODEL) {
|
|
520
|
-
this.projectPath = projectPath;
|
|
521
|
-
this.model = model;
|
|
522
|
-
}
|
|
523
|
-
async run(prompt, _isPlanning = false) {
|
|
524
|
-
const maxRetries = 3;
|
|
525
|
-
let lastError = null;
|
|
526
|
-
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
527
|
-
try {
|
|
528
|
-
return await this.executeRun(prompt);
|
|
529
|
-
} catch (error) {
|
|
530
|
-
const err = error;
|
|
531
|
-
lastError = err;
|
|
532
|
-
const isLastAttempt = attempt === maxRetries;
|
|
533
|
-
if (!isLastAttempt) {
|
|
534
|
-
const delay = Math.pow(2, attempt) * 1000;
|
|
535
|
-
console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
536
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
541
|
-
}
|
|
542
|
-
executeRun(prompt) {
|
|
543
|
-
return new Promise((resolve, reject) => {
|
|
544
|
-
const args = [
|
|
545
|
-
"--dangerously-skip-permissions",
|
|
546
|
-
"--print",
|
|
547
|
-
"--model",
|
|
548
|
-
this.model
|
|
549
|
-
];
|
|
550
|
-
const claude = import_node_child_process.spawn("claude", args, {
|
|
551
|
-
cwd: this.projectPath,
|
|
552
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
553
|
-
env: process.env,
|
|
554
|
-
shell: true
|
|
555
|
-
});
|
|
556
|
-
let output = "";
|
|
557
|
-
let errorOutput = "";
|
|
558
|
-
claude.stdout.on("data", (data) => {
|
|
559
|
-
output += data.toString();
|
|
560
|
-
});
|
|
561
|
-
claude.stderr.on("data", (data) => {
|
|
562
|
-
errorOutput += data.toString();
|
|
563
|
-
});
|
|
564
|
-
claude.on("error", (err) => reject(new Error(`Failed to start Claude CLI (shell: true): ${err.message}. Please ensure the 'claude' command is available in your PATH.`)));
|
|
565
|
-
claude.on("close", (code) => {
|
|
566
|
-
if (code === 0)
|
|
567
|
-
resolve(output);
|
|
568
|
-
else {
|
|
569
|
-
const detail = errorOutput.trim();
|
|
570
|
-
const message = detail ? `Claude CLI error (exit code ${code}): ${detail}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
|
|
571
|
-
reject(new Error(message));
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
claude.stdin.write(prompt);
|
|
575
|
-
claude.stdin.end();
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
470
|
+
var import_node_path2 = require("node:path");
|
|
579
471
|
|
|
580
472
|
// src/utils/colors.ts
|
|
581
473
|
var ESC = "\x1B[";
|
|
@@ -625,9 +517,270 @@ var c = {
|
|
|
625
517
|
underline: (t) => c.text(t, "underline")
|
|
626
518
|
};
|
|
627
519
|
|
|
628
|
-
// src/
|
|
520
|
+
// src/ai/claude-runner.ts
|
|
521
|
+
class ClaudeRunner {
|
|
522
|
+
model;
|
|
523
|
+
log;
|
|
524
|
+
projectPath;
|
|
525
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log) {
|
|
526
|
+
this.model = model;
|
|
527
|
+
this.log = log;
|
|
528
|
+
this.projectPath = import_node_path2.resolve(projectPath);
|
|
529
|
+
}
|
|
530
|
+
async run(prompt, _isPlanning = false) {
|
|
531
|
+
const maxRetries = 3;
|
|
532
|
+
let lastError = null;
|
|
533
|
+
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
534
|
+
try {
|
|
535
|
+
return await this.executeRun(prompt);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const err = error;
|
|
538
|
+
lastError = err;
|
|
539
|
+
const isLastAttempt = attempt === maxRetries;
|
|
540
|
+
if (!isLastAttempt) {
|
|
541
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
542
|
+
console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
|
|
543
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
548
|
+
}
|
|
549
|
+
executeRun(prompt) {
|
|
550
|
+
return new Promise((resolve2, reject) => {
|
|
551
|
+
const args = [
|
|
552
|
+
"--dangerously-skip-permissions",
|
|
553
|
+
"--print",
|
|
554
|
+
"--verbose",
|
|
555
|
+
"--output-format",
|
|
556
|
+
"stream-json",
|
|
557
|
+
"--include-partial-messages",
|
|
558
|
+
"--model",
|
|
559
|
+
this.model
|
|
560
|
+
];
|
|
561
|
+
const env = {
|
|
562
|
+
...process.env,
|
|
563
|
+
FORCE_COLOR: "1",
|
|
564
|
+
TERM: "xterm-256color"
|
|
565
|
+
};
|
|
566
|
+
const claude = import_node_child_process.spawn("claude", args, {
|
|
567
|
+
cwd: this.projectPath,
|
|
568
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
569
|
+
env
|
|
570
|
+
});
|
|
571
|
+
let finalResult = "";
|
|
572
|
+
let errorOutput = "";
|
|
573
|
+
let buffer = "";
|
|
574
|
+
claude.stdout.on("data", (data) => {
|
|
575
|
+
buffer += data.toString();
|
|
576
|
+
const lines = buffer.split(`
|
|
577
|
+
`);
|
|
578
|
+
buffer = lines.pop() || "";
|
|
579
|
+
for (const line of lines) {
|
|
580
|
+
const result = this.handleStreamLine(line);
|
|
581
|
+
if (result)
|
|
582
|
+
finalResult = result;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
claude.stderr.on("data", (data) => {
|
|
586
|
+
const msg = data.toString();
|
|
587
|
+
errorOutput += msg;
|
|
588
|
+
process.stderr.write(msg);
|
|
589
|
+
});
|
|
590
|
+
claude.on("error", (err) => {
|
|
591
|
+
reject(new Error(`Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`));
|
|
592
|
+
});
|
|
593
|
+
claude.on("close", (code) => {
|
|
594
|
+
process.stdout.write(`
|
|
595
|
+
`);
|
|
596
|
+
if (code === 0) {
|
|
597
|
+
resolve2(finalResult);
|
|
598
|
+
} else {
|
|
599
|
+
reject(this.createExecutionError(code, errorOutput));
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
claude.stdin.write(prompt);
|
|
603
|
+
claude.stdin.end();
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
handleStreamLine(line) {
|
|
607
|
+
if (!line.trim())
|
|
608
|
+
return null;
|
|
609
|
+
try {
|
|
610
|
+
const item = JSON.parse(line);
|
|
611
|
+
return this.processStreamItem(item);
|
|
612
|
+
} catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
processStreamItem(item) {
|
|
617
|
+
if (item.type === "result") {
|
|
618
|
+
return item.result || "";
|
|
619
|
+
}
|
|
620
|
+
if (item.type === "stream_event" && item.event) {
|
|
621
|
+
this.handleEvent(item.event);
|
|
622
|
+
}
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
handleEvent(event) {
|
|
626
|
+
const { type, delta, content_block } = event;
|
|
627
|
+
if (type === "content_block_delta" && delta) {
|
|
628
|
+
if (delta.type === "text_delta" && delta.text) {
|
|
629
|
+
this.log?.(delta.text, "info");
|
|
630
|
+
}
|
|
631
|
+
} else if (type === "content_block_start" && content_block) {
|
|
632
|
+
if (content_block.type === "tool_use" && content_block.name) {
|
|
633
|
+
this.log?.(`
|
|
634
|
+
|
|
635
|
+
${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
636
|
+
`, "info");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
createExecutionError(code, detail) {
|
|
641
|
+
const errorMsg = detail.trim();
|
|
642
|
+
const message = errorMsg ? `Claude CLI error (exit code ${code}): ${errorMsg}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in.`;
|
|
643
|
+
return new Error(message);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/ai/codex-runner.ts
|
|
648
|
+
var import_node_child_process2 = require("node:child_process");
|
|
649
|
+
var import_node_crypto = require("node:crypto");
|
|
629
650
|
var import_node_fs = require("node:fs");
|
|
630
|
-
var
|
|
651
|
+
var import_node_os = require("node:os");
|
|
652
|
+
var import_node_path3 = require("node:path");
|
|
653
|
+
class CodexRunner {
|
|
654
|
+
projectPath;
|
|
655
|
+
model;
|
|
656
|
+
log;
|
|
657
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log) {
|
|
658
|
+
this.projectPath = projectPath;
|
|
659
|
+
this.model = model;
|
|
660
|
+
this.log = log;
|
|
661
|
+
}
|
|
662
|
+
async run(prompt) {
|
|
663
|
+
const maxRetries = 3;
|
|
664
|
+
let lastError = null;
|
|
665
|
+
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
666
|
+
try {
|
|
667
|
+
return await this.executeRun(prompt);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
lastError = error;
|
|
670
|
+
if (attempt < maxRetries) {
|
|
671
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
672
|
+
console.warn(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`);
|
|
673
|
+
await this.sleep(delay);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
678
|
+
}
|
|
679
|
+
executeRun(prompt) {
|
|
680
|
+
return new Promise((resolve2, reject) => {
|
|
681
|
+
const outputPath = import_node_path3.join(import_node_os.tmpdir(), `locus-codex-${import_node_crypto.randomUUID()}.txt`);
|
|
682
|
+
const args = this.buildArgs(outputPath);
|
|
683
|
+
const codex = import_node_child_process2.spawn("codex", args, {
|
|
684
|
+
cwd: this.projectPath,
|
|
685
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
686
|
+
env: process.env,
|
|
687
|
+
shell: false
|
|
688
|
+
});
|
|
689
|
+
let output = "";
|
|
690
|
+
let errorOutput = "";
|
|
691
|
+
const handleOutput = (data) => {
|
|
692
|
+
const msg = data.toString();
|
|
693
|
+
output += msg;
|
|
694
|
+
this.streamToConsole(msg);
|
|
695
|
+
};
|
|
696
|
+
codex.stdout.on("data", handleOutput);
|
|
697
|
+
codex.stderr.on("data", (data) => {
|
|
698
|
+
const msg = data.toString();
|
|
699
|
+
errorOutput += msg;
|
|
700
|
+
this.streamToConsole(msg);
|
|
701
|
+
});
|
|
702
|
+
codex.on("error", (err) => {
|
|
703
|
+
reject(new Error(`Failed to start Codex CLI: ${err.message}. ` + `Ensure 'codex' is installed and available in PATH.`));
|
|
704
|
+
});
|
|
705
|
+
codex.on("close", (code) => {
|
|
706
|
+
this.cleanupTempFile(outputPath);
|
|
707
|
+
if (code === 0) {
|
|
708
|
+
resolve2(this.readOutput(outputPath, output));
|
|
709
|
+
} else {
|
|
710
|
+
reject(this.createErrorFromOutput(code, errorOutput));
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
codex.stdin.write(prompt);
|
|
714
|
+
codex.stdin.end();
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
buildArgs(outputPath) {
|
|
718
|
+
const args = ["exec", "--full-auto", "--output-last-message", outputPath];
|
|
719
|
+
if (this.model) {
|
|
720
|
+
args.push("--model", this.model);
|
|
721
|
+
}
|
|
722
|
+
args.push("-");
|
|
723
|
+
return args;
|
|
724
|
+
}
|
|
725
|
+
streamToConsole(chunk) {
|
|
726
|
+
for (const rawLine of chunk.split(`
|
|
727
|
+
`)) {
|
|
728
|
+
const line = rawLine.trim();
|
|
729
|
+
if (line && this.shouldDisplay(line)) {
|
|
730
|
+
const formattedLine = "[Codex]: ".concat(line.replace(/\*/g, ""));
|
|
731
|
+
this.log?.(formattedLine, "info");
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
shouldDisplay(line) {
|
|
736
|
+
return [
|
|
737
|
+
/^thinking\b/,
|
|
738
|
+
/^\*\*/,
|
|
739
|
+
/^Plan update\b/,
|
|
740
|
+
/^[→•✓]/
|
|
741
|
+
].some((pattern) => pattern.test(line));
|
|
742
|
+
}
|
|
743
|
+
readOutput(outputPath, fallback) {
|
|
744
|
+
if (import_node_fs.existsSync(outputPath)) {
|
|
745
|
+
try {
|
|
746
|
+
const text = import_node_fs.readFileSync(outputPath, "utf-8").trim();
|
|
747
|
+
if (text)
|
|
748
|
+
return text;
|
|
749
|
+
} catch {}
|
|
750
|
+
}
|
|
751
|
+
return fallback.trim();
|
|
752
|
+
}
|
|
753
|
+
createErrorFromOutput(code, errorOutput) {
|
|
754
|
+
const detail = errorOutput.trim();
|
|
755
|
+
const message = detail ? `Codex CLI error (exit code ${code}): ${detail}` : `Codex CLI exited with code ${code}. ` + `Ensure Codex CLI is installed and you are logged in.`;
|
|
756
|
+
return new Error(message);
|
|
757
|
+
}
|
|
758
|
+
cleanupTempFile(path) {
|
|
759
|
+
try {
|
|
760
|
+
if (import_node_fs.existsSync(path))
|
|
761
|
+
import_node_fs.unlinkSync(path);
|
|
762
|
+
} catch {}
|
|
763
|
+
}
|
|
764
|
+
sleep(ms) {
|
|
765
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/ai/factory.ts
|
|
770
|
+
function createAiRunner(provider, config) {
|
|
771
|
+
const resolvedProvider = provider ?? PROVIDER.CLAUDE;
|
|
772
|
+
const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
|
|
773
|
+
switch (resolvedProvider) {
|
|
774
|
+
case PROVIDER.CODEX:
|
|
775
|
+
return new CodexRunner(config.projectPath, model, config.log);
|
|
776
|
+
default:
|
|
777
|
+
return new ClaudeRunner(config.projectPath, model, config.log);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/agent/artifact-syncer.ts
|
|
782
|
+
var import_node_fs2 = require("node:fs");
|
|
783
|
+
var import_node_path4 = require("node:path");
|
|
631
784
|
class ArtifactSyncer {
|
|
632
785
|
deps;
|
|
633
786
|
constructor(deps) {
|
|
@@ -653,21 +806,21 @@ class ArtifactSyncer {
|
|
|
653
806
|
}
|
|
654
807
|
async sync() {
|
|
655
808
|
const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
|
|
656
|
-
if (!
|
|
657
|
-
|
|
809
|
+
if (!import_node_fs2.existsSync(artifactsDir)) {
|
|
810
|
+
import_node_fs2.mkdirSync(artifactsDir, { recursive: true });
|
|
658
811
|
return;
|
|
659
812
|
}
|
|
660
813
|
try {
|
|
661
|
-
const files =
|
|
814
|
+
const files = import_node_fs2.readdirSync(artifactsDir);
|
|
662
815
|
if (files.length === 0)
|
|
663
816
|
return;
|
|
664
817
|
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
665
818
|
const artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
666
819
|
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
667
820
|
for (const file of files) {
|
|
668
|
-
const filePath =
|
|
669
|
-
if (
|
|
670
|
-
const content =
|
|
821
|
+
const filePath = import_node_path4.join(artifactsDir, file);
|
|
822
|
+
if (import_node_fs2.statSync(filePath).isFile()) {
|
|
823
|
+
const content = import_node_fs2.readFileSync(filePath, "utf-8");
|
|
671
824
|
const title = file.replace(/\.md$/, "").trim();
|
|
672
825
|
if (!title)
|
|
673
826
|
continue;
|
|
@@ -694,8 +847,8 @@ class ArtifactSyncer {
|
|
|
694
847
|
}
|
|
695
848
|
|
|
696
849
|
// src/core/indexer.ts
|
|
697
|
-
var
|
|
698
|
-
var
|
|
850
|
+
var import_node_fs3 = require("node:fs");
|
|
851
|
+
var import_node_path5 = require("node:path");
|
|
699
852
|
var import_globby = require("globby");
|
|
700
853
|
|
|
701
854
|
class CodebaseIndexer {
|
|
@@ -703,7 +856,7 @@ class CodebaseIndexer {
|
|
|
703
856
|
indexPath;
|
|
704
857
|
constructor(projectPath) {
|
|
705
858
|
this.projectPath = projectPath;
|
|
706
|
-
this.indexPath =
|
|
859
|
+
this.indexPath = import_node_path5.join(projectPath, ".locus", "codebase-index.json");
|
|
707
860
|
}
|
|
708
861
|
async index(onProgress, treeSummarizer) {
|
|
709
862
|
if (!treeSummarizer) {
|
|
@@ -711,11 +864,11 @@ class CodebaseIndexer {
|
|
|
711
864
|
}
|
|
712
865
|
if (onProgress)
|
|
713
866
|
onProgress("Generating file tree...");
|
|
714
|
-
const gitmodulesPath =
|
|
867
|
+
const gitmodulesPath = import_node_path5.join(this.projectPath, ".gitmodules");
|
|
715
868
|
const submoduleIgnores = [];
|
|
716
|
-
if (
|
|
869
|
+
if (import_node_fs3.existsSync(gitmodulesPath)) {
|
|
717
870
|
try {
|
|
718
|
-
const content =
|
|
871
|
+
const content = import_node_fs3.readFileSync(gitmodulesPath, "utf-8");
|
|
719
872
|
const lines = content.split(`
|
|
720
873
|
`);
|
|
721
874
|
for (const line of lines) {
|
|
@@ -778,9 +931,9 @@ class CodebaseIndexer {
|
|
|
778
931
|
return index;
|
|
779
932
|
}
|
|
780
933
|
loadIndex() {
|
|
781
|
-
if (
|
|
934
|
+
if (import_node_fs3.existsSync(this.indexPath)) {
|
|
782
935
|
try {
|
|
783
|
-
return JSON.parse(
|
|
936
|
+
return JSON.parse(import_node_fs3.readFileSync(this.indexPath, "utf-8"));
|
|
784
937
|
} catch {
|
|
785
938
|
return null;
|
|
786
939
|
}
|
|
@@ -788,11 +941,11 @@ class CodebaseIndexer {
|
|
|
788
941
|
return null;
|
|
789
942
|
}
|
|
790
943
|
saveIndex(index) {
|
|
791
|
-
const dir =
|
|
792
|
-
if (!
|
|
793
|
-
|
|
944
|
+
const dir = import_node_path5.dirname(this.indexPath);
|
|
945
|
+
if (!import_node_fs3.existsSync(dir)) {
|
|
946
|
+
import_node_fs3.mkdirSync(dir, { recursive: true });
|
|
794
947
|
}
|
|
795
|
-
|
|
948
|
+
import_node_fs3.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
796
949
|
}
|
|
797
950
|
}
|
|
798
951
|
|
|
@@ -821,15 +974,7 @@ File tree:
|
|
|
821
974
|
${tree}
|
|
822
975
|
|
|
823
976
|
Return ONLY valid JSON, no markdown formatting.`;
|
|
824
|
-
|
|
825
|
-
if (this.deps.anthropicClient) {
|
|
826
|
-
response = await this.deps.anthropicClient.run({
|
|
827
|
-
systemPrompt: "You are a codebase analysis expert specialized in extracting structure and symbols from file trees.",
|
|
828
|
-
userPrompt: prompt
|
|
829
|
-
});
|
|
830
|
-
} else {
|
|
831
|
-
response = await this.deps.claudeRunner.run(prompt, true);
|
|
832
|
-
}
|
|
977
|
+
const response = await this.deps.aiRunner.run(prompt, true);
|
|
833
978
|
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
834
979
|
if (jsonMatch) {
|
|
835
980
|
return JSON.parse(jsonMatch[0]);
|
|
@@ -855,30 +1000,7 @@ class SprintPlanner {
|
|
|
855
1000
|
try {
|
|
856
1001
|
const taskList = tasks2.map((t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`).join(`
|
|
857
1002
|
`);
|
|
858
|
-
|
|
859
|
-
if (this.deps.anthropicClient) {
|
|
860
|
-
const systemPrompt = `You are an expert project manager and lead engineer specialized in sprint planning and task prioritization.`;
|
|
861
|
-
const userPrompt = `# Sprint Planning: ${sprint.name}
|
|
862
|
-
|
|
863
|
-
## Tasks
|
|
864
|
-
${taskList}
|
|
865
|
-
|
|
866
|
-
## Instructions
|
|
867
|
-
1. Analyze dependencies between these tasks.
|
|
868
|
-
2. Prioritize them for the most efficient execution.
|
|
869
|
-
3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
|
|
870
|
-
4. Output your final plan. The plan should clearly state the order of execution.
|
|
871
|
-
|
|
872
|
-
**IMPORTANT**:
|
|
873
|
-
- Do NOT create any files on the filesystem during this planning phase.
|
|
874
|
-
- Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
|
|
875
|
-
- Your output will be saved as the official sprint mindmap on the server.`;
|
|
876
|
-
plan = await this.deps.anthropicClient.run({
|
|
877
|
-
systemPrompt,
|
|
878
|
-
userPrompt
|
|
879
|
-
});
|
|
880
|
-
} else {
|
|
881
|
-
const planningPrompt = `# Sprint Planning: ${sprint.name}
|
|
1003
|
+
const planningPrompt = `# Sprint Planning: ${sprint.name}
|
|
882
1004
|
|
|
883
1005
|
You are an expert project manager and lead engineer. You need to create a mindmap and execution plan for the following tasks in this sprint.
|
|
884
1006
|
|
|
@@ -895,8 +1017,7 @@ ${taskList}
|
|
|
895
1017
|
- Do NOT create any files on the filesystem during this planning phase.
|
|
896
1018
|
- Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
|
|
897
1019
|
- Your output will be saved as the official sprint mindmap on the server.`;
|
|
898
|
-
|
|
899
|
-
}
|
|
1020
|
+
const plan = await this.deps.aiRunner.run(planningPrompt, true);
|
|
900
1021
|
this.deps.log("Sprint mindmap generated and posted to server.", "success");
|
|
901
1022
|
return plan;
|
|
902
1023
|
} catch (error) {
|
|
@@ -907,7 +1028,7 @@ ${taskList}
|
|
|
907
1028
|
}
|
|
908
1029
|
|
|
909
1030
|
// src/core/prompt-builder.ts
|
|
910
|
-
var
|
|
1031
|
+
var import_node_fs4 = require("node:fs");
|
|
911
1032
|
var import_shared2 = require("@locusai/shared");
|
|
912
1033
|
class PromptBuilder {
|
|
913
1034
|
projectPath;
|
|
@@ -930,9 +1051,9 @@ ${task.description || "No description provided."}
|
|
|
930
1051
|
|
|
931
1052
|
`;
|
|
932
1053
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
933
|
-
if (
|
|
1054
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
934
1055
|
try {
|
|
935
|
-
const context =
|
|
1056
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
936
1057
|
prompt += `## Project Context (from CLAUDE.md)
|
|
937
1058
|
${context}
|
|
938
1059
|
|
|
@@ -942,7 +1063,7 @@ ${context}
|
|
|
942
1063
|
}
|
|
943
1064
|
}
|
|
944
1065
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
945
|
-
if (
|
|
1066
|
+
if (import_node_fs4.existsSync(indexPath)) {
|
|
946
1067
|
prompt += `## Codebase Overview
|
|
947
1068
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
948
1069
|
|
|
@@ -1033,22 +1154,20 @@ ${this.deps.sprintPlan}
|
|
|
1033
1154
|
${basePrompt}`;
|
|
1034
1155
|
}
|
|
1035
1156
|
try {
|
|
1036
|
-
let plan =
|
|
1037
|
-
if (this.deps.
|
|
1038
|
-
this.deps.log("Phase 1: Planning (
|
|
1039
|
-
const cacheableContext = [basePrompt];
|
|
1040
|
-
plan = await this.deps.anthropicClient.run({
|
|
1041
|
-
systemPrompt: "You are an expert software engineer. Analyze the task carefully and create a detailed implementation plan.",
|
|
1042
|
-
cacheableContext,
|
|
1043
|
-
userPrompt: `## Phase 1: Planning
|
|
1044
|
-
Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`
|
|
1045
|
-
});
|
|
1157
|
+
let plan = null;
|
|
1158
|
+
if (this.deps.skipPlanning) {
|
|
1159
|
+
this.deps.log("Skipping Phase 1: Planning (CLI)...", "info");
|
|
1046
1160
|
} else {
|
|
1047
|
-
this.deps.log("
|
|
1161
|
+
this.deps.log("Phase 1: Planning (CLI)...", "info");
|
|
1162
|
+
const planningPrompt = `${basePrompt}
|
|
1163
|
+
|
|
1164
|
+
## Phase 1: Planning
|
|
1165
|
+
Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`;
|
|
1166
|
+
plan = await this.deps.aiRunner.run(planningPrompt, true);
|
|
1048
1167
|
}
|
|
1049
1168
|
this.deps.log("Starting Execution...", "info");
|
|
1050
1169
|
let executionPrompt = basePrompt;
|
|
1051
|
-
if (plan) {
|
|
1170
|
+
if (plan != null) {
|
|
1052
1171
|
executionPrompt += `
|
|
1053
1172
|
|
|
1054
1173
|
## Phase 2: Execution
|
|
@@ -1064,11 +1183,11 @@ Execute the task directly.`;
|
|
|
1064
1183
|
executionPrompt += `
|
|
1065
1184
|
|
|
1066
1185
|
When finished, output: <promise>COMPLETE</promise>`;
|
|
1067
|
-
const output = await this.deps.
|
|
1186
|
+
const output = await this.deps.aiRunner.run(executionPrompt);
|
|
1068
1187
|
const success = output.includes("<promise>COMPLETE</promise>");
|
|
1069
1188
|
return {
|
|
1070
1189
|
success,
|
|
1071
|
-
summary: success ? "Task completed by
|
|
1190
|
+
summary: success ? "Task completed by the agent" : "The agent did not signal completion"
|
|
1072
1191
|
};
|
|
1073
1192
|
} catch (error) {
|
|
1074
1193
|
return { success: false, summary: `Error: ${error}` };
|
|
@@ -1077,17 +1196,27 @@ When finished, output: <promise>COMPLETE</promise>`;
|
|
|
1077
1196
|
}
|
|
1078
1197
|
|
|
1079
1198
|
// src/agent/worker.ts
|
|
1199
|
+
function resolveProvider(value) {
|
|
1200
|
+
if (!value || value.startsWith("--")) {
|
|
1201
|
+
console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
|
|
1202
|
+
return PROVIDER.CLAUDE;
|
|
1203
|
+
}
|
|
1204
|
+
if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
|
|
1205
|
+
return value;
|
|
1206
|
+
console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
|
|
1207
|
+
return PROVIDER.CLAUDE;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1080
1210
|
class AgentWorker {
|
|
1081
1211
|
config;
|
|
1082
1212
|
client;
|
|
1083
|
-
|
|
1084
|
-
anthropicClient;
|
|
1213
|
+
aiRunner;
|
|
1085
1214
|
sprintPlanner;
|
|
1086
1215
|
indexerService;
|
|
1087
1216
|
artifactSyncer;
|
|
1088
1217
|
taskExecutor;
|
|
1089
1218
|
consecutiveEmpty = 0;
|
|
1090
|
-
maxEmpty =
|
|
1219
|
+
maxEmpty = 5;
|
|
1091
1220
|
maxTasks = 50;
|
|
1092
1221
|
tasksCompleted = 0;
|
|
1093
1222
|
pollInterval = 1e4;
|
|
@@ -1105,41 +1234,37 @@ class AgentWorker {
|
|
|
1105
1234
|
factor: 2
|
|
1106
1235
|
}
|
|
1107
1236
|
});
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1237
|
+
const log = this.log.bind(this);
|
|
1238
|
+
const provider = config.provider ?? PROVIDER.CLAUDE;
|
|
1239
|
+
this.aiRunner = createAiRunner(provider, {
|
|
1240
|
+
projectPath,
|
|
1241
|
+
model: config.model,
|
|
1242
|
+
log
|
|
1243
|
+
});
|
|
1114
1244
|
this.sprintPlanner = new SprintPlanner({
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
log: logFn
|
|
1245
|
+
aiRunner: this.aiRunner,
|
|
1246
|
+
log
|
|
1118
1247
|
});
|
|
1119
1248
|
this.indexerService = new CodebaseIndexerService({
|
|
1120
|
-
|
|
1121
|
-
claudeRunner: this.claudeRunner,
|
|
1249
|
+
aiRunner: this.aiRunner,
|
|
1122
1250
|
projectPath,
|
|
1123
|
-
log
|
|
1251
|
+
log
|
|
1124
1252
|
});
|
|
1125
1253
|
this.artifactSyncer = new ArtifactSyncer({
|
|
1126
1254
|
client: this.client,
|
|
1127
1255
|
workspaceId: config.workspaceId,
|
|
1128
1256
|
projectPath,
|
|
1129
|
-
log
|
|
1257
|
+
log
|
|
1130
1258
|
});
|
|
1131
1259
|
this.taskExecutor = new TaskExecutor({
|
|
1132
|
-
|
|
1133
|
-
claudeRunner: this.claudeRunner,
|
|
1260
|
+
aiRunner: this.aiRunner,
|
|
1134
1261
|
projectPath,
|
|
1135
1262
|
sprintPlan: null,
|
|
1136
|
-
|
|
1263
|
+
skipPlanning: config.skipPlanning,
|
|
1264
|
+
log
|
|
1137
1265
|
});
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
} else {
|
|
1141
|
-
this.log("Using Claude CLI for all phases", "info");
|
|
1142
|
-
}
|
|
1266
|
+
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
1267
|
+
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
1143
1268
|
}
|
|
1144
1269
|
log(message, level = "info") {
|
|
1145
1270
|
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
@@ -1186,16 +1311,17 @@ class AgentWorker {
|
|
|
1186
1311
|
const tasks2 = await this.client.tasks.list(this.config.workspaceId, {
|
|
1187
1312
|
sprintId: sprint.id
|
|
1188
1313
|
});
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
return taskDate > latest ? taskDate : latest;
|
|
1193
|
-
}, new Date(0));
|
|
1194
|
-
const mindmapDate = sprint.mindmapUpdatedAt ? new Date(sprint.mindmapUpdatedAt) : new Date(0);
|
|
1195
|
-
if (tasks2.length <= 1) {
|
|
1314
|
+
const activeTasks = tasks2.filter((t) => t.status === import_shared3.TaskStatus.BACKLOG || t.status === import_shared3.TaskStatus.IN_PROGRESS);
|
|
1315
|
+
this.log(`Sprint tasks found: ${activeTasks.length}`, "info");
|
|
1316
|
+
if (activeTasks.length <= 1) {
|
|
1196
1317
|
this.log("Skipping mindmap generation (only one task in sprint).", "info");
|
|
1197
1318
|
this.sprintPlan = null;
|
|
1198
1319
|
} else {
|
|
1320
|
+
const latestTaskCreation = activeTasks.reduce((latest, task) => {
|
|
1321
|
+
const taskDate = new Date(task.createdAt);
|
|
1322
|
+
return taskDate > latest ? taskDate : latest;
|
|
1323
|
+
}, new Date(0));
|
|
1324
|
+
const mindmapDate = sprint.mindmapUpdatedAt ? new Date(sprint.mindmapUpdatedAt) : new Date(0);
|
|
1199
1325
|
const needsPlanning = !sprint.mindmap || sprint.mindmap.trim() === "" || latestTaskCreation > mindmapDate;
|
|
1200
1326
|
if (needsPlanning) {
|
|
1201
1327
|
if (sprint.mindmap && latestTaskCreation > mindmapDate) {
|
|
@@ -1217,6 +1343,9 @@ class AgentWorker {
|
|
|
1217
1343
|
while (this.tasksCompleted < this.maxTasks && this.consecutiveEmpty < this.maxEmpty) {
|
|
1218
1344
|
const task = await this.getNextTask();
|
|
1219
1345
|
if (!task) {
|
|
1346
|
+
if (this.consecutiveEmpty === 0) {
|
|
1347
|
+
this.log("Queue empty, waiting for tasks...", "info");
|
|
1348
|
+
}
|
|
1220
1349
|
this.consecutiveEmpty++;
|
|
1221
1350
|
if (this.consecutiveEmpty >= this.maxEmpty)
|
|
1222
1351
|
break;
|
|
@@ -1228,6 +1357,7 @@ class AgentWorker {
|
|
|
1228
1357
|
const result = await this.executeTask(task);
|
|
1229
1358
|
await this.artifactSyncer.sync();
|
|
1230
1359
|
if (result.success) {
|
|
1360
|
+
this.log(`Completed: ${task.title}`, "success");
|
|
1231
1361
|
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
1232
1362
|
status: "VERIFICATION"
|
|
1233
1363
|
});
|
|
@@ -1237,6 +1367,7 @@ class AgentWorker {
|
|
|
1237
1367
|
});
|
|
1238
1368
|
this.tasksCompleted++;
|
|
1239
1369
|
} else {
|
|
1370
|
+
this.log(`Failed: ${task.title} - ${result.summary}`, "error");
|
|
1240
1371
|
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
1241
1372
|
status: "BACKLOG",
|
|
1242
1373
|
assignedTo: null
|
|
@@ -1265,12 +1396,17 @@ if (process.argv[1]?.includes("agent-worker") || process.argv[1]?.includes("work
|
|
|
1265
1396
|
config.apiBase = args[++i];
|
|
1266
1397
|
else if (arg === "--api-key")
|
|
1267
1398
|
config.apiKey = args[++i];
|
|
1268
|
-
else if (arg === "--anthropic-api-key")
|
|
1269
|
-
config.anthropicApiKey = args[++i];
|
|
1270
1399
|
else if (arg === "--project-path")
|
|
1271
1400
|
config.projectPath = args[++i];
|
|
1272
1401
|
else if (arg === "--model")
|
|
1273
1402
|
config.model = args[++i];
|
|
1403
|
+
else if (arg === "--provider") {
|
|
1404
|
+
const value = args[i + 1];
|
|
1405
|
+
if (value && !value.startsWith("--"))
|
|
1406
|
+
i++;
|
|
1407
|
+
config.provider = resolveProvider(value);
|
|
1408
|
+
} else if (arg === "--skip-planning")
|
|
1409
|
+
config.skipPlanning = true;
|
|
1274
1410
|
}
|
|
1275
1411
|
if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
|
|
1276
1412
|
console.error("Missing required arguments");
|
|
@@ -1287,6 +1423,7 @@ if (process.argv[1]?.includes("agent-worker") || process.argv[1]?.includes("work
|
|
|
1287
1423
|
var exports_index_node = {};
|
|
1288
1424
|
__export(exports_index_node, {
|
|
1289
1425
|
getLocusPath: () => getLocusPath,
|
|
1426
|
+
createAiRunner: () => createAiRunner,
|
|
1290
1427
|
c: () => c,
|
|
1291
1428
|
WorkspacesModule: () => WorkspacesModule,
|
|
1292
1429
|
TasksModule: () => TasksModule,
|
|
@@ -1294,6 +1431,7 @@ __export(exports_index_node, {
|
|
|
1294
1431
|
SprintsModule: () => SprintsModule,
|
|
1295
1432
|
SprintPlanner: () => SprintPlanner,
|
|
1296
1433
|
PromptBuilder: () => PromptBuilder,
|
|
1434
|
+
PROVIDER: () => PROVIDER,
|
|
1297
1435
|
OrganizationsModule: () => OrganizationsModule,
|
|
1298
1436
|
LocusEvent: () => LocusEvent,
|
|
1299
1437
|
LocusEmitter: () => LocusEmitter,
|
|
@@ -1302,23 +1440,23 @@ __export(exports_index_node, {
|
|
|
1302
1440
|
InvitationsModule: () => InvitationsModule,
|
|
1303
1441
|
DocsModule: () => DocsModule,
|
|
1304
1442
|
DEFAULT_MODEL: () => DEFAULT_MODEL,
|
|
1443
|
+
CodexRunner: () => CodexRunner,
|
|
1305
1444
|
CodebaseIndexerService: () => CodebaseIndexerService,
|
|
1306
1445
|
CodebaseIndexer: () => CodebaseIndexer,
|
|
1307
1446
|
ClaudeRunner: () => ClaudeRunner,
|
|
1308
1447
|
CiModule: () => CiModule,
|
|
1309
1448
|
AuthModule: () => AuthModule,
|
|
1310
1449
|
ArtifactSyncer: () => ArtifactSyncer,
|
|
1311
|
-
AnthropicClient: () => AnthropicClient,
|
|
1312
1450
|
AgentWorker: () => AgentWorker,
|
|
1313
1451
|
AgentOrchestrator: () => AgentOrchestrator
|
|
1314
1452
|
});
|
|
1315
1453
|
module.exports = __toCommonJS(exports_index_node);
|
|
1316
1454
|
// src/orchestrator.ts
|
|
1317
|
-
var
|
|
1318
|
-
var
|
|
1319
|
-
var
|
|
1455
|
+
var import_node_child_process3 = require("node:child_process");
|
|
1456
|
+
var import_node_fs5 = require("node:fs");
|
|
1457
|
+
var import_node_path6 = require("node:path");
|
|
1320
1458
|
var import_node_url = require("node:url");
|
|
1321
|
-
var
|
|
1459
|
+
var import_shared4 = require("@locusai/shared");
|
|
1322
1460
|
var import_events3 = require("events");
|
|
1323
1461
|
class AgentOrchestrator extends import_events3.EventEmitter {
|
|
1324
1462
|
client;
|
|
@@ -1412,9 +1550,9 @@ ${c.success("✅ Orchestrator finished")}`);
|
|
|
1412
1550
|
`);
|
|
1413
1551
|
const potentialPaths = [];
|
|
1414
1552
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
|
|
1415
|
-
const currentModuleDir =
|
|
1416
|
-
potentialPaths.push(
|
|
1417
|
-
const workerPath = potentialPaths.find((p) =>
|
|
1553
|
+
const currentModuleDir = import_node_path6.dirname(currentModulePath);
|
|
1554
|
+
potentialPaths.push(import_node_path6.join(currentModuleDir, "agent", "worker.js"), import_node_path6.join(currentModuleDir, "worker.js"), import_node_path6.join(currentModuleDir, "agent", "worker.ts"));
|
|
1555
|
+
const workerPath = potentialPaths.find((p) => import_node_fs5.existsSync(p));
|
|
1418
1556
|
if (!workerPath) {
|
|
1419
1557
|
throw new Error(`Worker file not found. Checked: ${potentialPaths.join(", ")}. ` + `Make sure the SDK is properly built and installed.`);
|
|
1420
1558
|
}
|
|
@@ -1430,16 +1568,26 @@ ${c.success("✅ Orchestrator finished")}`);
|
|
|
1430
1568
|
"--project-path",
|
|
1431
1569
|
this.config.projectPath
|
|
1432
1570
|
];
|
|
1433
|
-
if (this.config.anthropicApiKey) {
|
|
1434
|
-
workerArgs.push("--anthropic-api-key", this.config.anthropicApiKey);
|
|
1435
|
-
}
|
|
1436
1571
|
if (this.config.model) {
|
|
1437
1572
|
workerArgs.push("--model", this.config.model);
|
|
1438
1573
|
}
|
|
1574
|
+
if (this.config.provider) {
|
|
1575
|
+
workerArgs.push("--provider", this.config.provider);
|
|
1576
|
+
}
|
|
1577
|
+
if (this.config.skipPlanning) {
|
|
1578
|
+
workerArgs.push("--skip-planning");
|
|
1579
|
+
}
|
|
1439
1580
|
if (this.resolvedSprintId) {
|
|
1440
1581
|
workerArgs.push("--sprint-id", this.resolvedSprintId);
|
|
1441
1582
|
}
|
|
1442
|
-
const agentProcess =
|
|
1583
|
+
const agentProcess = import_node_child_process3.spawn(process.execPath, [workerPath, ...workerArgs], {
|
|
1584
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1585
|
+
env: {
|
|
1586
|
+
...process.env,
|
|
1587
|
+
FORCE_COLOR: "1",
|
|
1588
|
+
TERM: "xterm-256color"
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1443
1591
|
agentState.process = agentProcess;
|
|
1444
1592
|
agentProcess.on("message", (msg) => {
|
|
1445
1593
|
if (msg.type === "stats") {
|
|
@@ -1451,7 +1599,7 @@ ${c.success("✅ Orchestrator finished")}`);
|
|
|
1451
1599
|
process.stdout.write(data.toString());
|
|
1452
1600
|
});
|
|
1453
1601
|
agentProcess.stderr?.on("data", (data) => {
|
|
1454
|
-
process.stderr.write(
|
|
1602
|
+
process.stderr.write(data.toString());
|
|
1455
1603
|
});
|
|
1456
1604
|
agentProcess.on("exit", (code) => {
|
|
1457
1605
|
console.log(`
|
|
@@ -1487,10 +1635,10 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
1487
1635
|
try {
|
|
1488
1636
|
const tasks2 = await this.getAvailableTasks();
|
|
1489
1637
|
const priorityOrder = [
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1638
|
+
import_shared4.TaskPriority.CRITICAL,
|
|
1639
|
+
import_shared4.TaskPriority.HIGH,
|
|
1640
|
+
import_shared4.TaskPriority.MEDIUM,
|
|
1641
|
+
import_shared4.TaskPriority.LOW
|
|
1494
1642
|
];
|
|
1495
1643
|
let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
|
|
1496
1644
|
if (!task && tasks2.length > 0) {
|
|
@@ -1514,7 +1662,7 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
1514
1662
|
async completeTask(taskId, agentId, summary) {
|
|
1515
1663
|
try {
|
|
1516
1664
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
1517
|
-
status:
|
|
1665
|
+
status: import_shared4.TaskStatus.VERIFICATION
|
|
1518
1666
|
});
|
|
1519
1667
|
if (summary) {
|
|
1520
1668
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
@@ -1539,7 +1687,7 @@ ${summary}`
|
|
|
1539
1687
|
async failTask(taskId, agentId, error) {
|
|
1540
1688
|
try {
|
|
1541
1689
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
1542
|
-
status:
|
|
1690
|
+
status: import_shared4.TaskStatus.BACKLOG,
|
|
1543
1691
|
assignedTo: null
|
|
1544
1692
|
});
|
|
1545
1693
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
@@ -1580,6 +1728,6 @@ ${summary}`
|
|
|
1580
1728
|
};
|
|
1581
1729
|
}
|
|
1582
1730
|
sleep(ms) {
|
|
1583
|
-
return new Promise((
|
|
1731
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1584
1732
|
}
|
|
1585
1733
|
}
|