@locusai/sdk 0.5.1 → 0.7.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.
Files changed (37) hide show
  1. package/dist/agent/artifact-syncer.d.ts.map +1 -1
  2. package/dist/agent/index.d.ts +0 -1
  3. package/dist/agent/index.d.ts.map +1 -1
  4. package/dist/agent/task-executor.d.ts +1 -3
  5. package/dist/agent/task-executor.d.ts.map +1 -1
  6. package/dist/agent/worker.d.ts +0 -2
  7. package/dist/agent/worker.d.ts.map +1 -1
  8. package/dist/agent/worker.js +340 -121
  9. package/dist/ai/claude-runner.d.ts +1 -0
  10. package/dist/ai/claude-runner.d.ts.map +1 -1
  11. package/dist/ai/codex-runner.d.ts.map +1 -1
  12. package/dist/core/config.d.ts +2 -0
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/core/prompt-builder.d.ts +9 -1
  15. package/dist/core/prompt-builder.d.ts.map +1 -1
  16. package/dist/index-node.d.ts +1 -0
  17. package/dist/index-node.d.ts.map +1 -1
  18. package/dist/index-node.js +346 -127
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +65 -3
  22. package/dist/modules/ai.d.ts +47 -0
  23. package/dist/modules/ai.d.ts.map +1 -0
  24. package/dist/modules/auth.d.ts +7 -1
  25. package/dist/modules/auth.d.ts.map +1 -1
  26. package/dist/modules/organizations.d.ts +3 -3
  27. package/dist/modules/organizations.d.ts.map +1 -1
  28. package/dist/modules/sprints.d.ts +1 -0
  29. package/dist/modules/sprints.d.ts.map +1 -1
  30. package/dist/modules/tasks.d.ts +6 -0
  31. package/dist/modules/tasks.d.ts.map +1 -1
  32. package/dist/modules/workspaces.d.ts +18 -0
  33. package/dist/modules/workspaces.d.ts.map +1 -1
  34. package/dist/orchestrator.d.ts.map +1 -1
  35. package/package.json +2 -2
  36. package/dist/agent/sprint-planner.d.ts +0 -16
  37. package/dist/agent/sprint-planner.d.ts.map +0 -1
@@ -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,12 +86,46 @@ 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, { timeout: 300000 });
93
+ return data;
94
+ }
95
+ async detectIntent(workspaceId, body) {
96
+ const { data } = await this.api.post(`/ai/${workspaceId}/chat/intent`, body, { timeout: 300000 });
97
+ return data;
98
+ }
99
+ async executeIntent(workspaceId, body) {
100
+ const { data } = await this.api.post(`/ai/${workspaceId}/chat/execute`, body, { timeout: 300000 });
101
+ return data;
102
+ }
103
+ async listSessions(workspaceId) {
104
+ const { data } = await this.api.get(`/ai/${workspaceId}/sessions`);
105
+ return data;
106
+ }
107
+ async getSession(workspaceId, sessionId) {
108
+ const { data } = await this.api.get(`/ai/${workspaceId}/session/${sessionId}`);
109
+ return data;
110
+ }
111
+ getChatStreamUrl(workspaceId, sessionId) {
112
+ return `${this.api.defaults.baseURL}/ai/${workspaceId}/chat/stream?sessionId=${sessionId}`;
113
+ }
114
+ async deleteSession(workspaceId, sessionId) {
115
+ await this.api.delete(`/ai/${workspaceId}/session/${sessionId}`);
116
+ }
117
+ }
118
+
88
119
  // src/modules/auth.ts
