@locusai/sdk 0.4.6 → 0.4.9

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 (58) hide show
  1. package/dist/index-node.js +1590 -20
  2. package/dist/index.js +429 -121
  3. package/dist/orchestrator.d.ts.map +1 -1
  4. package/package.json +12 -23
  5. package/dist/agent/artifact-syncer.js +0 -77
  6. package/dist/agent/codebase-indexer-service.js +0 -55
  7. package/dist/agent/index.js +0 -5
  8. package/dist/agent/sprint-planner.js +0 -68
  9. package/dist/agent/task-executor.js +0 -60
  10. package/dist/agent/worker.js +0 -252
  11. package/dist/ai/anthropic-client.js +0 -70
  12. package/dist/ai/claude-runner.js +0 -71
  13. package/dist/ai/index.js +0 -2
  14. package/dist/core/config.js +0 -15
  15. package/dist/core/index.js +0 -3
  16. package/dist/core/indexer.js +0 -113
  17. package/dist/core/prompt-builder.js +0 -83
  18. package/dist/events.js +0 -15
  19. package/dist/modules/auth.js +0 -23
  20. package/dist/modules/base.js +0 -8
  21. package/dist/modules/ci.js +0 -7
  22. package/dist/modules/docs.js +0 -38
  23. package/dist/modules/invitations.js +0 -22
  24. package/dist/modules/organizations.js +0 -39
  25. package/dist/modules/sprints.js +0 -34
  26. package/dist/modules/tasks.js +0 -56
  27. package/dist/modules/workspaces.js +0 -49
  28. package/dist/orchestrator.js +0 -356
  29. package/dist/utils/colors.js +0 -54
  30. package/dist/utils/retry.js +0 -37
  31. package/src/agent/artifact-syncer.ts +0 -111
  32. package/src/agent/codebase-indexer-service.ts +0 -71
  33. package/src/agent/index.ts +0 -5
  34. package/src/agent/sprint-planner.ts +0 -86
  35. package/src/agent/task-executor.ts +0 -85
  36. package/src/agent/worker.ts +0 -322
  37. package/src/ai/anthropic-client.ts +0 -93
  38. package/src/ai/claude-runner.ts +0 -86
  39. package/src/ai/index.ts +0 -2
  40. package/src/core/config.ts +0 -21
  41. package/src/core/index.ts +0 -3
  42. package/src/core/indexer.ts +0 -131
  43. package/src/core/prompt-builder.ts +0 -91
  44. package/src/events.ts +0 -35
  45. package/src/index-node.ts +0 -23
  46. package/src/index.ts +0 -159
  47. package/src/modules/auth.ts +0 -48
  48. package/src/modules/base.ts +0 -9
  49. package/src/modules/ci.ts +0 -12
  50. package/src/modules/docs.ts +0 -84
  51. package/src/modules/invitations.ts +0 -45
  52. package/src/modules/organizations.ts +0 -90
  53. package/src/modules/sprints.ts +0 -69
  54. package/src/modules/tasks.ts +0 -110
  55. package/src/modules/workspaces.ts +0 -94
  56. package/src/orchestrator.ts +0 -473
  57. package/src/utils/colors.ts +0 -63
  58. package/src/utils/retry.ts +0 -56
