@locusai/sdk 0.5.0 → 0.6.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.map +1 -1
- package/dist/agent/codebase-indexer-service.d.ts +1 -1
- package/dist/agent/codebase-indexer-service.d.ts.map +1 -1
- package/dist/agent/index.d.ts +0 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/task-executor.d.ts +1 -3
- package/dist/agent/task-executor.d.ts.map +1 -1
- package/dist/agent/worker.d.ts +0 -2
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +427 -131
- package/dist/ai/claude-runner.d.ts.map +1 -1
- package/dist/ai/codex-runner.d.ts.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/indexer.d.ts +15 -2
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/prompt-builder.d.ts +9 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/index-node.d.ts +1 -0
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +433 -137
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -2
- package/dist/modules/ai.d.ts +32 -0
- package/dist/modules/ai.d.ts.map +1 -0
- package/dist/modules/sprints.d.ts +1 -0
- package/dist/modules/sprints.d.ts.map +1 -1
- package/dist/modules/tasks.d.ts +6 -0
- package/dist/modules/tasks.d.ts.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/agent/sprint-planner.d.ts +0 -16
- package/dist/agent/sprint-planner.d.ts.map +0 -1
package/dist/index-node.js
CHANGED
|
@@ -52,7 +52,8 @@ __export(exports_src, {
|
|
|
52
52
|
InvitationsModule: () => InvitationsModule,
|
|
53
53
|
DocsModule: () => DocsModule,
|
|
54
54
|
CiModule: () => CiModule,
|
|
55
|
-
AuthModule: () => AuthModule
|
|
55
|
+
AuthModule: () => AuthModule,
|
|
56
|
+
AIModule: () => AIModule
|
|
56
57
|
});
|
|
57
58
|
module.exports = __toCommonJS(exports_src);
|
|
58
59
|
var import_axios = __toESM(require("axios"));
|
|
@@ -85,6 +86,28 @@ class BaseModule {
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
// src/modules/ai.ts
|
|
90
|
+
class AIModule extends BaseModule {
|
|
91
|
+
async chat(workspaceId, body) {
|
|
92
|
+
const { data } = await this.api.post(`/ai/${workspaceId}/chat`, body);
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
async listSessions(workspaceId) {
|
|
96
|
+
const { data } = await this.api.get(`/ai/${workspaceId}/sessions`);
|
|
97
|
+
return data;
|
|
98
|
+
}
|
|
99
|
+
async getSession(workspaceId, sessionId) {
|
|
100
|
+
const { data } = await this.api.get(`/ai/${workspaceId}/session/${sessionId}`);
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
getChatStreamUrl(workspaceId, sessionId) {
|
|
104
|
+
return `${this.api.defaults.baseURL}/ai/${workspaceId}/chat/stream?sessionId=${sessionId}`;
|
|
105
|
+
}
|
|
106
|
+
async deleteSession(workspaceId, sessionId) {
|
|
107
|
+
await this.api.delete(`/ai/${workspaceId}/session/${sessionId}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
88
111
|
// src/modules/auth.ts
|
|
89
112
|
class AuthModule extends BaseModule {
|
|
90
113
|
async getMe() {
|
|
@@ -248,6 +271,10 @@ class SprintsModule extends BaseModule {
|
|
|
248
271
|
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
|
|
249
272
|
return data.sprint;
|
|
250
273
|
}
|
|
274
|
+
async triggerAIPlanning(id, workspaceId) {
|
|
275
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/trigger-ai-planning`);
|
|
276
|
+
return data.sprint;
|
|
277
|
+
}
|
|
251
278
|
}
|
|
252
279
|
|
|
253
280
|
// src/modules/tasks.ts
|
|
@@ -294,6 +321,16 @@ class TasksModule extends BaseModule {
|
|
|
294
321
|
const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
|
|
295
322
|
return data.comment;
|
|
296
323
|
}
|
|
324
|
+
async batchUpdate(ids, workspaceId, updates) {
|
|
325
|
+
await this.api.patch(`/workspaces/${workspaceId}/tasks/batch`, {
|
|
326
|
+
ids,
|
|
327
|
+
updates
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async getContext(id, workspaceId) {
|
|
331
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}/context`);
|
|
332
|
+
return data;
|
|
333
|
+
}
|
|
297
334
|
}
|
|
298
335
|
|
|
299
336
|
// src/modules/workspaces.ts
|
|
@@ -347,6 +384,7 @@ class LocusClient {
|
|
|
347
384
|
api;
|
|
348
385
|
emitter;
|
|
349
386
|
auth;
|
|
387
|
+
ai;
|
|
350
388
|
tasks;
|
|
351
389
|
sprints;
|
|
352
390
|
workspaces;
|
|
@@ -358,7 +396,7 @@ class LocusClient {
|
|
|
358
396
|
this.emitter = new LocusEmitter;
|
|
359
397
|
this.api = import_axios.default.create({
|
|
360
398
|
baseURL: config.baseUrl,
|
|
361
|
-
timeout: config.timeout ||
|
|
399
|
+
timeout: config.timeout || 60000,
|
|
362
400
|
headers: {
|
|
363
401
|
"Content-Type": "application/json",
|
|
364
402
|
...config.token ? { Authorization: `Bearer ${config.token}` } : {}
|
|
@@ -366,6 +404,7 @@ class LocusClient {
|
|
|
366
404
|
});
|
|
367
405
|
this.setupInterceptors();
|
|
368
406
|
this.auth = new AuthModule(this.api, this.emitter);
|
|
407
|
+
this.ai = new AIModule(this.api, this.emitter);
|
|
369
408
|
this.tasks = new TasksModule(this.api, this.emitter);
|
|
370
409
|
this.sprints = new SprintsModule(this.api, this.emitter);
|
|
371
410
|
this.workspaces = new WorkspacesModule(this.api, this.emitter);
|
|
@@ -439,7 +478,6 @@ __export(exports_worker, {
|
|
|
439
478
|
AgentWorker: () => AgentWorker
|
|
440
479
|
});
|
|
441
480
|
module.exports = __toCommonJS(exports_worker);
|
|
442
|
-
var import_shared3 = require("@locusai/shared");
|
|
443
481
|
|
|
444
482
|
// src/core/config.ts
|
|
445
483
|
var import_node_path = require("node:path");
|
|
@@ -456,7 +494,9 @@ var LOCUS_CONFIG = {
|
|
|
456
494
|
configFile: "config.json",
|
|
457
495
|
indexFile: "codebase-index.json",
|
|
458
496
|
contextFile: "CLAUDE.md",
|
|
459
|
-
artifactsDir: "artifacts"
|
|
497
|
+
artifactsDir: "artifacts",
|
|
498
|
+
documentsDir: "documents",
|
|
499
|
+
agentSkillsDir: ".agent/skills"
|
|
460
500
|
};
|
|
461
501
|
function getLocusPath(projectPath, fileName) {
|
|
462
502
|
if (fileName === "contextFile") {
|
|
@@ -623,12 +663,8 @@ class ClaudeRunner {
|
|
|
623
663
|
return null;
|
|
624
664
|
}
|
|
625
665
|
handleEvent(event) {
|
|
626
|
-
const { type,
|
|
627
|
-
if (type === "
|
|
628
|
-
if (delta.type === "text_delta" && delta.text) {
|
|
629
|
-
this.log?.(delta.text, "info");
|
|
630
|
-
}
|
|
631
|
-
} else if (type === "content_block_start" && content_block) {
|
|
666
|
+
const { type, content_block } = event;
|
|
667
|
+
if (type === "content_block_start" && content_block) {
|
|
632
668
|
if (content_block.type === "tool_use" && content_block.name) {
|
|
633
669
|
this.log?.(`
|
|
634
670
|
|
|
@@ -715,7 +751,13 @@ class CodexRunner {
|
|
|
715
751
|
});
|
|
716
752
|
}
|
|
717
753
|
buildArgs(outputPath) {
|
|
718
|
-
const args = [
|
|
754
|
+
const args = [
|
|
755
|
+
"exec",
|
|
756
|
+
"--full-auto",
|
|
757
|
+
"--skip-git-repo-check",
|
|
758
|
+
"--output-last-message",
|
|
759
|
+
outputPath
|
|
760
|
+
];
|
|
719
761
|
if (this.model) {
|
|
720
762
|
args.push("--model", this.model);
|
|
721
763
|
}
|
|
@@ -781,6 +823,7 @@ function createAiRunner(provider, config) {
|
|
|
781
823
|
// src/agent/artifact-syncer.ts
|
|
782
824
|
var import_node_fs2 = require("node:fs");
|
|
783
825
|
var import_node_path4 = require("node:path");
|
|
826
|
+
var import_shared2 = require("@locusai/shared");
|
|
784
827
|
class ArtifactSyncer {
|
|
785
828
|
deps;
|
|
786
829
|
constructor(deps) {
|
|
@@ -812,10 +855,13 @@ class ArtifactSyncer {
|
|
|
812
855
|
}
|
|
813
856
|
try {
|
|
814
857
|
const files = import_node_fs2.readdirSync(artifactsDir);
|
|
815
|
-
if (files.length === 0)
|
|
816
|
-
return;
|
|
817
858
|
this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
|
|
818
|
-
const
|
|
859
|
+
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
860
|
+
const groupMap = new Map(groups.map((g) => [g.id, g.name]));
|
|
861
|
+
let artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
|
|
862
|
+
if (!artifactsGroupId) {
|
|
863
|
+
artifactsGroupId = await this.getOrCreateArtifactsGroup();
|
|
864
|
+
}
|
|
819
865
|
const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
|
|
820
866
|
for (const file of files) {
|
|
821
867
|
const filePath = import_node_path4.join(artifactsDir, file);
|
|
@@ -834,12 +880,35 @@ class ArtifactSyncer {
|
|
|
834
880
|
await this.deps.client.docs.create(this.deps.workspaceId, {
|
|
835
881
|
title,
|
|
836
882
|
content,
|
|
837
|
-
groupId: artifactsGroupId
|
|
883
|
+
groupId: artifactsGroupId,
|
|
884
|
+
type: import_shared2.DocType.GENERAL
|
|
838
885
|
});
|
|
839
886
|
this.deps.log(`Created artifact: ${file}`, "success");
|
|
840
887
|
}
|
|
841
888
|
}
|
|
842
889
|
}
|
|
890
|
+
for (const doc of existingDocs) {
|
|
891
|
+
if (doc.groupId === artifactsGroupId) {
|
|
892
|
+
const fileName = `${doc.title}.md`;
|
|
893
|
+
const filePath = import_node_path4.join(artifactsDir, fileName);
|
|
894
|
+
if (!import_node_fs2.existsSync(filePath)) {
|
|
895
|
+
import_node_fs2.writeFileSync(filePath, doc.content || "");
|
|
896
|
+
this.deps.log(`Fetched artifact: ${fileName}`, "success");
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
900
|
+
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
901
|
+
const groupDir = import_node_path4.join(documentsDir, groupName);
|
|
902
|
+
if (!import_node_fs2.existsSync(groupDir)) {
|
|
903
|
+
import_node_fs2.mkdirSync(groupDir, { recursive: true });
|
|
904
|
+
}
|
|
905
|
+
const fileName = `${doc.title}.md`;
|
|
906
|
+
const filePath = import_node_path4.join(groupDir, fileName);
|
|
907
|
+
if (!import_node_fs2.existsSync(filePath) || import_node_fs2.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
908
|
+
import_node_fs2.writeFileSync(filePath, doc.content || "");
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
843
912
|
} catch (error) {
|
|
844
913
|
this.deps.log(`Failed to sync artifacts: ${error}`, "error");
|
|
845
914
|
}
|
|
@@ -847,6 +916,7 @@ class ArtifactSyncer {
|
|
|
847
916
|
}
|
|
848
917
|
|
|
849
918
|
// src/core/indexer.ts
|
|
919
|
+
var import_node_crypto2 = require("node:crypto");
|
|
850
920
|
var import_node_fs3 = require("node:fs");
|
|
851
921
|
var import_node_path5 = require("node:path");
|
|
852
922
|
var import_globby = require("globby");
|
|
@@ -854,16 +924,64 @@ var import_globby = require("globby");
|
|
|
854
924
|
class CodebaseIndexer {
|
|
855
925
|
projectPath;
|
|
856
926
|
indexPath;
|
|
927
|
+
fullReindexRatioThreshold = 0.2;
|
|
857
928
|
constructor(projectPath) {
|
|
858
929
|
this.projectPath = projectPath;
|
|
859
930
|
this.indexPath = import_node_path5.join(projectPath, ".locus", "codebase-index.json");
|
|
860
931
|
}
|
|
861
|
-
async index(onProgress, treeSummarizer) {
|
|
932
|
+
async index(onProgress, treeSummarizer, force = false) {
|
|
862
933
|
if (!treeSummarizer) {
|
|
863
934
|
throw new Error("A treeSummarizer is required for this indexing method.");
|
|
864
935
|
}
|
|
865
|
-
|
|
866
|
-
|
|
936
|
+
onProgress?.("Generating file tree...");
|
|
937
|
+
const currentFiles = await this.getFileTree();
|
|
938
|
+
const treeString = currentFiles.join(`
|
|
939
|
+
`);
|
|
940
|
+
const newTreeHash = this.hashTree(treeString);
|
|
941
|
+
const existingIndex = this.loadIndex();
|
|
942
|
+
if (!force && existingIndex?.treeHash === newTreeHash) {
|
|
943
|
+
onProgress?.("No file changes detected, skipping reindex");
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
const currentHashes = this.computeFileHashes(currentFiles);
|
|
947
|
+
const existingHashes = existingIndex?.fileHashes;
|
|
948
|
+
const canIncremental = !force && existingIndex && existingHashes;
|
|
949
|
+
if (canIncremental) {
|
|
950
|
+
onProgress?.("Performing incremental update");
|
|
951
|
+
const { added, deleted, modified } = this.diffFiles(currentHashes, existingHashes);
|
|
952
|
+
const changedFiles = [...added, ...modified];
|
|
953
|
+
const totalChanges = changedFiles.length + deleted.length;
|
|
954
|
+
const existingFileCount = Object.keys(existingHashes).length;
|
|
955
|
+
onProgress?.(`File changes detected: ${changedFiles.length} changed, ${added.length} added, ${deleted.length} deleted`);
|
|
956
|
+
if (existingFileCount > 0) {
|
|
957
|
+
const changeRatio = totalChanges / existingFileCount;
|
|
958
|
+
if (changeRatio <= this.fullReindexRatioThreshold && changedFiles.length > 0) {
|
|
959
|
+
onProgress?.(`Reindexing ${changedFiles.length} changed files and merging with existing index`);
|
|
960
|
+
const incrementalIndex = await treeSummarizer(changedFiles.join(`
|
|
961
|
+
`));
|
|
962
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
963
|
+
this.removeFilesFromIndex(updatedIndex, [...deleted, ...modified]);
|
|
964
|
+
return this.mergeIndex(updatedIndex, incrementalIndex, currentHashes, newTreeHash);
|
|
965
|
+
}
|
|
966
|
+
if (changedFiles.length === 0 && deleted.length > 0) {
|
|
967
|
+
onProgress?.(`Removing ${deleted.length} deleted files from index`);
|
|
968
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
969
|
+
this.removeFilesFromIndex(updatedIndex, deleted);
|
|
970
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
971
|
+
}
|
|
972
|
+
if (changedFiles.length === 0 && deleted.length === 0) {
|
|
973
|
+
onProgress?.("No actual file changes, updating hashes only");
|
|
974
|
+
const updatedIndex = this.cloneIndex(existingIndex);
|
|
975
|
+
return this.applyIndexMetadata(updatedIndex, currentHashes, newTreeHash);
|
|
976
|
+
}
|
|
977
|
+
onProgress?.(`Too many changes (${(changeRatio * 100).toFixed(1)}%), performing full reindex`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
onProgress?.("AI is analyzing codebase structure...");
|
|
981
|
+
const index = await treeSummarizer(treeString);
|
|
982
|
+
return this.applyIndexMetadata(index, currentHashes, newTreeHash);
|
|
983
|
+
}
|
|
984
|
+
async getFileTree() {
|
|
867
985
|
const gitmodulesPath = import_node_path5.join(this.projectPath, ".gitmodules");
|
|
868
986
|
const submoduleIgnores = [];
|
|
869
987
|
if (import_node_fs3.existsSync(gitmodulesPath)) {
|
|
@@ -881,7 +999,7 @@ class CodebaseIndexer {
|
|
|
881
999
|
}
|
|
882
1000
|
} catch {}
|
|
883
1001
|
}
|
|
884
|
-
|
|
1002
|
+
return import_globby.globby(["**/*"], {
|
|
885
1003
|
cwd: this.projectPath,
|
|
886
1004
|
gitignore: true,
|
|
887
1005
|
ignore: [
|
|
@@ -922,13 +1040,6 @@ class CodebaseIndexer {
|
|
|
922
1040
|
"**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}"
|
|
923
1041
|
]
|
|
924
1042
|
});
|
|
925
|
-
const treeString = files.join(`
|
|
926
|
-
`);
|
|
927
|
-
if (onProgress)
|
|
928
|
-
onProgress("AI is analyzing codebase structure...");
|
|
929
|
-
const index = await treeSummarizer(treeString);
|
|
930
|
-
index.lastIndexed = new Date().toISOString();
|
|
931
|
-
return index;
|
|
932
1043
|
}
|
|
933
1044
|
loadIndex() {
|
|
934
1045
|
if (import_node_fs3.existsSync(this.indexPath)) {
|
|
@@ -947,6 +1058,79 @@ class CodebaseIndexer {
|
|
|
947
1058
|
}
|
|
948
1059
|
import_node_fs3.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
949
1060
|
}
|
|
1061
|
+
cloneIndex(index) {
|
|
1062
|
+
return JSON.parse(JSON.stringify(index));
|
|
1063
|
+
}
|
|
1064
|
+
applyIndexMetadata(index, fileHashes, treeHash) {
|
|
1065
|
+
index.lastIndexed = new Date().toISOString();
|
|
1066
|
+
index.treeHash = treeHash;
|
|
1067
|
+
index.fileHashes = fileHashes;
|
|
1068
|
+
return index;
|
|
1069
|
+
}
|
|
1070
|
+
hashTree(tree) {
|
|
1071
|
+
return import_node_crypto2.createHash("sha256").update(tree).digest("hex");
|
|
1072
|
+
}
|
|
1073
|
+
hashFile(filePath) {
|
|
1074
|
+
try {
|
|
1075
|
+
const content = import_node_fs3.readFileSync(import_node_path5.join(this.projectPath, filePath), "utf-8");
|
|
1076
|
+
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1077
|
+
} catch {
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
computeFileHashes(files) {
|
|
1082
|
+
const hashes = {};
|
|
1083
|
+
for (const file of files) {
|
|
1084
|
+
const hash = this.hashFile(file);
|
|
1085
|
+
if (hash !== null) {
|
|
1086
|
+
hashes[file] = hash;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return hashes;
|
|
1090
|
+
}
|
|
1091
|
+
diffFiles(currentHashes, existingHashes) {
|
|
1092
|
+
const currentFiles = Object.keys(currentHashes);
|
|
1093
|
+
const existingFiles = Object.keys(existingHashes);
|
|
1094
|
+
const existingSet = new Set(existingFiles);
|
|
1095
|
+
const currentSet = new Set(currentFiles);
|
|
1096
|
+
const added = currentFiles.filter((f) => !existingSet.has(f));
|
|
1097
|
+
const deleted = existingFiles.filter((f) => !currentSet.has(f));
|
|
1098
|
+
const modified = currentFiles.filter((f) => existingSet.has(f) && currentHashes[f] !== existingHashes[f]);
|
|
1099
|
+
return { added, deleted, modified };
|
|
1100
|
+
}
|
|
1101
|
+
removeFilesFromIndex(index, files) {
|
|
1102
|
+
const fileSet = new Set(files);
|
|
1103
|
+
for (const file of files) {
|
|
1104
|
+
delete index.responsibilities[file];
|
|
1105
|
+
}
|
|
1106
|
+
for (const [symbol, paths] of Object.entries(index.symbols)) {
|
|
1107
|
+
index.symbols[symbol] = paths.filter((p) => !fileSet.has(p));
|
|
1108
|
+
if (index.symbols[symbol].length === 0) {
|
|
1109
|
+
delete index.symbols[symbol];
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
mergeIndex(existing, incremental, newHashes, newTreeHash) {
|
|
1114
|
+
const mergedSymbols = { ...existing.symbols };
|
|
1115
|
+
for (const [symbol, paths] of Object.entries(incremental.symbols)) {
|
|
1116
|
+
if (mergedSymbols[symbol]) {
|
|
1117
|
+
mergedSymbols[symbol] = [
|
|
1118
|
+
...new Set([...mergedSymbols[symbol], ...paths])
|
|
1119
|
+
];
|
|
1120
|
+
} else {
|
|
1121
|
+
mergedSymbols[symbol] = paths;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
const merged = {
|
|
1125
|
+
symbols: mergedSymbols,
|
|
1126
|
+
responsibilities: {
|
|
1127
|
+
...existing.responsibilities,
|
|
1128
|
+
...incremental.responsibilities
|
|
1129
|
+
},
|
|
1130
|
+
lastIndexed: ""
|
|
1131
|
+
};
|
|
1132
|
+
return this.applyIndexMetadata(merged, newHashes, newTreeHash);
|
|
1133
|
+
}
|
|
950
1134
|
}
|
|
951
1135
|
|
|
952
1136
|
// src/agent/codebase-indexer-service.ts
|
|
@@ -957,9 +1141,8 @@ class CodebaseIndexerService {
|
|
|
957
1141
|
this.deps = deps;
|
|
958
1142
|
this.indexer = new CodebaseIndexer(deps.projectPath);
|
|
959
1143
|
}
|
|
960
|
-
async reindex() {
|
|
1144
|
+
async reindex(force = false) {
|
|
961
1145
|
try {
|
|
962
|
-
this.deps.log("Reindexing codebase...", "info");
|
|
963
1146
|
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
964
1147
|
const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
|
|
965
1148
|
1. Key symbols (classes, functions, types) and their locations
|
|
@@ -980,7 +1163,11 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
980
1163
|
return JSON.parse(jsonMatch[0]);
|
|
981
1164
|
}
|
|
982
1165
|
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
983
|
-
});
|
|
1166
|
+
}, force);
|
|
1167
|
+
if (index === null) {
|
|
1168
|
+
this.deps.log("No changes detected, skipping reindex", "info");
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
984
1171
|
this.indexer.saveIndex(index);
|
|
985
1172
|
this.deps.log("Codebase reindexed successfully", "success");
|
|
986
1173
|
} catch (error) {
|
|
@@ -989,53 +1176,17 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
989
1176
|
}
|
|
990
1177
|
}
|
|
991
1178
|
|
|
992
|
-
// src/agent/sprint-planner.ts
|
|
993
|
-
class SprintPlanner {
|
|
994
|
-
deps;
|
|
995
|
-
constructor(deps) {
|
|
996
|
-
this.deps = deps;
|
|
997
|
-
}
|
|
998
|
-
async planSprint(sprint, tasks2) {
|
|
999
|
-
this.deps.log(`Planning sprint: ${sprint.name}`, "info");
|
|
1000
|
-
try {
|
|
1001
|
-
const taskList = tasks2.map((t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`).join(`
|
|
1002
|
-
`);
|
|
1003
|
-
const planningPrompt = `# Sprint Planning: ${sprint.name}
|
|
1004
|
-
|
|
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.
|
|
1006
|
-
|
|
1007
|
-
## Tasks
|
|
1008
|
-
${taskList}
|
|
1009
|
-
|
|
1010
|
-
## Instructions
|
|
1011
|
-
1. Analyze dependencies between these tasks.
|
|
1012
|
-
2. Prioritize them for the most efficient execution.
|
|
1013
|
-
3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
|
|
1014
|
-
4. Output your final plan. The plan should clearly state the order of execution.
|
|
1015
|
-
|
|
1016
|
-
**IMPORTANT**:
|
|
1017
|
-
- Do NOT create any files on the filesystem during this planning phase.
|
|
1018
|
-
- Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
|
|
1019
|
-
- Your output will be saved as the official sprint mindmap on the server.`;
|
|
1020
|
-
const plan = await this.deps.aiRunner.run(planningPrompt, true);
|
|
1021
|
-
this.deps.log("Sprint mindmap generated and posted to server.", "success");
|
|
1022
|
-
return plan;
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
this.deps.log(`Sprint planning failed: ${error}`, "error");
|
|
1025
|
-
return sprint.mindmap || "";
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
1179
|
// src/core/prompt-builder.ts
|
|
1031
1180
|
var import_node_fs4 = require("node:fs");
|
|
1032
|
-
var
|
|
1181
|
+
var import_node_os2 = require("node:os");
|
|
1182
|
+
var import_node_path6 = require("node:path");
|
|
1183
|
+
var import_shared3 = require("@locusai/shared");
|
|
1033
1184
|
class PromptBuilder {
|
|
1034
1185
|
projectPath;
|
|
1035
1186
|
constructor(projectPath) {
|
|
1036
1187
|
this.projectPath = projectPath;
|
|
1037
1188
|
}
|
|
1038
|
-
async build(task) {
|
|
1189
|
+
async build(task, options = {}) {
|
|
1039
1190
|
let prompt = `# Task: ${task.title}
|
|
1040
1191
|
|
|
1041
1192
|
`;
|
|
@@ -1050,18 +1201,81 @@ You are acting as a ${roleText}.
|
|
|
1050
1201
|
${task.description || "No description provided."}
|
|
1051
1202
|
|
|
1052
1203
|
`;
|
|
1204
|
+
const projectConfig = this.getProjectConfig();
|
|
1205
|
+
if (projectConfig) {
|
|
1206
|
+
prompt += `## Project Metadata
|
|
1207
|
+
`;
|
|
1208
|
+
prompt += `- Version: ${projectConfig.version || "Unknown"}
|
|
1209
|
+
`;
|
|
1210
|
+
prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
|
|
1211
|
+
|
|
1212
|
+
`;
|
|
1213
|
+
}
|
|
1214
|
+
let serverContext = null;
|
|
1215
|
+
if (options.taskContext) {
|
|
1216
|
+
try {
|
|
1217
|
+
serverContext = JSON.parse(options.taskContext);
|
|
1218
|
+
} catch {
|
|
1219
|
+
serverContext = { context: options.taskContext };
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1053
1222
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
1223
|
+
let hasLocalContext = false;
|
|
1054
1224
|
if (import_node_fs4.existsSync(contextPath)) {
|
|
1055
1225
|
try {
|
|
1056
1226
|
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
1057
|
-
|
|
1227
|
+
if (context.trim().length > 20) {
|
|
1228
|
+
prompt += `## Project Context (Local)
|
|
1058
1229
|
${context}
|
|
1059
1230
|
|
|
1060
1231
|
`;
|
|
1232
|
+
hasLocalContext = true;
|
|
1233
|
+
}
|
|
1061
1234
|
} catch (err) {
|
|
1062
1235
|
console.warn(`Warning: Could not read context file: ${err}`);
|
|
1063
1236
|
}
|
|
1064
1237
|
}
|
|
1238
|
+
if (!hasLocalContext) {
|
|
1239
|
+
const fallback = this.getFallbackContext();
|
|
1240
|
+
if (fallback) {
|
|
1241
|
+
prompt += `## Project Context (README Fallback)
|
|
1242
|
+
${fallback}
|
|
1243
|
+
|
|
1244
|
+
`;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (serverContext) {
|
|
1248
|
+
prompt += `## Project Context (Server)
|
|
1249
|
+
`;
|
|
1250
|
+
if (serverContext.project) {
|
|
1251
|
+
prompt += `- Project: ${serverContext.project.name || "Unknown"}
|
|
1252
|
+
`;
|
|
1253
|
+
if (!hasLocalContext && serverContext.project.techStack?.length) {
|
|
1254
|
+
prompt += `- Tech Stack: ${serverContext.project.techStack.join(", ")}
|
|
1255
|
+
`;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (serverContext.context) {
|
|
1259
|
+
prompt += `
|
|
1260
|
+
${serverContext.context}
|
|
1261
|
+
`;
|
|
1262
|
+
}
|
|
1263
|
+
prompt += `
|
|
1264
|
+
`;
|
|
1265
|
+
}
|
|
1266
|
+
prompt += this.getProjectStructure();
|
|
1267
|
+
prompt += this.getSkillsInfo();
|
|
1268
|
+
prompt += `## Project Knowledge Base
|
|
1269
|
+
`;
|
|
1270
|
+
prompt += `You have access to the following documentation directories for context:
|
|
1271
|
+
`;
|
|
1272
|
+
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
1273
|
+
`;
|
|
1274
|
+
prompt += `- Documents: \`.locus/documents\`
|
|
1275
|
+
`;
|
|
1276
|
+
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
1277
|
+
|
|
1278
|
+
`;
|
|
1065
1279
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
1066
1280
|
if (import_node_fs4.existsSync(indexPath)) {
|
|
1067
1281
|
prompt += `## Codebase Overview
|
|
@@ -1070,11 +1284,19 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
1070
1284
|
`;
|
|
1071
1285
|
}
|
|
1072
1286
|
if (task.docs && task.docs.length > 0) {
|
|
1073
|
-
prompt += `## Attached Documents
|
|
1287
|
+
prompt += `## Attached Documents (Summarized)
|
|
1288
|
+
`;
|
|
1289
|
+
prompt += `> Full content available on server. Rely on Task Description for specific requirements.
|
|
1290
|
+
|
|
1074
1291
|
`;
|
|
1075
1292
|
for (const doc of task.docs) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1293
|
+
const content = doc.content || "";
|
|
1294
|
+
const limit = 800;
|
|
1295
|
+
const preview = content.slice(0, limit);
|
|
1296
|
+
const isTruncated = content.length > limit;
|
|
1297
|
+
prompt += `### Doc: ${doc.title}
|
|
1298
|
+
${preview}${isTruncated ? `
|
|
1299
|
+
...(truncated)...` : ""}
|
|
1078
1300
|
|
|
1079
1301
|
`;
|
|
1080
1302
|
}
|
|
@@ -1090,7 +1312,7 @@ ${doc.content || "(No content)"}
|
|
|
1090
1312
|
`;
|
|
1091
1313
|
}
|
|
1092
1314
|
if (task.comments && task.comments.length > 0) {
|
|
1093
|
-
const comments = task.comments.slice(0,
|
|
1315
|
+
const comments = task.comments.slice(0, 3);
|
|
1094
1316
|
prompt += `## Task History & Feedback
|
|
1095
1317
|
`;
|
|
1096
1318
|
prompt += `Review the following comments for context or rejection feedback:
|
|
@@ -1112,20 +1334,122 @@ ${comment.text}
|
|
|
1112
1334
|
`;
|
|
1113
1335
|
return prompt;
|
|
1114
1336
|
}
|
|
1337
|
+
getProjectConfig() {
|
|
1338
|
+
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
1339
|
+
if (import_node_fs4.existsSync(configPath)) {
|
|
1340
|
+
try {
|
|
1341
|
+
return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
|
|
1342
|
+
} catch {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
getFallbackContext() {
|
|
1349
|
+
const readmePath = import_node_path6.join(this.projectPath, "README.md");
|
|
1350
|
+
if (import_node_fs4.existsSync(readmePath)) {
|
|
1351
|
+
try {
|
|
1352
|
+
const content = import_node_fs4.readFileSync(readmePath, "utf-8");
|
|
1353
|
+
const limit = 1000;
|
|
1354
|
+
return content.slice(0, limit) + (content.length > limit ? `
|
|
1355
|
+
...(truncated)...` : "");
|
|
1356
|
+
} catch {
|
|
1357
|
+
return "";
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return "";
|
|
1361
|
+
}
|
|
1362
|
+
getProjectStructure() {
|
|
1363
|
+
try {
|
|
1364
|
+
const entries = import_node_fs4.readdirSync(this.projectPath);
|
|
1365
|
+
const folders = entries.filter((e) => {
|
|
1366
|
+
if (e.startsWith(".") || e === "node_modules")
|
|
1367
|
+
return false;
|
|
1368
|
+
try {
|
|
1369
|
+
return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
|
|
1370
|
+
} catch {
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
if (folders.length === 0)
|
|
1375
|
+
return "";
|
|
1376
|
+
let structure = `## Project Structure
|
|
1377
|
+
`;
|
|
1378
|
+
structure += `Key directories in this project:
|
|
1379
|
+
`;
|
|
1380
|
+
for (const folder of folders) {
|
|
1381
|
+
structure += `- \`${folder}/\`
|
|
1382
|
+
`;
|
|
1383
|
+
}
|
|
1384
|
+
return `${structure}
|
|
1385
|
+
`;
|
|
1386
|
+
} catch {
|
|
1387
|
+
return "";
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
getSkillsInfo() {
|
|
1391
|
+
const projectSkillsDirs = [
|
|
1392
|
+
LOCUS_CONFIG.agentSkillsDir,
|
|
1393
|
+
".cursor/skills",
|
|
1394
|
+
".claude/skills",
|
|
1395
|
+
".codex/skills",
|
|
1396
|
+
".gemini/skills"
|
|
1397
|
+
];
|
|
1398
|
+
const globalHome = import_node_os2.homedir();
|
|
1399
|
+
const globalSkillsDirs = [
|
|
1400
|
+
import_node_path6.join(globalHome, ".cursor/skills"),
|
|
1401
|
+
import_node_path6.join(globalHome, ".claude/skills"),
|
|
1402
|
+
import_node_path6.join(globalHome, ".codex/skills"),
|
|
1403
|
+
import_node_path6.join(globalHome, ".gemini/skills")
|
|
1404
|
+
];
|
|
1405
|
+
const allSkillNames = new Set;
|
|
1406
|
+
for (const relativePath of projectSkillsDirs) {
|
|
1407
|
+
const fullPath = import_node_path6.join(this.projectPath, relativePath);
|
|
1408
|
+
this.scanSkillsInDirectory(fullPath, allSkillNames);
|
|
1409
|
+
}
|
|
1410
|
+
for (const fullPath of globalSkillsDirs) {
|
|
1411
|
+
this.scanSkillsInDirectory(fullPath, allSkillNames);
|
|
1412
|
+
}
|
|
1413
|
+
const uniqueSkills = Array.from(allSkillNames).sort();
|
|
1414
|
+
if (uniqueSkills.length === 0)
|
|
1415
|
+
return "";
|
|
1416
|
+
return `## Available Agent Skills
|
|
1417
|
+
` + `The project has the following specialized skills available (from project or global locations):
|
|
1418
|
+
` + uniqueSkills.map((s) => `- ${s}`).join(`
|
|
1419
|
+
`) + `
|
|
1420
|
+
|
|
1421
|
+
`;
|
|
1422
|
+
}
|
|
1423
|
+
scanSkillsInDirectory(dirPath, skillSet) {
|
|
1424
|
+
if (!import_node_fs4.existsSync(dirPath))
|
|
1425
|
+
return;
|
|
1426
|
+
try {
|
|
1427
|
+
const entries = import_node_fs4.readdirSync(dirPath).filter((name) => {
|
|
1428
|
+
try {
|
|
1429
|
+
return import_node_fs4.statSync(import_node_path6.join(dirPath, name)).isDirectory();
|
|
1430
|
+
} catch {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
for (const entry of entries) {
|
|
1435
|
+
skillSet.add(entry);
|
|
1436
|
+
}
|
|
1437
|
+
} catch {}
|
|
1438
|
+
}
|
|
1115
1439
|
roleToText(role) {
|
|
1116
1440
|
if (!role) {
|
|
1117
1441
|
return null;
|
|
1118
1442
|
}
|
|
1119
1443
|
switch (role) {
|
|
1120
|
-
case
|
|
1444
|
+
case import_shared3.AssigneeRole.BACKEND:
|
|
1121
1445
|
return "Backend Engineer";
|
|
1122
|
-
case
|
|
1446
|
+
case import_shared3.AssigneeRole.FRONTEND:
|
|
1123
1447
|
return "Frontend Engineer";
|
|
1124
|
-
case
|
|
1448
|
+
case import_shared3.AssigneeRole.PM:
|
|
1125
1449
|
return "Product Manager";
|
|
1126
|
-
case
|
|
1450
|
+
case import_shared3.AssigneeRole.QA:
|
|
1127
1451
|
return "QA Engineer";
|
|
1128
|
-
case
|
|
1452
|
+
case import_shared3.AssigneeRole.DESIGN:
|
|
1129
1453
|
return "Product Designer";
|
|
1130
1454
|
default:
|
|
1131
1455
|
return "engineer";
|
|
@@ -1141,18 +1465,11 @@ class TaskExecutor {
|
|
|
1141
1465
|
this.deps = deps;
|
|
1142
1466
|
this.promptBuilder = new PromptBuilder(deps.projectPath);
|
|
1143
1467
|
}
|
|
1144
|
-
|
|
1145
|
-
this.deps.sprintPlan = sprintPlan;
|
|
1146
|
-
}
|
|
1147
|
-
async execute(task) {
|
|
1468
|
+
async execute(task, context) {
|
|
1148
1469
|
this.deps.log(`Executing: ${task.title}`, "info");
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
${this.deps.sprintPlan}
|
|
1153
|
-
|
|
1154
|
-
${basePrompt}`;
|
|
1155
|
-
}
|
|
1470
|
+
const basePrompt = await this.promptBuilder.build(task, {
|
|
1471
|
+
taskContext: context
|
|
1472
|
+
});
|
|
1156
1473
|
try {
|
|
1157
1474
|
let plan = null;
|
|
1158
1475
|
if (this.deps.skipPlanning) {
|
|
@@ -1211,16 +1528,14 @@ class AgentWorker {
|
|
|
1211
1528
|
config;
|
|
1212
1529
|
client;
|
|
1213
1530
|
aiRunner;
|
|
1214
|
-
sprintPlanner;
|
|
1215
1531
|
indexerService;
|
|
1216
1532
|
artifactSyncer;
|
|
1217
1533
|
taskExecutor;
|
|
1218
1534
|
consecutiveEmpty = 0;
|
|
1219
|
-
maxEmpty =
|
|
1535
|
+
maxEmpty = 60;
|
|
1220
1536
|
maxTasks = 50;
|
|
1221
1537
|
tasksCompleted = 0;
|
|
1222
1538
|
pollInterval = 1e4;
|
|
1223
|
-
sprintPlan = null;
|
|
1224
1539
|
constructor(config) {
|
|
1225
1540
|
this.config = config;
|
|
1226
1541
|
const projectPath = config.projectPath || process.cwd();
|
|
@@ -1241,10 +1556,6 @@ class AgentWorker {
|
|
|
1241
1556
|
model: config.model,
|
|
1242
1557
|
log
|
|
1243
1558
|
});
|
|
1244
|
-
this.sprintPlanner = new SprintPlanner({
|
|
1245
|
-
aiRunner: this.aiRunner,
|
|
1246
|
-
log
|
|
1247
|
-
});
|
|
1248
1559
|
this.indexerService = new CodebaseIndexerService({
|
|
1249
1560
|
aiRunner: this.aiRunner,
|
|
1250
1561
|
projectPath,
|
|
@@ -1259,7 +1570,6 @@ class AgentWorker {
|
|
|
1259
1570
|
this.taskExecutor = new TaskExecutor({
|
|
1260
1571
|
aiRunner: this.aiRunner,
|
|
1261
1572
|
projectPath,
|
|
1262
|
-
sprintPlan: null,
|
|
1263
1573
|
skipPlanning: config.skipPlanning,
|
|
1264
1574
|
log
|
|
1265
1575
|
});
|
|
@@ -1298,8 +1608,13 @@ class AgentWorker {
|
|
|
1298
1608
|
}
|
|
1299
1609
|
async executeTask(task) {
|
|
1300
1610
|
const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
|
|
1301
|
-
|
|
1302
|
-
|
|
1611
|
+
let context = "";
|
|
1612
|
+
try {
|
|
1613
|
+
context = await this.client.tasks.getContext(task.id, this.config.workspaceId);
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
this.log(`Failed to fetch task context: ${err}`, "warn");
|
|
1616
|
+
}
|
|
1617
|
+
const result = await this.taskExecutor.execute(fullTask, context);
|
|
1303
1618
|
await this.indexerService.reindex();
|
|
1304
1619
|
return result;
|
|
1305
1620
|
}
|
|
@@ -1307,35 +1622,12 @@ class AgentWorker {
|
|
|
1307
1622
|
this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
|
|
1308
1623
|
const sprint = await this.getActiveSprint();
|
|
1309
1624
|
if (sprint) {
|
|
1310
|
-
this.log(`
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
if (activeTasks.length <= 1) {
|
|
1317
|
-
this.log("Skipping mindmap generation (only one task in sprint).", "info");
|
|
1318
|
-
this.sprintPlan = null;
|
|
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);
|
|
1325
|
-
const needsPlanning = !sprint.mindmap || sprint.mindmap.trim() === "" || latestTaskCreation > mindmapDate;
|
|
1326
|
-
if (needsPlanning) {
|
|
1327
|
-
if (sprint.mindmap && latestTaskCreation > mindmapDate) {
|
|
1328
|
-
this.log("New tasks have been added to the sprint since last mindmap. Regenerating...", "warn");
|
|
1329
|
-
}
|
|
1330
|
-
this.sprintPlan = await this.sprintPlanner.planSprint(sprint, tasks2);
|
|
1331
|
-
await this.client.sprints.update(sprint.id, this.config.workspaceId, {
|
|
1332
|
-
mindmap: this.sprintPlan,
|
|
1333
|
-
mindmapUpdatedAt: new Date
|
|
1334
|
-
});
|
|
1335
|
-
} else {
|
|
1336
|
-
this.log("Using existing sprint mindmap.", "info");
|
|
1337
|
-
this.sprintPlan = sprint.mindmap ?? null;
|
|
1338
|
-
}
|
|
1625
|
+
this.log(`Active sprint found: ${sprint.name}. Ensuring plan is up to date...`, "info");
|
|
1626
|
+
try {
|
|
1627
|
+
await this.client.sprints.triggerAIPlanning(sprint.id, this.config.workspaceId);
|
|
1628
|
+
this.log(`Sprint plan sync checked on server.`, "success");
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
1339
1631
|
}
|
|
1340
1632
|
} else {
|
|
1341
1633
|
this.log("No active sprint found for planning.", "warn");
|
|
@@ -1355,7 +1647,11 @@ class AgentWorker {
|
|
|
1355
1647
|
this.consecutiveEmpty = 0;
|
|
1356
1648
|
this.log(`Claimed: ${task.title}`, "success");
|
|
1357
1649
|
const result = await this.executeTask(task);
|
|
1358
|
-
|
|
1650
|
+
try {
|
|
1651
|
+
await this.artifactSyncer.sync();
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
this.log(`Artifact sync failed: ${err}`, "error");
|
|
1654
|
+
}
|
|
1359
1655
|
if (result.success) {
|
|
1360
1656
|
this.log(`Completed: ${task.title}`, "success");
|
|
1361
1657
|
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
@@ -1392,7 +1688,7 @@ if (process.argv[1]?.includes("agent-worker") || process.argv[1]?.includes("work
|
|
|
1392
1688
|
config.workspaceId = args[++i];
|
|
1393
1689
|
else if (arg === "--sprint-id")
|
|
1394
1690
|
config.sprintId = args[++i];
|
|
1395
|
-
else if (arg === "--api-
|
|
1691
|
+
else if (arg === "--api-url")
|
|
1396
1692
|
config.apiBase = args[++i];
|
|
1397
1693
|
else if (arg === "--api-key")
|
|
1398
1694
|
config.apiKey = args[++i];
|
|
@@ -1429,7 +1725,6 @@ __export(exports_index_node, {
|
|
|
1429
1725
|
TasksModule: () => TasksModule,
|
|
1430
1726
|
TaskExecutor: () => TaskExecutor,
|
|
1431
1727
|
SprintsModule: () => SprintsModule,
|
|
1432
|
-
SprintPlanner: () => SprintPlanner,
|
|
1433
1728
|
PromptBuilder: () => PromptBuilder,
|
|
1434
1729
|
PROVIDER: () => PROVIDER,
|
|
1435
1730
|
OrganizationsModule: () => OrganizationsModule,
|
|
@@ -1448,13 +1743,14 @@ __export(exports_index_node, {
|
|
|
1448
1743
|
AuthModule: () => AuthModule,
|
|
1449
1744
|
ArtifactSyncer: () => ArtifactSyncer,
|
|
1450
1745
|
AgentWorker: () => AgentWorker,
|
|
1451
|
-
AgentOrchestrator: () => AgentOrchestrator
|
|
1746
|
+
AgentOrchestrator: () => AgentOrchestrator,
|
|
1747
|
+
AIModule: () => AIModule
|
|
1452
1748
|
});
|
|
1453
1749
|
module.exports = __toCommonJS(exports_index_node);
|
|
1454
1750
|
// src/orchestrator.ts
|
|
1455
1751
|
var import_node_child_process3 = require("node:child_process");
|
|
1456
1752
|
var import_node_fs5 = require("node:fs");
|
|
1457
|
-
var
|
|
1753
|
+
var import_node_path7 = require("node:path");
|
|
1458
1754
|
var import_node_url = require("node:url");
|
|
1459
1755
|
var import_shared4 = require("@locusai/shared");
|
|
1460
1756
|
var import_events3 = require("events");
|
|
@@ -1550,8 +1846,8 @@ ${c.success("✅ Orchestrator finished")}`);
|
|
|
1550
1846
|
`);
|
|
1551
1847
|
const potentialPaths = [];
|
|
1552
1848
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
|
|
1553
|
-
const currentModuleDir =
|
|
1554
|
-
potentialPaths.push(
|
|
1849
|
+
const currentModuleDir = import_node_path7.dirname(currentModulePath);
|
|
1850
|
+
potentialPaths.push(import_node_path7.join(currentModuleDir, "agent", "worker.js"), import_node_path7.join(currentModuleDir, "worker.js"), import_node_path7.join(currentModuleDir, "agent", "worker.ts"));
|
|
1555
1851
|
const workerPath = potentialPaths.find((p) => import_node_fs5.existsSync(p));
|
|
1556
1852
|
if (!workerPath) {
|
|
1557
1853
|
throw new Error(`Worker file not found. Checked: ${potentialPaths.join(", ")}. ` + `Make sure the SDK is properly built and installed.`);
|
|
@@ -1561,7 +1857,7 @@ ${c.success("✅ Orchestrator finished")}`);
|
|
|
1561
1857
|
agentId,
|
|
1562
1858
|
"--workspace-id",
|
|
1563
1859
|
this.config.workspaceId,
|
|
1564
|
-
"--api-
|
|
1860
|
+
"--api-url",
|
|
1565
1861
|
this.config.apiBase,
|
|
1566
1862
|
"--api-key",
|
|
1567
1863
|
this.config.apiKey,
|