89
120
  class AuthModule extends BaseModule {
90
- async getMe() {
121
+ async getProfile() {
91
122
  const { data } = await this.api.get("/auth/me");
92
123
  return data;
93
124
  }
125
+ async getApiKeyInfo() {
126
+ const { data } = await this.api.get("/auth/api-key");
127
+ return data;
128
+ }
94
129
  async requestRegisterOtp(email) {
95
130
  const { data } = await this.api.post("/auth/register-otp", { email });
96
131
  return data;
@@ -248,6 +283,10 @@ class SprintsModule extends BaseModule {
248
283
  const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
249
284
  return data.sprint;
250
285
  }
286
+ async triggerAIPlanning(id, workspaceId) {
287
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/trigger-ai-planning`);
288
+ return data.sprint;
289
+ }
251
290
  }
252
291
 
253
292
  // src/modules/tasks.ts
@@ -294,6 +333,16 @@ class TasksModule extends BaseModule {
294
333
  const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
295
334
  return data.comment;
296
335
  }
336
+ async batchUpdate(ids, workspaceId, updates) {
337
+ await this.api.patch(`/workspaces/${workspaceId}/tasks/batch`, {
338
+ ids,
339
+ updates
340
+ });
341
+ }
342
+ async getContext(id, workspaceId) {
343
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}/context`);
344
+ return data;
345
+ }
297
346
  }
298
347
 
299
348
  // src/modules/workspaces.ts
@@ -340,6 +389,17 @@ class WorkspacesModule extends BaseModule {
340
389
  const { data } = await this.api.post(`/workspaces/${id}/dispatch`, { workerId, sprintId });
341
390
  return data.task;
342
391
  }
392
+ async listApiKeys(workspaceId) {
393
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/api-keys`);
394
+ return data.apiKeys;
395
+ }
396
+ async createApiKey(workspaceId, name) {
397
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/api-keys`, { name });
398
+ return data.apiKey;
399
+ }
400
+ async deleteApiKey(workspaceId, keyId) {
401
+ await this.api.delete(`/workspaces/${workspaceId}/api-keys/${keyId}`);
402
+ }
343
403
  }
344
404
 
345
405
  // src/index.ts
