@locusai/sdk 0.9.17 → 0.10.1
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/codebase-indexer-service.d.ts.map +1 -1
- package/dist/agent/git-workflow.d.ts +44 -0
- package/dist/agent/git-workflow.d.ts.map +1 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/reviewer-worker.d.ts.map +1 -1
- package/dist/agent/worker-cli.d.ts +6 -0
- package/dist/agent/worker-cli.d.ts.map +1 -0
- package/dist/agent/worker-types.d.ts +44 -0
- package/dist/agent/worker-types.d.ts.map +1 -0
- package/dist/agent/worker.d.ts +12 -48
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +1026 -847
- package/dist/ai/claude-runner.d.ts.map +1 -1
- package/dist/index-node.d.ts +2 -1
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +1713 -1143
- package/dist/index.js +363 -316
- package/dist/orchestrator/agent-pool.d.ts +59 -0
- package/dist/orchestrator/agent-pool.d.ts.map +1 -0
- package/dist/orchestrator/execution.d.ts +55 -0
- package/dist/orchestrator/execution.d.ts.map +1 -0
- package/dist/orchestrator/index.d.ts +91 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/tier-merge.d.ts +50 -0
- package/dist/orchestrator/tier-merge.d.ts.map +1 -0
- package/dist/orchestrator/types.d.ts +45 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
- package/dist/planning/agents/sprint-organizer.d.ts.map +1 -1
- package/dist/planning/plan-manager.d.ts.map +1 -1
- package/dist/planning/sprint-plan.d.ts +2 -0
- package/dist/planning/sprint-plan.d.ts.map +1 -1
- package/dist/project/knowledge-base.d.ts +4 -5
- package/dist/project/knowledge-base.d.ts.map +1 -1
- package/dist/utils/json-extractor.d.ts +6 -0
- package/dist/utils/json-extractor.d.ts.map +1 -0
- package/dist/utils/resolve-bin.d.ts +3 -0
- package/dist/utils/resolve-bin.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/orchestrator.d.ts +0 -124
- package/dist/orchestrator.d.ts.map +0 -1
package/dist/index-node.js
CHANGED
|
@@ -38,42 +38,26 @@ var __export = (target, all) => {
|
|
|
38
38
|
set: (newValue) => all[name] = () => newValue
|
|
39
39
|
});
|
|
40
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"));
|
|
41
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
59
42
|
|
|
60
43
|
// src/events.ts
|
|
61
|
-
var import_events
|
|
62
|
-
var
|
|
63
|
-
(
|
|
64
|
-
LocusEvent2
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class LocusEmitter extends import_events.EventEmitter {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
44
|
+
var import_events, LocusEvent, LocusEmitter;
|
|
45
|
+
var init_events = __esm(() => {
|
|
46
|
+
import_events = require("events");
|
|
47
|
+
((LocusEvent2) => {
|
|
48
|
+
LocusEvent2["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
|
|
49
|
+
LocusEvent2["AUTH_ERROR"] = "AUTH_ERROR";
|
|
50
|
+
LocusEvent2["REQUEST_ERROR"] = "REQUEST_ERROR";
|
|
51
|
+
})(LocusEvent ||= {});
|
|
52
|
+
LocusEmitter = class LocusEmitter extends import_events.EventEmitter {
|
|
53
|
+
on(event, listener) {
|
|
54
|
+
return super.on(event, listener);
|
|
55
|
+
}
|
|
56
|
+
emit(event, ...args) {
|
|
57
|
+
return super.emit(event, ...args);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
});
|
|
77
61
|
|
|
78
62
|
// src/modules/base.ts
|
|
79
63
|
class BaseModule {
|
|
@@ -86,304 +70,344 @@ class BaseModule {
|
|
|
86
70
|
}
|
|
87
71
|
|
|
88
72
|
// src/modules/auth.ts
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
73
|
+
var AuthModule;
|
|
74
|
+
var init_auth = __esm(() => {
|
|
75
|
+
AuthModule = class AuthModule extends BaseModule {
|
|
76
|
+
async getProfile() {
|
|
77
|
+
const { data } = await this.api.get("/auth/me");
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
async getApiKeyInfo() {
|
|
81
|
+
const { data } = await this.api.get("/auth/api-key");
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
async requestRegisterOtp(email) {
|
|
85
|
+
const { data } = await this.api.post("/auth/register-otp", { email });
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
async requestLoginOtp(email) {
|
|
89
|
+
const { data } = await this.api.post("/auth/login-otp", { email });
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
async verifyLogin(body) {
|
|
93
|
+
const { data } = await this.api.post("/auth/verify-login", body);
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
async completeRegistration(body) {
|
|
97
|
+
const { data } = await this.api.post("/auth/complete-registration", body);
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
async deleteAccount() {
|
|
101
|
+
const { data } = await this.api.delete("/auth/account");
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
});
|
|
119
106
|
|
|
120
107
|
// src/modules/ci.ts
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
var CiModule;
|
|
109
|
+
var init_ci = __esm(() => {
|
|
110
|
+
CiModule = class CiModule extends BaseModule {
|
|
111
|
+
async report(body) {
|
|
112
|
+
const { data } = await this.api.post("/ci/report", body);
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
});
|
|
127
117
|
|
|
128
118
|
// src/modules/docs.ts
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
119
|
+
var DocsModule;
|
|
120
|
+
var init_docs = __esm(() => {
|
|
121
|
+
DocsModule = 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
|
+
});
|
|
165
158
|
|
|
166
159
|
// src/modules/invitations.ts
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
160
|
+
var InvitationsModule;
|
|
161
|
+
var init_invitations = __esm(() => {
|
|
162
|
+
InvitationsModule = class InvitationsModule extends BaseModule {
|
|
163
|
+
async create(orgId, body) {
|
|
164
|
+
const { data } = await this.api.post(`/org/${orgId}/invitations`, body);
|
|
165
|
+
return data.invitation;
|
|
166
|
+
}
|
|
167
|
+
async list(orgId) {
|
|
168
|
+
const { data } = await this.api.get(`/org/${orgId}/invitations`);
|
|
169
|
+
return data.invitations;
|
|
170
|
+
}
|
|
171
|
+
async verify(token) {
|
|
172
|
+
const { data } = await this.api.get(`/invitations/verify/${token}`);
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
async accept(body) {
|
|
176
|
+
const { data } = await this.api.post("/invitations/accept", body);
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
async revoke(orgId, id) {
|
|
180
|
+
await this.api.delete(`/org/${orgId}/invitations/${id}`);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
});
|
|
188
184
|
|
|
189
185
|
// src/modules/organizations.ts
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
186
|
+
var OrganizationsModule;
|
|
187
|
+
var init_organizations = __esm(() => {
|
|
188
|
+
OrganizationsModule = class OrganizationsModule extends BaseModule {
|
|
189
|
+
async list() {
|
|
190
|
+
const { data } = await this.api.get("/organizations");
|
|
191
|
+
return data.organizations;
|
|
192
|
+
}
|
|
193
|
+
async getById(id) {
|
|
194
|
+
const { data } = await this.api.get(`/organizations/${id}`);
|
|
195
|
+
return data.organization;
|
|
196
|
+
}
|
|
197
|
+
async listMembers(id) {
|
|
198
|
+
const { data } = await this.api.get(`/organizations/${id}/members`);
|
|
199
|
+
return data.members;
|
|
200
|
+
}
|
|
201
|
+
async addMember(id, body) {
|
|
202
|
+
const { data } = await this.api.post(`/organizations/${id}/members`, body);
|
|
203
|
+
return data.membership;
|
|
204
|
+
}
|
|
205
|
+
async removeMember(orgId, userId) {
|
|
206
|
+
await this.api.delete(`/organizations/${orgId}/members/${userId}`);
|
|
207
|
+
}
|
|
208
|
+
async delete(orgId) {
|
|
209
|
+
await this.api.delete(`/organizations/${orgId}`);
|
|
210
|
+
}
|
|
211
|
+
async listApiKeys(orgId) {
|
|
212
|
+
const { data } = await this.api.get(`/organizations/${orgId}/api-keys`);
|
|
213
|
+
return data.apiKeys;
|
|
214
|
+
}
|
|
215
|
+
async createApiKey(orgId, name) {
|
|
216
|
+
const { data } = await this.api.post(`/organizations/${orgId}/api-keys`, { name });
|
|
217
|
+
return data.apiKey;
|
|
218
|
+
}
|
|
219
|
+
async deleteApiKey(orgId, keyId) {
|
|
220
|
+
await this.api.delete(`/organizations/${orgId}/api-keys/${keyId}`);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
});
|
|
225
224
|
|
|
226
225
|
// src/modules/sprints.ts
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
226
|
+
var SprintsModule;
|
|
227
|
+
var init_sprints = __esm(() => {
|
|
228
|
+
SprintsModule = class SprintsModule extends BaseModule {
|
|
229
|
+
async list(workspaceId) {
|
|
230
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints`);
|
|
231
|
+
return data.sprints;
|
|
232
|
+
}
|
|
233
|
+
async getActive(workspaceId) {
|
|
234
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/active`);
|
|
235
|
+
return data.sprint;
|
|
236
|
+
}
|
|
237
|
+
async getById(id, workspaceId) {
|
|
238
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/sprints/${id}`);
|
|
239
|
+
return data.sprint;
|
|
240
|
+
}
|
|
241
|
+
async create(workspaceId, body) {
|
|
242
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints`, body);
|
|
243
|
+
return data.sprint;
|
|
244
|
+
}
|
|
245
|
+
async update(id, workspaceId, body) {
|
|
246
|
+
const { data } = await this.api.patch(`/workspaces/${workspaceId}/sprints/${id}`, body);
|
|
247
|
+
return data.sprint;
|
|
248
|
+
}
|
|
249
|
+
async delete(id, workspaceId) {
|
|
250
|
+
await this.api.delete(`/workspaces/${workspaceId}/sprints/${id}`);
|
|
251
|
+
}
|
|
252
|
+
async start(id, workspaceId) {
|
|
253
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/start`);
|
|
254
|
+
return data.sprint;
|
|
255
|
+
}
|
|
256
|
+
async complete(id, workspaceId) {
|
|
257
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/complete`);
|
|
258
|
+
return data.sprint;
|
|
259
|
+
}
|
|
260
|
+
async triggerAIPlanning(id, workspaceId) {
|
|
261
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/sprints/${id}/trigger-ai-planning`);
|
|
262
|
+
return data.sprint;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
});
|
|
264
266
|
|
|
265
267
|
// src/modules/tasks.ts
|
|
266
|
-
var import_shared
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
tasks = tasks
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
268
|
+
var import_shared, TasksModule;
|
|
269
|
+
var init_tasks = __esm(() => {
|
|
270
|
+
import_shared = require("@locusai/shared");
|
|
271
|
+
TasksModule = class TasksModule extends BaseModule {
|
|
272
|
+
async list(workspaceId, options) {
|
|
273
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks`);
|
|
274
|
+
let tasks = data.tasks;
|
|
275
|
+
if (options?.sprintId) {
|
|
276
|
+
tasks = tasks.filter((t) => t.sprintId === options.sprintId);
|
|
277
|
+
}
|
|
278
|
+
if (options?.status) {
|
|
279
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
280
|
+
tasks = tasks.filter((t) => statuses.includes(t.status));
|
|
281
|
+
}
|
|
282
|
+
return tasks;
|
|
283
|
+
}
|
|
284
|
+
async getAvailable(workspaceId, sprintId) {
|
|
285
|
+
const tasks = await this.list(workspaceId, {
|
|
286
|
+
sprintId
|
|
287
|
+
});
|
|
288
|
+
return tasks.filter((t) => t.status === import_shared.TaskStatus.BACKLOG || t.status === import_shared.TaskStatus.IN_PROGRESS && !t.assignedTo);
|
|
289
|
+
}
|
|
290
|
+
async getById(id, workspaceId) {
|
|
291
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/${id}`);
|
|
292
|
+
return data.task;
|
|
293
|
+
}
|
|
294
|
+
async create(workspaceId, body) {
|
|
295
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks`, body);
|
|
296
|
+
return data.task;
|
|
297
|
+
}
|
|
298
|
+
async update(id, workspaceId, body) {
|
|
299
|
+
const { data } = await this.api.patch(`/workspaces/${workspaceId}/tasks/${id}`, body);
|
|
300
|
+
return data.task;
|
|
301
|
+
}
|
|
302
|
+
async delete(id, workspaceId) {
|
|
303
|
+
await this.api.delete(`/workspaces/${workspaceId}/tasks/${id}`);
|
|
304
|
+
}
|
|
305
|
+
async getBacklog(workspaceId) {
|
|
306
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/tasks/backlog`);
|
|
307
|
+
return data.tasks;
|
|
308
|
+
}
|
|
309
|
+
async addComment(id, workspaceId, body) {
|
|
310
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/tasks/${id}/comment`, body);
|
|
311
|
+
return data.comment;
|
|
312
|
+
}
|
|
313
|
+
async batchUpdate(ids, workspaceId, updates) {
|
|
314
|
+
await this.api.patch(`/workspaces/${workspaceId}/tasks/batch`, {
|
|
315
|
+
ids,
|
|
316
|
+
updates
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
});
|
|
316
321
|
|
|
317
322
|
// src/modules/workspaces.ts
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
323
|
+
var WorkspacesModule;
|
|
324
|
+
var init_workspaces = __esm(() => {
|
|
325
|
+
WorkspacesModule = class WorkspacesModule extends BaseModule {
|
|
326
|
+
async listAll() {
|
|
327
|
+
const { data } = await this.api.get("/workspaces");
|
|
328
|
+
return data.workspaces;
|
|
329
|
+
}
|
|
330
|
+
async listByOrg(orgId) {
|
|
331
|
+
const { data } = await this.api.get(`/workspaces/org/${orgId}`);
|
|
332
|
+
return data.workspaces;
|
|
333
|
+
}
|
|
334
|
+
async create(body) {
|
|
335
|
+
const { orgId, ...bodyWithoutOrgId } = body;
|
|
336
|
+
const { data } = await this.api.post(`/workspaces/org/${orgId}`, bodyWithoutOrgId);
|
|
337
|
+
return data.workspace;
|
|
338
|
+
}
|
|
339
|
+
async createWithAutoOrg(body) {
|
|
340
|
+
const { data } = await this.api.post("/workspaces", body);
|
|
341
|
+
return data.workspace;
|
|
342
|
+
}
|
|
343
|
+
async getById(id) {
|
|
344
|
+
const { data } = await this.api.get(`/workspaces/${id}`);
|
|
345
|
+
return data.workspace;
|
|
346
|
+
}
|
|
347
|
+
async update(id, body) {
|
|
348
|
+
const { data } = await this.api.put(`/workspaces/${id}`, body);
|
|
349
|
+
return data.workspace;
|
|
350
|
+
}
|
|
351
|
+
async delete(id) {
|
|
352
|
+
await this.api.delete(`/workspaces/${id}`);
|
|
353
|
+
}
|
|
354
|
+
async getStats(id) {
|
|
355
|
+
const { data } = await this.api.get(`/workspaces/${id}/stats`);
|
|
356
|
+
return data;
|
|
357
|
+
}
|
|
358
|
+
async getActivity(id, limit) {
|
|
359
|
+
const { data } = await this.api.get(`/workspaces/${id}/activity`, {
|
|
360
|
+
params: { limit }
|
|
361
|
+
});
|
|
362
|
+
return data.activity;
|
|
363
|
+
}
|
|
364
|
+
async dispatch(id, workerId, sprintId) {
|
|
365
|
+
const { data } = await this.api.post(`/workspaces/${id}/dispatch`, { workerId, sprintId });
|
|
366
|
+
return data.task;
|
|
367
|
+
}
|
|
368
|
+
async heartbeat(workspaceId, agentId, currentTaskId, status) {
|
|
369
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/agents/heartbeat`, {
|
|
370
|
+
agentId,
|
|
371
|
+
currentTaskId: currentTaskId ?? null,
|
|
372
|
+
status: status ?? "WORKING"
|
|
373
|
+
});
|
|
374
|
+
return data.agent;
|
|
375
|
+
}
|
|
376
|
+
async getAgents(workspaceId) {
|
|
377
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/agents`);
|
|
378
|
+
return data.agents;
|
|
379
|
+
}
|
|
380
|
+
async listApiKeys(workspaceId) {
|
|
381
|
+
const { data } = await this.api.get(`/workspaces/${workspaceId}/api-keys`);
|
|
382
|
+
return data.apiKeys;
|
|
383
|
+
}
|
|
384
|
+
async createApiKey(workspaceId, name) {
|
|
385
|
+
const { data } = await this.api.post(`/workspaces/${workspaceId}/api-keys`, { name });
|
|
386
|
+
return data.apiKey;
|
|
387
|
+
}
|
|
388
|
+
async deleteApiKey(workspaceId, keyId) {
|
|
389
|
+
await this.api.delete(`/workspaces/${workspaceId}/api-keys/${keyId}`);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
});
|
|
385
393
|
|
|
386
394
|
// src/index.ts
|
|
395
|
+
var exports_src = {};
|
|
396
|
+
__export(exports_src, {
|
|
397
|
+
WorkspacesModule: () => WorkspacesModule,
|
|
398
|
+
TasksModule: () => TasksModule,
|
|
399
|
+
SprintsModule: () => SprintsModule,
|
|
400
|
+
OrganizationsModule: () => OrganizationsModule,
|
|
401
|
+
LocusEvent: () => LocusEvent,
|
|
402
|
+
LocusEmitter: () => LocusEmitter,
|
|
403
|
+
LocusClient: () => LocusClient,
|
|
404
|
+
InvitationsModule: () => InvitationsModule,
|
|
405
|
+
DocsModule: () => DocsModule,
|
|
406
|
+
CiModule: () => CiModule,
|
|
407
|
+
AuthModule: () => AuthModule
|
|
408
|
+
});
|
|
409
|
+
module.exports = __toCommonJS(exports_src);
|
|
410
|
+
|
|
387
411
|
class LocusClient {
|
|
388
412
|
api;
|
|
389
413
|
emitter;
|
|
@@ -473,70 +497,30 @@ class LocusClient {
|
|
|
473
497
|
}
|
|
474
498
|
}
|
|
475
499
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
500
|
+
var import_axios;
|
|
501
|
+
var init_src = __esm(() => {
|
|
502
|
+
init_events();
|
|
503
|
+
init_auth();
|
|
504
|
+
init_ci();
|
|
505
|
+
init_docs();
|
|
506
|
+
init_invitations();
|
|
507
|
+
init_organizations();
|
|
508
|
+
init_sprints();
|
|
509
|
+
init_tasks();
|
|
510
|
+
init_workspaces();
|
|
511
|
+
import_axios = __toESM(require("axios"));
|
|
512
|
+
init_events();
|
|
513
|
+
init_auth();
|
|
514
|
+
init_ci();
|
|
515
|
+
init_docs();
|
|
516
|
+
init_invitations();
|
|
517
|
+
init_organizations();
|
|
518
|
+
init_sprints();
|
|
519
|
+
init_tasks();
|
|
520
|
+
init_workspaces();
|
|
481
521
|
});
|
|
482
|
-
module.exports = __toCommonJS(exports_worker);
|
|
483
|
-
var import_shared3 = require("@locusai/shared");
|
|
484
522
|
|
|
485
523
|
// src/core/config.ts
|
|
486
|
-
var import_node_path = require("node:path");
|
|
487
|
-
var PROVIDER = {
|
|
488
|
-
CLAUDE: "claude",
|
|
489
|
-
CODEX: "codex"
|
|
490
|
-
};
|
|
491
|
-
var DEFAULT_MODEL = {
|
|
492
|
-
[PROVIDER.CLAUDE]: "opus",
|
|
493
|
-
[PROVIDER.CODEX]: "gpt-5.3-codex"
|
|
494
|
-
};
|
|
495
|
-
var LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas";
|
|
496
|
-
var LOCUS_SCHEMAS = {
|
|
497
|
-
config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
|
|
498
|
-
settings: `${LOCUS_SCHEMA_BASE_URL}/settings.schema.json`
|
|
499
|
-
};
|
|
500
|
-
var LOCUS_CONFIG = {
|
|
501
|
-
dir: ".locus",
|
|
502
|
-
configFile: "config.json",
|
|
503
|
-
settingsFile: "settings.json",
|
|
504
|
-
indexFile: "codebase-index.json",
|
|
505
|
-
contextFile: "LOCUS.md",
|
|
506
|
-
artifactsDir: "artifacts",
|
|
507
|
-
documentsDir: "documents",
|
|
508
|
-
sessionsDir: "sessions",
|
|
509
|
-
reviewsDir: "reviews",
|
|
510
|
-
plansDir: "plans",
|
|
511
|
-
projectDir: "project",
|
|
512
|
-
projectContextFile: "context.md",
|
|
513
|
-
projectProgressFile: "progress.md"
|
|
514
|
-
};
|
|
515
|
-
var LOCUS_GITIGNORE_PATTERNS = [
|
|
516
|
-
"# Locus AI - Session data (user-specific, can grow large)",
|
|
517
|
-
".locus/sessions/",
|
|
518
|
-
"",
|
|
519
|
-
"# Locus AI - Artifacts (local-only, user-specific)",
|
|
520
|
-
".locus/artifacts/",
|
|
521
|
-
"",
|
|
522
|
-
"# Locus AI - Review reports (generated per sprint)",
|
|
523
|
-
".locus/reviews/",
|
|
524
|
-
"",
|
|
525
|
-
"# Locus AI - Plans (generated per task)",
|
|
526
|
-
".locus/plans/",
|
|
527
|
-
"",
|
|
528
|
-
"# Locus AI - Agent worktrees (parallel execution)",
|
|
529
|
-
".locus-worktrees/",
|
|
530
|
-
"",
|
|
531
|
-
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
532
|
-
".locus/settings.json",
|
|
533
|
-
"",
|
|
534
|
-
"# Locus AI - Configuration (contains project context, progress, etc.)",
|
|
535
|
-
".locus/config.json",
|
|
536
|
-
"",
|
|
537
|
-
"# Locus AI - Project progress (contains project progress, etc.)",
|
|
538
|
-
".locus/project/progress.md"
|
|
539
|
-
];
|
|
540
524
|
function getLocusPath(projectPath, fileName) {
|
|
541
525
|
if (fileName === "projectContextFile" || fileName === "projectProgressFile") {
|
|
542
526
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.projectDir, LOCUS_CONFIG[fileName]);
|
|
@@ -547,123 +531,235 @@ function getAgentArtifactsPath(projectPath, agentId) {
|
|
|
547
531
|
const shortId = agentId.slice(-8);
|
|
548
532
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
|
|
549
533
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
534
|
+
var import_node_path, PROVIDER, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
|
|
535
|
+
var init_config = __esm(() => {
|
|
536
|
+
import_node_path = require("node:path");
|
|
537
|
+
PROVIDER = {
|
|
538
|
+
CLAUDE: "claude",
|
|
539
|
+
CODEX: "codex"
|
|
540
|
+
};
|
|
541
|
+
DEFAULT_MODEL = {
|
|
542
|
+
[PROVIDER.CLAUDE]: "opus",
|
|
543
|
+
[PROVIDER.CODEX]: "gpt-5.3-codex"
|
|
544
|
+
};
|
|
545
|
+
LOCUS_SCHEMAS = {
|
|
546
|
+
config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
|
|
547
|
+
settings: `${LOCUS_SCHEMA_BASE_URL}/settings.schema.json`
|
|
548
|
+
};
|
|
549
|
+
LOCUS_CONFIG = {
|
|
550
|
+
dir: ".locus",
|
|
551
|
+
configFile: "config.json",
|
|
552
|
+
settingsFile: "settings.json",
|
|
553
|
+
indexFile: "codebase-index.json",
|
|
554
|
+
contextFile: "LOCUS.md",
|
|
555
|
+
artifactsDir: "artifacts",
|
|
556
|
+
documentsDir: "documents",
|
|
557
|
+
sessionsDir: "sessions",
|
|
558
|
+
reviewsDir: "reviews",
|
|
559
|
+
plansDir: "plans",
|
|
560
|
+
projectDir: "project",
|
|
561
|
+
projectContextFile: "context.md",
|
|
562
|
+
projectProgressFile: "progress.md"
|
|
563
|
+
};
|
|
564
|
+
LOCUS_GITIGNORE_PATTERNS = [
|
|
565
|
+
"# Locus AI - Session data (user-specific, can grow large)",
|
|
566
|
+
".locus/sessions/",
|
|
567
|
+
"",
|
|
568
|
+
"# Locus AI - Artifacts (local-only, user-specific)",
|
|
569
|
+
".locus/artifacts/",
|
|
570
|
+
"",
|
|
571
|
+
"# Locus AI - Review reports (generated per sprint)",
|
|
572
|
+
".locus/reviews/",
|
|
573
|
+
"",
|
|
574
|
+
"# Locus AI - Plans (generated per task)",
|
|
575
|
+
".locus/plans/",
|
|
576
|
+
"",
|
|
577
|
+
"# Locus AI - Agent worktrees (parallel execution)",
|
|
578
|
+
".locus-worktrees/",
|
|
579
|
+
"",
|
|
580
|
+
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
581
|
+
".locus/settings.json",
|
|
582
|
+
"",
|
|
583
|
+
"# Locus AI - Configuration (contains project context, progress, etc.)",
|
|
584
|
+
".locus/config.json",
|
|
585
|
+
"",
|
|
586
|
+
"# Locus AI - Project progress (contains project progress, etc.)",
|
|
587
|
+
".locus/project/progress.md"
|
|
588
|
+
];
|
|
589
|
+
});
|
|
554
590
|
|
|
555
591
|
// src/utils/colors.ts
|
|
556
|
-
var ESC = "\x1B[";
|
|
557
|
-
var
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
592
|
+
var ESC = "\x1B[", RESET, colors, c;
|
|
593
|
+
var init_colors = __esm(() => {
|
|
594
|
+
RESET = `${ESC}0m`;
|
|
595
|
+
colors = {
|
|
596
|
+
reset: RESET,
|
|
597
|
+
bold: `${ESC}1m`,
|
|
598
|
+
dim: `${ESC}2m`,
|
|
599
|
+
italic: `${ESC}3m`,
|
|
600
|
+
underline: `${ESC}4m`,
|
|
601
|
+
black: `${ESC}30m`,
|
|
602
|
+
red: `${ESC}31m`,
|
|
603
|
+
green: `${ESC}32m`,
|
|
604
|
+
yellow: `${ESC}33m`,
|
|
605
|
+
blue: `${ESC}34m`,
|
|
606
|
+
magenta: `${ESC}35m`,
|
|
607
|
+
cyan: `${ESC}36m`,
|
|
608
|
+
white: `${ESC}37m`,
|
|
609
|
+
gray: `${ESC}90m`,
|
|
610
|
+
brightRed: `${ESC}91m`,
|
|
611
|
+
brightGreen: `${ESC}92m`,
|
|
612
|
+
brightYellow: `${ESC}93m`,
|
|
613
|
+
brightBlue: `${ESC}94m`,
|
|
614
|
+
brightMagenta: `${ESC}95m`,
|
|
615
|
+
brightCyan: `${ESC}96m`,
|
|
616
|
+
brightWhite: `${ESC}97m`,
|
|
617
|
+
bgBlack: `${ESC}40m`,
|
|
618
|
+
bgRed: `${ESC}41m`,
|
|
619
|
+
bgGreen: `${ESC}42m`,
|
|
620
|
+
bgYellow: `${ESC}43m`,
|
|
621
|
+
bgBlue: `${ESC}44m`,
|
|
622
|
+
bgMagenta: `${ESC}45m`,
|
|
623
|
+
bgCyan: `${ESC}46m`,
|
|
624
|
+
bgWhite: `${ESC}47m`
|
|
625
|
+
};
|
|
626
|
+
c = {
|
|
627
|
+
text: (text, ...colorNames) => {
|
|
628
|
+
const codes = colorNames.map((name) => colors[name]).join("");
|
|
629
|
+
return `${codes}${text}${RESET}`;
|
|
630
|
+
},
|
|
631
|
+
bold: (t) => c.text(t, "bold"),
|
|
632
|
+
dim: (t) => c.text(t, "dim"),
|
|
633
|
+
red: (t) => c.text(t, "red"),
|
|
634
|
+
green: (t) => c.text(t, "green"),
|
|
635
|
+
yellow: (t) => c.text(t, "yellow"),
|
|
636
|
+
blue: (t) => c.text(t, "blue"),
|
|
637
|
+
magenta: (t) => c.text(t, "magenta"),
|
|
638
|
+
cyan: (t) => c.text(t, "cyan"),
|
|
639
|
+
gray: (t) => c.text(t, "gray"),
|
|
640
|
+
white: (t) => c.text(t, "white"),
|
|
641
|
+
brightBlue: (t) => c.text(t, "brightBlue"),
|
|
642
|
+
bgBlue: (t) => c.text(t, "bgBlue", "white", "bold"),
|
|
643
|
+
success: (t) => c.text(t, "green", "bold"),
|
|
644
|
+
error: (t) => c.text(t, "red", "bold"),
|
|
645
|
+
warning: (t) => c.text(t, "yellow", "bold"),
|
|
646
|
+
info: (t) => c.text(t, "cyan", "bold"),
|
|
647
|
+
primary: (t) => c.text(t, "blue", "bold"),
|
|
648
|
+
secondary: (t) => c.text(t, "magenta", "bold"),
|
|
649
|
+
header: (t) => c.text(` ${t} `, "bgBlue", "white", "bold"),
|
|
650
|
+
step: (t) => c.text(` ${t} `, "bgCyan", "black", "bold"),
|
|
651
|
+
underline: (t) => c.text(t, "underline")
|
|
652
|
+
};
|
|
653
|
+
});
|
|
616
654
|
|
|
617
655
|
// src/utils/resolve-bin.ts
|
|
618
|
-
|
|
619
|
-
var import_node_os = require("node:os");
|
|
620
|
-
var import_node_path2 = require("node:path");
|
|
621
|
-
var EXTRA_BIN_DIRS = [
|
|
622
|
-
import_node_path2.join(import_node_os.homedir(), ".local", "bin"),
|
|
623
|
-
import_node_path2.join(import_node_os.homedir(), ".npm", "bin"),
|
|
624
|
-
import_node_path2.join(import_node_os.homedir(), ".npm-global", "bin"),
|
|
625
|
-
import_node_path2.join(import_node_os.homedir(), ".yarn", "bin"),
|
|
626
|
-
"/usr/local/bin"
|
|
627
|
-
];
|
|
628
|
-
function getNodeManagerDirs() {
|
|
629
|
-
const dirs = [];
|
|
656
|
+
function getNvmNodeBinDir() {
|
|
630
657
|
const nvmDir = process.env.NVM_DIR || import_node_path2.join(import_node_os.homedir(), ".nvm");
|
|
631
|
-
const
|
|
632
|
-
if (import_node_fs.existsSync(
|
|
633
|
-
|
|
658
|
+
const versionsDir = import_node_path2.join(nvmDir, "versions", "node");
|
|
659
|
+
if (!import_node_fs.existsSync(versionsDir))
|
|
660
|
+
return null;
|
|
661
|
+
let versions;
|
|
662
|
+
try {
|
|
663
|
+
versions = import_node_fs.readdirSync(versionsDir).filter((d) => d.startsWith("v"));
|
|
664
|
+
} catch {
|
|
665
|
+
return null;
|
|
634
666
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
667
|
+
if (versions.length === 0)
|
|
668
|
+
return null;
|
|
669
|
+
const currentNodeVersion = `v${process.versions.node}`;
|
|
670
|
+
const currentBin = import_node_path2.join(versionsDir, currentNodeVersion, "bin");
|
|
671
|
+
if (versions.includes(currentNodeVersion) && import_node_fs.existsSync(currentBin)) {
|
|
672
|
+
return currentBin;
|
|
673
|
+
}
|
|
674
|
+
const aliasPath = import_node_path2.join(nvmDir, "alias", "default");
|
|
675
|
+
if (import_node_fs.existsSync(aliasPath)) {
|
|
676
|
+
try {
|
|
677
|
+
const alias = import_node_fs.readFileSync(aliasPath, "utf-8").trim();
|
|
678
|
+
const match = versions.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
|
|
679
|
+
if (match) {
|
|
680
|
+
const bin2 = import_node_path2.join(versionsDir, match, "bin");
|
|
681
|
+
if (import_node_fs.existsSync(bin2))
|
|
682
|
+
return bin2;
|
|
683
|
+
}
|
|
684
|
+
} catch {}
|
|
639
685
|
}
|
|
640
|
-
|
|
686
|
+
const sorted = versions.sort((a, b) => {
|
|
687
|
+
const pa = a.slice(1).split(".").map(Number);
|
|
688
|
+
const pb = b.slice(1).split(".").map(Number);
|
|
689
|
+
for (let i = 0;i < 3; i++) {
|
|
690
|
+
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
691
|
+
return (pb[i] || 0) - (pa[i] || 0);
|
|
692
|
+
}
|
|
693
|
+
return 0;
|
|
694
|
+
});
|
|
695
|
+
const bin = import_node_path2.join(versionsDir, sorted[0], "bin");
|
|
696
|
+
return import_node_fs.existsSync(bin) ? bin : null;
|
|
697
|
+
}
|
|
698
|
+
function getFnmNodeBinDir() {
|
|
699
|
+
const fnmDir = process.env.FNM_DIR || import_node_path2.join(import_node_os.homedir(), ".fnm");
|
|
700
|
+
const currentBin = import_node_path2.join(fnmDir, "current", "bin");
|
|
701
|
+
if (import_node_fs.existsSync(currentBin))
|
|
702
|
+
return currentBin;
|
|
703
|
+
const aliasDir = import_node_path2.join(fnmDir, "aliases", "default");
|
|
704
|
+
if (import_node_fs.existsSync(aliasDir)) {
|
|
705
|
+
const bin = import_node_path2.join(aliasDir, "bin");
|
|
706
|
+
if (import_node_fs.existsSync(bin))
|
|
707
|
+
return bin;
|
|
708
|
+
}
|
|
709
|
+
return null;
|
|
641
710
|
}
|
|
642
711
|
function getAugmentedPath() {
|
|
643
712
|
const currentPath = process.env.PATH || "";
|
|
644
713
|
const currentDirs = new Set(currentPath.split(import_node_path2.delimiter));
|
|
645
|
-
const extra = [
|
|
714
|
+
const extra = [];
|
|
715
|
+
for (const dir of EXTRA_BIN_DIRS) {
|
|
716
|
+
if (!currentDirs.has(dir) && import_node_fs.existsSync(dir)) {
|
|
717
|
+
extra.push(dir);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const nvmBin = getNvmNodeBinDir();
|
|
721
|
+
if (nvmBin && !currentDirs.has(nvmBin)) {
|
|
722
|
+
extra.push(nvmBin);
|
|
723
|
+
}
|
|
724
|
+
const fnmBin = getFnmNodeBinDir();
|
|
725
|
+
if (fnmBin && !currentDirs.has(fnmBin)) {
|
|
726
|
+
extra.push(fnmBin);
|
|
727
|
+
}
|
|
646
728
|
if (extra.length === 0)
|
|
647
729
|
return currentPath;
|
|
648
730
|
return currentPath + import_node_path2.delimiter + extra.join(import_node_path2.delimiter);
|
|
649
731
|
}
|
|
650
732
|
function getAugmentedEnv(overrides = {}) {
|
|
651
|
-
|
|
733
|
+
const env = {
|
|
652
734
|
...process.env,
|
|
653
735
|
...overrides,
|
|
654
736
|
PATH: getAugmentedPath()
|
|
655
737
|
};
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
// src/ai/claude-runner.ts
|
|
659
|
-
var SANDBOX_SETTINGS = JSON.stringify({
|
|
660
|
-
sandbox: {
|
|
661
|
-
enabled: true,
|
|
662
|
-
autoAllow: true,
|
|
663
|
-
allowUnsandboxedCommands: false
|
|
738
|
+
for (const key of ENV_VARS_TO_STRIP) {
|
|
739
|
+
delete env[key];
|
|
664
740
|
}
|
|
741
|
+
return env;
|
|
742
|
+
}
|
|
743
|
+
var import_node_fs, import_node_os, import_node_path2, EXTRA_BIN_DIRS, ENV_VARS_TO_STRIP;
|
|
744
|
+
var init_resolve_bin = __esm(() => {
|
|
745
|
+
import_node_fs = require("node:fs");
|
|
746
|
+
import_node_os = require("node:os");
|
|
747
|
+
import_node_path2 = require("node:path");
|
|
748
|
+
EXTRA_BIN_DIRS = [
|
|
749
|
+
import_node_path2.join(import_node_os.homedir(), ".local", "bin"),
|
|
750
|
+
import_node_path2.join(import_node_os.homedir(), ".npm", "bin"),
|
|
751
|
+
import_node_path2.join(import_node_os.homedir(), ".npm-global", "bin"),
|
|
752
|
+
import_node_path2.join(import_node_os.homedir(), ".yarn", "bin"),
|
|
753
|
+
import_node_path2.join(import_node_os.homedir(), ".bun", "bin"),
|
|
754
|
+
import_node_path2.join(import_node_os.homedir(), "Library", "pnpm"),
|
|
755
|
+
"/usr/local/bin",
|
|
756
|
+
"/opt/homebrew/bin",
|
|
757
|
+
"/opt/homebrew/sbin"
|
|
758
|
+
];
|
|
759
|
+
ENV_VARS_TO_STRIP = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
|
|
665
760
|
});
|
|
666
761
|
|
|
762
|
+
// src/ai/claude-runner.ts
|
|
667
763
|
class ClaudeRunner {
|
|
668
764
|
model;
|
|
669
765
|
log;
|
|
@@ -735,11 +831,13 @@ class ClaudeRunner {
|
|
|
735
831
|
this.activeProcess = claude;
|
|
736
832
|
let buffer = "";
|
|
737
833
|
let stderrBuffer = "";
|
|
834
|
+
let stderrFull = "";
|
|
738
835
|
let resolveChunk = null;
|
|
739
836
|
const chunkQueue = [];
|
|
740
837
|
let processEnded = false;
|
|
741
838
|
let errorMessage = "";
|
|
742
839
|
let finalContent = "";
|
|
840
|
+
let lastResultContent = "";
|
|
743
841
|
let isThinking = false;
|
|
744
842
|
const enqueueChunk = (chunk) => {
|
|
745
843
|
this.emitEventForChunk(chunk, isThinking);
|
|
@@ -777,6 +875,9 @@ class ClaudeRunner {
|
|
|
777
875
|
for (const line of lines) {
|
|
778
876
|
const chunk = this.parseStreamLineToChunk(line);
|
|
779
877
|
if (chunk) {
|
|
878
|
+
if (chunk.type === "result") {
|
|
879
|
+
lastResultContent = chunk.content;
|
|
880
|
+
}
|
|
780
881
|
enqueueChunk(chunk);
|
|
781
882
|
}
|
|
782
883
|
}
|
|
@@ -784,6 +885,7 @@ class ClaudeRunner {
|
|
|
784
885
|
claude.stderr.on("data", (data) => {
|
|
785
886
|
const chunk = data.toString();
|
|
786
887
|
stderrBuffer += chunk;
|
|
888
|
+
stderrFull += chunk;
|
|
787
889
|
const lines = stderrBuffer.split(`
|
|
788
890
|
`);
|
|
789
891
|
stderrBuffer = lines.pop() || "";
|
|
@@ -806,7 +908,8 @@ class ClaudeRunner {
|
|
|
806
908
|
`);
|
|
807
909
|
}
|
|
808
910
|
if (code !== 0 && !errorMessage) {
|
|
809
|
-
|
|
911
|
+
const detail = stderrFull.trim() || lastResultContent.trim();
|
|
912
|
+
errorMessage = this.createExecutionError(code, detail).message;
|
|
810
913
|
this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
|
|
811
914
|
}
|
|
812
915
|
signalEnd();
|
|
@@ -1015,7 +1118,8 @@ class ClaudeRunner {
|
|
|
1015
1118
|
if (code === 0) {
|
|
1016
1119
|
resolve2(finalResult);
|
|
1017
1120
|
} else {
|
|
1018
|
-
|
|
1121
|
+
const detail = errorOutput.trim() || finalResult.trim();
|
|
1122
|
+
reject(this.createExecutionError(code, detail));
|
|
1019
1123
|
}
|
|
1020
1124
|
});
|
|
1021
1125
|
claude.stdin.write(prompt);
|
|
@@ -1061,13 +1165,23 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
|
|
|
1061
1165
|
return new Error(message);
|
|
1062
1166
|
}
|
|
1063
1167
|
}
|
|
1168
|
+
var import_node_child_process, import_node_path3, SANDBOX_SETTINGS;
|
|
1169
|
+
var init_claude_runner = __esm(() => {
|
|
1170
|
+
init_config();
|
|
1171
|
+
init_colors();
|
|
1172
|
+
init_resolve_bin();
|
|
1173
|
+
import_node_child_process = require("node:child_process");
|
|
1174
|
+
import_node_path3 = require("node:path");
|
|
1175
|
+
SANDBOX_SETTINGS = JSON.stringify({
|
|
1176
|
+
sandbox: {
|
|
1177
|
+
enabled: true,
|
|
1178
|
+
autoAllow: true,
|
|
1179
|
+
allowUnsandboxedCommands: false
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1064
1183
|
|
|
1065
1184
|
// src/ai/codex-runner.ts
|
|
1066
|
-
var import_node_child_process2 = require("node:child_process");
|
|
1067
|
-
var import_node_crypto = require("node:crypto");
|
|
1068
|
-
var import_node_fs2 = require("node:fs");
|
|
1069
|
-
var import_node_os2 = require("node:os");
|
|
1070
|
-
var import_node_path4 = require("node:path");
|
|
1071
1185
|
class CodexRunner {
|
|
1072
1186
|
projectPath;
|
|
1073
1187
|
model;
|
|
@@ -1294,6 +1408,16 @@ class CodexRunner {
|
|
|
1294
1408
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1295
1409
|
}
|
|
1296
1410
|
}
|
|
1411
|
+
var import_node_child_process2, import_node_crypto, import_node_fs2, import_node_os2, import_node_path4;
|
|
1412
|
+
var init_codex_runner = __esm(() => {
|
|
1413
|
+
init_config();
|
|
1414
|
+
init_resolve_bin();
|
|
1415
|
+
import_node_child_process2 = require("node:child_process");
|
|
1416
|
+
import_node_crypto = require("node:crypto");
|
|
1417
|
+
import_node_fs2 = require("node:fs");
|
|
1418
|
+
import_node_os2 = require("node:os");
|
|
1419
|
+
import_node_path4 = require("node:path");
|
|
1420
|
+
});
|
|
1297
1421
|
|
|
1298
1422
|
// src/ai/factory.ts
|
|
1299
1423
|
function createAiRunner(provider, config) {
|
|
@@ -1306,9 +1430,13 @@ function createAiRunner(provider, config) {
|
|
|
1306
1430
|
return new ClaudeRunner(config.projectPath, model, config.log);
|
|
1307
1431
|
}
|
|
1308
1432
|
}
|
|
1433
|
+
var init_factory = __esm(() => {
|
|
1434
|
+
init_config();
|
|
1435
|
+
init_claude_runner();
|
|
1436
|
+
init_codex_runner();
|
|
1437
|
+
});
|
|
1309
1438
|
|
|
1310
1439
|
// src/git/git-utils.ts
|
|
1311
|
-
var import_node_child_process3 = require("node:child_process");
|
|
1312
1440
|
function isGitAvailable() {
|
|
1313
1441
|
try {
|
|
1314
1442
|
import_node_child_process3.execFileSync("git", ["--version"], {
|
|
@@ -1399,18 +1527,121 @@ function getDefaultBranch(projectPath, remote = "origin") {
|
|
|
1399
1527
|
}
|
|
1400
1528
|
}
|
|
1401
1529
|
}
|
|
1530
|
+
var import_node_child_process3;
|
|
1531
|
+
var init_git_utils = __esm(() => {
|
|
1532
|
+
import_node_child_process3 = require("node:child_process");
|
|
1533
|
+
});
|
|
1402
1534
|
|
|
1403
|
-
// src/
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
this.
|
|
1410
|
-
this.log = log;
|
|
1535
|
+
// src/project/knowledge-base.ts
|
|
1536
|
+
class KnowledgeBase {
|
|
1537
|
+
contextPath;
|
|
1538
|
+
progressPath;
|
|
1539
|
+
constructor(projectPath) {
|
|
1540
|
+
this.contextPath = getLocusPath(projectPath, "projectContextFile");
|
|
1541
|
+
this.progressPath = getLocusPath(projectPath, "projectProgressFile");
|
|
1411
1542
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1543
|
+
readContext() {
|
|
1544
|
+
if (!import_node_fs3.existsSync(this.contextPath)) {
|
|
1545
|
+
return "";
|
|
1546
|
+
}
|
|
1547
|
+
return import_node_fs3.readFileSync(this.contextPath, "utf-8");
|
|
1548
|
+
}
|
|
1549
|
+
readProgress() {
|
|
1550
|
+
if (!import_node_fs3.existsSync(this.progressPath)) {
|
|
1551
|
+
return "";
|
|
1552
|
+
}
|
|
1553
|
+
return import_node_fs3.readFileSync(this.progressPath, "utf-8");
|
|
1554
|
+
}
|
|
1555
|
+
updateContext(content) {
|
|
1556
|
+
this.ensureDir(this.contextPath);
|
|
1557
|
+
import_node_fs3.writeFileSync(this.contextPath, content);
|
|
1558
|
+
}
|
|
1559
|
+
updateProgress(entry) {
|
|
1560
|
+
this.ensureDir(this.progressPath);
|
|
1561
|
+
const existing = this.readProgress();
|
|
1562
|
+
const timestamp = (entry.timestamp ?? new Date).toISOString();
|
|
1563
|
+
const label = entry.role === "user" ? "User" : "Assistant";
|
|
1564
|
+
const line = `**${label}** (${timestamp}):
|
|
1565
|
+
${entry.content}`;
|
|
1566
|
+
const updated = existing ? `${existing}
|
|
1567
|
+
|
|
1568
|
+
---
|
|
1569
|
+
|
|
1570
|
+
${line}` : `# Conversation History
|
|
1571
|
+
|
|
1572
|
+
${line}`;
|
|
1573
|
+
import_node_fs3.writeFileSync(this.progressPath, updated);
|
|
1574
|
+
}
|
|
1575
|
+
getFullContext() {
|
|
1576
|
+
const context = this.readContext();
|
|
1577
|
+
const progress = this.readProgress();
|
|
1578
|
+
const parts = [];
|
|
1579
|
+
if (context.trim()) {
|
|
1580
|
+
parts.push(context.trim());
|
|
1581
|
+
}
|
|
1582
|
+
if (progress.trim()) {
|
|
1583
|
+
parts.push(progress.trim());
|
|
1584
|
+
}
|
|
1585
|
+
return parts.join(`
|
|
1586
|
+
|
|
1587
|
+
---
|
|
1588
|
+
|
|
1589
|
+
`);
|
|
1590
|
+
}
|
|
1591
|
+
initialize(info) {
|
|
1592
|
+
this.ensureDir(this.contextPath);
|
|
1593
|
+
this.ensureDir(this.progressPath);
|
|
1594
|
+
const techStackList = info.techStack.map((t) => `- ${t}`).join(`
|
|
1595
|
+
`);
|
|
1596
|
+
const contextContent = `# Project: ${info.name}
|
|
1597
|
+
|
|
1598
|
+
## Mission
|
|
1599
|
+
${info.mission}
|
|
1600
|
+
|
|
1601
|
+
## Tech Stack
|
|
1602
|
+
${techStackList}
|
|
1603
|
+
|
|
1604
|
+
## Architecture
|
|
1605
|
+
<!-- Describe your high-level architecture here -->
|
|
1606
|
+
|
|
1607
|
+
## Key Decisions
|
|
1608
|
+
<!-- Document important technical decisions and their rationale -->
|
|
1609
|
+
|
|
1610
|
+
## Feature Areas
|
|
1611
|
+
<!-- List your main feature areas and their status -->
|
|
1612
|
+
`;
|
|
1613
|
+
const progressContent = `# Conversation History
|
|
1614
|
+
`;
|
|
1615
|
+
import_node_fs3.writeFileSync(this.contextPath, contextContent);
|
|
1616
|
+
import_node_fs3.writeFileSync(this.progressPath, progressContent);
|
|
1617
|
+
}
|
|
1618
|
+
get exists() {
|
|
1619
|
+
return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
|
|
1620
|
+
}
|
|
1621
|
+
ensureDir(filePath) {
|
|
1622
|
+
const dir = import_node_path5.dirname(filePath);
|
|
1623
|
+
if (!import_node_fs3.existsSync(dir)) {
|
|
1624
|
+
import_node_fs3.mkdirSync(dir, { recursive: true });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
var import_node_fs3, import_node_path5;
|
|
1629
|
+
var init_knowledge_base = __esm(() => {
|
|
1630
|
+
init_config();
|
|
1631
|
+
import_node_fs3 = require("node:fs");
|
|
1632
|
+
import_node_path5 = require("node:path");
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
// src/git/pr-service.ts
|
|
1636
|
+
class PrService {
|
|
1637
|
+
projectPath;
|
|
1638
|
+
log;
|
|
1639
|
+
constructor(projectPath, log) {
|
|
1640
|
+
this.projectPath = projectPath;
|
|
1641
|
+
this.log = log;
|
|
1642
|
+
}
|
|
1643
|
+
createPr(options) {
|
|
1644
|
+
const {
|
|
1414
1645
|
task,
|
|
1415
1646
|
branch,
|
|
1416
1647
|
baseBranch: requestedBaseBranch,
|
|
@@ -1631,148 +1862,21 @@ class PrService {
|
|
|
1631
1862
|
return match ? Number.parseInt(match[1], 10) : 0;
|
|
1632
1863
|
}
|
|
1633
1864
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
contextPath;
|
|
1640
|
-
progressPath;
|
|
1641
|
-
constructor(projectPath) {
|
|
1642
|
-
this.contextPath = getLocusPath(projectPath, "projectContextFile");
|
|
1643
|
-
this.progressPath = getLocusPath(projectPath, "projectProgressFile");
|
|
1644
|
-
}
|
|
1645
|
-
readContext() {
|
|
1646
|
-
if (!import_node_fs3.existsSync(this.contextPath)) {
|
|
1647
|
-
return "";
|
|
1648
|
-
}
|
|
1649
|
-
return import_node_fs3.readFileSync(this.contextPath, "utf-8");
|
|
1650
|
-
}
|
|
1651
|
-
readProgress() {
|
|
1652
|
-
if (!import_node_fs3.existsSync(this.progressPath)) {
|
|
1653
|
-
return "";
|
|
1654
|
-
}
|
|
1655
|
-
return import_node_fs3.readFileSync(this.progressPath, "utf-8");
|
|
1656
|
-
}
|
|
1657
|
-
updateContext(content) {
|
|
1658
|
-
this.ensureDir(this.contextPath);
|
|
1659
|
-
import_node_fs3.writeFileSync(this.contextPath, content);
|
|
1660
|
-
}
|
|
1661
|
-
updateProgress(event) {
|
|
1662
|
-
this.ensureDir(this.progressPath);
|
|
1663
|
-
const existing = this.readProgress();
|
|
1664
|
-
const timestamp = (event.timestamp ?? new Date).toISOString();
|
|
1665
|
-
let entry = "";
|
|
1666
|
-
switch (event.type) {
|
|
1667
|
-
case "task_completed":
|
|
1668
|
-
entry = `- [x] ${event.title} — completed ${timestamp}`;
|
|
1669
|
-
break;
|
|
1670
|
-
case "sprint_started":
|
|
1671
|
-
entry = `
|
|
1672
|
-
## Current Sprint: ${event.title}
|
|
1673
|
-
**Status:** ACTIVE | Started: ${timestamp}
|
|
1674
|
-
`;
|
|
1675
|
-
break;
|
|
1676
|
-
case "sprint_completed":
|
|
1677
|
-
entry = `
|
|
1678
|
-
### Sprint Completed: ${event.title} — ${timestamp}
|
|
1679
|
-
`;
|
|
1680
|
-
break;
|
|
1681
|
-
case "blocker":
|
|
1682
|
-
entry = `- BLOCKER: ${event.title}`;
|
|
1683
|
-
break;
|
|
1684
|
-
case "pr_opened":
|
|
1685
|
-
entry = `- [ ] ${event.title} — PR opened ${timestamp}`;
|
|
1686
|
-
break;
|
|
1687
|
-
case "pr_reviewed":
|
|
1688
|
-
entry = `- ${event.title} — reviewed ${timestamp}`;
|
|
1689
|
-
break;
|
|
1690
|
-
case "pr_merged":
|
|
1691
|
-
entry = `- [x] ${event.title} — PR merged ${timestamp}`;
|
|
1692
|
-
break;
|
|
1693
|
-
case "exec_completed":
|
|
1694
|
-
entry = `- [x] ${event.title} — exec ${timestamp}`;
|
|
1695
|
-
break;
|
|
1696
|
-
}
|
|
1697
|
-
if (event.details) {
|
|
1698
|
-
entry += `
|
|
1699
|
-
${event.details}`;
|
|
1700
|
-
}
|
|
1701
|
-
const updated = existing ? `${existing}
|
|
1702
|
-
${entry}` : `# Project Progress
|
|
1703
|
-
|
|
1704
|
-
${entry}`;
|
|
1705
|
-
import_node_fs3.writeFileSync(this.progressPath, updated);
|
|
1706
|
-
}
|
|
1707
|
-
getFullContext() {
|
|
1708
|
-
const context = this.readContext();
|
|
1709
|
-
const progress = this.readProgress();
|
|
1710
|
-
const parts = [];
|
|
1711
|
-
if (context.trim()) {
|
|
1712
|
-
parts.push(context.trim());
|
|
1713
|
-
}
|
|
1714
|
-
if (progress.trim()) {
|
|
1715
|
-
parts.push(progress.trim());
|
|
1716
|
-
}
|
|
1717
|
-
return parts.join(`
|
|
1718
|
-
|
|
1719
|
-
---
|
|
1720
|
-
|
|
1721
|
-
`);
|
|
1722
|
-
}
|
|
1723
|
-
initialize(info) {
|
|
1724
|
-
this.ensureDir(this.contextPath);
|
|
1725
|
-
this.ensureDir(this.progressPath);
|
|
1726
|
-
const techStackList = info.techStack.map((t) => `- ${t}`).join(`
|
|
1727
|
-
`);
|
|
1728
|
-
const contextContent = `# Project: ${info.name}
|
|
1729
|
-
|
|
1730
|
-
## Mission
|
|
1731
|
-
${info.mission}
|
|
1732
|
-
|
|
1733
|
-
## Tech Stack
|
|
1734
|
-
${techStackList}
|
|
1735
|
-
|
|
1736
|
-
## Architecture
|
|
1737
|
-
<!-- Describe your high-level architecture here -->
|
|
1738
|
-
|
|
1739
|
-
## Key Decisions
|
|
1740
|
-
<!-- Document important technical decisions and their rationale -->
|
|
1741
|
-
|
|
1742
|
-
## Feature Areas
|
|
1743
|
-
<!-- List your main feature areas and their status -->
|
|
1744
|
-
`;
|
|
1745
|
-
const progressContent = `# Project Progress
|
|
1746
|
-
|
|
1747
|
-
No sprints started yet.
|
|
1748
|
-
`;
|
|
1749
|
-
import_node_fs3.writeFileSync(this.contextPath, contextContent);
|
|
1750
|
-
import_node_fs3.writeFileSync(this.progressPath, progressContent);
|
|
1751
|
-
}
|
|
1752
|
-
get exists() {
|
|
1753
|
-
return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
|
|
1754
|
-
}
|
|
1755
|
-
ensureDir(filePath) {
|
|
1756
|
-
const dir = import_node_path5.dirname(filePath);
|
|
1757
|
-
if (!import_node_fs3.existsSync(dir)) {
|
|
1758
|
-
import_node_fs3.mkdirSync(dir, { recursive: true });
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// src/worktree/worktree-manager.ts
|
|
1764
|
-
var import_node_child_process5 = require("node:child_process");
|
|
1765
|
-
var import_node_fs4 = require("node:fs");
|
|
1766
|
-
var import_node_path6 = require("node:path");
|
|
1865
|
+
var import_node_child_process4;
|
|
1866
|
+
var init_pr_service = __esm(() => {
|
|
1867
|
+
init_git_utils();
|
|
1868
|
+
import_node_child_process4 = require("node:child_process");
|
|
1869
|
+
});
|
|
1767
1870
|
|
|
1768
1871
|
// src/worktree/worktree-config.ts
|
|
1769
|
-
var WORKTREE_ROOT_DIR = ".locus-worktrees";
|
|
1770
|
-
var
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
};
|
|
1872
|
+
var WORKTREE_ROOT_DIR = ".locus-worktrees", WORKTREE_BRANCH_PREFIX = "agent", DEFAULT_WORKTREE_CONFIG;
|
|
1873
|
+
var init_worktree_config = __esm(() => {
|
|
1874
|
+
DEFAULT_WORKTREE_CONFIG = {
|
|
1875
|
+
rootDir: WORKTREE_ROOT_DIR,
|
|
1876
|
+
branchPrefix: WORKTREE_BRANCH_PREFIX,
|
|
1877
|
+
cleanupPolicy: "retain-on-failure"
|
|
1878
|
+
};
|
|
1879
|
+
});
|
|
1776
1880
|
|
|
1777
1881
|
// src/worktree/worktree-manager.ts
|
|
1778
1882
|
class WorktreeManager {
|
|
@@ -1799,6 +1903,15 @@ class WorktreeManager {
|
|
|
1799
1903
|
const worktreePath = import_node_path6.join(this.rootPath, worktreeDir);
|
|
1800
1904
|
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
1801
1905
|
const baseBranch = options.baseBranch ?? this.config.baseBranch ?? this.getCurrentBranch();
|
|
1906
|
+
if (!this.branchExists(baseBranch)) {
|
|
1907
|
+
this.log(`Base branch "${baseBranch}" not found locally, fetching from origin`, "info");
|
|
1908
|
+
try {
|
|
1909
|
+
this.gitExec(["fetch", "origin", baseBranch], this.projectPath);
|
|
1910
|
+
this.gitExec(["branch", baseBranch, `origin/${baseBranch}`], this.projectPath);
|
|
1911
|
+
} catch {
|
|
1912
|
+
this.log(`Could not fetch/create local branch for "${baseBranch}", falling back to current branch`, "warn");
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1802
1915
|
this.log(`Creating worktree: ${worktreeDir} (branch: ${branch}, base: ${baseBranch})`, "info");
|
|
1803
1916
|
if (import_node_fs4.existsSync(worktreePath)) {
|
|
1804
1917
|
this.log(`Removing stale worktree directory: ${worktreePath}`, "warn");
|
|
@@ -2062,11 +2175,15 @@ class WorktreeManager {
|
|
|
2062
2175
|
});
|
|
2063
2176
|
}
|
|
2064
2177
|
}
|
|
2178
|
+
var import_node_child_process5, import_node_fs4, import_node_path6;
|
|
2179
|
+
var init_worktree_manager = __esm(() => {
|
|
2180
|
+
init_worktree_config();
|
|
2181
|
+
import_node_child_process5 = require("node:child_process");
|
|
2182
|
+
import_node_fs4 = require("node:fs");
|
|
2183
|
+
import_node_path6 = require("node:path");
|
|
2184
|
+
});
|
|
2065
2185
|
|
|
2066
2186
|
// src/core/prompt-builder.ts
|
|
2067
|
-
var import_node_fs5 = require("node:fs");
|
|
2068
|
-
var import_node_path7 = require("node:path");
|
|
2069
|
-
var import_shared2 = require("@locusai/shared");
|
|
2070
2187
|
class PromptBuilder {
|
|
2071
2188
|
projectPath;
|
|
2072
2189
|
constructor(projectPath) {
|
|
@@ -2362,6 +2479,13 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2362
2479
|
}
|
|
2363
2480
|
}
|
|
2364
2481
|
}
|
|
2482
|
+
var import_node_fs5, import_node_path7, import_shared2;
|
|
2483
|
+
var init_prompt_builder = __esm(() => {
|
|
2484
|
+
init_config();
|
|
2485
|
+
import_node_fs5 = require("node:fs");
|
|
2486
|
+
import_node_path7 = require("node:path");
|
|
2487
|
+
import_shared2 = require("@locusai/shared");
|
|
2488
|
+
});
|
|
2365
2489
|
|
|
2366
2490
|
// src/agent/task-executor.ts
|
|
2367
2491
|
class TaskExecutor {
|
|
@@ -2386,162 +2510,51 @@ class TaskExecutor {
|
|
|
2386
2510
|
}
|
|
2387
2511
|
}
|
|
2388
2512
|
}
|
|
2513
|
+
var init_task_executor = __esm(() => {
|
|
2514
|
+
init_prompt_builder();
|
|
2515
|
+
});
|
|
2389
2516
|
|
|
2390
|
-
// src/agent/
|
|
2391
|
-
|
|
2392
|
-
if (!value || value.startsWith("--")) {
|
|
2393
|
-
console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
|
|
2394
|
-
return PROVIDER.CLAUDE;
|
|
2395
|
-
}
|
|
2396
|
-
if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
|
|
2397
|
-
return value;
|
|
2398
|
-
console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
|
|
2399
|
-
return PROVIDER.CLAUDE;
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
class AgentWorker {
|
|
2517
|
+
// src/agent/git-workflow.ts
|
|
2518
|
+
class GitWorkflow {
|
|
2403
2519
|
config;
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
prService = null;
|
|
2410
|
-
maxTasks = 50;
|
|
2411
|
-
tasksCompleted = 0;
|
|
2412
|
-
heartbeatInterval = null;
|
|
2413
|
-
currentTaskId = null;
|
|
2414
|
-
currentWorktreePath = null;
|
|
2415
|
-
postCleanupDelayMs = 5000;
|
|
2416
|
-
ghUsername = null;
|
|
2417
|
-
constructor(config) {
|
|
2520
|
+
log;
|
|
2521
|
+
ghUsername;
|
|
2522
|
+
worktreeManager;
|
|
2523
|
+
prService;
|
|
2524
|
+
constructor(config, log, ghUsername) {
|
|
2418
2525
|
this.config = config;
|
|
2526
|
+
this.log = log;
|
|
2527
|
+
this.ghUsername = ghUsername;
|
|
2419
2528
|
const projectPath = config.projectPath || process.cwd();
|
|
2420
|
-
this.
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
const log = this.log.bind(this);
|
|
2431
|
-
if (config.useWorktrees && !isGitAvailable()) {
|
|
2432
|
-
this.log("git is not installed — worktree isolation will not work", "error");
|
|
2433
|
-
config.useWorktrees = false;
|
|
2434
|
-
}
|
|
2435
|
-
if (config.autoPush && !isGhAvailable(projectPath)) {
|
|
2436
|
-
this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
|
|
2437
|
-
}
|
|
2438
|
-
if (config.autoPush) {
|
|
2439
|
-
this.ghUsername = getGhUsername();
|
|
2440
|
-
if (this.ghUsername) {
|
|
2441
|
-
this.log(`GitHub user: ${this.ghUsername}`, "info");
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
const provider = config.provider ?? PROVIDER.CLAUDE;
|
|
2445
|
-
this.aiRunner = createAiRunner(provider, {
|
|
2446
|
-
projectPath,
|
|
2447
|
-
model: config.model,
|
|
2448
|
-
log
|
|
2449
|
-
});
|
|
2450
|
-
this.taskExecutor = new TaskExecutor({
|
|
2451
|
-
aiRunner: this.aiRunner,
|
|
2452
|
-
projectPath,
|
|
2453
|
-
log
|
|
2454
|
-
});
|
|
2455
|
-
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
2456
|
-
if (config.useWorktrees) {
|
|
2457
|
-
this.worktreeManager = new WorktreeManager(projectPath, {
|
|
2458
|
-
cleanupPolicy: "auto"
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
if (config.autoPush) {
|
|
2462
|
-
this.prService = new PrService(projectPath, log);
|
|
2463
|
-
}
|
|
2464
|
-
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
2465
|
-
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
2466
|
-
if (config.useWorktrees) {
|
|
2467
|
-
this.log("Per-task worktree isolation enabled", "info");
|
|
2468
|
-
if (config.autoPush) {
|
|
2469
|
-
this.log("Auto-push enabled: branches will be pushed to remote", "info");
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
log(message, level = "info") {
|
|
2474
|
-
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
2475
|
-
const colorFn = {
|
|
2476
|
-
info: c.cyan,
|
|
2477
|
-
success: c.green,
|
|
2478
|
-
warn: c.yellow,
|
|
2479
|
-
error: c.red
|
|
2480
|
-
}[level];
|
|
2481
|
-
const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
|
|
2482
|
-
console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
|
|
2483
|
-
}
|
|
2484
|
-
async getActiveSprint() {
|
|
2485
|
-
try {
|
|
2486
|
-
if (this.config.sprintId) {
|
|
2487
|
-
return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
|
|
2488
|
-
}
|
|
2489
|
-
return await this.client.sprints.getActive(this.config.workspaceId);
|
|
2490
|
-
} catch (_error) {
|
|
2491
|
-
return null;
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
async getNextTask() {
|
|
2495
|
-
const maxRetries = 10;
|
|
2496
|
-
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
2497
|
-
try {
|
|
2498
|
-
const task = await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
|
|
2499
|
-
return task;
|
|
2500
|
-
} catch (error) {
|
|
2501
|
-
const isAxiosError = error != null && typeof error === "object" && "response" in error && typeof error.response?.status === "number";
|
|
2502
|
-
const status = isAxiosError ? error.response.status : 0;
|
|
2503
|
-
if (status === 404) {
|
|
2504
|
-
this.log("No tasks available in the backlog.", "info");
|
|
2505
|
-
return null;
|
|
2506
|
-
}
|
|
2507
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
2508
|
-
if (attempt < maxRetries) {
|
|
2509
|
-
this.log(`Nothing dispatched (attempt ${attempt}/${maxRetries}): ${msg}. Retrying in 30s...`, "warn");
|
|
2510
|
-
await new Promise((r) => setTimeout(r, 30000));
|
|
2511
|
-
} else {
|
|
2512
|
-
this.log(`Nothing dispatched after ${maxRetries} attempts: ${msg}`, "warn");
|
|
2513
|
-
return null;
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
return null;
|
|
2518
|
-
}
|
|
2519
|
-
createTaskWorktree(task) {
|
|
2520
|
-
if (!this.worktreeManager) {
|
|
2521
|
-
return {
|
|
2522
|
-
worktreePath: null,
|
|
2523
|
-
baseBranch: null,
|
|
2524
|
-
executor: this.taskExecutor
|
|
2525
|
-
};
|
|
2529
|
+
this.worktreeManager = config.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }) : null;
|
|
2530
|
+
this.prService = config.autoPush ? new PrService(projectPath, log) : null;
|
|
2531
|
+
}
|
|
2532
|
+
createTaskWorktree(task, defaultExecutor) {
|
|
2533
|
+
if (!this.worktreeManager) {
|
|
2534
|
+
return {
|
|
2535
|
+
worktreePath: null,
|
|
2536
|
+
baseBranch: null,
|
|
2537
|
+
executor: defaultExecutor
|
|
2538
|
+
};
|
|
2526
2539
|
}
|
|
2527
2540
|
const slug = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2528
2541
|
const result = this.worktreeManager.create({
|
|
2529
2542
|
taskId: task.id,
|
|
2530
2543
|
taskSlug: slug,
|
|
2531
|
-
agentId: this.config.agentId
|
|
2544
|
+
agentId: this.config.agentId,
|
|
2545
|
+
baseBranch: this.config.baseBranch
|
|
2532
2546
|
});
|
|
2533
2547
|
this.log(`Worktree created: ${result.worktreePath} (${result.branch})`, "info");
|
|
2534
|
-
const log = this.log.bind(this);
|
|
2535
2548
|
const provider = this.config.provider ?? PROVIDER.CLAUDE;
|
|
2536
2549
|
const taskAiRunner = createAiRunner(provider, {
|
|
2537
2550
|
projectPath: result.worktreePath,
|
|
2538
2551
|
model: this.config.model,
|
|
2539
|
-
log
|
|
2552
|
+
log: this.log
|
|
2540
2553
|
});
|
|
2541
2554
|
const taskExecutor = new TaskExecutor({
|
|
2542
2555
|
aiRunner: taskAiRunner,
|
|
2543
2556
|
projectPath: result.worktreePath,
|
|
2544
|
-
log
|
|
2557
|
+
log: this.log
|
|
2545
2558
|
});
|
|
2546
2559
|
return {
|
|
2547
2560
|
worktreePath: result.worktreePath,
|
|
@@ -2549,7 +2562,7 @@ class AgentWorker {
|
|
|
2549
2562
|
executor: taskExecutor
|
|
2550
2563
|
};
|
|
2551
2564
|
}
|
|
2552
|
-
|
|
2565
|
+
commitAndPush(worktreePath, task, baseBranch) {
|
|
2553
2566
|
if (!this.worktreeManager) {
|
|
2554
2567
|
return { branch: null, pushed: false, pushFailed: false };
|
|
2555
2568
|
}
|
|
@@ -2631,7 +2644,7 @@ ${trailers.join(`
|
|
|
2631
2644
|
return { url: null, error: errorMessage };
|
|
2632
2645
|
}
|
|
2633
2646
|
}
|
|
2634
|
-
|
|
2647
|
+
cleanupWorktree(worktreePath, keepBranch) {
|
|
2635
2648
|
if (!this.worktreeManager || !worktreePath)
|
|
2636
2649
|
return;
|
|
2637
2650
|
try {
|
|
@@ -2640,11 +2653,205 @@ ${trailers.join(`
|
|
|
2640
2653
|
} catch {
|
|
2641
2654
|
this.log(`Could not clean up worktree: ${worktreePath}`, "warn");
|
|
2642
2655
|
}
|
|
2643
|
-
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
var init_git_workflow = __esm(() => {
|
|
2659
|
+
init_factory();
|
|
2660
|
+
init_config();
|
|
2661
|
+
init_pr_service();
|
|
2662
|
+
init_worktree_manager();
|
|
2663
|
+
init_task_executor();
|
|
2664
|
+
});
|
|
2665
|
+
|
|
2666
|
+
// src/agent/worker-cli.ts
|
|
2667
|
+
var exports_worker_cli = {};
|
|
2668
|
+
__export(exports_worker_cli, {
|
|
2669
|
+
parseWorkerArgs: () => parseWorkerArgs
|
|
2670
|
+
});
|
|
2671
|
+
function resolveProvider(value) {
|
|
2672
|
+
if (!value || value.startsWith("--")) {
|
|
2673
|
+
console.warn("Warning: --provider requires a value. Falling back to 'claude'.");
|
|
2674
|
+
return PROVIDER.CLAUDE;
|
|
2675
|
+
}
|
|
2676
|
+
if (value === PROVIDER.CLAUDE || value === PROVIDER.CODEX)
|
|
2677
|
+
return value;
|
|
2678
|
+
console.warn(`Warning: invalid --provider value '${value}'. Falling back to 'claude'.`);
|
|
2679
|
+
return PROVIDER.CLAUDE;
|
|
2680
|
+
}
|
|
2681
|
+
function parseWorkerArgs(argv) {
|
|
2682
|
+
const args = argv.slice(2);
|
|
2683
|
+
const config = {};
|
|
2684
|
+
for (let i = 0;i < args.length; i++) {
|
|
2685
|
+
const arg = args[i];
|
|
2686
|
+
if (arg === "--agent-id")
|
|
2687
|
+
config.agentId = args[++i];
|
|
2688
|
+
else if (arg === "--workspace-id")
|
|
2689
|
+
config.workspaceId = args[++i];
|
|
2690
|
+
else if (arg === "--sprint-id")
|
|
2691
|
+
config.sprintId = args[++i];
|
|
2692
|
+
else if (arg === "--api-url")
|
|
2693
|
+
config.apiBase = args[++i];
|
|
2694
|
+
else if (arg === "--api-key")
|
|
2695
|
+
config.apiKey = args[++i];
|
|
2696
|
+
else if (arg === "--project-path")
|
|
2697
|
+
config.projectPath = args[++i];
|
|
2698
|
+
else if (arg === "--main-project-path")
|
|
2699
|
+
config.mainProjectPath = args[++i];
|
|
2700
|
+
else if (arg === "--model")
|
|
2701
|
+
config.model = args[++i];
|
|
2702
|
+
else if (arg === "--use-worktrees")
|
|
2703
|
+
config.useWorktrees = true;
|
|
2704
|
+
else if (arg === "--auto-push")
|
|
2705
|
+
config.autoPush = true;
|
|
2706
|
+
else if (arg === "--base-branch")
|
|
2707
|
+
config.baseBranch = args[++i];
|
|
2708
|
+
else if (arg === "--provider") {
|
|
2709
|
+
const value = args[i + 1];
|
|
2710
|
+
if (value && !value.startsWith("--"))
|
|
2711
|
+
i++;
|
|
2712
|
+
config.provider = resolveProvider(value);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
|
|
2716
|
+
console.error("Missing required arguments");
|
|
2717
|
+
process.exit(1);
|
|
2718
|
+
}
|
|
2719
|
+
return config;
|
|
2720
|
+
}
|
|
2721
|
+
var entrypoint;
|
|
2722
|
+
var init_worker_cli = __esm(() => {
|
|
2723
|
+
init_config();
|
|
2724
|
+
init_worker();
|
|
2725
|
+
entrypoint = process.argv[1]?.split(/[\\/]/).pop();
|
|
2726
|
+
if (entrypoint === "worker-cli.js" || entrypoint === "worker-cli.ts") {
|
|
2727
|
+
process.title = "locus-worker";
|
|
2728
|
+
const config = parseWorkerArgs(process.argv);
|
|
2729
|
+
const worker = new AgentWorker(config);
|
|
2730
|
+
worker.run().catch((err) => {
|
|
2731
|
+
console.error("Fatal worker error:", err);
|
|
2732
|
+
process.exit(1);
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
// src/agent/worker.ts
|
|
2738
|
+
var exports_worker = {};
|
|
2739
|
+
__export(exports_worker, {
|
|
2740
|
+
AgentWorker: () => AgentWorker
|
|
2741
|
+
});
|
|
2742
|
+
module.exports = __toCommonJS(exports_worker);
|
|
2743
|
+
|
|
2744
|
+
class AgentWorker {
|
|
2745
|
+
config;
|
|
2746
|
+
client;
|
|
2747
|
+
aiRunner;
|
|
2748
|
+
taskExecutor;
|
|
2749
|
+
knowledgeBase;
|
|
2750
|
+
gitWorkflow;
|
|
2751
|
+
maxTasks = 50;
|
|
2752
|
+
tasksCompleted = 0;
|
|
2753
|
+
heartbeatInterval = null;
|
|
2754
|
+
currentTaskId = null;
|
|
2755
|
+
currentWorktreePath = null;
|
|
2756
|
+
postCleanupDelayMs = 5000;
|
|
2757
|
+
constructor(config) {
|
|
2758
|
+
this.config = config;
|
|
2759
|
+
const projectPath = config.projectPath || process.cwd();
|
|
2760
|
+
this.client = new LocusClient({
|
|
2761
|
+
baseUrl: config.apiBase,
|
|
2762
|
+
token: config.apiKey,
|
|
2763
|
+
retryOptions: {
|
|
2764
|
+
maxRetries: 3,
|
|
2765
|
+
initialDelay: 1000,
|
|
2766
|
+
maxDelay: 5000,
|
|
2767
|
+
factor: 2
|
|
2768
|
+
}
|
|
2769
|
+
});
|
|
2770
|
+
const log = this.log.bind(this);
|
|
2771
|
+
if (config.useWorktrees && !isGitAvailable()) {
|
|
2772
|
+
this.log("git is not installed — worktree isolation will not work", "error");
|
|
2773
|
+
config.useWorktrees = false;
|
|
2774
|
+
}
|
|
2775
|
+
if (config.autoPush && !isGhAvailable(projectPath)) {
|
|
2776
|
+
this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
|
|
2777
|
+
}
|
|
2778
|
+
const ghUsername = config.autoPush ? getGhUsername() : null;
|
|
2779
|
+
if (ghUsername) {
|
|
2780
|
+
this.log(`GitHub user: ${ghUsername}`, "info");
|
|
2781
|
+
}
|
|
2782
|
+
const provider = config.provider ?? PROVIDER.CLAUDE;
|
|
2783
|
+
this.aiRunner = createAiRunner(provider, {
|
|
2784
|
+
projectPath,
|
|
2785
|
+
model: config.model,
|
|
2786
|
+
log
|
|
2787
|
+
});
|
|
2788
|
+
this.taskExecutor = new TaskExecutor({
|
|
2789
|
+
aiRunner: this.aiRunner,
|
|
2790
|
+
projectPath,
|
|
2791
|
+
log
|
|
2792
|
+
});
|
|
2793
|
+
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
2794
|
+
this.gitWorkflow = new GitWorkflow(config, log, ghUsername);
|
|
2795
|
+
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
2796
|
+
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
2797
|
+
if (config.useWorktrees) {
|
|
2798
|
+
this.log("Per-task worktree isolation enabled", "info");
|
|
2799
|
+
if (config.baseBranch) {
|
|
2800
|
+
this.log(`Base branch for worktrees: ${config.baseBranch}`, "info");
|
|
2801
|
+
}
|
|
2802
|
+
if (config.autoPush) {
|
|
2803
|
+
this.log("Auto-push enabled: branches will be pushed to remote", "info");
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
log(message, level = "info") {
|
|
2808
|
+
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
2809
|
+
const colorFn = {
|
|
2810
|
+
info: c.cyan,
|
|
2811
|
+
success: c.green,
|
|
2812
|
+
warn: c.yellow,
|
|
2813
|
+
error: c.red
|
|
2814
|
+
}[level];
|
|
2815
|
+
const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
|
|
2816
|
+
console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
|
|
2817
|
+
}
|
|
2818
|
+
async getActiveSprint() {
|
|
2819
|
+
try {
|
|
2820
|
+
if (this.config.sprintId) {
|
|
2821
|
+
return await this.client.sprints.getById(this.config.sprintId, this.config.workspaceId);
|
|
2822
|
+
}
|
|
2823
|
+
return await this.client.sprints.getActive(this.config.workspaceId);
|
|
2824
|
+
} catch (_error) {
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async getNextTask() {
|
|
2829
|
+
const maxRetries = 10;
|
|
2830
|
+
for (let attempt = 1;attempt <= maxRetries; attempt++) {
|
|
2831
|
+
try {
|
|
2832
|
+
return await this.client.workspaces.dispatch(this.config.workspaceId, this.config.agentId, this.config.sprintId);
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
const isAxiosError = error != null && typeof error === "object" && "response" in error && typeof error.response?.status === "number";
|
|
2835
|
+
const status = isAxiosError ? error.response.status : 0;
|
|
2836
|
+
if (status === 404) {
|
|
2837
|
+
this.log("No tasks available in the backlog.", "info");
|
|
2838
|
+
return null;
|
|
2839
|
+
}
|
|
2840
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2841
|
+
if (attempt < maxRetries) {
|
|
2842
|
+
this.log(`Nothing dispatched (attempt ${attempt}/${maxRetries}): ${msg}. Retrying in 30s...`, "warn");
|
|
2843
|
+
await new Promise((r) => setTimeout(r, 30000));
|
|
2844
|
+
} else {
|
|
2845
|
+
this.log(`Nothing dispatched after ${maxRetries} attempts: ${msg}`, "warn");
|
|
2846
|
+
return null;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
return null;
|
|
2644
2851
|
}
|
|
2645
2852
|
async executeTask(task) {
|
|
2646
2853
|
const fullTask = await this.client.tasks.getById(task.id, this.config.workspaceId);
|
|
2647
|
-
const { worktreePath, baseBranch, executor } = this.createTaskWorktree(fullTask);
|
|
2854
|
+
const { worktreePath, baseBranch, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
2648
2855
|
this.currentWorktreePath = worktreePath;
|
|
2649
2856
|
let branchPushed = false;
|
|
2650
2857
|
let keepBranch = false;
|
|
@@ -2656,7 +2863,7 @@ ${trailers.join(`
|
|
|
2656
2863
|
let prError = null;
|
|
2657
2864
|
let noChanges = false;
|
|
2658
2865
|
if (result.success && worktreePath) {
|
|
2659
|
-
const commitResult = this.
|
|
2866
|
+
const commitResult = this.gitWorkflow.commitAndPush(worktreePath, fullTask, baseBranch ?? undefined);
|
|
2660
2867
|
taskBranch = commitResult.branch;
|
|
2661
2868
|
branchPushed = commitResult.pushed;
|
|
2662
2869
|
keepBranch = taskBranch !== null;
|
|
@@ -2667,7 +2874,7 @@ ${trailers.join(`
|
|
|
2667
2874
|
this.log(`Preserving worktree after push failure: ${worktreePath}`, "warn");
|
|
2668
2875
|
}
|
|
2669
2876
|
if (branchPushed && taskBranch) {
|
|
2670
|
-
const prResult = this.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
|
|
2877
|
+
const prResult = this.gitWorkflow.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
|
|
2671
2878
|
prUrl = prResult.url;
|
|
2672
2879
|
prError = prResult.error ?? null;
|
|
2673
2880
|
if (!prUrl) {
|
|
@@ -2691,29 +2898,29 @@ ${trailers.join(`
|
|
|
2691
2898
|
if (preserveWorktree || keepBranch) {
|
|
2692
2899
|
this.currentWorktreePath = null;
|
|
2693
2900
|
} else {
|
|
2694
|
-
this.
|
|
2901
|
+
this.gitWorkflow.cleanupWorktree(worktreePath, keepBranch);
|
|
2902
|
+
this.currentWorktreePath = null;
|
|
2695
2903
|
}
|
|
2696
2904
|
}
|
|
2697
2905
|
}
|
|
2698
|
-
updateProgress(task,
|
|
2906
|
+
updateProgress(task, summary) {
|
|
2699
2907
|
try {
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
}
|
|
2908
|
+
this.knowledgeBase.updateProgress({
|
|
2909
|
+
role: "user",
|
|
2910
|
+
content: task.title
|
|
2911
|
+
});
|
|
2912
|
+
this.knowledgeBase.updateProgress({
|
|
2913
|
+
role: "assistant",
|
|
2914
|
+
content: summary
|
|
2915
|
+
});
|
|
2916
|
+
this.log(`Updated progress.md: ${task.title}`, "info");
|
|
2708
2917
|
} catch (err) {
|
|
2709
2918
|
this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
2710
2919
|
}
|
|
2711
2920
|
}
|
|
2712
2921
|
startHeartbeat() {
|
|
2713
2922
|
this.sendHeartbeat();
|
|
2714
|
-
this.heartbeatInterval = setInterval(() =>
|
|
2715
|
-
this.sendHeartbeat();
|
|
2716
|
-
}, 60000);
|
|
2923
|
+
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
|
|
2717
2924
|
}
|
|
2718
2925
|
stopHeartbeat() {
|
|
2719
2926
|
if (this.heartbeatInterval) {
|
|
@@ -2738,7 +2945,7 @@ ${trailers.join(`
|
|
|
2738
2945
|
this.log("Received shutdown signal. Aborting...", "warn");
|
|
2739
2946
|
this.aiRunner.abort();
|
|
2740
2947
|
this.stopHeartbeat();
|
|
2741
|
-
this.
|
|
2948
|
+
this.gitWorkflow.cleanupWorktree(this.currentWorktreePath, false);
|
|
2742
2949
|
process.exit(1);
|
|
2743
2950
|
};
|
|
2744
2951
|
process.on("SIGTERM", handleShutdown);
|
|
@@ -2794,16 +3001,7 @@ PR automation error: ${result.prError}` : "";
|
|
|
2794
3001
|
text: `✅ ${result.summary}${branchInfo}${prInfo}${prErrorInfo}`
|
|
2795
3002
|
});
|
|
2796
3003
|
this.tasksCompleted++;
|
|
2797
|
-
this.updateProgress(task,
|
|
2798
|
-
if (result.prUrl) {
|
|
2799
|
-
try {
|
|
2800
|
-
this.knowledgeBase.updateProgress({
|
|
2801
|
-
type: "pr_opened",
|
|
2802
|
-
title: task.title,
|
|
2803
|
-
details: `PR: ${result.prUrl}`
|
|
2804
|
-
});
|
|
2805
|
-
} catch {}
|
|
2806
|
-
}
|
|
3004
|
+
this.updateProgress(task, result.summary);
|
|
2807
3005
|
}
|
|
2808
3006
|
} else {
|
|
2809
3007
|
this.log(`Failed: ${task.title} - ${result.summary}`, "error");
|
|
@@ -2823,53 +3021,33 @@ PR automation error: ${result.prError}` : "";
|
|
|
2823
3021
|
this.currentTaskId = null;
|
|
2824
3022
|
this.stopHeartbeat();
|
|
2825
3023
|
this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
|
|
2826
|
-
process.exit(0);
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
var workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
|
|
2830
|
-
if (workerEntrypoint === "worker.js" || workerEntrypoint === "worker.ts") {
|
|
2831
|
-
process.title = "locus-worker";
|
|
2832
|
-
const args = process.argv.slice(2);
|
|
2833
|
-
const config = {};
|
|
2834
|
-
for (let i = 0;i < args.length; i++) {
|
|
2835
|
-
const arg = args[i];
|
|
2836
|
-
if (arg === "--agent-id")
|
|
2837
|
-
config.agentId = args[++i];
|
|
2838
|
-
else if (arg === "--workspace-id")
|
|
2839
|
-
config.workspaceId = args[++i];
|
|
2840
|
-
else if (arg === "--sprint-id")
|
|
2841
|
-
config.sprintId = args[++i];
|
|
2842
|
-
else if (arg === "--api-url")
|
|
2843
|
-
config.apiBase = args[++i];
|
|
2844
|
-
else if (arg === "--api-key")
|
|
2845
|
-
config.apiKey = args[++i];
|
|
2846
|
-
else if (arg === "--project-path")
|
|
2847
|
-
config.projectPath = args[++i];
|
|
2848
|
-
else if (arg === "--main-project-path")
|
|
2849
|
-
config.mainProjectPath = args[++i];
|
|
2850
|
-
else if (arg === "--model")
|
|
2851
|
-
config.model = args[++i];
|
|
2852
|
-
else if (arg === "--use-worktrees")
|
|
2853
|
-
config.useWorktrees = true;
|
|
2854
|
-
else if (arg === "--auto-push")
|
|
2855
|
-
config.autoPush = true;
|
|
2856
|
-
else if (arg === "--provider") {
|
|
2857
|
-
const value = args[i + 1];
|
|
2858
|
-
if (value && !value.startsWith("--"))
|
|
2859
|
-
i++;
|
|
2860
|
-
config.provider = resolveProvider(value);
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
if (!config.agentId || !config.workspaceId || !config.apiBase || !config.apiKey || !config.projectPath) {
|
|
2864
|
-
console.error("Missing required arguments");
|
|
2865
|
-
process.exit(1);
|
|
3024
|
+
process.exit(0);
|
|
2866
3025
|
}
|
|
2867
|
-
const worker = new AgentWorker(config);
|
|
2868
|
-
worker.run().catch((err) => {
|
|
2869
|
-
console.error("Fatal worker error:", err);
|
|
2870
|
-
process.exit(1);
|
|
2871
|
-
});
|
|
2872
3026
|
}
|
|
3027
|
+
var import_shared3, workerEntrypoint;
|
|
3028
|
+
var init_worker = __esm(() => {
|
|
3029
|
+
init_factory();
|
|
3030
|
+
init_config();
|
|
3031
|
+
init_git_utils();
|
|
3032
|
+
init_src();
|
|
3033
|
+
init_knowledge_base();
|
|
3034
|
+
init_colors();
|
|
3035
|
+
init_git_workflow();
|
|
3036
|
+
init_task_executor();
|
|
3037
|
+
import_shared3 = require("@locusai/shared");
|
|
3038
|
+
workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
|
|
3039
|
+
if (workerEntrypoint === "worker.js" || workerEntrypoint === "worker.ts") {
|
|
3040
|
+
process.title = "locus-worker";
|
|
3041
|
+
Promise.resolve().then(() => (init_worker_cli(), exports_worker_cli)).then(({ parseWorkerArgs: parseWorkerArgs2 }) => {
|
|
3042
|
+
const config = parseWorkerArgs2(process.argv);
|
|
3043
|
+
const worker = new AgentWorker(config);
|
|
3044
|
+
worker.run().catch((err) => {
|
|
3045
|
+
console.error("Fatal worker error:", err);
|
|
3046
|
+
process.exit(1);
|
|
3047
|
+
});
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
});
|
|
2873
3051
|
|
|
2874
3052
|
// src/index-node.ts
|
|
2875
3053
|
var exports_index_node = {};
|
|
@@ -2882,6 +3060,7 @@ __export(exports_index_node, {
|
|
|
2882
3060
|
getDefaultBranch: () => getDefaultBranch,
|
|
2883
3061
|
getCurrentBranch: () => getCurrentBranch,
|
|
2884
3062
|
getAgentArtifactsPath: () => getAgentArtifactsPath,
|
|
3063
|
+
extractJsonFromLLMOutput: () => extractJsonFromLLMOutput,
|
|
2885
3064
|
detectRemoteProvider: () => detectRemoteProvider,
|
|
2886
3065
|
createAiRunner: () => createAiRunner,
|
|
2887
3066
|
c: () => c,
|
|
@@ -2910,6 +3089,7 @@ __export(exports_index_node, {
|
|
|
2910
3089
|
KnowledgeBase: () => KnowledgeBase,
|
|
2911
3090
|
InvitationsModule: () => InvitationsModule,
|
|
2912
3091
|
HistoryManager: () => HistoryManager,
|
|
3092
|
+
GitWorkflow: () => GitWorkflow,
|
|
2913
3093
|
ExecSession: () => ExecSession,
|
|
2914
3094
|
ExecEventType: () => ExecEventType,
|
|
2915
3095
|
ExecEventEmitter: () => ExecEventEmitter,
|
|
@@ -3148,6 +3328,51 @@ class CodebaseIndexer {
|
|
|
3148
3328
|
}
|
|
3149
3329
|
}
|
|
3150
3330
|
|
|
3331
|
+
// src/utils/json-extractor.ts
|
|
3332
|
+
function extractJsonFromLLMOutput(raw) {
|
|
3333
|
+
const trimmed = raw.trim();
|
|
3334
|
+
const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
3335
|
+
if (codeBlockMatch) {
|
|
3336
|
+
return codeBlockMatch[1]?.trim() || "";
|
|
3337
|
+
}
|
|
3338
|
+
if (trimmed.startsWith("{")) {
|
|
3339
|
+
return trimmed;
|
|
3340
|
+
}
|
|
3341
|
+
const startIdx = trimmed.indexOf("{");
|
|
3342
|
+
if (startIdx === -1) {
|
|
3343
|
+
return trimmed;
|
|
3344
|
+
}
|
|
3345
|
+
let depth = 0;
|
|
3346
|
+
let inString = false;
|
|
3347
|
+
let escaped = false;
|
|
3348
|
+
for (let i = startIdx;i < trimmed.length; i++) {
|
|
3349
|
+
const ch = trimmed[i];
|
|
3350
|
+
if (escaped) {
|
|
3351
|
+
escaped = false;
|
|
3352
|
+
continue;
|
|
3353
|
+
}
|
|
3354
|
+
if (ch === "\\") {
|
|
3355
|
+
escaped = true;
|
|
3356
|
+
continue;
|
|
3357
|
+
}
|
|
3358
|
+
if (ch === '"') {
|
|
3359
|
+
inString = !inString;
|
|
3360
|
+
continue;
|
|
3361
|
+
}
|
|
3362
|
+
if (inString)
|
|
3363
|
+
continue;
|
|
3364
|
+
if (ch === "{")
|
|
3365
|
+
depth++;
|
|
3366
|
+
else if (ch === "}") {
|
|
3367
|
+
depth--;
|
|
3368
|
+
if (depth === 0) {
|
|
3369
|
+
return trimmed.slice(startIdx, i + 1);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
return trimmed.slice(startIdx);
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3151
3376
|
// src/agent/codebase-indexer-service.ts
|
|
3152
3377
|
class CodebaseIndexerService {
|
|
3153
3378
|
deps;
|
|
@@ -3173,11 +3398,12 @@ ${tree}
|
|
|
3173
3398
|
|
|
3174
3399
|
Return ONLY valid JSON, no markdown formatting.`;
|
|
3175
3400
|
const response = await this.deps.aiRunner.run(prompt);
|
|
3176
|
-
const
|
|
3177
|
-
|
|
3178
|
-
return JSON.parse(
|
|
3401
|
+
const jsonStr = extractJsonFromLLMOutput(response);
|
|
3402
|
+
try {
|
|
3403
|
+
return JSON.parse(jsonStr);
|
|
3404
|
+
} catch {
|
|
3405
|
+
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
3179
3406
|
}
|
|
3180
|
-
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
3181
3407
|
}, force);
|
|
3182
3408
|
if (index === null) {
|
|
3183
3409
|
this.deps.log("No changes detected, skipping reindex", "info");
|
|
@@ -3191,8 +3417,10 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
3191
3417
|
}
|
|
3192
3418
|
}
|
|
3193
3419
|
// src/agent/document-fetcher.ts
|
|
3420
|
+
init_config();
|
|
3194
3421
|
var import_node_fs7 = require("node:fs");
|
|
3195
3422
|
var import_node_path9 = require("node:path");
|
|
3423
|
+
|
|
3196
3424
|
class DocumentFetcher {
|
|
3197
3425
|
deps;
|
|
3198
3426
|
constructor(deps) {
|
|
@@ -3234,6 +3462,10 @@ class DocumentFetcher {
|
|
|
3234
3462
|
}
|
|
3235
3463
|
}
|
|
3236
3464
|
}
|
|
3465
|
+
|
|
3466
|
+
// src/agent/index.ts
|
|
3467
|
+
init_git_workflow();
|
|
3468
|
+
|
|
3237
3469
|
// src/agent/review-service.ts
|
|
3238
3470
|
var import_node_child_process6 = require("node:child_process");
|
|
3239
3471
|
|
|
@@ -3293,6 +3525,13 @@ Keep the review concise but thorough. Focus on substance over style.`;
|
|
|
3293
3525
|
}
|
|
3294
3526
|
}
|
|
3295
3527
|
// src/agent/reviewer-worker.ts
|
|
3528
|
+
init_factory();
|
|
3529
|
+
init_config();
|
|
3530
|
+
init_git_utils();
|
|
3531
|
+
init_pr_service();
|
|
3532
|
+
init_src();
|
|
3533
|
+
init_knowledge_base();
|
|
3534
|
+
init_colors();
|
|
3296
3535
|
function resolveProvider2(value) {
|
|
3297
3536
|
if (!value || value.startsWith("--"))
|
|
3298
3537
|
return PROVIDER.CLAUDE;
|
|
@@ -3451,9 +3690,12 @@ ${summary}`;
|
|
|
3451
3690
|
const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
|
|
3452
3691
|
try {
|
|
3453
3692
|
this.knowledgeBase.updateProgress({
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3693
|
+
role: "user",
|
|
3694
|
+
content: `Review PR #${pr.number}: ${pr.title}`
|
|
3695
|
+
});
|
|
3696
|
+
this.knowledgeBase.updateProgress({
|
|
3697
|
+
role: "assistant",
|
|
3698
|
+
content: `${status}: ${result.summary}`
|
|
3457
3699
|
});
|
|
3458
3700
|
} catch {}
|
|
3459
3701
|
this.reviewsCompleted++;
|
|
@@ -3505,6 +3747,21 @@ if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "revie
|
|
|
3505
3747
|
process.exit(1);
|
|
3506
3748
|
});
|
|
3507
3749
|
}
|
|
3750
|
+
|
|
3751
|
+
// src/agent/index.ts
|
|
3752
|
+
init_task_executor();
|
|
3753
|
+
init_worker();
|
|
3754
|
+
// src/ai/index.ts
|
|
3755
|
+
init_claude_runner();
|
|
3756
|
+
init_codex_runner();
|
|
3757
|
+
init_factory();
|
|
3758
|
+
// src/core/index.ts
|
|
3759
|
+
init_config();
|
|
3760
|
+
init_prompt_builder();
|
|
3761
|
+
|
|
3762
|
+
// src/index-node.ts
|
|
3763
|
+
init_prompt_builder();
|
|
3764
|
+
|
|
3508
3765
|
// src/exec/context-tracker.ts
|
|
3509
3766
|
var REFERENCE_ALIASES = {
|
|
3510
3767
|
plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
|
|
@@ -3918,6 +4175,7 @@ class ExecEventEmitter {
|
|
|
3918
4175
|
}
|
|
3919
4176
|
}
|
|
3920
4177
|
// src/exec/history-manager.ts
|
|
4178
|
+
init_config();
|
|
3921
4179
|
var import_node_fs8 = require("node:fs");
|
|
3922
4180
|
var import_node_path10 = require("node:path");
|
|
3923
4181
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
@@ -4295,62 +4553,474 @@ ${currentPrompt}`);
|
|
|
4295
4553
|
|
|
4296
4554
|
`);
|
|
4297
4555
|
}
|
|
4298
|
-
save() {
|
|
4299
|
-
if (!this.currentSession) {
|
|
4300
|
-
throw new Error("Session not initialized. Call initialize() first.");
|
|
4556
|
+
save() {
|
|
4557
|
+
if (!this.currentSession) {
|
|
4558
|
+
throw new Error("Session not initialized. Call initialize() first.");
|
|
4559
|
+
}
|
|
4560
|
+
if (this.contextTracker.hasContent()) {
|
|
4561
|
+
this.currentSession.metadata.contextTracker = this.contextTracker.toJSON();
|
|
4562
|
+
}
|
|
4563
|
+
this.history.saveSession(this.currentSession);
|
|
4564
|
+
this.history.pruneSessions();
|
|
4565
|
+
}
|
|
4566
|
+
reset() {
|
|
4567
|
+
if (!this.currentSession) {
|
|
4568
|
+
throw new Error("Session not initialized. Call initialize() first.");
|
|
4569
|
+
}
|
|
4570
|
+
this.currentSession.messages = [];
|
|
4571
|
+
this.currentSession.updatedAt = Date.now();
|
|
4572
|
+
this.contextTracker.clear();
|
|
4573
|
+
}
|
|
4574
|
+
startNewSession() {
|
|
4575
|
+
this.currentSession = this.history.createNewSession(this.model, this.provider);
|
|
4576
|
+
this.contextTracker.clear();
|
|
4577
|
+
this.eventEmitter.emitSessionStarted({
|
|
4578
|
+
model: this.model,
|
|
4579
|
+
provider: this.provider
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
end(success = true) {
|
|
4583
|
+
this.eventEmitter.emitSessionEnded(success);
|
|
4584
|
+
}
|
|
4585
|
+
on(eventType, listener) {
|
|
4586
|
+
this.eventEmitter.on(eventType, listener);
|
|
4587
|
+
return this;
|
|
4588
|
+
}
|
|
4589
|
+
off(eventType, listener) {
|
|
4590
|
+
this.eventEmitter.off(eventType, listener);
|
|
4591
|
+
return this;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
// src/git/index.ts
|
|
4595
|
+
init_git_utils();
|
|
4596
|
+
init_pr_service();
|
|
4597
|
+
|
|
4598
|
+
// src/index-node.ts
|
|
4599
|
+
init_src();
|
|
4600
|
+
|
|
4601
|
+
// src/orchestrator/index.ts
|
|
4602
|
+
init_git_utils();
|
|
4603
|
+
init_src();
|
|
4604
|
+
init_colors();
|
|
4605
|
+
init_worktree_manager();
|
|
4606
|
+
var import_shared6 = require("@locusai/shared");
|
|
4607
|
+
var import_events5 = require("events");
|
|
4608
|
+
|
|
4609
|
+
// src/orchestrator/agent-pool.ts
|
|
4610
|
+
init_colors();
|
|
4611
|
+
init_resolve_bin();
|
|
4612
|
+
var import_node_child_process7 = require("node:child_process");
|
|
4613
|
+
var import_node_fs9 = require("node:fs");
|
|
4614
|
+
var import_node_path11 = require("node:path");
|
|
4615
|
+
var import_node_url = require("node:url");
|
|
4616
|
+
var import_shared4 = require("@locusai/shared");
|
|
4617
|
+
var import_events4 = require("events");
|
|
4618
|
+
var MAX_AGENTS = 5;
|
|
4619
|
+
|
|
4620
|
+
class AgentPool extends import_events4.EventEmitter {
|
|
4621
|
+
config;
|
|
4622
|
+
agents = new Map;
|
|
4623
|
+
heartbeatInterval = null;
|
|
4624
|
+
constructor(config) {
|
|
4625
|
+
super();
|
|
4626
|
+
this.config = config;
|
|
4627
|
+
}
|
|
4628
|
+
get size() {
|
|
4629
|
+
return this.agents.size;
|
|
4630
|
+
}
|
|
4631
|
+
get effectiveAgentCount() {
|
|
4632
|
+
return Math.min(Math.max(this.config.agentCount ?? 1, 1), MAX_AGENTS);
|
|
4633
|
+
}
|
|
4634
|
+
getAll() {
|
|
4635
|
+
return Array.from(this.agents.values());
|
|
4636
|
+
}
|
|
4637
|
+
get(agentId) {
|
|
4638
|
+
return this.agents.get(agentId);
|
|
4639
|
+
}
|
|
4640
|
+
async spawn(index, resolvedSprintId, baseBranch) {
|
|
4641
|
+
const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
4642
|
+
const agentState = {
|
|
4643
|
+
id: agentId,
|
|
4644
|
+
status: "IDLE",
|
|
4645
|
+
currentTaskId: null,
|
|
4646
|
+
tasksCompleted: 0,
|
|
4647
|
+
tasksFailed: 0,
|
|
4648
|
+
lastHeartbeat: new Date
|
|
4649
|
+
};
|
|
4650
|
+
this.agents.set(agentId, agentState);
|
|
4651
|
+
console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
|
|
4652
|
+
`);
|
|
4653
|
+
const workerPath = this.resolveWorkerPath();
|
|
4654
|
+
if (!workerPath) {
|
|
4655
|
+
throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
|
|
4656
|
+
}
|
|
4657
|
+
const workerArgs = this.buildWorkerArgs(agentId, resolvedSprintId, baseBranch);
|
|
4658
|
+
const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
|
|
4659
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4660
|
+
detached: true,
|
|
4661
|
+
env: getAugmentedEnv({
|
|
4662
|
+
FORCE_COLOR: "1",
|
|
4663
|
+
TERM: "xterm-256color",
|
|
4664
|
+
LOCUS_WORKER: agentId,
|
|
4665
|
+
LOCUS_WORKSPACE: this.config.workspaceId
|
|
4666
|
+
})
|
|
4667
|
+
});
|
|
4668
|
+
agentState.process = agentProcess;
|
|
4669
|
+
this.attachProcessHandlers(agentId, agentState, agentProcess);
|
|
4670
|
+
this.emit("agent:spawned", { agentId });
|
|
4671
|
+
}
|
|
4672
|
+
async waitForAll(isRunning) {
|
|
4673
|
+
while (this.agents.size > 0 && isRunning()) {
|
|
4674
|
+
await sleep(2000);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
startHeartbeatMonitor() {
|
|
4678
|
+
this.heartbeatInterval = setInterval(() => {
|
|
4679
|
+
const now = Date.now();
|
|
4680
|
+
for (const [agentId, agent] of this.agents.entries()) {
|
|
4681
|
+
if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
|
|
4682
|
+
console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
|
|
4683
|
+
if (agent.process && !agent.process.killed) {
|
|
4684
|
+
killProcessTree(agent.process);
|
|
4685
|
+
}
|
|
4686
|
+
this.emit("agent:stale", { agentId });
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
}, 60000);
|
|
4690
|
+
}
|
|
4691
|
+
stopAgent(agentId) {
|
|
4692
|
+
const agent = this.agents.get(agentId);
|
|
4693
|
+
if (!agent)
|
|
4694
|
+
return false;
|
|
4695
|
+
if (agent.process && !agent.process.killed) {
|
|
4696
|
+
killProcessTree(agent.process);
|
|
4697
|
+
}
|
|
4698
|
+
return true;
|
|
4699
|
+
}
|
|
4700
|
+
shutdown() {
|
|
4701
|
+
if (this.heartbeatInterval) {
|
|
4702
|
+
clearInterval(this.heartbeatInterval);
|
|
4703
|
+
this.heartbeatInterval = null;
|
|
4704
|
+
}
|
|
4705
|
+
for (const [agentId, agent] of this.agents.entries()) {
|
|
4706
|
+
if (agent.process && !agent.process.killed) {
|
|
4707
|
+
console.log(`Killing agent: ${agentId}`);
|
|
4708
|
+
killProcessTree(agent.process);
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
this.agents.clear();
|
|
4712
|
+
}
|
|
4713
|
+
getStats() {
|
|
4714
|
+
return {
|
|
4715
|
+
activeAgents: this.agents.size,
|
|
4716
|
+
agentCount: this.effectiveAgentCount,
|
|
4717
|
+
totalTasksCompleted: this.getAll().reduce((sum, a) => sum + a.tasksCompleted, 0),
|
|
4718
|
+
totalTasksFailed: this.getAll().reduce((sum, a) => sum + a.tasksFailed, 0)
|
|
4719
|
+
};
|
|
4720
|
+
}
|
|
4721
|
+
buildWorkerArgs(agentId, resolvedSprintId, baseBranch) {
|
|
4722
|
+
const args = [
|
|
4723
|
+
"--agent-id",
|
|
4724
|
+
agentId,
|
|
4725
|
+
"--workspace-id",
|
|
4726
|
+
this.config.workspaceId,
|
|
4727
|
+
"--api-url",
|
|
4728
|
+
this.config.apiBase,
|
|
4729
|
+
"--api-key",
|
|
4730
|
+
this.config.apiKey,
|
|
4731
|
+
"--project-path",
|
|
4732
|
+
this.config.projectPath
|
|
4733
|
+
];
|
|
4734
|
+
if (this.config.model) {
|
|
4735
|
+
args.push("--model", this.config.model);
|
|
4736
|
+
}
|
|
4737
|
+
if (this.config.provider) {
|
|
4738
|
+
args.push("--provider", this.config.provider);
|
|
4739
|
+
}
|
|
4740
|
+
if (resolvedSprintId) {
|
|
4741
|
+
args.push("--sprint-id", resolvedSprintId);
|
|
4742
|
+
}
|
|
4743
|
+
if (this.config.useWorktrees ?? true) {
|
|
4744
|
+
args.push("--use-worktrees");
|
|
4745
|
+
}
|
|
4746
|
+
if (this.config.autoPush) {
|
|
4747
|
+
args.push("--auto-push");
|
|
4748
|
+
}
|
|
4749
|
+
if (baseBranch) {
|
|
4750
|
+
args.push("--base-branch", baseBranch);
|
|
4751
|
+
}
|
|
4752
|
+
return args;
|
|
4753
|
+
}
|
|
4754
|
+
attachProcessHandlers(agentId, agentState, proc) {
|
|
4755
|
+
proc.on("message", (msg) => {
|
|
4756
|
+
if (msg.type === "stats") {
|
|
4757
|
+
agentState.tasksCompleted = msg.tasksCompleted || 0;
|
|
4758
|
+
agentState.tasksFailed = msg.tasksFailed || 0;
|
|
4759
|
+
}
|
|
4760
|
+
if (msg.type === "heartbeat") {
|
|
4761
|
+
agentState.lastHeartbeat = new Date;
|
|
4762
|
+
}
|
|
4763
|
+
});
|
|
4764
|
+
proc.stdout?.on("data", (data) => {
|
|
4765
|
+
process.stdout.write(data.toString());
|
|
4766
|
+
});
|
|
4767
|
+
proc.stderr?.on("data", (data) => {
|
|
4768
|
+
process.stderr.write(data.toString());
|
|
4769
|
+
});
|
|
4770
|
+
proc.on("exit", (code) => {
|
|
4771
|
+
console.log(`
|
|
4772
|
+
${agentId} finished (exit code: ${code})`);
|
|
4773
|
+
const agent = this.agents.get(agentId);
|
|
4774
|
+
if (agent) {
|
|
4775
|
+
agent.status = code === 0 ? "COMPLETED" : "FAILED";
|
|
4776
|
+
this.emit("agent:completed", {
|
|
4777
|
+
agentId,
|
|
4778
|
+
status: agent.status,
|
|
4779
|
+
tasksCompleted: agent.tasksCompleted,
|
|
4780
|
+
tasksFailed: agent.tasksFailed
|
|
4781
|
+
});
|
|
4782
|
+
this.agents.delete(agentId);
|
|
4783
|
+
}
|
|
4784
|
+
});
|
|
4785
|
+
}
|
|
4786
|
+
resolveWorkerPath() {
|
|
4787
|
+
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/agent-pool.ts");
|
|
4788
|
+
const currentModuleDir = import_node_path11.dirname(currentModulePath);
|
|
4789
|
+
const potentialPaths = [
|
|
4790
|
+
import_node_path11.join(currentModuleDir, "..", "agent", "worker.js"),
|
|
4791
|
+
import_node_path11.join(currentModuleDir, "agent", "worker.js"),
|
|
4792
|
+
import_node_path11.join(currentModuleDir, "worker.js"),
|
|
4793
|
+
import_node_path11.join(currentModuleDir, "..", "agent", "worker.ts")
|
|
4794
|
+
];
|
|
4795
|
+
return potentialPaths.find((p) => import_node_fs9.existsSync(p));
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
function killProcessTree(proc) {
|
|
4799
|
+
if (!proc.pid || proc.killed)
|
|
4800
|
+
return;
|
|
4801
|
+
try {
|
|
4802
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
4803
|
+
} catch {
|
|
4804
|
+
try {
|
|
4805
|
+
proc.kill("SIGTERM");
|
|
4806
|
+
} catch {}
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
function sleep(ms) {
|
|
4810
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
// src/orchestrator/execution.ts
|
|
4814
|
+
init_git_utils();
|
|
4815
|
+
init_colors();
|
|
4816
|
+
var import_shared5 = require("@locusai/shared");
|
|
4817
|
+
var SPAWN_DELAY_MS = 5000;
|
|
4818
|
+
|
|
4819
|
+
class ExecutionStrategy {
|
|
4820
|
+
config;
|
|
4821
|
+
pool;
|
|
4822
|
+
tierMerge;
|
|
4823
|
+
resolvedSprintId;
|
|
4824
|
+
isRunning;
|
|
4825
|
+
constructor(config, pool, tierMerge, resolvedSprintId, isRunning) {
|
|
4826
|
+
this.config = config;
|
|
4827
|
+
this.pool = pool;
|
|
4828
|
+
this.tierMerge = tierMerge;
|
|
4829
|
+
this.resolvedSprintId = resolvedSprintId;
|
|
4830
|
+
this.isRunning = isRunning;
|
|
4831
|
+
}
|
|
4832
|
+
async execute(tasks2) {
|
|
4833
|
+
const hasTiers = tasks2.some((t) => t.tier !== null && t.tier !== undefined);
|
|
4834
|
+
const useWorktrees = this.config.useWorktrees ?? true;
|
|
4835
|
+
if (hasTiers && useWorktrees) {
|
|
4836
|
+
await this.tierBasedExecution(tasks2);
|
|
4837
|
+
} else {
|
|
4838
|
+
await this.legacyExecution(tasks2);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
async tierBasedExecution(allTasks) {
|
|
4842
|
+
const tierMap = groupByTier(allTasks);
|
|
4843
|
+
const tiers = Array.from(tierMap.keys()).sort((a, b) => a - b);
|
|
4844
|
+
const defaultBranch = getDefaultBranch(this.config.projectPath);
|
|
4845
|
+
console.log(c.primary(`\uD83D\uDCCA Tier-based execution: ${tiers.length} tier(s) detected [${tiers.join(", ")}]`));
|
|
4846
|
+
let currentBaseBranch = defaultBranch;
|
|
4847
|
+
for (const tier of tiers) {
|
|
4848
|
+
if (!this.isRunning())
|
|
4849
|
+
break;
|
|
4850
|
+
const tierTasks = tierMap.get(tier) ?? [];
|
|
4851
|
+
const dispatchable = tierTasks.filter(isDispatchable);
|
|
4852
|
+
if (dispatchable.length === 0) {
|
|
4853
|
+
console.log(c.dim(`ℹ Tier ${tier}: all ${tierTasks.length} task(s) already completed, skipping`));
|
|
4854
|
+
const tierBranch = this.tierMerge.tierBranchName(tier);
|
|
4855
|
+
if (this.tierMerge.remoteBranchExists(tierBranch)) {
|
|
4856
|
+
currentBaseBranch = tierBranch;
|
|
4857
|
+
}
|
|
4858
|
+
continue;
|
|
4859
|
+
}
|
|
4860
|
+
console.log(`
|
|
4861
|
+
${c.primary(`\uD83C\uDFD7️ Tier ${tier}:`)} ${dispatchable.length} task(s) | base: ${c.bold(currentBaseBranch)}`);
|
|
4862
|
+
await this.spawnAgentsForTasks(dispatchable.length, currentBaseBranch);
|
|
4863
|
+
await this.pool.waitForAll(this.isRunning);
|
|
4864
|
+
console.log(c.success(`✓ Tier ${tier} complete`));
|
|
4865
|
+
if (this.config.autoPush && tiers.indexOf(tier) < tiers.length - 1) {
|
|
4866
|
+
const mergeBranch = this.tierMerge.createMergeBranch(tier, currentBaseBranch);
|
|
4867
|
+
if (mergeBranch) {
|
|
4868
|
+
currentBaseBranch = mergeBranch;
|
|
4869
|
+
console.log(c.success(`\uD83D\uDCCC Created merge branch: ${mergeBranch} (base for tier ${tier + 1})`));
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
async legacyExecution(tasks2) {
|
|
4875
|
+
const defaultBranch = getDefaultBranch(this.config.projectPath);
|
|
4876
|
+
await this.spawnAgentsForTasks(tasks2.length, defaultBranch);
|
|
4877
|
+
await this.pool.waitForAll(this.isRunning);
|
|
4878
|
+
}
|
|
4879
|
+
async spawnAgentsForTasks(taskCount, baseBranch) {
|
|
4880
|
+
const agentsToSpawn = Math.min(this.pool.effectiveAgentCount, taskCount);
|
|
4881
|
+
const spawnPromises = [];
|
|
4882
|
+
for (let i = 0;i < agentsToSpawn; i++) {
|
|
4883
|
+
if (i > 0) {
|
|
4884
|
+
await sleep2(SPAWN_DELAY_MS);
|
|
4885
|
+
}
|
|
4886
|
+
spawnPromises.push(this.pool.spawn(i, this.resolvedSprintId, baseBranch));
|
|
4887
|
+
}
|
|
4888
|
+
await Promise.all(spawnPromises);
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
function groupByTier(tasks2) {
|
|
4892
|
+
const tierMap = new Map;
|
|
4893
|
+
for (const task of tasks2) {
|
|
4894
|
+
const tier = task.tier ?? 0;
|
|
4895
|
+
const existing = tierMap.get(tier);
|
|
4896
|
+
if (existing) {
|
|
4897
|
+
existing.push(task);
|
|
4898
|
+
} else {
|
|
4899
|
+
tierMap.set(tier, [task]);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
return tierMap;
|
|
4903
|
+
}
|
|
4904
|
+
function isDispatchable(task) {
|
|
4905
|
+
return task.status === import_shared5.TaskStatus.BACKLOG || task.status === import_shared5.TaskStatus.IN_PROGRESS && !task.assignedTo;
|
|
4906
|
+
}
|
|
4907
|
+
function sleep2(ms) {
|
|
4908
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
// src/orchestrator/tier-merge.ts
|
|
4912
|
+
init_colors();
|
|
4913
|
+
var import_node_child_process8 = require("node:child_process");
|
|
4914
|
+
var TIER_BRANCH_PREFIX = "locus/tier";
|
|
4915
|
+
|
|
4916
|
+
class TierMergeService {
|
|
4917
|
+
projectPath;
|
|
4918
|
+
sprintId;
|
|
4919
|
+
tierTaskIds = new Map;
|
|
4920
|
+
constructor(projectPath, sprintId) {
|
|
4921
|
+
this.projectPath = projectPath;
|
|
4922
|
+
this.sprintId = sprintId;
|
|
4923
|
+
}
|
|
4924
|
+
registerTierTasks(tasks2) {
|
|
4925
|
+
for (const task of tasks2) {
|
|
4926
|
+
const tier = task.tier ?? 0;
|
|
4927
|
+
const existing = this.tierTaskIds.get(tier);
|
|
4928
|
+
if (existing) {
|
|
4929
|
+
existing.push(task.id);
|
|
4930
|
+
} else {
|
|
4931
|
+
this.tierTaskIds.set(tier, [task.id]);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
tierBranchName(tier) {
|
|
4936
|
+
const suffix = this.sprintId ? `-${this.sprintId.slice(0, 8)}` : "";
|
|
4937
|
+
return `${TIER_BRANCH_PREFIX}-${tier}${suffix}`;
|
|
4938
|
+
}
|
|
4939
|
+
remoteBranchExists(branch) {
|
|
4940
|
+
try {
|
|
4941
|
+
import_node_child_process8.execFileSync("git", ["ls-remote", "--exit-code", "--heads", "origin", branch], {
|
|
4942
|
+
cwd: this.projectPath,
|
|
4943
|
+
encoding: "utf-8",
|
|
4944
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4945
|
+
});
|
|
4946
|
+
return true;
|
|
4947
|
+
} catch {
|
|
4948
|
+
return false;
|
|
4301
4949
|
}
|
|
4302
|
-
|
|
4303
|
-
|
|
4950
|
+
}
|
|
4951
|
+
createMergeBranch(tier, baseBranch) {
|
|
4952
|
+
const mergeBranchName = this.tierBranchName(tier);
|
|
4953
|
+
try {
|
|
4954
|
+
this.gitExec(["fetch", "origin"]);
|
|
4955
|
+
const tierTaskBranches = this.findTierTaskBranches(tier);
|
|
4956
|
+
if (tierTaskBranches.length === 0) {
|
|
4957
|
+
console.log(c.dim(` Tier ${tier}: no pushed task branches found, skipping merge branch creation`));
|
|
4958
|
+
return null;
|
|
4959
|
+
}
|
|
4960
|
+
console.log(c.dim(` Merging ${tierTaskBranches.length} branch(es) into ${mergeBranchName}: ${tierTaskBranches.join(", ")}`));
|
|
4961
|
+
try {
|
|
4962
|
+
this.gitExec(["branch", "-D", mergeBranchName]);
|
|
4963
|
+
} catch {}
|
|
4964
|
+
this.gitExec(["checkout", "-b", mergeBranchName, `origin/${baseBranch}`]);
|
|
4965
|
+
for (const branch of tierTaskBranches) {
|
|
4966
|
+
try {
|
|
4967
|
+
this.gitExec(["merge", `origin/${branch}`, "--no-edit"]);
|
|
4968
|
+
} catch (err) {
|
|
4969
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4970
|
+
console.log(c.error(` Merge conflict merging ${branch} into ${mergeBranchName}: ${msg}`));
|
|
4971
|
+
try {
|
|
4972
|
+
this.gitExec(["merge", "--abort"]);
|
|
4973
|
+
} catch {}
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
this.gitExec(["push", "-u", "origin", mergeBranchName, "--force"]);
|
|
4977
|
+
this.gitExec(["checkout", baseBranch]);
|
|
4978
|
+
return mergeBranchName;
|
|
4979
|
+
} catch (err) {
|
|
4980
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4981
|
+
console.log(c.error(`Failed to create tier merge branch: ${msg}`));
|
|
4982
|
+
try {
|
|
4983
|
+
this.gitExec(["checkout", baseBranch]);
|
|
4984
|
+
} catch {}
|
|
4985
|
+
return null;
|
|
4304
4986
|
}
|
|
4305
|
-
this.history.saveSession(this.currentSession);
|
|
4306
|
-
this.history.pruneSessions();
|
|
4307
4987
|
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4988
|
+
findTierTaskBranches(tier) {
|
|
4989
|
+
try {
|
|
4990
|
+
const output = import_node_child_process8.execSync('git branch -r --list "origin/agent/*" --format="%(refname:short)"', { cwd: this.projectPath, encoding: "utf-8" }).trim();
|
|
4991
|
+
if (!output)
|
|
4992
|
+
return [];
|
|
4993
|
+
const remoteBranches = output.split(`
|
|
4994
|
+
`).map((b) => b.replace("origin/", ""));
|
|
4995
|
+
return remoteBranches.filter((branch) => {
|
|
4996
|
+
const match = branch.match(/^agent\/([^-]+)/);
|
|
4997
|
+
if (!match)
|
|
4998
|
+
return false;
|
|
4999
|
+
const taskIdPrefix = match[1];
|
|
5000
|
+
return this.tierTaskIds.get(tier)?.some((id) => id.startsWith(taskIdPrefix) || taskIdPrefix.startsWith(id.slice(0, 8))) ?? false;
|
|
5001
|
+
});
|
|
5002
|
+
} catch {
|
|
5003
|
+
return [];
|
|
4311
5004
|
}
|
|
4312
|
-
this.currentSession.messages = [];
|
|
4313
|
-
this.currentSession.updatedAt = Date.now();
|
|
4314
|
-
this.contextTracker.clear();
|
|
4315
5005
|
}
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
provider: this.provider
|
|
5006
|
+
gitExec(args) {
|
|
5007
|
+
return import_node_child_process8.execFileSync("git", args, {
|
|
5008
|
+
cwd: this.projectPath,
|
|
5009
|
+
encoding: "utf-8",
|
|
5010
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4322
5011
|
});
|
|
4323
5012
|
}
|
|
4324
|
-
end(success = true) {
|
|
4325
|
-
this.eventEmitter.emitSessionEnded(success);
|
|
4326
|
-
}
|
|
4327
|
-
on(eventType, listener) {
|
|
4328
|
-
this.eventEmitter.on(eventType, listener);
|
|
4329
|
-
return this;
|
|
4330
|
-
}
|
|
4331
|
-
off(eventType, listener) {
|
|
4332
|
-
this.eventEmitter.off(eventType, listener);
|
|
4333
|
-
return this;
|
|
4334
|
-
}
|
|
4335
5013
|
}
|
|
4336
|
-
// src/orchestrator.ts
|
|
4337
|
-
var import_node_child_process7 = require("node:child_process");
|
|
4338
|
-
var import_node_fs9 = require("node:fs");
|
|
4339
|
-
var import_node_path11 = require("node:path");
|
|
4340
|
-
var import_node_url = require("node:url");
|
|
4341
|
-
var import_shared4 = require("@locusai/shared");
|
|
4342
|
-
var import_events4 = require("events");
|
|
4343
|
-
var MAX_AGENTS = 5;
|
|
4344
5014
|
|
|
4345
|
-
|
|
5015
|
+
// src/orchestrator/index.ts
|
|
5016
|
+
class AgentOrchestrator extends import_events5.EventEmitter {
|
|
4346
5017
|
client;
|
|
4347
5018
|
config;
|
|
4348
|
-
|
|
5019
|
+
pool;
|
|
4349
5020
|
isRunning = false;
|
|
4350
5021
|
processedTasks = new Set;
|
|
4351
5022
|
resolvedSprintId = null;
|
|
4352
5023
|
worktreeManager = null;
|
|
4353
|
-
heartbeatInterval = null;
|
|
4354
5024
|
constructor(config) {
|
|
4355
5025
|
super();
|
|
4356
5026
|
this.config = config;
|
|
@@ -4358,9 +5028,10 @@ class AgentOrchestrator extends import_events4.EventEmitter {
|
|
|
4358
5028
|
baseUrl: config.apiBase,
|
|
4359
5029
|
token: config.apiKey
|
|
4360
5030
|
});
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
5031
|
+
this.pool = new AgentPool(config);
|
|
5032
|
+
this.pool.on("agent:spawned", (data) => this.emit("agent:spawned", data));
|
|
5033
|
+
this.pool.on("agent:completed", (data) => this.emit("agent:completed", data));
|
|
5034
|
+
this.pool.on("agent:stale", (data) => this.emit("agent:stale", data));
|
|
4364
5035
|
}
|
|
4365
5036
|
get useWorktrees() {
|
|
4366
5037
|
return this.config.useWorktrees ?? true;
|
|
@@ -4404,6 +5075,28 @@ class AgentOrchestrator extends import_events4.EventEmitter {
|
|
|
4404
5075
|
config: this.config,
|
|
4405
5076
|
sprintId: this.resolvedSprintId
|
|
4406
5077
|
});
|
|
5078
|
+
this.printBanner();
|
|
5079
|
+
const tasks2 = await this.getAvailableTasks();
|
|
5080
|
+
if (tasks2.length === 0) {
|
|
5081
|
+
console.log(c.dim("ℹ No available tasks found in the backlog."));
|
|
5082
|
+
return;
|
|
5083
|
+
}
|
|
5084
|
+
if (!this.preflightChecks(tasks2))
|
|
5085
|
+
return;
|
|
5086
|
+
if (this.useWorktrees) {
|
|
5087
|
+
this.worktreeManager = new WorktreeManager(this.config.projectPath, {
|
|
5088
|
+
cleanupPolicy: this.worktreeCleanupPolicy
|
|
5089
|
+
});
|
|
5090
|
+
}
|
|
5091
|
+
this.pool.startHeartbeatMonitor();
|
|
5092
|
+
const tierMerge = new TierMergeService(this.config.projectPath, this.resolvedSprintId);
|
|
5093
|
+
tierMerge.registerTierTasks(tasks2);
|
|
5094
|
+
const execution = new ExecutionStrategy(this.config, this.pool, tierMerge, this.resolvedSprintId, () => this.isRunning);
|
|
5095
|
+
await execution.execute(tasks2);
|
|
5096
|
+
console.log(`
|
|
5097
|
+
${c.success("✅ Orchestrator finished")}`);
|
|
5098
|
+
}
|
|
5099
|
+
printBanner() {
|
|
4407
5100
|
console.log(`
|
|
4408
5101
|
${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
4409
5102
|
console.log(c.dim("----------------------------------------------"));
|
|
@@ -4411,7 +5104,7 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
|
4411
5104
|
if (this.resolvedSprintId) {
|
|
4412
5105
|
console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
|
|
4413
5106
|
}
|
|
4414
|
-
console.log(`${c.bold("Agents:")} ${this.
|
|
5107
|
+
console.log(`${c.bold("Agents:")} ${this.pool.effectiveAgentCount}`);
|
|
4415
5108
|
console.log(`${c.bold("Worktrees:")} ${this.useWorktrees ? "enabled" : "disabled"}`);
|
|
4416
5109
|
if (this.useWorktrees) {
|
|
4417
5110
|
console.log(`${c.bold("Cleanup policy:")} ${this.worktreeCleanupPolicy}`);
|
|
@@ -4420,154 +5113,16 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
|
4420
5113
|
console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
|
|
4421
5114
|
console.log(c.dim(`----------------------------------------------
|
|
4422
5115
|
`));
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
return;
|
|
4427
|
-
}
|
|
4428
|
-
if (tasks2.length > 0 && this.useWorktrees && !isGitAvailable()) {
|
|
5116
|
+
}
|
|
5117
|
+
preflightChecks(_tasks) {
|
|
5118
|
+
if (this.useWorktrees && !isGitAvailable()) {
|
|
4429
5119
|
console.log(c.error("git is not installed. Worktree isolation requires git. Install from https://git-scm.com/"));
|
|
4430
|
-
return;
|
|
5120
|
+
return false;
|
|
4431
5121
|
}
|
|
4432
|
-
if (
|
|
5122
|
+
if (this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
|
|
4433
5123
|
console.log(c.warning("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/"));
|
|
4434
5124
|
}
|
|
4435
|
-
|
|
4436
|
-
this.worktreeManager = new WorktreeManager(this.config.projectPath, {
|
|
4437
|
-
cleanupPolicy: this.worktreeCleanupPolicy
|
|
4438
|
-
});
|
|
4439
|
-
}
|
|
4440
|
-
this.startHeartbeatMonitor();
|
|
4441
|
-
const agentsToSpawn = Math.min(this.agentCount, tasks2.length);
|
|
4442
|
-
const SPAWN_DELAY_MS = 5000;
|
|
4443
|
-
const spawnPromises = [];
|
|
4444
|
-
for (let i = 0;i < agentsToSpawn; i++) {
|
|
4445
|
-
if (i > 0) {
|
|
4446
|
-
await this.sleep(SPAWN_DELAY_MS);
|
|
4447
|
-
}
|
|
4448
|
-
spawnPromises.push(this.spawnAgent(i));
|
|
4449
|
-
}
|
|
4450
|
-
await Promise.all(spawnPromises);
|
|
4451
|
-
while (this.agents.size > 0 && this.isRunning) {
|
|
4452
|
-
if (this.agents.size === 0) {
|
|
4453
|
-
break;
|
|
4454
|
-
}
|
|
4455
|
-
await this.sleep(2000);
|
|
4456
|
-
}
|
|
4457
|
-
console.log(`
|
|
4458
|
-
${c.success("✅ Orchestrator finished")}`);
|
|
4459
|
-
}
|
|
4460
|
-
async spawnAgent(index) {
|
|
4461
|
-
const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
4462
|
-
const agentState = {
|
|
4463
|
-
id: agentId,
|
|
4464
|
-
status: "IDLE",
|
|
4465
|
-
currentTaskId: null,
|
|
4466
|
-
tasksCompleted: 0,
|
|
4467
|
-
tasksFailed: 0,
|
|
4468
|
-
lastHeartbeat: new Date
|
|
4469
|
-
};
|
|
4470
|
-
this.agents.set(agentId, agentState);
|
|
4471
|
-
console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
|
|
4472
|
-
`);
|
|
4473
|
-
const workerPath = this.resolveWorkerPath();
|
|
4474
|
-
if (!workerPath) {
|
|
4475
|
-
throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
|
|
4476
|
-
}
|
|
4477
|
-
const workerArgs = [
|
|
4478
|
-
"--agent-id",
|
|
4479
|
-
agentId,
|
|
4480
|
-
"--workspace-id",
|
|
4481
|
-
this.config.workspaceId,
|
|
4482
|
-
"--api-url",
|
|
4483
|
-
this.config.apiBase,
|
|
4484
|
-
"--api-key",
|
|
4485
|
-
this.config.apiKey,
|
|
4486
|
-
"--project-path",
|
|
4487
|
-
this.config.projectPath
|
|
4488
|
-
];
|
|
4489
|
-
if (this.config.model) {
|
|
4490
|
-
workerArgs.push("--model", this.config.model);
|
|
4491
|
-
}
|
|
4492
|
-
if (this.config.provider) {
|
|
4493
|
-
workerArgs.push("--provider", this.config.provider);
|
|
4494
|
-
}
|
|
4495
|
-
if (this.resolvedSprintId) {
|
|
4496
|
-
workerArgs.push("--sprint-id", this.resolvedSprintId);
|
|
4497
|
-
}
|
|
4498
|
-
if (this.useWorktrees) {
|
|
4499
|
-
workerArgs.push("--use-worktrees");
|
|
4500
|
-
}
|
|
4501
|
-
if (this.config.autoPush) {
|
|
4502
|
-
workerArgs.push("--auto-push");
|
|
4503
|
-
}
|
|
4504
|
-
const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
|
|
4505
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
4506
|
-
detached: true,
|
|
4507
|
-
env: {
|
|
4508
|
-
...process.env,
|
|
4509
|
-
FORCE_COLOR: "1",
|
|
4510
|
-
TERM: "xterm-256color",
|
|
4511
|
-
LOCUS_WORKER: agentId,
|
|
4512
|
-
LOCUS_WORKSPACE: this.config.workspaceId
|
|
4513
|
-
}
|
|
4514
|
-
});
|
|
4515
|
-
agentState.process = agentProcess;
|
|
4516
|
-
agentProcess.on("message", (msg) => {
|
|
4517
|
-
if (msg.type === "stats") {
|
|
4518
|
-
agentState.tasksCompleted = msg.tasksCompleted || 0;
|
|
4519
|
-
agentState.tasksFailed = msg.tasksFailed || 0;
|
|
4520
|
-
}
|
|
4521
|
-
if (msg.type === "heartbeat") {
|
|
4522
|
-
agentState.lastHeartbeat = new Date;
|
|
4523
|
-
}
|
|
4524
|
-
});
|
|
4525
|
-
agentProcess.stdout?.on("data", (data) => {
|
|
4526
|
-
process.stdout.write(data.toString());
|
|
4527
|
-
});
|
|
4528
|
-
agentProcess.stderr?.on("data", (data) => {
|
|
4529
|
-
process.stderr.write(data.toString());
|
|
4530
|
-
});
|
|
4531
|
-
agentProcess.on("exit", (code) => {
|
|
4532
|
-
console.log(`
|
|
4533
|
-
${agentId} finished (exit code: ${code})`);
|
|
4534
|
-
const agent = this.agents.get(agentId);
|
|
4535
|
-
if (agent) {
|
|
4536
|
-
agent.status = code === 0 ? "COMPLETED" : "FAILED";
|
|
4537
|
-
this.emit("agent:completed", {
|
|
4538
|
-
agentId,
|
|
4539
|
-
status: agent.status,
|
|
4540
|
-
tasksCompleted: agent.tasksCompleted,
|
|
4541
|
-
tasksFailed: agent.tasksFailed
|
|
4542
|
-
});
|
|
4543
|
-
this.agents.delete(agentId);
|
|
4544
|
-
}
|
|
4545
|
-
});
|
|
4546
|
-
this.emit("agent:spawned", { agentId });
|
|
4547
|
-
}
|
|
4548
|
-
resolveWorkerPath() {
|
|
4549
|
-
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
|
|
4550
|
-
const currentModuleDir = import_node_path11.dirname(currentModulePath);
|
|
4551
|
-
const potentialPaths = [
|
|
4552
|
-
import_node_path11.join(currentModuleDir, "agent", "worker.js"),
|
|
4553
|
-
import_node_path11.join(currentModuleDir, "worker.js"),
|
|
4554
|
-
import_node_path11.join(currentModuleDir, "agent", "worker.ts")
|
|
4555
|
-
];
|
|
4556
|
-
return potentialPaths.find((p) => import_node_fs9.existsSync(p));
|
|
4557
|
-
}
|
|
4558
|
-
startHeartbeatMonitor() {
|
|
4559
|
-
this.heartbeatInterval = setInterval(() => {
|
|
4560
|
-
const now = Date.now();
|
|
4561
|
-
for (const [agentId, agent] of this.agents.entries()) {
|
|
4562
|
-
if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
|
|
4563
|
-
console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
|
|
4564
|
-
if (agent.process && !agent.process.killed) {
|
|
4565
|
-
this.killProcessTree(agent.process);
|
|
4566
|
-
}
|
|
4567
|
-
this.emit("agent:stale", { agentId });
|
|
4568
|
-
}
|
|
4569
|
-
}
|
|
4570
|
-
}, 60000);
|
|
5125
|
+
return true;
|
|
4571
5126
|
}
|
|
4572
5127
|
async getAvailableTasks() {
|
|
4573
5128
|
try {
|
|
@@ -4579,16 +5134,16 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4579
5134
|
}
|
|
4580
5135
|
}
|
|
4581
5136
|
async assignTaskToAgent(agentId) {
|
|
4582
|
-
const agent = this.
|
|
5137
|
+
const agent = this.pool.get(agentId);
|
|
4583
5138
|
if (!agent)
|
|
4584
5139
|
return null;
|
|
4585
5140
|
try {
|
|
4586
5141
|
const tasks2 = await this.getAvailableTasks();
|
|
4587
5142
|
const priorityOrder = [
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
5143
|
+
import_shared6.TaskPriority.CRITICAL,
|
|
5144
|
+
import_shared6.TaskPriority.HIGH,
|
|
5145
|
+
import_shared6.TaskPriority.MEDIUM,
|
|
5146
|
+
import_shared6.TaskPriority.LOW
|
|
4592
5147
|
];
|
|
4593
5148
|
let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
|
|
4594
5149
|
if (!task && tasks2.length > 0) {
|
|
@@ -4612,7 +5167,7 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4612
5167
|
async completeTask(taskId, agentId, summary) {
|
|
4613
5168
|
try {
|
|
4614
5169
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
4615
|
-
status:
|
|
5170
|
+
status: import_shared6.TaskStatus.IN_REVIEW
|
|
4616
5171
|
});
|
|
4617
5172
|
if (summary) {
|
|
4618
5173
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
@@ -4623,7 +5178,7 @@ ${summary}`
|
|
|
4623
5178
|
});
|
|
4624
5179
|
}
|
|
4625
5180
|
this.processedTasks.add(taskId);
|
|
4626
|
-
const agent = this.
|
|
5181
|
+
const agent = this.pool.get(agentId);
|
|
4627
5182
|
if (agent) {
|
|
4628
5183
|
agent.tasksCompleted += 1;
|
|
4629
5184
|
agent.currentTaskId = null;
|
|
@@ -4637,14 +5192,14 @@ ${summary}`
|
|
|
4637
5192
|
async failTask(taskId, agentId, error) {
|
|
4638
5193
|
try {
|
|
4639
5194
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
4640
|
-
status:
|
|
5195
|
+
status: import_shared6.TaskStatus.BACKLOG,
|
|
4641
5196
|
assignedTo: null
|
|
4642
5197
|
});
|
|
4643
5198
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
4644
5199
|
author: agentId,
|
|
4645
5200
|
text: `❌ Agent failed: ${error}`
|
|
4646
5201
|
});
|
|
4647
|
-
const agent = this.
|
|
5202
|
+
const agent = this.pool.get(agentId);
|
|
4648
5203
|
if (agent) {
|
|
4649
5204
|
agent.tasksFailed += 1;
|
|
4650
5205
|
agent.currentTaskId = null;
|
|
@@ -4661,36 +5216,10 @@ ${summary}`
|
|
|
4661
5216
|
this.emit("stopped", { timestamp: new Date });
|
|
4662
5217
|
}
|
|
4663
5218
|
stopAgent(agentId) {
|
|
4664
|
-
|
|
4665
|
-
if (!agent)
|
|
4666
|
-
return false;
|
|
4667
|
-
if (agent.process && !agent.process.killed) {
|
|
4668
|
-
this.killProcessTree(agent.process);
|
|
4669
|
-
}
|
|
4670
|
-
return true;
|
|
4671
|
-
}
|
|
4672
|
-
killProcessTree(proc) {
|
|
4673
|
-
if (!proc.pid || proc.killed)
|
|
4674
|
-
return;
|
|
4675
|
-
try {
|
|
4676
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
4677
|
-
} catch {
|
|
4678
|
-
try {
|
|
4679
|
-
proc.kill("SIGTERM");
|
|
4680
|
-
} catch {}
|
|
4681
|
-
}
|
|
5219
|
+
return this.pool.stopAgent(agentId);
|
|
4682
5220
|
}
|
|
4683
5221
|
async cleanup() {
|
|
4684
|
-
|
|
4685
|
-
clearInterval(this.heartbeatInterval);
|
|
4686
|
-
this.heartbeatInterval = null;
|
|
4687
|
-
}
|
|
4688
|
-
for (const [agentId, agent] of this.agents.entries()) {
|
|
4689
|
-
if (agent.process && !agent.process.killed) {
|
|
4690
|
-
console.log(`Killing agent: ${agentId}`);
|
|
4691
|
-
this.killProcessTree(agent.process);
|
|
4692
|
-
}
|
|
4693
|
-
}
|
|
5222
|
+
this.pool.shutdown();
|
|
4694
5223
|
if (this.worktreeManager) {
|
|
4695
5224
|
try {
|
|
4696
5225
|
if (this.worktreeCleanupPolicy === "auto") {
|
|
@@ -4708,31 +5237,27 @@ ${summary}`
|
|
|
4708
5237
|
console.log(c.dim("Could not clean up some worktrees"));
|
|
4709
5238
|
}
|
|
4710
5239
|
}
|
|
4711
|
-
this.agents.clear();
|
|
4712
5240
|
}
|
|
4713
5241
|
getStats() {
|
|
5242
|
+
const poolStats = this.pool.getStats();
|
|
4714
5243
|
return {
|
|
4715
|
-
|
|
4716
|
-
agentCount: this.agentCount,
|
|
5244
|
+
...poolStats,
|
|
4717
5245
|
useWorktrees: this.useWorktrees,
|
|
4718
|
-
processedTasks: this.processedTasks.size
|
|
4719
|
-
totalTasksCompleted: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksCompleted, 0),
|
|
4720
|
-
totalTasksFailed: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksFailed, 0)
|
|
5246
|
+
processedTasks: this.processedTasks.size
|
|
4721
5247
|
};
|
|
4722
5248
|
}
|
|
4723
5249
|
getAgentStates() {
|
|
4724
|
-
return
|
|
4725
|
-
}
|
|
4726
|
-
sleep(ms) {
|
|
4727
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5250
|
+
return this.pool.getAll();
|
|
4728
5251
|
}
|
|
4729
5252
|
}
|
|
4730
5253
|
// src/planning/plan-manager.ts
|
|
5254
|
+
init_config();
|
|
5255
|
+
init_knowledge_base();
|
|
4731
5256
|
var import_node_fs10 = require("node:fs");
|
|
4732
5257
|
var import_node_path12 = require("node:path");
|
|
4733
5258
|
|
|
4734
5259
|
// src/planning/sprint-plan.ts
|
|
4735
|
-
var
|
|
5260
|
+
var import_shared7 = require("@locusai/shared");
|
|
4736
5261
|
function sprintPlanToMarkdown(plan) {
|
|
4737
5262
|
const lines = [];
|
|
4738
5263
|
lines.push(`# Sprint Plan: ${plan.name}`);
|
|
@@ -4754,23 +5279,32 @@ function sprintPlanToMarkdown(plan) {
|
|
|
4754
5279
|
}
|
|
4755
5280
|
lines.push(`## Tasks (${plan.tasks.length})`);
|
|
4756
5281
|
lines.push("");
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
}
|
|
5282
|
+
const maxTier = Math.max(0, ...plan.tasks.map((t) => t.tier ?? 0));
|
|
5283
|
+
for (let tier = 0;tier <= maxTier; tier++) {
|
|
5284
|
+
const tierTasks = plan.tasks.filter((t) => (t.tier ?? 0) === tier);
|
|
5285
|
+
if (tierTasks.length === 0)
|
|
5286
|
+
continue;
|
|
5287
|
+
lines.push(`### Tier ${tier}${tier === 0 ? " (Foundation)" : ""}`);
|
|
5288
|
+
lines.push(tier === 0 ? "_These tasks run first and must complete before higher tiers start._" : `_These tasks run in parallel after Tier ${tier - 1} completes._`);
|
|
4765
5289
|
lines.push("");
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
lines.push(
|
|
4770
|
-
|
|
4771
|
-
|
|
5290
|
+
for (const task of tierTasks) {
|
|
5291
|
+
lines.push(`#### ${task.index}. ${task.title}`);
|
|
5292
|
+
lines.push(`- **Role:** ${task.assigneeRole}`);
|
|
5293
|
+
lines.push(`- **Priority:** ${task.priority}`);
|
|
5294
|
+
lines.push(`- **Complexity:** ${"█".repeat(task.complexity)}${"░".repeat(5 - task.complexity)} (${task.complexity}/5)`);
|
|
5295
|
+
if (task.labels.length > 0) {
|
|
5296
|
+
lines.push(`- **Labels:** ${task.labels.join(", ")}`);
|
|
4772
5297
|
}
|
|
4773
5298
|
lines.push("");
|
|
5299
|
+
lines.push(task.description);
|
|
5300
|
+
lines.push("");
|
|
5301
|
+
if (task.acceptanceCriteria.length > 0) {
|
|
5302
|
+
lines.push(`**Acceptance Criteria:**`);
|
|
5303
|
+
for (const ac of task.acceptanceCriteria) {
|
|
5304
|
+
lines.push(`- [ ] ${ac}`);
|
|
5305
|
+
}
|
|
5306
|
+
lines.push("");
|
|
5307
|
+
}
|
|
4774
5308
|
}
|
|
4775
5309
|
}
|
|
4776
5310
|
if (plan.risks.length > 0) {
|
|
@@ -4791,12 +5325,13 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
|
|
|
4791
5325
|
return plan.tasks.map((task) => ({
|
|
4792
5326
|
title: task.title,
|
|
4793
5327
|
description: task.description,
|
|
4794
|
-
status:
|
|
5328
|
+
status: import_shared7.TaskStatus.BACKLOG,
|
|
4795
5329
|
assigneeRole: task.assigneeRole,
|
|
4796
5330
|
priority: task.priority,
|
|
4797
5331
|
labels: task.labels,
|
|
4798
5332
|
sprintId,
|
|
4799
5333
|
order: task.index * 10,
|
|
5334
|
+
tier: task.tier,
|
|
4800
5335
|
acceptanceChecklist: task.acceptanceCriteria.map((text, i) => ({
|
|
4801
5336
|
id: `ac-${i + 1}`,
|
|
4802
5337
|
text,
|
|
@@ -4805,11 +5340,7 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
|
|
|
4805
5340
|
}));
|
|
4806
5341
|
}
|
|
4807
5342
|
function parseSprintPlanFromAI(raw, directive) {
|
|
4808
|
-
|
|
4809
|
-
const jsonMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
4810
|
-
if (jsonMatch) {
|
|
4811
|
-
jsonStr = jsonMatch[1]?.trim() || "";
|
|
4812
|
-
}
|
|
5343
|
+
const jsonStr = extractJsonFromLLMOutput(raw);
|
|
4813
5344
|
let parsed = JSON.parse(jsonStr);
|
|
4814
5345
|
if (parsed.revisedPlan) {
|
|
4815
5346
|
parsed = parsed.revisedPlan;
|
|
@@ -4824,7 +5355,8 @@ function parseSprintPlanFromAI(raw, directive) {
|
|
|
4824
5355
|
priority: t.priority || "MEDIUM",
|
|
4825
5356
|
complexity: t.complexity || 3,
|
|
4826
5357
|
acceptanceCriteria: t.acceptanceCriteria || [],
|
|
4827
|
-
labels: t.labels || []
|
|
5358
|
+
labels: t.labels || [],
|
|
5359
|
+
tier: typeof t.tier === "number" ? t.tier : 0
|
|
4828
5360
|
}));
|
|
4829
5361
|
return {
|
|
4830
5362
|
id,
|
|
@@ -4913,9 +5445,12 @@ class PlanManager {
|
|
|
4913
5445
|
this.save(plan);
|
|
4914
5446
|
const kb = new KnowledgeBase(this.projectPath);
|
|
4915
5447
|
kb.updateProgress({
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
5448
|
+
role: "user",
|
|
5449
|
+
content: `Start sprint: ${plan.name}`
|
|
5450
|
+
});
|
|
5451
|
+
kb.updateProgress({
|
|
5452
|
+
role: "assistant",
|
|
5453
|
+
content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
|
|
4919
5454
|
});
|
|
4920
5455
|
return { sprint, tasks: tasks2 };
|
|
4921
5456
|
}
|
|
@@ -4978,6 +5513,8 @@ class PlanManager {
|
|
|
4978
5513
|
}
|
|
4979
5514
|
}
|
|
4980
5515
|
// src/planning/planning-meeting.ts
|
|
5516
|
+
init_config();
|
|
5517
|
+
init_knowledge_base();
|
|
4981
5518
|
var import_node_fs11 = require("node:fs");
|
|
4982
5519
|
|
|
4983
5520
|
// src/planning/agents/architect.ts
|
|
@@ -5032,7 +5569,7 @@ Tasks are executed by INDEPENDENT agents on SEPARATE git branches that get merge
|
|
|
5032
5569
|
|
|
5033
5570
|
## Output Format
|
|
5034
5571
|
|
|
5035
|
-
|
|
5572
|
+
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5036
5573
|
|
|
5037
5574
|
{
|
|
5038
5575
|
"tasks": [
|
|
@@ -5066,13 +5603,16 @@ You are a combined Architect, Senior Engineer, and Sprint Planner performing a F
|
|
|
5066
5603
|
|
|
5067
5604
|
## Context
|
|
5068
5605
|
|
|
5069
|
-
In this system,
|
|
5070
|
-
-
|
|
5071
|
-
-
|
|
5072
|
-
-
|
|
5073
|
-
-
|
|
5606
|
+
In this system, tasks are organized into execution **tiers** (0, 1, 2, ...):
|
|
5607
|
+
- All tasks within the same tier run IN PARALLEL on separate git branches (worktrees)
|
|
5608
|
+
- Agents within a tier have NO knowledge of what other agents in that tier are doing
|
|
5609
|
+
- After ALL tasks in tier N complete and merge, tier N+1 starts
|
|
5610
|
+
- Tier N+1 branches are created FROM the merged tier N result, so they can see tier N's work
|
|
5074
5611
|
|
|
5075
|
-
This means
|
|
5612
|
+
This means:
|
|
5613
|
+
- Two tasks in the SAME tier that modify the same file WILL cause merge conflicts
|
|
5614
|
+
- Two tasks in DIFFERENT tiers are safe — the later tier sees the earlier tier's merged output
|
|
5615
|
+
- Tier assignment is critical: foundational work must be in tier 0, dependent work in higher tiers
|
|
5076
5616
|
|
|
5077
5617
|
## CEO Directive
|
|
5078
5618
|
> ${input.directive}
|
|
@@ -5096,42 +5636,50 @@ ${input.sprintOrganizerOutput}
|
|
|
5096
5636
|
|
|
5097
5637
|
Go through EACH pair of tasks and check for:
|
|
5098
5638
|
|
|
5099
|
-
### 1. File Overlap Analysis
|
|
5639
|
+
### 1. File Overlap Analysis (WITHIN the same tier)
|
|
5100
5640
|
For each task, list the files it will likely modify. Then check:
|
|
5101
|
-
- Do any two tasks modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
|
|
5102
|
-
- If yes: MERGE those tasks or move shared changes to a foundational task
|
|
5641
|
+
- Do any two tasks **in the same tier** modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
|
|
5642
|
+
- If yes: MERGE those tasks, move them to different tiers, or move shared changes to a foundational task in a lower tier
|
|
5643
|
+
- Note: tasks in different tiers are safe because higher tiers branch from merged lower-tier results
|
|
5103
5644
|
|
|
5104
|
-
### 2. Duplicated Work Detection
|
|
5105
|
-
Check if multiple tasks
|
|
5645
|
+
### 2. Duplicated Work Detection (WITHIN the same tier)
|
|
5646
|
+
Check if multiple tasks **in the same tier**:
|
|
5106
5647
|
- Add the same environment variable or config field
|
|
5107
5648
|
- Install or configure the same dependency
|
|
5108
5649
|
- Register the same module or provider
|
|
5109
5650
|
- Create the same helper function, guard, interceptor, or middleware
|
|
5110
5651
|
- Add the same import to a shared file
|
|
5111
|
-
If yes: consolidate into ONE task
|
|
5652
|
+
If yes: consolidate into ONE task or move the shared work to a lower tier
|
|
5112
5653
|
|
|
5113
5654
|
### 3. Self-Containment Validation
|
|
5114
5655
|
For each task, verify:
|
|
5115
5656
|
- Does it include ALL config/env changes it needs?
|
|
5116
5657
|
- Does it include ALL module registrations it needs?
|
|
5117
5658
|
- Does it include ALL dependency installations it needs?
|
|
5118
|
-
- Can it be completed without ANY output from
|
|
5659
|
+
- Can it be completed without ANY output from tasks in the SAME tier? (It CAN depend on lower-tier tasks that are already merged)
|
|
5660
|
+
|
|
5661
|
+
### 4. Tier Assignment Validation
|
|
5662
|
+
Verify tier assignments are correct:
|
|
5663
|
+
- Foundational tasks (schemas, config, shared code) MUST be in tier 0
|
|
5664
|
+
- Tasks that depend on another task's output must be in a HIGHER tier
|
|
5665
|
+
- Tasks in the same tier must be truly independent of each other
|
|
5666
|
+
- No circular dependencies between tiers
|
|
5119
5667
|
|
|
5120
|
-
###
|
|
5121
|
-
Identify the highest-risk files (files that multiple tasks might touch) and ensure only ONE task modifies each.
|
|
5668
|
+
### 5. Merge Conflict Risk Zones
|
|
5669
|
+
Identify the highest-risk files (files that multiple same-tier tasks might touch) and ensure only ONE task per tier modifies each.
|
|
5122
5670
|
|
|
5123
5671
|
## Output Format
|
|
5124
5672
|
|
|
5125
|
-
|
|
5673
|
+
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5126
5674
|
|
|
5127
5675
|
{
|
|
5128
5676
|
"hasIssues": true | false,
|
|
5129
5677
|
"issues": [
|
|
5130
5678
|
{
|
|
5131
|
-
"type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk",
|
|
5679
|
+
"type": "file_overlap" | "duplicated_work" | "not_self_contained" | "merge_conflict_risk" | "wrong_tier",
|
|
5132
5680
|
"description": "string describing the specific issue",
|
|
5133
5681
|
"affectedTasks": ["Task Title 1", "Task Title 2"],
|
|
5134
|
-
"resolution": "string describing how to fix it (merge, move, consolidate)"
|
|
5682
|
+
"resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
|
|
5135
5683
|
}
|
|
5136
5684
|
],
|
|
5137
5685
|
"revisedPlan": {
|
|
@@ -5146,7 +5694,8 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
|
|
|
5146
5694
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5147
5695
|
"labels": ["string"],
|
|
5148
5696
|
"acceptanceCriteria": ["string"],
|
|
5149
|
-
"complexity": 3
|
|
5697
|
+
"complexity": 3,
|
|
5698
|
+
"tier": 0
|
|
5150
5699
|
}
|
|
5151
5700
|
],
|
|
5152
5701
|
"risks": [
|
|
@@ -5160,11 +5709,14 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
|
|
|
5160
5709
|
}
|
|
5161
5710
|
|
|
5162
5711
|
IMPORTANT:
|
|
5163
|
-
- If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (tasks merged, duplicated work consolidated, etc.)
|
|
5712
|
+
- If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (tasks merged, duplicated work consolidated, tier assignments fixed, etc.)
|
|
5164
5713
|
- If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
|
|
5165
5714
|
- The revisedPlan is ALWAYS required — it becomes the final plan
|
|
5166
5715
|
- When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
|
|
5167
|
-
- Prefer fewer, larger, self-contained tasks over many small conflicting ones
|
|
5716
|
+
- Prefer fewer, larger, self-contained tasks over many small conflicting ones
|
|
5717
|
+
- Every task MUST have a "tier" field (integer >= 0)
|
|
5718
|
+
- tier 0 = foundational (runs first), tier 1 = depends on tier 0, tier 2 = depends on tier 1, etc.
|
|
5719
|
+
- Tasks within the same tier run in parallel — they MUST NOT conflict with each other`;
|
|
5168
5720
|
return prompt;
|
|
5169
5721
|
}
|
|
5170
5722
|
|
|
@@ -5196,13 +5748,16 @@ Produce the final sprint plan:
|
|
|
5196
5748
|
1. **Sprint Name** — A concise, memorable name for this sprint (e.g., "User Authentication", "Payment Integration")
|
|
5197
5749
|
2. **Sprint Goal** — One paragraph describing what this sprint delivers
|
|
5198
5750
|
3. **Task Ordering** — Final ordering so that foundational work comes first. The position in the array IS the execution order — task at index 0 runs first, index 1 runs second, etc.
|
|
5199
|
-
4. **
|
|
5200
|
-
5. **
|
|
5751
|
+
4. **Tier Assignment** — Assign each task an execution tier (integer, starting at 0). Tasks within the same tier run IN PARALLEL on separate git branches. Tasks in tier N+1 only start AFTER all tier N tasks are complete and merged. Tier 0 = foundational tasks (config, schemas, shared code). Higher tiers build on lower tier outputs.
|
|
5752
|
+
5. **Duration Estimate** — How many days this sprint will take with 2-3 agents working in parallel
|
|
5753
|
+
6. **Final Task List** — Each task with all fields filled in, ordered by execution priority
|
|
5201
5754
|
|
|
5202
5755
|
Guidelines:
|
|
5203
5756
|
- The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
|
|
5204
|
-
- Foundation tasks (schemas, config, shared code) must appear before tasks that build on them
|
|
5205
|
-
-
|
|
5757
|
+
- Foundation tasks (schemas, config, shared code) must appear before tasks that build on them AND must be in tier 0
|
|
5758
|
+
- Tasks within the same tier MUST be truly independent — no shared file modifications, no dependencies between them
|
|
5759
|
+
- Tasks that depend on outputs from other tasks must be in a higher tier than those dependencies
|
|
5760
|
+
- Group related independent tasks in the same tier for maximum parallelism
|
|
5206
5761
|
- Ensure acceptance criteria are specific and testable
|
|
5207
5762
|
- Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
|
|
5208
5763
|
|
|
@@ -5212,12 +5767,12 @@ Before finalizing, validate that EVERY task is fully self-contained and conflict
|
|
|
5212
5767
|
|
|
5213
5768
|
1. **No two tasks should modify the same file.** If they do, merge them or restructure so shared changes live in one foundational task.
|
|
5214
5769
|
2. **No duplicated work.** Each env var, config field, dependency, module import, or helper function must be introduced by exactly ONE task.
|
|
5215
|
-
3. **Each task is independently executable.** An agent working on task
|
|
5770
|
+
3. **Each task is independently executable within its tier.** An agent working on a task must be able to complete it without knowing what other tasks in the same tier are doing. Tasks CAN depend on lower-tier tasks since those are merged before the current tier starts.
|
|
5216
5771
|
4. **Prefer fewer, larger self-contained tasks over many small overlapping ones.** Do not split a task if the parts would conflict with each other.
|
|
5217
5772
|
|
|
5218
5773
|
## Output Format
|
|
5219
5774
|
|
|
5220
|
-
|
|
5775
|
+
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5221
5776
|
|
|
5222
5777
|
{
|
|
5223
5778
|
"name": "string (2-4 words)",
|
|
@@ -5231,7 +5786,8 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
|
|
|
5231
5786
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5232
5787
|
"labels": ["string"],
|
|
5233
5788
|
"acceptanceCriteria": ["string"],
|
|
5234
|
-
"complexity": 3
|
|
5789
|
+
"complexity": 3,
|
|
5790
|
+
"tier": 0
|
|
5235
5791
|
}
|
|
5236
5792
|
],
|
|
5237
5793
|
"risks": [
|
|
@@ -5241,7 +5797,14 @@ Respond with ONLY a JSON object (no markdown code blocks, no explanation):
|
|
|
5241
5797
|
"severity": "low | medium | high"
|
|
5242
5798
|
}
|
|
5243
5799
|
]
|
|
5244
|
-
}
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
IMPORTANT about tiers:
|
|
5803
|
+
- tier 0 = foundational tasks (run first, merged before anything else)
|
|
5804
|
+
- tier 1 = tasks that depend on tier 0 outputs (run in parallel after tier 0 merges)
|
|
5805
|
+
- tier 2 = tasks that depend on tier 1 outputs (run in parallel after tier 1 merges)
|
|
5806
|
+
- Tasks within the same tier run in parallel on separate branches — they MUST NOT conflict
|
|
5807
|
+
- Every task MUST have a "tier" field (integer >= 0)`;
|
|
5245
5808
|
return prompt;
|
|
5246
5809
|
}
|
|
5247
5810
|
|
|
@@ -5298,7 +5861,7 @@ Tasks will be executed by INDEPENDENT agents on SEPARATE git branches that get m
|
|
|
5298
5861
|
|
|
5299
5862
|
## Output Format
|
|
5300
5863
|
|
|
5301
|
-
|
|
5864
|
+
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5302
5865
|
|
|
5303
5866
|
{
|
|
5304
5867
|
"tasks": [
|
|
@@ -5410,3 +5973,10 @@ class PlanningMeeting {
|
|
|
5410
5973
|
}
|
|
5411
5974
|
}
|
|
5412
5975
|
}
|
|
5976
|
+
// src/index-node.ts
|
|
5977
|
+
init_knowledge_base();
|
|
5978
|
+
init_colors();
|
|
5979
|
+
|
|
5980
|
+
// src/worktree/index.ts
|
|
5981
|
+
init_worktree_config();
|
|
5982
|
+
init_worktree_manager();
|