@locusai/sdk 0.7.1 → 0.7.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/dist/agent/document-fetcher.d.ts +23 -0
- package/dist/agent/document-fetcher.d.ts.map +1 -0
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/worker.d.ts +1 -1
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +492 -123
- package/dist/ai/claude-runner.d.ts +17 -0
- package/dist/ai/claude-runner.d.ts.map +1 -1
- package/dist/ai/codex-runner.d.ts +2 -0
- package/dist/ai/codex-runner.d.ts.map +1 -1
- package/dist/ai/runner.d.ts +7 -0
- package/dist/ai/runner.d.ts.map +1 -1
- package/dist/core/config.d.ts +10 -0
- 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/core/prompt-builder.d.ts +1 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/exec/context-tracker.d.ts +183 -0
- package/dist/exec/context-tracker.d.ts.map +1 -0
- package/dist/exec/event-emitter.d.ts +117 -0
- package/dist/exec/event-emitter.d.ts.map +1 -0
- package/dist/exec/events.d.ts +171 -0
- package/dist/exec/events.d.ts.map +1 -0
- package/dist/exec/exec-session.d.ts +164 -0
- package/dist/exec/exec-session.d.ts.map +1 -0
- package/dist/exec/history-manager.d.ts +153 -0
- package/dist/exec/history-manager.d.ts.map +1 -0
- package/dist/exec/index.d.ts +7 -0
- package/dist/exec/index.d.ts.map +1 -0
- package/dist/exec/types.d.ts +130 -0
- package/dist/exec/types.d.ts.map +1 -0
- package/dist/index-node.d.ts +1 -0
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +1342 -138
- package/package.json +2 -2
- package/dist/agent/artifact-syncer.d.ts +0 -18
- package/dist/agent/artifact-syncer.d.ts.map +0 -1
package/dist/agent/worker.js
CHANGED
|
@@ -516,8 +516,8 @@ var PROVIDER = {
|
|
|
516
516
|
CODEX: "codex"
|
|
517
517
|
};
|
|
518
518
|
var DEFAULT_MODEL = {
|
|
519
|
-
[PROVIDER.CLAUDE]: "
|
|
520
|
-
[PROVIDER.CODEX]: "gpt-5.
|
|
519
|
+
[PROVIDER.CLAUDE]: "opus",
|
|
520
|
+
[PROVIDER.CODEX]: "gpt-5.2-codex"
|
|
521
521
|
};
|
|
522
522
|
var LOCUS_CONFIG = {
|
|
523
523
|
dir: ".locus",
|
|
@@ -526,14 +526,26 @@ var LOCUS_CONFIG = {
|
|
|
526
526
|
contextFile: "CLAUDE.md",
|
|
527
527
|
artifactsDir: "artifacts",
|
|
528
528
|
documentsDir: "documents",
|
|
529
|
-
agentSkillsDir: ".agent/skills"
|
|
529
|
+
agentSkillsDir: ".agent/skills",
|
|
530
|
+
sessionsDir: "sessions"
|
|
530
531
|
};
|
|
532
|
+
var LOCUS_GITIGNORE_PATTERNS = [
|
|
533
|
+
"# Locus AI - Session data (user-specific, can grow large)",
|
|
534
|
+
".locus/sessions/",
|
|
535
|
+
"",
|
|
536
|
+
"# Locus AI - Artifacts (local-only, user-specific)",
|
|
537
|
+
".locus/artifacts/"
|
|
538
|
+
];
|
|
531
539
|
function getLocusPath(projectPath, fileName) {
|
|
532
540
|
if (fileName === "contextFile") {
|
|
533
541
|
return import_node_path.join(projectPath, LOCUS_CONFIG.contextFile);
|
|
534
542
|
}
|
|
535
543
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
|
|
536
544
|
}
|
|
545
|
+
function getAgentArtifactsPath(projectPath, agentId) {
|
|
546
|
+
const shortId = agentId.slice(-8);
|
|
547
|
+
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
|
|
548
|
+
}
|
|
537
549
|
|
|
538
550
|
// src/ai/claude-runner.ts
|
|
539
551
|
var import_node_child_process = require("node:child_process");
|
|
@@ -606,11 +618,17 @@ class ClaudeRunner {
|
|
|
606
618
|
model;
|
|
607
619
|
log;
|
|
608
620
|
projectPath;
|
|
621
|
+
eventEmitter;
|
|
622
|
+
currentToolName;
|
|
623
|
+
activeTools = new Map;
|
|
609
624
|
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log) {
|
|
610
625
|
this.model = model;
|
|
611
626
|
this.log = log;
|
|
612
627
|
this.projectPath = import_node_path2.resolve(projectPath);
|
|
613
628
|
}
|
|
629
|
+
setEventEmitter(emitter) {
|
|
630
|
+
this.eventEmitter = emitter;
|
|
631
|
+
}
|
|
614
632
|
async run(prompt, _isPlanning = false) {
|
|
615
633
|
const maxRetries = 3;
|
|
616
634
|
let lastError = null;
|
|
@@ -630,6 +648,246 @@ class ClaudeRunner {
|
|
|
630
648
|
}
|
|
631
649
|
throw lastError || new Error("Claude CLI failed after multiple attempts");
|
|
632
650
|
}
|
|
651
|
+
async* runStream(prompt) {
|
|
652
|
+
const args = [
|
|
653
|
+
"--dangerously-skip-permissions",
|
|
654
|
+
"--print",
|
|
655
|
+
"--verbose",
|
|
656
|
+
"--output-format",
|
|
657
|
+
"stream-json",
|
|
658
|
+
"--include-partial-messages",
|
|
659
|
+
"--model",
|
|
660
|
+
this.model
|
|
661
|
+
];
|
|
662
|
+
const env = {
|
|
663
|
+
...process.env,
|
|
664
|
+
FORCE_COLOR: "1",
|
|
665
|
+
TERM: "xterm-256color"
|
|
666
|
+
};
|
|
667
|
+
this.eventEmitter?.emitSessionStarted({
|
|
668
|
+
model: this.model,
|
|
669
|
+
provider: "claude"
|
|
670
|
+
});
|
|
671
|
+
this.eventEmitter?.emitPromptSubmitted(prompt, prompt.length > 500);
|
|
672
|
+
const claude = import_node_child_process.spawn("claude", args, {
|
|
673
|
+
cwd: this.projectPath,
|
|
674
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
675
|
+
env
|
|
676
|
+
});
|
|
677
|
+
let buffer = "";
|
|
678
|
+
let stderrBuffer = "";
|
|
679
|
+
let resolveChunk = null;
|
|
680
|
+
const chunkQueue = [];
|
|
681
|
+
let processEnded = false;
|
|
682
|
+
let errorMessage = "";
|
|
683
|
+
let finalContent = "";
|
|
684
|
+
let isThinking = false;
|
|
685
|
+
const enqueueChunk = (chunk) => {
|
|
686
|
+
this.emitEventForChunk(chunk, isThinking);
|
|
687
|
+
if (chunk.type === "thinking") {
|
|
688
|
+
isThinking = true;
|
|
689
|
+
} else if (chunk.type === "text_delta" || chunk.type === "tool_use") {
|
|
690
|
+
if (isThinking) {
|
|
691
|
+
this.eventEmitter?.emitThinkingStoped();
|
|
692
|
+
isThinking = false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (chunk.type === "text_delta") {
|
|
696
|
+
finalContent += chunk.content;
|
|
697
|
+
}
|
|
698
|
+
if (resolveChunk) {
|
|
699
|
+
const resolve2 = resolveChunk;
|
|
700
|
+
resolveChunk = null;
|
|
701
|
+
resolve2(chunk);
|
|
702
|
+
} else {
|
|
703
|
+
chunkQueue.push(chunk);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const signalEnd = () => {
|
|
707
|
+
processEnded = true;
|
|
708
|
+
if (resolveChunk) {
|
|
709
|
+
resolveChunk(null);
|
|
710
|
+
resolveChunk = null;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
claude.stdout.on("data", (data) => {
|
|
714
|
+
buffer += data.toString();
|
|
715
|
+
const lines = buffer.split(`
|
|
716
|
+
`);
|
|
717
|
+
buffer = lines.pop() || "";
|
|
718
|
+
for (const line of lines) {
|
|
719
|
+
const chunk = this.parseStreamLineToChunk(line);
|
|
720
|
+
if (chunk) {
|
|
721
|
+
enqueueChunk(chunk);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
claude.stderr.on("data", (data) => {
|
|
726
|
+
const chunk = data.toString();
|
|
727
|
+
stderrBuffer += chunk;
|
|
728
|
+
const lines = stderrBuffer.split(`
|
|
729
|
+
`);
|
|
730
|
+
stderrBuffer = lines.pop() || "";
|
|
731
|
+
for (const line of lines) {
|
|
732
|
+
if (!this.shouldSuppressLine(line)) {
|
|
733
|
+
process.stderr.write(`${line}
|
|
734
|
+
`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
claude.on("error", (err) => {
|
|
739
|
+
errorMessage = `Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`;
|
|
740
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, "SPAWN_ERROR");
|
|
741
|
+
signalEnd();
|
|
742
|
+
});
|
|
743
|
+
claude.on("close", (code) => {
|
|
744
|
+
if (stderrBuffer && !this.shouldSuppressLine(stderrBuffer)) {
|
|
745
|
+
process.stderr.write(`${stderrBuffer}
|
|
746
|
+
`);
|
|
747
|
+
}
|
|
748
|
+
if (code !== 0 && !errorMessage) {
|
|
749
|
+
errorMessage = this.createExecutionError(code, stderrBuffer).message;
|
|
750
|
+
this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
|
|
751
|
+
}
|
|
752
|
+
signalEnd();
|
|
753
|
+
});
|
|
754
|
+
claude.stdin.write(prompt);
|
|
755
|
+
claude.stdin.end();
|
|
756
|
+
while (true) {
|
|
757
|
+
if (chunkQueue.length > 0) {
|
|
758
|
+
const chunk = chunkQueue.shift();
|
|
759
|
+
if (chunk)
|
|
760
|
+
yield chunk;
|
|
761
|
+
} else if (processEnded) {
|
|
762
|
+
if (errorMessage) {
|
|
763
|
+
yield { type: "error", error: errorMessage };
|
|
764
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
765
|
+
} else {
|
|
766
|
+
if (finalContent) {
|
|
767
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
768
|
+
}
|
|
769
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
} else {
|
|
773
|
+
const chunk = await new Promise((resolve2) => {
|
|
774
|
+
resolveChunk = resolve2;
|
|
775
|
+
});
|
|
776
|
+
if (chunk === null) {
|
|
777
|
+
if (errorMessage) {
|
|
778
|
+
yield { type: "error", error: errorMessage };
|
|
779
|
+
this.eventEmitter?.emitSessionEnded(false);
|
|
780
|
+
} else {
|
|
781
|
+
if (finalContent) {
|
|
782
|
+
this.eventEmitter?.emitResponseCompleted(finalContent);
|
|
783
|
+
}
|
|
784
|
+
this.eventEmitter?.emitSessionEnded(true);
|
|
785
|
+
}
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
yield chunk;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
emitEventForChunk(chunk, isThinking) {
|
|
793
|
+
if (!this.eventEmitter)
|
|
794
|
+
return;
|
|
795
|
+
switch (chunk.type) {
|
|
796
|
+
case "text_delta":
|
|
797
|
+
this.eventEmitter.emitTextDelta(chunk.content);
|
|
798
|
+
break;
|
|
799
|
+
case "tool_use":
|
|
800
|
+
if (this.currentToolName) {
|
|
801
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
802
|
+
}
|
|
803
|
+
this.currentToolName = chunk.tool;
|
|
804
|
+
this.eventEmitter.emitToolStarted(chunk.tool, chunk.id);
|
|
805
|
+
break;
|
|
806
|
+
case "thinking":
|
|
807
|
+
if (!isThinking) {
|
|
808
|
+
this.eventEmitter.emitThinkingStarted(chunk.content);
|
|
809
|
+
}
|
|
810
|
+
break;
|
|
811
|
+
case "result":
|
|
812
|
+
if (this.currentToolName) {
|
|
813
|
+
this.eventEmitter.emitToolCompleted(this.currentToolName);
|
|
814
|
+
this.currentToolName = undefined;
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
case "error":
|
|
818
|
+
this.eventEmitter.emitErrorOccurred(chunk.error);
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
parseStreamLineToChunk(line) {
|
|
823
|
+
if (!line.trim())
|
|
824
|
+
return null;
|
|
825
|
+
try {
|
|
826
|
+
const item = JSON.parse(line);
|
|
827
|
+
return this.processStreamItemToChunk(item);
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
processStreamItemToChunk(item) {
|
|
833
|
+
if (item.type === "result") {
|
|
834
|
+
return { type: "result", content: item.result || "" };
|
|
835
|
+
}
|
|
836
|
+
if (item.type === "stream_event" && item.event) {
|
|
837
|
+
return this.handleEventToChunk(item.event);
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
handleEventToChunk(event) {
|
|
842
|
+
const { type, delta, content_block, index } = event;
|
|
843
|
+
if (type === "content_block_delta" && delta?.type === "text_delta") {
|
|
844
|
+
return { type: "text_delta", content: delta.text || "" };
|
|
845
|
+
}
|
|
846
|
+
if (type === "content_block_delta" && delta?.type === "input_json_delta" && delta.partial_json !== undefined && index !== undefined) {
|
|
847
|
+
const activeTool = this.activeTools.get(index);
|
|
848
|
+
if (activeTool) {
|
|
849
|
+
activeTool.parameterJson += delta.partial_json;
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
if (type === "content_block_start" && content_block) {
|
|
854
|
+
if (content_block.type === "tool_use" && content_block.name) {
|
|
855
|
+
if (index !== undefined) {
|
|
856
|
+
this.activeTools.set(index, {
|
|
857
|
+
name: content_block.name,
|
|
858
|
+
id: content_block.id,
|
|
859
|
+
index,
|
|
860
|
+
parameterJson: "",
|
|
861
|
+
startTime: Date.now()
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
type: "tool_use",
|
|
866
|
+
tool: content_block.name,
|
|
867
|
+
id: content_block.id
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (content_block.type === "thinking") {
|
|
871
|
+
return { type: "thinking" };
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (type === "content_block_stop" && index !== undefined) {
|
|
875
|
+
const activeTool = this.activeTools.get(index);
|
|
876
|
+
if (activeTool?.parameterJson) {
|
|
877
|
+
try {
|
|
878
|
+
const parameters = JSON.parse(activeTool.parameterJson);
|
|
879
|
+
return {
|
|
880
|
+
type: "tool_parameters",
|
|
881
|
+
tool: activeTool.name,
|
|
882
|
+
id: activeTool.id,
|
|
883
|
+
parameters
|
|
884
|
+
};
|
|
885
|
+
} catch {}
|
|
886
|
+
}
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
633
891
|
executeRun(prompt) {
|
|
634
892
|
return new Promise((resolve2, reject) => {
|
|
635
893
|
const args = [
|
|
@@ -725,7 +983,6 @@ class ClaudeRunner {
|
|
|
725
983
|
if (type === "content_block_start" && content_block) {
|
|
726
984
|
if (content_block.type === "tool_use" && content_block.name) {
|
|
727
985
|
this.log?.(`
|
|
728
|
-
|
|
729
986
|
${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
730
987
|
`, "info");
|
|
731
988
|
}
|
|
@@ -774,6 +1031,99 @@ class CodexRunner {
|
|
|
774
1031
|
}
|
|
775
1032
|
throw lastError || new Error("Codex CLI failed after multiple attempts");
|
|
776
1033
|
}
|
|
1034
|
+
async* runStream(prompt) {
|
|
1035
|
+
const outputPath = import_node_path3.join(import_node_os.tmpdir(), `locus-codex-${import_node_crypto.randomUUID()}.txt`);
|
|
1036
|
+
const args = this.buildArgs(outputPath);
|
|
1037
|
+
const codex = import_node_child_process2.spawn("codex", args, {
|
|
1038
|
+
cwd: this.projectPath,
|
|
1039
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1040
|
+
env: process.env,
|
|
1041
|
+
shell: false
|
|
1042
|
+
});
|
|
1043
|
+
let resolveChunk = null;
|
|
1044
|
+
const chunkQueue = [];
|
|
1045
|
+
let processEnded = false;
|
|
1046
|
+
let errorMessage = "";
|
|
1047
|
+
let finalOutput = "";
|
|
1048
|
+
const enqueueChunk = (chunk) => {
|
|
1049
|
+
if (resolveChunk) {
|
|
1050
|
+
const resolve2 = resolveChunk;
|
|
1051
|
+
resolveChunk = null;
|
|
1052
|
+
resolve2(chunk);
|
|
1053
|
+
} else {
|
|
1054
|
+
chunkQueue.push(chunk);
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
const signalEnd = () => {
|
|
1058
|
+
processEnded = true;
|
|
1059
|
+
if (resolveChunk) {
|
|
1060
|
+
resolveChunk(null);
|
|
1061
|
+
resolveChunk = null;
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
const processOutput = (data) => {
|
|
1065
|
+
const msg = data.toString();
|
|
1066
|
+
finalOutput += msg;
|
|
1067
|
+
for (const rawLine of msg.split(`
|
|
1068
|
+
`)) {
|
|
1069
|
+
const line = rawLine.trim();
|
|
1070
|
+
if (!line)
|
|
1071
|
+
continue;
|
|
1072
|
+
if (/^thinking\b/i.test(line)) {
|
|
1073
|
+
enqueueChunk({ type: "thinking", content: line });
|
|
1074
|
+
} else if (/^[→•✓]/.test(line) || /^Plan update\b/.test(line)) {
|
|
1075
|
+
enqueueChunk({
|
|
1076
|
+
type: "tool_use",
|
|
1077
|
+
tool: line.replace(/^[→•✓]\s*/, "")
|
|
1078
|
+
});
|
|
1079
|
+
} else if (this.shouldDisplay(line)) {
|
|
1080
|
+
enqueueChunk({ type: "text_delta", content: `${line}
|
|
1081
|
+
` });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
codex.stdout.on("data", processOutput);
|
|
1086
|
+
codex.stderr.on("data", processOutput);
|
|
1087
|
+
codex.on("error", (err) => {
|
|
1088
|
+
errorMessage = `Failed to start Codex CLI: ${err.message}. Ensure 'codex' is installed and available in PATH.`;
|
|
1089
|
+
signalEnd();
|
|
1090
|
+
});
|
|
1091
|
+
codex.on("close", (code) => {
|
|
1092
|
+
this.cleanupTempFile(outputPath);
|
|
1093
|
+
if (code === 0) {
|
|
1094
|
+
const result = this.readOutput(outputPath, finalOutput);
|
|
1095
|
+
enqueueChunk({ type: "result", content: result });
|
|
1096
|
+
} else if (!errorMessage) {
|
|
1097
|
+
errorMessage = this.createErrorFromOutput(code, finalOutput).message;
|
|
1098
|
+
}
|
|
1099
|
+
signalEnd();
|
|
1100
|
+
});
|
|
1101
|
+
codex.stdin.write(prompt);
|
|
1102
|
+
codex.stdin.end();
|
|
1103
|
+
while (true) {
|
|
1104
|
+
if (chunkQueue.length > 0) {
|
|
1105
|
+
const chunk = chunkQueue.shift();
|
|
1106
|
+
if (chunk)
|
|
1107
|
+
yield chunk;
|
|
1108
|
+
} else if (processEnded) {
|
|
1109
|
+
if (errorMessage) {
|
|
1110
|
+
yield { type: "error", error: errorMessage };
|
|
1111
|
+
}
|
|
1112
|
+
break;
|
|
1113
|
+
} else {
|
|
1114
|
+
const chunk = await new Promise((resolve2) => {
|
|
1115
|
+
resolveChunk = resolve2;
|
|
1116
|
+
});
|
|
1117
|
+
if (chunk === null) {
|
|
1118
|
+
if (errorMessage) {
|
|
1119
|
+
yield { type: "error", error: errorMessage };
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
yield chunk;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
777
1127
|
executeRun(prompt) {
|
|
778
1128
|
return new Promise((resolve2, reject) => {
|
|
779
1129
|
const outputPath = import_node_path3.join(import_node_os.tmpdir(), `locus-codex-${import_node_crypto.randomUUID()}.txt`);
|
|
@@ -882,105 +1232,10 @@ function createAiRunner(provider, config) {
|
|
|
882
1232
|
}
|
|
883
1233
|
}
|
|
884
1234
|
|
|
885
|
-
// src/agent/artifact-syncer.ts
|
|
886
|
-
var import_node_fs2 = require("node:fs");
|
|
887
|
-
var import_node_path4 = require("node:path");
|
|
888
|
-
var import_shared2 = require("@locusai/shared");
|
|
889
|
-
class ArtifactSyncer {
|
|
890
|
-
deps;
|
|
891
|
-
constructor(deps) {
|
|
892
|
-
this.deps = deps;
|
|
893
|
-
}
|
|
894
|
-
async getOrCreateArtifactsGroup() {
|
|
895
|
-
try {
|
|
896
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
897
|
-
const artifactsGroup = groups.find((g) => g.name === "Artifacts");
|
|
898
|
-
if (artifactsGroup) {
|
|
899
|
-
return artifactsGroup.id;
|
|
900
|
-
}
|
|
901
|
-
const newGroup = await this.deps.client.docs.createGroup(this.deps.workspaceId, {
|
|
902
|
-
name: "Artifacts",
|
|
903
|
-
order: 999
|
|
904
|
-
});
|
|
905
|
-
this.deps.log("Created 'Artifacts' group for agent-generated docs", "info");
|
|
906
|
-
return newGroup.id;
|
|
907
|
-
} catch (error) {
|
|
908
|
-
this.deps.log(`Failed to get/create Artifacts group: ${error}`, "error");
|
|
909
|
-
throw error;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
async sync() {
|
|
913
|
-
const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
|
|
914
|
-
if (!import_node_fs2.existsSync(artifactsDir)) {
|
|
915
|
-
import_node_fs2.mkdirSync(artifactsDir, { recursive: true });
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
try {
|
|
919
|
-
const files = import_node_fs2.readdirSync(artifactsDir);
|
|
920
|
-
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
921
|
-
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
922
|
-
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
923
|
-
let artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
924
|
-
if (!artifactsGroupId) {
|
|
925
|
-
artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
926
|
-
}
|
|
927
|
-
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
928
|
-
for (const file of files) {
|
|
929
|
-
const filePath = import_node_path4.join(artifactsDir, file);
|
|
930
|
-
if (import_node_fs2.statSync(filePath).isFile()) {
|
|
931
|
-
const content = import_node_fs2.readFileSync(filePath, "utf-8");
|
|
932
|
-
const title = file.replace(/\.md$/, "").trim();
|
|
933
|
-
if (!title)
|
|
934
|
-
continue;
|
|
935
|
-
const existing = existingDocs.find((d) => d.title === title);
|
|
936
|
-
if (existing) {
|
|
937
|
-
if (existing.content !== content || existing.groupId !== artifactsGroupId) {
|
|
938
|
-
await this.deps.client.docs.update(existing.id, this.deps.workspaceId, { content, groupId: artifactsGroupId });
|
|
939
|
-
this.deps.log(`Updated artifact: ${file}`, "success");
|
|
940
|
-
}
|
|
941
|
-
} else {
|
|
942
|
-
await this.deps.client.docs.create(this.deps.workspaceId, {
|
|
943
|
-
title,
|
|
944
|
-
content,
|
|
945
|
-
groupId: artifactsGroupId,
|
|
946
|
-
type: import_shared2.DocType.GENERAL
|
|
947
|
-
});
|
|
948
|
-
this.deps.log(`Created artifact: ${file}`, "success");
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
for (const doc of existingDocs) {
|
|
953
|
-
if (doc.groupId === artifactsGroupId) {
|
|
954
|
-
const fileName = `${doc.title}.md`;
|
|
955
|
-
const filePath = import_node_path4.join(artifactsDir, fileName);
|
|
956
|
-
if (!import_node_fs2.existsSync(filePath)) {
|
|
957
|
-
import_node_fs2.writeFileSync(filePath, doc.content || "");
|
|
958
|
-
this.deps.log(`Fetched artifact: ${fileName}`, "success");
|
|
959
|
-
}
|
|
960
|
-
} else {
|
|
961
|
-
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
962
|
-
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
963
|
-
const groupDir = import_node_path4.join(documentsDir, groupName);
|
|
964
|
-
if (!import_node_fs2.existsSync(groupDir)) {
|
|
965
|
-
import_node_fs2.mkdirSync(groupDir, { recursive: true });
|
|
966
|
-
}
|
|
967
|
-
const fileName = `${doc.title}.md`;
|
|
968
|
-
const filePath = import_node_path4.join(groupDir, fileName);
|
|
969
|
-
if (!import_node_fs2.existsSync(filePath) || import_node_fs2.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
970
|
-
import_node_fs2.writeFileSync(filePath, doc.content || "");
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
} catch (error) {
|
|
975
|
-
this.deps.log(`Failed to sync artifacts: ${error}`, "error");
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
1235
|
// src/core/indexer.ts
|
|
981
1236
|
var import_node_crypto2 = require("node:crypto");
|
|
982
|
-
var
|
|
983
|
-
var
|
|
1237
|
+
var import_node_fs2 = require("node:fs");
|
|
1238
|
+
var import_node_path4 = require("node:path");
|
|
984
1239
|
var import_globby = require("globby");
|
|
985
1240
|
|
|
986
1241
|
class CodebaseIndexer {
|
|
@@ -989,7 +1244,7 @@ class CodebaseIndexer {
|
|
|
989
1244
|
fullReindexRatioThreshold = 0.2;
|
|
990
1245
|
constructor(projectPath) {
|
|
991
1246
|
this.projectPath = projectPath;
|
|
992
|
-
this.indexPath =
|
|
1247
|
+
this.indexPath = import_node_path4.join(projectPath, ".locus", "codebase-index.json");
|
|
993
1248
|
}
|
|
994
1249
|
async index(onProgress, treeSummarizer, force = false) {
|
|
995
1250
|
if (!treeSummarizer) {
|
|
@@ -1045,11 +1300,11 @@ class CodebaseIndexer {
|
|
|
1045
1300
|
}
|
|
1046
1301
|
}
|
|
1047
1302
|
async getFileTree() {
|
|
1048
|
-
const gitmodulesPath =
|
|
1303
|
+
const gitmodulesPath = import_node_path4.join(this.projectPath, ".gitmodules");
|
|
1049
1304
|
const submoduleIgnores = [];
|
|
1050
|
-
if (
|
|
1305
|
+
if (import_node_fs2.existsSync(gitmodulesPath)) {
|
|
1051
1306
|
try {
|
|
1052
|
-
const content =
|
|
1307
|
+
const content = import_node_fs2.readFileSync(gitmodulesPath, "utf-8");
|
|
1053
1308
|
const lines = content.split(`
|
|
1054
1309
|
`);
|
|
1055
1310
|
for (const line of lines) {
|
|
@@ -1105,9 +1360,9 @@ class CodebaseIndexer {
|
|
|
1105
1360
|
});
|
|
1106
1361
|
}
|
|
1107
1362
|
loadIndex() {
|
|
1108
|
-
if (
|
|
1363
|
+
if (import_node_fs2.existsSync(this.indexPath)) {
|
|
1109
1364
|
try {
|
|
1110
|
-
return JSON.parse(
|
|
1365
|
+
return JSON.parse(import_node_fs2.readFileSync(this.indexPath, "utf-8"));
|
|
1111
1366
|
} catch {
|
|
1112
1367
|
return null;
|
|
1113
1368
|
}
|
|
@@ -1115,11 +1370,11 @@ class CodebaseIndexer {
|
|
|
1115
1370
|
return null;
|
|
1116
1371
|
}
|
|
1117
1372
|
saveIndex(index) {
|
|
1118
|
-
const dir =
|
|
1119
|
-
if (!
|
|
1120
|
-
|
|
1373
|
+
const dir = import_node_path4.dirname(this.indexPath);
|
|
1374
|
+
if (!import_node_fs2.existsSync(dir)) {
|
|
1375
|
+
import_node_fs2.mkdirSync(dir, { recursive: true });
|
|
1121
1376
|
}
|
|
1122
|
-
|
|
1377
|
+
import_node_fs2.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
1123
1378
|
}
|
|
1124
1379
|
cloneIndex(index) {
|
|
1125
1380
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -1135,7 +1390,7 @@ class CodebaseIndexer {
|
|
|
1135
1390
|
}
|
|
1136
1391
|
hashFile(filePath) {
|
|
1137
1392
|
try {
|
|
1138
|
-
const content =
|
|
1393
|
+
const content = import_node_fs2.readFileSync(import_node_path4.join(this.projectPath, filePath), "utf-8");
|
|
1139
1394
|
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1140
1395
|
} catch {
|
|
1141
1396
|
return null;
|
|
@@ -1239,11 +1494,55 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
1239
1494
|
}
|
|
1240
1495
|
}
|
|
1241
1496
|
|
|
1497
|
+
// src/agent/document-fetcher.ts
|
|
1498
|
+
var import_node_fs3 = require("node:fs");
|
|
1499
|
+
var import_node_path5 = require("node:path");
|
|
1500
|
+
class DocumentFetcher {
|
|
1501
|
+
deps;
|
|
1502
|
+
constructor(deps) {
|
|
1503
|
+
this.deps = deps;
|
|
1504
|
+
}
|
|
1505
|
+
async fetch() {
|
|
1506
|
+
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
1507
|
+
if (!import_node_fs3.existsSync(documentsDir)) {
|
|
1508
|
+
import_node_fs3.mkdirSync(documentsDir, { recursive: true });
|
|
1509
|
+
}
|
|
1510
|
+
try {
|
|
1511
|
+
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
1512
|
+
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
1513
|
+
const docs2 = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
1514
|
+
const artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
1515
|
+
let fetchedCount = 0;
|
|
1516
|
+
for (const doc of docs2) {
|
|
1517
|
+
if (doc.groupId === artifactsGroupId) {
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
1521
|
+
const groupDir = import_node_path5.join(documentsDir, groupName);
|
|
1522
|
+
if (!import_node_fs3.existsSync(groupDir)) {
|
|
1523
|
+
import_node_fs3.mkdirSync(groupDir, { recursive: true });
|
|
1524
|
+
}
|
|
1525
|
+
const fileName = `${doc.title}.md`;
|
|
1526
|
+
const filePath = import_node_path5.join(groupDir, fileName);
|
|
1527
|
+
if (!import_node_fs3.existsSync(filePath) || import_node_fs3.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
1528
|
+
import_node_fs3.writeFileSync(filePath, doc.content || "");
|
|
1529
|
+
fetchedCount++;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (fetchedCount > 0) {
|
|
1533
|
+
this.deps.log(`Fetched ${fetchedCount} document(s) from server`, "info");
|
|
1534
|
+
}
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
this.deps.log(`Failed to fetch documents: ${error}`, "error");
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1242
1541
|
// src/core/prompt-builder.ts
|
|
1243
1542
|
var import_node_fs4 = require("node:fs");
|
|
1244
1543
|
var import_node_os2 = require("node:os");
|
|
1245
1544
|
var import_node_path6 = require("node:path");
|
|
1246
|
-
var
|
|
1545
|
+
var import_shared2 = require("@locusai/shared");
|
|
1247
1546
|
class PromptBuilder {
|
|
1248
1547
|
projectPath;
|
|
1249
1548
|
constructor(projectPath) {
|
|
@@ -1332,7 +1631,7 @@ ${serverContext.context}
|
|
|
1332
1631
|
`;
|
|
1333
1632
|
prompt += `You have access to the following documentation directories for context:
|
|
1334
1633
|
`;
|
|
1335
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
1634
|
+
prompt += `- Artifacts: \`.locus/artifacts\`)
|
|
1336
1635
|
`;
|
|
1337
1636
|
prompt += `- Documents: \`.locus/documents\`
|
|
1338
1637
|
`;
|
|
@@ -1394,6 +1693,76 @@ ${comment.text}
|
|
|
1394
1693
|
2. **Artifact Management**: If you create any high-level documentation (PRDs, technical drafts, architecture docs), you MUST save them in \`.locus/artifacts/\`. Do NOT create them in the root directory.
|
|
1395
1694
|
3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
1396
1695
|
4. When finished successfully, output: <promise>COMPLETE</promise>
|
|
1696
|
+
`;
|
|
1697
|
+
return prompt;
|
|
1698
|
+
}
|
|
1699
|
+
async buildGenericPrompt(query) {
|
|
1700
|
+
let prompt = `# Direct Execution
|
|
1701
|
+
|
|
1702
|
+
`;
|
|
1703
|
+
prompt += `## Prompt
|
|
1704
|
+
${query}
|
|
1705
|
+
|
|
1706
|
+
`;
|
|
1707
|
+
const projectConfig = this.getProjectConfig();
|
|
1708
|
+
if (projectConfig) {
|
|
1709
|
+
prompt += `## Project Metadata
|
|
1710
|
+
`;
|
|
1711
|
+
prompt += `- Version: ${projectConfig.version || "Unknown"}
|
|
1712
|
+
`;
|
|
1713
|
+
prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
|
|
1714
|
+
|
|
1715
|
+
`;
|
|
1716
|
+
}
|
|
1717
|
+
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
1718
|
+
let hasLocalContext = false;
|
|
1719
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
1720
|
+
try {
|
|
1721
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
1722
|
+
if (context.trim().length > 20) {
|
|
1723
|
+
prompt += `## Project Context (Local)
|
|
1724
|
+
${context}
|
|
1725
|
+
|
|
1726
|
+
`;
|
|
1727
|
+
hasLocalContext = true;
|
|
1728
|
+
}
|
|
1729
|
+
} catch (err) {
|
|
1730
|
+
console.warn(`Warning: Could not read context file: ${err}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (!hasLocalContext) {
|
|
1734
|
+
const fallback = this.getFallbackContext();
|
|
1735
|
+
if (fallback) {
|
|
1736
|
+
prompt += `## Project Context (README Fallback)
|
|
1737
|
+
${fallback}
|
|
1738
|
+
|
|
1739
|
+
`;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
prompt += this.getProjectStructure();
|
|
1743
|
+
prompt += this.getSkillsInfo();
|
|
1744
|
+
prompt += `## Project Knowledge Base
|
|
1745
|
+
`;
|
|
1746
|
+
prompt += `You have access to the following documentation directories for context:
|
|
1747
|
+
`;
|
|
1748
|
+
prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
1749
|
+
`;
|
|
1750
|
+
prompt += `- Documents: \`.locus/documents\` (synced from cloud)
|
|
1751
|
+
`;
|
|
1752
|
+
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
1753
|
+
|
|
1754
|
+
`;
|
|
1755
|
+
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
1756
|
+
if (import_node_fs4.existsSync(indexPath)) {
|
|
1757
|
+
prompt += `## Codebase Overview
|
|
1758
|
+
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
1759
|
+
|
|
1760
|
+
`;
|
|
1761
|
+
}
|
|
1762
|
+
prompt += `## Instructions
|
|
1763
|
+
1. Execute the prompt based on the provided project context.
|
|
1764
|
+
2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
1765
|
+
3. When finished successfully, output: <promise>COMPLETE</promise>
|
|
1397
1766
|
`;
|
|
1398
1767
|
return prompt;
|
|
1399
1768
|
}
|
|
@@ -1504,15 +1873,15 @@ ${comment.text}
|
|
|
1504
1873
|
return null;
|
|
1505
1874
|
}
|
|
1506
1875
|
switch (role) {
|
|
1507
|
-
case
|
|
1876
|
+
case import_shared2.AssigneeRole.BACKEND:
|
|
1508
1877
|
return "Backend Engineer";
|
|
1509
|
-
case
|
|
1878
|
+
case import_shared2.AssigneeRole.FRONTEND:
|
|
1510
1879
|
return "Frontend Engineer";
|
|
1511
|
-
case
|
|
1880
|
+
case import_shared2.AssigneeRole.PM:
|
|
1512
1881
|
return "Product Manager";
|
|
1513
|
-
case
|
|
1882
|
+
case import_shared2.AssigneeRole.QA:
|
|
1514
1883
|
return "QA Engineer";
|
|
1515
|
-
case
|
|
1884
|
+
case import_shared2.AssigneeRole.DESIGN:
|
|
1516
1885
|
return "Product Designer";
|
|
1517
1886
|
default:
|
|
1518
1887
|
return "engineer";
|
|
@@ -1588,7 +1957,7 @@ class AgentWorker {
|
|
|
1588
1957
|
client;
|
|
1589
1958
|
aiRunner;
|
|
1590
1959
|
indexerService;
|
|
1591
|
-
|
|
1960
|
+
documentFetcher;
|
|
1592
1961
|
taskExecutor;
|
|
1593
1962
|
consecutiveEmpty = 0;
|
|
1594
1963
|
maxEmpty = 60;
|
|
@@ -1620,7 +1989,7 @@ class AgentWorker {
|
|
|
1620
1989
|
projectPath,
|
|
1621
1990
|
log
|
|
1622
1991
|
});
|
|
1623
|
-
this.
|
|
1992
|
+
this.documentFetcher = new DocumentFetcher({
|
|
1624
1993
|
client: this.client,
|
|
1625
1994
|
workspaceId: config.workspaceId,
|
|
1626
1995
|
projectPath,
|
|
@@ -1706,9 +2075,9 @@ class AgentWorker {
|
|
|
1706
2075
|
this.log(`Claimed: ${task.title}`, "success");
|
|
1707
2076
|
const result = await this.executeTask(task);
|
|
1708
2077
|
try {
|
|
1709
|
-
await this.
|
|
2078
|
+
await this.documentFetcher.fetch();
|
|
1710
2079
|
} catch (err) {
|
|
1711
|
-
this.log(`
|
|
2080
|
+
this.log(`Document fetch failed: ${err}`, "error");
|
|
1712
2081
|
}
|
|
1713
2082
|
if (result.success) {
|
|
1714
2083
|
this.log(`Completed: ${task.title}`, "success");
|