@rallycry/conveyor-agent 4.2.2 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NUD2M24L.js → chunk-FS3A4THO.js} +1680 -1660
- package/dist/chunk-FS3A4THO.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +8 -20
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-NUD2M24L.js.map +0 -1
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
// src/connection/task-connection.ts
|
|
2
2
|
import { io } from "socket.io-client";
|
|
3
|
+
|
|
4
|
+
// src/connection/task-connection-rpc.ts
|
|
5
|
+
function requireSocket(socket) {
|
|
6
|
+
if (!socket) throw new Error("Not connected");
|
|
7
|
+
return socket;
|
|
8
|
+
}
|
|
9
|
+
function emitRpc(socket, event, data) {
|
|
10
|
+
return new Promise((resolve2, reject) => {
|
|
11
|
+
socket.emit(event, data, (response) => {
|
|
12
|
+
if (response.success && response.data) resolve2(response.data);
|
|
13
|
+
else reject(new Error(response.error ?? `Failed: ${event}`));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function emitRpcVoid(socket, event, data) {
|
|
18
|
+
return new Promise((resolve2, reject) => {
|
|
19
|
+
socket.emit(event, data, (response) => {
|
|
20
|
+
if (response.success) resolve2();
|
|
21
|
+
else reject(new Error(response.error ?? `Failed: ${event}`));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function fetchChatMessages(socket, limit, taskId) {
|
|
26
|
+
return emitRpc(requireSocket(socket), "agentRunner:getChatMessages", { limit, taskId });
|
|
27
|
+
}
|
|
28
|
+
function fetchTaskFiles(socket) {
|
|
29
|
+
return emitRpc(requireSocket(socket), "agentRunner:getTaskFiles", {});
|
|
30
|
+
}
|
|
31
|
+
function fetchTaskFile(socket, fileId) {
|
|
32
|
+
return emitRpc(requireSocket(socket), "agentRunner:getTaskFile", { fileId });
|
|
33
|
+
}
|
|
34
|
+
function fetchTaskContext(socket) {
|
|
35
|
+
return emitRpc(requireSocket(socket), "agentRunner:getTaskContext", {});
|
|
36
|
+
}
|
|
37
|
+
function createPR(socket, params) {
|
|
38
|
+
return emitRpc(requireSocket(socket), "agentRunner:createPR", params);
|
|
39
|
+
}
|
|
40
|
+
function createSubtask(socket, data) {
|
|
41
|
+
return emitRpc(requireSocket(socket), "agentRunner:createSubtask", data);
|
|
42
|
+
}
|
|
43
|
+
function listSubtasks(socket) {
|
|
44
|
+
return emitRpc(requireSocket(socket), "agentRunner:listSubtasks", {});
|
|
45
|
+
}
|
|
46
|
+
function fetchCliHistory(socket, taskId, limit, source) {
|
|
47
|
+
const s = requireSocket(socket);
|
|
48
|
+
const timeout = 1e4;
|
|
49
|
+
return Promise.race([
|
|
50
|
+
emitRpc(s, "agentRunner:getCliHistory", {
|
|
51
|
+
taskId,
|
|
52
|
+
limit,
|
|
53
|
+
source
|
|
54
|
+
}),
|
|
55
|
+
new Promise((_, reject) => {
|
|
56
|
+
setTimeout(() => reject(new Error("CLI history request timed out")), timeout);
|
|
57
|
+
})
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
function startChildCloudBuild(socket, childTaskId) {
|
|
61
|
+
return emitRpc(requireSocket(socket), "agentRunner:startChildCloudBuild", { childTaskId });
|
|
62
|
+
}
|
|
63
|
+
function approveAndMergePR(socket, childTaskId) {
|
|
64
|
+
return emitRpc(requireSocket(socket), "agentRunner:approveAndMergePR", { childTaskId });
|
|
65
|
+
}
|
|
66
|
+
function postChildChatMessage(socket, childTaskId, content) {
|
|
67
|
+
return emitRpcVoid(requireSocket(socket), "agentRunner:postChildChatMessage", {
|
|
68
|
+
childTaskId,
|
|
69
|
+
content
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function updateChildStatus(socket, childTaskId, status) {
|
|
73
|
+
return emitRpcVoid(requireSocket(socket), "agentRunner:updateChildStatus", {
|
|
74
|
+
childTaskId,
|
|
75
|
+
status
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function stopChildBuild(socket, childTaskId) {
|
|
79
|
+
return emitRpcVoid(requireSocket(socket), "agentRunner:stopChildBuild", { childTaskId });
|
|
80
|
+
}
|
|
81
|
+
function fetchTask(socket, slugOrId) {
|
|
82
|
+
return emitRpc(requireSocket(socket), "agentRunner:getTask", { slugOrId });
|
|
83
|
+
}
|
|
84
|
+
function listIcons(socket) {
|
|
85
|
+
return emitRpc(requireSocket(socket), "agentRunner:listIcons", {});
|
|
86
|
+
}
|
|
87
|
+
function generateTaskIcon(socket, prompt, aspectRatio) {
|
|
88
|
+
return emitRpc(requireSocket(socket), "agentRunner:generateTaskIcon", { prompt, aspectRatio });
|
|
89
|
+
}
|
|
90
|
+
function getTaskProperties(socket) {
|
|
91
|
+
return emitRpc(requireSocket(socket), "agentRunner:getTaskProperties", {});
|
|
92
|
+
}
|
|
93
|
+
function triggerIdentification(socket) {
|
|
94
|
+
return emitRpc(requireSocket(socket), "agentRunner:triggerIdentification", {});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/connection/task-connection.ts
|
|
3
98
|
var ConveyorConnection = class _ConveyorConnection {
|
|
4
99
|
socket = null;
|
|
5
100
|
config;
|
|
@@ -30,23 +125,15 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
30
125
|
reconnectionDelay: 2e3,
|
|
31
126
|
reconnectionDelayMax: 3e4,
|
|
32
127
|
randomizationFactor: 0.3,
|
|
33
|
-
extraHeaders: {
|
|
34
|
-
"ngrok-skip-browser-warning": "true"
|
|
35
|
-
}
|
|
128
|
+
extraHeaders: { "ngrok-skip-browser-warning": "true" }
|
|
36
129
|
});
|
|
37
130
|
this.socket.on("agentRunner:incomingMessage", (msg) => {
|
|
38
|
-
if (this.chatMessageCallback)
|
|
39
|
-
|
|
40
|
-
} else {
|
|
41
|
-
this.earlyMessages.push(msg);
|
|
42
|
-
}
|
|
131
|
+
if (this.chatMessageCallback) this.chatMessageCallback(msg);
|
|
132
|
+
else this.earlyMessages.push(msg);
|
|
43
133
|
});
|
|
44
134
|
this.socket.on("agentRunner:stop", () => {
|
|
45
|
-
if (this.stopCallback)
|
|
46
|
-
|
|
47
|
-
} else {
|
|
48
|
-
this.earlyStop = true;
|
|
49
|
-
}
|
|
135
|
+
if (this.stopCallback) this.stopCallback();
|
|
136
|
+
else this.earlyStop = true;
|
|
50
137
|
});
|
|
51
138
|
this.socket.on("agentRunner:questionAnswer", (data) => {
|
|
52
139
|
const resolver = this.pendingQuestionResolvers.get(data.requestId);
|
|
@@ -56,16 +143,11 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
56
143
|
}
|
|
57
144
|
});
|
|
58
145
|
this.socket.on("agentRunner:setMode", (data) => {
|
|
59
|
-
if (this.modeChangeCallback)
|
|
60
|
-
|
|
61
|
-
} else {
|
|
62
|
-
this.earlyModeChanges.push(data);
|
|
63
|
-
}
|
|
146
|
+
if (this.modeChangeCallback) this.modeChangeCallback(data);
|
|
147
|
+
else this.earlyModeChanges.push(data);
|
|
64
148
|
});
|
|
65
149
|
this.socket.on("agentRunner:runStartCommand", () => {
|
|
66
|
-
if (this.runStartCommandCallback)
|
|
67
|
-
this.runStartCommandCallback();
|
|
68
|
-
}
|
|
150
|
+
if (this.runStartCommandCallback) this.runStartCommandCallback();
|
|
69
151
|
});
|
|
70
152
|
this.socket.on("connect", () => {
|
|
71
153
|
if (!settled) {
|
|
@@ -83,72 +165,16 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
83
165
|
});
|
|
84
166
|
}
|
|
85
167
|
fetchChatMessages(limit, taskId) {
|
|
86
|
-
|
|
87
|
-
if (!socket) throw new Error("Not connected");
|
|
88
|
-
return new Promise((resolve2, reject) => {
|
|
89
|
-
socket.emit(
|
|
90
|
-
"agentRunner:getChatMessages",
|
|
91
|
-
{ limit, taskId },
|
|
92
|
-
(response) => {
|
|
93
|
-
if (response.success && response.data) {
|
|
94
|
-
resolve2(response.data);
|
|
95
|
-
} else {
|
|
96
|
-
reject(new Error(response.error ?? "Failed to fetch chat messages"));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
});
|
|
168
|
+
return fetchChatMessages(this.socket, limit, taskId);
|
|
101
169
|
}
|
|
102
170
|
fetchTaskFiles() {
|
|
103
|
-
|
|
104
|
-
if (!socket) throw new Error("Not connected");
|
|
105
|
-
return new Promise((resolve2, reject) => {
|
|
106
|
-
socket.emit(
|
|
107
|
-
"agentRunner:getTaskFiles",
|
|
108
|
-
{},
|
|
109
|
-
(response) => {
|
|
110
|
-
if (response.success && response.data) {
|
|
111
|
-
resolve2(response.data);
|
|
112
|
-
} else {
|
|
113
|
-
reject(new Error(response.error ?? "Failed to fetch task files"));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
);
|
|
117
|
-
});
|
|
171
|
+
return fetchTaskFiles(this.socket);
|
|
118
172
|
}
|
|
119
173
|
fetchTaskFile(fileId) {
|
|
120
|
-
|
|
121
|
-
if (!socket) throw new Error("Not connected");
|
|
122
|
-
return new Promise((resolve2, reject) => {
|
|
123
|
-
socket.emit(
|
|
124
|
-
"agentRunner:getTaskFile",
|
|
125
|
-
{ fileId },
|
|
126
|
-
(response) => {
|
|
127
|
-
if (response.success && response.data) {
|
|
128
|
-
resolve2(response.data);
|
|
129
|
-
} else {
|
|
130
|
-
reject(new Error(response.error ?? "Failed to fetch task file"));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
});
|
|
174
|
+
return fetchTaskFile(this.socket, fileId);
|
|
135
175
|
}
|
|
136
176
|
fetchTaskContext() {
|
|
137
|
-
|
|
138
|
-
if (!socket) throw new Error("Not connected");
|
|
139
|
-
return new Promise((resolve2, reject) => {
|
|
140
|
-
socket.emit(
|
|
141
|
-
"agentRunner:getTaskContext",
|
|
142
|
-
{},
|
|
143
|
-
(response) => {
|
|
144
|
-
if (response.success && response.data) {
|
|
145
|
-
resolve2(response.data);
|
|
146
|
-
} else {
|
|
147
|
-
reject(new Error(response.error ?? "Failed to fetch task context"));
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
});
|
|
177
|
+
return fetchTaskContext(this.socket);
|
|
152
178
|
}
|
|
153
179
|
sendEvent(event) {
|
|
154
180
|
if (!this.socket) throw new Error("Not connected");
|
|
@@ -163,9 +189,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
163
189
|
this.flushTimer = null;
|
|
164
190
|
}
|
|
165
191
|
if (!this.socket || this.eventBuffer.length === 0) return;
|
|
166
|
-
for (const entry of this.eventBuffer)
|
|
167
|
-
this.socket.emit("agentRunner:event", entry);
|
|
168
|
-
}
|
|
192
|
+
for (const entry of this.eventBuffer) this.socket.emit("agentRunner:event", entry);
|
|
169
193
|
this.eventBuffer = [];
|
|
170
194
|
}
|
|
171
195
|
updateStatus(status) {
|
|
@@ -177,21 +201,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
177
201
|
this.socket.emit("agentRunner:chatMessage", { content });
|
|
178
202
|
}
|
|
179
203
|
createPR(params) {
|
|
180
|
-
|
|
181
|
-
if (!socket) throw new Error("Not connected");
|
|
182
|
-
return new Promise((resolve2, reject) => {
|
|
183
|
-
socket.emit(
|
|
184
|
-
"agentRunner:createPR",
|
|
185
|
-
params,
|
|
186
|
-
(response) => {
|
|
187
|
-
if (response.success && response.data) {
|
|
188
|
-
resolve2(response.data);
|
|
189
|
-
} else {
|
|
190
|
-
reject(new Error(response.error ?? "Failed to create pull request"));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
);
|
|
194
|
-
});
|
|
204
|
+
return createPR(this.socket, params);
|
|
195
205
|
}
|
|
196
206
|
askUserQuestion(requestId, questions) {
|
|
197
207
|
if (!this.socket) throw new Error("Not connected");
|
|
@@ -213,9 +223,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
213
223
|
}
|
|
214
224
|
onChatMessage(callback) {
|
|
215
225
|
this.chatMessageCallback = callback;
|
|
216
|
-
for (const msg of this.earlyMessages)
|
|
217
|
-
callback(msg);
|
|
218
|
-
}
|
|
226
|
+
for (const msg of this.earlyMessages) callback(msg);
|
|
219
227
|
this.earlyMessages = [];
|
|
220
228
|
}
|
|
221
229
|
onStopRequested(callback) {
|
|
@@ -227,9 +235,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
227
235
|
}
|
|
228
236
|
onModeChange(callback) {
|
|
229
237
|
this.modeChangeCallback = callback;
|
|
230
|
-
for (const data of this.earlyModeChanges)
|
|
231
|
-
callback(data);
|
|
232
|
-
}
|
|
238
|
+
for (const data of this.earlyModeChanges) callback(data);
|
|
233
239
|
this.earlyModeChanges = [];
|
|
234
240
|
}
|
|
235
241
|
onRunStartCommand(callback) {
|
|
@@ -258,14 +264,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
258
264
|
this.sendEvent({ type: "agent_typing_stop" });
|
|
259
265
|
}
|
|
260
266
|
createSubtask(data) {
|
|
261
|
-
|
|
262
|
-
if (!socket) throw new Error("Not connected");
|
|
263
|
-
return new Promise((resolve2, reject) => {
|
|
264
|
-
socket.emit("agentRunner:createSubtask", data, (response) => {
|
|
265
|
-
if (response.success && response.data) resolve2(response.data);
|
|
266
|
-
else reject(new Error(response.error ?? "Failed to create subtask"));
|
|
267
|
-
});
|
|
268
|
-
});
|
|
267
|
+
return createSubtask(this.socket, data);
|
|
269
268
|
}
|
|
270
269
|
updateSubtask(subtaskId, fields) {
|
|
271
270
|
if (!this.socket) throw new Error("Not connected");
|
|
@@ -276,183 +275,44 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
276
275
|
this.socket.emit("agentRunner:deleteSubtask", { subtaskId });
|
|
277
276
|
}
|
|
278
277
|
listSubtasks() {
|
|
279
|
-
|
|
280
|
-
if (!socket) throw new Error("Not connected");
|
|
281
|
-
return new Promise((resolve2, reject) => {
|
|
282
|
-
socket.emit("agentRunner:listSubtasks", {}, (response) => {
|
|
283
|
-
if (response.success && response.data) resolve2(response.data);
|
|
284
|
-
else reject(new Error(response.error ?? "Failed to list subtasks"));
|
|
285
|
-
});
|
|
286
|
-
});
|
|
278
|
+
return listSubtasks(this.socket);
|
|
287
279
|
}
|
|
288
280
|
fetchCliHistory(taskId, limit, source) {
|
|
289
|
-
|
|
290
|
-
if (!socket) throw new Error("Not connected");
|
|
291
|
-
const timeout = 1e4;
|
|
292
|
-
return Promise.race([
|
|
293
|
-
new Promise((resolve2, reject) => {
|
|
294
|
-
socket.emit(
|
|
295
|
-
"agentRunner:getCliHistory",
|
|
296
|
-
{ taskId: taskId ?? this.config.taskId, limit, source },
|
|
297
|
-
(response) => {
|
|
298
|
-
if (response.success && response.data) {
|
|
299
|
-
resolve2(response.data);
|
|
300
|
-
} else {
|
|
301
|
-
reject(new Error(response.error ?? "Failed to fetch CLI history"));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
);
|
|
305
|
-
}),
|
|
306
|
-
new Promise(
|
|
307
|
-
(_, reject) => setTimeout(() => reject(new Error("CLI history request timed out")), timeout)
|
|
308
|
-
)
|
|
309
|
-
]);
|
|
281
|
+
return fetchCliHistory(this.socket, taskId ?? this.config.taskId, limit, source);
|
|
310
282
|
}
|
|
311
283
|
startChildCloudBuild(childTaskId) {
|
|
312
|
-
|
|
313
|
-
if (!socket) throw new Error("Not connected");
|
|
314
|
-
return new Promise((resolve2, reject) => {
|
|
315
|
-
socket.emit(
|
|
316
|
-
"agentRunner:startChildCloudBuild",
|
|
317
|
-
{ childTaskId },
|
|
318
|
-
(response) => {
|
|
319
|
-
if (response.success && response.data) resolve2(response.data);
|
|
320
|
-
else reject(new Error(response.error ?? "Failed to start child cloud build"));
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
});
|
|
284
|
+
return startChildCloudBuild(this.socket, childTaskId);
|
|
324
285
|
}
|
|
325
286
|
approveAndMergePR(childTaskId) {
|
|
326
|
-
|
|
327
|
-
if (!socket) throw new Error("Not connected");
|
|
328
|
-
return new Promise((resolve2, reject) => {
|
|
329
|
-
socket.emit(
|
|
330
|
-
"agentRunner:approveAndMergePR",
|
|
331
|
-
{ childTaskId },
|
|
332
|
-
(response) => {
|
|
333
|
-
if (response.success && response.data) {
|
|
334
|
-
resolve2(response.data);
|
|
335
|
-
} else {
|
|
336
|
-
reject(new Error(response.error ?? "Failed to approve and merge PR"));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
);
|
|
340
|
-
});
|
|
287
|
+
return approveAndMergePR(this.socket, childTaskId);
|
|
341
288
|
}
|
|
342
289
|
postChildChatMessage(childTaskId, content) {
|
|
343
|
-
|
|
344
|
-
if (!socket) throw new Error("Not connected");
|
|
345
|
-
return new Promise((resolve2, reject) => {
|
|
346
|
-
socket.emit(
|
|
347
|
-
"agentRunner:postChildChatMessage",
|
|
348
|
-
{ childTaskId, content },
|
|
349
|
-
(response) => {
|
|
350
|
-
if (response.success) resolve2();
|
|
351
|
-
else reject(new Error(response.error ?? "Failed to post to child chat"));
|
|
352
|
-
}
|
|
353
|
-
);
|
|
354
|
-
});
|
|
290
|
+
return postChildChatMessage(this.socket, childTaskId, content);
|
|
355
291
|
}
|
|
356
292
|
updateChildStatus(childTaskId, status) {
|
|
357
|
-
|
|
358
|
-
if (!socket) throw new Error("Not connected");
|
|
359
|
-
return new Promise((resolve2, reject) => {
|
|
360
|
-
socket.emit(
|
|
361
|
-
"agentRunner:updateChildStatus",
|
|
362
|
-
{ childTaskId, status },
|
|
363
|
-
(response) => {
|
|
364
|
-
if (response.success) resolve2();
|
|
365
|
-
else reject(new Error(response.error ?? "Failed to update child status"));
|
|
366
|
-
}
|
|
367
|
-
);
|
|
368
|
-
});
|
|
293
|
+
return updateChildStatus(this.socket, childTaskId, status);
|
|
369
294
|
}
|
|
370
295
|
stopChildBuild(childTaskId) {
|
|
371
|
-
|
|
372
|
-
if (!socket) throw new Error("Not connected");
|
|
373
|
-
return new Promise((resolve2, reject) => {
|
|
374
|
-
socket.emit(
|
|
375
|
-
"agentRunner:stopChildBuild",
|
|
376
|
-
{ childTaskId },
|
|
377
|
-
(response) => {
|
|
378
|
-
if (response.success) resolve2();
|
|
379
|
-
else reject(new Error(response.error ?? "Failed to stop child build"));
|
|
380
|
-
}
|
|
381
|
-
);
|
|
382
|
-
});
|
|
296
|
+
return stopChildBuild(this.socket, childTaskId);
|
|
383
297
|
}
|
|
384
298
|
fetchTask(slugOrId) {
|
|
385
|
-
|
|
386
|
-
if (!socket) throw new Error("Not connected");
|
|
387
|
-
return new Promise((resolve2, reject) => {
|
|
388
|
-
socket.emit(
|
|
389
|
-
"agentRunner:getTask",
|
|
390
|
-
{ slugOrId },
|
|
391
|
-
(response) => {
|
|
392
|
-
if (response.success && response.data) {
|
|
393
|
-
resolve2(response.data);
|
|
394
|
-
} else {
|
|
395
|
-
reject(new Error(response.error ?? "Failed to fetch task"));
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
});
|
|
299
|
+
return fetchTask(this.socket, slugOrId);
|
|
400
300
|
}
|
|
401
301
|
updateTaskProperties(data) {
|
|
402
302
|
if (!this.socket) throw new Error("Not connected");
|
|
403
303
|
this.socket.emit("agentRunner:updateTaskProperties", data);
|
|
404
304
|
}
|
|
405
305
|
listIcons() {
|
|
406
|
-
|
|
407
|
-
if (!socket) throw new Error("Not connected");
|
|
408
|
-
return new Promise((resolve2, reject) => {
|
|
409
|
-
socket.emit("agentRunner:listIcons", {}, (response) => {
|
|
410
|
-
if (response.success && response.data) resolve2(response.data);
|
|
411
|
-
else reject(new Error(response.error ?? "Failed to list icons"));
|
|
412
|
-
});
|
|
413
|
-
});
|
|
306
|
+
return listIcons(this.socket);
|
|
414
307
|
}
|
|
415
308
|
generateTaskIcon(prompt, aspectRatio) {
|
|
416
|
-
|
|
417
|
-
if (!socket) throw new Error("Not connected");
|
|
418
|
-
return new Promise((resolve2, reject) => {
|
|
419
|
-
socket.emit(
|
|
420
|
-
"agentRunner:generateTaskIcon",
|
|
421
|
-
{ prompt, aspectRatio },
|
|
422
|
-
(response) => {
|
|
423
|
-
if (response.success && response.data) resolve2(response.data);
|
|
424
|
-
else reject(new Error(response.error ?? "Failed to generate icon"));
|
|
425
|
-
}
|
|
426
|
-
);
|
|
427
|
-
});
|
|
309
|
+
return generateTaskIcon(this.socket, prompt, aspectRatio);
|
|
428
310
|
}
|
|
429
311
|
getTaskProperties() {
|
|
430
|
-
|
|
431
|
-
if (!socket) throw new Error("Not connected");
|
|
432
|
-
return new Promise((resolve2, reject) => {
|
|
433
|
-
socket.emit(
|
|
434
|
-
"agentRunner:getTaskProperties",
|
|
435
|
-
{},
|
|
436
|
-
(response) => {
|
|
437
|
-
if (response.success && response.data) resolve2(response.data);
|
|
438
|
-
else reject(new Error(response.error ?? "Failed to get task properties"));
|
|
439
|
-
}
|
|
440
|
-
);
|
|
441
|
-
});
|
|
312
|
+
return getTaskProperties(this.socket);
|
|
442
313
|
}
|
|
443
314
|
triggerIdentification() {
|
|
444
|
-
|
|
445
|
-
if (!socket) throw new Error("Not connected");
|
|
446
|
-
return new Promise((resolve2, reject) => {
|
|
447
|
-
socket.emit(
|
|
448
|
-
"agentRunner:triggerIdentification",
|
|
449
|
-
{},
|
|
450
|
-
(response) => {
|
|
451
|
-
if (response.success && response.data) resolve2(response.data);
|
|
452
|
-
else reject(new Error(response.error ?? "Identification failed"));
|
|
453
|
-
}
|
|
454
|
-
);
|
|
455
|
-
});
|
|
315
|
+
return triggerIdentification(this.socket);
|
|
456
316
|
}
|
|
457
317
|
emitModeTransition(payload) {
|
|
458
318
|
if (!this.socket) return;
|
|
@@ -615,14 +475,52 @@ var ProjectConnection = class {
|
|
|
615
475
|
}
|
|
616
476
|
};
|
|
617
477
|
|
|
478
|
+
// src/runner/worktree.ts
|
|
479
|
+
import { execSync } from "child_process";
|
|
480
|
+
import { existsSync } from "fs";
|
|
481
|
+
import { join } from "path";
|
|
482
|
+
var WORKTREE_DIR = ".worktrees";
|
|
483
|
+
function ensureWorktree(projectDir, taskId, branch) {
|
|
484
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
485
|
+
if (existsSync(worktreePath)) {
|
|
486
|
+
if (branch) {
|
|
487
|
+
try {
|
|
488
|
+
execSync(`git checkout --detach origin/${branch}`, {
|
|
489
|
+
cwd: worktreePath,
|
|
490
|
+
stdio: "ignore"
|
|
491
|
+
});
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return worktreePath;
|
|
496
|
+
}
|
|
497
|
+
const ref = branch ? `origin/${branch}` : "HEAD";
|
|
498
|
+
execSync(`git worktree add --detach "${worktreePath}" ${ref}`, {
|
|
499
|
+
cwd: projectDir,
|
|
500
|
+
stdio: "ignore"
|
|
501
|
+
});
|
|
502
|
+
return worktreePath;
|
|
503
|
+
}
|
|
504
|
+
function removeWorktree(projectDir, taskId) {
|
|
505
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
506
|
+
if (!existsSync(worktreePath)) return;
|
|
507
|
+
try {
|
|
508
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
509
|
+
cwd: projectDir,
|
|
510
|
+
stdio: "ignore"
|
|
511
|
+
});
|
|
512
|
+
} catch {
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
618
516
|
// src/setup/config.ts
|
|
619
517
|
import { readFile } from "fs/promises";
|
|
620
|
-
import { join } from "path";
|
|
518
|
+
import { join as join2 } from "path";
|
|
621
519
|
var CONVEYOR_CONFIG_PATH = ".conveyor/config.json";
|
|
622
520
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
623
521
|
async function loadForwardPorts(workspaceDir) {
|
|
624
522
|
try {
|
|
625
|
-
const raw = await readFile(
|
|
523
|
+
const raw = await readFile(join2(workspaceDir, DEVCONTAINER_PATH), "utf-8");
|
|
626
524
|
const parsed = JSON.parse(raw);
|
|
627
525
|
return parsed.forwardPorts ?? [];
|
|
628
526
|
} catch {
|
|
@@ -631,13 +529,13 @@ async function loadForwardPorts(workspaceDir) {
|
|
|
631
529
|
}
|
|
632
530
|
async function loadConveyorConfig(workspaceDir) {
|
|
633
531
|
try {
|
|
634
|
-
const raw = await readFile(
|
|
532
|
+
const raw = await readFile(join2(workspaceDir, CONVEYOR_CONFIG_PATH), "utf-8");
|
|
635
533
|
const parsed = JSON.parse(raw);
|
|
636
534
|
if (parsed.setupCommand || parsed.startCommand) return parsed;
|
|
637
535
|
} catch {
|
|
638
536
|
}
|
|
639
537
|
try {
|
|
640
|
-
const raw = await readFile(
|
|
538
|
+
const raw = await readFile(join2(workspaceDir, DEVCONTAINER_PATH), "utf-8");
|
|
641
539
|
const parsed = JSON.parse(raw);
|
|
642
540
|
if (parsed.conveyor && (parsed.conveyor.startCommand || parsed.conveyor.setupCommand)) {
|
|
643
541
|
return parsed.conveyor;
|
|
@@ -692,17 +590,17 @@ function runStartCommand(cmd, cwd, onOutput) {
|
|
|
692
590
|
}
|
|
693
591
|
|
|
694
592
|
// src/setup/codespace.ts
|
|
695
|
-
import { execSync } from "child_process";
|
|
593
|
+
import { execSync as execSync2 } from "child_process";
|
|
696
594
|
function initRtk() {
|
|
697
595
|
try {
|
|
698
|
-
|
|
699
|
-
|
|
596
|
+
execSync2("rtk --version", { stdio: "ignore" });
|
|
597
|
+
execSync2("rtk init --global --auto-patch", { stdio: "ignore" });
|
|
700
598
|
} catch {
|
|
701
599
|
}
|
|
702
600
|
}
|
|
703
601
|
function unshallowRepo(workspaceDir) {
|
|
704
602
|
try {
|
|
705
|
-
|
|
603
|
+
execSync2("git fetch --unshallow", {
|
|
706
604
|
cwd: workspaceDir,
|
|
707
605
|
stdio: "ignore",
|
|
708
606
|
timeout: 6e4
|
|
@@ -711,44 +609,6 @@ function unshallowRepo(workspaceDir) {
|
|
|
711
609
|
}
|
|
712
610
|
}
|
|
713
611
|
|
|
714
|
-
// src/runner/worktree.ts
|
|
715
|
-
import { execSync as execSync2 } from "child_process";
|
|
716
|
-
import { existsSync } from "fs";
|
|
717
|
-
import { join as join2 } from "path";
|
|
718
|
-
var WORKTREE_DIR = ".worktrees";
|
|
719
|
-
function ensureWorktree(projectDir, taskId, branch) {
|
|
720
|
-
const worktreePath = join2(projectDir, WORKTREE_DIR, taskId);
|
|
721
|
-
if (existsSync(worktreePath)) {
|
|
722
|
-
if (branch) {
|
|
723
|
-
try {
|
|
724
|
-
execSync2(`git checkout --detach origin/${branch}`, {
|
|
725
|
-
cwd: worktreePath,
|
|
726
|
-
stdio: "ignore"
|
|
727
|
-
});
|
|
728
|
-
} catch {
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return worktreePath;
|
|
732
|
-
}
|
|
733
|
-
const ref = branch ? `origin/${branch}` : "HEAD";
|
|
734
|
-
execSync2(`git worktree add --detach "${worktreePath}" ${ref}`, {
|
|
735
|
-
cwd: projectDir,
|
|
736
|
-
stdio: "ignore"
|
|
737
|
-
});
|
|
738
|
-
return worktreePath;
|
|
739
|
-
}
|
|
740
|
-
function removeWorktree(projectDir, taskId) {
|
|
741
|
-
const worktreePath = join2(projectDir, WORKTREE_DIR, taskId);
|
|
742
|
-
if (!existsSync(worktreePath)) return;
|
|
743
|
-
try {
|
|
744
|
-
execSync2(`git worktree remove "${worktreePath}" --force`, {
|
|
745
|
-
cwd: projectDir,
|
|
746
|
-
stdio: "ignore"
|
|
747
|
-
});
|
|
748
|
-
} catch {
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
612
|
// src/runner/agent-runner.ts
|
|
753
613
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
754
614
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -785,45 +645,64 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
785
645
|
}
|
|
786
646
|
}
|
|
787
647
|
var API_ERROR_PATTERN = /API Error: [45]\d\d/;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
648
|
+
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
649
|
+
function isRetriableMessage(msg, durationMs) {
|
|
650
|
+
if (IMAGE_ERROR_PATTERN.test(msg)) return true;
|
|
651
|
+
if (API_ERROR_PATTERN.test(msg) && (durationMs === void 0 || durationMs < 3e4)) return true;
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
function handleSuccessResult(event, host, context, startTime) {
|
|
655
|
+
const durationMs = Date.now() - startTime;
|
|
656
|
+
const summary = event.result || "Task completed.";
|
|
657
|
+
const retriable = isRetriableMessage(summary, durationMs);
|
|
658
|
+
const cumulativeTotal = host.costTracker.addQueryCost(event.total_cost_usd);
|
|
659
|
+
const { modelUsage } = event;
|
|
660
|
+
if (modelUsage && typeof modelUsage === "object") {
|
|
661
|
+
host.costTracker.addModelUsage(modelUsage);
|
|
662
|
+
}
|
|
663
|
+
host.connection.sendEvent({ type: "completed", summary, costUsd: cumulativeTotal, durationMs });
|
|
664
|
+
if (modelUsage && typeof modelUsage === "object") {
|
|
665
|
+
let queryInputTokens = 0;
|
|
666
|
+
let contextWindow = 0;
|
|
667
|
+
for (const data of Object.values(modelUsage)) {
|
|
668
|
+
queryInputTokens += data.inputTokens ?? 0;
|
|
669
|
+
const cw = data.contextWindow ?? 0;
|
|
670
|
+
if (cw > contextWindow) contextWindow = cw;
|
|
671
|
+
}
|
|
672
|
+
if (contextWindow > 0) {
|
|
673
|
+
host.connection.sendEvent({
|
|
674
|
+
type: "context_update",
|
|
675
|
+
contextTokens: queryInputTokens,
|
|
676
|
+
contextWindow
|
|
815
677
|
});
|
|
816
678
|
}
|
|
817
|
-
} else {
|
|
818
|
-
const errorEvent = event;
|
|
819
|
-
const errorMsg = errorEvent.errors.length > 0 ? errorEvent.errors.join(", ") : `Agent stopped: ${errorEvent.subtype}`;
|
|
820
|
-
if (API_ERROR_PATTERN.test(errorMsg) || /Could not process image/i.test(errorMsg)) {
|
|
821
|
-
retriable = true;
|
|
822
|
-
}
|
|
823
|
-
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
824
679
|
}
|
|
680
|
+
if (cumulativeTotal > 0 && context.agentId && context._runnerSessionId) {
|
|
681
|
+
const breakdown = host.costTracker.modelBreakdown;
|
|
682
|
+
host.connection.trackSpending({
|
|
683
|
+
agentId: context.agentId,
|
|
684
|
+
sessionId: context._runnerSessionId,
|
|
685
|
+
totalCostUsd: cumulativeTotal,
|
|
686
|
+
onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
687
|
+
modelUsage: breakdown.length > 0 ? breakdown : void 0
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return { totalCostUsd: cumulativeTotal, retriable };
|
|
691
|
+
}
|
|
692
|
+
function handleErrorResult(event, host) {
|
|
693
|
+
const errorMsg = event.errors.length > 0 ? event.errors.join(", ") : `Agent stopped: ${event.subtype}`;
|
|
694
|
+
const retriable = isRetriableMessage(errorMsg);
|
|
695
|
+
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
696
|
+
return { retriable };
|
|
697
|
+
}
|
|
698
|
+
function handleResultEvent(event, host, context, startTime) {
|
|
825
699
|
const resultSummary = event.subtype === "success" ? event.result : event.errors.join(", ");
|
|
826
|
-
|
|
700
|
+
if (event.subtype === "success") {
|
|
701
|
+
const result2 = handleSuccessResult(event, host, context, startTime);
|
|
702
|
+
return { ...result2, resultSummary };
|
|
703
|
+
}
|
|
704
|
+
const result = handleErrorResult(event, host);
|
|
705
|
+
return { totalCostUsd: 0, ...result, resultSummary };
|
|
827
706
|
}
|
|
828
707
|
async function emitResultEvent(event, host, context, startTime) {
|
|
829
708
|
const result = handleResultEvent(event, host, context, startTime);
|
|
@@ -926,256 +805,137 @@ async function processEvents(events, context, host) {
|
|
|
926
805
|
}
|
|
927
806
|
|
|
928
807
|
// src/execution/query-executor.ts
|
|
929
|
-
import { randomUUID } from "crypto";
|
|
930
808
|
import {
|
|
931
809
|
query
|
|
932
810
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
933
811
|
|
|
934
|
-
// src/execution/prompt
|
|
935
|
-
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["InProgress", "ReviewPR", "ReviewDev", "ReviewLive"]);
|
|
936
|
-
function formatFileSize(bytes) {
|
|
937
|
-
if (bytes === void 0) return "";
|
|
938
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
939
|
-
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
940
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
941
|
-
}
|
|
812
|
+
// src/execution/pack-runner-prompt.ts
|
|
942
813
|
function findLastAgentMessageIndex(history) {
|
|
943
814
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
944
815
|
if (history[i].role === "assistant") return i;
|
|
945
816
|
}
|
|
946
817
|
return -1;
|
|
947
818
|
}
|
|
948
|
-
function
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
return
|
|
819
|
+
function formatProjectAgents(projectAgents) {
|
|
820
|
+
const parts = [``, `## Project Agents`];
|
|
821
|
+
for (const pa of projectAgents) {
|
|
822
|
+
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
823
|
+
const sp = pa.storyPoints === null || pa.storyPoints === void 0 ? "" : `, story points: ${pa.storyPoints}`;
|
|
824
|
+
parts.push(`- ${pa.agent.name} (${role}${sp})`);
|
|
825
|
+
}
|
|
826
|
+
return parts;
|
|
956
827
|
}
|
|
957
|
-
function
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (mode === "pm") {
|
|
963
|
-
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
964
|
-
if (newMessages.length > 0) {
|
|
965
|
-
parts.push(
|
|
966
|
-
`You have been relaunched. Here are new messages since your last session:`,
|
|
967
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`)
|
|
968
|
-
);
|
|
969
|
-
} else {
|
|
970
|
-
parts.push(`You have been relaunched. No new messages since your last session.`);
|
|
971
|
-
}
|
|
972
|
-
parts.push(
|
|
973
|
-
`
|
|
974
|
-
You are the project manager for this task.`,
|
|
975
|
-
`Review the context above and wait for the team to provide instructions before taking action.`
|
|
976
|
-
);
|
|
977
|
-
} else if (scenario === "feedback_relaunch") {
|
|
978
|
-
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
979
|
-
parts.push(
|
|
980
|
-
`You have been relaunched with new feedback.`,
|
|
981
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
982
|
-
`
|
|
983
|
-
New messages since your last run:`,
|
|
984
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
985
|
-
`
|
|
986
|
-
Address the requested changes. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 review the feedback and implement the changes directly.`,
|
|
987
|
-
`Commit and push your updates.`
|
|
988
|
-
);
|
|
989
|
-
if (context.githubPRUrl) {
|
|
990
|
-
parts.push(
|
|
991
|
-
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch. Do NOT create a new PR.`
|
|
992
|
-
);
|
|
993
|
-
} else {
|
|
994
|
-
parts.push(
|
|
995
|
-
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI.`
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
} else {
|
|
999
|
-
parts.push(
|
|
1000
|
-
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1001
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1002
|
-
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
1003
|
-
`Review the current state of the codebase and verify everything is working correctly.`,
|
|
1004
|
-
`Reply with a brief status update (visible in chat), then wait for further instructions.`
|
|
1005
|
-
);
|
|
1006
|
-
if (context.githubPRUrl) {
|
|
1007
|
-
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1008
|
-
}
|
|
828
|
+
function formatStoryPoints(storyPoints) {
|
|
829
|
+
const parts = [``, `## Story Point Tiers`];
|
|
830
|
+
for (const sp of storyPoints) {
|
|
831
|
+
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
832
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1009
833
|
}
|
|
1010
|
-
return parts
|
|
834
|
+
return parts;
|
|
1011
835
|
}
|
|
1012
|
-
function
|
|
1013
|
-
const parts = [
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
836
|
+
function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
837
|
+
const parts = [
|
|
838
|
+
`You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
|
|
839
|
+
`You are running locally with full access to the repository and task management tools.`,
|
|
840
|
+
`Your job is to sequentially execute child tasks by firing cloud builds, reviewing their PRs, and merging them.`,
|
|
841
|
+
``,
|
|
842
|
+
`## Child Task Status Lifecycle`,
|
|
843
|
+
`- "Planning" \u2014 Not ready for execution. Skip it (or escalate if blocking).`,
|
|
844
|
+
`- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
|
|
845
|
+
`- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
|
|
846
|
+
`- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
|
|
847
|
+
`- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
|
|
848
|
+
`- "Complete" \u2014 Fully done. Move on.`,
|
|
849
|
+
``,
|
|
850
|
+
`## Autonomous Loop`,
|
|
851
|
+
`Follow this loop each time you are launched or relaunched:`,
|
|
852
|
+
``,
|
|
853
|
+
`1. Call list_subtasks to see the current state of all child tasks.`,
|
|
854
|
+
` The response includes PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId).`,
|
|
855
|
+
``,
|
|
856
|
+
`2. Evaluate each child by status (in ordinal order):`,
|
|
857
|
+
` - "ReviewPR": Review and merge its PR with approve_and_merge_pr.`,
|
|
858
|
+
` - If merge fails due to pending CI: post a status update to chat, state you are going idle, and the system will relaunch you to try again.`,
|
|
859
|
+
` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check what went wrong. Escalate to the team in chat.`,
|
|
860
|
+
` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
|
|
861
|
+
` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
|
|
862
|
+
` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
|
|
863
|
+
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
864
|
+
` - "Planning": Not ready. If this is blocking progress, notify the team.`,
|
|
865
|
+
``,
|
|
866
|
+
`3. After merging a PR: run \`git pull origin ${context.baseBranch}\` to get the merged changes before firing the next child.`,
|
|
867
|
+
``,
|
|
868
|
+
`4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
|
|
869
|
+
``,
|
|
870
|
+
`5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with update_task_status("Complete").`,
|
|
871
|
+
``,
|
|
872
|
+
`## Important Rules`,
|
|
873
|
+
`- Process children ONE at a time, in ordinal order.`,
|
|
874
|
+
`- After firing a child build OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
|
|
875
|
+
`- Do NOT attempt to write code yourself. Your role is coordination only.`,
|
|
876
|
+
`- If a child is stuck in "InProgress" for an unusually long time, use get_task_cli(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
|
|
877
|
+
`- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
|
|
878
|
+
`- list_subtasks returns PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId) for each child \u2014 use this to verify readiness before firing builds.`,
|
|
879
|
+
`- You can use read_task_chat to check for team messages.`
|
|
880
|
+
];
|
|
881
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
882
|
+
parts.push(...formatStoryPoints(context.storyPoints));
|
|
1019
883
|
}
|
|
1020
|
-
if (context.
|
|
1021
|
-
parts.push(
|
|
1022
|
-
## Plan
|
|
1023
|
-
${context.plan}`);
|
|
884
|
+
if (context.projectAgents && context.projectAgents.length > 0) {
|
|
885
|
+
parts.push(...formatProjectAgents(context.projectAgents));
|
|
1024
886
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
parts.push(
|
|
1027
|
-
## Attached Files`);
|
|
1028
|
-
for (const file of context.files) {
|
|
1029
|
-
if (file.content && file.contentEncoding === "utf-8") {
|
|
1030
|
-
parts.push(`
|
|
1031
|
-
### ${file.fileName} (${file.mimeType})`);
|
|
1032
|
-
parts.push("```");
|
|
1033
|
-
parts.push(file.content);
|
|
1034
|
-
parts.push("```");
|
|
1035
|
-
} else if (file.content && file.contentEncoding === "base64") {
|
|
1036
|
-
const size = formatFileSize(file.fileSize);
|
|
1037
|
-
parts.push(
|
|
1038
|
-
`- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1039
|
-
);
|
|
1040
|
-
} else if (!file.content) {
|
|
1041
|
-
parts.push(`- **${file.fileName}** (${file.mimeType}): ${file.downloadUrl}`);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
887
|
+
if (setupLog.length > 0) {
|
|
888
|
+
parts.push(``, `## Environment setup log`, "```", ...setupLog, "```");
|
|
1044
889
|
}
|
|
1045
|
-
if (context.
|
|
1046
|
-
parts.push(
|
|
1047
|
-
## Repository References`);
|
|
1048
|
-
for (const ref of context.repoRefs) {
|
|
1049
|
-
const icon = ref.refType === "folder" ? "folder" : "file";
|
|
1050
|
-
parts.push(`- [${icon}] \`${ref.path}\``);
|
|
1051
|
-
}
|
|
890
|
+
if (context.agentInstructions) {
|
|
891
|
+
parts.push(``, `## Agent Instructions`, context.agentInstructions);
|
|
1052
892
|
}
|
|
1053
|
-
if (
|
|
1054
|
-
|
|
1055
|
-
parts.push(`
|
|
1056
|
-
## Recent Chat Context`);
|
|
1057
|
-
for (const msg of relevant) {
|
|
1058
|
-
const sender = msg.userName ?? msg.role;
|
|
1059
|
-
parts.push(`[${sender}]: ${msg.content}`);
|
|
1060
|
-
if (msg.files?.length) {
|
|
1061
|
-
for (const file of msg.files) {
|
|
1062
|
-
const sizeStr = file.fileSize ? `, ${formatFileSize(file.fileSize)}` : "";
|
|
1063
|
-
if (file.content && file.contentEncoding === "utf-8") {
|
|
1064
|
-
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
1065
|
-
parts.push("```");
|
|
1066
|
-
parts.push(file.content);
|
|
1067
|
-
parts.push("```");
|
|
1068
|
-
} else if (!file.content) {
|
|
1069
|
-
parts.push(
|
|
1070
|
-
`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]: ${file.downloadUrl}`
|
|
1071
|
-
);
|
|
1072
|
-
} else if (file.content && file.contentEncoding === "base64") {
|
|
1073
|
-
parts.push(
|
|
1074
|
-
`[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1075
|
-
);
|
|
1076
|
-
} else {
|
|
1077
|
-
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
893
|
+
if (config.instructions) {
|
|
894
|
+
parts.push(``, `## Additional Instructions`, config.instructions);
|
|
1082
895
|
}
|
|
1083
|
-
|
|
896
|
+
parts.push(
|
|
897
|
+
``,
|
|
898
|
+
`Your responses are sent directly to the task chat \u2014 the team sees everything you say.`,
|
|
899
|
+
`Do NOT call the post_to_chat tool for your own task; your replies already appear in chat automatically.`,
|
|
900
|
+
`Only use post_to_chat if you need to message a different task's chat (e.g. a child task).`,
|
|
901
|
+
`Use read_task_chat only if you need to re-read earlier messages beyond the chat context above.`
|
|
902
|
+
);
|
|
903
|
+
return parts.join("\n");
|
|
1084
904
|
}
|
|
1085
|
-
function
|
|
905
|
+
function buildPackRunnerInstructions(context, scenario) {
|
|
1086
906
|
const parts = [`
|
|
1087
907
|
## Instructions`];
|
|
1088
|
-
const isPm = mode === "pm";
|
|
1089
|
-
const isAutoMode = agentMode === "auto";
|
|
1090
908
|
if (scenario === "fresh") {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
`4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
|
|
1098
|
-
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
1099
|
-
);
|
|
1100
|
-
} else if (isPm && context.isParentTask) {
|
|
1101
|
-
parts.push(
|
|
1102
|
-
`You are the project manager for this task and its subtasks.`,
|
|
1103
|
-
`Use list_subtasks to review the current state of child tasks.`,
|
|
1104
|
-
`The task details are provided above. Wait for the team to provide instructions before taking action.`,
|
|
1105
|
-
`When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
|
|
1106
|
-
);
|
|
1107
|
-
} else if (isPm) {
|
|
1108
|
-
parts.push(
|
|
1109
|
-
`You are the project manager for this task.`,
|
|
1110
|
-
`The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
|
|
1111
|
-
`When you finish planning, save the plan with update_task and end your turn. Your reply summarizing the plan will be visible in chat. A separate task agent will execute the plan after review.`
|
|
1112
|
-
);
|
|
1113
|
-
} else {
|
|
1114
|
-
parts.push(
|
|
1115
|
-
`Begin executing the task plan above immediately.`,
|
|
1116
|
-
`Your FIRST action should be reading the relevant source files mentioned in the plan, then writing code. Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`,
|
|
1117
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch for the entire task. Do not checkout or create other branches.`,
|
|
1118
|
-
`Your replies are visible to the team in chat \u2014 briefly describe what you're doing when you begin meaningful implementation, and again when the PR is ready.`,
|
|
1119
|
-
`When finished, commit your changes, then run \`git fetch origin ${context.githubBranch}\` and \`git push origin ${context.githubBranch}\` (use --force-with-lease if push fails). Then use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
1120
|
-
);
|
|
1121
|
-
}
|
|
909
|
+
parts.push(
|
|
910
|
+
`You are the Pack Runner for this task and its subtasks.`,
|
|
911
|
+
`Begin your autonomous loop immediately: call list_subtasks to assess the current state.`,
|
|
912
|
+
`If any child is in "ReviewPR" status, review and merge its PR first.`,
|
|
913
|
+
`Then fire the next "Open" child task.`
|
|
914
|
+
);
|
|
1122
915
|
} else if (scenario === "idle_relaunch") {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
)
|
|
1129
|
-
|
|
1130
|
-
parts.push(
|
|
1131
|
-
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1132
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1133
|
-
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
|
|
1134
|
-
`Reply with a brief status update summarizing where things stand (visible in chat).`,
|
|
1135
|
-
`Then wait for further instructions \u2014 do NOT redo work that was already completed.`
|
|
1136
|
-
);
|
|
1137
|
-
if (context.githubPRUrl) {
|
|
1138
|
-
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
916
|
+
parts.push(
|
|
917
|
+
`You have been relaunched \u2014 a child task likely changed status (completed work, opened a PR, or was merged).`,
|
|
918
|
+
`Call list_subtasks to check the current state of all children.`,
|
|
919
|
+
`Look for children in "ReviewPR" status first \u2014 review and merge their PRs.`,
|
|
920
|
+
`If a child you previously fired is now in "ReviewDev", it was merged. Pull latest with \`git pull origin ${context.baseBranch}\` and continue to the next "Open" child.`,
|
|
921
|
+
`If no children need action (all InProgress or waiting), state you are going idle.`
|
|
922
|
+
);
|
|
1141
923
|
} else {
|
|
1142
924
|
const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
|
|
1143
925
|
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
`You are the project manager for this task.`,
|
|
1148
|
-
`
|
|
1149
|
-
New messages since your last run:`,
|
|
1150
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1151
|
-
`
|
|
1152
|
-
Review these messages and wait for the team to provide instructions before taking action.`
|
|
1153
|
-
);
|
|
1154
|
-
} else {
|
|
1155
|
-
parts.push(
|
|
1156
|
-
`You have been relaunched to address feedback on your previous work.`,
|
|
1157
|
-
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1158
|
-
`Start by running \`git log --oneline -10\` and \`git diff HEAD~3 HEAD --stat\` to review what you already committed.`,
|
|
1159
|
-
`
|
|
926
|
+
parts.push(
|
|
927
|
+
`You have been relaunched with new messages.`,
|
|
928
|
+
`
|
|
1160
929
|
New messages since your last run:`,
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
);
|
|
1166
|
-
if (context.githubPRUrl) {
|
|
1167
|
-
parts.push(
|
|
1168
|
-
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch to update it. Do NOT create a new PR.`
|
|
1169
|
-
);
|
|
1170
|
-
} else {
|
|
1171
|
-
parts.push(
|
|
1172
|
-
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
930
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
931
|
+
`
|
|
932
|
+
After addressing the feedback, resume your autonomous loop: call list_subtasks and proceed accordingly.`
|
|
933
|
+
);
|
|
1176
934
|
}
|
|
1177
935
|
return parts;
|
|
1178
936
|
}
|
|
937
|
+
|
|
938
|
+
// src/execution/mode-prompt.ts
|
|
1179
939
|
function buildPropertyInstructions(context) {
|
|
1180
940
|
const parts = [];
|
|
1181
941
|
parts.push(
|
|
@@ -1287,135 +1047,15 @@ function buildModePrompt(agentMode, context) {
|
|
|
1287
1047
|
return null;
|
|
1288
1048
|
}
|
|
1289
1049
|
}
|
|
1290
|
-
|
|
1050
|
+
|
|
1051
|
+
// src/execution/system-prompt.ts
|
|
1052
|
+
function formatProjectAgentLine(pa) {
|
|
1053
|
+
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
1054
|
+
const sp = pa.storyPoints === null || pa.storyPoints === void 0 ? "" : `, story points: ${pa.storyPoints}`;
|
|
1055
|
+
return `- ${pa.agent.name} (${role}${sp})`;
|
|
1056
|
+
}
|
|
1057
|
+
function buildPmPreamble(context) {
|
|
1291
1058
|
const parts = [
|
|
1292
|
-
`You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
|
|
1293
|
-
`You are running locally with full access to the repository and task management tools.`,
|
|
1294
|
-
`Your job is to sequentially execute child tasks by firing cloud builds, reviewing their PRs, and merging them.`,
|
|
1295
|
-
``,
|
|
1296
|
-
`## Child Task Status Lifecycle`,
|
|
1297
|
-
`- "Planning" \u2014 Not ready for execution. Skip it (or escalate if blocking).`,
|
|
1298
|
-
`- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
|
|
1299
|
-
`- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
|
|
1300
|
-
`- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
|
|
1301
|
-
`- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
|
|
1302
|
-
`- "Complete" \u2014 Fully done. Move on.`,
|
|
1303
|
-
``,
|
|
1304
|
-
`## Autonomous Loop`,
|
|
1305
|
-
`Follow this loop each time you are launched or relaunched:`,
|
|
1306
|
-
``,
|
|
1307
|
-
`1. Call list_subtasks to see the current state of all child tasks.`,
|
|
1308
|
-
` The response includes PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId).`,
|
|
1309
|
-
``,
|
|
1310
|
-
`2. Evaluate each child by status (in ordinal order):`,
|
|
1311
|
-
` - "ReviewPR": Review and merge its PR with approve_and_merge_pr.`,
|
|
1312
|
-
` - If merge fails due to pending CI: post a status update to chat, state you are going idle, and the system will relaunch you to try again.`,
|
|
1313
|
-
` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check what went wrong. Escalate to the team in chat.`,
|
|
1314
|
-
` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
|
|
1315
|
-
` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
|
|
1316
|
-
` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
|
|
1317
|
-
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
1318
|
-
` - "Planning": Not ready. If this is blocking progress, notify the team.`,
|
|
1319
|
-
``,
|
|
1320
|
-
`3. After merging a PR: run \`git pull origin ${context.baseBranch}\` to get the merged changes before firing the next child.`,
|
|
1321
|
-
``,
|
|
1322
|
-
`4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
|
|
1323
|
-
``,
|
|
1324
|
-
`5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with update_task_status("Complete").`,
|
|
1325
|
-
``,
|
|
1326
|
-
`## Important Rules`,
|
|
1327
|
-
`- Process children ONE at a time, in ordinal order.`,
|
|
1328
|
-
`- After firing a child build OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
|
|
1329
|
-
`- Do NOT attempt to write code yourself. Your role is coordination only.`,
|
|
1330
|
-
`- If a child is stuck in "InProgress" for an unusually long time, use get_task_cli(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
|
|
1331
|
-
`- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
|
|
1332
|
-
`- list_subtasks returns PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId) for each child \u2014 use this to verify readiness before firing builds.`,
|
|
1333
|
-
`- You can use read_task_chat to check for team messages.`
|
|
1334
|
-
];
|
|
1335
|
-
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
1336
|
-
parts.push(``, `## Story Point Tiers`);
|
|
1337
|
-
for (const sp of context.storyPoints) {
|
|
1338
|
-
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
1339
|
-
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
if (context.projectAgents && context.projectAgents.length > 0) {
|
|
1343
|
-
parts.push(``, `## Project Agents`);
|
|
1344
|
-
for (const pa of context.projectAgents) {
|
|
1345
|
-
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
1346
|
-
const sp = pa.storyPoints != null ? `, story points: ${pa.storyPoints}` : "";
|
|
1347
|
-
parts.push(`- ${pa.agent.name} (${role}${sp})`);
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
if (setupLog.length > 0) {
|
|
1351
|
-
parts.push(``, `## Environment setup log`, "```", ...setupLog, "```");
|
|
1352
|
-
}
|
|
1353
|
-
if (context.agentInstructions) {
|
|
1354
|
-
parts.push(``, `## Agent Instructions`, context.agentInstructions);
|
|
1355
|
-
}
|
|
1356
|
-
if (config.instructions) {
|
|
1357
|
-
parts.push(``, `## Additional Instructions`, config.instructions);
|
|
1358
|
-
}
|
|
1359
|
-
parts.push(
|
|
1360
|
-
``,
|
|
1361
|
-
`Your responses are sent directly to the task chat \u2014 the team sees everything you say.`,
|
|
1362
|
-
`Do NOT call the post_to_chat tool for your own task; your replies already appear in chat automatically.`,
|
|
1363
|
-
`Only use post_to_chat if you need to message a different task's chat (e.g. a child task).`,
|
|
1364
|
-
`Use read_task_chat only if you need to re-read earlier messages beyond the chat context above.`
|
|
1365
|
-
);
|
|
1366
|
-
return parts.join("\n");
|
|
1367
|
-
}
|
|
1368
|
-
function buildPackRunnerInstructions(context, scenario) {
|
|
1369
|
-
const parts = [`
|
|
1370
|
-
## Instructions`];
|
|
1371
|
-
if (scenario === "fresh") {
|
|
1372
|
-
parts.push(
|
|
1373
|
-
`You are the Pack Runner for this task and its subtasks.`,
|
|
1374
|
-
`Begin your autonomous loop immediately: call list_subtasks to assess the current state.`,
|
|
1375
|
-
`If any child is in "ReviewPR" status, review and merge its PR first.`,
|
|
1376
|
-
`Then fire the next "Open" child task.`
|
|
1377
|
-
);
|
|
1378
|
-
} else if (scenario === "idle_relaunch") {
|
|
1379
|
-
parts.push(
|
|
1380
|
-
`You have been relaunched \u2014 a child task likely changed status (completed work, opened a PR, or was merged).`,
|
|
1381
|
-
`Call list_subtasks to check the current state of all children.`,
|
|
1382
|
-
`Look for children in "ReviewPR" status first \u2014 review and merge their PRs.`,
|
|
1383
|
-
`If a child you previously fired is now in "ReviewDev", it was merged. Pull latest with \`git pull origin ${context.baseBranch}\` and continue to the next "Open" child.`,
|
|
1384
|
-
`If no children need action (all InProgress or waiting), state you are going idle.`
|
|
1385
|
-
);
|
|
1386
|
-
} else {
|
|
1387
|
-
const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
|
|
1388
|
-
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1389
|
-
parts.push(
|
|
1390
|
-
`You have been relaunched with new messages.`,
|
|
1391
|
-
`
|
|
1392
|
-
New messages since your last run:`,
|
|
1393
|
-
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1394
|
-
`
|
|
1395
|
-
After addressing the feedback, resume your autonomous loop: call list_subtasks and proceed accordingly.`
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
return parts;
|
|
1399
|
-
}
|
|
1400
|
-
function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
1401
|
-
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
1402
|
-
if (!isPackRunner) {
|
|
1403
|
-
const sessionRelaunch = buildRelaunchWithSession(mode, context);
|
|
1404
|
-
if (sessionRelaunch) return sessionRelaunch;
|
|
1405
|
-
}
|
|
1406
|
-
const scenario = detectRelaunchScenario(context);
|
|
1407
|
-
const body = buildTaskBody(context);
|
|
1408
|
-
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
1409
|
-
return [...body, ...instructions].join("\n");
|
|
1410
|
-
}
|
|
1411
|
-
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
1412
|
-
const isPm = mode === "pm";
|
|
1413
|
-
const isPmActive = isPm && agentMode === "building";
|
|
1414
|
-
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
1415
|
-
if (isPackRunner) {
|
|
1416
|
-
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
1417
|
-
}
|
|
1418
|
-
const pmParts = [
|
|
1419
1059
|
`You are an AI project manager helping to plan tasks for the "${context.title}" project.`,
|
|
1420
1060
|
`You are running locally with full access to the repository.`,
|
|
1421
1061
|
`You can read files, search code, and run shell commands (e.g. git log, git diff) to understand the codebase. You cannot write or edit files.`,
|
|
@@ -1431,8 +1071,8 @@ Workflow:`,
|
|
|
1431
1071
|
`- After saving the plan, end your turn with a summary reply (the team sees your responses in chat automatically). Do NOT attempt to execute the plan yourself.`,
|
|
1432
1072
|
`- A separate task agent will handle execution after the team reviews and approves your plan.`
|
|
1433
1073
|
];
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1074
|
+
if (context.isParentTask) {
|
|
1075
|
+
parts.push(
|
|
1436
1076
|
`
|
|
1437
1077
|
You are the Project Manager for this set of tasks.`,
|
|
1438
1078
|
`This task has child tasks (subtasks) that are tracked on the board.`,
|
|
@@ -1440,26 +1080,27 @@ You are the Project Manager for this set of tasks.`,
|
|
|
1440
1080
|
`Use the subtask tools (create_subtask, update_subtask, list_subtasks) to manage work breakdown.`
|
|
1441
1081
|
);
|
|
1442
1082
|
}
|
|
1443
|
-
if (
|
|
1444
|
-
|
|
1083
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
1084
|
+
parts.push(`
|
|
1445
1085
|
Story Point Tiers:`);
|
|
1446
1086
|
for (const sp of context.storyPoints) {
|
|
1447
1087
|
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
1448
|
-
|
|
1088
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1449
1089
|
}
|
|
1450
1090
|
}
|
|
1451
|
-
if (
|
|
1452
|
-
|
|
1091
|
+
if (context.projectAgents && context.projectAgents.length > 0) {
|
|
1092
|
+
parts.push(`
|
|
1453
1093
|
Project Agents:`);
|
|
1454
1094
|
for (const pa of context.projectAgents) {
|
|
1455
|
-
|
|
1456
|
-
const sp = pa.storyPoints != null ? `, story points: ${pa.storyPoints}` : "";
|
|
1457
|
-
pmParts.push(`- ${pa.agent.name} (${role}${sp})`);
|
|
1095
|
+
parts.push(formatProjectAgentLine(pa));
|
|
1458
1096
|
}
|
|
1459
1097
|
}
|
|
1460
|
-
|
|
1098
|
+
return parts;
|
|
1099
|
+
}
|
|
1100
|
+
function buildActivePreamble(context, workspaceDir) {
|
|
1101
|
+
return [
|
|
1461
1102
|
`You are an AI project manager in ACTIVE mode for the "${context.title}" project.`,
|
|
1462
|
-
`You have direct coding access to the repository at ${
|
|
1103
|
+
`You have direct coding access to the repository at ${workspaceDir}.`,
|
|
1463
1104
|
`You can edit files, run tests, and make commits.`,
|
|
1464
1105
|
`You still have access to all PM tools (subtasks, update_task, chat).`,
|
|
1465
1106
|
`
|
|
@@ -1470,7 +1111,7 @@ Environment (ready, no setup required):`,
|
|
|
1470
1111
|
context.githubBranch ? `- You are working on branch: \`${context.githubBranch}\`` : "",
|
|
1471
1112
|
`
|
|
1472
1113
|
Safety rules:`,
|
|
1473
|
-
`- Stay within the project directory (${
|
|
1114
|
+
`- Stay within the project directory (${workspaceDir}).`,
|
|
1474
1115
|
`- Do NOT run \`git push --force\` or \`git reset --hard\`. Use \`--force-with-lease\` if needed.`,
|
|
1475
1116
|
`- Do NOT delete \`.env\` files or modify \`node_modules\`.`,
|
|
1476
1117
|
`- Do NOT run destructive commands like \`rm -rf /\`.`,
|
|
@@ -1480,7 +1121,9 @@ Workflow:`,
|
|
|
1480
1121
|
`- When done with changes, summarize what you did in your reply.`,
|
|
1481
1122
|
`- If you toggled into active mode temporarily, mention when you're done so the team can switch you back to planning mode.`
|
|
1482
1123
|
].filter(Boolean);
|
|
1483
|
-
|
|
1124
|
+
}
|
|
1125
|
+
function buildTaskAgentPreamble(context) {
|
|
1126
|
+
return [
|
|
1484
1127
|
`You are an AI agent working on a task for the "${context.title}" project.`,
|
|
1485
1128
|
`You are running inside a GitHub Codespace with full access to the repository.`,
|
|
1486
1129
|
`
|
|
@@ -1505,6 +1148,15 @@ Git safety \u2014 STRICT rules:`,
|
|
|
1505
1148
|
`- This branch was created from \`${context.baseBranch}\`. PRs will automatically target that branch.`,
|
|
1506
1149
|
`- If \`git push\` fails with "non-fast-forward", run \`git push --force-with-lease origin ${context.githubBranch}\`. This branch is exclusively yours \u2014 force-with-lease is safe.`
|
|
1507
1150
|
];
|
|
1151
|
+
}
|
|
1152
|
+
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
1153
|
+
const isPm = mode === "pm";
|
|
1154
|
+
const isPmActive = isPm && agentMode === "building";
|
|
1155
|
+
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
1156
|
+
if (isPackRunner) {
|
|
1157
|
+
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
1158
|
+
}
|
|
1159
|
+
const parts = isPmActive ? buildActivePreamble(context, config.workspaceDir) : isPm ? buildPmPreamble(context) : buildTaskAgentPreamble(context);
|
|
1508
1160
|
if (setupLog.length > 0) {
|
|
1509
1161
|
parts.push(
|
|
1510
1162
|
`
|
|
@@ -1536,11 +1188,287 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
|
|
|
1536
1188
|
`Use the create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
|
|
1537
1189
|
);
|
|
1538
1190
|
}
|
|
1539
|
-
const modePrompt = buildModePrompt(agentMode, context);
|
|
1540
|
-
if (modePrompt) {
|
|
1541
|
-
parts.push(modePrompt);
|
|
1191
|
+
const modePrompt = buildModePrompt(agentMode, context);
|
|
1192
|
+
if (modePrompt) {
|
|
1193
|
+
parts.push(modePrompt);
|
|
1194
|
+
}
|
|
1195
|
+
return parts.join("\n");
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/execution/prompt-builder.ts
|
|
1199
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["InProgress", "ReviewPR", "ReviewDev", "ReviewLive"]);
|
|
1200
|
+
function formatFileSize(bytes) {
|
|
1201
|
+
if (bytes === void 0) return "";
|
|
1202
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
1203
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
1204
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1205
|
+
}
|
|
1206
|
+
function findLastAgentMessageIndex2(history) {
|
|
1207
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
1208
|
+
if (history[i].role === "assistant") return i;
|
|
1209
|
+
}
|
|
1210
|
+
return -1;
|
|
1211
|
+
}
|
|
1212
|
+
function detectRelaunchScenario(context) {
|
|
1213
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1214
|
+
if (lastAgentIdx === -1) return "fresh";
|
|
1215
|
+
const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId || ACTIVE_STATUSES.has(context.status ?? "");
|
|
1216
|
+
if (!hasPriorWork) return "fresh";
|
|
1217
|
+
const messagesAfterAgent = context.chatHistory.slice(lastAgentIdx + 1);
|
|
1218
|
+
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
1219
|
+
return hasNewUserMessages ? "feedback_relaunch" : "idle_relaunch";
|
|
1220
|
+
}
|
|
1221
|
+
function buildRelaunchWithSession(mode, context) {
|
|
1222
|
+
const scenario = detectRelaunchScenario(context);
|
|
1223
|
+
if (!context.claudeSessionId || scenario === "fresh") return null;
|
|
1224
|
+
const parts = [];
|
|
1225
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1226
|
+
if (mode === "pm") {
|
|
1227
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1228
|
+
if (newMessages.length > 0) {
|
|
1229
|
+
parts.push(
|
|
1230
|
+
`You have been relaunched. Here are new messages since your last session:`,
|
|
1231
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`)
|
|
1232
|
+
);
|
|
1233
|
+
} else {
|
|
1234
|
+
parts.push(`You have been relaunched. No new messages since your last session.`);
|
|
1235
|
+
}
|
|
1236
|
+
parts.push(
|
|
1237
|
+
`
|
|
1238
|
+
You are the project manager for this task.`,
|
|
1239
|
+
`Review the context above and wait for the team to provide instructions before taking action.`
|
|
1240
|
+
);
|
|
1241
|
+
} else if (scenario === "feedback_relaunch") {
|
|
1242
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1243
|
+
parts.push(
|
|
1244
|
+
`You have been relaunched with new feedback.`,
|
|
1245
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1246
|
+
`
|
|
1247
|
+
New messages since your last run:`,
|
|
1248
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1249
|
+
`
|
|
1250
|
+
Address the requested changes. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 review the feedback and implement the changes directly.`,
|
|
1251
|
+
`Commit and push your updates.`
|
|
1252
|
+
);
|
|
1253
|
+
if (context.githubPRUrl) {
|
|
1254
|
+
parts.push(
|
|
1255
|
+
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch. Do NOT create a new PR.`
|
|
1256
|
+
);
|
|
1257
|
+
} else {
|
|
1258
|
+
parts.push(
|
|
1259
|
+
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI.`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
} else {
|
|
1263
|
+
parts.push(
|
|
1264
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1265
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1266
|
+
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
1267
|
+
`Review the current state of the codebase and verify everything is working correctly.`,
|
|
1268
|
+
`Reply with a brief status update (visible in chat), then wait for further instructions.`
|
|
1269
|
+
);
|
|
1270
|
+
if (context.githubPRUrl) {
|
|
1271
|
+
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return parts.join("\n");
|
|
1275
|
+
}
|
|
1276
|
+
function formatChatFile(file) {
|
|
1277
|
+
const sizeStr = file.fileSize ? `, ${formatFileSize(file.fileSize)}` : "";
|
|
1278
|
+
if (file.content && file.contentEncoding === "utf-8") {
|
|
1279
|
+
return [
|
|
1280
|
+
`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`,
|
|
1281
|
+
"```",
|
|
1282
|
+
file.content,
|
|
1283
|
+
"```"
|
|
1284
|
+
];
|
|
1285
|
+
}
|
|
1286
|
+
if (!file.content) {
|
|
1287
|
+
return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]: ${file.downloadUrl}`];
|
|
1288
|
+
}
|
|
1289
|
+
if (file.content && file.contentEncoding === "base64") {
|
|
1290
|
+
return [
|
|
1291
|
+
`[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1292
|
+
];
|
|
1293
|
+
}
|
|
1294
|
+
return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`];
|
|
1295
|
+
}
|
|
1296
|
+
function formatTaskFile(file) {
|
|
1297
|
+
if (file.content && file.contentEncoding === "utf-8") {
|
|
1298
|
+
return [`
|
|
1299
|
+
### ${file.fileName} (${file.mimeType})`, "```", file.content, "```"];
|
|
1300
|
+
}
|
|
1301
|
+
if (file.content && file.contentEncoding === "base64") {
|
|
1302
|
+
const size = formatFileSize(file.fileSize);
|
|
1303
|
+
return [
|
|
1304
|
+
`- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1305
|
+
];
|
|
1306
|
+
}
|
|
1307
|
+
if (!file.content) {
|
|
1308
|
+
return [`- **${file.fileName}** (${file.mimeType}): ${file.downloadUrl}`];
|
|
1309
|
+
}
|
|
1310
|
+
return [];
|
|
1311
|
+
}
|
|
1312
|
+
function formatChatHistory(chatHistory) {
|
|
1313
|
+
const relevant = chatHistory.slice(-20);
|
|
1314
|
+
const parts = [`
|
|
1315
|
+
## Recent Chat Context`];
|
|
1316
|
+
for (const msg of relevant) {
|
|
1317
|
+
const sender = msg.userName ?? msg.role;
|
|
1318
|
+
parts.push(`[${sender}]: ${msg.content}`);
|
|
1319
|
+
if (msg.files?.length) {
|
|
1320
|
+
for (const file of msg.files) {
|
|
1321
|
+
parts.push(...formatChatFile(file));
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return parts;
|
|
1326
|
+
}
|
|
1327
|
+
function buildTaskBody(context) {
|
|
1328
|
+
const parts = [];
|
|
1329
|
+
parts.push(`# Task: ${context.title}`);
|
|
1330
|
+
if (context.description) {
|
|
1331
|
+
parts.push(`
|
|
1332
|
+
## Description
|
|
1333
|
+
${context.description}`);
|
|
1334
|
+
}
|
|
1335
|
+
if (context.plan) {
|
|
1336
|
+
parts.push(`
|
|
1337
|
+
## Plan
|
|
1338
|
+
${context.plan}`);
|
|
1339
|
+
}
|
|
1340
|
+
if (context.files && context.files.length > 0) {
|
|
1341
|
+
parts.push(`
|
|
1342
|
+
## Attached Files`);
|
|
1343
|
+
for (const file of context.files) {
|
|
1344
|
+
parts.push(...formatTaskFile(file));
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (context.repoRefs && context.repoRefs.length > 0) {
|
|
1348
|
+
parts.push(`
|
|
1349
|
+
## Repository References`);
|
|
1350
|
+
for (const ref of context.repoRefs) {
|
|
1351
|
+
const icon = ref.refType === "folder" ? "folder" : "file";
|
|
1352
|
+
parts.push(`- [${icon}] \`${ref.path}\``);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (context.chatHistory.length > 0) {
|
|
1356
|
+
parts.push(...formatChatHistory(context.chatHistory));
|
|
1357
|
+
}
|
|
1358
|
+
return parts;
|
|
1359
|
+
}
|
|
1360
|
+
function buildFreshInstructions(isPm, isAutoMode, context) {
|
|
1361
|
+
if (isAutoMode && isPm) {
|
|
1362
|
+
return [
|
|
1363
|
+
`You are operating autonomously. Begin planning immediately.`,
|
|
1364
|
+
`1. Explore the codebase to understand the architecture and relevant files`,
|
|
1365
|
+
`2. Draft a clear implementation plan and save it with update_task`,
|
|
1366
|
+
`3. Set story points (set_story_points), tags (set_task_tags), and title (set_task_title)`,
|
|
1367
|
+
`4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
|
|
1368
|
+
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
1369
|
+
];
|
|
1370
|
+
}
|
|
1371
|
+
if (isPm && context.isParentTask) {
|
|
1372
|
+
return [
|
|
1373
|
+
`You are the project manager for this task and its subtasks.`,
|
|
1374
|
+
`Use list_subtasks to review the current state of child tasks.`,
|
|
1375
|
+
`The task details are provided above. Wait for the team to provide instructions before taking action.`,
|
|
1376
|
+
`When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
|
|
1377
|
+
];
|
|
1378
|
+
}
|
|
1379
|
+
if (isPm) {
|
|
1380
|
+
return [
|
|
1381
|
+
`You are the project manager for this task.`,
|
|
1382
|
+
`The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
|
|
1383
|
+
`When you finish planning, save the plan with update_task and end your turn. Your reply summarizing the plan will be visible in chat. A separate task agent will execute the plan after review.`
|
|
1384
|
+
];
|
|
1385
|
+
}
|
|
1386
|
+
return [
|
|
1387
|
+
`Begin executing the task plan above immediately.`,
|
|
1388
|
+
`Your FIRST action should be reading the relevant source files mentioned in the plan, then writing code. Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`,
|
|
1389
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch for the entire task. Do not checkout or create other branches.`,
|
|
1390
|
+
`Your replies are visible to the team in chat \u2014 briefly describe what you're doing when you begin meaningful implementation, and again when the PR is ready.`,
|
|
1391
|
+
`When finished, commit your changes, then run \`git fetch origin ${context.githubBranch}\` and \`git push origin ${context.githubBranch}\` (use --force-with-lease if push fails). Then use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
1392
|
+
];
|
|
1393
|
+
}
|
|
1394
|
+
function buildFeedbackInstructions(context, isPm) {
|
|
1395
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1396
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1397
|
+
if (isPm) {
|
|
1398
|
+
return [
|
|
1399
|
+
`You were relaunched with new feedback since your last run.`,
|
|
1400
|
+
`You are the project manager for this task.`,
|
|
1401
|
+
`
|
|
1402
|
+
New messages since your last run:`,
|
|
1403
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1404
|
+
`
|
|
1405
|
+
Review these messages and wait for the team to provide instructions before taking action.`
|
|
1406
|
+
];
|
|
1407
|
+
}
|
|
1408
|
+
const parts = [
|
|
1409
|
+
`You have been relaunched to address feedback on your previous work.`,
|
|
1410
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1411
|
+
`Start by running \`git log --oneline -10\` and \`git diff HEAD~3 HEAD --stat\` to review what you already committed.`,
|
|
1412
|
+
`
|
|
1413
|
+
New messages since your last run:`,
|
|
1414
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1415
|
+
`
|
|
1416
|
+
Address the requested changes directly. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 go straight to implementing the feedback.`,
|
|
1417
|
+
`Commit and push your updates.`
|
|
1418
|
+
];
|
|
1419
|
+
if (context.githubPRUrl) {
|
|
1420
|
+
parts.push(
|
|
1421
|
+
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch to update it. Do NOT create a new PR.`
|
|
1422
|
+
);
|
|
1423
|
+
} else {
|
|
1424
|
+
parts.push(
|
|
1425
|
+
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
return parts;
|
|
1429
|
+
}
|
|
1430
|
+
function buildInstructions(mode, context, scenario, agentMode) {
|
|
1431
|
+
const parts = [`
|
|
1432
|
+
## Instructions`];
|
|
1433
|
+
const isPm = mode === "pm";
|
|
1434
|
+
if (scenario === "fresh") {
|
|
1435
|
+
parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context));
|
|
1436
|
+
return parts;
|
|
1437
|
+
}
|
|
1438
|
+
if (scenario === "idle_relaunch") {
|
|
1439
|
+
if (isPm) {
|
|
1440
|
+
parts.push(
|
|
1441
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1442
|
+
`You are the project manager for this task.`,
|
|
1443
|
+
`Wait for the team to provide instructions before taking action.`
|
|
1444
|
+
);
|
|
1445
|
+
} else {
|
|
1446
|
+
parts.push(
|
|
1447
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1448
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1449
|
+
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
|
|
1450
|
+
`Reply with a brief status update summarizing where things stand (visible in chat).`,
|
|
1451
|
+
`Then wait for further instructions \u2014 do NOT redo work that was already completed.`
|
|
1452
|
+
);
|
|
1453
|
+
if (context.githubPRUrl) {
|
|
1454
|
+
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return parts;
|
|
1458
|
+
}
|
|
1459
|
+
parts.push(...buildFeedbackInstructions(context, isPm));
|
|
1460
|
+
return parts;
|
|
1461
|
+
}
|
|
1462
|
+
function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
1463
|
+
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
1464
|
+
if (!isPackRunner) {
|
|
1465
|
+
const sessionRelaunch = buildRelaunchWithSession(mode, context);
|
|
1466
|
+
if (sessionRelaunch) return sessionRelaunch;
|
|
1542
1467
|
}
|
|
1543
|
-
|
|
1468
|
+
const scenario = detectRelaunchScenario(context);
|
|
1469
|
+
const body = buildTaskBody(context);
|
|
1470
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
1471
|
+
return [...body, ...instructions].join("\n");
|
|
1544
1472
|
}
|
|
1545
1473
|
|
|
1546
1474
|
// src/tools/index.ts
|
|
@@ -1552,212 +1480,218 @@ import { z } from "zod";
|
|
|
1552
1480
|
function isImageMimeType(mimeType) {
|
|
1553
1481
|
return mimeType.startsWith("image/");
|
|
1554
1482
|
}
|
|
1483
|
+
var cliEventFormatters = {
|
|
1484
|
+
thinking: (e) => e.message ?? "",
|
|
1485
|
+
tool_use: (e) => `${e.tool}: ${e.input?.slice(0, 1e3) ?? ""}`,
|
|
1486
|
+
tool_result: (e) => `${e.tool} \u2192 ${e.output?.slice(0, 500) ?? ""}${e.isError ? " [ERROR]" : ""}`,
|
|
1487
|
+
message: (e) => e.content ?? "",
|
|
1488
|
+
error: (e) => `ERROR: ${e.message ?? ""}`,
|
|
1489
|
+
completed: (e) => `Completed: ${e.summary ?? ""} (cost: $${e.costUsd ?? "?"}, duration: ${e.durationMs ?? "?"}ms)`,
|
|
1490
|
+
setup_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
|
|
1491
|
+
start_command_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
|
|
1492
|
+
turn_end: (e) => `Turn complete (${e.toolCalls?.length ?? 0} tool calls)`
|
|
1493
|
+
};
|
|
1555
1494
|
function formatCliEvent(e) {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
return e.message ?? "";
|
|
1559
|
-
case "tool_use": {
|
|
1560
|
-
const tu = e;
|
|
1561
|
-
return `${tu.tool}: ${tu.input?.slice(0, 1e3) ?? ""}`;
|
|
1562
|
-
}
|
|
1563
|
-
case "tool_result": {
|
|
1564
|
-
const tr = e;
|
|
1565
|
-
return `${tr.tool} \u2192 ${tr.output?.slice(0, 500) ?? ""}${tr.isError ? " [ERROR]" : ""}`;
|
|
1566
|
-
}
|
|
1567
|
-
case "message":
|
|
1568
|
-
return e.content ?? "";
|
|
1569
|
-
case "error":
|
|
1570
|
-
return `ERROR: ${e.message ?? ""}`;
|
|
1571
|
-
case "completed": {
|
|
1572
|
-
const c = e;
|
|
1573
|
-
return `Completed: ${c.summary ?? ""} (cost: $${c.costUsd ?? "?"}, duration: ${c.durationMs ?? "?"}ms)`;
|
|
1574
|
-
}
|
|
1575
|
-
case "setup_output":
|
|
1576
|
-
case "start_command_output": {
|
|
1577
|
-
const so = e;
|
|
1578
|
-
return `[${so.stream ?? "stdout"}] ${so.data ?? ""}`;
|
|
1579
|
-
}
|
|
1580
|
-
case "turn_end": {
|
|
1581
|
-
const te = e;
|
|
1582
|
-
return `Turn complete (${te.toolCalls?.length ?? 0} tool calls)`;
|
|
1583
|
-
}
|
|
1584
|
-
default:
|
|
1585
|
-
return JSON.stringify(e);
|
|
1586
|
-
}
|
|
1495
|
+
const formatter = cliEventFormatters[e.type];
|
|
1496
|
+
return formatter ? formatter(e) : JSON.stringify(e);
|
|
1587
1497
|
}
|
|
1588
|
-
function
|
|
1589
|
-
return
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
);
|
|
1607
|
-
}
|
|
1608
|
-
},
|
|
1609
|
-
{ annotations: { readOnlyHint: true } }
|
|
1610
|
-
),
|
|
1611
|
-
tool(
|
|
1612
|
-
"post_to_chat",
|
|
1613
|
-
"Post a message to a task chat. Your normal replies already appear in chat \u2014 only use this for explicit out-of-band updates or posting to a child task's chat.",
|
|
1614
|
-
{
|
|
1615
|
-
message: z.string().describe("The message to post to the team"),
|
|
1616
|
-
task_id: z.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
|
|
1617
|
-
},
|
|
1618
|
-
async ({ message, task_id }) => {
|
|
1619
|
-
try {
|
|
1620
|
-
if (task_id) {
|
|
1621
|
-
await connection.postChildChatMessage(task_id, message);
|
|
1622
|
-
return textResult(`Message posted to child task ${task_id} chat.`);
|
|
1623
|
-
}
|
|
1624
|
-
connection.postChatMessage(message);
|
|
1625
|
-
return textResult("Message posted to task chat.");
|
|
1626
|
-
} catch (error) {
|
|
1627
|
-
return textResult(
|
|
1628
|
-
`Failed to post message: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1629
|
-
);
|
|
1630
|
-
}
|
|
1498
|
+
function buildReadTaskChatTool(connection) {
|
|
1499
|
+
return tool(
|
|
1500
|
+
"read_task_chat",
|
|
1501
|
+
"Read recent messages from a task chat. Omit task_id to read the current task's chat, or provide a child task ID to read a child's chat.",
|
|
1502
|
+
{
|
|
1503
|
+
limit: z.number().optional().describe("Number of recent messages to fetch (default 20)"),
|
|
1504
|
+
task_id: z.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
|
|
1505
|
+
},
|
|
1506
|
+
async ({ limit, task_id }) => {
|
|
1507
|
+
try {
|
|
1508
|
+
const messages = await connection.fetchChatMessages(limit, task_id);
|
|
1509
|
+
return textResult(JSON.stringify(messages, null, 2));
|
|
1510
|
+
} catch {
|
|
1511
|
+
return textResult(
|
|
1512
|
+
JSON.stringify({
|
|
1513
|
+
note: "Could not fetch live chat. Chat history was provided in the initial context."
|
|
1514
|
+
})
|
|
1515
|
+
);
|
|
1631
1516
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
return textResult(
|
|
1650
|
-
`Failed to update status: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1651
|
-
);
|
|
1517
|
+
},
|
|
1518
|
+
{ annotations: { readOnlyHint: true } }
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
function buildPostToChatTool(connection) {
|
|
1522
|
+
return tool(
|
|
1523
|
+
"post_to_chat",
|
|
1524
|
+
"Post a message to a task chat. Your normal replies already appear in chat \u2014 only use this for explicit out-of-band updates or posting to a child task's chat.",
|
|
1525
|
+
{
|
|
1526
|
+
message: z.string().describe("The message to post to the team"),
|
|
1527
|
+
task_id: z.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
|
|
1528
|
+
},
|
|
1529
|
+
async ({ message, task_id }) => {
|
|
1530
|
+
try {
|
|
1531
|
+
if (task_id) {
|
|
1532
|
+
await connection.postChildChatMessage(task_id, message);
|
|
1533
|
+
return textResult(`Message posted to child task ${task_id} chat.`);
|
|
1652
1534
|
}
|
|
1535
|
+
connection.postChatMessage(message);
|
|
1536
|
+
return textResult("Message posted to task chat.");
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
return textResult(
|
|
1539
|
+
`Failed to post message: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1540
|
+
);
|
|
1653
1541
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
"get_task",
|
|
1671
|
-
"Look up a task by slug or ID to get its title, description, plan, and status",
|
|
1672
|
-
{
|
|
1673
|
-
slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
|
|
1674
|
-
},
|
|
1675
|
-
async ({ slug_or_id }) => {
|
|
1676
|
-
try {
|
|
1677
|
-
const task = await connection.fetchTask(slug_or_id);
|
|
1678
|
-
return textResult(JSON.stringify(task, null, 2));
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
return textResult(
|
|
1681
|
-
`Failed to get task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1682
|
-
);
|
|
1683
|
-
}
|
|
1684
|
-
},
|
|
1685
|
-
{ annotations: { readOnlyHint: true } }
|
|
1686
|
-
),
|
|
1687
|
-
tool(
|
|
1688
|
-
"get_task_cli",
|
|
1689
|
-
"Read CLI execution logs from a task. Returns agent reasoning, tool calls, setup output, and other execution events. Use 'source' to filter: 'agent' for agent reasoning/tool calls only, 'application' for setup/dev-server output only.",
|
|
1690
|
-
{
|
|
1691
|
-
task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
|
|
1692
|
-
source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
|
|
1693
|
-
limit: z.number().optional().describe("Max number of log entries to return (default 50, max 500).")
|
|
1694
|
-
},
|
|
1695
|
-
async ({ task_id, source, limit }) => {
|
|
1696
|
-
try {
|
|
1697
|
-
const effectiveLimit = Math.min(limit ?? 50, 500);
|
|
1698
|
-
const result = await connection.fetchCliHistory(task_id, effectiveLimit, source);
|
|
1699
|
-
const formatted = result.map((entry) => {
|
|
1700
|
-
const time = entry.time;
|
|
1701
|
-
const e = entry.event;
|
|
1702
|
-
return `[${time}] [${e.type}] ${formatCliEvent(e)}`;
|
|
1703
|
-
}).join("\n");
|
|
1704
|
-
return textResult(formatted || "No CLI logs found.");
|
|
1705
|
-
} catch (error) {
|
|
1706
|
-
return textResult(
|
|
1707
|
-
`Failed to fetch CLI logs: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1708
|
-
);
|
|
1709
|
-
}
|
|
1710
|
-
},
|
|
1711
|
-
{ annotations: { readOnlyHint: true } }
|
|
1712
|
-
),
|
|
1713
|
-
tool(
|
|
1714
|
-
"list_task_files",
|
|
1715
|
-
"List all files attached to this task with metadata (name, type, size) and download URLs",
|
|
1716
|
-
{},
|
|
1717
|
-
async () => {
|
|
1718
|
-
try {
|
|
1719
|
-
const files = await connection.fetchTaskFiles();
|
|
1720
|
-
const metadata = files.map(({ content: _c, ...rest }) => rest);
|
|
1721
|
-
const content = [
|
|
1722
|
-
{ type: "text", text: JSON.stringify(metadata, null, 2) }
|
|
1723
|
-
];
|
|
1724
|
-
for (const file of files) {
|
|
1725
|
-
if (file.content && file.contentEncoding === "base64" && isImageMimeType(file.mimeType)) {
|
|
1726
|
-
content.push(imageBlock(file.content, file.mimeType));
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
return { content };
|
|
1730
|
-
} catch {
|
|
1731
|
-
return textResult("Failed to list task files.");
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
function buildUpdateTaskStatusTool(connection) {
|
|
1546
|
+
return tool(
|
|
1547
|
+
"update_task_status",
|
|
1548
|
+
"Update a task's status on the Kanban board. Omit task_id to update the current task, or provide a child task ID to update a child's status.",
|
|
1549
|
+
{
|
|
1550
|
+
status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
|
|
1551
|
+
task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
1552
|
+
},
|
|
1553
|
+
async ({ status, task_id }) => {
|
|
1554
|
+
try {
|
|
1555
|
+
if (task_id) {
|
|
1556
|
+
await connection.updateChildStatus(task_id, status);
|
|
1557
|
+
return textResult(`Child task ${task_id} status updated to ${status}.`);
|
|
1732
1558
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1559
|
+
connection.updateStatus(status);
|
|
1560
|
+
return textResult(`Task status updated to ${status}.`);
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
return textResult(
|
|
1563
|
+
`Failed to update status: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
function buildGetTaskPlanTool(connection, config) {
|
|
1570
|
+
return tool(
|
|
1571
|
+
"get_task_plan",
|
|
1572
|
+
"Re-read the latest task plan in case it was updated",
|
|
1573
|
+
{},
|
|
1574
|
+
async () => {
|
|
1575
|
+
try {
|
|
1576
|
+
const ctx = await connection.fetchTaskContext();
|
|
1577
|
+
return textResult(ctx.plan ?? "No plan available.");
|
|
1578
|
+
} catch {
|
|
1579
|
+
return textResult(`Task ID: ${config.taskId} - could not fetch updated plan.`);
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
1582
|
+
{ annotations: { readOnlyHint: true } }
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
function buildGetTaskTool(connection) {
|
|
1586
|
+
return tool(
|
|
1587
|
+
"get_task",
|
|
1588
|
+
"Look up a task by slug or ID to get its title, description, plan, and status",
|
|
1589
|
+
{
|
|
1590
|
+
slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
|
|
1591
|
+
},
|
|
1592
|
+
async ({ slug_or_id }) => {
|
|
1593
|
+
try {
|
|
1594
|
+
const task = await connection.fetchTask(slug_or_id);
|
|
1595
|
+
return textResult(JSON.stringify(task, null, 2));
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
return textResult(
|
|
1598
|
+
`Failed to get task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
{ annotations: { readOnlyHint: true } }
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
function buildGetTaskCliTool(connection) {
|
|
1606
|
+
return tool(
|
|
1607
|
+
"get_task_cli",
|
|
1608
|
+
"Read CLI execution logs from a task. Returns agent reasoning, tool calls, setup output, and other execution events. Use 'source' to filter: 'agent' for agent reasoning/tool calls only, 'application' for setup/dev-server output only.",
|
|
1609
|
+
{
|
|
1610
|
+
task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
|
|
1611
|
+
source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
|
|
1612
|
+
limit: z.number().optional().describe("Max number of log entries to return (default 50, max 500).")
|
|
1613
|
+
},
|
|
1614
|
+
async ({ task_id, source, limit }) => {
|
|
1615
|
+
try {
|
|
1616
|
+
const effectiveLimit = Math.min(limit ?? 50, 500);
|
|
1617
|
+
const result = await connection.fetchCliHistory(task_id, effectiveLimit, source);
|
|
1618
|
+
const formatted = result.map((entry) => {
|
|
1619
|
+
const time = entry.time;
|
|
1620
|
+
const e = entry.event;
|
|
1621
|
+
return `[${time}] [${e.type}] ${formatCliEvent(e)}`;
|
|
1622
|
+
}).join("\n");
|
|
1623
|
+
return textResult(formatted || "No CLI logs found.");
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
return textResult(
|
|
1626
|
+
`Failed to fetch CLI logs: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
{ annotations: { readOnlyHint: true } }
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
function buildListTaskFilesTool(connection) {
|
|
1634
|
+
return tool(
|
|
1635
|
+
"list_task_files",
|
|
1636
|
+
"List all files attached to this task with metadata (name, type, size) and download URLs",
|
|
1637
|
+
{},
|
|
1638
|
+
async () => {
|
|
1639
|
+
try {
|
|
1640
|
+
const files = await connection.fetchTaskFiles();
|
|
1641
|
+
const metadata = files.map(({ content: _c, ...rest }) => rest);
|
|
1642
|
+
const content = [
|
|
1643
|
+
{ type: "text", text: JSON.stringify(metadata, null, 2) }
|
|
1644
|
+
];
|
|
1645
|
+
for (const file of files) {
|
|
1646
|
+
if (file.content && file.contentEncoding === "base64" && isImageMimeType(file.mimeType)) {
|
|
1647
|
+
content.push(imageBlock(file.content, file.mimeType));
|
|
1751
1648
|
}
|
|
1752
|
-
return { content };
|
|
1753
|
-
} catch (error) {
|
|
1754
|
-
return textResult(
|
|
1755
|
-
`Failed to get task file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1756
|
-
);
|
|
1757
1649
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1650
|
+
return { content };
|
|
1651
|
+
} catch {
|
|
1652
|
+
return textResult("Failed to list task files.");
|
|
1653
|
+
}
|
|
1654
|
+
},
|
|
1655
|
+
{ annotations: { readOnlyHint: true } }
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
function buildGetTaskFileTool(connection) {
|
|
1659
|
+
return tool(
|
|
1660
|
+
"get_task_file",
|
|
1661
|
+
"Get a specific task file's content and download URL by file ID",
|
|
1662
|
+
{ fileId: z.string().describe("The file ID to retrieve") },
|
|
1663
|
+
async ({ fileId }) => {
|
|
1664
|
+
try {
|
|
1665
|
+
const file = await connection.fetchTaskFile(fileId);
|
|
1666
|
+
const { content: rawContent, ...metadata } = file;
|
|
1667
|
+
const content = [
|
|
1668
|
+
{ type: "text", text: JSON.stringify(metadata, null, 2) }
|
|
1669
|
+
];
|
|
1670
|
+
if (rawContent && file.contentEncoding === "base64" && isImageMimeType(file.mimeType)) {
|
|
1671
|
+
content.push(imageBlock(rawContent, file.mimeType));
|
|
1672
|
+
} else if (rawContent) {
|
|
1673
|
+
content[0] = { type: "text", text: JSON.stringify(file, null, 2) };
|
|
1674
|
+
}
|
|
1675
|
+
return { content };
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
return textResult(
|
|
1678
|
+
`Failed to get task file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
{ annotations: { readOnlyHint: true } }
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
function buildCommonTools(connection, config) {
|
|
1686
|
+
return [
|
|
1687
|
+
buildReadTaskChatTool(connection),
|
|
1688
|
+
buildPostToChatTool(connection),
|
|
1689
|
+
buildUpdateTaskStatusTool(connection),
|
|
1690
|
+
buildGetTaskPlanTool(connection, config),
|
|
1691
|
+
buildGetTaskTool(connection),
|
|
1692
|
+
buildGetTaskCliTool(connection),
|
|
1693
|
+
buildListTaskFilesTool(connection),
|
|
1694
|
+
buildGetTaskFileTool(connection)
|
|
1761
1695
|
];
|
|
1762
1696
|
}
|
|
1763
1697
|
|
|
@@ -1771,26 +1705,8 @@ function buildStoryPointDescription(storyPoints) {
|
|
|
1771
1705
|
}
|
|
1772
1706
|
return "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
|
|
1773
1707
|
}
|
|
1774
|
-
function
|
|
1775
|
-
|
|
1776
|
-
const includePackTools = options?.includePackTools ?? false;
|
|
1777
|
-
const tools = [
|
|
1778
|
-
tool2(
|
|
1779
|
-
"update_task",
|
|
1780
|
-
"Save the finalized task plan and/or description",
|
|
1781
|
-
{
|
|
1782
|
-
plan: z2.string().optional().describe("The task plan in markdown"),
|
|
1783
|
-
description: z2.string().optional().describe("Updated task description")
|
|
1784
|
-
},
|
|
1785
|
-
async ({ plan, description }) => {
|
|
1786
|
-
try {
|
|
1787
|
-
await Promise.resolve(connection.updateTaskFields({ plan, description }));
|
|
1788
|
-
return textResult("Task updated successfully.");
|
|
1789
|
-
} catch {
|
|
1790
|
-
return textResult("Failed to update task.");
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
),
|
|
1708
|
+
function buildSubtaskTools(connection, spDescription) {
|
|
1709
|
+
return [
|
|
1794
1710
|
tool2(
|
|
1795
1711
|
"create_subtask",
|
|
1796
1712
|
"Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
|
|
@@ -1860,9 +1776,33 @@ function buildPmTools(connection, storyPoints, options) {
|
|
|
1860
1776
|
{ annotations: { readOnlyHint: true } }
|
|
1861
1777
|
)
|
|
1862
1778
|
];
|
|
1863
|
-
|
|
1779
|
+
}
|
|
1780
|
+
function buildPmTools(connection, storyPoints, options) {
|
|
1781
|
+
const spDescription = buildStoryPointDescription(storyPoints);
|
|
1782
|
+
const tools = [
|
|
1783
|
+
tool2(
|
|
1784
|
+
"update_task",
|
|
1785
|
+
"Save the finalized task plan and/or description",
|
|
1786
|
+
{
|
|
1787
|
+
plan: z2.string().optional().describe("The task plan in markdown"),
|
|
1788
|
+
description: z2.string().optional().describe("Updated task description")
|
|
1789
|
+
},
|
|
1790
|
+
async ({ plan, description }) => {
|
|
1791
|
+
try {
|
|
1792
|
+
await Promise.resolve(connection.updateTaskFields({ plan, description }));
|
|
1793
|
+
return textResult("Task updated successfully.");
|
|
1794
|
+
} catch {
|
|
1795
|
+
return textResult("Failed to update task.");
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
),
|
|
1799
|
+
...buildSubtaskTools(connection, spDescription)
|
|
1800
|
+
];
|
|
1801
|
+
if (!options?.includePackTools) return tools;
|
|
1802
|
+
return [...tools, ...buildPackTools(connection)];
|
|
1803
|
+
}
|
|
1804
|
+
function buildPackTools(connection) {
|
|
1864
1805
|
return [
|
|
1865
|
-
...tools,
|
|
1866
1806
|
tool2(
|
|
1867
1807
|
"start_child_cloud_build",
|
|
1868
1808
|
"Start a cloud build for a child task. The child must be in Open status with story points and an agent assigned.",
|
|
@@ -1948,10 +1888,67 @@ function buildTaskTools(connection) {
|
|
|
1948
1888
|
)
|
|
1949
1889
|
];
|
|
1950
1890
|
}
|
|
1951
|
-
|
|
1952
|
-
// src/tools/discovery-tools.ts
|
|
1953
|
-
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
1954
|
-
import { z as z4 } from "zod";
|
|
1891
|
+
|
|
1892
|
+
// src/tools/discovery-tools.ts
|
|
1893
|
+
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
1894
|
+
import { z as z4 } from "zod";
|
|
1895
|
+
function buildIconTools(connection) {
|
|
1896
|
+
return [
|
|
1897
|
+
tool4(
|
|
1898
|
+
"list_icons",
|
|
1899
|
+
"List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before set_task_icon to check for existing matches.",
|
|
1900
|
+
{},
|
|
1901
|
+
async () => {
|
|
1902
|
+
try {
|
|
1903
|
+
const icons = await connection.listIcons();
|
|
1904
|
+
return textResult(JSON.stringify(icons, null, 2));
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
return textResult(
|
|
1907
|
+
`Failed to list icons: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
},
|
|
1911
|
+
{ annotations: { readOnlyHint: true } }
|
|
1912
|
+
),
|
|
1913
|
+
tool4(
|
|
1914
|
+
"set_task_icon",
|
|
1915
|
+
"Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
|
|
1916
|
+
{
|
|
1917
|
+
iconId: z4.string().describe("The icon ID to assign")
|
|
1918
|
+
},
|
|
1919
|
+
async ({ iconId }) => {
|
|
1920
|
+
try {
|
|
1921
|
+
await Promise.resolve(connection.updateTaskProperties({ iconId }));
|
|
1922
|
+
return textResult("Icon assigned to task.");
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
return textResult(
|
|
1925
|
+
`Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
),
|
|
1930
|
+
tool4(
|
|
1931
|
+
"generate_task_icon",
|
|
1932
|
+
"Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
|
|
1933
|
+
{
|
|
1934
|
+
prompt: z4.string().describe(
|
|
1935
|
+
"Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
|
|
1936
|
+
),
|
|
1937
|
+
aspectRatio: z4.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
|
|
1938
|
+
},
|
|
1939
|
+
async ({ prompt, aspectRatio }) => {
|
|
1940
|
+
try {
|
|
1941
|
+
const result = await connection.generateTaskIcon(prompt, aspectRatio ?? "square");
|
|
1942
|
+
return textResult(`Icon generated and assigned: ${result.iconId}`);
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
return textResult(
|
|
1945
|
+
`Failed to generate icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
)
|
|
1950
|
+
];
|
|
1951
|
+
}
|
|
1955
1952
|
function buildDiscoveryTools(connection, context) {
|
|
1956
1953
|
const spDescription = buildStoryPointDescription(context?.storyPoints);
|
|
1957
1954
|
return [
|
|
@@ -1961,7 +1958,7 @@ function buildDiscoveryTools(connection, context) {
|
|
|
1961
1958
|
{ value: z4.number().describe(spDescription) },
|
|
1962
1959
|
async ({ value }) => {
|
|
1963
1960
|
try {
|
|
1964
|
-
connection.updateTaskProperties({ storyPointValue: value });
|
|
1961
|
+
await Promise.resolve(connection.updateTaskProperties({ storyPointValue: value }));
|
|
1965
1962
|
return textResult(`Story points set to ${value}`);
|
|
1966
1963
|
} catch (error) {
|
|
1967
1964
|
return textResult(
|
|
@@ -1978,7 +1975,7 @@ function buildDiscoveryTools(connection, context) {
|
|
|
1978
1975
|
},
|
|
1979
1976
|
async ({ tagIds }) => {
|
|
1980
1977
|
try {
|
|
1981
|
-
connection.updateTaskProperties({ tagIds });
|
|
1978
|
+
await Promise.resolve(connection.updateTaskProperties({ tagIds }));
|
|
1982
1979
|
return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
|
|
1983
1980
|
} catch (error) {
|
|
1984
1981
|
return textResult(
|
|
@@ -1995,7 +1992,7 @@ function buildDiscoveryTools(connection, context) {
|
|
|
1995
1992
|
},
|
|
1996
1993
|
async ({ title }) => {
|
|
1997
1994
|
try {
|
|
1998
|
-
connection.updateTaskProperties({ title });
|
|
1995
|
+
await Promise.resolve(connection.updateTaskProperties({ title }));
|
|
1999
1996
|
return textResult(`Task title updated to: ${title}`);
|
|
2000
1997
|
} catch (error) {
|
|
2001
1998
|
return textResult(
|
|
@@ -2004,59 +2001,7 @@ function buildDiscoveryTools(connection, context) {
|
|
|
2004
2001
|
}
|
|
2005
2002
|
}
|
|
2006
2003
|
),
|
|
2007
|
-
|
|
2008
|
-
"list_icons",
|
|
2009
|
-
"List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before set_task_icon to check for existing matches.",
|
|
2010
|
-
{},
|
|
2011
|
-
async () => {
|
|
2012
|
-
try {
|
|
2013
|
-
const icons = await connection.listIcons();
|
|
2014
|
-
return textResult(JSON.stringify(icons, null, 2));
|
|
2015
|
-
} catch (error) {
|
|
2016
|
-
return textResult(
|
|
2017
|
-
`Failed to list icons: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2018
|
-
);
|
|
2019
|
-
}
|
|
2020
|
-
},
|
|
2021
|
-
{ annotations: { readOnlyHint: true } }
|
|
2022
|
-
),
|
|
2023
|
-
tool4(
|
|
2024
|
-
"set_task_icon",
|
|
2025
|
-
"Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
|
|
2026
|
-
{
|
|
2027
|
-
iconId: z4.string().describe("The icon ID to assign")
|
|
2028
|
-
},
|
|
2029
|
-
async ({ iconId }) => {
|
|
2030
|
-
try {
|
|
2031
|
-
connection.updateTaskProperties({ iconId });
|
|
2032
|
-
return textResult("Icon assigned to task.");
|
|
2033
|
-
} catch (error) {
|
|
2034
|
-
return textResult(
|
|
2035
|
-
`Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2036
|
-
);
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
),
|
|
2040
|
-
tool4(
|
|
2041
|
-
"generate_task_icon",
|
|
2042
|
-
"Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
|
|
2043
|
-
{
|
|
2044
|
-
prompt: z4.string().describe(
|
|
2045
|
-
"Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
|
|
2046
|
-
),
|
|
2047
|
-
aspectRatio: z4.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
|
|
2048
|
-
},
|
|
2049
|
-
async ({ prompt, aspectRatio }) => {
|
|
2050
|
-
try {
|
|
2051
|
-
const result = await connection.generateTaskIcon(prompt, aspectRatio ?? "square");
|
|
2052
|
-
return textResult(`Icon generated and assigned: ${result.iconId}`);
|
|
2053
|
-
} catch (error) {
|
|
2054
|
-
return textResult(
|
|
2055
|
-
`Failed to generate icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
)
|
|
2004
|
+
...buildIconTools(connection)
|
|
2060
2005
|
];
|
|
2061
2006
|
}
|
|
2062
2007
|
|
|
@@ -2067,37 +2012,28 @@ function textResult(text) {
|
|
|
2067
2012
|
function imageBlock(data, mimeType) {
|
|
2068
2013
|
return { type: "image", data, mimeType };
|
|
2069
2014
|
}
|
|
2070
|
-
function
|
|
2071
|
-
const commonTools = buildCommonTools(connection, config);
|
|
2072
|
-
const agentMode = context?.agentMode;
|
|
2073
|
-
let modeTools;
|
|
2015
|
+
function getModeTools(agentMode, connection, config, context) {
|
|
2074
2016
|
switch (agentMode) {
|
|
2075
2017
|
case "building":
|
|
2076
|
-
|
|
2018
|
+
return context?.isParentTask ? [
|
|
2077
2019
|
...buildTaskTools(connection),
|
|
2078
2020
|
...buildPmTools(connection, context?.storyPoints, { includePackTools: true })
|
|
2079
2021
|
] : buildTaskTools(connection);
|
|
2080
|
-
break;
|
|
2081
2022
|
case "review":
|
|
2082
|
-
modeTools = buildPmTools(connection, context?.storyPoints, {
|
|
2083
|
-
includePackTools: !!context?.isParentTask
|
|
2084
|
-
});
|
|
2085
|
-
break;
|
|
2086
2023
|
case "auto":
|
|
2087
|
-
modeTools = buildPmTools(connection, context?.storyPoints, {
|
|
2088
|
-
includePackTools: !!context?.isParentTask
|
|
2089
|
-
});
|
|
2090
|
-
break;
|
|
2091
2024
|
case "discovery":
|
|
2092
2025
|
case "help":
|
|
2093
|
-
|
|
2026
|
+
return buildPmTools(connection, context?.storyPoints, {
|
|
2094
2027
|
includePackTools: !!context?.isParentTask
|
|
2095
2028
|
});
|
|
2096
|
-
break;
|
|
2097
2029
|
default:
|
|
2098
|
-
|
|
2099
|
-
break;
|
|
2030
|
+
return config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : buildTaskTools(connection);
|
|
2100
2031
|
}
|
|
2032
|
+
}
|
|
2033
|
+
function createConveyorMcpServer(connection, config, context) {
|
|
2034
|
+
const commonTools = buildCommonTools(connection, config);
|
|
2035
|
+
const agentMode = context?.agentMode ?? void 0;
|
|
2036
|
+
const modeTools = getModeTools(agentMode, connection, config, context);
|
|
2101
2037
|
const discoveryTools = agentMode === "discovery" || agentMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
2102
2038
|
return createSdkMcpServer({
|
|
2103
2039
|
name: "conveyor",
|
|
@@ -2105,16 +2041,17 @@ function createConveyorMcpServer(connection, config, context) {
|
|
|
2105
2041
|
});
|
|
2106
2042
|
}
|
|
2107
2043
|
|
|
2108
|
-
// src/execution/
|
|
2109
|
-
|
|
2110
|
-
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
2111
|
-
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2044
|
+
// src/execution/tool-access.ts
|
|
2045
|
+
import { randomUUID } from "crypto";
|
|
2112
2046
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
2113
2047
|
var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
|
|
2048
|
+
function isPlanFile(input) {
|
|
2049
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
2050
|
+
return filePath.includes(".claude/plans/");
|
|
2051
|
+
}
|
|
2114
2052
|
function handleDiscoveryToolAccess(toolName, input) {
|
|
2115
2053
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2116
|
-
|
|
2117
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2054
|
+
if (isPlanFile(input)) {
|
|
2118
2055
|
return { behavior: "allow", updatedInput: input };
|
|
2119
2056
|
}
|
|
2120
2057
|
return {
|
|
@@ -2141,8 +2078,7 @@ function handleReviewToolAccess(toolName, input, isParentTask) {
|
|
|
2141
2078
|
return handleBuildingToolAccess(toolName, input);
|
|
2142
2079
|
}
|
|
2143
2080
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2144
|
-
|
|
2145
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2081
|
+
if (isPlanFile(input)) {
|
|
2146
2082
|
return { behavior: "allow", updatedInput: input };
|
|
2147
2083
|
}
|
|
2148
2084
|
return {
|
|
@@ -2163,8 +2099,7 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
|
|
|
2163
2099
|
return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
|
|
2164
2100
|
}
|
|
2165
2101
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2166
|
-
|
|
2167
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2102
|
+
if (isPlanFile(input)) {
|
|
2168
2103
|
return { behavior: "allow", updatedInput: input };
|
|
2169
2104
|
}
|
|
2170
2105
|
return {
|
|
@@ -2174,65 +2109,71 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
|
|
|
2174
2109
|
}
|
|
2175
2110
|
return { behavior: "allow", updatedInput: input };
|
|
2176
2111
|
}
|
|
2177
|
-
function
|
|
2112
|
+
async function handleExitPlanMode(host, input) {
|
|
2113
|
+
try {
|
|
2114
|
+
const taskProps = await host.connection.getTaskProperties();
|
|
2115
|
+
const missingProps = [];
|
|
2116
|
+
if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
|
|
2117
|
+
if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
|
|
2118
|
+
if (!taskProps.title || taskProps.title === "Untitled")
|
|
2119
|
+
missingProps.push("title (use set_task_title)");
|
|
2120
|
+
if (missingProps.length > 0) {
|
|
2121
|
+
return {
|
|
2122
|
+
behavior: "deny",
|
|
2123
|
+
message: [
|
|
2124
|
+
"Cannot exit plan mode yet. Required task properties are missing:",
|
|
2125
|
+
...missingProps.map((p) => `- ${p}`),
|
|
2126
|
+
"",
|
|
2127
|
+
"Fill these in using MCP tools, then try ExitPlanMode again."
|
|
2128
|
+
].join("\n")
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
await host.connection.triggerIdentification();
|
|
2132
|
+
host.hasExitedPlanMode = true;
|
|
2133
|
+
const newMode = host.isParentTask ? "review" : "building";
|
|
2134
|
+
host.pendingModeRestart = true;
|
|
2135
|
+
if (host.onModeTransition) {
|
|
2136
|
+
host.onModeTransition(newMode);
|
|
2137
|
+
}
|
|
2138
|
+
return { behavior: "allow", updatedInput: input };
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
return {
|
|
2141
|
+
behavior: "deny",
|
|
2142
|
+
message: `Identification failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
async function handleAskUserQuestion(host, input) {
|
|
2178
2147
|
const QUESTION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2148
|
+
const questions = input.questions;
|
|
2149
|
+
const requestId = randomUUID();
|
|
2150
|
+
host.connection.emitStatus("waiting_for_input");
|
|
2151
|
+
host.connection.sendEvent({
|
|
2152
|
+
type: "tool_use",
|
|
2153
|
+
tool: "AskUserQuestion",
|
|
2154
|
+
input: JSON.stringify(input)
|
|
2155
|
+
});
|
|
2156
|
+
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
2157
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
2158
|
+
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
2159
|
+
});
|
|
2160
|
+
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
2161
|
+
host.connection.emitStatus("running");
|
|
2162
|
+
if (!answers) {
|
|
2163
|
+
return {
|
|
2164
|
+
behavior: "deny",
|
|
2165
|
+
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2169
|
+
}
|
|
2170
|
+
function buildCanUseTool(host) {
|
|
2179
2171
|
return async (toolName, input) => {
|
|
2180
2172
|
if (toolName === "ExitPlanMode" && host.agentMode === "auto" && !host.hasExitedPlanMode) {
|
|
2181
|
-
|
|
2182
|
-
const taskProps = await host.connection.getTaskProperties();
|
|
2183
|
-
const missingProps = [];
|
|
2184
|
-
if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
|
|
2185
|
-
if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
|
|
2186
|
-
if (!taskProps.title || taskProps.title === "Untitled")
|
|
2187
|
-
missingProps.push("title (use set_task_title)");
|
|
2188
|
-
if (missingProps.length > 0) {
|
|
2189
|
-
return {
|
|
2190
|
-
behavior: "deny",
|
|
2191
|
-
message: [
|
|
2192
|
-
"Cannot exit plan mode yet. Required task properties are missing:",
|
|
2193
|
-
...missingProps.map((p) => `- ${p}`),
|
|
2194
|
-
"",
|
|
2195
|
-
"Fill these in using MCP tools, then try ExitPlanMode again."
|
|
2196
|
-
].join("\n")
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
await host.connection.triggerIdentification();
|
|
2200
|
-
host.hasExitedPlanMode = true;
|
|
2201
|
-
const newMode = host.isParentTask ? "review" : "building";
|
|
2202
|
-
host.pendingModeRestart = true;
|
|
2203
|
-
if (host.onModeTransition) {
|
|
2204
|
-
host.onModeTransition(newMode);
|
|
2205
|
-
}
|
|
2206
|
-
return { behavior: "allow", updatedInput: input };
|
|
2207
|
-
} catch (err) {
|
|
2208
|
-
return {
|
|
2209
|
-
behavior: "deny",
|
|
2210
|
-
message: `Identification failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2173
|
+
return await handleExitPlanMode(host, input);
|
|
2213
2174
|
}
|
|
2214
2175
|
if (toolName === "AskUserQuestion") {
|
|
2215
|
-
|
|
2216
|
-
const requestId = randomUUID();
|
|
2217
|
-
host.connection.emitStatus("waiting_for_input");
|
|
2218
|
-
host.connection.sendEvent({
|
|
2219
|
-
type: "tool_use",
|
|
2220
|
-
tool: "AskUserQuestion",
|
|
2221
|
-
input: JSON.stringify(input)
|
|
2222
|
-
});
|
|
2223
|
-
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
2224
|
-
const timeoutPromise = new Promise((resolve2) => {
|
|
2225
|
-
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
2226
|
-
});
|
|
2227
|
-
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
2228
|
-
host.connection.emitStatus("running");
|
|
2229
|
-
if (!answers) {
|
|
2230
|
-
return {
|
|
2231
|
-
behavior: "deny",
|
|
2232
|
-
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2176
|
+
return await handleAskUserQuestion(host, input);
|
|
2236
2177
|
}
|
|
2237
2178
|
switch (host.agentMode) {
|
|
2238
2179
|
case "discovery":
|
|
@@ -2248,24 +2189,13 @@ function buildCanUseTool(host) {
|
|
|
2248
2189
|
}
|
|
2249
2190
|
};
|
|
2250
2191
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
context,
|
|
2259
|
-
host.config,
|
|
2260
|
-
host.setupLog,
|
|
2261
|
-
mode
|
|
2262
|
-
);
|
|
2263
|
-
const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
|
|
2264
|
-
const isReadOnlyMode = mode === "discovery" || mode === "help" || mode === "auto" && !host.hasExitedPlanMode;
|
|
2265
|
-
const modeDisallowedTools = isReadOnlyMode ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
2266
|
-
const disallowedTools = [...settings.disallowedTools ?? [], ...modeDisallowedTools];
|
|
2267
|
-
const settingSources = settings.settingSources ?? ["user", "project"];
|
|
2268
|
-
const hooks = {
|
|
2192
|
+
|
|
2193
|
+
// src/execution/query-executor.ts
|
|
2194
|
+
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
2195
|
+
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
2196
|
+
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2197
|
+
function buildHooks(host) {
|
|
2198
|
+
return {
|
|
2269
2199
|
PostToolUse: [
|
|
2270
2200
|
{
|
|
2271
2201
|
hooks: [
|
|
@@ -2279,13 +2209,56 @@ function buildQueryOptions(host, context) {
|
|
|
2279
2209
|
isError: false
|
|
2280
2210
|
});
|
|
2281
2211
|
}
|
|
2282
|
-
return { continue: true };
|
|
2212
|
+
return await Promise.resolve({ continue: true });
|
|
2283
2213
|
}
|
|
2284
2214
|
],
|
|
2285
2215
|
timeout: 5
|
|
2286
2216
|
}
|
|
2287
2217
|
]
|
|
2288
2218
|
};
|
|
2219
|
+
}
|
|
2220
|
+
function buildSandboxConfig(host) {
|
|
2221
|
+
const apiHostname = new URL(host.config.conveyorApiUrl).hostname;
|
|
2222
|
+
return {
|
|
2223
|
+
enabled: true,
|
|
2224
|
+
autoAllowBashIfSandboxed: true,
|
|
2225
|
+
allowUnsandboxedCommands: false,
|
|
2226
|
+
filesystem: {
|
|
2227
|
+
allowWrite: [`${host.config.workspaceDir}/**`],
|
|
2228
|
+
denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
|
|
2229
|
+
denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
|
|
2230
|
+
},
|
|
2231
|
+
network: {
|
|
2232
|
+
allowedDomains: [apiHostname, "api.anthropic.com"],
|
|
2233
|
+
allowManagedDomainsOnly: true,
|
|
2234
|
+
allowLocalBinding: true
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function isActiveBuildMode(mode, hasExitedPlanMode) {
|
|
2239
|
+
return mode === "building" || mode === "review" || mode === "auto" && hasExitedPlanMode;
|
|
2240
|
+
}
|
|
2241
|
+
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
2242
|
+
return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
|
|
2243
|
+
}
|
|
2244
|
+
function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
|
|
2245
|
+
const modeDisallowed = isReadOnlyMode(mode, hasExitedPlanMode) ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
2246
|
+
const configured = settings.disallowedTools ?? [];
|
|
2247
|
+
const combined = [...configured, ...modeDisallowed];
|
|
2248
|
+
return combined.length > 0 ? combined : void 0;
|
|
2249
|
+
}
|
|
2250
|
+
function buildQueryOptions(host, context) {
|
|
2251
|
+
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
2252
|
+
const mode = host.agentMode;
|
|
2253
|
+
const shouldSandbox = host.config.mode === "pm" && isActiveBuildMode(mode, host.hasExitedPlanMode) && context.useSandbox !== false;
|
|
2254
|
+
const systemPromptText = buildSystemPrompt(
|
|
2255
|
+
host.config.mode,
|
|
2256
|
+
context,
|
|
2257
|
+
host.config,
|
|
2258
|
+
host.setupLog,
|
|
2259
|
+
mode
|
|
2260
|
+
);
|
|
2261
|
+
const settingSources = settings.settingSources ?? ["user", "project"];
|
|
2289
2262
|
const baseOptions = {
|
|
2290
2263
|
model: context.model || host.config.model,
|
|
2291
2264
|
systemPrompt: {
|
|
@@ -2299,33 +2272,18 @@ function buildQueryOptions(host, context) {
|
|
|
2299
2272
|
allowDangerouslySkipPermissions: !shouldSandbox,
|
|
2300
2273
|
canUseTool: buildCanUseTool(host),
|
|
2301
2274
|
tools: { type: "preset", preset: "claude_code" },
|
|
2302
|
-
mcpServers: { conveyor:
|
|
2303
|
-
hooks,
|
|
2275
|
+
mcpServers: { conveyor: createConveyorMcpServer(host.connection, host.config, context) },
|
|
2276
|
+
hooks: buildHooks(host),
|
|
2304
2277
|
maxTurns: settings.maxTurns,
|
|
2305
2278
|
effort: settings.effort,
|
|
2306
2279
|
thinking: settings.thinking,
|
|
2307
2280
|
betas: settings.betas,
|
|
2308
2281
|
maxBudgetUsd: settings.maxBudgetUsd ?? 50,
|
|
2309
|
-
disallowedTools:
|
|
2282
|
+
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
2310
2283
|
enableFileCheckpointing: settings.enableFileCheckpointing
|
|
2311
2284
|
};
|
|
2312
2285
|
if (shouldSandbox) {
|
|
2313
|
-
|
|
2314
|
-
baseOptions.sandbox = {
|
|
2315
|
-
enabled: true,
|
|
2316
|
-
autoAllowBashIfSandboxed: true,
|
|
2317
|
-
allowUnsandboxedCommands: false,
|
|
2318
|
-
filesystem: {
|
|
2319
|
-
allowWrite: [`${host.config.workspaceDir}/**`],
|
|
2320
|
-
denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
|
|
2321
|
-
denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
|
|
2322
|
-
},
|
|
2323
|
-
network: {
|
|
2324
|
-
allowedDomains: [apiHostname, "api.anthropic.com"],
|
|
2325
|
-
allowManagedDomainsOnly: true,
|
|
2326
|
-
allowLocalBinding: true
|
|
2327
|
-
}
|
|
2328
|
-
};
|
|
2286
|
+
baseOptions.sandbox = buildSandboxConfig(host);
|
|
2329
2287
|
}
|
|
2330
2288
|
return baseOptions;
|
|
2331
2289
|
}
|
|
@@ -2350,34 +2308,47 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
2350
2308
|
source: {
|
|
2351
2309
|
type: "base64",
|
|
2352
2310
|
media_type: file.mimeType,
|
|
2353
|
-
data: file.content
|
|
2311
|
+
data: file.content ?? ""
|
|
2354
2312
|
}
|
|
2355
2313
|
});
|
|
2356
|
-
blocks.push({
|
|
2357
|
-
type: "text",
|
|
2358
|
-
text: `[Attached image: ${file.fileName} (${file.mimeType})]`
|
|
2359
|
-
});
|
|
2314
|
+
blocks.push({ type: "text", text: `[Attached image: ${file.fileName} (${file.mimeType})]` });
|
|
2360
2315
|
}
|
|
2361
2316
|
for (const file of chatImages) {
|
|
2362
2317
|
blocks.push({
|
|
2363
2318
|
type: "image",
|
|
2364
|
-
source: {
|
|
2365
|
-
type: "base64",
|
|
2366
|
-
media_type: file.mimeType,
|
|
2367
|
-
data: file.content
|
|
2368
|
-
}
|
|
2369
|
-
});
|
|
2370
|
-
blocks.push({
|
|
2371
|
-
type: "text",
|
|
2372
|
-
text: `[Chat image: ${file.fileName} (${file.mimeType})]`
|
|
2319
|
+
source: { type: "base64", media_type: file.mimeType, data: file.content }
|
|
2373
2320
|
});
|
|
2321
|
+
blocks.push({ type: "text", text: `[Chat image: ${file.fileName} (${file.mimeType})]` });
|
|
2374
2322
|
}
|
|
2375
2323
|
return blocks;
|
|
2376
2324
|
}
|
|
2325
|
+
function buildFollowUpPrompt(host, context, followUpContent) {
|
|
2326
|
+
const isPmMode = host.config.mode === "pm";
|
|
2327
|
+
const followUpText = typeof followUpContent === "string" ? followUpContent : followUpContent.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
2328
|
+
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
2329
|
+
(b) => b.type === "image"
|
|
2330
|
+
);
|
|
2331
|
+
const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode)}
|
|
2332
|
+
|
|
2333
|
+
---
|
|
2334
|
+
|
|
2335
|
+
The team says:
|
|
2336
|
+
${followUpText}` : followUpText;
|
|
2337
|
+
if (isPmMode) {
|
|
2338
|
+
const prompt = buildMultimodalPrompt(textPrompt, context);
|
|
2339
|
+
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
2340
|
+
prompt.push(...followUpImages);
|
|
2341
|
+
}
|
|
2342
|
+
return prompt;
|
|
2343
|
+
}
|
|
2344
|
+
if (followUpImages.length > 0) {
|
|
2345
|
+
return [{ type: "text", text: textPrompt }, ...followUpImages];
|
|
2346
|
+
}
|
|
2347
|
+
return textPrompt;
|
|
2348
|
+
}
|
|
2377
2349
|
async function runSdkQuery(host, context, followUpContent) {
|
|
2378
2350
|
if (host.isStopped()) return;
|
|
2379
2351
|
const mode = host.agentMode;
|
|
2380
|
-
const isPmMode = host.config.mode === "pm";
|
|
2381
2352
|
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
2382
2353
|
if (isDiscoveryLike) {
|
|
2383
2354
|
host.snapshotPlanFiles();
|
|
@@ -2385,27 +2356,7 @@ async function runSdkQuery(host, context, followUpContent) {
|
|
|
2385
2356
|
const options = buildQueryOptions(host, context);
|
|
2386
2357
|
const resume = context.claudeSessionId ?? void 0;
|
|
2387
2358
|
if (followUpContent) {
|
|
2388
|
-
const
|
|
2389
|
-
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
2390
|
-
(b) => b.type === "image"
|
|
2391
|
-
);
|
|
2392
|
-
const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode)}
|
|
2393
|
-
|
|
2394
|
-
---
|
|
2395
|
-
|
|
2396
|
-
The team says:
|
|
2397
|
-
${followUpText}` : followUpText;
|
|
2398
|
-
let prompt;
|
|
2399
|
-
if (isPmMode) {
|
|
2400
|
-
prompt = buildMultimodalPrompt(textPrompt, context);
|
|
2401
|
-
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
2402
|
-
prompt.push(...followUpImages);
|
|
2403
|
-
}
|
|
2404
|
-
} else if (followUpImages.length > 0) {
|
|
2405
|
-
prompt = [{ type: "text", text: textPrompt }, ...followUpImages];
|
|
2406
|
-
} else {
|
|
2407
|
-
prompt = textPrompt;
|
|
2408
|
-
}
|
|
2359
|
+
const prompt = buildFollowUpPrompt(host, context, followUpContent);
|
|
2409
2360
|
const agentQuery = query({
|
|
2410
2361
|
prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
|
|
2411
2362
|
options: { ...options, resume }
|
|
@@ -2426,55 +2377,94 @@ ${followUpText}` : followUpText;
|
|
|
2426
2377
|
host.syncPlanFile();
|
|
2427
2378
|
}
|
|
2428
2379
|
}
|
|
2380
|
+
function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
2381
|
+
if (lastErrorWasImage) {
|
|
2382
|
+
host.connection.postChatMessage(
|
|
2383
|
+
"An attached image could not be processed. Retrying without images..."
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
const retryPrompt = buildMultimodalPrompt(
|
|
2387
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2388
|
+
context,
|
|
2389
|
+
lastErrorWasImage
|
|
2390
|
+
);
|
|
2391
|
+
return query({
|
|
2392
|
+
prompt: host.createInputStream(retryPrompt),
|
|
2393
|
+
options: { ...options, resume: void 0 }
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
function handleStaleSession(context, host, options) {
|
|
2397
|
+
context.claudeSessionId = null;
|
|
2398
|
+
host.connection.storeSessionId("");
|
|
2399
|
+
const freshPrompt = buildMultimodalPrompt(
|
|
2400
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2401
|
+
context
|
|
2402
|
+
);
|
|
2403
|
+
const freshQuery = query({
|
|
2404
|
+
prompt: host.createInputStream(freshPrompt),
|
|
2405
|
+
options: { ...options, resume: void 0 }
|
|
2406
|
+
});
|
|
2407
|
+
return runWithRetry(freshQuery, context, host, options);
|
|
2408
|
+
}
|
|
2409
|
+
async function waitForRetryDelay(host, delayMs) {
|
|
2410
|
+
await new Promise((resolve2) => {
|
|
2411
|
+
const timer = setTimeout(resolve2, delayMs);
|
|
2412
|
+
const checkStopped = setInterval(() => {
|
|
2413
|
+
if (host.isStopped()) {
|
|
2414
|
+
clearTimeout(timer);
|
|
2415
|
+
clearInterval(checkStopped);
|
|
2416
|
+
resolve2();
|
|
2417
|
+
}
|
|
2418
|
+
}, 1e3);
|
|
2419
|
+
setTimeout(() => clearInterval(checkStopped), delayMs + 100);
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
function isStaleOrExitedSession(error, context) {
|
|
2423
|
+
if (!(error instanceof Error)) return false;
|
|
2424
|
+
if (error.message.includes("No conversation found with session ID")) return true;
|
|
2425
|
+
return !!context.claudeSessionId && error.message.includes("process exited");
|
|
2426
|
+
}
|
|
2427
|
+
function isRetriableError(error) {
|
|
2428
|
+
if (!(error instanceof Error)) return false;
|
|
2429
|
+
return API_ERROR_PATTERN2.test(error.message) || IMAGE_ERROR_PATTERN2.test(error.message);
|
|
2430
|
+
}
|
|
2431
|
+
function classifyImageError(error) {
|
|
2432
|
+
return error instanceof Error && IMAGE_ERROR_PATTERN2.test(error.message);
|
|
2433
|
+
}
|
|
2434
|
+
async function emitRetryStatus(host, attempt, delayMs) {
|
|
2435
|
+
const delayMin = Math.round(delayMs / 6e4);
|
|
2436
|
+
host.connection.postChatMessage(
|
|
2437
|
+
`API error encountered. Retrying in ${delayMin} minute${delayMin > 1 ? "s" : ""}... (attempt ${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2438
|
+
);
|
|
2439
|
+
host.connection.sendEvent({
|
|
2440
|
+
type: "error",
|
|
2441
|
+
message: `API error, retrying in ${delayMin}m (${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2442
|
+
});
|
|
2443
|
+
host.connection.emitStatus("waiting_for_input");
|
|
2444
|
+
await host.callbacks.onStatusChange("waiting_for_input");
|
|
2445
|
+
await waitForRetryDelay(host, delayMs);
|
|
2446
|
+
host.connection.emitStatus("running");
|
|
2447
|
+
await host.callbacks.onStatusChange("running");
|
|
2448
|
+
}
|
|
2429
2449
|
async function runWithRetry(initialQuery, context, host, options) {
|
|
2430
2450
|
let lastErrorWasImage = false;
|
|
2431
2451
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
2432
2452
|
if (host.isStopped()) return;
|
|
2433
|
-
const agentQuery = attempt === 0 ? initialQuery : (
|
|
2434
|
-
if (lastErrorWasImage) {
|
|
2435
|
-
host.connection.postChatMessage(
|
|
2436
|
-
"An attached image could not be processed. Retrying without images..."
|
|
2437
|
-
);
|
|
2438
|
-
}
|
|
2439
|
-
const retryPrompt = buildMultimodalPrompt(
|
|
2440
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2441
|
-
context,
|
|
2442
|
-
lastErrorWasImage
|
|
2443
|
-
);
|
|
2444
|
-
return query({
|
|
2445
|
-
prompt: host.createInputStream(retryPrompt),
|
|
2446
|
-
options: { ...options, resume: void 0 }
|
|
2447
|
-
});
|
|
2448
|
-
})();
|
|
2453
|
+
const agentQuery = attempt === 0 ? initialQuery : buildRetryQuery(host, context, options, lastErrorWasImage);
|
|
2449
2454
|
try {
|
|
2450
2455
|
const { retriable, resultSummary, modeRestart } = await processEvents(
|
|
2451
2456
|
agentQuery,
|
|
2452
2457
|
context,
|
|
2453
2458
|
host
|
|
2454
2459
|
);
|
|
2455
|
-
if (modeRestart) return;
|
|
2456
|
-
|
|
2457
|
-
lastErrorWasImage = IMAGE_ERROR_PATTERN.test(resultSummary ?? "");
|
|
2460
|
+
if (modeRestart || !retriable || host.isStopped()) return;
|
|
2461
|
+
lastErrorWasImage = IMAGE_ERROR_PATTERN2.test(resultSummary ?? "");
|
|
2458
2462
|
} catch (error) {
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
if ((isStaleSession || isResumeProcessExit) && context.claudeSessionId) {
|
|
2462
|
-
context.claudeSessionId = null;
|
|
2463
|
-
host.connection.storeSessionId("");
|
|
2464
|
-
const freshPrompt = buildMultimodalPrompt(
|
|
2465
|
-
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2466
|
-
context
|
|
2467
|
-
);
|
|
2468
|
-
const freshQuery = query({
|
|
2469
|
-
prompt: host.createInputStream(freshPrompt),
|
|
2470
|
-
options: { ...options, resume: void 0 }
|
|
2471
|
-
});
|
|
2472
|
-
return runWithRetry(freshQuery, context, host, options);
|
|
2463
|
+
if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
|
|
2464
|
+
return handleStaleSession(context, host, options);
|
|
2473
2465
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
if (isImageError) lastErrorWasImage = true;
|
|
2477
|
-
if (!isApiError && !isImageError) throw error;
|
|
2466
|
+
if (classifyImageError(error)) lastErrorWasImage = true;
|
|
2467
|
+
if (!isRetriableError(error)) throw error;
|
|
2478
2468
|
}
|
|
2479
2469
|
if (attempt >= RETRY_DELAYS_MS.length) {
|
|
2480
2470
|
host.connection.postChatMessage(
|
|
@@ -2482,30 +2472,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
2482
2472
|
);
|
|
2483
2473
|
return;
|
|
2484
2474
|
}
|
|
2485
|
-
|
|
2486
|
-
const delayMin = Math.round(delayMs / 6e4);
|
|
2487
|
-
host.connection.postChatMessage(
|
|
2488
|
-
`API error encountered. Retrying in ${delayMin} minute${delayMin > 1 ? "s" : ""}... (attempt ${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2489
|
-
);
|
|
2490
|
-
host.connection.sendEvent({
|
|
2491
|
-
type: "error",
|
|
2492
|
-
message: `API error, retrying in ${delayMin}m (${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2493
|
-
});
|
|
2494
|
-
host.connection.emitStatus("waiting_for_input");
|
|
2495
|
-
await host.callbacks.onStatusChange("waiting_for_input");
|
|
2496
|
-
await new Promise((resolve2) => {
|
|
2497
|
-
const timer = setTimeout(resolve2, delayMs);
|
|
2498
|
-
const checkStopped = setInterval(() => {
|
|
2499
|
-
if (host.isStopped()) {
|
|
2500
|
-
clearTimeout(timer);
|
|
2501
|
-
clearInterval(checkStopped);
|
|
2502
|
-
resolve2();
|
|
2503
|
-
}
|
|
2504
|
-
}, 1e3);
|
|
2505
|
-
setTimeout(() => clearInterval(checkStopped), delayMs + 100);
|
|
2506
|
-
});
|
|
2507
|
-
host.connection.emitStatus("running");
|
|
2508
|
-
await host.callbacks.onStatusChange("running");
|
|
2475
|
+
await emitRetryStatus(host, attempt, RETRY_DELAYS_MS[attempt]);
|
|
2509
2476
|
}
|
|
2510
2477
|
}
|
|
2511
2478
|
|
|
@@ -2582,19 +2549,7 @@ var PlanSync = class {
|
|
|
2582
2549
|
}
|
|
2583
2550
|
}
|
|
2584
2551
|
}
|
|
2585
|
-
|
|
2586
|
-
if (this.lockedPlanFile) {
|
|
2587
|
-
try {
|
|
2588
|
-
const content = readFileSync(this.lockedPlanFile, "utf-8").trim();
|
|
2589
|
-
if (content) {
|
|
2590
|
-
this.connection.updateTaskFields({ plan: content });
|
|
2591
|
-
const fileName = this.lockedPlanFile.split("/").pop();
|
|
2592
|
-
this.connection.postChatMessage(`Synced local plan file (${fileName}) to the task plan.`);
|
|
2593
|
-
}
|
|
2594
|
-
} catch {
|
|
2595
|
-
}
|
|
2596
|
-
return;
|
|
2597
|
-
}
|
|
2552
|
+
findNewestPlanFile() {
|
|
2598
2553
|
let newest = null;
|
|
2599
2554
|
for (const plansDir of this.getPlanDirs()) {
|
|
2600
2555
|
let files;
|
|
@@ -2617,24 +2572,245 @@ var PlanSync = class {
|
|
|
2617
2572
|
}
|
|
2618
2573
|
}
|
|
2619
2574
|
}
|
|
2575
|
+
return newest;
|
|
2576
|
+
}
|
|
2577
|
+
syncPlanFile() {
|
|
2578
|
+
if (this.lockedPlanFile) {
|
|
2579
|
+
try {
|
|
2580
|
+
const content = readFileSync(this.lockedPlanFile, "utf-8").trim();
|
|
2581
|
+
if (content) {
|
|
2582
|
+
this.connection.updateTaskFields({ plan: content });
|
|
2583
|
+
const fileName = this.lockedPlanFile.split("/").pop() ?? "plan";
|
|
2584
|
+
this.connection.postChatMessage(`Synced local plan file (${fileName}) to the task plan.`);
|
|
2585
|
+
}
|
|
2586
|
+
} catch {
|
|
2587
|
+
}
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const newest = this.findNewestPlanFile();
|
|
2620
2591
|
if (newest) {
|
|
2621
2592
|
this.lockedPlanFile = newest.path;
|
|
2622
2593
|
const content = readFileSync(newest.path, "utf-8").trim();
|
|
2623
2594
|
if (content) {
|
|
2624
2595
|
this.connection.updateTaskFields({ plan: content });
|
|
2625
|
-
const fileName = newest.path.split("/").pop();
|
|
2596
|
+
const fileName = newest.path.split("/").pop() ?? "plan";
|
|
2626
2597
|
this.connection.postChatMessage(
|
|
2627
2598
|
`Detected local plan file (${fileName}) and synced it to the task plan.`
|
|
2628
2599
|
);
|
|
2629
2600
|
}
|
|
2630
2601
|
}
|
|
2631
2602
|
}
|
|
2632
|
-
};
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// src/runner/runner-setup.ts
|
|
2606
|
+
var MAX_SETUP_LOG_LINES = 50;
|
|
2607
|
+
function pushSetupLog(setupLog, line) {
|
|
2608
|
+
setupLog.push(line);
|
|
2609
|
+
if (setupLog.length > MAX_SETUP_LOG_LINES) {
|
|
2610
|
+
setupLog.splice(0, setupLog.length - MAX_SETUP_LOG_LINES);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
async function executeSetupConfig(config, runnerConfig, connection, setupLog, effectiveAgentMode) {
|
|
2614
|
+
let deferredStartConfig = null;
|
|
2615
|
+
if (config.setupCommand) {
|
|
2616
|
+
pushSetupLog(setupLog, `$ ${config.setupCommand}`);
|
|
2617
|
+
await runSetupCommand(config.setupCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2618
|
+
connection.sendEvent({ type: "setup_output", stream, data });
|
|
2619
|
+
for (const line of data.split("\n").filter(Boolean)) {
|
|
2620
|
+
pushSetupLog(setupLog, `[${stream}] ${line}`);
|
|
2621
|
+
}
|
|
2622
|
+
});
|
|
2623
|
+
pushSetupLog(setupLog, "(exit 0)");
|
|
2624
|
+
}
|
|
2625
|
+
if (config.startCommand) {
|
|
2626
|
+
if (effectiveAgentMode === "auto") {
|
|
2627
|
+
deferredStartConfig = config;
|
|
2628
|
+
pushSetupLog(setupLog, `[conveyor] startCommand deferred (auto mode)`);
|
|
2629
|
+
} else {
|
|
2630
|
+
pushSetupLog(setupLog, `$ ${config.startCommand} & (background)`);
|
|
2631
|
+
runStartCommand(config.startCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2632
|
+
connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return deferredStartConfig;
|
|
2637
|
+
}
|
|
2638
|
+
async function runSetupSafe(runnerConfig, connection, callbacks, setupLog, effectiveAgentMode, setState) {
|
|
2639
|
+
await setState("setup");
|
|
2640
|
+
const ports = await loadForwardPorts(runnerConfig.workspaceDir);
|
|
2641
|
+
if (ports.length > 0 && process.env.CODESPACE_NAME) {
|
|
2642
|
+
const visibility = ports.map((p) => `${p}:public`).join(" ");
|
|
2643
|
+
runStartCommand(
|
|
2644
|
+
`gh codespace ports visibility ${visibility} -c "${process.env.CODESPACE_NAME}" 2>/dev/null`,
|
|
2645
|
+
runnerConfig.workspaceDir,
|
|
2646
|
+
() => void 0
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
const config = await loadConveyorConfig(runnerConfig.workspaceDir);
|
|
2650
|
+
if (!config) {
|
|
2651
|
+
connection.sendEvent({ type: "setup_complete" });
|
|
2652
|
+
await callbacks.onEvent({ type: "setup_complete" });
|
|
2653
|
+
return { ok: true, deferredStartConfig: null };
|
|
2654
|
+
}
|
|
2655
|
+
try {
|
|
2656
|
+
const deferredStartConfig = await executeSetupConfig(
|
|
2657
|
+
config,
|
|
2658
|
+
runnerConfig,
|
|
2659
|
+
connection,
|
|
2660
|
+
setupLog,
|
|
2661
|
+
effectiveAgentMode
|
|
2662
|
+
);
|
|
2663
|
+
const setupEvent = {
|
|
2664
|
+
type: "setup_complete",
|
|
2665
|
+
previewPort: config.previewPort ?? void 0,
|
|
2666
|
+
startCommandDeferred: deferredStartConfig === null ? void 0 : true
|
|
2667
|
+
};
|
|
2668
|
+
connection.sendEvent(setupEvent);
|
|
2669
|
+
await callbacks.onEvent(setupEvent);
|
|
2670
|
+
return { ok: true, deferredStartConfig };
|
|
2671
|
+
} catch (error) {
|
|
2672
|
+
const message = error instanceof Error ? error.message : "Setup failed";
|
|
2673
|
+
connection.sendEvent({ type: "setup_error", message });
|
|
2674
|
+
await callbacks.onEvent({ type: "setup_error", message });
|
|
2675
|
+
connection.postChatMessage(
|
|
2676
|
+
`Environment setup failed: ${message}
|
|
2677
|
+
The agent cannot start until this is resolved.`
|
|
2678
|
+
);
|
|
2679
|
+
return { ok: false, deferredStartConfig: null };
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
function runDeferredStartCommand(deferredStartConfig, runnerConfig, connection, setupLog) {
|
|
2683
|
+
if (!deferredStartConfig?.startCommand) return;
|
|
2684
|
+
pushSetupLog(setupLog, `$ ${deferredStartConfig.startCommand} & (background, deferred)`);
|
|
2685
|
+
connection.sendEvent({ type: "start_command_started" });
|
|
2686
|
+
runStartCommand(deferredStartConfig.startCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2687
|
+
connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2688
|
+
});
|
|
2689
|
+
const setupEvent = {
|
|
2690
|
+
type: "setup_complete",
|
|
2691
|
+
previewPort: deferredStartConfig.previewPort ?? void 0
|
|
2692
|
+
};
|
|
2693
|
+
connection.sendEvent(setupEvent);
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// src/runner/runner-helpers.ts
|
|
2697
|
+
function buildFileBlock(f) {
|
|
2698
|
+
if (f.content && f.contentEncoding === "base64") {
|
|
2699
|
+
return [
|
|
2700
|
+
{
|
|
2701
|
+
type: "image",
|
|
2702
|
+
source: { type: "base64", media_type: f.mimeType, data: f.content }
|
|
2703
|
+
},
|
|
2704
|
+
{ type: "text", text: `[Attached image: ${f.fileName} (${f.mimeType})]` }
|
|
2705
|
+
];
|
|
2706
|
+
}
|
|
2707
|
+
if (f.content && f.contentEncoding === "utf-8") {
|
|
2708
|
+
return [
|
|
2709
|
+
{
|
|
2710
|
+
type: "text",
|
|
2711
|
+
text: `[Attached file: ${f.fileName} (${f.mimeType})]
|
|
2712
|
+
\`\`\`
|
|
2713
|
+
${f.content}
|
|
2714
|
+
\`\`\``
|
|
2715
|
+
}
|
|
2716
|
+
];
|
|
2717
|
+
}
|
|
2718
|
+
return [
|
|
2719
|
+
{
|
|
2720
|
+
type: "text",
|
|
2721
|
+
text: `[Attached file: ${f.fileName} (${f.mimeType})] Download: ${f.downloadUrl}`
|
|
2722
|
+
}
|
|
2723
|
+
];
|
|
2724
|
+
}
|
|
2725
|
+
function buildMessageContent(content, files) {
|
|
2726
|
+
if (!files?.length) return content;
|
|
2727
|
+
const blocks = [{ type: "text", text: content }];
|
|
2728
|
+
for (const f of files) blocks.push(...buildFileBlock(f));
|
|
2729
|
+
return blocks;
|
|
2730
|
+
}
|
|
2731
|
+
function formatThinkingSetting(thinking) {
|
|
2732
|
+
if (thinking?.type === "enabled") return `enabled(${thinking.budgetTokens ?? "?"})`;
|
|
2733
|
+
return thinking?.type ?? "default";
|
|
2734
|
+
}
|
|
2735
|
+
function getModelForMode(context, mode, isParentTask) {
|
|
2736
|
+
if (mode === "building" && !isParentTask) {
|
|
2737
|
+
return context.builderModel ?? context.model;
|
|
2738
|
+
}
|
|
2739
|
+
return context.pmModel ?? context.model;
|
|
2740
|
+
}
|
|
2741
|
+
async function handleAutoModeRestart(state, connection, setState, runQuerySafe, fetchFreshContext) {
|
|
2742
|
+
const { taskContext } = state;
|
|
2743
|
+
const currentModel = taskContext.model;
|
|
2744
|
+
const currentSessionId = taskContext.claudeSessionId;
|
|
2745
|
+
if (currentSessionId && currentModel) {
|
|
2746
|
+
state.sessionIds.set(currentModel, currentSessionId);
|
|
2747
|
+
}
|
|
2748
|
+
const newMode = state.agentMode;
|
|
2749
|
+
const isParentTask = !!taskContext.isParentTask;
|
|
2750
|
+
const newModel = getModelForMode(taskContext, newMode, isParentTask);
|
|
2751
|
+
const resumeSessionId = newModel ? state.sessionIds.get(newModel) : null;
|
|
2752
|
+
if (resumeSessionId) {
|
|
2753
|
+
taskContext.claudeSessionId = resumeSessionId;
|
|
2754
|
+
} else {
|
|
2755
|
+
taskContext.claudeSessionId = null;
|
|
2756
|
+
connection.storeSessionId("");
|
|
2757
|
+
}
|
|
2758
|
+
if (newModel) taskContext.model = newModel;
|
|
2759
|
+
taskContext.agentMode = newMode;
|
|
2760
|
+
connection.emitModeTransition({ fromMode: "auto", toMode: newMode ?? "building" });
|
|
2761
|
+
connection.emitModeChanged(newMode);
|
|
2762
|
+
connection.postChatMessage(
|
|
2763
|
+
`Transitioning to **${newMode}** mode${newModel ? ` with ${newModel}` : ""}. Restarting session...`
|
|
2764
|
+
);
|
|
2765
|
+
const freshContext = await fetchFreshContext();
|
|
2766
|
+
if (freshContext) {
|
|
2767
|
+
freshContext._runnerSessionId = taskContext._runnerSessionId;
|
|
2768
|
+
freshContext.claudeSessionId = taskContext.claudeSessionId;
|
|
2769
|
+
freshContext.agentMode = newMode;
|
|
2770
|
+
if (newModel) freshContext.model = newModel;
|
|
2771
|
+
state.taskContext = freshContext;
|
|
2772
|
+
}
|
|
2773
|
+
await setState("running");
|
|
2774
|
+
await runQuerySafe(state.taskContext);
|
|
2775
|
+
}
|
|
2776
|
+
function buildQueryHost(deps) {
|
|
2777
|
+
return {
|
|
2778
|
+
config: deps.config,
|
|
2779
|
+
connection: deps.connection,
|
|
2780
|
+
callbacks: deps.callbacks,
|
|
2781
|
+
setupLog: deps.setupLog,
|
|
2782
|
+
costTracker: deps.costTracker,
|
|
2783
|
+
get agentMode() {
|
|
2784
|
+
return deps.getEffectiveAgentMode();
|
|
2785
|
+
},
|
|
2786
|
+
get isParentTask() {
|
|
2787
|
+
return deps.getIsParentTask();
|
|
2788
|
+
},
|
|
2789
|
+
get hasExitedPlanMode() {
|
|
2790
|
+
return deps.getHasExitedPlanMode();
|
|
2791
|
+
},
|
|
2792
|
+
set hasExitedPlanMode(val) {
|
|
2793
|
+
deps.setHasExitedPlanMode(val);
|
|
2794
|
+
},
|
|
2795
|
+
get pendingModeRestart() {
|
|
2796
|
+
return deps.getPendingModeRestart();
|
|
2797
|
+
},
|
|
2798
|
+
set pendingModeRestart(val) {
|
|
2799
|
+
deps.setPendingModeRestart(val);
|
|
2800
|
+
},
|
|
2801
|
+
sessionIds: deps.sessionIds,
|
|
2802
|
+
isStopped: deps.isStopped,
|
|
2803
|
+
createInputStream: deps.createInputStream,
|
|
2804
|
+
snapshotPlanFiles: () => deps.planSync.snapshotPlanFiles(),
|
|
2805
|
+
syncPlanFile: () => deps.planSync.syncPlanFile(),
|
|
2806
|
+
onModeTransition: deps.onModeTransition
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2633
2809
|
|
|
2634
2810
|
// src/runner/agent-runner.ts
|
|
2635
2811
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2636
2812
|
var IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
2637
|
-
var AgentRunner = class
|
|
2813
|
+
var AgentRunner = class {
|
|
2638
2814
|
config;
|
|
2639
2815
|
connection;
|
|
2640
2816
|
callbacks;
|
|
@@ -2653,11 +2829,10 @@ var AgentRunner = class _AgentRunner {
|
|
|
2653
2829
|
pendingModeRestart = false;
|
|
2654
2830
|
sessionIds = /* @__PURE__ */ new Map();
|
|
2655
2831
|
lastQueryModeRestart = false;
|
|
2656
|
-
deferredStartConfig = null;
|
|
2657
2832
|
startCommandStarted = false;
|
|
2658
2833
|
idleTimer = null;
|
|
2659
2834
|
idleCheckInterval = null;
|
|
2660
|
-
|
|
2835
|
+
deferredStartConfig = null;
|
|
2661
2836
|
constructor(config, callbacks) {
|
|
2662
2837
|
this.config = config;
|
|
2663
2838
|
this.connection = new ConveyorConnection(config);
|
|
@@ -2667,15 +2842,10 @@ var AgentRunner = class _AgentRunner {
|
|
|
2667
2842
|
get state() {
|
|
2668
2843
|
return this._state;
|
|
2669
2844
|
}
|
|
2670
|
-
/**
|
|
2671
|
-
* Resolve the effective AgentMode from explicit agentMode or legacy config flags.
|
|
2672
|
-
* This is the single axis of behavior for all execution path decisions.
|
|
2673
|
-
*/
|
|
2674
2845
|
get effectiveAgentMode() {
|
|
2675
2846
|
if (this.agentMode) return this.agentMode;
|
|
2676
2847
|
if (this.config.mode === "pm") {
|
|
2677
|
-
|
|
2678
|
-
return "discovery";
|
|
2848
|
+
return this.config.isAuto ? "auto" : "discovery";
|
|
2679
2849
|
}
|
|
2680
2850
|
return "building";
|
|
2681
2851
|
}
|
|
@@ -2686,9 +2856,7 @@ var AgentRunner = class _AgentRunner {
|
|
|
2686
2856
|
}
|
|
2687
2857
|
startHeartbeat() {
|
|
2688
2858
|
this.heartbeatTimer = setInterval(() => {
|
|
2689
|
-
if (!this.stopped)
|
|
2690
|
-
this.connection.sendHeartbeat();
|
|
2691
|
-
}
|
|
2859
|
+
if (!this.stopped) this.connection.sendHeartbeat();
|
|
2692
2860
|
}, HEARTBEAT_INTERVAL_MS);
|
|
2693
2861
|
}
|
|
2694
2862
|
stopHeartbeat() {
|
|
@@ -2715,32 +2883,44 @@ var AgentRunner = class _AgentRunner {
|
|
|
2715
2883
|
(message) => this.injectHumanMessage(message.content, message.files)
|
|
2716
2884
|
);
|
|
2717
2885
|
this.connection.onModeChange((data) => this.handleModeChange(data.agentMode));
|
|
2718
|
-
this.connection.onRunStartCommand(() => this.
|
|
2886
|
+
this.connection.onRunStartCommand(() => this.handleRunStartCommand());
|
|
2719
2887
|
await this.setState("connected");
|
|
2720
2888
|
this.connection.sendEvent({ type: "connected", taskId: this.config.taskId });
|
|
2721
2889
|
this.startHeartbeat();
|
|
2722
2890
|
if (this.config.mode !== "pm" && process.env.CODESPACES === "true") {
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2891
|
+
const result = await runSetupSafe(
|
|
2892
|
+
this.config,
|
|
2893
|
+
this.connection,
|
|
2894
|
+
this.callbacks,
|
|
2895
|
+
this.setupLog,
|
|
2896
|
+
this.effectiveAgentMode,
|
|
2897
|
+
(s) => this.setState(s)
|
|
2898
|
+
);
|
|
2899
|
+
if (!result.ok) {
|
|
2725
2900
|
this.stopHeartbeat();
|
|
2726
2901
|
await this.setState("error");
|
|
2727
2902
|
this.connection.disconnect();
|
|
2728
2903
|
return;
|
|
2729
2904
|
}
|
|
2905
|
+
this.deferredStartConfig = result.deferredStartConfig;
|
|
2730
2906
|
}
|
|
2731
2907
|
initRtk();
|
|
2908
|
+
this.tryInitWorktree();
|
|
2909
|
+
if (!await this.fetchAndInitContext()) return;
|
|
2910
|
+
this.tryPostContextWorktree();
|
|
2911
|
+
this.checkoutWorktreeBranch();
|
|
2912
|
+
await this.executeInitialMode();
|
|
2913
|
+
await this.runCoreLoop();
|
|
2914
|
+
this.stopHeartbeat();
|
|
2915
|
+
await this.setState("finished");
|
|
2916
|
+
this.connection.disconnect();
|
|
2917
|
+
}
|
|
2918
|
+
tryInitWorktree() {
|
|
2732
2919
|
if (process.env.CODESPACES !== "true" && process.env.CONVEYOR_USE_WORKTREE !== "false" && (this.config.mode === "pm" || process.env.CONVEYOR_USE_WORKTREE === "true")) {
|
|
2733
|
-
|
|
2734
|
-
const worktreePath = ensureWorktree(this.config.workspaceDir, this.config.taskId);
|
|
2735
|
-
this.config = { ...this.config, workspaceDir: worktreePath };
|
|
2736
|
-
this.planSync.updateWorkspaceDir(worktreePath);
|
|
2737
|
-
this.worktreeActive = true;
|
|
2738
|
-
this.setupLog.push(`[conveyor] Using worktree: ${worktreePath}`);
|
|
2739
|
-
} catch (error) {
|
|
2740
|
-
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
2741
|
-
this.setupLog.push(`[conveyor] Worktree creation failed, using shared workspace: ${msg}`);
|
|
2742
|
-
}
|
|
2920
|
+
this.activateWorktree("[conveyor] Using worktree:");
|
|
2743
2921
|
}
|
|
2922
|
+
}
|
|
2923
|
+
async fetchAndInitContext() {
|
|
2744
2924
|
await this.setState("fetching_context");
|
|
2745
2925
|
try {
|
|
2746
2926
|
this.taskContext = await this.connection.fetchTaskContext();
|
|
@@ -2751,67 +2931,56 @@ var AgentRunner = class _AgentRunner {
|
|
|
2751
2931
|
this.stopHeartbeat();
|
|
2752
2932
|
await this.setState("error");
|
|
2753
2933
|
this.connection.disconnect();
|
|
2754
|
-
return;
|
|
2934
|
+
return false;
|
|
2755
2935
|
}
|
|
2756
2936
|
this.taskContext._runnerSessionId = randomUUID2();
|
|
2757
|
-
if (this.taskContext.agentMode)
|
|
2758
|
-
this.agentMode = this.taskContext.agentMode;
|
|
2759
|
-
}
|
|
2937
|
+
if (this.taskContext.agentMode) this.agentMode = this.taskContext.agentMode;
|
|
2760
2938
|
this.logEffectiveSettings();
|
|
2761
|
-
if (process.env.CODESPACES === "true")
|
|
2762
|
-
|
|
2939
|
+
if (process.env.CODESPACES === "true") unshallowRepo(this.config.workspaceDir);
|
|
2940
|
+
return true;
|
|
2941
|
+
}
|
|
2942
|
+
tryPostContextWorktree() {
|
|
2943
|
+
if (process.env.CODESPACES !== "true" && !this.worktreeActive && this.taskContext?.useWorktree) {
|
|
2944
|
+
this.activateWorktree("[conveyor] Using worktree (from task config):");
|
|
2763
2945
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2946
|
+
}
|
|
2947
|
+
activateWorktree(logPrefix) {
|
|
2948
|
+
try {
|
|
2949
|
+
const worktreePath = ensureWorktree(this.config.workspaceDir, this.config.taskId);
|
|
2950
|
+
this.config = { ...this.config, workspaceDir: worktreePath };
|
|
2951
|
+
this.planSync.updateWorkspaceDir(worktreePath);
|
|
2952
|
+
this.worktreeActive = true;
|
|
2953
|
+
pushSetupLog(this.setupLog, `${logPrefix} ${worktreePath}`);
|
|
2954
|
+
} catch (error) {
|
|
2955
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
2956
|
+
pushSetupLog(
|
|
2957
|
+
this.setupLog,
|
|
2958
|
+
`[conveyor] Worktree creation failed, using shared workspace: ${msg}`
|
|
2959
|
+
);
|
|
2775
2960
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
}
|
|
2961
|
+
}
|
|
2962
|
+
checkoutWorktreeBranch() {
|
|
2963
|
+
if (!this.worktreeActive || !this.taskContext?.githubBranch) return;
|
|
2964
|
+
try {
|
|
2965
|
+
const branch = this.taskContext.githubBranch;
|
|
2966
|
+
execSync3(`git fetch origin ${branch} && git checkout ${branch}`, {
|
|
2967
|
+
cwd: this.config.workspaceDir,
|
|
2968
|
+
stdio: "ignore"
|
|
2969
|
+
});
|
|
2970
|
+
} catch {
|
|
2785
2971
|
}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
if (this.taskContext.isParentTask) {
|
|
2798
|
-
await this.setState("running");
|
|
2799
|
-
await this.runQuerySafe(this.taskContext);
|
|
2800
|
-
if (!this.stopped) await this.setState("idle");
|
|
2801
|
-
} else {
|
|
2802
|
-
await this.setState("idle");
|
|
2803
|
-
}
|
|
2804
|
-
break;
|
|
2805
|
-
case "auto":
|
|
2806
|
-
await this.setState("running");
|
|
2807
|
-
await this.runQuerySafe(this.taskContext);
|
|
2808
|
-
if (!this.stopped) await this.setState("idle");
|
|
2809
|
-
break;
|
|
2972
|
+
}
|
|
2973
|
+
async executeInitialMode() {
|
|
2974
|
+
if (!this.taskContext) return;
|
|
2975
|
+
const mode = this.effectiveAgentMode;
|
|
2976
|
+
const shouldRun = mode === "building" || mode === "auto" || mode === "review" && !!this.taskContext.isParentTask;
|
|
2977
|
+
if (shouldRun) {
|
|
2978
|
+
await this.setState("running");
|
|
2979
|
+
await this.runQuerySafe(this.taskContext);
|
|
2980
|
+
if (!this.stopped) await this.setState("idle");
|
|
2981
|
+
} else {
|
|
2982
|
+
await this.setState("idle");
|
|
2810
2983
|
}
|
|
2811
|
-
await this.runCoreLoop();
|
|
2812
|
-
this.stopHeartbeat();
|
|
2813
|
-
await this.setState("finished");
|
|
2814
|
-
this.connection.disconnect();
|
|
2815
2984
|
}
|
|
2816
2985
|
async runQuerySafe(context, followUp) {
|
|
2817
2986
|
this.lastQueryModeRestart = false;
|
|
@@ -2833,7 +3002,21 @@ var AgentRunner = class _AgentRunner {
|
|
|
2833
3002
|
while (!this.stopped) {
|
|
2834
3003
|
if (this.lastQueryModeRestart) {
|
|
2835
3004
|
this.lastQueryModeRestart = false;
|
|
2836
|
-
await
|
|
3005
|
+
await handleAutoModeRestart(
|
|
3006
|
+
{ agentMode: this.agentMode, sessionIds: this.sessionIds, taskContext: this.taskContext },
|
|
3007
|
+
this.connection,
|
|
3008
|
+
(s) => this.setState(s),
|
|
3009
|
+
(ctx) => this.runQuerySafe(ctx),
|
|
3010
|
+
async () => {
|
|
3011
|
+
try {
|
|
3012
|
+
return await this.connection.fetchTaskContext();
|
|
3013
|
+
} catch {
|
|
3014
|
+
return null;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
);
|
|
3018
|
+
this.taskContext = await this.connection.fetchTaskContext().catch(() => null) ?? this.taskContext;
|
|
3019
|
+
if (!this.stopped && this._state !== "error") await this.setState("idle");
|
|
2837
3020
|
continue;
|
|
2838
3021
|
}
|
|
2839
3022
|
if (this._state === "idle") {
|
|
@@ -2849,108 +3032,17 @@ var AgentRunner = class _AgentRunner {
|
|
|
2849
3032
|
}
|
|
2850
3033
|
}
|
|
2851
3034
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
if (!this.taskContext) return;
|
|
2858
|
-
const currentModel = this.taskContext.model;
|
|
2859
|
-
const currentSessionId = this.taskContext.claudeSessionId;
|
|
2860
|
-
if (currentSessionId && currentModel) {
|
|
2861
|
-
this.sessionIds.set(currentModel, currentSessionId);
|
|
2862
|
-
}
|
|
2863
|
-
const newMode = this.agentMode;
|
|
2864
|
-
const isParentTask = !!this.taskContext.isParentTask;
|
|
2865
|
-
const newModel = this.getModelForMode(newMode, isParentTask);
|
|
2866
|
-
const resumeSessionId = newModel ? this.sessionIds.get(newModel) : null;
|
|
2867
|
-
if (resumeSessionId) {
|
|
2868
|
-
this.taskContext.claudeSessionId = resumeSessionId;
|
|
2869
|
-
} else {
|
|
2870
|
-
this.taskContext.claudeSessionId = null;
|
|
2871
|
-
this.connection.storeSessionId("");
|
|
2872
|
-
}
|
|
2873
|
-
if (newModel) {
|
|
2874
|
-
this.taskContext.model = newModel;
|
|
2875
|
-
}
|
|
2876
|
-
this.taskContext.agentMode = newMode;
|
|
2877
|
-
this.connection.emitModeTransition({
|
|
2878
|
-
fromMode: "auto",
|
|
2879
|
-
toMode: newMode ?? "building"
|
|
2880
|
-
});
|
|
2881
|
-
this.connection.emitModeChanged(newMode);
|
|
2882
|
-
this.connection.postChatMessage(
|
|
2883
|
-
`Transitioning to **${newMode}** mode${newModel ? ` with ${newModel}` : ""}. Restarting session...`
|
|
2884
|
-
);
|
|
2885
|
-
try {
|
|
2886
|
-
const freshContext = await this.connection.fetchTaskContext();
|
|
2887
|
-
freshContext._runnerSessionId = this.taskContext._runnerSessionId;
|
|
2888
|
-
freshContext.claudeSessionId = this.taskContext.claudeSessionId;
|
|
2889
|
-
freshContext.agentMode = newMode;
|
|
2890
|
-
if (newModel) freshContext.model = newModel;
|
|
2891
|
-
this.taskContext = freshContext;
|
|
2892
|
-
} catch {
|
|
2893
|
-
}
|
|
2894
|
-
await this.setState("running");
|
|
2895
|
-
await this.runQuerySafe(this.taskContext);
|
|
2896
|
-
if (!this.stopped && this._state !== "error") {
|
|
2897
|
-
await this.setState("idle");
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
/**
|
|
2901
|
-
* Get the appropriate model for a given mode.
|
|
2902
|
-
* Building uses the builder model (Sonnet), Discovery/Review use PM model (Opus).
|
|
2903
|
-
*/
|
|
2904
|
-
getModelForMode(mode, isParentTask) {
|
|
2905
|
-
if (!this.taskContext) return null;
|
|
2906
|
-
if (mode === "building" && !isParentTask) {
|
|
2907
|
-
return this.taskContext.builderModel ?? this.taskContext.model;
|
|
2908
|
-
}
|
|
2909
|
-
return this.taskContext.pmModel ?? this.taskContext.model;
|
|
2910
|
-
}
|
|
2911
|
-
async runSetupSafe() {
|
|
2912
|
-
await this.setState("setup");
|
|
2913
|
-
const ports = await loadForwardPorts(this.config.workspaceDir);
|
|
2914
|
-
if (ports.length > 0 && process.env.CODESPACE_NAME) {
|
|
2915
|
-
const visibility = ports.map((p) => `${p}:public`).join(" ");
|
|
2916
|
-
runStartCommand(
|
|
2917
|
-
`gh codespace ports visibility ${visibility} -c "${process.env.CODESPACE_NAME}" 2>/dev/null`,
|
|
2918
|
-
this.config.workspaceDir,
|
|
2919
|
-
() => void 0
|
|
2920
|
-
);
|
|
2921
|
-
}
|
|
2922
|
-
const config = await loadConveyorConfig(this.config.workspaceDir);
|
|
2923
|
-
if (!config) {
|
|
2924
|
-
this.connection.sendEvent({ type: "setup_complete" });
|
|
2925
|
-
await this.callbacks.onEvent({ type: "setup_complete" });
|
|
2926
|
-
return true;
|
|
2927
|
-
}
|
|
2928
|
-
try {
|
|
2929
|
-
await this.executeSetupConfig(config);
|
|
2930
|
-
const setupEvent = {
|
|
2931
|
-
type: "setup_complete",
|
|
2932
|
-
previewPort: config.previewPort ?? void 0,
|
|
2933
|
-
startCommandDeferred: this.deferredStartConfig !== null ? true : void 0
|
|
2934
|
-
};
|
|
2935
|
-
this.connection.sendEvent(setupEvent);
|
|
2936
|
-
await this.callbacks.onEvent(setupEvent);
|
|
2937
|
-
return true;
|
|
2938
|
-
} catch (error) {
|
|
2939
|
-
const message = error instanceof Error ? error.message : "Setup failed";
|
|
2940
|
-
this.connection.sendEvent({ type: "setup_error", message });
|
|
2941
|
-
await this.callbacks.onEvent({ type: "setup_error", message });
|
|
2942
|
-
this.connection.postChatMessage(
|
|
2943
|
-
`Environment setup failed: ${message}
|
|
2944
|
-
The agent cannot start until this is resolved.`
|
|
2945
|
-
);
|
|
2946
|
-
return false;
|
|
2947
|
-
}
|
|
3035
|
+
handleRunStartCommand() {
|
|
3036
|
+
if (!this.deferredStartConfig?.startCommand || this.startCommandStarted) return;
|
|
3037
|
+
this.startCommandStarted = true;
|
|
3038
|
+
runDeferredStartCommand(this.deferredStartConfig, this.config, this.connection, this.setupLog);
|
|
3039
|
+
this.deferredStartConfig = null;
|
|
2948
3040
|
}
|
|
2949
3041
|
logEffectiveSettings() {
|
|
2950
3042
|
if (!this.taskContext) return;
|
|
2951
3043
|
const s = this.taskContext.agentSettings ?? this.config.agentSettings ?? {};
|
|
2952
3044
|
const model = this.taskContext.model || this.config.model;
|
|
2953
|
-
const thinking =
|
|
3045
|
+
const thinking = formatThinkingSetting(s.thinking);
|
|
2954
3046
|
const parts = [
|
|
2955
3047
|
`model=${model}`,
|
|
2956
3048
|
`mode=${this.config.mode ?? "task"}`,
|
|
@@ -2964,84 +3056,8 @@ The agent cannot start until this is resolved.`
|
|
|
2964
3056
|
if (s.enableFileCheckpointing) parts.push(`checkpointing=on`);
|
|
2965
3057
|
console.log(`[conveyor-agent] ${parts.join(", ")}`);
|
|
2966
3058
|
}
|
|
2967
|
-
pushSetupLog(line) {
|
|
2968
|
-
this.setupLog.push(line);
|
|
2969
|
-
if (this.setupLog.length > _AgentRunner.MAX_SETUP_LOG_LINES) {
|
|
2970
|
-
this.setupLog.splice(0, this.setupLog.length - _AgentRunner.MAX_SETUP_LOG_LINES);
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
async executeSetupConfig(config) {
|
|
2974
|
-
if (config.setupCommand) {
|
|
2975
|
-
this.pushSetupLog(`$ ${config.setupCommand}`);
|
|
2976
|
-
await runSetupCommand(config.setupCommand, this.config.workspaceDir, (stream, data) => {
|
|
2977
|
-
this.connection.sendEvent({ type: "setup_output", stream, data });
|
|
2978
|
-
for (const line of data.split("\n").filter(Boolean)) {
|
|
2979
|
-
this.pushSetupLog(`[${stream}] ${line}`);
|
|
2980
|
-
}
|
|
2981
|
-
});
|
|
2982
|
-
this.pushSetupLog("(exit 0)");
|
|
2983
|
-
}
|
|
2984
|
-
if (config.startCommand) {
|
|
2985
|
-
if (this.effectiveAgentMode === "auto") {
|
|
2986
|
-
this.deferredStartConfig = config;
|
|
2987
|
-
this.pushSetupLog(`[conveyor] startCommand deferred (auto mode)`);
|
|
2988
|
-
} else {
|
|
2989
|
-
this.pushSetupLog(`$ ${config.startCommand} & (background)`);
|
|
2990
|
-
runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
|
|
2991
|
-
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2992
|
-
});
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
runDeferredStartCommand() {
|
|
2997
|
-
if (!this.deferredStartConfig?.startCommand || this.startCommandStarted) return;
|
|
2998
|
-
this.startCommandStarted = true;
|
|
2999
|
-
const config = this.deferredStartConfig;
|
|
3000
|
-
this.deferredStartConfig = null;
|
|
3001
|
-
this.pushSetupLog(`$ ${config.startCommand} & (background, deferred)`);
|
|
3002
|
-
this.connection.sendEvent({ type: "start_command_started" });
|
|
3003
|
-
runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
|
|
3004
|
-
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
3005
|
-
});
|
|
3006
|
-
const setupEvent = {
|
|
3007
|
-
type: "setup_complete",
|
|
3008
|
-
previewPort: config.previewPort ?? void 0
|
|
3009
|
-
};
|
|
3010
|
-
this.connection.sendEvent(setupEvent);
|
|
3011
|
-
}
|
|
3012
3059
|
injectHumanMessage(content, files) {
|
|
3013
|
-
|
|
3014
|
-
if (files?.length) {
|
|
3015
|
-
const blocks = [{ type: "text", text: content }];
|
|
3016
|
-
for (const f of files) {
|
|
3017
|
-
if (f.content && f.contentEncoding === "base64") {
|
|
3018
|
-
blocks.push({
|
|
3019
|
-
type: "image",
|
|
3020
|
-
source: { type: "base64", media_type: f.mimeType, data: f.content }
|
|
3021
|
-
});
|
|
3022
|
-
blocks.push({
|
|
3023
|
-
type: "text",
|
|
3024
|
-
text: `[Attached image: ${f.fileName} (${f.mimeType})]`
|
|
3025
|
-
});
|
|
3026
|
-
} else if (f.content && f.contentEncoding === "utf-8") {
|
|
3027
|
-
blocks.push({
|
|
3028
|
-
type: "text",
|
|
3029
|
-
text: `[Attached file: ${f.fileName} (${f.mimeType})]
|
|
3030
|
-
\`\`\`
|
|
3031
|
-
${f.content}
|
|
3032
|
-
\`\`\``
|
|
3033
|
-
});
|
|
3034
|
-
} else {
|
|
3035
|
-
blocks.push({
|
|
3036
|
-
type: "text",
|
|
3037
|
-
text: `[Attached file: ${f.fileName} (${f.mimeType})] Download: ${f.downloadUrl}`
|
|
3038
|
-
});
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
messageContent = blocks;
|
|
3042
|
-
} else {
|
|
3043
|
-
messageContent = content;
|
|
3044
|
-
}
|
|
3060
|
+
const messageContent = buildMessageContent(content, files);
|
|
3045
3061
|
const msg = {
|
|
3046
3062
|
type: "user",
|
|
3047
3063
|
session_id: "",
|
|
@@ -3085,16 +3101,11 @@ ${f.content}
|
|
|
3085
3101
|
}
|
|
3086
3102
|
async waitForUserContent() {
|
|
3087
3103
|
if (this.pendingMessages.length > 0) {
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
if (!content2) return null;
|
|
3091
|
-
return content2;
|
|
3104
|
+
const content = this.pendingMessages.shift()?.message.content;
|
|
3105
|
+
return content ?? null;
|
|
3092
3106
|
}
|
|
3093
3107
|
const msg = await this.waitForMessage();
|
|
3094
|
-
|
|
3095
|
-
const content = msg.message.content;
|
|
3096
|
-
if (!content) return null;
|
|
3097
|
-
return content;
|
|
3108
|
+
return msg?.message.content ?? null;
|
|
3098
3109
|
}
|
|
3099
3110
|
async *createInputStream(initialPrompt) {
|
|
3100
3111
|
const makeUserMessage = (content) => ({
|
|
@@ -3124,55 +3135,34 @@ ${f.content}
|
|
|
3124
3135
|
}
|
|
3125
3136
|
}
|
|
3126
3137
|
asQueryHost() {
|
|
3127
|
-
|
|
3128
|
-
const getHasExitedPlanMode = () => this.hasExitedPlanMode;
|
|
3129
|
-
const setHasExitedPlanMode = (val) => {
|
|
3130
|
-
this.hasExitedPlanMode = val;
|
|
3131
|
-
};
|
|
3132
|
-
const getPendingModeRestart = () => this.pendingModeRestart;
|
|
3133
|
-
const setPendingModeRestart = (val) => {
|
|
3134
|
-
this.pendingModeRestart = val;
|
|
3135
|
-
};
|
|
3136
|
-
const getIsParentTask = () => !!this.taskContext?.isParentTask;
|
|
3137
|
-
return {
|
|
3138
|
+
return buildQueryHost({
|
|
3138
3139
|
config: this.config,
|
|
3139
3140
|
connection: this.connection,
|
|
3140
3141
|
callbacks: this.callbacks,
|
|
3141
3142
|
setupLog: this.setupLog,
|
|
3142
3143
|
costTracker: this.costTracker,
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
return getHasExitedPlanMode();
|
|
3151
|
-
},
|
|
3152
|
-
set hasExitedPlanMode(val) {
|
|
3153
|
-
setHasExitedPlanMode(val);
|
|
3154
|
-
},
|
|
3155
|
-
get pendingModeRestart() {
|
|
3156
|
-
return getPendingModeRestart();
|
|
3144
|
+
planSync: this.planSync,
|
|
3145
|
+
sessionIds: this.sessionIds,
|
|
3146
|
+
getEffectiveAgentMode: () => this.effectiveAgentMode,
|
|
3147
|
+
getIsParentTask: () => !!this.taskContext?.isParentTask,
|
|
3148
|
+
getHasExitedPlanMode: () => this.hasExitedPlanMode,
|
|
3149
|
+
setHasExitedPlanMode: (val) => {
|
|
3150
|
+
this.hasExitedPlanMode = val;
|
|
3157
3151
|
},
|
|
3158
|
-
|
|
3159
|
-
|
|
3152
|
+
getPendingModeRestart: () => this.pendingModeRestart,
|
|
3153
|
+
setPendingModeRestart: (val) => {
|
|
3154
|
+
this.pendingModeRestart = val;
|
|
3160
3155
|
},
|
|
3161
|
-
sessionIds: this.sessionIds,
|
|
3162
3156
|
isStopped: () => this.stopped,
|
|
3163
3157
|
createInputStream: (prompt) => this.createInputStream(prompt),
|
|
3164
|
-
snapshotPlanFiles: () => this.planSync.snapshotPlanFiles(),
|
|
3165
|
-
syncPlanFile: () => this.planSync.syncPlanFile(),
|
|
3166
3158
|
onModeTransition: (newMode) => {
|
|
3167
3159
|
this.agentMode = newMode;
|
|
3168
3160
|
}
|
|
3169
|
-
};
|
|
3161
|
+
});
|
|
3170
3162
|
}
|
|
3171
3163
|
handleModeChange(newAgentMode) {
|
|
3172
3164
|
if (this.config.mode !== "pm") return;
|
|
3173
|
-
if (newAgentMode)
|
|
3174
|
-
this.agentMode = newAgentMode;
|
|
3175
|
-
}
|
|
3165
|
+
if (newAgentMode) this.agentMode = newAgentMode;
|
|
3176
3166
|
const effectiveMode = this.effectiveAgentMode;
|
|
3177
3167
|
this.connection.emitModeChanged(effectiveMode);
|
|
3178
3168
|
this.connection.postChatMessage(
|
|
@@ -3239,87 +3229,103 @@ function buildPrompt(message, chatHistory) {
|
|
|
3239
3229
|
${message.content}`);
|
|
3240
3230
|
return parts.join("\n");
|
|
3241
3231
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3232
|
+
function processContentBlock(block, responseParts, turnToolCalls) {
|
|
3233
|
+
if (block.type === "text" && block.text) {
|
|
3234
|
+
responseParts.push(block.text);
|
|
3235
|
+
} else if (block.type === "tool_use" && block.name) {
|
|
3236
|
+
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
3237
|
+
turnToolCalls.push({
|
|
3238
|
+
tool: block.name,
|
|
3239
|
+
input: inputStr.slice(0, 1e4),
|
|
3240
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3241
|
+
});
|
|
3242
|
+
console.log(`[project-chat] [tool_use] ${block.name}`);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
async function fetchContext(connection) {
|
|
3246
|
+
let agentCtx = null;
|
|
3244
3247
|
try {
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3248
|
+
agentCtx = await connection.fetchAgentContext();
|
|
3249
|
+
} catch {
|
|
3250
|
+
console.log("[project-chat] Could not fetch agent context, using defaults");
|
|
3251
|
+
}
|
|
3252
|
+
let chatHistory = [];
|
|
3253
|
+
try {
|
|
3254
|
+
chatHistory = await connection.fetchChatHistory(30);
|
|
3255
|
+
} catch {
|
|
3256
|
+
console.log("[project-chat] Could not fetch chat history, proceeding without it");
|
|
3257
|
+
}
|
|
3258
|
+
return { agentCtx, chatHistory };
|
|
3259
|
+
}
|
|
3260
|
+
function buildChatQueryOptions(agentCtx, projectDir) {
|
|
3261
|
+
const model = agentCtx?.model || FALLBACK_MODEL;
|
|
3262
|
+
const settings = agentCtx?.agentSettings ?? {};
|
|
3263
|
+
return {
|
|
3264
|
+
model,
|
|
3265
|
+
systemPrompt: {
|
|
3266
|
+
type: "preset",
|
|
3267
|
+
preset: "claude_code",
|
|
3268
|
+
append: buildSystemPrompt2(projectDir, agentCtx)
|
|
3269
|
+
},
|
|
3270
|
+
cwd: projectDir,
|
|
3271
|
+
permissionMode: "bypassPermissions",
|
|
3272
|
+
allowDangerouslySkipPermissions: true,
|
|
3273
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
3274
|
+
maxTurns: settings.maxTurns ?? 15,
|
|
3275
|
+
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
3276
|
+
effort: settings.effort,
|
|
3277
|
+
thinking: settings.thinking
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
function processEventStream(event, connection, responseParts, turnToolCalls, isTyping) {
|
|
3281
|
+
if (event.type === "assistant") {
|
|
3282
|
+
if (!isTyping.value) {
|
|
3283
|
+
setTimeout(() => connection.emitEvent({ type: "agent_typing_start" }), 200);
|
|
3284
|
+
isTyping.value = true;
|
|
3250
3285
|
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
} catch {
|
|
3255
|
-
console.log("[project-chat] Could not fetch chat history, proceeding without it");
|
|
3256
|
-
}
|
|
3257
|
-
const model = agentCtx?.model || FALLBACK_MODEL;
|
|
3258
|
-
const settings = agentCtx?.agentSettings ?? {};
|
|
3259
|
-
const systemPrompt = buildSystemPrompt2(projectDir, agentCtx);
|
|
3260
|
-
const prompt = buildPrompt(message, chatHistory);
|
|
3261
|
-
const events = query2({
|
|
3262
|
-
prompt,
|
|
3263
|
-
options: {
|
|
3264
|
-
model,
|
|
3265
|
-
systemPrompt: {
|
|
3266
|
-
type: "preset",
|
|
3267
|
-
preset: "claude_code",
|
|
3268
|
-
append: systemPrompt
|
|
3269
|
-
},
|
|
3270
|
-
cwd: projectDir,
|
|
3271
|
-
permissionMode: "bypassPermissions",
|
|
3272
|
-
allowDangerouslySkipPermissions: true,
|
|
3273
|
-
tools: { type: "preset", preset: "claude_code" },
|
|
3274
|
-
maxTurns: settings.maxTurns ?? 15,
|
|
3275
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
3276
|
-
effort: settings.effort,
|
|
3277
|
-
thinking: settings.thinking
|
|
3278
|
-
}
|
|
3279
|
-
});
|
|
3280
|
-
const responseParts = [];
|
|
3281
|
-
const turnToolCalls = [];
|
|
3282
|
-
let isTyping = false;
|
|
3283
|
-
for await (const event of events) {
|
|
3284
|
-
if (event.type === "assistant") {
|
|
3285
|
-
if (!isTyping) {
|
|
3286
|
-
setTimeout(() => connection.emitEvent({ type: "agent_typing_start" }), 200);
|
|
3287
|
-
isTyping = true;
|
|
3288
|
-
}
|
|
3289
|
-
const assistantEvent = event;
|
|
3290
|
-
const { content } = assistantEvent.message;
|
|
3291
|
-
for (const block of content) {
|
|
3292
|
-
if (block.type === "text") {
|
|
3293
|
-
responseParts.push(block.text);
|
|
3294
|
-
} else if (block.type === "tool_use") {
|
|
3295
|
-
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
3296
|
-
turnToolCalls.push({
|
|
3297
|
-
tool: block.name,
|
|
3298
|
-
input: inputStr.slice(0, 1e4),
|
|
3299
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3300
|
-
});
|
|
3301
|
-
console.log(`[project-chat] [tool_use] ${block.name}`);
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
if (turnToolCalls.length > 0) {
|
|
3305
|
-
connection.emitEvent({ type: "activity_block", events: [...turnToolCalls] });
|
|
3306
|
-
turnToolCalls.length = 0;
|
|
3307
|
-
}
|
|
3308
|
-
} else if (event.type === "result") {
|
|
3309
|
-
if (isTyping) {
|
|
3310
|
-
connection.emitEvent({ type: "agent_typing_stop" });
|
|
3311
|
-
isTyping = false;
|
|
3312
|
-
}
|
|
3313
|
-
break;
|
|
3314
|
-
}
|
|
3286
|
+
const assistantEvent = event;
|
|
3287
|
+
for (const block of assistantEvent.message.content) {
|
|
3288
|
+
processContentBlock(block, responseParts, turnToolCalls);
|
|
3315
3289
|
}
|
|
3316
|
-
if (
|
|
3317
|
-
connection.emitEvent({ type: "
|
|
3290
|
+
if (turnToolCalls.length > 0) {
|
|
3291
|
+
connection.emitEvent({ type: "activity_block", events: [...turnToolCalls] });
|
|
3292
|
+
turnToolCalls.length = 0;
|
|
3318
3293
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3294
|
+
return false;
|
|
3295
|
+
}
|
|
3296
|
+
if (event.type === "result") {
|
|
3297
|
+
if (isTyping.value) {
|
|
3298
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
3299
|
+
isTyping.value = false;
|
|
3322
3300
|
}
|
|
3301
|
+
return true;
|
|
3302
|
+
}
|
|
3303
|
+
return false;
|
|
3304
|
+
}
|
|
3305
|
+
async function runChatQuery(message, connection, projectDir) {
|
|
3306
|
+
const { agentCtx, chatHistory } = await fetchContext(connection);
|
|
3307
|
+
const options = buildChatQueryOptions(agentCtx, projectDir);
|
|
3308
|
+
const prompt = buildPrompt(message, chatHistory);
|
|
3309
|
+
const events = query2({ prompt, options });
|
|
3310
|
+
const responseParts = [];
|
|
3311
|
+
const turnToolCalls = [];
|
|
3312
|
+
const isTyping = { value: false };
|
|
3313
|
+
for await (const event of events) {
|
|
3314
|
+
const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
|
|
3315
|
+
if (done) break;
|
|
3316
|
+
}
|
|
3317
|
+
if (isTyping.value) {
|
|
3318
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
3319
|
+
}
|
|
3320
|
+
const responseText = responseParts.join("\n\n").trim();
|
|
3321
|
+
if (responseText) {
|
|
3322
|
+
await connection.emitChatMessage(responseText);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
async function handleProjectChatMessage(message, connection, projectDir) {
|
|
3326
|
+
connection.emitAgentStatus("busy");
|
|
3327
|
+
try {
|
|
3328
|
+
await runChatQuery(message, connection, projectDir);
|
|
3323
3329
|
} catch (error) {
|
|
3324
3330
|
console.error(
|
|
3325
3331
|
"[project-chat] Failed to handle message:",
|
|
@@ -3342,6 +3348,72 @@ var __dirname = path.dirname(__filename);
|
|
|
3342
3348
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
3343
3349
|
var MAX_CONCURRENT = 5;
|
|
3344
3350
|
var STOP_TIMEOUT_MS = 3e4;
|
|
3351
|
+
function setupWorkDir(projectDir, assignment) {
|
|
3352
|
+
const { taskId, branch, devBranch, useWorktree } = assignment;
|
|
3353
|
+
const shortId = taskId.slice(0, 8);
|
|
3354
|
+
const shouldWorktree = useWorktree === true;
|
|
3355
|
+
let workDir;
|
|
3356
|
+
if (shouldWorktree) {
|
|
3357
|
+
workDir = ensureWorktree(projectDir, taskId, devBranch);
|
|
3358
|
+
} else {
|
|
3359
|
+
workDir = projectDir;
|
|
3360
|
+
}
|
|
3361
|
+
if (branch && branch !== devBranch) {
|
|
3362
|
+
try {
|
|
3363
|
+
execSync4(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3364
|
+
} catch {
|
|
3365
|
+
try {
|
|
3366
|
+
execSync4(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3367
|
+
} catch {
|
|
3368
|
+
console.log(`[task:${shortId}] Warning: could not checkout branch ${branch}`);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
return { workDir, usesWorktree: shouldWorktree };
|
|
3373
|
+
}
|
|
3374
|
+
function spawnChildAgent(assignment, workDir) {
|
|
3375
|
+
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox } = assignment;
|
|
3376
|
+
const cliPath = path.resolve(__dirname, "cli.js");
|
|
3377
|
+
const childEnv = { ...process.env };
|
|
3378
|
+
delete childEnv.CONVEYOR_PROJECT_TOKEN;
|
|
3379
|
+
delete childEnv.CONVEYOR_PROJECT_ID;
|
|
3380
|
+
const child = fork(cliPath, [], {
|
|
3381
|
+
env: {
|
|
3382
|
+
...childEnv,
|
|
3383
|
+
CONVEYOR_API_URL: apiUrl,
|
|
3384
|
+
CONVEYOR_TASK_TOKEN: taskToken,
|
|
3385
|
+
CONVEYOR_TASK_ID: taskId,
|
|
3386
|
+
CONVEYOR_MODE: mode,
|
|
3387
|
+
CONVEYOR_WORKSPACE: workDir,
|
|
3388
|
+
CONVEYOR_USE_WORKTREE: "false",
|
|
3389
|
+
CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
|
|
3390
|
+
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
3391
|
+
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false"
|
|
3392
|
+
},
|
|
3393
|
+
cwd: workDir,
|
|
3394
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
3395
|
+
});
|
|
3396
|
+
child.stdin?.on("error", () => {
|
|
3397
|
+
});
|
|
3398
|
+
child.stdout?.on("error", () => {
|
|
3399
|
+
});
|
|
3400
|
+
child.stderr?.on("error", () => {
|
|
3401
|
+
});
|
|
3402
|
+
const shortId = taskId.slice(0, 8);
|
|
3403
|
+
child.stdout?.on("data", (data) => {
|
|
3404
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
3405
|
+
for (const line of lines) {
|
|
3406
|
+
console.log(`[task:${shortId}] ${line}`);
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
child.stderr?.on("data", (data) => {
|
|
3410
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
3411
|
+
for (const line of lines) {
|
|
3412
|
+
console.error(`[task:${shortId}] ${line}`);
|
|
3413
|
+
}
|
|
3414
|
+
});
|
|
3415
|
+
return child;
|
|
3416
|
+
}
|
|
3345
3417
|
var ProjectRunner = class {
|
|
3346
3418
|
connection;
|
|
3347
3419
|
projectDir;
|
|
@@ -3360,7 +3432,7 @@ var ProjectRunner = class {
|
|
|
3360
3432
|
async start() {
|
|
3361
3433
|
await this.connection.connect();
|
|
3362
3434
|
this.connection.onTaskAssignment((assignment) => {
|
|
3363
|
-
|
|
3435
|
+
this.handleAssignment(assignment);
|
|
3364
3436
|
});
|
|
3365
3437
|
this.connection.onStopTask((data) => {
|
|
3366
3438
|
this.handleStopTask(data.taskId);
|
|
@@ -3383,8 +3455,8 @@ var ProjectRunner = class {
|
|
|
3383
3455
|
process.on("SIGINT", () => void this.stop());
|
|
3384
3456
|
});
|
|
3385
3457
|
}
|
|
3386
|
-
|
|
3387
|
-
const { taskId,
|
|
3458
|
+
handleAssignment(assignment) {
|
|
3459
|
+
const { taskId, mode } = assignment;
|
|
3388
3460
|
const shortId = taskId.slice(0, 8);
|
|
3389
3461
|
if (this.activeAgents.has(taskId)) {
|
|
3390
3462
|
console.log(`[project-runner] Task ${shortId} already running, skipping`);
|
|
@@ -3403,67 +3475,13 @@ var ProjectRunner = class {
|
|
|
3403
3475
|
} catch {
|
|
3404
3476
|
console.log(`[task:${shortId}] Warning: git fetch failed`);
|
|
3405
3477
|
}
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
if (shouldWorktree) {
|
|
3409
|
-
workDir = ensureWorktree(this.projectDir, taskId, devBranch);
|
|
3410
|
-
} else {
|
|
3411
|
-
workDir = this.projectDir;
|
|
3412
|
-
}
|
|
3413
|
-
if (branch && branch !== devBranch) {
|
|
3414
|
-
try {
|
|
3415
|
-
execSync4(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3416
|
-
} catch {
|
|
3417
|
-
try {
|
|
3418
|
-
execSync4(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3419
|
-
} catch {
|
|
3420
|
-
console.log(`[task:${shortId}] Warning: could not checkout branch ${branch}`);
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
const cliPath = path.resolve(__dirname, "cli.js");
|
|
3425
|
-
const childEnv = { ...process.env };
|
|
3426
|
-
delete childEnv.CONVEYOR_PROJECT_TOKEN;
|
|
3427
|
-
delete childEnv.CONVEYOR_PROJECT_ID;
|
|
3428
|
-
const child = fork(cliPath, [], {
|
|
3429
|
-
env: {
|
|
3430
|
-
...childEnv,
|
|
3431
|
-
CONVEYOR_API_URL: apiUrl,
|
|
3432
|
-
CONVEYOR_TASK_TOKEN: taskToken,
|
|
3433
|
-
CONVEYOR_TASK_ID: taskId,
|
|
3434
|
-
CONVEYOR_MODE: mode,
|
|
3435
|
-
CONVEYOR_WORKSPACE: workDir,
|
|
3436
|
-
CONVEYOR_USE_WORKTREE: "false",
|
|
3437
|
-
CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
|
|
3438
|
-
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
3439
|
-
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false"
|
|
3440
|
-
},
|
|
3441
|
-
cwd: workDir,
|
|
3442
|
-
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
3443
|
-
});
|
|
3444
|
-
child.stdin?.on("error", () => {
|
|
3445
|
-
});
|
|
3446
|
-
child.stdout?.on("error", () => {
|
|
3447
|
-
});
|
|
3448
|
-
child.stderr?.on("error", () => {
|
|
3449
|
-
});
|
|
3450
|
-
child.stdout?.on("data", (data) => {
|
|
3451
|
-
const lines = data.toString().trimEnd().split("\n");
|
|
3452
|
-
for (const line of lines) {
|
|
3453
|
-
console.log(`[task:${shortId}] ${line}`);
|
|
3454
|
-
}
|
|
3455
|
-
});
|
|
3456
|
-
child.stderr?.on("data", (data) => {
|
|
3457
|
-
const lines = data.toString().trimEnd().split("\n");
|
|
3458
|
-
for (const line of lines) {
|
|
3459
|
-
console.error(`[task:${shortId}] ${line}`);
|
|
3460
|
-
}
|
|
3461
|
-
});
|
|
3478
|
+
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
3479
|
+
const child = spawnChildAgent(assignment, workDir);
|
|
3462
3480
|
this.activeAgents.set(taskId, {
|
|
3463
3481
|
process: child,
|
|
3464
3482
|
worktreePath: workDir,
|
|
3465
3483
|
mode,
|
|
3466
|
-
usesWorktree
|
|
3484
|
+
usesWorktree
|
|
3467
3485
|
});
|
|
3468
3486
|
this.connection.emitTaskStarted(taskId);
|
|
3469
3487
|
console.log(`[project-runner] Started task ${shortId} in ${mode} mode at ${workDir}`);
|
|
@@ -3472,7 +3490,7 @@ var ProjectRunner = class {
|
|
|
3472
3490
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
3473
3491
|
this.connection.emitTaskStopped(taskId, reason);
|
|
3474
3492
|
console.log(`[project-runner] Task ${shortId} ${reason}`);
|
|
3475
|
-
if (code === 0 &&
|
|
3493
|
+
if (code === 0 && usesWorktree) {
|
|
3476
3494
|
try {
|
|
3477
3495
|
removeWorktree(this.projectDir, taskId);
|
|
3478
3496
|
} catch {
|
|
@@ -3534,7 +3552,9 @@ var ProjectRunner = class {
|
|
|
3534
3552
|
);
|
|
3535
3553
|
await Promise.race([
|
|
3536
3554
|
Promise.all(stopPromises),
|
|
3537
|
-
new Promise((resolve2) =>
|
|
3555
|
+
new Promise((resolve2) => {
|
|
3556
|
+
setTimeout(resolve2, 6e4);
|
|
3557
|
+
})
|
|
3538
3558
|
]);
|
|
3539
3559
|
this.connection.disconnect();
|
|
3540
3560
|
console.log("[project-runner] Shutdown complete");
|
|
@@ -3612,13 +3632,13 @@ var FileCache = class {
|
|
|
3612
3632
|
export {
|
|
3613
3633
|
ConveyorConnection,
|
|
3614
3634
|
ProjectConnection,
|
|
3635
|
+
ensureWorktree,
|
|
3636
|
+
removeWorktree,
|
|
3615
3637
|
loadConveyorConfig,
|
|
3616
3638
|
runSetupCommand,
|
|
3617
3639
|
runStartCommand,
|
|
3618
|
-
ensureWorktree,
|
|
3619
|
-
removeWorktree,
|
|
3620
3640
|
AgentRunner,
|
|
3621
3641
|
ProjectRunner,
|
|
3622
3642
|
FileCache
|
|
3623
3643
|
};
|
|
3624
|
-
//# sourceMappingURL=chunk-
|
|
3644
|
+
//# sourceMappingURL=chunk-FS3A4THO.js.map
|