@@ -1,20 +1,1590 @@
1
- /**
2
- * Node.js-only exports
3
- * This is a separate entry point for Node.js/CLI only
4
- * It should NOT be imported by browser applications
5
- *
6
- * These modules use Node.js APIs (fs, child_process, etc.)
7
- * and will break in browser environments
8
- */
9
- // Node.js-only: Agent system
10
- export * from "./agent/index.js";
11
- // Node.js-only: AI clients
12
- export * from "./ai/index.js";
13
- // Node.js-only: Core utilities (uses fs)
14
- export * from "./core/index.js";
15
- // Re-export everything from main index (browser-safe)
16
- export * from "./index.js";
17
- // Node.js-only: Orchestrator
18
- export { AgentOrchestrator } from "./orchestrator.js";
19
- // Utilities
20
- export { c } from "./utils/colors.js";
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
19
+ var __toCommonJS = (from) => {
20
+ var entry = __moduleCache.get(from), desc;
21
+ if (entry)
22
+ return entry;
23
+ entry = __defProp({}, "__esModule", { value: true });
24
+ if (from && typeof from === "object" || typeof from === "function")
25
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
26
+ get: () => from[key],
27
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
28
+ }));
29
+ __moduleCache.set(from, entry);
30
+ return entry;
31
+ };
32
+ var __export = (target, all) => {
33
+ for (var name in all)
34
+ __defProp(target, name, {
35
+ get: all[name],
36
+ enumerable: true,
37
+ configurable: true,
38
+ set: (newValue) => all[name] = () => newValue
39
+ });
40
+ };
41
+
42
+ // src/index.ts
43
+ var exports_src = {};
44
+ __export(exports_src, {
45
+ WorkspacesModule: () => WorkspacesModule,
46
+ TasksModule: () => TasksModule,
47
+ SprintsModule: () => SprintsModule,
48
+ OrganizationsModule: () => OrganizationsModule,
49
+ LocusEvent: () => LocusEvent,
50
+ LocusEmitter: () => LocusEmitter,
51
+ LocusClient: () => LocusClient,
52
+ InvitationsModule: () => InvitationsModule,
53
+ DocsModule: () => DocsModule,
54
+ CiModule: () => CiModule,
55
+ AuthModule: () => AuthModule
56
+ });
57
+ module.exports = __toCommonJS(exports_src);
58
+ var import_axios = __toESM(require("axios"));
59
+
60
+ // src/events.ts
61
+ var import_events = require("events");
62
+ var LocusEvent;
63
+ ((LocusEvent2) => {
64
+ LocusEvent2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
65
+ LocusEvent2["AUTH_ERROR"] = "AUTH_ERROR";
66
+ LocusEvent2["REQUEST_ERROR"] = "REQUEST_ERROR";
67
+ })(LocusEvent ||= {});
68
+
69
+ class LocusEmitter extends import_events.EventEmitter {
70
+ on(event, listener) {
71
+ return super.on(event, listener);
72
+ }
73
+ emit(event, ...args) {
74
+ return super.emit(event, ...args);
75
+ }
76
+ }
77
+
78
+ // src/modules/base.ts
79
+ class BaseModule {
80
+ api;
81
+ emitter;
82
+ constructor(api, emitter) {
83
+ this.api = api;
84
+ this.emitter = emitter;
85
+ }
86
+ }
87
+
88
+ // src/modules/auth.ts
89
+ class AuthModule extends BaseModule {
90
+ async getMe() {
91
+ const { data } = await this.api.get("/auth/me");
92
+ return data;
93
+ }
94
+ async requestRegisterOtp(email) {
95
+ const { data } = await this.api.post("/auth/register-otp", { email });
96
+ return data;
97
+ }
98
+ async requestLoginOtp(email) {
99
+ const { data } = await this.api.post("/auth/login-otp", { email });
100
+ return data;
101
+ }
102
+ async verifyLogin(body) {
103
+ const { data } = await this.api.post("/auth/verify-login", body);
104
+ return data;
105
+ }
106
+ async completeRegistration(body) {
107
+ const { data } = await this.api.post("/auth/complete-registration", body);
108
+ return data;
109
+ }
110
+ }
111
+
112
+ // src/modules/ci.ts
113
+ class CiModule extends BaseModule {
114
+ async report(body) {
115
+ const { data } = await this.api.post("/ci/report", body);
116
+ return data;
117
+ }
118
+ }
119
+
120
+ // src/modules/docs.ts
121
+ class DocsModule extends BaseModule {
122
+ async create(workspaceId, body) {
123
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/docs`, body);
124
+ return data.doc;
125
+ }
126
+ async list(workspaceId) {
127
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/docs`);
128
+ return data.docs;
129
+ }
130
+ async getById(id, workspaceId) {
131
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/docs/${id}`);
132
+ return data.doc;
133
+ }
134
+ async update(id, workspaceId, body) {
135
+ const { data } = await this.api.put(`/workspaces/${workspaceId}/docs/${id}`, body);
136
+ return data.doc;
137
+ }
138
+ async delete(id, workspaceId) {
139
+ await this.api.delete(`/workspaces/${workspaceId}/docs/${id}`);
140
+ }
141
+ async listGroups(workspaceId) {
142
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/doc-groups`);
143
+ return data.groups;
144
+ }
145
+ async createGroup(workspaceId, body) {
146
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/doc-groups`, body);
147
+ return data.group;
148
+ }
149
+ async updateGroup(id, workspaceId, body) {
150
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/doc-groups/${id}`, body);
151
+ return data.group;
152
+ }
153
+ async deleteGroup(id, workspaceId) {
154
+ await this.api.delete(`/workspaces/${workspaceId}/doc-groups/${id}`);
155
+ }
156
+ }
157
+
158
+ // src/modules/invitations.ts
159
+ class InvitationsModule extends BaseModule {
160
+ async create(orgId, body) {
161
+ const { data } = await this.api.post(`/org/${orgId}/invitations`, body);
162
+ return data.invitation;
163
+ }
164
+ async list(orgId) {
165
+ const { data } = await this.api.get(`/org/${orgId}/invitations`);
166
+ return data.invitations;
167
+ }
168
+ async verify(token) {
169
+ const { data } = await this.api.get(`/invitations/verify/${token}`);
170
+ return data;
171
+ }
172
+ async accept(body) {
173
+ const { data } = await this.api.post("/invitations/accept", body);
174
+ return data;
175
+ }
176
+ async revoke(orgId, id) {
177
+ await this.api.delete(`/org/${orgId}/invitations/${id}`);
178
+ }
179
+ }
180
+
181
+ // src/modules/organizations.ts
182
+ class OrganizationsModule extends BaseModule {
183
+ async list() {
184
+ const { data } = await this.api.get("/organizations");
185
+ return data.organizations;
186
+ }
187
+ async getById(id) {
188
+ const { data } = await this.api.get(`/organizations/${id}`);
189
+ return data.organization;
190
+ }
191
+ async listMembers(id) {
192
+ const { data } = await this.api.get(`/organizations/${id}/members`);
193
+ return data.members;
194
+ }
195
+ async addMember(id, body) {
196
+ const { data } = await this.api.post(`/organizations/${id}/members`, body);
197
+ return data.membership;
198
+ }
199
+ async removeMember(orgId, userId) {
200
+ await this.api.delete(`/organizations/${orgId}/members/${userId}`);
201
+ }
202
+ async delete(orgId) {
203
+ await this.api.delete(`/organizations/${orgId}`);
204
+ }
205
+ async listApiKeys(orgId) {
206
+ const { data } = await this.api.get(`/organizations/${orgId}/api-keys`);
207
+ return data.apiKeys;
208
+ }
209
+ async createApiKey(orgId, name) {
210
+ const { data } = await this.api.post(`/organizations/${orgId}/api-keys`, { name });
211
+ return data.apiKey;
212
+ }
213
+ async deleteApiKey(orgId, keyId) {
214
+ await this.api.delete(`/organizations/${orgId}/api-keys/${keyId}`);
215
+ }
216
+ }
217
+
218
+ // src/modules/sprints.ts
219
+ class SprintsModule extends BaseModule {
220
+ async list(workspaceId) {
221
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints`);
222
+ return data.sprints;
223
+ }
224
+ async getActive(workspaceId) {
225
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/active`);
226
+ return data.sprint;
227
+ }
228
+ async getById(id, workspaceId) {
229
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/${id}`);
230
+ return data.sprint;
231
+ }
232
+ async create(workspaceId, body) {
233
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints`, body);
234
+ return data.sprint;
235
+ }
236
+ async update(id, workspaceId, body) {
237
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/sprints/${id}`, body);
238
+ return data.sprint;
239
+ }
240
+ async delete(id, workspaceId) {
241
+ await this.api.delete(`/workspaces/${workspaceId}/sprints/${id}`);
242
+ }
243
+ async start(id, workspaceId) {
244
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/start`);
245
+ return data.sprint;
246
+ }
247
+ async complete(id, workspaceId) {
248
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
249
+ return data.sprint;
250
+ }
251
+ }
252
+
253
+ // src/modules/tasks.ts
254
+ var import_shared = require("@locusai/shared");
255
+ class TasksModule extends BaseModule {
256
+ async list(workspaceId, options) {
257
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks`);
258
+ let tasks = data.tasks;
259
+ if (options?.sprintId) {
260
+ tasks = tasks.filter((t) => t.sprintId === options.sprintId);
261
+ }
262
+ if (options?.status) {
263
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
264
+ tasks = tasks.filter((t) => statuses.includes(t.status));
265
+ }
266
+ return tasks;
267
+ }
268
+ async getAvailable(workspaceId, sprintId) {
269
+ const tasks = await this.list(workspaceId, {
270
+ sprintId
271
+ });
272
+ return tasks.filter((t) => t.status === import_shared.TaskStatus.BACKLOG || t.status === import_shared.TaskStatus.IN_PROGRESS && !t.assignedTo);
273
+ }
274
+ async getById(id, workspaceId) {
275
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}`);
276
+ return data.task;
277
+ }
278
+ async create(workspaceId, body) {
279
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks`, body);
280
+ return data.task;
281
+ }
282
+ async update(id, workspaceId, body) {
283
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/tasks/${id}`, body);
284
+ return data.task;
285
+ }
286
+ async delete(id, workspaceId) {
287
+ await this.api.delete(`/workspaces/${workspaceId}/tasks/${id}`);
288
+ }
289
+ async getBacklog(workspaceId) {
290
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/backlog`);
291
+ return data.tasks;
292
+ }
293
+ async addComment(id, workspaceId, body) {
294
+ const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
295
+ return data.comment;
296
+ }
297
+ }
298
+
299
+ // src/modules/workspaces.ts
300
+ class WorkspacesModule extends BaseModule {
301
+ async listAll() {
302
+ const { data } = await this.api.get("/workspaces");
303
+ return data.workspaces;
304
+ }
305
+ async listByOrg(orgId) {
306
+ const { data } = await this.api.get(`/workspaces/org/${orgId}`);
307
+ return data.workspaces;
308
+ }
309
+ async create(body) {
310
+ const { orgId, ...bodyWithoutOrgId } = body;
311
+ const { data } = await this.api.post(`/workspaces/org/${orgId}`, bodyWithoutOrgId);
312
+ return data.workspace;
313
+ }
314
+ async createWithAutoOrg(body) {
315
+ const { data } = await this.api.post("/workspaces", body);
316
+ return data.workspace;
317
+ }
318
+ async getById(id) {
319
+ const { data } = await this.api.get(`/workspaces/${id}`);
320
+ return data.workspace;
321
+ }
322
+ async update(id, body) {
323
+ const { data } = await this.api.put(`/workspaces/${id}`, body);
324
+ return data.workspace;
325
+ }
326
+ async delete(id) {
327
+ await this.api.delete(`/workspaces/${id}`);
328
+ }
329
+ async getStats(id) {
330
+ const { data } = await this.api.get(`/workspaces/${id}/stats`);
331
+ return data;
332
+ }
333
+ async getActivity(id, limit) {
334
+ const { data } = await this.api.get(`/workspaces/${id}/activity`, {
335
+ params: { limit }
336
+ });
337
+ return data.activity;
338
+ }
339
+ async dispatch(id, workerId, sprintId) {
340
+ const { data } = await this.api.post(`/workspaces/${id}/dispatch`, { workerId, sprintId });
341
+ return data.task;
342
+ }
343
+ }
344
+
345
+ // src/index.ts
346
+ class LocusClient {
347
+ api;
348
+ emitter;
349
+ auth;
350
+ tasks;
351
+ sprints;
352
+ workspaces;
353
+ organizations;
354
+ invitations;
355
+ docs;
356
+ ci;
357
+ constructor(config) {
358
+ this.emitter = new LocusEmitter;
359
+ this.api = import_axios.default.create({
360
+ baseURL: config.baseUrl,
361
+ timeout: config.timeout || 1e4,
362
+ headers: {
363
+ "Content-Type": "application/json",
364
+ ...config.token ? { Authorization: `Bearer ${config.token}` } : {}
365
+ }
366
+ });
367
+ this.setupInterceptors();
368
+ this.auth = new AuthModule(this.api, this.emitter);
369
+ this.tasks = new TasksModule(this.api, this.emitter);
370
+ this.sprints = new SprintsModule(this.api, this.emitter);
371
+ this.workspaces = new WorkspacesModule(this.api, this.emitter);
372
+ this.organizations = new OrganizationsModule(this.api, this.emitter);
373
+ this.invitations = new InvitationsModule(this.api, this.emitter);
374
+ this.docs = new DocsModule(this.api, this.emitter);
375
+ this.ci = new CiModule(this.api, this.emitter);
376
+ if (config.retryOptions) {
377
+ this.setupRetryInterceptor(config.retryOptions);
378
+ }
379
+ }
380
+ setupRetryInterceptor(retryOptions) {
381
+ this.api.interceptors.response.use(undefined, async (error) => {
382
+ const config = error.config;
383
+ if (!config || !retryOptions) {
384
+ return Promise.reject(error);
385
+ }
386
+ config._retryCount = config._retryCount || 0;
387
+ const maxRetries = retryOptions.maxRetries ?? 3;
388
+ const shouldRetry = config._retryCount < maxRetries && (retryOptions.retryCondition ? retryOptions.retryCondition(error) : !error.response || error.response.status >= 500);
389
+ if (shouldRetry) {
390
+ config._retryCount++;
391
+ const delay = Math.min((retryOptions.initialDelay ?? 1000) * Math.pow(retryOptions.factor ?? 2, config._retryCount - 1), retryOptions.maxDelay ?? 5000);
392
+ await new Promise((resolve) => setTimeout(resolve, delay));
393
+ return this.api(config);
394
+ }
395
+ return Promise.reject(error);
396
+ });
397
+ }
398
+ setupInterceptors() {
399
+ this.api.interceptors.response.use((response) => {
400
+ if (response.data && typeof response.data === "object" && "data" in response.data) {
401
+ response.data = response.data.data;
402
+ }
403
+ return response;
404
+ }, (error) => {
405
+ const status = error.response?.status;
406
+ let message;
407
+ if (error.response?.data?.error?.message && typeof error.response.data.error.message === "string") {
408
+ message = error.response.data.error.message;
409
+ } else if (error.response?.data?.message && typeof error.response.data.message === "string") {
410
+ message = error.response.data.message;
411
+ } else if (error.message && typeof error.message === "string") {
412
+ message = error.message;
413
+ } else {
414
+ message = "An error occurred";
415
+ }
416
+ const enhancedError = new Error(message);
417
+ enhancedError.name = `HTTP${status || "Error"}`;
418
+ if (status === 401) {
419
+ this.emitter.emit("TOKEN_EXPIRED" /* TOKEN_EXPIRED */);
420
+ this.emitter.emit("AUTH_ERROR" /* AUTH_ERROR */, enhancedError);
421
+ } else {
422
+ this.emitter.emit("REQUEST_ERROR" /* REQUEST_ERROR */, enhancedError);
423
+ }
424
+ return Promise.reject(enhancedError);
425
+ });
426
+ }
427
+ setToken(token) {
428
+ if (token) {
429
+ this.api.defaults.headers.common.Authorization = `Bearer ${token}`;
430
+ } else {
431
+ delete this.api.defaults.headers.common.Authorization;
432
+ }
433
+ }
434
+ }
435
+
436
+ // src/index-node.ts
437
+ var exports_index_node = {};
438
+ __export(exports_index_node, {
439
+ getLocusPath: () => getLocusPath,
440
+ c: () => c,
441
+ WorkspacesModule: () => WorkspacesModule,
442
+ TasksModule: () => TasksModule,
443
+ TaskExecutor: () => TaskExecutor,
444
+ SprintsModule: () => SprintsModule,
445
+ SprintPlanner: () => SprintPlanner,
446
+ PromptBuilder: () => PromptBuilder,
447
+ OrganizationsModule: () => OrganizationsModule,
448
+ LocusEvent: () => LocusEvent,
449
+ LocusEmitter: () => LocusEmitter,
450
+ LocusClient: () => LocusClient,
451
+ LOCUS_CONFIG: () => LOCUS_CONFIG,
452
+ InvitationsModule: () => InvitationsModule,
453
+ DocsModule: () => DocsModule,
454
+ DEFAULT_MODEL: () => DEFAULT_MODEL,
455
+ CodebaseIndexerService: () => CodebaseIndexerService,
456
+ CodebaseIndexer: () => CodebaseIndexer,
457
+ ClaudeRunner: () => ClaudeRunner,
458
+ CiModule: () => CiModule,
459
+ AuthModule: () => AuthModule,
460
+ ArtifactSyncer: () => ArtifactSyncer,
461
+ AnthropicClient: () => AnthropicClient,
462
+ AgentWorker: () => AgentWorker,
463
+ AgentOrchestrator: () => AgentOrchestrator
464
+ });
465
+ module.exports = __toCommonJS(exports_index_node);
466
+
467
+ // src/agent/artifact-syncer.ts
468
+ var import_node_fs = require("node:fs");
469
+ var import_node_path2 = require("node:path");
470
+
471
+ // src/core/config.ts
472
+ var import_node_path = require("node:path");
473
+ var DEFAULT_MODEL = "sonnet";
474
+ var LOCUS_CONFIG = {
475
+ dir: ".locus",
476
+ configFile: "config.json",
477
+ indexFile: "codebase-index.json",
478
+ contextFile: "CLAUDE.md",
479
+ artifactsDir: "artifacts"
480
+ };
481
+ function getLocusPath(projectPath, fileName) {
482
+ if (fileName === "contextFile") {
483
+ return import_node_path.join(projectPath, LOCUS_CONFIG.contextFile);
484
+ }
485
+ return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
486
+ }
487
+
488
+ // src/agent/artifact-syncer.ts
489
+ class ArtifactSyncer {
490
+ deps;
491
+ constructor(deps) {
492
+ this.deps = deps;
493
+ }
494
+ async getOrCreateArtifactsGroup() {
495
+ try {
496
+ const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
497
+ const artifactsGroup = groups.find((g) => g.name === "Artifacts");
498
+ if (artifactsGroup) {
499
+ return artifactsGroup.id;
500
+ }
501
+ const newGroup = await this.deps.client.docs.createGroup(this.deps.workspaceId, {
502
+ name: "Artifacts",
503
+ order: 999
504
+ });
505
+ this.deps.log("Created 'Artifacts' group for agent-generated docs", "info");
506
+ return newGroup.id;
507
+ } catch (error) {
508
+ this.deps.log(`Failed to get/create Artifacts group: ${error}`, "error");
509
+ throw error;
510
+ }
511
+ }
512
+ async sync() {
513
+ const artifactsDir = getLocusPath(this.deps.projectPath, "artifactsDir");
514
+ if (!import_node_fs.existsSync(artifactsDir)) {
515
+ import_node_fs.mkdirSync(artifactsDir, { recursive: true });
516
+ return;
517
+ }
518
+ try {
519
+ const files = import_node_fs.readdirSync(artifactsDir);
520
+ if (files.length === 0)
521
+ return;
522
+ this.deps.log(`Syncing ${files.length} artifacts to server...`, "info");
523
+ const artifactsGroupId = await this.getOrCreateArtifactsGroup();
524
+ const existingDocs = await this.deps.client.docs.list(this.deps.workspaceId);
525
+ for (const file of files) {
526
+ const filePath = import_node_path2.join(artifactsDir, file);
527
+ if (import_node_fs.statSync(filePath).isFile()) {
528
+ const content = import_node_fs.readFileSync(filePath, "utf-8");
529
+ const title = file.replace(/\.md$/, "").trim();
530
+ if (!title)
531
+ continue;
532
+ const existing = existingDocs.find((d) => d.title === title);
533
+ if (existing) {
534
+ if (existing.content !== content || existing.groupId !== artifactsGroupId) {
535
+ await this.deps.client.docs.update(existing.id, this.deps.workspaceId, { content, groupId: artifactsGroupId });
536
+ this.deps.log(`Updated artifact: ${file}`, "success");
537
+ }
538
+ } else {
539
+ await this.deps.client.docs.create(this.deps.workspaceId, {
540
+ title,
541
+ content,
542
+ groupId: artifactsGroupId
543
+ });
544
+ this.deps.log(`Created artifact: ${file}`, "success");
545
+ }
546
+ }
547
+ }
548
+ } catch (error) {
549
+ this.deps.log(`Failed to sync artifacts: ${error}`, "error");
550
+ }
551
+ }
552
+ }
553
+ // src/core/indexer.ts
554
+ var import_node_fs2 = require("node:fs");
555
+ var import_node_path3 = require("node:path");
556
+ var import_globby = require("globby");
557
+
558
+ class CodebaseIndexer {
559
+ projectPath;
560
+ indexPath;
561
+ constructor(projectPath) {
562
+ this.projectPath = projectPath;
563
+ this.indexPath = import_node_path3.join(projectPath, ".locus", "codebase-index.json");
564
+ }
565
+ async index(onProgress, treeSummarizer) {
566
+ if (!treeSummarizer) {
567
+ throw new Error("A treeSummarizer is required for this indexing method.");
568
+ }
569
+ if (onProgress)
570
+ onProgress("Generating file tree...");
571
+ const gitmodulesPath = import_node_path3.join(this.projectPath, ".gitmodules");
572
+ const submoduleIgnores = [];
573
+ if (import_node_fs2.existsSync(gitmodulesPath)) {
574
+ try {
575
+ const content = import_node_fs2.readFileSync(gitmodulesPath, "utf-8");
576
+ const lines = content.split(`
577
+ `);
578
+ for (const line of lines) {
579
+ const match = line.match(/^\s*path\s*=\s*(.*)$/);
580
+ const path = match?.[1]?.trim();
581
+ if (path) {
582
+ submoduleIgnores.push(`${path}/**`);
583
+ submoduleIgnores.push(`**/${path}/**`);
584
+ }
585
+ }
586
+ } catch {}
587
+ }
588
+ const files = await import_globby.globby(["**/*"], {
589
+ cwd: this.projectPath,
590
+ gitignore: true,
591
+ ignore: [
592
+ ...submoduleIgnores,
593
+ "**/node_modules/**",
594
+ "**/dist/**",
595
+ "**/build/**",
596
+ "**/target/**",
597
+ "**/bin/**",
598
+ "**/obj/**",
599
+ "**/.next/**",
600
+ "**/.svelte-kit/**",
601
+ "**/.nuxt/**",
602
+ "**/.cache/**",
603
+ "**/out/**",
604
+ "**/__tests__/**",
605
+ "**/coverage/**",
606
+ "**/*.test.*",
607
+ "**/*.spec.*",
608
+ "**/*.d.ts",
609
+ "**/tsconfig.tsbuildinfo",
610
+ "**/.locus/*.json",
611
+ "**/.locus/*.md",
612
+ "**/.locus/!(artifacts)/**",
613
+ "**/.git/**",
614
+ "**/.svn/**",
615
+ "**/.hg/**",
616
+ "**/.vscode/**",
617
+ "**/.idea/**",
618
+ "**/.DS_Store",
619
+ "**/bun.lock",
620
+ "**/package-lock.json",
621
+ "**/yarn.lock",
622
+ "**/pnpm-lock.yaml",
623
+ "**/Cargo.lock",
624
+ "**/go.sum",
625
+ "**/poetry.lock",
626
+ "**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}"
627
+ ]
628
+ });
629
+ const treeString = files.join(`
630
+ `);
631
+ if (onProgress)
632
+ onProgress("AI is analyzing codebase structure...");
633
+ const index = await treeSummarizer(treeString);
634
+ index.lastIndexed = new Date().toISOString();
635
+ return index;
636
+ }
637
+ loadIndex() {
638
+ if (import_node_fs2.existsSync(this.indexPath)) {
639
+ try {
640
+ return JSON.parse(import_node_fs2.readFileSync(this.indexPath, "utf-8"));
641
+ } catch {
642
+ return null;
643
+ }
644
+ }
645
+ return null;
646
+ }
647
+ saveIndex(index) {
648
+ const dir = import_node_path3.dirname(this.indexPath);
649
+ if (!import_node_fs2.existsSync(dir)) {
650
+ import_node_fs2.mkdirSync(dir, { recursive: true });
651
+ }
652
+ import_node_fs2.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
653
+ }
654
+ }
655
+
656
+ // src/agent/codebase-indexer-service.ts
657
+ class CodebaseIndexerService {
658
+ deps;
659
+ indexer;
660
+ constructor(deps) {
661
+ this.deps = deps;
662
+ this.indexer = new CodebaseIndexer(deps.projectPath);
663
+ }
664
+ async reindex() {
665
+ try {
666
+ this.deps.log("Reindexing codebase...", "info");
667
+ const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
668
+ const prompt = `You are a codebase analysis expert. Analyze the file tree and extract:
669
+ 1. Key symbols (classes, functions, types) and their locations
670
+ 2. Responsibilities of each directory/file
671
+ 3. Overall project structure
672
+
673
+ Analyze this file tree and provide a JSON response with:
674
+ - "symbols": object mapping symbol names to file paths (array)
675
+ - "responsibilities": object mapping paths to brief descriptions
676
+
677
+ File tree:
678
+ ${tree}
679
+
680
+ Return ONLY valid JSON, no markdown formatting.`;
681
+ let response;
682
+ if (this.deps.anthropicClient) {
683
+ response = await this.deps.anthropicClient.run({
684
+ systemPrompt: "You are a codebase analysis expert specialized in extracting structure and symbols from file trees.",
685
+ userPrompt: prompt
686
+ });
687
+ } else {
688
+ response = await this.deps.claudeRunner.run(prompt, true);
689
+ }
690
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
691
+ if (jsonMatch) {
692
+ return JSON.parse(jsonMatch[0]);
693
+ }
694
+ return { symbols: {}, responsibilities: {}, lastIndexed: "" };
695
+ });
696
+ this.indexer.saveIndex(index);
697
+ this.deps.log("Codebase reindexed successfully", "success");
698
+ } catch (error) {
699
+ this.deps.log(`Failed to reindex codebase: ${error}`, "error");
700
+ }
701
+ }
702
+ }
703
+ // src/agent/sprint-planner.ts
704
+ class SprintPlanner {
705
+ deps;
706
+ constructor(deps) {
707
+ this.deps = deps;
708
+ }
709
+ async planSprint(sprint, tasks2) {
710
+ this.deps.log(`Planning sprint: ${sprint.name}`, "info");
711
+ try {
712
+ const taskList = tasks2.map((t) => `- [${t.id}] ${t.title}: ${t.description || "No description"}`).join(`
713
+ `);
714
+ let plan;
715
+ if (this.deps.anthropicClient) {
716
+ const systemPrompt = `You are an expert project manager and lead engineer specialized in sprint planning and task prioritization.`;
717
+ const userPrompt = `# Sprint Planning: ${sprint.name}
718
+
719
+ ## Tasks
720
+ ${taskList}
721
+
722
+ ## Instructions
723
+ 1. Analyze dependencies between these tasks.
724
+ 2. Prioritize them for the most efficient execution.
725
+ 3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
726
+ 4. Output your final plan. The plan should clearly state the order of execution.
727
+
728
+ **IMPORTANT**:
729
+ - Do NOT create any files on the filesystem during this planning phase.
730
+ - Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
731
+ - Your output will be saved as the official sprint mindmap on the server.`;
732
+ plan = await this.deps.anthropicClient.run({
733
+ systemPrompt,
734
+ userPrompt
735
+ });
736
+ } else {
737
+ const planningPrompt = `# Sprint Planning: ${sprint.name}
738
+
739
+ 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.
740
+
741
+ ## Tasks
742
+ ${taskList}
743
+
744
+ ## Instructions
745
+ 1. Analyze dependencies between these tasks.
746
+ 2. Prioritize them for the most efficient execution.
747
+ 3. Create a mindmap (in Markdown or Mermaid format) that visualizes the sprint structure.
748
+ 4. Output your final plan. The plan should clearly state the order of execution.
749
+
750
+ **IMPORTANT**:
751
+ - Do NOT create any files on the filesystem during this planning phase.
752
+ - Avoid using absolute local paths (e.g., /Users/...) in your output. Use relative paths starting from the project root if necessary.
753
+ - Your output will be saved as the official sprint mindmap on the server.`;
754
+ plan = await this.deps.claudeRunner.run(planningPrompt, true);
755
+ }
756
+ this.deps.log("Sprint mindmap generated and posted to server.", "success");
757
+ return plan;
758
+ } catch (error) {
759
+ this.deps.log(`Sprint planning failed: ${error}`, "error");
760
+ return sprint.mindmap || "";
761
+ }
762
+ }
763
+ }
764
+ // src/core/prompt-builder.ts
765
+ var import_node_fs3 = require("node:fs");
766
+ var import_shared2 = require("@locusai/shared");
767
+ class PromptBuilder {
768
+ projectPath;
769
+ constructor(projectPath) {
770
+ this.projectPath = projectPath;
771
+ }
772
+ async build(task) {
773
+ let prompt = `# Task: ${task.title}
774
+
775
+ `;
776
+ const roleText = this.roleToText(task.assigneeRole);
777
+ if (roleText) {
778
+ prompt += `## Role
779
+ You are acting as a ${roleText}.
780
+
781
+ `;
782
+ }
783
+ prompt += `## Description
784
+ ${task.description || "No description provided."}
785
+
786
+ `;
787
+ const contextPath = getLocusPath(this.projectPath, "contextFile");
788
+ if (import_node_fs3.existsSync(contextPath)) {
789
+ try {
790
+ const context = import_node_fs3.readFileSync(contextPath, "utf-8");
791
+ prompt += `## Project Context (from CLAUDE.md)
792
+ ${context}
793
+
794
+ `;
795
+ } catch (err) {
796
+ console.warn(`Warning: Could not read context file: ${err}`);
797
+ }
798
+ }
799
+ const indexPath = getLocusPath(this.projectPath, "indexFile");
800
+ if (import_node_fs3.existsSync(indexPath)) {
801
+ prompt += `## Codebase Overview
802
+ There is an index file in the .locus/codebase-index.json and if you need you can check it.
803
+
804
+ `;
805
+ }
806
+ if (task.docs && task.docs.length > 0) {
807
+ prompt += `## Attached Documents
808
+ `;
809
+ for (const doc of task.docs) {
810
+ prompt += `### ${doc.title}
811
+ ${doc.content || "(No content)"}
812
+
813
+ `;
814
+ }
815
+ }
816
+ if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
817
+ prompt += `## Acceptance Criteria
818
+ `;
819
+ for (const item of task.acceptanceChecklist) {
820
+ prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
821
+ `;
822
+ }
823
+ prompt += `
824
+ `;
825
+ }
826
+ if (task.comments && task.comments.length > 0) {
827
+ const comments = task.comments.slice(0, 5);
828
+ prompt += `## Task History & Feedback
829
+ `;
830
+ prompt += `Review the following comments for context or rejection feedback:
831
+
832
+ `;
833
+ for (const comment of comments) {
834
+ const date = new Date(comment.createdAt).toLocaleString();
835
+ prompt += `### ${comment.author} (${date})
836
+ ${comment.text}
837
+
838
+ `;
839
+ }
840
+ }
841
+ prompt += `## Instructions
842
+ 1. Complete this task.
843
+ 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.
844
+ 3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
845
+ 4. When finished successfully, output: <promise>COMPLETE</promise>
846
+ `;
847
+ return prompt;
848
+ }
849
+ roleToText(role) {
850
+ if (!role) {
851
+ return null;
852
+ }
853
+ switch (role) {
854
+ case import_shared2.AssigneeRole.BACKEND:
855
+ return "Backend Engineer";
856
+ case import_shared2.AssigneeRole.FRONTEND:
857
+ return "Frontend Engineer";
858
+ case import_shared2.AssigneeRole.PM:
859
+ return "Product Manager";
860
+ case import_shared2.AssigneeRole.QA:
861
+ return "QA Engineer";
862
+ case import_shared2.AssigneeRole.DESIGN:
863
+ return "Product Designer";
864
+ default:
865
+ return "engineer";
866
+ }
867
+ }
868
+ }
869
+
870
+ // src/agent/task-executor.ts
871
+ class TaskExecutor {
872
+ deps;
873
+ promptBuilder;
874
+ constructor(deps) {
875
+ this.deps = deps;
876
+ this.promptBuilder = new PromptBuilder(deps.projectPath);
877
+ }
878
+ updateSprintPlan(sprintPlan) {
879
+ this.deps.sprintPlan = sprintPlan;
880
+ }
881
+ async execute(task) {
882
+ this.deps.log(`Executing: ${task.title}`, "info");
883
+ let basePrompt = await this.promptBuilder.build(task);
884
+ if (this.deps.sprintPlan) {
885
+ basePrompt = `## Sprint Context
886
+ ${this.deps.sprintPlan}
887
+
888
+ ${basePrompt}`;
889
+ }
890
+ try {
891
+ let plan = "";
892
+ if (this.deps.anthropicClient) {
893
+ this.deps.log("Phase 1: Planning (Anthropic SDK)...", "info");
894
+ const cacheableContext = [basePrompt];
895
+ plan = await this.deps.anthropicClient.run({
896
+ systemPrompt: "You are an expert software engineer. Analyze the task carefully and create a detailed implementation plan.",
897
+ cacheableContext,
898
+ userPrompt: `## Phase 1: Planning
899
+ Analyze and create a detailed plan for THIS SPECIFIC TASK. Do NOT execute changes yet.`
900
+ });
901
+ } else {
902
+ this.deps.log("Skipping Phase 1: Planning (No Anthropic API Key)...", "info");
903
+ }
904
+ this.deps.log("Starting Execution...", "info");
905
+ let executionPrompt = basePrompt;
906
+ if (plan) {
907
+ executionPrompt += `
908
+
909
+ ## Phase 2: Execution
910
+ Based on the plan, execute the task:
911
+
912
+ ${plan}`;
913
+ } else {
914
+ executionPrompt += `
915
+
916
+ ## Execution
917
+ Execute the task directly.`;
918
+ }
919
+ executionPrompt += `
920
+
921
+ When finished, output: <promise>COMPLETE</promise>`;
922
+ const output = await this.deps.claudeRunner.run(executionPrompt);
923
+ const success = output.includes("<promise>COMPLETE</promise>");
924
+ return {
925
+ success,
926
+ summary: success ? "Task completed by Claude" : "Claude did not signal completion"
927
+ };
928
+ } catch (error) {
929
+ return { success: false, summary: `Error: ${error}` };
930
+ }
931
+ }
932
+ }
933
+ // src/ai/anthropic-client.ts
934
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"));
935
+ class AnthropicClient {
936
+ client;
937
+ model;
938
+ constructor(config) {
939
+ this.client = new import_sdk.default({
940
+ apiKey: config.apiKey
941
+ });
942
+ this.model = config.model || DEFAULT_MODEL;
943
+ }
944
+ async run(options) {
945
+ const { systemPrompt, cacheableContext = [], userPrompt } = options;
946
+ const systemContent = [];
947
+ if (systemPrompt) {
948
+ systemContent.push({
949
+ type: "text",
950
+ text: systemPrompt
951
+ });
952
+ }
953
+ for (let i = 0;i < cacheableContext.length; i++) {
954
+ const isLast = i === cacheableContext.length - 1;
955
+ systemContent.push({
956
+ type: "text",
957
+ text: cacheableContext[i],
958
+ ...isLast && {
959
+ cache_control: { type: "ephemeral" }
960
+ }
961
+ });
962
+ }
963
+ const response = await this.client.messages.create({
964
+ model: this.model,
965
+ max_tokens: 8000,
966
+ system: systemContent,
967
+ messages: [
968
+ {
969
+ role: "user",
970
+ content: userPrompt
971
+ }
972
+ ]
973
+ });
974
+ const textBlocks = response.content.filter((block) => block.type === "text");
975
+ return textBlocks.map((block) => block.text).join(`
976
+ `);
977
+ }
978
+ async runSimple(prompt) {
979
+ return this.run({
980
+ userPrompt: prompt
981
+ });
982
+ }
983
+ }
984
+
985
+ // src/ai/claude-runner.ts
986
+ var import_node_child_process = require("node:child_process");
987
+ class ClaudeRunner {
988
+ projectPath;
989
+ model;
990
+ constructor(projectPath, model = DEFAULT_MODEL) {
991
+ this.projectPath = projectPath;
992
+ this.model = model;
993
+ }
994
+ async run(prompt, _isPlanning = false) {
995
+ const maxRetries = 3;
996
+ let lastError = null;
997
+ for (let attempt = 1;attempt <= maxRetries; attempt++) {
998
+ try {
999
+ return await this.executeRun(prompt);
1000
+ } catch (error) {
1001
+ const err = error;
1002
+ lastError = err;
1003
+ const isLastAttempt = attempt === maxRetries;
1004
+ if (!isLastAttempt) {
1005
+ const delay = Math.pow(2, attempt) * 1000;
1006
+ console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
1007
+ await new Promise((resolve) => setTimeout(resolve, delay));
1008
+ }
1009
+ }
1010
+ }
1011
+ throw lastError || new Error("Claude CLI failed after multiple attempts");
1012
+ }
1013
+ executeRun(prompt) {
1014
+ return new Promise((resolve, reject) => {
1015
+ const args = [
1016
+ "--dangerously-skip-permissions",
1017
+ "--print",
1018
+ "--model",
1019
+ this.model
1020
+ ];
1021
+ const claude = import_node_child_process.spawn("claude", args, {
1022
+ cwd: this.projectPath,
1023
+ stdio: ["pipe", "pipe", "pipe"],
1024
+ env: process.env,
1025
+ shell: true
1026
+ });
1027
+ let output = "";
1028
+ let errorOutput = "";
1029
+ claude.stdout.on("data", (data) => {
1030
+ output += data.toString();
1031
+ });
1032
+ claude.stderr.on("data", (data) => {
1033
+ errorOutput += data.toString();
1034
+ });
1035
+ claude.on("error", (err) => reject(new Error(`Failed to start Claude CLI (shell: true): ${err.message}. Please ensure the 'claude' command is available in your PATH.`)));
1036
+ claude.on("close", (code) => {
1037
+ if (code === 0)
1038
+ resolve(output);
1039
+ else {
1040
+ const detail = errorOutput.trim();
1041
+ const message = detail ? `Claude CLI error (exit code ${code}): ${detail}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
1042
+ reject(new Error(message));
1043
+ }
1044
+ });
1045
+ claude.stdin.write(prompt);
1046
+ claude.stdin.end();
1047
+ });
1048
+ }
1049
+ }
1050
+
1051
+ // src/utils/colors.ts
1052
+ var ESC = "\x1B[";
1053
+ var RESET = `${ESC}0m`;
1054
+ var colors = {
1055
+ reset: RESET,
1056
+ bold: `${ESC}1m`,
1057
+ dim: `${ESC}2m`,
1058
+ italic: `${ESC}3m`,
1059
+ underline: `${ESC}4m`,
1060
+ black: `${ESC}30m`,
1061
+ red: `${ESC}31m`,
1062
+ green: `${ESC}32m`,
1063
+ yellow: `${ESC}33m`,
1064
+ blue: `${ESC}34m`,
1065
+ magenta: `${ESC}35m`,
1066
+ cyan: `${ESC}36m`,
1067
+ white: `${ESC}37m`,
1068
+ gray: `${ESC}90m`,
1069
+ brightRed: `${ESC}91m`,
1070
+ brightGreen: `${ESC}92m`,
1071
+ brightYellow: `${ESC}93m`,
1072
+ brightBlue: `${ESC}94m`,
1073
+ brightMagenta: `${ESC}95m`,
1074
+ brightCyan: `${ESC}96m`,
1075
+ brightWhite: `${ESC}97m`
1076
+ };
1077
+ var c = {
1078
+ text: (text, ...colorNames) => {
1079
+ const codes = colorNames.map((name) => colors[name]).join("");
1080
+ return `${codes}${text}${RESET}`;
1081
+ },
1082
+ bold: (t) => c.text(t, "bold"),
1083
+ dim: (t) => c.text(t, "dim"),
1084
+ red: (t) => c.text(t, "red"),
1085
+ green: (t) => c.text(t, "green"),
1086
+ yellow: (t) => c.text(t, "yellow"),
1087
+ blue: (t) => c.text(t, "blue"),
1088
+ magenta: (t) => c.text(t, "magenta"),
1089
+ cyan: (t) => c.text(t, "cyan"),
1090
+ gray: (t) => c.text(t, "gray"),
1091
+ success: (t) => c.text(t, "green", "bold"),
1092
+ error: (t) => c.text(t, "red", "bold"),
1093
+ warning: (t) => c.text(t, "yellow", "bold"),
1094
+ info: (t) => c.text(t, "cyan", "bold"),
1095
+ primary: (t) => c.text(t, "blue", "bold"),
1096
+ underline: (t) => c.text(t, "underline")
1097
+ };
1098
+
1099
+ // src/agent/worker.ts
1100
+ class AgentWorker {
1101
+ config;
1102
+ client;
1103
+ claudeRunner;
1104
+ anthropicClient;
1105
+ sprintPlanner;
1106
+ indexerService;
1107
+ artifactSyncer;
1108
+ taskExecutor;
1109
+ consecutiveEmpty = 0;
1110
+ maxEmpty = 10;
1111
+ maxTasks = 50;
1112
+ tasksCompleted = 0;
1113
+ pollInterval = 1e4;
1114
+ sprintPlan = null;
1115
+ constructor(config) {
1116
+ this.config = config;
1117
+ const projectPath = config.projectPath || process.cwd();
1118
+ this.client = new LocusClient({
1119
+ baseUrl: config.apiBase,
1120
+ token: config.apiKey,
1121
+ retryOptions: {
1122
+ maxRetries: 3,
1123
+ initialDelay: 1000,
1124
+ maxDelay: 5000,
1125
+ factor: 2
1126
+ }
1127
+ });
1128
+ this.claudeRunner = new ClaudeRunner(projectPath, config.model);
1129
+ this.anthropicClient = config.anthropicApiKey ? new AnthropicClient({
1130
+ apiKey: config.anthropicApiKey,
1131
+ model: config.model
1132
+ }) : null;
1133
+ const logFn = this.log.bind(this);
1134
+ this.sprintPlanner = new SprintPlanner({
1135
+ anthropicClient: this.anthropicClient,
1136
+ claudeRunner: this.claudeRunner,
1137
+ log: logFn
1138
+ });
1139
+ this.indexerService = new CodebaseIndexerService({
1140
+ anthropicClient: this.anthropicClient,
1141
+ claudeRunner: this.claudeRunner,
1142
+ projectPath,
1143
+ log: logFn
1144
+ });
1145
+ this.artifactSyncer = new ArtifactSyncer({
1146
+ client: this.client,
1147
+ workspaceId: config.workspaceId,
1148
+ projectPath,
1149
+ log: logFn
1150
+ });
1151
+ this.taskExecutor = new TaskExecutor({
1152
+ anthropicClient: this.anthropicClient,
1153
+ claudeRunner: this.claudeRunner,
1154
+ projectPath,
1155
+ sprintPlan: null,
1156
+ log: logFn
1157
+ });
1158
+ if (this.anthropicClient) {
1159
+ this.log("Using Anthropic SDK with prompt caching for planning phases", "info");
1160
+ } else {
1161
+ this.log("Using Claude CLI for all phases (no Anthropic API key provided)", "info");
1162
+ }
1163
+ }
1164
+ log(message, level = "info") {
1165
+ const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
1166
+ const colorFn = {
1167
+ info: c.cyan,
1168
+ success: c.green,
1169
+ warn: c.yellow,
1170
+ error: c.red
1171
+ }[level];
1172
+ const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
1173
+ console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
1174
+ }
1175
+ async getActiveSprint() {
1176
+ try {
1177
+ if (this.config.sprintId) {
1178
+ return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
1179
+ }
1180
+ return await this.client.sprints.getActive(this.config.workspaceId);
1181
+ } catch (_error) {
1182
+ return null;
1183
+ }
1184
+ }
1185
+ async getNextTask() {
1186
+ try {
1187
+ const task = await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
1188
+ return task;
1189
+ } catch (error) {
1190
+ this.log(`No task dispatched: ${error instanceof Error ? error.message : String(error)}`, "info");
1191
+ return null;
1192
+ }
1193
+ }
1194
+ async executeTask(task) {
1195
+ const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
1196
+ this.taskExecutor.updateSprintPlan(this.sprintPlan);
1197
+ const result = await this.taskExecutor.execute(fullTask);
1198
+ await this.indexerService.reindex();
1199
+ return result;
1200
+ }
1201
+ async run() {
1202
+ this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
1203
+ const sprint = await this.getActiveSprint();
1204
+ if (sprint) {
1205
+ this.log(`Found active sprint: ${sprint.name} (${sprint.id})`, "info");
1206
+ const tasks2 = await this.client.tasks.list(this.config.workspaceId, {
1207
+ sprintId: sprint.id
1208
+ });
1209
+ this.log(`Sprint tasks found: ${tasks2.length}`, "info");
1210
+ const latestTaskCreation = tasks2.reduce((latest, task) => {
1211
+ const taskDate = new Date(task.createdAt);
1212
+ return taskDate > latest ? taskDate : latest;
1213
+ }, new Date(0));
1214
+ const mindmapDate = sprint.mindmapUpdatedAt ? new Date(sprint.mindmapUpdatedAt) : new Date(0);
1215
+ if (tasks2.length <= 1) {
1216
+ this.log("Skipping mindmap generation (only one task in sprint).", "info");
1217
+ this.sprintPlan = null;
1218
+ } else {
1219
+ const needsPlanning = !sprint.mindmap || sprint.mindmap.trim() === "" || latestTaskCreation > mindmapDate;
1220
+ if (needsPlanning) {
1221
+ if (sprint.mindmap && latestTaskCreation > mindmapDate) {
1222
+ this.log("New tasks have been added to the sprint since last mindmap. Regenerating...", "warn");
1223
+ }
1224
+ this.sprintPlan = await this.sprintPlanner.planSprint(sprint, tasks2);
1225
+ await this.client.sprints.update(sprint.id, this.config.workspaceId, {
1226
+ mindmap: this.sprintPlan,
1227
+ mindmapUpdatedAt: new Date
1228
+ });
1229
+ } else {
1230
+ this.log("Using existing sprint mindmap.", "info");
1231
+ this.sprintPlan = sprint.mindmap ?? null;
1232
+ }
1233
+ }
1234
+ } else {
1235
+ this.log("No active sprint found for planning.", "warn");
1236
+ }
1237
+ while (this.tasksCompleted < this.maxTasks && this.consecutiveEmpty < this.maxEmpty) {
1238
+ const task = await this.getNextTask();
1239
+ if (!task) {
1240
+ this.consecutiveEmpty++;
1241
+ if (this.consecutiveEmpty >= this.maxEmpty)
1242
+ break;
1243
+ await new Promise((r) => setTimeout(r, this.pollInterval));
1244
+ continue;
1245
+ }
1246
+ this.consecutiveEmpty = 0;
1247
+ this.log(`Claimed: ${task.title}`, "success");
1248
+ const result = await this.executeTask(task);
1249
+ await this.artifactSyncer.sync();
1250
+ if (result.success) {
1251
+ await this.client.tasks.update(task.id, this.config.workspaceId, {
1252
+ status: "VERIFICATION"
1253
+ });
1254
+ await this.client.tasks.addComment(task.id, this.config.workspaceId, {
1255
+ author: this.config.agentId,
1256
+ text: `✅ ${result.summary}`
1257
+ });
1258
+ this.tasksCompleted++;
1259
+ } else {
1260
+ await this.client.tasks.update(task.id, this.config.workspaceId, {
1261
+ status: "BACKLOG",
1262
+ assignedTo: null
1263
+ });
1264
+ await this.client.tasks.addComment(task.id, this.config.workspaceId, {
1265
+ author: this.config.agentId,
1266
+ text: `❌ ${result.summary}`
1267
+ });
1268
+ }
1269
+ }
1270
+ process.exit(0);
1271
+ }
1272
+ }
1273
+ if (process.argv[1]?.includes("agent-worker") || process.argv[1]?.includes("worker")) {
1274
+ const args = process.argv.slice(2);
1275
+ const config = {};
1276
+ for (let i = 0;i < args.length; i++) {
1277
+ const arg = args[i];
1278
+ if (arg === "--agent-id")
1279
+ config.agentId = args[++i];
1280
+ else if (arg === "--workspace-id")
1281
+ config.workspaceId = args[++i];
1282
+ else if (arg === "--sprint-id")
1283
+ config.sprintId = args[++i];
1284
+ else if (arg === "--api-base")
1285
+ config.apiBase = args[++i];
1286
+ else if (arg === "--api-key")
1287
+ config.apiKey = args[++i];
1288
+ else if (arg === "--anthropic-api-key")
1289
+ config.anthropicApiKey = args[++i];
1290
+ else if (arg === "--project-path")
1291
+ config.projectPath = args[++i];
1292
+ else if (arg === "--model")
1293
+ config.model = args[++i];
1294
+ }
1295
+ if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
1296
+ console.error("Missing required arguments");
1297
+ process.exit(1);
1298
+ }
1299
+ const worker = new AgentWorker(config);
1300
+ worker.run().catch((err) => {
1301
+ console.error("Fatal worker error:", err);
1302
+ process.exit(1);
1303
+ });
1304
+ }
1305
+ // src/orchestrator.ts
1306
+ var import_node_child_process2 = require("node:child_process");
1307
+ var import_node_fs4 = require("node:fs");
1308
+ var import_node_path4 = require("node:path");
1309
+ var import_shared3 = require("@locusai/shared");
1310
+ var import_events3 = require("events");
1311
+ var __dirname = "/home/runner/work/locusai/locusai/packages/sdk/src";
1312
+
1313
+ class AgentOrchestrator extends import_events3.EventEmitter {
1314
+ client;
1315
+ config;
1316
+ agents = new Map;
1317
+ isRunning = false;
1318
+ processedTasks = new Set;
1319
+ resolvedSprintId = null;
1320
+ constructor(config) {
1321
+ super();
1322
+ this.config = config;
1323
+ this.client = new LocusClient({
1324
+ baseUrl: config.apiBase,
1325
+ token: config.apiKey
1326
+ });
1327
+ }
1328
+ async resolveSprintId() {
1329
+ if (this.config.sprintId) {
1330
+ return this.config.sprintId;
1331
+ }
1332
+ try {
1333
+ const sprint = await this.client.sprints.getActive(this.config.workspaceId);
1334
+ if (sprint?.id) {
1335
+ console.log(c.info(`\uD83D\uDCCB Using active sprint: ${sprint.name}`));
1336
+ return sprint.id;
1337
+ }
1338
+ } catch {}
1339
+ console.log(c.dim("ℹ No sprint specified, working with all workspace tasks"));
1340
+ return "";
1341
+ }
1342
+ async start() {
1343
+ if (this.isRunning) {
1344
+ throw new Error("Orchestrator is already running");
1345
+ }
1346
+ this.isRunning = true;
1347
+ this.processedTasks.clear();
1348
+ try {
1349
+ await this.orchestrationLoop();
1350
+ } catch (error) {
1351
+ this.emit("error", error);
1352
+ throw error;
1353
+ } finally {
1354
+ await this.cleanup();
1355
+ }
1356
+ }
1357
+ async orchestrationLoop() {
1358
+ this.resolvedSprintId = await this.resolveSprintId();
1359
+ this.emit("started", {
1360
+ timestamp: new Date,
1361
+ config: this.config,
1362
+ sprintId: this.resolvedSprintId
1363
+ });
1364
+ console.log(`
1365
+ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
1366
+ console.log(c.dim("----------------------------------------------"));
1367
+ console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
1368
+ if (this.resolvedSprintId) {
1369
+ console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
1370
+ }
1371
+ console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
1372
+ console.log(c.dim(`----------------------------------------------
1373
+ `));
1374
+ const tasks2 = await this.getAvailableTasks();
1375
+ if (tasks2.length === 0) {
1376
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
1377
+ return;
1378
+ }
1379
+ await this.spawnAgent();
1380
+ while (this.agents.size > 0 && this.isRunning) {
1381
+ await this.reapAgents();
1382
+ if (this.agents.size === 0) {
1383
+ break;
1384
+ }
1385
+ await this.sleep(2000);
1386
+ }
1387
+ console.log(`
1388
+ ${c.success("✅ Orchestrator finished")}`);
1389
+ }
1390
+ findPackageRoot(startPath) {
1391
+ let currentDir = startPath;
1392
+ while (currentDir !== "/") {
1393
+ if (import_node_fs4.existsSync(import_node_path4.join(currentDir, "package.json"))) {
1394
+ return currentDir;
1395
+ }
1396
+ currentDir = import_node_path4.dirname(currentDir);
1397
+ }
1398
+ return startPath;
1399
+ }
1400
+ async spawnAgent() {
1401
+ const agentId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1402
+ const agentState = {
1403
+ id: agentId,
1404
+ status: "IDLE",
1405
+ currentTaskId: null,
1406
+ tasksCompleted: 0,
1407
+ tasksFailed: 0,
1408
+ lastHeartbeat: new Date
1409
+ };
1410
+ this.agents.set(agentId, agentState);
1411
+ console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
1412
+ `);
1413
+ const potentialPaths = [];
1414
+ try {
1415
+ const sdkIndexPath = require.resolve("@locusai/sdk");
1416
+ const sdkDir = import_node_path4.dirname(sdkIndexPath);
1417
+ const sdkRoot = this.findPackageRoot(sdkDir);
1418
+ potentialPaths.push(import_node_path4.join(sdkRoot, "dist", "agent", "worker.js"), import_node_path4.join(sdkRoot, "src", "agent", "worker.ts"));
1419
+ } catch {}
1420
+ const packageRoot = this.findPackageRoot(__dirname);
1421
+ potentialPaths.push(import_node_path4.join(packageRoot, "dist", "agent", "worker.js"), import_node_path4.join(packageRoot, "src", "agent", "worker.ts"), import_node_path4.join(__dirname, "agent", "worker.ts"), import_node_path4.join(__dirname, "agent", "worker.js"));
1422
+ const workerPath = potentialPaths.find((p) => import_node_fs4.existsSync(p));
1423
+ if (!workerPath) {
1424
+ throw new Error(`Worker file not found. Checked: ${potentialPaths.join(", ")}. ` + `Make sure the SDK is properly built and installed.`);
1425
+ }
1426
+ const workerArgs = [
1427
+ "--agent-id",
1428
+ agentId,
1429
+ "--workspace-id",
1430
+ this.config.workspaceId,
1431
+ "--api-base",
1432
+ this.config.apiBase,
1433
+ "--api-key",
1434
+ this.config.apiKey,
1435
+ "--project-path",
1436
+ this.config.projectPath
1437
+ ];
1438
+ if (this.config.anthropicApiKey) {
1439
+ workerArgs.push("--anthropic-api-key", this.config.anthropicApiKey);
1440
+ }
1441
+ if (this.config.model) {
1442
+ workerArgs.push("--model", this.config.model);
1443
+ }
1444
+ if (this.resolvedSprintId) {
1445
+ workerArgs.push("--sprint-id", this.resolvedSprintId);
1446
+ }
1447
+ const agentProcess = import_node_child_process2.spawn(process.execPath, [workerPath, ...workerArgs]);
1448
+ agentState.process = agentProcess;
1449
+ agentProcess.on("message", (msg) => {
1450
+ if (msg.type === "stats") {
1451
+ agentState.tasksCompleted = msg.tasksCompleted || 0;
1452
+ agentState.tasksFailed = msg.tasksFailed || 0;
1453
+ }
1454
+ });
1455
+ agentProcess.stdout?.on("data", (data) => {
1456
+ process.stdout.write(data.toString());
1457
+ });
1458
+ agentProcess.stderr?.on("data", (data) => {
1459
+ process.stderr.write(`[${agentId}] ERR: ${data.toString()}`);
1460
+ });
1461
+ agentProcess.on("exit", (code) => {
1462
+ console.log(`
1463
+ ${agentId} finished (exit code: ${code})`);
1464
+ const agent = this.agents.get(agentId);
1465
+ if (agent) {
1466
+ agent.status = code === 0 ? "COMPLETED" : "FAILED";
1467
+ this.emit("agent:completed", {
1468
+ agentId,
1469
+ status: agent.status,
1470
+ tasksCompleted: agent.tasksCompleted,
1471
+ tasksFailed: agent.tasksFailed
1472
+ });
1473
+ this.agents.delete(agentId);
1474
+ }
1475
+ });
1476
+ this.emit("agent:spawned", { agentId });
1477
+ }
1478
+ async reapAgents() {}
1479
+ async getAvailableTasks() {
1480
+ try {
1481
+ const tasks2 = await this.client.tasks.getAvailable(this.config.workspaceId, this.resolvedSprintId || undefined);
1482
+ return tasks2.filter((task) => !this.processedTasks.has(task.id));
1483
+ } catch (error) {
1484
+ this.emit("error", error);
1485
+ return [];
1486
+ }
1487
+ }
1488
+ async assignTaskToAgent(agentId) {
1489
+ const agent = this.agents.get(agentId);
1490
+ if (!agent)
1491
+ return null;
1492
+ try {
1493
+ const tasks2 = await this.getAvailableTasks();
1494
+ const priorityOrder = [
1495
+ import_shared3.TaskPriority.CRITICAL,
1496
+ import_shared3.TaskPriority.HIGH,
1497
+ import_shared3.TaskPriority.MEDIUM,
1498
+ import_shared3.TaskPriority.LOW
1499
+ ];
1500
+ let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
1501
+ if (!task && tasks2.length > 0) {
1502
+ task = tasks2[0];
1503
+ }
1504
+ if (!task)
1505
+ return null;
1506
+ agent.currentTaskId = task.id;
1507
+ agent.status = "WORKING";
1508
+ this.emit("task:assigned", {
1509
+ agentId,
1510
+ taskId: task.id,
1511
+ title: task.title
1512
+ });
1513
+ return task;
1514
+ } catch (error) {
1515
+ this.emit("error", error);
1516
+ return null;
1517
+ }
1518
+ }
1519
+ async completeTask(taskId, agentId, summary) {
1520
+ try {
1521
+ await this.client.tasks.update(taskId, this.config.workspaceId, {
1522
+ status: import_shared3.TaskStatus.VERIFICATION
1523
+ });
1524
+ if (summary) {
1525
+ await this.client.tasks.addComment(taskId, this.config.workspaceId, {
1526
+ author: agentId,
1527
+ text: `✅ Task completed
1528
+
1529
+ ${summary}`
1530
+ });
1531
+ }
1532
+ this.processedTasks.add(taskId);
1533
+ const agent = this.agents.get(agentId);
1534
+ if (agent) {
1535
+ agent.tasksCompleted += 1;
1536
+ agent.currentTaskId = null;
1537
+ agent.status = "IDLE";
1538
+ }
1539
+ this.emit("task:completed", { agentId, taskId });
1540
+ } catch (error) {
1541
+ this.emit("error", error);
1542
+ }
1543
+ }
1544
+ async failTask(taskId, agentId, error) {
1545
+ try {
1546
+ await this.client.tasks.update(taskId, this.config.workspaceId, {
1547
+ status: import_shared3.TaskStatus.BACKLOG,
1548
+ assignedTo: null
1549
+ });
1550
+ await this.client.tasks.addComment(taskId, this.config.workspaceId, {
1551
+ author: agentId,
1552
+ text: `❌ Agent failed: ${error}`
1553
+ });
1554
+ const agent = this.agents.get(agentId);
1555
+ if (agent) {
1556
+ agent.tasksFailed += 1;
1557
+ agent.currentTaskId = null;
1558
+ agent.status = "IDLE";
1559
+ }
1560
+ this.emit("task:failed", { agentId, taskId, error });
1561
+ } catch (error2) {
1562
+ this.emit("error", error2);
1563
+ }
1564
+ }
1565
+ async stop() {
1566
+ this.isRunning = false;
1567
+ await this.cleanup();
1568
+ this.emit("stopped", { timestamp: new Date });
1569
+ }
1570
+ async cleanup() {
1571
+ for (const [agentId, agent] of this.agents.entries()) {
1572
+ if (agent.process && !agent.process.killed) {
1573
+ console.log(`Killing agent: ${agentId}`);
1574
+ agent.process.kill();
1575
+ }
1576
+ }
1577
+ this.agents.clear();
1578
+ }
1579
+ getStats() {
1580
+ return {
1581
+ activeAgents: this.agents.size,
1582
+ processedTasks: this.processedTasks.size,
1583
+ totalTasksCompleted: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksCompleted, 0),
1584
+ totalTasksFailed: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksFailed, 0)
1585
+ };
1586
+ }
1587
+ sleep(ms) {
1588
+ return new Promise((resolve) => setTimeout(resolve, ms));
1589
+ }
1590
+ }