@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.
Files changed (39) hide show
  1. package/dist/agent/document-fetcher.d.ts +23 -0
  2. package/dist/agent/document-fetcher.d.ts.map +1 -0
  3. package/dist/agent/index.d.ts +1 -1
  4. package/dist/agent/index.d.ts.map +1 -1
  5. package/dist/agent/worker.d.ts +1 -1
  6. package/dist/agent/worker.d.ts.map +1 -1
  7. package/dist/agent/worker.js +492 -123
  8. package/dist/ai/claude-runner.d.ts +17 -0
  9. package/dist/ai/claude-runner.d.ts.map +1 -1
  10. package/dist/ai/codex-runner.d.ts +2 -0
  11. package/dist/ai/codex-runner.d.ts.map +1 -1
  12. package/dist/ai/runner.d.ts +7 -0
  13. package/dist/ai/runner.d.ts.map +1 -1
  14. package/dist/core/config.d.ts +10 -0
  15. package/dist/core/config.d.ts.map +1 -1
  16. package/dist/core/index.d.ts +1 -1
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/prompt-builder.d.ts +1 -0
  19. package/dist/core/prompt-builder.d.ts.map +1 -1
  20. package/dist/exec/context-tracker.d.ts +183 -0
  21. package/dist/exec/context-tracker.d.ts.map +1 -0
  22. package/dist/exec/event-emitter.d.ts +117 -0
  23. package/dist/exec/event-emitter.d.ts.map +1 -0
  24. package/dist/exec/events.d.ts +171 -0
  25. package/dist/exec/events.d.ts.map +1 -0
  26. package/dist/exec/exec-session.d.ts +164 -0
  27. package/dist/exec/exec-session.d.ts.map +1 -0
  28. package/dist/exec/history-manager.d.ts +153 -0
  29. package/dist/exec/history-manager.d.ts.map +1 -0
  30. package/dist/exec/index.d.ts +7 -0
  31. package/dist/exec/index.d.ts.map +1 -0
  32. package/dist/exec/types.d.ts +130 -0
  33. package/dist/exec/types.d.ts.map +1 -0
  34. package/dist/index-node.d.ts +1 -0
  35. package/dist/index-node.d.ts.map +1 -1
  36. package/dist/index-node.js +1342 -138
  37. package/package.json +2 -2
  38. package/dist/agent/artifact-syncer.d.ts +0 -18
  39. package/dist/agent/artifact-syncer.d.ts.map +0 -1
@@ -516,8 +516,8 @@ var PROVIDER = {
516
516
  CODEX: "codex"
517
517
  };
518
518
  var DEFAULT_MODEL = {
519
- [PROVIDER.CLAUDE]: "sonnet",
520
- [PROVIDER.CODEX]: "gpt-5.1-codex-mini"
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 import_node_fs3 = require("node:fs");
983
- var import_node_path5 = require("node:path");
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 = import_node_path5.join(projectPath, ".locus", "codebase-index.json");
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 = import_node_path5.join(this.projectPath, ".gitmodules");
1303
+ const gitmodulesPath = import_node_path4.join(this.projectPath, ".gitmodules");
1049
1304
  const submoduleIgnores = [];
1050
- if (import_node_fs3.existsSync(gitmodulesPath)) {
1305
+ if (import_node_fs2.existsSync(gitmodulesPath)) {
1051
1306
  try {
1052
- const content = import_node_fs3.readFileSync(gitmodulesPath, "utf-8");
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 (import_node_fs3.existsSync(this.indexPath)) {
1363
+ if (import_node_fs2.existsSync(this.indexPath)) {
1109
1364
  try {
1110
- return JSON.parse(import_node_fs3.readFileSync(this.indexPath, "utf-8"));
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 = import_node_path5.dirname(this.indexPath);
1119
- if (!import_node_fs3.existsSync(dir)) {
1120
- import_node_fs3.mkdirSync(dir, { recursive: true });
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
- import_node_fs3.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
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 = import_node_fs3.readFileSync(import_node_path5.join(this.projectPath, filePath), "utf-8");
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 import_shared3 = require("@locusai/shared");
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 import_shared3.AssigneeRole.BACKEND:
1876
+ case import_shared2.AssigneeRole.BACKEND:
1508
1877
  return "Backend Engineer";
1509
- case import_shared3.AssigneeRole.FRONTEND:
1878
+ case import_shared2.AssigneeRole.FRONTEND:
1510
1879
  return "Frontend Engineer";
1511
- case import_shared3.AssigneeRole.PM:
1880
+ case import_shared2.AssigneeRole.PM:
1512
1881
  return "Product Manager";
1513
- case import_shared3.AssigneeRole.QA:
1882
+ case import_shared2.AssigneeRole.QA:
1514
1883
  return "QA Engineer";
1515
- case import_shared3.AssigneeRole.DESIGN:
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
- artifactSyncer;
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.artifactSyncer = new ArtifactSyncer({
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.artifactSyncer.sync();
2078
+ await this.documentFetcher.fetch();
1710
2079
  } catch (err) {
1711
- this.log(`Artifact sync failed: ${err}`, "error");
2080
+ this.log(`Document fetch failed: ${err}`, "error");
1712
2081
  }
1713
2082
  if (result.success) {
1714
2083
  this.log(`Completed: ${task.title}`, "success");