@@ -347,6 +407,7 @@ class LocusClient {
347
407
  api;
348
408
  emitter;
349
409
  auth;
410
+ ai;
350
411
  tasks;
351
412
  sprints;
352
413
  workspaces;
@@ -358,7 +419,7 @@ class LocusClient {
358
419
  this.emitter = new LocusEmitter;
359
420
  this.api = import_axios.default.create({
360
421
  baseURL: config.baseUrl,
361
- timeout: config.timeout || 1e4,
422
+ timeout: config.timeout || 60000,
362
423
  headers: {
363
424
  "Content-Type": "application/json",
364
425
  ...config.token ? { Authorization: `Bearer ${config.token}` } : {}
@@ -366,6 +427,7 @@ class LocusClient {
366
427
  });
367
428
  this.setupInterceptors();
368
429
  this.auth = new AuthModule(this.api, this.emitter);
430
+ this.ai = new AIModule(this.api, this.emitter);
369
431
  this.tasks = new TasksModule(this.api, this.emitter);
370
432
  this.sprints = new SprintsModule(this.api, this.emitter);
371
433
  this.workspaces = new WorkspacesModule(this.api, this.emitter);
@@ -439,7 +501,6 @@ __export(exports_worker, {
439
501
  AgentWorker: () => AgentWorker
440
502
  });
441
503
  module.exports = __toCommonJS(exports_worker);
442
- var import_shared3 = require("@locusai/shared");
443
504
 
444
505
  // src/core/config.ts
445
506
  var import_node_path = require("node:path");
@@ -456,7 +517,9 @@ var LOCUS_CONFIG = {
456
517
  configFile: "config.json",
457
518
  indexFile: "codebase-index.json",
458
519
  contextFile: "CLAUDE.md",
459
- artifactsDir: "artifacts"
520
+ artifactsDir: "artifacts",
521
+ documentsDir: "documents",
522
+ agentSkillsDir: ".agent/skills"
460
523
  };
461
524
  function getLocusPath(projectPath, fileName) {
462
525
  if (fileName === "contextFile") {
@@ -571,6 +634,7 @@ class ClaudeRunner {
571
634
  let finalResult = "";
572
635
  let errorOutput = "";
573
636
  let buffer = "";
637
+ let stderrBuffer = "";
574
638
  claude.stdout.on("data", (data) => {
575
639
  buffer += data.toString();
576
640
  const lines = buffer.split(`
@@ -583,14 +647,27 @@ class ClaudeRunner {
583
647
  }
584
648
  });
585
649
  claude.stderr.on("data", (data) => {
586
- const msg = data.toString();
587
- errorOutput += msg;
588
- process.stderr.write(msg);
650
+ const chunk = data.toString();
651
+ errorOutput += chunk;
652
+ stderrBuffer += chunk;
653
+ const lines = stderrBuffer.split(`
654
+ `);
655
+ stderrBuffer = lines.pop() || "";
656
+ for (const line of lines) {
657
+ if (!this.shouldSuppressLine(line)) {
658
+ process.stderr.write(`${line}
659
+ `);
660
+ }
661
+ }
589
662
  });
590
663
  claude.on("error", (err) => {
591
664
  reject(new Error(`Failed to start Claude CLI: ${err.message}. Please ensure the 'claude' command is available in your PATH.`));
592
665
  });
593
666
  claude.on("close", (code) => {
667
+ if (stderrBuffer && !this.shouldSuppressLine(stderrBuffer)) {
668
+ process.stderr.write(`${stderrBuffer}
669
+ `);
670
+ }
594
671
  process.stdout.write(`
595
672
  `);
596
673
  if (code === 0) {
@@ -623,12 +700,8 @@ class ClaudeRunner {
623
700
  return null;
624
701
  }
625
702
  handleEvent(event) {
626
- const { type, delta, content_block } = event;
627
- if (type === "content_block_delta" && delta) {
628
- if (delta.type === "text_delta" && delta.text) {
629
- this.log?.(delta.text, "info");
630
- }
631
- } else if (type === "content_block_start" && content_block) {
703
+ const { type, content_block } = event;
704
+ if (type === "content_block_start" && content_block) {
632
705
  if (content_block.type === "tool_use" && content_block.name) {
633
706
  this.log?.(`
634
707
 
@@ -637,6 +710,10 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
637
710
  }
638
711
  }
639
712
  }
713
+ shouldSuppressLine(line) {
714
+ const infoLogRegex = /^\[\d{2}:\d{2}:\d{2}\]\s\[.*?\]\sℹ\s*$/;
715
+ return infoLogRegex.test(line.trim());
716
+ }
640
717
  createExecutionError(code, detail) {
641
718
  const errorMsg = detail.trim();
642
719
  const message = errorMsg ? `Claude CLI error (exit code ${code}): ${errorMsg}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in.`;
@@ -715,7 +792,13 @@ class CodexRunner {
715
792
  });
716
793
  }
717
794
  buildArgs(outputPath) {
718
- const args = ["exec", "--full-auto", "--output-last-message", outputPath];
795
+ const args = [
796
+ "exec",
797
+ "--full-auto",
798
+ "--skip-git-repo-check",
799
+ "--output-last-message",
800
+ outputPath
801
+ ];
719
802
  if (this.model) {
720
803
  args.push("--model", this.model);
721
804
  }
@@ -781,6 +864,7 @@ function createAiRunner(provider, config) {
781
864
  // src/agent/artifact-syncer.ts
782
865
  var import_node_fs2 = require("node:fs");
783
866
  var import_node_path4 = require("node:path");
867
+ var import_shared2 = require("@locusai/shared");
784
868
  class ArtifactSyncer {
785
869
  deps;
786
870
  constructor(deps) {
@@ -812,10 +896,13 @@ class ArtifactSyncer {
812
896
  }
813
897
  try {
814
898
  const files = import_node_fs2.readdirSync(artifactsDir);
815
- if (files.length === 0)
816
- return;
817
899
  this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
818
- const artifactsGroupId = await this.getOrCreateArtifactsGroup();
900
+ const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
901
+ const groupMap = new Map(groups.map((g) => [g.id, g.name]));
902
+ let artifactsGroupId = groups.find((g) => g.name === "Artifacts")?.id;
903
+ if (!artifactsGroupId) {
904
+ artifactsGroupId = await this.getOrCreateArtifactsGroup();
905
+ }
819
906
  const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
820
907
  for (const file of files) {
821
908
  const filePath = import_node_path4.join(artifactsDir, file);
@@ -834,12 +921,35 @@ class ArtifactSyncer {
834
921
  await this.deps.client.docs.create(this.deps.workspaceId, {
835
922
  title,
836
923
  content,
837
- groupId: artifactsGroupId
924
+ groupId: artifactsGroupId,
925
+ type: import_shared2.DocType.GENERAL
838
926
  });
839
927
  this.deps.log(`Created artifact: ${file}`, "success");
840
928
  }
841
929
  }
842
930
  }
931
+ for (const doc of existingDocs) {
932
+ if (doc.groupId === artifactsGroupId) {
933
+ const fileName = `${doc.title}.md`;
934
+ const filePath = import_node_path4.join(artifactsDir, fileName);
935
+ if (!import_node_fs2.existsSync(filePath)) {
936
+ import_node_fs2.writeFileSync(filePath, doc.content || "");
937
+ this.deps.log(`Fetched artifact: ${fileName}`, "success");
938
+ }
939
+ } else {
940
+ const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
941
+ const groupName = groupMap.get(doc.groupId || "") || "General";
942
+ const groupDir = import_node_path4.join(documentsDir, groupName);
943
+ if (!import_node_fs2.existsSync(groupDir)) {
944
+ import_node_fs2.mkdirSync(groupDir, { recursive: true });
945
+ }
946
+ const fileName = `${doc.title}.md`;
947
+ const filePath = import_node_path4.join(groupDir, fileName);
948
+ if (!import_node_fs2.existsSync(filePath) || import_node_fs2.readFileSync(filePath, "utf-8") !== doc.content) {
949
+ import_node_fs2.writeFileSync(filePath, doc.content || "");
950
+ }
951
+ }
952
+ }
843
953
  } catch (error) {
844
954
  this.deps.log(`Failed to sync artifacts: ${error}`, "error");
845
955
  }
@@ -1107,53 +1217,17 @@ Return ONLY valid JSON, no markdown formatting.`;
1107
1217
  }
1108
1218
  }
1109
1219
 
1110
- // src/agent/sprint-planner.ts
1111
- class SprintPlanner {
1112
- deps;
1113
- constructor(deps) {
1114
- this.deps = deps;
1115
- }
1116
- async planSprint(sprint, tasks2) {
1117
- this.deps.log(`Planning sprint: ${sprint.name}`, "info");
1118
- try {
1119
- const taskList = tasks2.map((t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`).join(`
1120
- `);
1121
- const planningPrompt = `# Sprint Planning: ${sprint.name}
1122
-
1123
- 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.
1124
-
1125
- ## Tasks
1126
- ${taskList}
1127
-
1128
- ## Instructions
1129
- 1. Analyze dependencies between these tasks.
1130
- 2. Prioritize them for the most efficient execution.
1131
- 3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
1132
- 4. Output your final plan. The plan should clearly state the order of execution.
1133
-
1134
- **IMPORTANT**:
1135
- - Do NOT create any files on the filesystem during this planning phase.
1136
- - Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
1137
- - Your output will be saved as the official sprint mindmap on the server.`;
1138
- const plan = await this.deps.aiRunner.run(planningPrompt, true);
1139
- this.deps.log("Sprint mindmap generated and posted to server.", "success");
1140
- return plan;
1141
- } catch (error) {
1142
- this.deps.log(`Sprint planning failed: ${error}`, "error");
1143
- return sprint.mindmap || "";
1144
- }
1145
- }
1146
- }
1147
-
1148
1220
  // src/core/prompt-builder.ts
1149
1221
  var import_node_fs4 = require("node:fs");
1150
- var import_shared2 = require("@locusai/shared");
1222
+ var import_node_os2 = require("node:os");
1223
+ var import_node_path6 = require("node:path");
1224
+ var import_shared3 = require("@locusai/shared");
1151
1225
  class PromptBuilder {
1152
1226
  projectPath;
1153
1227
  constructor(projectPath) {
1154
1228
  this.projectPath = projectPath;
1155
1229
  }
1156
- async build(task) {
1230
+ async build(task, options = {}) {
1157
1231
  let prompt = `# Task: ${task.title}
1158
1232
 
1159
1233
  `;
@@ -1168,18 +1242,81 @@ You are acting as a ${roleText}.
1168
1242
  ${task.description || "No description provided."}
1169
1243
 
1170
1244
  `;
1245
+ const projectConfig = this.getProjectConfig();
1246
+ if (projectConfig) {
1247
+ prompt += `## Project Metadata
1248
+ `;
1249
+ prompt += `- Version: ${projectConfig.version || "Unknown"}
1250
+ `;
1251
+ prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
1252
+
1253
+ `;
1254
+ }
1255
+ let serverContext = null;
1256
+ if (options.taskContext) {
1257
+ try {
1258
+ serverContext = JSON.parse(options.taskContext);
1259
+ } catch {
1260
+ serverContext = { context: options.taskContext };
1261
+ }
1262
+ }
1171
1263
  const contextPath = getLocusPath(this.projectPath, "contextFile");
1264
+ let hasLocalContext = false;
1172
1265
  if (import_node_fs4.existsSync(contextPath)) {
1173
1266
  try {
1174
1267
  const context = import_node_fs4.readFileSync(contextPath, "utf-8");
1175
- prompt += `## Project Context (from CLAUDE.md)
1268
+ if (context.trim().length > 20) {
1269
+ prompt += `## Project Context (Local)
1176
1270
  ${context}
1177
1271
 
1178
1272
  `;
1273
+ hasLocalContext = true;
1274
+ }
1179
1275
  } catch (err) {
1180
1276
  console.warn(`Warning: Could not read context file: ${err}`);
1181
1277
  }
1182
1278
  }
1279
+ if (!hasLocalContext) {
1280
+ const fallback = this.getFallbackContext();
1281
+ if (fallback) {
1282
+ prompt += `## Project Context (README Fallback)
1283
+ ${fallback}
1284
+
1285
+ `;
1286
+ }
1287
+ }
1288
+ if (serverContext) {
1289
+ prompt += `## Project Context (Server)
1290
+ `;
1291
+ if (serverContext.project) {
1292
+ prompt += `- Project: ${serverContext.project.name || "Unknown"}
1293
+ `;
1294
+ if (!hasLocalContext && serverContext.project.techStack?.length) {
1295
+ prompt += `- Tech Stack: ${serverContext.project.techStack.join(", ")}
1296
+ `;
1297
+ }
1298
+ }
1299
+ if (serverContext.context) {
1300
+ prompt += `
1301
+ ${serverContext.context}
1302
+ `;
1303
+ }
1304
+ prompt += `
1305
+ `;
1306
+ }
1307
+ prompt += this.getProjectStructure();
1308
+ prompt += this.getSkillsInfo();
1309
+ prompt += `## Project Knowledge Base
1310
+ `;
1311
+ prompt += `You have access to the following documentation directories for context:
1312
+ `;
1313
+ prompt += `- Artifacts: \`.locus/artifacts\`
1314
+ `;
1315
+ prompt += `- Documents: \`.locus/documents\`
1316
+ `;
1317
+ prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
1318
+
1319
+ `;
1183
1320
  const indexPath = getLocusPath(this.projectPath, "indexFile");
1184
1321
  if (import_node_fs4.existsSync(indexPath)) {
1185
1322
  prompt += `## Codebase Overview
@@ -1188,11 +1325,19 @@ There is an index file in the .locus/codebase-index.json and if you need you can
1188
1325
  `;
1189
1326
  }
1190
1327
  if (task.docs && task.docs.length > 0) {
1191
- prompt += `## Attached Documents
1328
+ prompt += `## Attached Documents (Summarized)
1329
+ `;
1330
+ prompt += `> Full content available on server. Rely on Task Description for specific requirements.
1331
+
1192
1332
  `;
1193
1333
  for (const doc of task.docs) {
1194
- prompt += `### ${doc.title}
1195
- ${doc.content || "(No content)"}
1334
+ const content = doc.content || "";
1335
+ const limit = 800;
1336
+ const preview = content.slice(0, limit);
1337
+ const isTruncated = content.length > limit;
1338
+ prompt += `### Doc: ${doc.title}
1339
+ ${preview}${isTruncated ? `
1340
+ ...(truncated)...` : ""}
1196
1341
 
1197
1342
  `;
1198
1343
  }
@@ -1208,7 +1353,7 @@ ${doc.content || "(No content)"}
1208
1353
  `;
1209
1354
  }
1210
1355
  if (task.comments && task.comments.length > 0) {
1211
- const comments = task.comments.slice(0, 5);
1356
+ const comments = task.comments.slice(0, 3);
1212
1357
  prompt += `## Task History & Feedback
1213
1358
  `;
1214
1359
  prompt += `Review the following comments for context or rejection feedback:
@@ -1230,20 +1375,122 @@ ${comment.text}
1230
1375
  `;
1231
1376
  return prompt;
1232
1377
  }
1378
+ getProjectConfig() {
1379
+ const configPath = getLocusPath(this.projectPath, "configFile");
1380
+ if (import_node_fs4.existsSync(configPath)) {
1381
+ try {
1382
+ return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
1383
+ } catch {
1384
+ return null;
1385
+ }
1386
+ }
1387
+ return null;
1388
+ }
1389
+ getFallbackContext() {
1390
+ const readmePath = import_node_path6.join(this.projectPath, "README.md");
1391
+ if (import_node_fs4.existsSync(readmePath)) {
1392
+ try {
1393
+ const content = import_node_fs4.readFileSync(readmePath, "utf-8");
1394
+ const limit = 1000;
1395
+ return content.slice(0, limit) + (content.length > limit ? `
1396
+ ...(truncated)...` : "");
1397
+ } catch {
1398
+ return "";
1399
+ }
1400
+ }
1401
+ return "";
1402
+ }
1403
+ getProjectStructure() {
1404
+ try {
1405
+ const entries = import_node_fs4.readdirSync(this.projectPath);
1406
+ const folders = entries.filter((e) => {
1407
+ if (e.startsWith(".") || e === "node_modules")
1408
+ return false;
1409
+ try {
1410
+ return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
1411
+ } catch {
1412
+ return false;
1413
+ }
1414
+ });
1415
+ if (folders.length === 0)
1416
+ return "";
1417
+ let structure = `## Project Structure
1418
+ `;
1419
+ structure += `Key directories in this project:
1420
+ `;
1421
+ for (const folder of folders) {
1422
+ structure += `- \`${folder}/\`
1423
+ `;
1424
+ }
1425
+ return `${structure}
1426
+ `;
1427
+ } catch {
1428
+ return "";
1429
+ }
1430
+ }
1431
+ getSkillsInfo() {
1432
+ const projectSkillsDirs = [
1433
+ LOCUS_CONFIG.agentSkillsDir,
1434
+ ".cursor/skills",
1435
+ ".claude/skills",
1436
+ ".codex/skills",
1437
+ ".gemini/skills"
1438
+ ];
1439
+ const globalHome = import_node_os2.homedir();
1440
+ const globalSkillsDirs = [
1441
+ import_node_path6.join(globalHome, ".cursor/skills"),
1442
+ import_node_path6.join(globalHome, ".claude/skills"),
1443
+ import_node_path6.join(globalHome, ".codex/skills"),
1444
+ import_node_path6.join(globalHome, ".gemini/skills")
1445
+ ];
1446
+ const allSkillNames = new Set;
1447
+ for (const relativePath of projectSkillsDirs) {
1448
+ const fullPath = import_node_path6.join(this.projectPath, relativePath);
1449
+ this.scanSkillsInDirectory(fullPath, allSkillNames);
1450
+ }
1451
+ for (const fullPath of globalSkillsDirs) {
1452
+ this.scanSkillsInDirectory(fullPath, allSkillNames);
1453
+ }
1454
+ const uniqueSkills = Array.from(allSkillNames).sort();
1455
+ if (uniqueSkills.length === 0)
1456
+ return "";
1457
+ return `## Available Agent Skills
1458
+ ` + `The project has the following specialized skills available (from project or global locations):
1459
+ ` + uniqueSkills.map((s) => `- ${s}`).join(`
1460
+ `) + `
1461
+
1462
+ `;
1463
+ }
1464
+ scanSkillsInDirectory(dirPath, skillSet) {
1465
+ if (!import_node_fs4.existsSync(dirPath))
1466
+ return;
1467
+ try {
1468
+ const entries = import_node_fs4.readdirSync(dirPath).filter((name) => {
1469
+ try {
1470
+ return import_node_fs4.statSync(import_node_path6.join(dirPath, name)).isDirectory();
1471
+ } catch {
1472
+ return false;
1473
+ }
1474
+ });
1475
+ for (const entry of entries) {
1476
+ skillSet.add(entry);
1477
+ }
1478
+ } catch {}
1479
+ }
1233
1480
  roleToText(role) {
1234
1481
  if (!role) {
1235
1482
  return null;
1236
1483
  }
1237
1484
  switch (role) {
1238
- case import_shared2.AssigneeRole.BACKEND:
1485
+ case import_shared3.AssigneeRole.BACKEND:
1239
1486
  return "Backend Engineer";
1240
- case import_shared2.AssigneeRole.FRONTEND:
1487
+ case import_shared3.AssigneeRole.FRONTEND:
1241
1488
  return "Frontend Engineer";
1242
- case import_shared2.AssigneeRole.PM:
1489
+ case import_shared3.AssigneeRole.PM:
1243
1490
  return "Product Manager";
1244
- case import_shared2.AssigneeRole.QA:
1491
+ case import_shared3.AssigneeRole.QA:
1245
1492
  return "QA Engineer";
1246
- case import_shared2.AssigneeRole.DESIGN:
1493
+ case import_shared3.AssigneeRole.DESIGN:
1247
1494
  return "Product Designer";
1248
1495
  default:
1249
1496
  return "engineer";
@@ -1259,18 +1506,11 @@ class TaskExecutor {
1259
1506
  this.deps = deps;
1260
1507
  this.promptBuilder = new PromptBuilder(deps.projectPath);
1261
1508
  }
1262
- updateSprintPlan(sprintPlan) {
1263
- this.deps.sprintPlan = sprintPlan;
1264
- }
1265
- async execute(task) {
1509
+ async execute(task, context) {
1266
1510
  this.deps.log(`Executing: ${task.title}`, "info");
1267
- let basePrompt = await this.promptBuilder.build(task);
1268
- if (this.deps.sprintPlan) {
1269
- basePrompt = `## Sprint Context
1270
- ${this.deps.sprintPlan}
1271
-
1272
- ${basePrompt}`;
1273
- }
1511
+ const basePrompt = await this.promptBuilder.build(task, {
1512
+ taskContext: context
1513
+ });
1274
1514
  try {
1275
1515
  let plan = null;
1276
1516
  if (this.deps.skipPlanning) {
@@ -1329,16 +1569,14 @@ class AgentWorker {
1329
1569
  config;
1330
1570
  client;
1331
1571
  aiRunner;
1332
- sprintPlanner;
1333
1572
  indexerService;
1334
1573
  artifactSyncer;
1335
1574
  taskExecutor;
1336
1575
  consecutiveEmpty = 0;
1337
- maxEmpty = 5;
1576
+ maxEmpty = 60;
1338
1577
  maxTasks = 50;
1339
1578
  tasksCompleted = 0;
1340
1579
  pollInterval = 1e4;
1341
- sprintPlan = null;
1342
1580
  constructor(config) {
1343
1581
  this.config = config;
1344
1582
  const projectPath = config.projectPath || process.cwd();
@@ -1359,10 +1597,6 @@ class AgentWorker {
1359
1597
  model: config.model,
1360
1598
  log
1361
1599
  });
1362
- this.sprintPlanner = new SprintPlanner({
1363
- aiRunner: this.aiRunner,
1364
- log
1365
- });
1366
1600
  this.indexerService = new CodebaseIndexerService({
1367
1601
  aiRunner: this.aiRunner,
1368
1602
  projectPath,
@@ -1377,7 +1611,6 @@ class AgentWorker {
1377
1611
  this.taskExecutor = new TaskExecutor({
1378
1612
  aiRunner: this.aiRunner,
1379
1613
  projectPath,
1380
- sprintPlan: null,
1381
1614
  skipPlanning: config.skipPlanning,
1382
1615
  log
1383
1616
  });
@@ -1416,8 +1649,13 @@ class AgentWorker {
1416
1649
  }
1417
1650
  async executeTask(task) {
1418
1651
  const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
1419
- this.taskExecutor.updateSprintPlan(this.sprintPlan);
1420
- const result = await this.taskExecutor.execute(fullTask);
1652
+ let context = "";
1653
+ try {
1654
+ context = await this.client.tasks.getContext(task.id, this.config.workspaceId);
1655
+ } catch (err) {
1656
+ this.log(`Failed to fetch task context: ${err}`, "warn");
1657
+ }
1658
+ const result = await this.taskExecutor.execute(fullTask, context);
1421
1659
  await this.indexerService.reindex();
1422
1660
  return result;
1423
1661
  }
@@ -1425,35 +1663,12 @@ class AgentWorker {
1425
1663
  this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
1426
1664
  const sprint = await this.getActiveSprint();
1427
1665
  if (sprint) {
1428
- this.log(`Found active sprint: ${sprint.name} (${sprint.id})`, "info");
1429
- const tasks2 = await this.client.tasks.list(this.config.workspaceId, {
1430
- sprintId: sprint.id
1431
- });
1432
- const activeTasks = tasks2.filter((t) => t.status === import_shared3.TaskStatus.BACKLOG || t.status === import_shared3.TaskStatus.IN_PROGRESS);
1433
- this.log(`Sprint tasks found: ${activeTasks.length}`, "info");
1434
- if (activeTasks.length <= 1) {
1435
- this.log("Skipping mindmap generation (only one task in sprint).", "info");
1436
- this.sprintPlan = null;
1437
- } else {
1438
- const latestTaskCreation = activeTasks.reduce((latest, task) => {
1439
- const taskDate = new Date(task.createdAt);
1440
- return taskDate > latest ? taskDate : latest;
1441
- }, new Date(0));
1442
- const mindmapDate = sprint.mindmapUpdatedAt ? new Date(sprint.mindmapUpdatedAt) : new Date(0);
1443
- const needsPlanning = !sprint.mindmap || sprint.mindmap.trim() === "" || latestTaskCreation > mindmapDate;
1444
- if (needsPlanning) {
1445
- if (sprint.mindmap && latestTaskCreation > mindmapDate) {
1446
- this.log("New tasks have been added to the sprint since last mindmap. Regenerating...", "warn");
1447
- }
1448
- this.sprintPlan = await this.sprintPlanner.planSprint(sprint, tasks2);
1449
- await this.client.sprints.update(sprint.id, this.config.workspaceId, {
1450
- mindmap: this.sprintPlan,
1451
- mindmapUpdatedAt: new Date
1452
- });
1453
- } else {
1454
- this.log("Using existing sprint mindmap.", "info");
1455
- this.sprintPlan = sprint.mindmap ?? null;
1456
- }
1666
+ this.log(`Active sprint found: ${sprint.name}. Ensuring plan is up to date...`, "info");
1667
+ try {
1668
+ await this.client.sprints.triggerAIPlanning(sprint.id, this.config.workspaceId);
1669
+ this.log(`Sprint plan sync checked on server.`, "success");
1670
+ } catch (err) {
1671
+ this.log(`Sprint planning sync failed (non-critical): ${err instanceof Error ? err.message : String(err)}`, "warn");
1457
1672
  }
1458
1673
  } else {
1459
1674
  this.log("No active sprint found for planning.", "warn");
@@ -1473,7 +1688,11 @@ class AgentWorker {
1473
1688
  this.consecutiveEmpty = 0;
1474
1689
  this.log(`Claimed: ${task.title}`, "success");
1475
1690
  const result = await this.executeTask(task);
1476
- await this.artifactSyncer.sync();
1691
+ try {
1692
+ await this.artifactSyncer.sync();
1693
+ } catch (err) {
1694
+ this.log(`Artifact sync failed: ${err}`, "error");
1695
+ }
1477
1696
  if (result.success) {
1478
1697
  this.log(`Completed: ${task.title}`, "success");
1479
1698
  await this.client.tasks.update(task.id, this.config.workspaceId, {
@@ -1510,7 +1729,7 @@ if (process.argv[1]?.includes("agent-worker") || process.argv[1]?.includes("work
1510
1729
  config.workspaceId = args[++i];
1511
1730
  else if (arg === "--sprint-id")
1512
1731
  config.sprintId = args[++i];
1513
- else if (arg === "--api-base")
1732
+ else if (arg === "--api-url")
1514
1733
  config.apiBase = args[++i];
1515
1734
  else if (arg === "--api-key")
1516
1735
  config.apiKey = args[++i];
@@ -1547,7 +1766,6 @@ __export(exports_index_node, {
1547
1766
  TasksModule: () => TasksModule,
1548
1767
  TaskExecutor: () => TaskExecutor,
1549
1768
  SprintsModule: () => SprintsModule,
1550
- SprintPlanner: () => SprintPlanner,
1551
1769
  PromptBuilder: () => PromptBuilder,
1552
1770
  PROVIDER: () => PROVIDER,
1553
1771
  OrganizationsModule: () => OrganizationsModule,
@@ -1566,13 +1784,14 @@ __export(exports_index_node, {
1566
1784
  AuthModule: () => AuthModule,
1567
1785
  ArtifactSyncer: () => ArtifactSyncer,
1568
1786
  AgentWorker: () => AgentWorker,
1569
- AgentOrchestrator: () => AgentOrchestrator
1787
+ AgentOrchestrator: () => AgentOrchestrator,
1788
+ AIModule: () => AIModule
1570
1789
  });
1571
1790
  module.exports = __toCommonJS(exports_index_node);
1572
1791
  // src/orchestrator.ts
1573
1792
  var import_node_child_process3 = require("node:child_process");
1574
1793
  var import_node_fs5 = require("node:fs");
1575
- var import_node_path6 = require("node:path");
1794
+ var import_node_path7 = require("node:path");
1576
1795
  var import_node_url = require("node:url");
1577
1796
  var import_shared4 = require("@locusai/shared");
1578
1797
  var import_events3 = require("events");
@@ -1668,8 +1887,8 @@ ${c.success("✅ Orchestrator finished")}`);
1668
1887
  `);
1669
1888
  const potentialPaths = [];
1670
1889
  const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
1671
- const currentModuleDir = import_node_path6.dirname(currentModulePath);
1672
- potentialPaths.push(import_node_path6.join(currentModuleDir, "agent", "worker.js"), import_node_path6.join(currentModuleDir, "worker.js"), import_node_path6.join(currentModuleDir, "agent", "worker.ts"));
1890
+ const currentModuleDir = import_node_path7.dirname(currentModulePath);
1891
+ 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"));
1673
1892
  const workerPath = potentialPaths.find((p) => import_node_fs5.existsSync(p));
1674
1893
  if (!workerPath) {
1675
1894
  throw new Error(`Worker file not found. Checked: ${potentialPaths.join(", ")}. ` + `Make sure the SDK is properly built and installed.`);
@@ -1679,7 +1898,7 @@ ${c.success("✅ Orchestrator finished")}`);
1679
1898
  agentId,
1680
1899
  "--workspace-id",
1681
1900
  this.config.workspaceId,
1682
- "--api-base",
1901
+ "--api-url",
1683
1902
  this.config.apiBase,
1684
1903
  "--api-key",
1685
1904
  this.config.apiKey,