@locusai/sdk 0.9.18 → 0.10.2
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/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 +1 -1
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +1660 -1133
- 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/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");
|
|
@@ -2820,56 +3018,36 @@ PR automation error: ${result.prError}` : "";
|
|
|
2820
3018
|
this.sendHeartbeat();
|
|
2821
3019
|
await this.delayAfterCleanup();
|
|
2822
3020
|
}
|
|
2823
|
-
this.currentTaskId = null;
|
|
2824
|
-
this.stopHeartbeat();
|
|
2825
|
-
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);
|
|
3021
|
+
this.currentTaskId = null;
|
|
3022
|
+
this.stopHeartbeat();
|
|
3023
|
+
this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
|
|
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 = {};
|
|
@@ -2911,6 +3089,7 @@ __export(exports_index_node, {
|
|
|
2911
3089
|
KnowledgeBase: () => KnowledgeBase,
|
|
2912
3090
|
InvitationsModule: () => InvitationsModule,
|
|
2913
3091
|
HistoryManager: () => HistoryManager,
|
|
3092
|
+
GitWorkflow: () => GitWorkflow,
|
|
2914
3093
|
ExecSession: () => ExecSession,
|
|
2915
3094
|
ExecEventType: () => ExecEventType,
|
|
2916
3095
|
ExecEventEmitter: () => ExecEventEmitter,
|
|
@@ -3238,8 +3417,10 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
3238
3417
|
}
|
|
3239
3418
|
}
|
|
3240
3419
|
// src/agent/document-fetcher.ts
|
|
3420
|
+
init_config();
|
|
3241
3421
|
var import_node_fs7 = require("node:fs");
|
|
3242
3422
|
var import_node_path9 = require("node:path");
|
|
3423
|
+
|
|
3243
3424
|
class DocumentFetcher {
|
|
3244
3425
|
deps;
|
|
3245
3426
|
constructor(deps) {
|
|
@@ -3281,6 +3462,10 @@ class DocumentFetcher {
|
|
|
3281
3462
|
}
|
|
3282
3463
|
}
|
|
3283
3464
|
}
|
|
3465
|
+
|
|
3466
|
+
// src/agent/index.ts
|
|
3467
|
+
init_git_workflow();
|
|
3468
|
+
|
|
3284
3469
|
// src/agent/review-service.ts
|
|
3285
3470
|
var import_node_child_process6 = require("node:child_process");
|
|
3286
3471
|
|
|
@@ -3340,6 +3525,13 @@ Keep the review concise but thorough. Focus on substance over style.`;
|
|
|
3340
3525
|
}
|
|
3341
3526
|
}
|
|
3342
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();
|
|
3343
3535
|
function resolveProvider2(value) {
|
|
3344
3536
|
if (!value || value.startsWith("--"))
|
|
3345
3537
|
return PROVIDER.CLAUDE;
|
|
@@ -3498,9 +3690,12 @@ ${summary}`;
|
|
|
3498
3690
|
const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
|
|
3499
3691
|
try {
|
|
3500
3692
|
this.knowledgeBase.updateProgress({
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3693
|
+
role: "user",
|
|
3694
|
+
content: `Review PR #${pr.number}: ${pr.title}`
|
|
3695
|
+
});
|
|
3696
|
+
this.knowledgeBase.updateProgress({
|
|
3697
|
+
role: "assistant",
|
|
3698
|
+
content: `${status}: ${result.summary}`
|
|
3504
3699
|
});
|
|
3505
3700
|
} catch {}
|
|
3506
3701
|
this.reviewsCompleted++;
|
|
@@ -3552,6 +3747,21 @@ if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "revie
|
|
|
3552
3747
|
process.exit(1);
|
|
3553
3748
|
});
|
|
3554
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
|
+
|
|
3555
3765
|
// src/exec/context-tracker.ts
|
|
3556
3766
|
var REFERENCE_ALIASES = {
|
|
3557
3767
|
plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
|
|
@@ -3965,6 +4175,7 @@ class ExecEventEmitter {
|
|
|
3965
4175
|
}
|
|
3966
4176
|
}
|
|
3967
4177
|
// src/exec/history-manager.ts
|
|
4178
|
+
init_config();
|
|
3968
4179
|
var import_node_fs8 = require("node:fs");
|
|
3969
4180
|
var import_node_path10 = require("node:path");
|
|
3970
4181
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
@@ -4342,62 +4553,474 @@ ${currentPrompt}`);
|
|
|
4342
4553
|
|
|
4343
4554
|
`);
|
|
4344
4555
|
}
|
|
4345
|
-
save() {
|
|
4346
|
-
if (!this.currentSession) {
|
|
4347
|
-
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;
|
|
4348
4949
|
}
|
|
4349
|
-
|
|
4350
|
-
|
|
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;
|
|
4351
4986
|
}
|
|
4352
|
-
this.history.saveSession(this.currentSession);
|
|
4353
|
-
this.history.pruneSessions();
|
|
4354
4987
|
}
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
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 [];
|
|
4358
5004
|
}
|
|
4359
|
-
this.currentSession.messages = [];
|
|
4360
|
-
this.currentSession.updatedAt = Date.now();
|
|
4361
|
-
this.contextTracker.clear();
|
|
4362
5005
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
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"]
|
|
4369
5011
|
});
|
|
4370
5012
|
}
|
|
4371
|
-
end(success = true) {
|
|
4372
|
-
this.eventEmitter.emitSessionEnded(success);
|
|
4373
|
-
}
|
|
4374
|
-
on(eventType, listener) {
|
|
4375
|
-
this.eventEmitter.on(eventType, listener);
|
|
4376
|
-
return this;
|
|
4377
|
-
}
|
|
4378
|
-
off(eventType, listener) {
|
|
4379
|
-
this.eventEmitter.off(eventType, listener);
|
|
4380
|
-
return this;
|
|
4381
|
-
}
|
|
4382
5013
|
}
|
|
4383
|
-
// src/orchestrator.ts
|
|
4384
|
-
var import_node_child_process7 = require("node:child_process");
|
|
4385
|
-
var import_node_fs9 = require("node:fs");
|
|
4386
|
-
var import_node_path11 = require("node:path");
|
|
4387
|
-
var import_node_url = require("node:url");
|
|
4388
|
-
var import_shared4 = require("@locusai/shared");
|
|
4389
|
-
var import_events4 = require("events");
|
|
4390
|
-
var MAX_AGENTS = 5;
|
|
4391
5014
|
|
|
4392
|
-
|
|
5015
|
+
// src/orchestrator/index.ts
|
|
5016
|
+
class AgentOrchestrator extends import_events5.EventEmitter {
|
|
4393
5017
|
client;
|
|
4394
5018
|
config;
|
|
4395
|
-
|
|
5019
|
+
pool;
|
|
4396
5020
|
isRunning = false;
|
|
4397
5021
|
processedTasks = new Set;
|
|
4398
5022
|
resolvedSprintId = null;
|
|
4399
5023
|
worktreeManager = null;
|
|
4400
|
-
heartbeatInterval = null;
|
|
4401
5024
|
constructor(config) {
|
|
4402
5025
|
super();
|
|
4403
5026
|
this.config = config;
|
|
@@ -4405,9 +5028,10 @@ class AgentOrchestrator extends import_events4.EventEmitter {
|
|
|
4405
5028
|
baseUrl: config.apiBase,
|
|
4406
5029
|
token: config.apiKey
|
|
4407
5030
|
});
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
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));
|
|
4411
5035
|
}
|
|
4412
5036
|
get useWorktrees() {
|
|
4413
5037
|
return this.config.useWorktrees ?? true;
|
|
@@ -4451,6 +5075,28 @@ class AgentOrchestrator extends import_events4.EventEmitter {
|
|
|
4451
5075
|
config: this.config,
|
|
4452
5076
|
sprintId: this.resolvedSprintId
|
|
4453
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() {
|
|
4454
5100
|
console.log(`
|
|
4455
5101
|
${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
4456
5102
|
console.log(c.dim("----------------------------------------------"));
|
|
@@ -4458,7 +5104,7 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
|
4458
5104
|
if (this.resolvedSprintId) {
|
|
4459
5105
|
console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
|
|
4460
5106
|
}
|
|
4461
|
-
console.log(`${c.bold("Agents:")} ${this.
|
|
5107
|
+
console.log(`${c.bold("Agents:")} ${this.pool.effectiveAgentCount}`);
|
|
4462
5108
|
console.log(`${c.bold("Worktrees:")} ${this.useWorktrees ? "enabled" : "disabled"}`);
|
|
4463
5109
|
if (this.useWorktrees) {
|
|
4464
5110
|
console.log(`${c.bold("Cleanup policy:")} ${this.worktreeCleanupPolicy}`);
|
|
@@ -4467,154 +5113,16 @@ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
|
|
|
4467
5113
|
console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
|
|
4468
5114
|
console.log(c.dim(`----------------------------------------------
|
|
4469
5115
|
`));
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
return;
|
|
4474
|
-
}
|
|
4475
|
-
if (tasks2.length > 0 && this.useWorktrees && !isGitAvailable()) {
|
|
5116
|
+
}
|
|
5117
|
+
preflightChecks(_tasks) {
|
|
5118
|
+
if (this.useWorktrees && !isGitAvailable()) {
|
|
4476
5119
|
console.log(c.error("git is not installed. Worktree isolation requires git. Install from https://git-scm.com/"));
|
|
4477
|
-
return;
|
|
5120
|
+
return false;
|
|
4478
5121
|
}
|
|
4479
|
-
if (
|
|
5122
|
+
if (this.config.autoPush && !isGhAvailable(this.config.projectPath)) {
|
|
4480
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/"));
|
|
4481
5124
|
}
|
|
4482
|
-
|
|
4483
|
-
this.worktreeManager = new WorktreeManager(this.config.projectPath, {
|
|
4484
|
-
cleanupPolicy: this.worktreeCleanupPolicy
|
|
4485
|
-
});
|
|
4486
|
-
}
|
|
4487
|
-
this.startHeartbeatMonitor();
|
|
4488
|
-
const agentsToSpawn = Math.min(this.agentCount, tasks2.length);
|
|
4489
|
-
const SPAWN_DELAY_MS = 5000;
|
|
4490
|
-
const spawnPromises = [];
|
|
4491
|
-
for (let i = 0;i < agentsToSpawn; i++) {
|
|
4492
|
-
if (i > 0) {
|
|
4493
|
-
await this.sleep(SPAWN_DELAY_MS);
|
|
4494
|
-
}
|
|
4495
|
-
spawnPromises.push(this.spawnAgent(i));
|
|
4496
|
-
}
|
|
4497
|
-
await Promise.all(spawnPromises);
|
|
4498
|
-
while (this.agents.size > 0 && this.isRunning) {
|
|
4499
|
-
if (this.agents.size === 0) {
|
|
4500
|
-
break;
|
|
4501
|
-
}
|
|
4502
|
-
await this.sleep(2000);
|
|
4503
|
-
}
|
|
4504
|
-
console.log(`
|
|
4505
|
-
${c.success("✅ Orchestrator finished")}`);
|
|
4506
|
-
}
|
|
4507
|
-
async spawnAgent(index) {
|
|
4508
|
-
const agentId = `agent-${index}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
4509
|
-
const agentState = {
|
|
4510
|
-
id: agentId,
|
|
4511
|
-
status: "IDLE",
|
|
4512
|
-
currentTaskId: null,
|
|
4513
|
-
tasksCompleted: 0,
|
|
4514
|
-
tasksFailed: 0,
|
|
4515
|
-
lastHeartbeat: new Date
|
|
4516
|
-
};
|
|
4517
|
-
this.agents.set(agentId, agentState);
|
|
4518
|
-
console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
|
|
4519
|
-
`);
|
|
4520
|
-
const workerPath = this.resolveWorkerPath();
|
|
4521
|
-
if (!workerPath) {
|
|
4522
|
-
throw new Error("Worker file not found. Make sure the SDK is properly built and installed.");
|
|
4523
|
-
}
|
|
4524
|
-
const workerArgs = [
|
|
4525
|
-
"--agent-id",
|
|
4526
|
-
agentId,
|
|
4527
|
-
"--workspace-id",
|
|
4528
|
-
this.config.workspaceId,
|
|
4529
|
-
"--api-url",
|
|
4530
|
-
this.config.apiBase,
|
|
4531
|
-
"--api-key",
|
|
4532
|
-
this.config.apiKey,
|
|
4533
|
-
"--project-path",
|
|
4534
|
-
this.config.projectPath
|
|
4535
|
-
];
|
|
4536
|
-
if (this.config.model) {
|
|
4537
|
-
workerArgs.push("--model", this.config.model);
|
|
4538
|
-
}
|
|
4539
|
-
if (this.config.provider) {
|
|
4540
|
-
workerArgs.push("--provider", this.config.provider);
|
|
4541
|
-
}
|
|
4542
|
-
if (this.resolvedSprintId) {
|
|
4543
|
-
workerArgs.push("--sprint-id", this.resolvedSprintId);
|
|
4544
|
-
}
|
|
4545
|
-
if (this.useWorktrees) {
|
|
4546
|
-
workerArgs.push("--use-worktrees");
|
|
4547
|
-
}
|
|
4548
|
-
if (this.config.autoPush) {
|
|
4549
|
-
workerArgs.push("--auto-push");
|
|
4550
|
-
}
|
|
4551
|
-
const agentProcess = import_node_child_process7.spawn(process.execPath, [workerPath, ...workerArgs], {
|
|
4552
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
4553
|
-
detached: true,
|
|
4554
|
-
env: {
|
|
4555
|
-
...process.env,
|
|
4556
|
-
FORCE_COLOR: "1",
|
|
4557
|
-
TERM: "xterm-256color",
|
|
4558
|
-
LOCUS_WORKER: agentId,
|
|
4559
|
-
LOCUS_WORKSPACE: this.config.workspaceId
|
|
4560
|
-
}
|
|
4561
|
-
});
|
|
4562
|
-
agentState.process = agentProcess;
|
|
4563
|
-
agentProcess.on("message", (msg) => {
|
|
4564
|
-
if (msg.type === "stats") {
|
|
4565
|
-
agentState.tasksCompleted = msg.tasksCompleted || 0;
|
|
4566
|
-
agentState.tasksFailed = msg.tasksFailed || 0;
|
|
4567
|
-
}
|
|
4568
|
-
if (msg.type === "heartbeat") {
|
|
4569
|
-
agentState.lastHeartbeat = new Date;
|
|
4570
|
-
}
|
|
4571
|
-
});
|
|
4572
|
-
agentProcess.stdout?.on("data", (data) => {
|
|
4573
|
-
process.stdout.write(data.toString());
|
|
4574
|
-
});
|
|
4575
|
-
agentProcess.stderr?.on("data", (data) => {
|
|
4576
|
-
process.stderr.write(data.toString());
|
|
4577
|
-
});
|
|
4578
|
-
agentProcess.on("exit", (code) => {
|
|
4579
|
-
console.log(`
|
|
4580
|
-
${agentId} finished (exit code: ${code})`);
|
|
4581
|
-
const agent = this.agents.get(agentId);
|
|
4582
|
-
if (agent) {
|
|
4583
|
-
agent.status = code === 0 ? "COMPLETED" : "FAILED";
|
|
4584
|
-
this.emit("agent:completed", {
|
|
4585
|
-
agentId,
|
|
4586
|
-
status: agent.status,
|
|
4587
|
-
tasksCompleted: agent.tasksCompleted,
|
|
4588
|
-
tasksFailed: agent.tasksFailed
|
|
4589
|
-
});
|
|
4590
|
-
this.agents.delete(agentId);
|
|
4591
|
-
}
|
|
4592
|
-
});
|
|
4593
|
-
this.emit("agent:spawned", { agentId });
|
|
4594
|
-
}
|
|
4595
|
-
resolveWorkerPath() {
|
|
4596
|
-
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator.ts");
|
|
4597
|
-
const currentModuleDir = import_node_path11.dirname(currentModulePath);
|
|
4598
|
-
const potentialPaths = [
|
|
4599
|
-
import_node_path11.join(currentModuleDir, "agent", "worker.js"),
|
|
4600
|
-
import_node_path11.join(currentModuleDir, "worker.js"),
|
|
4601
|
-
import_node_path11.join(currentModuleDir, "agent", "worker.ts")
|
|
4602
|
-
];
|
|
4603
|
-
return potentialPaths.find((p) => import_node_fs9.existsSync(p));
|
|
4604
|
-
}
|
|
4605
|
-
startHeartbeatMonitor() {
|
|
4606
|
-
this.heartbeatInterval = setInterval(() => {
|
|
4607
|
-
const now = Date.now();
|
|
4608
|
-
for (const [agentId, agent] of this.agents.entries()) {
|
|
4609
|
-
if (agent.status === "WORKING" && now - agent.lastHeartbeat.getTime() > import_shared4.STALE_AGENT_TIMEOUT_MS) {
|
|
4610
|
-
console.log(c.error(`Agent ${agentId} is stale (no heartbeat for 10 minutes). Killing.`));
|
|
4611
|
-
if (agent.process && !agent.process.killed) {
|
|
4612
|
-
this.killProcessTree(agent.process);
|
|
4613
|
-
}
|
|
4614
|
-
this.emit("agent:stale", { agentId });
|
|
4615
|
-
}
|
|
4616
|
-
}
|
|
4617
|
-
}, 60000);
|
|
5125
|
+
return true;
|
|
4618
5126
|
}
|
|
4619
5127
|
async getAvailableTasks() {
|
|
4620
5128
|
try {
|
|
@@ -4626,16 +5134,16 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4626
5134
|
}
|
|
4627
5135
|
}
|
|
4628
5136
|
async assignTaskToAgent(agentId) {
|
|
4629
|
-
const agent = this.
|
|
5137
|
+
const agent = this.pool.get(agentId);
|
|
4630
5138
|
if (!agent)
|
|
4631
5139
|
return null;
|
|
4632
5140
|
try {
|
|
4633
5141
|
const tasks2 = await this.getAvailableTasks();
|
|
4634
5142
|
const priorityOrder = [
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
5143
|
+
import_shared6.TaskPriority.CRITICAL,
|
|
5144
|
+
import_shared6.TaskPriority.HIGH,
|
|
5145
|
+
import_shared6.TaskPriority.MEDIUM,
|
|
5146
|
+
import_shared6.TaskPriority.LOW
|
|
4639
5147
|
];
|
|
4640
5148
|
let task = tasks2.sort((a, b) => priorityOrder.indexOf(a.priority) - priorityOrder.indexOf(b.priority))[0];
|
|
4641
5149
|
if (!task && tasks2.length > 0) {
|
|
@@ -4659,7 +5167,7 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4659
5167
|
async completeTask(taskId, agentId, summary) {
|
|
4660
5168
|
try {
|
|
4661
5169
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
4662
|
-
status:
|
|
5170
|
+
status: import_shared6.TaskStatus.IN_REVIEW
|
|
4663
5171
|
});
|
|
4664
5172
|
if (summary) {
|
|
4665
5173
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
@@ -4670,7 +5178,7 @@ ${summary}`
|
|
|
4670
5178
|
});
|
|
4671
5179
|
}
|
|
4672
5180
|
this.processedTasks.add(taskId);
|
|
4673
|
-
const agent = this.
|
|
5181
|
+
const agent = this.pool.get(agentId);
|
|
4674
5182
|
if (agent) {
|
|
4675
5183
|
agent.tasksCompleted += 1;
|
|
4676
5184
|
agent.currentTaskId = null;
|
|
@@ -4684,14 +5192,14 @@ ${summary}`
|
|
|
4684
5192
|
async failTask(taskId, agentId, error) {
|
|
4685
5193
|
try {
|
|
4686
5194
|
await this.client.tasks.update(taskId, this.config.workspaceId, {
|
|
4687
|
-
status:
|
|
5195
|
+
status: import_shared6.TaskStatus.BACKLOG,
|
|
4688
5196
|
assignedTo: null
|
|
4689
5197
|
});
|
|
4690
5198
|
await this.client.tasks.addComment(taskId, this.config.workspaceId, {
|
|
4691
5199
|
author: agentId,
|
|
4692
5200
|
text: `❌ Agent failed: ${error}`
|
|
4693
5201
|
});
|
|
4694
|
-
const agent = this.
|
|
5202
|
+
const agent = this.pool.get(agentId);
|
|
4695
5203
|
if (agent) {
|
|
4696
5204
|
agent.tasksFailed += 1;
|
|
4697
5205
|
agent.currentTaskId = null;
|
|
@@ -4708,36 +5216,10 @@ ${summary}`
|
|
|
4708
5216
|
this.emit("stopped", { timestamp: new Date });
|
|
4709
5217
|
}
|
|
4710
5218
|
stopAgent(agentId) {
|
|
4711
|
-
|
|
4712
|
-
if (!agent)
|
|
4713
|
-
return false;
|
|
4714
|
-
if (agent.process && !agent.process.killed) {
|
|
4715
|
-
this.killProcessTree(agent.process);
|
|
4716
|
-
}
|
|
4717
|
-
return true;
|
|
4718
|
-
}
|
|
4719
|
-
killProcessTree(proc) {
|
|
4720
|
-
if (!proc.pid || proc.killed)
|
|
4721
|
-
return;
|
|
4722
|
-
try {
|
|
4723
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
4724
|
-
} catch {
|
|
4725
|
-
try {
|
|
4726
|
-
proc.kill("SIGTERM");
|
|
4727
|
-
} catch {}
|
|
4728
|
-
}
|
|
5219
|
+
return this.pool.stopAgent(agentId);
|
|
4729
5220
|
}
|
|
4730
5221
|
async cleanup() {
|
|
4731
|
-
|
|
4732
|
-
clearInterval(this.heartbeatInterval);
|
|
4733
|
-
this.heartbeatInterval = null;
|
|
4734
|
-
}
|
|
4735
|
-
for (const [agentId, agent] of this.agents.entries()) {
|
|
4736
|
-
if (agent.process && !agent.process.killed) {
|
|
4737
|
-
console.log(`Killing agent: ${agentId}`);
|
|
4738
|
-
this.killProcessTree(agent.process);
|
|
4739
|
-
}
|
|
4740
|
-
}
|
|
5222
|
+
this.pool.shutdown();
|
|
4741
5223
|
if (this.worktreeManager) {
|
|
4742
5224
|
try {
|
|
4743
5225
|
if (this.worktreeCleanupPolicy === "auto") {
|
|
@@ -4755,31 +5237,27 @@ ${summary}`
|
|
|
4755
5237
|
console.log(c.dim("Could not clean up some worktrees"));
|
|
4756
5238
|
}
|
|
4757
5239
|
}
|
|
4758
|
-
this.agents.clear();
|
|
4759
5240
|
}
|
|
4760
5241
|
getStats() {
|
|
5242
|
+
const poolStats = this.pool.getStats();
|
|
4761
5243
|
return {
|
|
4762
|
-
|
|
4763
|
-
agentCount: this.agentCount,
|
|
5244
|
+
...poolStats,
|
|
4764
5245
|
useWorktrees: this.useWorktrees,
|
|
4765
|
-
processedTasks: this.processedTasks.size
|
|
4766
|
-
totalTasksCompleted: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksCompleted, 0),
|
|
4767
|
-
totalTasksFailed: Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.tasksFailed, 0)
|
|
5246
|
+
processedTasks: this.processedTasks.size
|
|
4768
5247
|
};
|
|
4769
5248
|
}
|
|
4770
5249
|
getAgentStates() {
|
|
4771
|
-
return
|
|
4772
|
-
}
|
|
4773
|
-
sleep(ms) {
|
|
4774
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5250
|
+
return this.pool.getAll();
|
|
4775
5251
|
}
|
|
4776
5252
|
}
|
|
4777
5253
|
// src/planning/plan-manager.ts
|
|
5254
|
+
init_config();
|
|
5255
|
+
init_knowledge_base();
|
|
4778
5256
|
var import_node_fs10 = require("node:fs");
|
|
4779
5257
|
var import_node_path12 = require("node:path");
|
|
4780
5258
|
|
|
4781
5259
|
// src/planning/sprint-plan.ts
|
|
4782
|
-
var
|
|
5260
|
+
var import_shared7 = require("@locusai/shared");
|
|
4783
5261
|
function sprintPlanToMarkdown(plan) {
|
|
4784
5262
|
const lines = [];
|
|
4785
5263
|
lines.push(`# Sprint Plan: ${plan.name}`);
|
|
@@ -4801,23 +5279,32 @@ function sprintPlanToMarkdown(plan) {
|
|
|
4801
5279
|
}
|
|
4802
5280
|
lines.push(`## Tasks (${plan.tasks.length})`);
|
|
4803
5281
|
lines.push("");
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
}
|
|
4812
|
-
lines.push("");
|
|
4813
|
-
lines.push(task.description);
|
|
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._`);
|
|
4814
5289
|
lines.push("");
|
|
4815
|
-
|
|
4816
|
-
lines.push(
|
|
4817
|
-
|
|
4818
|
-
|
|
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(", ")}`);
|
|
4819
5297
|
}
|
|
4820
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
|
+
}
|
|
4821
5308
|
}
|
|
4822
5309
|
}
|
|
4823
5310
|
if (plan.risks.length > 0) {
|
|
@@ -4838,12 +5325,13 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
|
|
|
4838
5325
|
return plan.tasks.map((task) => ({
|
|
4839
5326
|
title: task.title,
|
|
4840
5327
|
description: task.description,
|
|
4841
|
-
status:
|
|
5328
|
+
status: import_shared7.TaskStatus.BACKLOG,
|
|
4842
5329
|
assigneeRole: task.assigneeRole,
|
|
4843
5330
|
priority: task.priority,
|
|
4844
5331
|
labels: task.labels,
|
|
4845
5332
|
sprintId,
|
|
4846
5333
|
order: task.index * 10,
|
|
5334
|
+
tier: task.tier,
|
|
4847
5335
|
acceptanceChecklist: task.acceptanceCriteria.map((text, i) => ({
|
|
4848
5336
|
id: `ac-${i + 1}`,
|
|
4849
5337
|
text,
|
|
@@ -4867,7 +5355,8 @@ function parseSprintPlanFromAI(raw, directive) {
|
|
|
4867
5355
|
priority: t.priority || "MEDIUM",
|
|
4868
5356
|
complexity: t.complexity || 3,
|
|
4869
5357
|
acceptanceCriteria: t.acceptanceCriteria || [],
|
|
4870
|
-
labels: t.labels || []
|
|
5358
|
+
labels: t.labels || [],
|
|
5359
|
+
tier: typeof t.tier === "number" ? t.tier : 0
|
|
4871
5360
|
}));
|
|
4872
5361
|
return {
|
|
4873
5362
|
id,
|
|
@@ -4956,9 +5445,12 @@ class PlanManager {
|
|
|
4956
5445
|
this.save(plan);
|
|
4957
5446
|
const kb = new KnowledgeBase(this.projectPath);
|
|
4958
5447
|
kb.updateProgress({
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
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}`
|
|
4962
5454
|
});
|
|
4963
5455
|
return { sprint, tasks: tasks2 };
|
|
4964
5456
|
}
|
|
@@ -5021,6 +5513,8 @@ class PlanManager {
|
|
|
5021
5513
|
}
|
|
5022
5514
|
}
|
|
5023
5515
|
// src/planning/planning-meeting.ts
|
|
5516
|
+
init_config();
|
|
5517
|
+
init_knowledge_base();
|
|
5024
5518
|
var import_node_fs11 = require("node:fs");
|
|
5025
5519
|
|
|
5026
5520
|
// src/planning/agents/architect.ts
|
|
@@ -5109,13 +5603,16 @@ You are a combined Architect, Senior Engineer, and Sprint Planner performing a F
|
|
|
5109
5603
|
|
|
5110
5604
|
## Context
|
|
5111
5605
|
|
|
5112
|
-
In this system,
|
|
5113
|
-
-
|
|
5114
|
-
-
|
|
5115
|
-
-
|
|
5116
|
-
-
|
|
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
|
|
5117
5611
|
|
|
5118
|
-
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
|
|
5119
5616
|
|
|
5120
5617
|
## CEO Directive
|
|
5121
5618
|
> ${input.directive}
|
|
@@ -5139,29 +5636,37 @@ ${input.sprintOrganizerOutput}
|
|
|
5139
5636
|
|
|
5140
5637
|
Go through EACH pair of tasks and check for:
|
|
5141
5638
|
|
|
5142
|
-
### 1. File Overlap Analysis
|
|
5639
|
+
### 1. File Overlap Analysis (WITHIN the same tier)
|
|
5143
5640
|
For each task, list the files it will likely modify. Then check:
|
|
5144
|
-
- Do any two tasks modify the same file? (e.g., app.module.ts, configuration.ts, package.json, shared DTOs)
|
|
5145
|
-
- 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
|
|
5146
5644
|
|
|
5147
|
-
### 2. Duplicated Work Detection
|
|
5148
|
-
Check if multiple tasks
|
|
5645
|
+
### 2. Duplicated Work Detection (WITHIN the same tier)
|
|
5646
|
+
Check if multiple tasks **in the same tier**:
|
|
5149
5647
|
- Add the same environment variable or config field
|
|
5150
5648
|
- Install or configure the same dependency
|
|
5151
5649
|
- Register the same module or provider
|
|
5152
5650
|
- Create the same helper function, guard, interceptor, or middleware
|
|
5153
5651
|
- Add the same import to a shared file
|
|
5154
|
-
If yes: consolidate into ONE task
|
|
5652
|
+
If yes: consolidate into ONE task or move the shared work to a lower tier
|
|
5155
5653
|
|
|
5156
5654
|
### 3. Self-Containment Validation
|
|
5157
5655
|
For each task, verify:
|
|
5158
5656
|
- Does it include ALL config/env changes it needs?
|
|
5159
5657
|
- Does it include ALL module registrations it needs?
|
|
5160
5658
|
- Does it include ALL dependency installations it needs?
|
|
5161
|
-
- 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
|
|
5162
5667
|
|
|
5163
|
-
###
|
|
5164
|
-
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.
|
|
5165
5670
|
|
|
5166
5671
|
## Output Format
|
|
5167
5672
|
|
|
@@ -5171,10 +5676,10 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
5171
5676
|
"hasIssues": true | false,
|
|
5172
5677
|
"issues": [
|
|
5173
5678
|
{
|
|
5174
|
-
"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",
|
|
5175
5680
|
"description": "string describing the specific issue",
|
|
5176
5681
|
"affectedTasks": ["Task Title 1", "Task Title 2"],
|
|
5177
|
-
"resolution": "string describing how to fix it (merge, move, consolidate)"
|
|
5682
|
+
"resolution": "string describing how to fix it (merge, move to different tier, consolidate)"
|
|
5178
5683
|
}
|
|
5179
5684
|
],
|
|
5180
5685
|
"revisedPlan": {
|
|
@@ -5189,7 +5694,8 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
5189
5694
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5190
5695
|
"labels": ["string"],
|
|
5191
5696
|
"acceptanceCriteria": ["string"],
|
|
5192
|
-
"complexity": 3
|
|
5697
|
+
"complexity": 3,
|
|
5698
|
+
"tier": 0
|
|
5193
5699
|
}
|
|
5194
5700
|
],
|
|
5195
5701
|
"risks": [
|
|
@@ -5203,11 +5709,14 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
5203
5709
|
}
|
|
5204
5710
|
|
|
5205
5711
|
IMPORTANT:
|
|
5206
|
-
- 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.)
|
|
5207
5713
|
- If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
|
|
5208
5714
|
- The revisedPlan is ALWAYS required — it becomes the final plan
|
|
5209
5715
|
- When merging tasks, combine their acceptance criteria and update descriptions to cover all consolidated work
|
|
5210
|
-
- 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`;
|
|
5211
5720
|
return prompt;
|
|
5212
5721
|
}
|
|
5213
5722
|
|
|
@@ -5239,13 +5748,16 @@ Produce the final sprint plan:
|
|
|
5239
5748
|
1. **Sprint Name** — A concise, memorable name for this sprint (e.g., "User Authentication", "Payment Integration")
|
|
5240
5749
|
2. **Sprint Goal** — One paragraph describing what this sprint delivers
|
|
5241
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.
|
|
5242
|
-
4. **
|
|
5243
|
-
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
|
|
5244
5754
|
|
|
5245
5755
|
Guidelines:
|
|
5246
5756
|
- The order of tasks in the array determines execution order. Tasks are dispatched sequentially from first to last.
|
|
5247
|
-
- Foundation tasks (schemas, config, shared code) must appear before tasks that build on them
|
|
5248
|
-
-
|
|
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
|
|
5249
5761
|
- Ensure acceptance criteria are specific and testable
|
|
5250
5762
|
- Keep the sprint focused — if it's too large (>12 tasks), consider reducing scope
|
|
5251
5763
|
|
|
@@ -5255,7 +5767,7 @@ Before finalizing, validate that EVERY task is fully self-contained and conflict
|
|
|
5255
5767
|
|
|
5256
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.
|
|
5257
5769
|
2. **No duplicated work.** Each env var, config field, dependency, module import, or helper function must be introduced by exactly ONE task.
|
|
5258
|
-
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.
|
|
5259
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.
|
|
5260
5772
|
|
|
5261
5773
|
## Output Format
|
|
@@ -5274,7 +5786,8 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
5274
5786
|
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5275
5787
|
"labels": ["string"],
|
|
5276
5788
|
"acceptanceCriteria": ["string"],
|
|
5277
|
-
"complexity": 3
|
|
5789
|
+
"complexity": 3,
|
|
5790
|
+
"tier": 0
|
|
5278
5791
|
}
|
|
5279
5792
|
],
|
|
5280
5793
|
"risks": [
|
|
@@ -5284,7 +5797,14 @@ Your entire response must be a single JSON object — no text before it, no text
|
|
|
5284
5797
|
"severity": "low | medium | high"
|
|
5285
5798
|
}
|
|
5286
5799
|
]
|
|
5287
|
-
}
|
|
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)`;
|
|
5288
5808
|
return prompt;
|
|
5289
5809
|
}
|
|
5290
5810
|
|
|
@@ -5453,3 +5973,10 @@ class PlanningMeeting {
|
|
|
5453
5973
|
}
|
|
5454
5974
|
}
|
|
5455
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();
|