@rallycry/conveyor-agent 4.3.0 → 4.5.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-7Y3RP3ZA.js} +1740 -1665
- package/dist/chunk-7Y3RP3ZA.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +9 -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) {
|
|
@@ -247,6 +253,10 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
247
253
|
if (!this.socket) return;
|
|
248
254
|
this.socket.emit("agentRunner:statusUpdate", { status });
|
|
249
255
|
}
|
|
256
|
+
emitRateLimitPause(resetsAt) {
|
|
257
|
+
if (!this.socket) return;
|
|
258
|
+
this.socket.emit("agentRunner:rateLimitPause", { resetsAt });
|
|
259
|
+
}
|
|
250
260
|
sendHeartbeat() {
|
|
251
261
|
if (!this.socket) return;
|
|
252
262
|
this.socket.emit("agentRunner:heartbeat", {});
|
|
@@ -258,14 +268,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
258
268
|
this.sendEvent({ type: "agent_typing_stop" });
|
|
259
269
|
}
|
|
260
270
|
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
|
-
});
|
|
271
|
+
return createSubtask(this.socket, data);
|
|
269
272
|
}
|
|
270
273
|
updateSubtask(subtaskId, fields) {
|
|
271
274
|
if (!this.socket) throw new Error("Not connected");
|
|
@@ -276,183 +279,44 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
276
279
|
this.socket.emit("agentRunner:deleteSubtask", { subtaskId });
|
|
277
280
|
}
|
|
278
281
|
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
|
-
});
|
|
282
|
+
return listSubtasks(this.socket);
|
|
287
283
|
}
|
|
288
284
|
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
|
-
]);
|
|
285
|
+
return fetchCliHistory(this.socket, taskId ?? this.config.taskId, limit, source);
|
|
310
286
|
}
|
|
311
287
|
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
|
-
});
|
|
288
|
+
return startChildCloudBuild(this.socket, childTaskId);
|
|
324
289
|
}
|
|
325
290
|
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
|
-
});
|
|
291
|
+
return approveAndMergePR(this.socket, childTaskId);
|
|
341
292
|
}
|
|
342
293
|
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
|
-
});
|
|
294
|
+
return postChildChatMessage(this.socket, childTaskId, content);
|
|
355
295
|
}
|
|
356
296
|
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
|
-
});
|
|
297
|
+
return updateChildStatus(this.socket, childTaskId, status);
|
|
369
298
|
}
|
|
370
299
|
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
|
-
});
|
|
300
|
+
return stopChildBuild(this.socket, childTaskId);
|
|
383
301
|
}
|
|
384
302
|
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
|
-
});
|
|
303
|
+
return fetchTask(this.socket, slugOrId);
|
|
400
304
|
}
|
|
401
305
|
updateTaskProperties(data) {
|
|
402
306
|
if (!this.socket) throw new Error("Not connected");
|
|
403
307
|
this.socket.emit("agentRunner:updateTaskProperties", data);
|
|
404
308
|
}
|
|
405
309
|
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
|
-
});
|
|
310
|
+
return listIcons(this.socket);
|
|
414
311
|
}
|
|
415
312
|
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
|
-
});
|
|
313
|
+
return generateTaskIcon(this.socket, prompt, aspectRatio);
|
|
428
314
|
}
|
|
429
315
|
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
|
-
});
|
|
316
|
+
return getTaskProperties(this.socket);
|
|
442
317
|
}
|
|
443
318
|
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
|
-
});
|
|
319
|
+
return triggerIdentification(this.socket);
|
|
456
320
|
}
|
|
457
321
|
emitModeTransition(payload) {
|
|
458
322
|
if (!this.socket) return;
|
|
@@ -615,14 +479,52 @@ var ProjectConnection = class {
|
|
|
615
479
|
}
|
|
616
480
|
};
|
|
617
481
|
|
|
482
|
+
// src/runner/worktree.ts
|
|
483
|
+
import { execSync } from "child_process";
|
|
484
|
+
import { existsSync } from "fs";
|
|
485
|
+
import { join } from "path";
|
|
486
|
+
var WORKTREE_DIR = ".worktrees";
|
|
487
|
+
function ensureWorktree(projectDir, taskId, branch) {
|
|
488
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
489
|
+
if (existsSync(worktreePath)) {
|
|
490
|
+
if (branch) {
|
|
491
|
+
try {
|
|
492
|
+
execSync(`git checkout --detach origin/${branch}`, {
|
|
493
|
+
cwd: worktreePath,
|
|
494
|
+
stdio: "ignore"
|
|
495
|
+
});
|
|
496
|
+
} catch {
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return worktreePath;
|
|
500
|
+
}
|
|
501
|
+
const ref = branch ? `origin/${branch}` : "HEAD";
|
|
502
|
+
execSync(`git worktree add --detach "${worktreePath}" ${ref}`, {
|
|
503
|
+
cwd: projectDir,
|
|
504
|
+
stdio: "ignore"
|
|
505
|
+
});
|
|
506
|
+
return worktreePath;
|
|
507
|
+
}
|
|
508
|
+
function removeWorktree(projectDir, taskId) {
|
|
509
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
510
|
+
if (!existsSync(worktreePath)) return;
|
|
511
|
+
try {
|
|
512
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
513
|
+
cwd: projectDir,
|
|
514
|
+
stdio: "ignore"
|
|
515
|
+
});
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
618
520
|
// src/setup/config.ts
|
|
619
521
|
import { readFile } from "fs/promises";
|
|
620
|
-
import { join } from "path";
|
|
522
|
+
import { join as join2 } from "path";
|
|
621
523
|
var CONVEYOR_CONFIG_PATH = ".conveyor/config.json";
|
|
622
524
|
var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
|
|
623
525
|
async function loadForwardPorts(workspaceDir) {
|
|
624
526
|
try {
|
|
625
|
-
const raw = await readFile(
|
|
527
|
+
const raw = await readFile(join2(workspaceDir, DEVCONTAINER_PATH), "utf-8");
|
|
626
528
|
const parsed = JSON.parse(raw);
|
|
627
529
|
return parsed.forwardPorts ?? [];
|
|
628
530
|
} catch {
|
|
@@ -631,13 +533,13 @@ async function loadForwardPorts(workspaceDir) {
|
|
|
631
533
|
}
|
|
632
534
|
async function loadConveyorConfig(workspaceDir) {
|
|
633
535
|
try {
|
|
634
|
-
const raw = await readFile(
|
|
536
|
+
const raw = await readFile(join2(workspaceDir, CONVEYOR_CONFIG_PATH), "utf-8");
|
|
635
537
|
const parsed = JSON.parse(raw);
|
|
636
538
|
if (parsed.setupCommand || parsed.startCommand) return parsed;
|
|
637
539
|
} catch {
|
|
638
540
|
}
|
|
639
541
|
try {
|
|
640
|
-
const raw = await readFile(
|
|
542
|
+
const raw = await readFile(join2(workspaceDir, DEVCONTAINER_PATH), "utf-8");
|
|
641
543
|
const parsed = JSON.parse(raw);
|
|
642
544
|
if (parsed.conveyor && (parsed.conveyor.startCommand || parsed.conveyor.setupCommand)) {
|
|
643
545
|
return parsed.conveyor;
|
|
@@ -692,17 +594,17 @@ function runStartCommand(cmd, cwd, onOutput) {
|
|
|
692
594
|
}
|
|
693
595
|
|
|
694
596
|
// src/setup/codespace.ts
|
|
695
|
-
import { execSync } from "child_process";
|
|
597
|
+
import { execSync as execSync2 } from "child_process";
|
|
696
598
|
function initRtk() {
|
|
697
599
|
try {
|
|
698
|
-
|
|
699
|
-
|
|
600
|
+
execSync2("rtk --version", { stdio: "ignore" });
|
|
601
|
+
execSync2("rtk init --global --auto-patch", { stdio: "ignore" });
|
|
700
602
|
} catch {
|
|
701
603
|
}
|
|
702
604
|
}
|
|
703
605
|
function unshallowRepo(workspaceDir) {
|
|
704
606
|
try {
|
|
705
|
-
|
|
607
|
+
execSync2("git fetch --unshallow", {
|
|
706
608
|
cwd: workspaceDir,
|
|
707
609
|
stdio: "ignore",
|
|
708
610
|
timeout: 6e4
|
|
@@ -711,44 +613,6 @@ function unshallowRepo(workspaceDir) {
|
|
|
711
613
|
}
|
|
712
614
|
}
|
|
713
615
|
|
|
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
616
|
// src/runner/agent-runner.ts
|
|
753
617
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
754
618
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -785,45 +649,64 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
785
649
|
}
|
|
786
650
|
}
|
|
787
651
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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;
|
|
652
|
+
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
653
|
+
function isRetriableMessage(msg, durationMs) {
|
|
654
|
+
if (IMAGE_ERROR_PATTERN.test(msg)) return true;
|
|
655
|
+
if (API_ERROR_PATTERN.test(msg) && (durationMs === void 0 || durationMs < 3e4)) return true;
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
function handleSuccessResult(event, host, context, startTime) {
|
|
659
|
+
const durationMs = Date.now() - startTime;
|
|
660
|
+
const summary = event.result || "Task completed.";
|
|
661
|
+
const retriable = isRetriableMessage(summary, durationMs);
|
|
662
|
+
const cumulativeTotal = host.costTracker.addQueryCost(event.total_cost_usd);
|
|
663
|
+
const { modelUsage } = event;
|
|
664
|
+
if (modelUsage && typeof modelUsage === "object") {
|
|
665
|
+
host.costTracker.addModelUsage(modelUsage);
|
|
666
|
+
}
|
|
667
|
+
host.connection.sendEvent({ type: "completed", summary, costUsd: cumulativeTotal, durationMs });
|
|
668
|
+
if (modelUsage && typeof modelUsage === "object") {
|
|
669
|
+
let queryInputTokens = 0;
|
|
670
|
+
let contextWindow = 0;
|
|
671
|
+
for (const data of Object.values(modelUsage)) {
|
|
672
|
+
queryInputTokens += data.inputTokens ?? 0;
|
|
673
|
+
const cw = data.contextWindow ?? 0;
|
|
674
|
+
if (cw > contextWindow) contextWindow = cw;
|
|
675
|
+
}
|
|
676
|
+
if (contextWindow > 0) {
|
|
677
|
+
host.connection.sendEvent({
|
|
678
|
+
type: "context_update",
|
|
679
|
+
contextTokens: queryInputTokens,
|
|
680
|
+
contextWindow
|
|
681
|
+
});
|
|
822
682
|
}
|
|
823
|
-
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
824
683
|
}
|
|
684
|
+
if (cumulativeTotal > 0 && context.agentId && context._runnerSessionId) {
|
|
685
|
+
const breakdown = host.costTracker.modelBreakdown;
|
|
686
|
+
host.connection.trackSpending({
|
|
687
|
+
agentId: context.agentId,
|
|
688
|
+
sessionId: context._runnerSessionId,
|
|
689
|
+
totalCostUsd: cumulativeTotal,
|
|
690
|
+
onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN,
|
|
691
|
+
modelUsage: breakdown.length > 0 ? breakdown : void 0
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return { totalCostUsd: cumulativeTotal, retriable };
|
|
695
|
+
}
|
|
696
|
+
function handleErrorResult(event, host) {
|
|
697
|
+
const errorMsg = event.errors.length > 0 ? event.errors.join(", ") : `Agent stopped: ${event.subtype}`;
|
|
698
|
+
const retriable = isRetriableMessage(errorMsg);
|
|
699
|
+
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
700
|
+
return { retriable };
|
|
701
|
+
}
|
|
702
|
+
function handleResultEvent(event, host, context, startTime) {
|
|
825
703
|
const resultSummary = event.subtype === "success" ? event.result : event.errors.join(", ");
|
|
826
|
-
|
|
704
|
+
if (event.subtype === "success") {
|
|
705
|
+
const result2 = handleSuccessResult(event, host, context, startTime);
|
|
706
|
+
return { ...result2, resultSummary };
|
|
707
|
+
}
|
|
708
|
+
const result = handleErrorResult(event, host);
|
|
709
|
+
return { totalCostUsd: 0, ...result, resultSummary };
|
|
827
710
|
}
|
|
828
711
|
async function emitResultEvent(event, host, context, startTime) {
|
|
829
712
|
const result = handleResultEvent(event, host, context, startTime);
|
|
@@ -848,16 +731,19 @@ function handleRateLimitEvent(event, host) {
|
|
|
848
731
|
const { rate_limit_info } = event;
|
|
849
732
|
const status = rate_limit_info.status;
|
|
850
733
|
if (status === "rejected") {
|
|
851
|
-
const resetsAt = rate_limit_info.resetsAt ? new Date(rate_limit_info.resetsAt).toISOString() :
|
|
852
|
-
const
|
|
734
|
+
const resetsAt = rate_limit_info.resetsAt ? new Date(rate_limit_info.resetsAt).toISOString() : void 0;
|
|
735
|
+
const resetsAtDisplay = resetsAt ?? "unknown";
|
|
736
|
+
const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
|
|
853
737
|
host.connection.sendEvent({ type: "error", message });
|
|
854
738
|
void host.callbacks.onEvent({ type: "error", message });
|
|
739
|
+
return resetsAt;
|
|
855
740
|
} else if (status === "allowed_warning") {
|
|
856
741
|
const utilization = rate_limit_info.utilization ? `${Math.round(rate_limit_info.utilization * 100)}%` : "high";
|
|
857
742
|
const message = `Rate limit warning: ${utilization} utilization (type: ${rate_limit_info.rateLimitType ?? "unknown"})`;
|
|
858
743
|
host.connection.sendEvent({ type: "thinking", message });
|
|
859
744
|
void host.callbacks.onEvent({ type: "thinking", message });
|
|
860
745
|
}
|
|
746
|
+
return void 0;
|
|
861
747
|
}
|
|
862
748
|
async function processEvents(events, context, host) {
|
|
863
749
|
const startTime = Date.now();
|
|
@@ -865,6 +751,7 @@ async function processEvents(events, context, host) {
|
|
|
865
751
|
let isTyping = false;
|
|
866
752
|
let retriable = false;
|
|
867
753
|
let resultSummary;
|
|
754
|
+
let rateLimitResetsAt;
|
|
868
755
|
const turnToolCalls = [];
|
|
869
756
|
for await (const event of events) {
|
|
870
757
|
if (host.isStopped()) break;
|
|
@@ -914,7 +801,8 @@ async function processEvents(events, context, host) {
|
|
|
914
801
|
break;
|
|
915
802
|
}
|
|
916
803
|
case "rate_limit_event": {
|
|
917
|
-
handleRateLimitEvent(event, host);
|
|
804
|
+
const resetsAt = handleRateLimitEvent(event, host);
|
|
805
|
+
if (resetsAt) rateLimitResetsAt = resetsAt;
|
|
918
806
|
break;
|
|
919
807
|
}
|
|
920
808
|
}
|
|
@@ -922,260 +810,141 @@ async function processEvents(events, context, host) {
|
|
|
922
810
|
if (isTyping) {
|
|
923
811
|
host.connection.sendTypingStop();
|
|
924
812
|
}
|
|
925
|
-
return { retriable, resultSummary };
|
|
813
|
+
return { retriable, resultSummary, rateLimitResetsAt };
|
|
926
814
|
}
|
|
927
815
|
|
|
928
816
|
// src/execution/query-executor.ts
|
|
929
|
-
import { randomUUID } from "crypto";
|
|
930
817
|
import {
|
|
931
818
|
query
|
|
932
819
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
933
820
|
|
|
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
|
-
}
|
|
821
|
+
// src/execution/pack-runner-prompt.ts
|
|
942
822
|
function findLastAgentMessageIndex(history) {
|
|
943
823
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
944
824
|
if (history[i].role === "assistant") return i;
|
|
945
825
|
}
|
|
946
826
|
return -1;
|
|
947
827
|
}
|
|
948
|
-
function
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
return
|
|
828
|
+
function formatProjectAgents(projectAgents) {
|
|
829
|
+
const parts = [``, `## Project Agents`];
|
|
830
|
+
for (const pa of projectAgents) {
|
|
831
|
+
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
832
|
+
const sp = pa.storyPoints === null || pa.storyPoints === void 0 ? "" : `, story points: ${pa.storyPoints}`;
|
|
833
|
+
parts.push(`- ${pa.agent.name} (${role}${sp})`);
|
|
834
|
+
}
|
|
835
|
+
return parts;
|
|
956
836
|
}
|
|
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
|
-
}
|
|
837
|
+
function formatStoryPoints(storyPoints) {
|
|
838
|
+
const parts = [``, `## Story Point Tiers`];
|
|
839
|
+
for (const sp of storyPoints) {
|
|
840
|
+
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
841
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1009
842
|
}
|
|
1010
|
-
return parts
|
|
843
|
+
return parts;
|
|
1011
844
|
}
|
|
1012
|
-
function
|
|
1013
|
-
const parts = [
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
845
|
+
function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
846
|
+
const parts = [
|
|
847
|
+
`You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
|
|
848
|
+
`You are running locally with full access to the repository and task management tools.`,
|
|
849
|
+
`Your job is to sequentially execute child tasks by firing cloud builds, reviewing their PRs, and merging them.`,
|
|
850
|
+
``,
|
|
851
|
+
`## Child Task Status Lifecycle`,
|
|
852
|
+
`- "Planning" \u2014 Not ready for execution. Skip it (or escalate if blocking).`,
|
|
853
|
+
`- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
|
|
854
|
+
`- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
|
|
855
|
+
`- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
|
|
856
|
+
`- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
|
|
857
|
+
`- "Complete" \u2014 Fully done. Move on.`,
|
|
858
|
+
``,
|
|
859
|
+
`## Autonomous Loop`,
|
|
860
|
+
`Follow this loop each time you are launched or relaunched:`,
|
|
861
|
+
``,
|
|
862
|
+
`1. Call list_subtasks to see the current state of all child tasks.`,
|
|
863
|
+
` The response includes PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId).`,
|
|
864
|
+
``,
|
|
865
|
+
`2. Evaluate each child by status (in ordinal order):`,
|
|
866
|
+
` - "ReviewPR": Review and merge its PR with approve_and_merge_pr.`,
|
|
867
|
+
` - 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.`,
|
|
868
|
+
` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check what went wrong. Escalate to the team in chat.`,
|
|
869
|
+
` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
|
|
870
|
+
` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
|
|
871
|
+
` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
|
|
872
|
+
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
873
|
+
` - "Planning": Not ready. If this is blocking progress, notify the team.`,
|
|
874
|
+
``,
|
|
875
|
+
`3. After merging a PR: run \`git pull origin ${context.baseBranch}\` to get the merged changes before firing the next child.`,
|
|
876
|
+
``,
|
|
877
|
+
`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.`,
|
|
878
|
+
``,
|
|
879
|
+
`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").`,
|
|
880
|
+
``,
|
|
881
|
+
`## Important Rules`,
|
|
882
|
+
`- Process children ONE at a time, in ordinal order.`,
|
|
883
|
+
`- 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.`,
|
|
884
|
+
`- Do NOT attempt to write code yourself. Your role is coordination only.`,
|
|
885
|
+
`- 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.`,
|
|
886
|
+
`- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
|
|
887
|
+
`- list_subtasks returns PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId) for each child \u2014 use this to verify readiness before firing builds.`,
|
|
888
|
+
`- You can use read_task_chat to check for team messages.`
|
|
889
|
+
];
|
|
890
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
891
|
+
parts.push(...formatStoryPoints(context.storyPoints));
|
|
1019
892
|
}
|
|
1020
|
-
if (context.
|
|
1021
|
-
parts.push(
|
|
1022
|
-
## Plan
|
|
1023
|
-
${context.plan}`);
|
|
893
|
+
if (context.projectAgents && context.projectAgents.length > 0) {
|
|
894
|
+
parts.push(...formatProjectAgents(context.projectAgents));
|
|
1024
895
|
}
|
|
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
|
-
}
|
|
896
|
+
if (setupLog.length > 0) {
|
|
897
|
+
parts.push(``, `## Environment setup log`, "```", ...setupLog, "```");
|
|
1044
898
|
}
|
|
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
|
-
}
|
|
899
|
+
if (context.agentInstructions) {
|
|
900
|
+
parts.push(``, `## Agent Instructions`, context.agentInstructions);
|
|
1052
901
|
}
|
|
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
|
-
}
|
|
902
|
+
if (config.instructions) {
|
|
903
|
+
parts.push(``, `## Additional Instructions`, config.instructions);
|
|
1082
904
|
}
|
|
1083
|
-
|
|
905
|
+
parts.push(
|
|
906
|
+
``,
|
|
907
|
+
`Your responses are sent directly to the task chat \u2014 the team sees everything you say.`,
|
|
908
|
+
`Do NOT call the post_to_chat tool for your own task; your replies already appear in chat automatically.`,
|
|
909
|
+
`Only use post_to_chat if you need to message a different task's chat (e.g. a child task).`,
|
|
910
|
+
`Use read_task_chat only if you need to re-read earlier messages beyond the chat context above.`
|
|
911
|
+
);
|
|
912
|
+
return parts.join("\n");
|
|
1084
913
|
}
|
|
1085
|
-
function
|
|
914
|
+
function buildPackRunnerInstructions(context, scenario) {
|
|
1086
915
|
const parts = [`
|
|
1087
916
|
## Instructions`];
|
|
1088
|
-
const isPm = mode === "pm";
|
|
1089
|
-
const isAutoMode = agentMode === "auto";
|
|
1090
917
|
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
|
-
}
|
|
918
|
+
parts.push(
|
|
919
|
+
`You are the Pack Runner for this task and its subtasks.`,
|
|
920
|
+
`Begin your autonomous loop immediately: call list_subtasks to assess the current state.`,
|
|
921
|
+
`If any child is in "ReviewPR" status, review and merge its PR first.`,
|
|
922
|
+
`Then fire the next "Open" child task.`
|
|
923
|
+
);
|
|
1122
924
|
} 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
|
-
}
|
|
925
|
+
parts.push(
|
|
926
|
+
`You have been relaunched \u2014 a child task likely changed status (completed work, opened a PR, or was merged).`,
|
|
927
|
+
`Call list_subtasks to check the current state of all children.`,
|
|
928
|
+
`Look for children in "ReviewPR" status first \u2014 review and merge their PRs.`,
|
|
929
|
+
`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.`,
|
|
930
|
+
`If no children need action (all InProgress or waiting), state you are going idle.`
|
|
931
|
+
);
|
|
1141
932
|
} else {
|
|
1142
933
|
const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
|
|
1143
934
|
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
|
-
`
|
|
935
|
+
parts.push(
|
|
936
|
+
`You have been relaunched with new messages.`,
|
|
937
|
+
`
|
|
1160
938
|
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
|
-
}
|
|
939
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
940
|
+
`
|
|
941
|
+
After addressing the feedback, resume your autonomous loop: call list_subtasks and proceed accordingly.`
|
|
942
|
+
);
|
|
1176
943
|
}
|
|
1177
944
|
return parts;
|
|
1178
945
|
}
|
|
946
|
+
|
|
947
|
+
// src/execution/mode-prompt.ts
|
|
1179
948
|
function buildPropertyInstructions(context) {
|
|
1180
949
|
const parts = [];
|
|
1181
950
|
parts.push(
|
|
@@ -1214,16 +983,28 @@ function buildModePrompt(agentMode, context) {
|
|
|
1214
983
|
## Mode: Discovery`,
|
|
1215
984
|
`You are in Discovery mode \u2014 helping plan and scope this task.`,
|
|
1216
985
|
`- You have read-only codebase access (can read files, run git commands, search code)`,
|
|
1217
|
-
`- You
|
|
986
|
+
`- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
|
|
1218
987
|
`- You can create and manage subtasks`,
|
|
1219
|
-
`- You cannot write code or edit files (except .claude/plans/)`,
|
|
1220
988
|
`- Goal: collaborate with the user to create a clear plan`,
|
|
1221
989
|
`- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
|
|
1222
990
|
``,
|
|
991
|
+
`### Self-Identification Tools`,
|
|
992
|
+
`Use these MCP tools to set your own task properties:`,
|
|
993
|
+
`- \`update_task\` \u2014 save your plan and description`,
|
|
994
|
+
`- \`set_story_points\` \u2014 assign story points`,
|
|
995
|
+
`- \`set_task_title\` \u2014 set an accurate title`,
|
|
996
|
+
`- \`set_task_tags\` \u2014 categorize the work`,
|
|
997
|
+
`- \`set_task_icon\` / \`generate_task_icon\` \u2014 set a task icon (call \`list_icons\` first)`,
|
|
998
|
+
``,
|
|
1223
999
|
`### Self-Update vs Subtasks`,
|
|
1224
1000
|
`- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
|
|
1225
1001
|
`- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
|
|
1226
|
-
|
|
1002
|
+
``,
|
|
1003
|
+
`### Finishing Planning`,
|
|
1004
|
+
`Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
|
|
1005
|
+
`- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via set_story_points), **title** (via set_task_title)`,
|
|
1006
|
+
`- ExitPlanMode validates these properties and moves the task to Open status`,
|
|
1007
|
+
`- It does NOT start building \u2014 the team controls when to switch to Build mode`
|
|
1227
1008
|
];
|
|
1228
1009
|
if (context) {
|
|
1229
1010
|
parts.push(...buildPropertyInstructions(context));
|
|
@@ -1287,135 +1068,15 @@ function buildModePrompt(agentMode, context) {
|
|
|
1287
1068
|
return null;
|
|
1288
1069
|
}
|
|
1289
1070
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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");
|
|
1071
|
+
|
|
1072
|
+
// src/execution/system-prompt.ts
|
|
1073
|
+
function formatProjectAgentLine(pa) {
|
|
1074
|
+
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
1075
|
+
const sp = pa.storyPoints === null || pa.storyPoints === void 0 ? "" : `, story points: ${pa.storyPoints}`;
|
|
1076
|
+
return `- ${pa.agent.name} (${role}${sp})`;
|
|
1410
1077
|
}
|
|
1411
|
-
function
|
|
1412
|
-
const
|
|
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 = [
|
|
1078
|
+
function buildPmPreamble(context) {
|
|
1079
|
+
const parts = [
|
|
1419
1080
|
`You are an AI project manager helping to plan tasks for the "${context.title}" project.`,
|
|
1420
1081
|
`You are running locally with full access to the repository.`,
|
|
1421
1082
|
`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 +1092,8 @@ Workflow:`,
|
|
|
1431
1092
|
`- 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
1093
|
`- A separate task agent will handle execution after the team reviews and approves your plan.`
|
|
1433
1094
|
];
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1095
|
+
if (context.isParentTask) {
|
|
1096
|
+
parts.push(
|
|
1436
1097
|
`
|
|
1437
1098
|
You are the Project Manager for this set of tasks.`,
|
|
1438
1099
|
`This task has child tasks (subtasks) that are tracked on the board.`,
|
|
@@ -1440,26 +1101,27 @@ You are the Project Manager for this set of tasks.`,
|
|
|
1440
1101
|
`Use the subtask tools (create_subtask, update_subtask, list_subtasks) to manage work breakdown.`
|
|
1441
1102
|
);
|
|
1442
1103
|
}
|
|
1443
|
-
if (
|
|
1444
|
-
|
|
1104
|
+
if (context.storyPoints && context.storyPoints.length > 0) {
|
|
1105
|
+
parts.push(`
|
|
1445
1106
|
Story Point Tiers:`);
|
|
1446
1107
|
for (const sp of context.storyPoints) {
|
|
1447
1108
|
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
1448
|
-
|
|
1109
|
+
parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
1449
1110
|
}
|
|
1450
1111
|
}
|
|
1451
|
-
if (
|
|
1452
|
-
|
|
1112
|
+
if (context.projectAgents && context.projectAgents.length > 0) {
|
|
1113
|
+
parts.push(`
|
|
1453
1114
|
Project Agents:`);
|
|
1454
1115
|
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})`);
|
|
1116
|
+
parts.push(formatProjectAgentLine(pa));
|
|
1458
1117
|
}
|
|
1459
1118
|
}
|
|
1460
|
-
|
|
1119
|
+
return parts;
|
|
1120
|
+
}
|
|
1121
|
+
function buildActivePreamble(context, workspaceDir) {
|
|
1122
|
+
return [
|
|
1461
1123
|
`You are an AI project manager in ACTIVE mode for the "${context.title}" project.`,
|
|
1462
|
-
`You have direct coding access to the repository at ${
|
|
1124
|
+
`You have direct coding access to the repository at ${workspaceDir}.`,
|
|
1463
1125
|
`You can edit files, run tests, and make commits.`,
|
|
1464
1126
|
`You still have access to all PM tools (subtasks, update_task, chat).`,
|
|
1465
1127
|
`
|
|
@@ -1470,7 +1132,7 @@ Environment (ready, no setup required):`,
|
|
|
1470
1132
|
context.githubBranch ? `- You are working on branch: \`${context.githubBranch}\`` : "",
|
|
1471
1133
|
`
|
|
1472
1134
|
Safety rules:`,
|
|
1473
|
-
`- Stay within the project directory (${
|
|
1135
|
+
`- Stay within the project directory (${workspaceDir}).`,
|
|
1474
1136
|
`- Do NOT run \`git push --force\` or \`git reset --hard\`. Use \`--force-with-lease\` if needed.`,
|
|
1475
1137
|
`- Do NOT delete \`.env\` files or modify \`node_modules\`.`,
|
|
1476
1138
|
`- Do NOT run destructive commands like \`rm -rf /\`.`,
|
|
@@ -1480,7 +1142,9 @@ Workflow:`,
|
|
|
1480
1142
|
`- When done with changes, summarize what you did in your reply.`,
|
|
1481
1143
|
`- If you toggled into active mode temporarily, mention when you're done so the team can switch you back to planning mode.`
|
|
1482
1144
|
].filter(Boolean);
|
|
1483
|
-
|
|
1145
|
+
}
|
|
1146
|
+
function buildTaskAgentPreamble(context) {
|
|
1147
|
+
return [
|
|
1484
1148
|
`You are an AI agent working on a task for the "${context.title}" project.`,
|
|
1485
1149
|
`You are running inside a GitHub Codespace with full access to the repository.`,
|
|
1486
1150
|
`
|
|
@@ -1505,6 +1169,15 @@ Git safety \u2014 STRICT rules:`,
|
|
|
1505
1169
|
`- This branch was created from \`${context.baseBranch}\`. PRs will automatically target that branch.`,
|
|
1506
1170
|
`- 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
1171
|
];
|
|
1172
|
+
}
|
|
1173
|
+
function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
|
|
1174
|
+
const isPm = mode === "pm";
|
|
1175
|
+
const isPmActive = isPm && agentMode === "building";
|
|
1176
|
+
const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
|
|
1177
|
+
if (isPackRunner) {
|
|
1178
|
+
return buildPackRunnerSystemPrompt(context, config, setupLog);
|
|
1179
|
+
}
|
|
1180
|
+
const parts = isPmActive ? buildActivePreamble(context, config.workspaceDir) : isPm ? buildPmPreamble(context) : buildTaskAgentPreamble(context);
|
|
1508
1181
|
if (setupLog.length > 0) {
|
|
1509
1182
|
parts.push(
|
|
1510
1183
|
`
|
|
@@ -1536,11 +1209,306 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
|
|
|
1536
1209
|
`Use the create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
|
|
1537
1210
|
);
|
|
1538
1211
|
}
|
|
1539
|
-
const modePrompt = buildModePrompt(agentMode, context);
|
|
1540
|
-
if (modePrompt) {
|
|
1541
|
-
parts.push(modePrompt);
|
|
1212
|
+
const modePrompt = buildModePrompt(agentMode, context);
|
|
1213
|
+
if (modePrompt) {
|
|
1214
|
+
parts.push(modePrompt);
|
|
1215
|
+
}
|
|
1216
|
+
return parts.join("\n");
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/execution/prompt-builder.ts
|
|
1220
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["InProgress", "ReviewPR", "ReviewDev", "ReviewLive"]);
|
|
1221
|
+
function formatFileSize(bytes) {
|
|
1222
|
+
if (bytes === void 0) return "";
|
|
1223
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
1224
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
1225
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1226
|
+
}
|
|
1227
|
+
function findLastAgentMessageIndex2(history) {
|
|
1228
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
1229
|
+
if (history[i].role === "assistant") return i;
|
|
1230
|
+
}
|
|
1231
|
+
return -1;
|
|
1232
|
+
}
|
|
1233
|
+
function detectRelaunchScenario(context) {
|
|
1234
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1235
|
+
if (lastAgentIdx === -1) return "fresh";
|
|
1236
|
+
const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId || ACTIVE_STATUSES.has(context.status ?? "");
|
|
1237
|
+
if (!hasPriorWork) return "fresh";
|
|
1238
|
+
const messagesAfterAgent = context.chatHistory.slice(lastAgentIdx + 1);
|
|
1239
|
+
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
1240
|
+
return hasNewUserMessages ? "feedback_relaunch" : "idle_relaunch";
|
|
1241
|
+
}
|
|
1242
|
+
function buildRelaunchWithSession(mode, context) {
|
|
1243
|
+
const scenario = detectRelaunchScenario(context);
|
|
1244
|
+
if (!context.claudeSessionId || scenario === "fresh") return null;
|
|
1245
|
+
const parts = [];
|
|
1246
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1247
|
+
if (mode === "pm") {
|
|
1248
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1249
|
+
if (newMessages.length > 0) {
|
|
1250
|
+
parts.push(
|
|
1251
|
+
`You have been relaunched. Here are new messages since your last session:`,
|
|
1252
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`)
|
|
1253
|
+
);
|
|
1254
|
+
} else {
|
|
1255
|
+
parts.push(`You have been relaunched. No new messages since your last session.`);
|
|
1256
|
+
}
|
|
1257
|
+
parts.push(
|
|
1258
|
+
`
|
|
1259
|
+
You are the project manager for this task.`,
|
|
1260
|
+
`Review the context above and wait for the team to provide instructions before taking action.`
|
|
1261
|
+
);
|
|
1262
|
+
} else if (scenario === "feedback_relaunch") {
|
|
1263
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1264
|
+
parts.push(
|
|
1265
|
+
`You have been relaunched with new feedback.`,
|
|
1266
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1267
|
+
`
|
|
1268
|
+
New messages since your last run:`,
|
|
1269
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1270
|
+
`
|
|
1271
|
+
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.`,
|
|
1272
|
+
`Commit and push your updates.`
|
|
1273
|
+
);
|
|
1274
|
+
if (context.githubPRUrl) {
|
|
1275
|
+
parts.push(
|
|
1276
|
+
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch. Do NOT create a new PR.`
|
|
1277
|
+
);
|
|
1278
|
+
} else {
|
|
1279
|
+
parts.push(
|
|
1280
|
+
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI.`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
} else {
|
|
1284
|
+
parts.push(
|
|
1285
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1286
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1287
|
+
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
1288
|
+
`Review the current state of the codebase and verify everything is working correctly.`,
|
|
1289
|
+
`Reply with a brief status update (visible in chat), then wait for further instructions.`
|
|
1290
|
+
);
|
|
1291
|
+
if (context.githubPRUrl) {
|
|
1292
|
+
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return parts.join("\n");
|
|
1296
|
+
}
|
|
1297
|
+
function formatChatFile(file) {
|
|
1298
|
+
const sizeStr = file.fileSize ? `, ${formatFileSize(file.fileSize)}` : "";
|
|
1299
|
+
if (file.content && file.contentEncoding === "utf-8") {
|
|
1300
|
+
return [
|
|
1301
|
+
`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`,
|
|
1302
|
+
"```",
|
|
1303
|
+
file.content,
|
|
1304
|
+
"```"
|
|
1305
|
+
];
|
|
1306
|
+
}
|
|
1307
|
+
if (!file.content) {
|
|
1308
|
+
return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]: ${file.downloadUrl}`];
|
|
1309
|
+
}
|
|
1310
|
+
if (file.content && file.contentEncoding === "base64") {
|
|
1311
|
+
return [
|
|
1312
|
+
`[Attached image: ${file.fileName} (${file.mimeType}${sizeStr}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1313
|
+
];
|
|
1314
|
+
}
|
|
1315
|
+
return [`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`];
|
|
1316
|
+
}
|
|
1317
|
+
function formatTaskFile(file) {
|
|
1318
|
+
if (file.content && file.contentEncoding === "utf-8") {
|
|
1319
|
+
return [`
|
|
1320
|
+
### ${file.fileName} (${file.mimeType})`, "```", file.content, "```"];
|
|
1321
|
+
}
|
|
1322
|
+
if (file.content && file.contentEncoding === "base64") {
|
|
1323
|
+
const size = formatFileSize(file.fileSize);
|
|
1324
|
+
return [
|
|
1325
|
+
`- [Attached image: ${file.fileName} (${file.mimeType}${size ? `, ${size}` : ""}) \u2014 use get_task_file("${file.fileId}") to view]`
|
|
1326
|
+
];
|
|
1327
|
+
}
|
|
1328
|
+
if (!file.content) {
|
|
1329
|
+
return [`- **${file.fileName}** (${file.mimeType}): ${file.downloadUrl}`];
|
|
1330
|
+
}
|
|
1331
|
+
return [];
|
|
1332
|
+
}
|
|
1333
|
+
function formatChatHistory(chatHistory) {
|
|
1334
|
+
const relevant = chatHistory.slice(-20);
|
|
1335
|
+
const parts = [`
|
|
1336
|
+
## Recent Chat Context`];
|
|
1337
|
+
for (const msg of relevant) {
|
|
1338
|
+
const sender = msg.userName ?? msg.role;
|
|
1339
|
+
parts.push(`[${sender}]: ${msg.content}`);
|
|
1340
|
+
if (msg.files?.length) {
|
|
1341
|
+
for (const file of msg.files) {
|
|
1342
|
+
parts.push(...formatChatFile(file));
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return parts;
|
|
1347
|
+
}
|
|
1348
|
+
function buildTaskBody(context) {
|
|
1349
|
+
const parts = [];
|
|
1350
|
+
parts.push(`# Task: ${context.title}`);
|
|
1351
|
+
if (context.description) {
|
|
1352
|
+
parts.push(`
|
|
1353
|
+
## Description
|
|
1354
|
+
${context.description}`);
|
|
1355
|
+
}
|
|
1356
|
+
if (context.plan) {
|
|
1357
|
+
parts.push(`
|
|
1358
|
+
## Plan
|
|
1359
|
+
${context.plan}`);
|
|
1360
|
+
}
|
|
1361
|
+
if (context.files && context.files.length > 0) {
|
|
1362
|
+
parts.push(`
|
|
1363
|
+
## Attached Files`);
|
|
1364
|
+
for (const file of context.files) {
|
|
1365
|
+
parts.push(...formatTaskFile(file));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (context.repoRefs && context.repoRefs.length > 0) {
|
|
1369
|
+
parts.push(`
|
|
1370
|
+
## Repository References`);
|
|
1371
|
+
for (const ref of context.repoRefs) {
|
|
1372
|
+
const icon = ref.refType === "folder" ? "folder" : "file";
|
|
1373
|
+
parts.push(`- [${icon}] \`${ref.path}\``);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
if (context.chatHistory.length > 0) {
|
|
1377
|
+
parts.push(...formatChatHistory(context.chatHistory));
|
|
1378
|
+
}
|
|
1379
|
+
return parts;
|
|
1380
|
+
}
|
|
1381
|
+
function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
|
|
1382
|
+
if (isPm && agentMode === "building") {
|
|
1383
|
+
return [
|
|
1384
|
+
`Your plan has been approved. Begin implementing it now.`,
|
|
1385
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1386
|
+
`Start by reading the relevant source files mentioned in the plan, then write code.`,
|
|
1387
|
+
`When finished, commit and push your changes, then use the create_pull_request tool to open a PR. Do NOT use gh CLI.`
|
|
1388
|
+
];
|
|
1389
|
+
}
|
|
1390
|
+
if (isAutoMode && isPm) {
|
|
1391
|
+
return [
|
|
1392
|
+
`You are operating autonomously. Begin planning immediately.`,
|
|
1393
|
+
`1. Explore the codebase to understand the architecture and relevant files`,
|
|
1394
|
+
`2. Draft a clear implementation plan and save it with update_task`,
|
|
1395
|
+
`3. Set story points (set_story_points), tags (set_task_tags), and title (set_task_title)`,
|
|
1396
|
+
`4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
|
|
1397
|
+
`Do NOT wait for team input \u2014 proceed autonomously.`
|
|
1398
|
+
];
|
|
1399
|
+
}
|
|
1400
|
+
if (isPm && context.isParentTask) {
|
|
1401
|
+
return [
|
|
1402
|
+
`You are the project manager for this task and its subtasks.`,
|
|
1403
|
+
`Use list_subtasks to review the current state of child tasks.`,
|
|
1404
|
+
`The task details are provided above. Wait for the team to provide instructions before taking action.`,
|
|
1405
|
+
`When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
|
|
1406
|
+
];
|
|
1407
|
+
}
|
|
1408
|
+
if (isPm) {
|
|
1409
|
+
return [
|
|
1410
|
+
`You are the project manager for this task.`,
|
|
1411
|
+
`The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
|
|
1412
|
+
`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.`
|
|
1413
|
+
];
|
|
1414
|
+
}
|
|
1415
|
+
return [
|
|
1416
|
+
`Begin executing the task plan above immediately.`,
|
|
1417
|
+
`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.`,
|
|
1418
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch for the entire task. Do not checkout or create other branches.`,
|
|
1419
|
+
`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.`,
|
|
1420
|
+
`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.`
|
|
1421
|
+
];
|
|
1422
|
+
}
|
|
1423
|
+
function buildFeedbackInstructions(context, isPm) {
|
|
1424
|
+
const lastAgentIdx = findLastAgentMessageIndex2(context.chatHistory);
|
|
1425
|
+
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
1426
|
+
if (isPm) {
|
|
1427
|
+
return [
|
|
1428
|
+
`You were relaunched with new feedback since your last run.`,
|
|
1429
|
+
`You are the project manager for this task.`,
|
|
1430
|
+
`
|
|
1431
|
+
New messages since your last run:`,
|
|
1432
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1433
|
+
`
|
|
1434
|
+
Review these messages and wait for the team to provide instructions before taking action.`
|
|
1435
|
+
];
|
|
1436
|
+
}
|
|
1437
|
+
const parts = [
|
|
1438
|
+
`You have been relaunched to address feedback on your previous work.`,
|
|
1439
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1440
|
+
`Start by running \`git log --oneline -10\` and \`git diff HEAD~3 HEAD --stat\` to review what you already committed.`,
|
|
1441
|
+
`
|
|
1442
|
+
New messages since your last run:`,
|
|
1443
|
+
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
1444
|
+
`
|
|
1445
|
+
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.`,
|
|
1446
|
+
`Commit and push your updates.`
|
|
1447
|
+
];
|
|
1448
|
+
if (context.githubPRUrl) {
|
|
1449
|
+
parts.push(
|
|
1450
|
+
`An existing PR is open at ${context.githubPRUrl} \u2014 push to the same branch to update it. Do NOT create a new PR.`
|
|
1451
|
+
);
|
|
1452
|
+
} else {
|
|
1453
|
+
parts.push(
|
|
1454
|
+
`When finished, use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
return parts;
|
|
1458
|
+
}
|
|
1459
|
+
function buildInstructions(mode, context, scenario, agentMode) {
|
|
1460
|
+
const parts = [`
|
|
1461
|
+
## Instructions`];
|
|
1462
|
+
const isPm = mode === "pm";
|
|
1463
|
+
if (scenario === "fresh") {
|
|
1464
|
+
parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context, agentMode));
|
|
1465
|
+
return parts;
|
|
1466
|
+
}
|
|
1467
|
+
if (scenario === "idle_relaunch") {
|
|
1468
|
+
if (isPm && (agentMode === "building" || agentMode === "review")) {
|
|
1469
|
+
parts.push(
|
|
1470
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1471
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1472
|
+
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
|
|
1473
|
+
`Reply with a brief status update summarizing where things stand (visible in chat).`,
|
|
1474
|
+
`Then wait for further instructions \u2014 do NOT redo work that was already completed.`
|
|
1475
|
+
);
|
|
1476
|
+
if (context.githubPRUrl) {
|
|
1477
|
+
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1478
|
+
}
|
|
1479
|
+
} else if (isPm) {
|
|
1480
|
+
parts.push(
|
|
1481
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1482
|
+
`You are the project manager for this task.`,
|
|
1483
|
+
`Wait for the team to provide instructions before taking action.`
|
|
1484
|
+
);
|
|
1485
|
+
} else {
|
|
1486
|
+
parts.push(
|
|
1487
|
+
`You were relaunched but no new instructions have been given since your last run.`,
|
|
1488
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
1489
|
+
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
|
|
1490
|
+
`Reply with a brief status update summarizing where things stand (visible in chat).`,
|
|
1491
|
+
`Then wait for further instructions \u2014 do NOT redo work that was already completed.`
|
|
1492
|
+
);
|
|
1493
|
+
if (context.githubPRUrl) {
|
|
1494
|
+
parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return parts;
|
|
1498
|
+
}
|
|
1499
|
+
parts.push(...buildFeedbackInstructions(context, isPm));
|
|
1500
|
+
return parts;
|
|
1501
|
+
}
|
|
1502
|
+
function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
1503
|
+
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
1504
|
+
if (!isPackRunner) {
|
|
1505
|
+
const sessionRelaunch = buildRelaunchWithSession(mode, context);
|
|
1506
|
+
if (sessionRelaunch) return sessionRelaunch;
|
|
1542
1507
|
}
|
|
1543
|
-
|
|
1508
|
+
const scenario = detectRelaunchScenario(context);
|
|
1509
|
+
const body = buildTaskBody(context);
|
|
1510
|
+
const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
|
|
1511
|
+
return [...body, ...instructions].join("\n");
|
|
1544
1512
|
}
|
|
1545
1513
|
|
|
1546
1514
|
// src/tools/index.ts
|
|
@@ -1552,212 +1520,218 @@ import { z } from "zod";
|
|
|
1552
1520
|
function isImageMimeType(mimeType) {
|
|
1553
1521
|
return mimeType.startsWith("image/");
|
|
1554
1522
|
}
|
|
1523
|
+
var cliEventFormatters = {
|
|
1524
|
+
thinking: (e) => e.message ?? "",
|
|
1525
|
+
tool_use: (e) => `${e.tool}: ${e.input?.slice(0, 1e3) ?? ""}`,
|
|
1526
|
+
tool_result: (e) => `${e.tool} \u2192 ${e.output?.slice(0, 500) ?? ""}${e.isError ? " [ERROR]" : ""}`,
|
|
1527
|
+
message: (e) => e.content ?? "",
|
|
1528
|
+
error: (e) => `ERROR: ${e.message ?? ""}`,
|
|
1529
|
+
completed: (e) => `Completed: ${e.summary ?? ""} (cost: $${e.costUsd ?? "?"}, duration: ${e.durationMs ?? "?"}ms)`,
|
|
1530
|
+
setup_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
|
|
1531
|
+
start_command_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
|
|
1532
|
+
turn_end: (e) => `Turn complete (${e.toolCalls?.length ?? 0} tool calls)`
|
|
1533
|
+
};
|
|
1555
1534
|
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
|
-
}
|
|
1535
|
+
const formatter = cliEventFormatters[e.type];
|
|
1536
|
+
return formatter ? formatter(e) : JSON.stringify(e);
|
|
1587
1537
|
}
|
|
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
|
-
}
|
|
1538
|
+
function buildReadTaskChatTool(connection) {
|
|
1539
|
+
return tool(
|
|
1540
|
+
"read_task_chat",
|
|
1541
|
+
"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.",
|
|
1542
|
+
{
|
|
1543
|
+
limit: z.number().optional().describe("Number of recent messages to fetch (default 20)"),
|
|
1544
|
+
task_id: z.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
|
|
1545
|
+
},
|
|
1546
|
+
async ({ limit, task_id }) => {
|
|
1547
|
+
try {
|
|
1548
|
+
const messages = await connection.fetchChatMessages(limit, task_id);
|
|
1549
|
+
return textResult(JSON.stringify(messages, null, 2));
|
|
1550
|
+
} catch {
|
|
1551
|
+
return textResult(
|
|
1552
|
+
JSON.stringify({
|
|
1553
|
+
note: "Could not fetch live chat. Chat history was provided in the initial context."
|
|
1554
|
+
})
|
|
1555
|
+
);
|
|
1631
1556
|
}
|
|
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
|
-
);
|
|
1557
|
+
},
|
|
1558
|
+
{ annotations: { readOnlyHint: true } }
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
function buildPostToChatTool(connection) {
|
|
1562
|
+
return tool(
|
|
1563
|
+
"post_to_chat",
|
|
1564
|
+
"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.",
|
|
1565
|
+
{
|
|
1566
|
+
message: z.string().describe("The message to post to the team"),
|
|
1567
|
+
task_id: z.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
|
|
1568
|
+
},
|
|
1569
|
+
async ({ message, task_id }) => {
|
|
1570
|
+
try {
|
|
1571
|
+
if (task_id) {
|
|
1572
|
+
await connection.postChildChatMessage(task_id, message);
|
|
1573
|
+
return textResult(`Message posted to child task ${task_id} chat.`);
|
|
1652
1574
|
}
|
|
1575
|
+
connection.postChatMessage(message);
|
|
1576
|
+
return textResult("Message posted to task chat.");
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
return textResult(
|
|
1579
|
+
`Failed to post message: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1580
|
+
);
|
|
1653
1581
|
}
|
|
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.");
|
|
1582
|
+
}
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
function buildUpdateTaskStatusTool(connection) {
|
|
1586
|
+
return tool(
|
|
1587
|
+
"update_task_status",
|
|
1588
|
+
"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.",
|
|
1589
|
+
{
|
|
1590
|
+
status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
|
|
1591
|
+
task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
1592
|
+
},
|
|
1593
|
+
async ({ status, task_id }) => {
|
|
1594
|
+
try {
|
|
1595
|
+
if (task_id) {
|
|
1596
|
+
await connection.updateChildStatus(task_id, status);
|
|
1597
|
+
return textResult(`Child task ${task_id} status updated to ${status}.`);
|
|
1732
1598
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1599
|
+
connection.updateStatus(status);
|
|
1600
|
+
return textResult(`Task status updated to ${status}.`);
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
return textResult(
|
|
1603
|
+
`Failed to update status: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
function buildGetTaskPlanTool(connection, config) {
|
|
1610
|
+
return tool(
|
|
1611
|
+
"get_task_plan",
|
|
1612
|
+
"Re-read the latest task plan in case it was updated",
|
|
1613
|
+
{},
|
|
1614
|
+
async () => {
|
|
1615
|
+
try {
|
|
1616
|
+
const ctx = await connection.fetchTaskContext();
|
|
1617
|
+
return textResult(ctx.plan ?? "No plan available.");
|
|
1618
|
+
} catch {
|
|
1619
|
+
return textResult(`Task ID: ${config.taskId} - could not fetch updated plan.`);
|
|
1620
|
+
}
|
|
1621
|
+
},
|
|
1622
|
+
{ annotations: { readOnlyHint: true } }
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
function buildGetTaskTool(connection) {
|
|
1626
|
+
return tool(
|
|
1627
|
+
"get_task",
|
|
1628
|
+
"Look up a task by slug or ID to get its title, description, plan, and status",
|
|
1629
|
+
{
|
|
1630
|
+
slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
|
|
1631
|
+
},
|
|
1632
|
+
async ({ slug_or_id }) => {
|
|
1633
|
+
try {
|
|
1634
|
+
const task = await connection.fetchTask(slug_or_id);
|
|
1635
|
+
return textResult(JSON.stringify(task, null, 2));
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
return textResult(
|
|
1638
|
+
`Failed to get task: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
},
|
|
1642
|
+
{ annotations: { readOnlyHint: true } }
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
function buildGetTaskCliTool(connection) {
|
|
1646
|
+
return tool(
|
|
1647
|
+
"get_task_cli",
|
|
1648
|
+
"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.",
|
|
1649
|
+
{
|
|
1650
|
+
task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
|
|
1651
|
+
source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
|
|
1652
|
+
limit: z.number().optional().describe("Max number of log entries to return (default 50, max 500).")
|
|
1653
|
+
},
|
|
1654
|
+
async ({ task_id, source, limit }) => {
|
|
1655
|
+
try {
|
|
1656
|
+
const effectiveLimit = Math.min(limit ?? 50, 500);
|
|
1657
|
+
const result = await connection.fetchCliHistory(task_id, effectiveLimit, source);
|
|
1658
|
+
const formatted = result.map((entry) => {
|
|
1659
|
+
const time = entry.time;
|
|
1660
|
+
const e = entry.event;
|
|
1661
|
+
return `[${time}] [${e.type}] ${formatCliEvent(e)}`;
|
|
1662
|
+
}).join("\n");
|
|
1663
|
+
return textResult(formatted || "No CLI logs found.");
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
return textResult(
|
|
1666
|
+
`Failed to fetch CLI logs: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
{ annotations: { readOnlyHint: true } }
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
function buildListTaskFilesTool(connection) {
|
|
1674
|
+
return tool(
|
|
1675
|
+
"list_task_files",
|
|
1676
|
+
"List all files attached to this task with metadata (name, type, size) and download URLs",
|
|
1677
|
+
{},
|
|
1678
|
+
async () => {
|
|
1679
|
+
try {
|
|
1680
|
+
const files = await connection.fetchTaskFiles();
|
|
1681
|
+
const metadata = files.map(({ content: _c, ...rest }) => rest);
|
|
1682
|
+
const content = [
|
|
1683
|
+
{ type: "text", text: JSON.stringify(metadata, null, 2) }
|
|
1684
|
+
];
|
|
1685
|
+
for (const file of files) {
|
|
1686
|
+
if (file.content && file.contentEncoding === "base64" && isImageMimeType(file.mimeType)) {
|
|
1687
|
+
content.push(imageBlock(file.content, file.mimeType));
|
|
1751
1688
|
}
|
|
1752
|
-
return { content };
|
|
1753
|
-
} catch (error) {
|
|
1754
|
-
return textResult(
|
|
1755
|
-
`Failed to get task file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1756
|
-
);
|
|
1757
1689
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1690
|
+
return { content };
|
|
1691
|
+
} catch {
|
|
1692
|
+
return textResult("Failed to list task files.");
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
{ annotations: { readOnlyHint: true } }
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
function buildGetTaskFileTool(connection) {
|
|
1699
|
+
return tool(
|
|
1700
|
+
"get_task_file",
|
|
1701
|
+
"Get a specific task file's content and download URL by file ID",
|
|
1702
|
+
{ fileId: z.string().describe("The file ID to retrieve") },
|
|
1703
|
+
async ({ fileId }) => {
|
|
1704
|
+
try {
|
|
1705
|
+
const file = await connection.fetchTaskFile(fileId);
|
|
1706
|
+
const { content: rawContent, ...metadata } = file;
|
|
1707
|
+
const content = [
|
|
1708
|
+
{ type: "text", text: JSON.stringify(metadata, null, 2) }
|
|
1709
|
+
];
|
|
1710
|
+
if (rawContent && file.contentEncoding === "base64" && isImageMimeType(file.mimeType)) {
|
|
1711
|
+
content.push(imageBlock(rawContent, file.mimeType));
|
|
1712
|
+
} else if (rawContent) {
|
|
1713
|
+
content[0] = { type: "text", text: JSON.stringify(file, null, 2) };
|
|
1714
|
+
}
|
|
1715
|
+
return { content };
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
return textResult(
|
|
1718
|
+
`Failed to get task file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
{ annotations: { readOnlyHint: true } }
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
function buildCommonTools(connection, config) {
|
|
1726
|
+
return [
|
|
1727
|
+
buildReadTaskChatTool(connection),
|
|
1728
|
+
buildPostToChatTool(connection),
|
|
1729
|
+
buildUpdateTaskStatusTool(connection),
|
|
1730
|
+
buildGetTaskPlanTool(connection, config),
|
|
1731
|
+
buildGetTaskTool(connection),
|
|
1732
|
+
buildGetTaskCliTool(connection),
|
|
1733
|
+
buildListTaskFilesTool(connection),
|
|
1734
|
+
buildGetTaskFileTool(connection)
|
|
1761
1735
|
];
|
|
1762
1736
|
}
|
|
1763
1737
|
|
|
@@ -1771,26 +1745,8 @@ function buildStoryPointDescription(storyPoints) {
|
|
|
1771
1745
|
}
|
|
1772
1746
|
return "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
|
|
1773
1747
|
}
|
|
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
|
-
),
|
|
1748
|
+
function buildSubtaskTools(connection, spDescription) {
|
|
1749
|
+
return [
|
|
1794
1750
|
tool2(
|
|
1795
1751
|
"create_subtask",
|
|
1796
1752
|
"Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
|
|
@@ -1860,9 +1816,33 @@ function buildPmTools(connection, storyPoints, options) {
|
|
|
1860
1816
|
{ annotations: { readOnlyHint: true } }
|
|
1861
1817
|
)
|
|
1862
1818
|
];
|
|
1863
|
-
|
|
1819
|
+
}
|
|
1820
|
+
function buildPmTools(connection, storyPoints, options) {
|
|
1821
|
+
const spDescription = buildStoryPointDescription(storyPoints);
|
|
1822
|
+
const tools = [
|
|
1823
|
+
tool2(
|
|
1824
|
+
"update_task",
|
|
1825
|
+
"Save the finalized task plan and/or description",
|
|
1826
|
+
{
|
|
1827
|
+
plan: z2.string().optional().describe("The task plan in markdown"),
|
|
1828
|
+
description: z2.string().optional().describe("Updated task description")
|
|
1829
|
+
},
|
|
1830
|
+
async ({ plan, description }) => {
|
|
1831
|
+
try {
|
|
1832
|
+
await Promise.resolve(connection.updateTaskFields({ plan, description }));
|
|
1833
|
+
return textResult("Task updated successfully.");
|
|
1834
|
+
} catch {
|
|
1835
|
+
return textResult("Failed to update task.");
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
),
|
|
1839
|
+
...buildSubtaskTools(connection, spDescription)
|
|
1840
|
+
];
|
|
1841
|
+
if (!options?.includePackTools) return tools;
|
|
1842
|
+
return [...tools, ...buildPackTools(connection)];
|
|
1843
|
+
}
|
|
1844
|
+
function buildPackTools(connection) {
|
|
1864
1845
|
return [
|
|
1865
|
-
...tools,
|
|
1866
1846
|
tool2(
|
|
1867
1847
|
"start_child_cloud_build",
|
|
1868
1848
|
"Start a cloud build for a child task. The child must be in Open status with story points and an agent assigned.",
|
|
@@ -1952,111 +1932,116 @@ function buildTaskTools(connection) {
|
|
|
1952
1932
|
// src/tools/discovery-tools.ts
|
|
1953
1933
|
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
1954
1934
|
import { z as z4 } from "zod";
|
|
1955
|
-
function
|
|
1956
|
-
const spDescription = buildStoryPointDescription(context?.storyPoints);
|
|
1935
|
+
function buildIconTools(connection) {
|
|
1957
1936
|
return [
|
|
1958
1937
|
tool4(
|
|
1959
|
-
"
|
|
1960
|
-
"
|
|
1961
|
-
{
|
|
1962
|
-
async (
|
|
1938
|
+
"list_icons",
|
|
1939
|
+
"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.",
|
|
1940
|
+
{},
|
|
1941
|
+
async () => {
|
|
1963
1942
|
try {
|
|
1964
|
-
connection.
|
|
1965
|
-
return textResult(
|
|
1943
|
+
const icons = await connection.listIcons();
|
|
1944
|
+
return textResult(JSON.stringify(icons, null, 2));
|
|
1966
1945
|
} catch (error) {
|
|
1967
1946
|
return textResult(
|
|
1968
|
-
`Failed to
|
|
1947
|
+
`Failed to list icons: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1969
1948
|
);
|
|
1970
1949
|
}
|
|
1971
|
-
}
|
|
1950
|
+
},
|
|
1951
|
+
{ annotations: { readOnlyHint: true } }
|
|
1972
1952
|
),
|
|
1973
1953
|
tool4(
|
|
1974
|
-
"
|
|
1975
|
-
"Assign
|
|
1954
|
+
"set_task_icon",
|
|
1955
|
+
"Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
|
|
1976
1956
|
{
|
|
1977
|
-
|
|
1957
|
+
iconId: z4.string().describe("The icon ID to assign")
|
|
1978
1958
|
},
|
|
1979
|
-
async ({
|
|
1959
|
+
async ({ iconId }) => {
|
|
1980
1960
|
try {
|
|
1981
|
-
connection.updateTaskProperties({
|
|
1982
|
-
return textResult(
|
|
1961
|
+
await Promise.resolve(connection.updateTaskProperties({ iconId }));
|
|
1962
|
+
return textResult("Icon assigned to task.");
|
|
1983
1963
|
} catch (error) {
|
|
1984
1964
|
return textResult(
|
|
1985
|
-
`Failed to set
|
|
1965
|
+
`Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1986
1966
|
);
|
|
1987
1967
|
}
|
|
1988
1968
|
}
|
|
1989
1969
|
),
|
|
1990
1970
|
tool4(
|
|
1991
|
-
"
|
|
1992
|
-
"
|
|
1971
|
+
"generate_task_icon",
|
|
1972
|
+
"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.",
|
|
1993
1973
|
{
|
|
1994
|
-
|
|
1974
|
+
prompt: z4.string().describe(
|
|
1975
|
+
"Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
|
|
1976
|
+
),
|
|
1977
|
+
aspectRatio: z4.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
|
|
1995
1978
|
},
|
|
1996
|
-
async ({
|
|
1979
|
+
async ({ prompt, aspectRatio }) => {
|
|
1997
1980
|
try {
|
|
1998
|
-
connection.
|
|
1999
|
-
return textResult(`
|
|
1981
|
+
const result = await connection.generateTaskIcon(prompt, aspectRatio ?? "square");
|
|
1982
|
+
return textResult(`Icon generated and assigned: ${result.iconId}`);
|
|
2000
1983
|
} catch (error) {
|
|
2001
1984
|
return textResult(
|
|
2002
|
-
`Failed to
|
|
1985
|
+
`Failed to generate icon: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2003
1986
|
);
|
|
2004
1987
|
}
|
|
2005
1988
|
}
|
|
2006
|
-
)
|
|
1989
|
+
)
|
|
1990
|
+
];
|
|
1991
|
+
}
|
|
1992
|
+
function buildDiscoveryTools(connection, context) {
|
|
1993
|
+
const spDescription = buildStoryPointDescription(context?.storyPoints);
|
|
1994
|
+
return [
|
|
2007
1995
|
tool4(
|
|
2008
|
-
"
|
|
2009
|
-
"
|
|
2010
|
-
{},
|
|
2011
|
-
async () => {
|
|
1996
|
+
"set_story_points",
|
|
1997
|
+
"Set the story point estimate for this task. Use after understanding the scope of the work.",
|
|
1998
|
+
{ value: z4.number().describe(spDescription) },
|
|
1999
|
+
async ({ value }) => {
|
|
2012
2000
|
try {
|
|
2013
|
-
|
|
2014
|
-
return textResult(
|
|
2001
|
+
await Promise.resolve(connection.updateTaskProperties({ storyPointValue: value }));
|
|
2002
|
+
return textResult(`Story points set to ${value}`);
|
|
2015
2003
|
} catch (error) {
|
|
2016
2004
|
return textResult(
|
|
2017
|
-
`Failed to
|
|
2005
|
+
`Failed to set story points: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2018
2006
|
);
|
|
2019
2007
|
}
|
|
2020
|
-
}
|
|
2021
|
-
{ annotations: { readOnlyHint: true } }
|
|
2008
|
+
}
|
|
2022
2009
|
),
|
|
2023
2010
|
tool4(
|
|
2024
|
-
"
|
|
2025
|
-
"Assign
|
|
2011
|
+
"set_task_tags",
|
|
2012
|
+
"Assign tags to this task from the project's available tags. Use the tag IDs from the project tags list.",
|
|
2026
2013
|
{
|
|
2027
|
-
|
|
2014
|
+
tagIds: z4.array(z4.string()).describe("Array of tag IDs to assign")
|
|
2028
2015
|
},
|
|
2029
|
-
async ({
|
|
2016
|
+
async ({ tagIds }) => {
|
|
2030
2017
|
try {
|
|
2031
|
-
connection.updateTaskProperties({
|
|
2032
|
-
return textResult(
|
|
2018
|
+
await Promise.resolve(connection.updateTaskProperties({ tagIds }));
|
|
2019
|
+
return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
|
|
2033
2020
|
} catch (error) {
|
|
2034
2021
|
return textResult(
|
|
2035
|
-
`Failed to set
|
|
2022
|
+
`Failed to set tags: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2036
2023
|
);
|
|
2037
2024
|
}
|
|
2038
2025
|
}
|
|
2039
2026
|
),
|
|
2040
2027
|
tool4(
|
|
2041
|
-
"
|
|
2042
|
-
"
|
|
2028
|
+
"set_task_title",
|
|
2029
|
+
"Update the task title to better reflect the planned work.",
|
|
2043
2030
|
{
|
|
2044
|
-
|
|
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")
|
|
2031
|
+
title: z4.string().describe("The new task title")
|
|
2048
2032
|
},
|
|
2049
|
-
async ({
|
|
2033
|
+
async ({ title }) => {
|
|
2050
2034
|
try {
|
|
2051
|
-
|
|
2052
|
-
return textResult(`
|
|
2035
|
+
await Promise.resolve(connection.updateTaskProperties({ title }));
|
|
2036
|
+
return textResult(`Task title updated to: ${title}`);
|
|
2053
2037
|
} catch (error) {
|
|
2054
2038
|
return textResult(
|
|
2055
|
-
`Failed to
|
|
2039
|
+
`Failed to update title: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2056
2040
|
);
|
|
2057
2041
|
}
|
|
2058
2042
|
}
|
|
2059
|
-
)
|
|
2043
|
+
),
|
|
2044
|
+
...buildIconTools(connection)
|
|
2060
2045
|
];
|
|
2061
2046
|
}
|
|
2062
2047
|
|
|
@@ -2067,37 +2052,28 @@ function textResult(text) {
|
|
|
2067
2052
|
function imageBlock(data, mimeType) {
|
|
2068
2053
|
return { type: "image", data, mimeType };
|
|
2069
2054
|
}
|
|
2070
|
-
function
|
|
2071
|
-
const commonTools = buildCommonTools(connection, config);
|
|
2072
|
-
const agentMode = context?.agentMode;
|
|
2073
|
-
let modeTools;
|
|
2055
|
+
function getModeTools(agentMode, connection, config, context) {
|
|
2074
2056
|
switch (agentMode) {
|
|
2075
2057
|
case "building":
|
|
2076
|
-
|
|
2058
|
+
return context?.isParentTask ? [
|
|
2077
2059
|
...buildTaskTools(connection),
|
|
2078
2060
|
...buildPmTools(connection, context?.storyPoints, { includePackTools: true })
|
|
2079
2061
|
] : buildTaskTools(connection);
|
|
2080
|
-
break;
|
|
2081
2062
|
case "review":
|
|
2082
|
-
modeTools = buildPmTools(connection, context?.storyPoints, {
|
|
2083
|
-
includePackTools: !!context?.isParentTask
|
|
2084
|
-
});
|
|
2085
|
-
break;
|
|
2086
2063
|
case "auto":
|
|
2087
|
-
modeTools = buildPmTools(connection, context?.storyPoints, {
|
|
2088
|
-
includePackTools: !!context?.isParentTask
|
|
2089
|
-
});
|
|
2090
|
-
break;
|
|
2091
2064
|
case "discovery":
|
|
2092
2065
|
case "help":
|
|
2093
|
-
|
|
2066
|
+
return buildPmTools(connection, context?.storyPoints, {
|
|
2094
2067
|
includePackTools: !!context?.isParentTask
|
|
2095
2068
|
});
|
|
2096
|
-
break;
|
|
2097
2069
|
default:
|
|
2098
|
-
|
|
2099
|
-
break;
|
|
2070
|
+
return config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : buildTaskTools(connection);
|
|
2100
2071
|
}
|
|
2072
|
+
}
|
|
2073
|
+
function createConveyorMcpServer(connection, config, context) {
|
|
2074
|
+
const commonTools = buildCommonTools(connection, config);
|
|
2075
|
+
const agentMode = context?.agentMode ?? void 0;
|
|
2076
|
+
const modeTools = getModeTools(agentMode, connection, config, context);
|
|
2101
2077
|
const discoveryTools = agentMode === "discovery" || agentMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
2102
2078
|
return createSdkMcpServer({
|
|
2103
2079
|
name: "conveyor",
|
|
@@ -2105,16 +2081,17 @@ function createConveyorMcpServer(connection, config, context) {
|
|
|
2105
2081
|
});
|
|
2106
2082
|
}
|
|
2107
2083
|
|
|
2108
|
-
// src/execution/
|
|
2109
|
-
|
|
2110
|
-
var IMAGE_ERROR_PATTERN = /Could not process image/i;
|
|
2111
|
-
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2084
|
+
// src/execution/tool-access.ts
|
|
2085
|
+
import { randomUUID } from "crypto";
|
|
2112
2086
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
2113
2087
|
var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
|
|
2088
|
+
function isPlanFile(input) {
|
|
2089
|
+
const filePath = String(input.file_path ?? input.path ?? "");
|
|
2090
|
+
return filePath.includes(".claude/plans/");
|
|
2091
|
+
}
|
|
2114
2092
|
function handleDiscoveryToolAccess(toolName, input) {
|
|
2115
2093
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2116
|
-
|
|
2117
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2094
|
+
if (isPlanFile(input)) {
|
|
2118
2095
|
return { behavior: "allow", updatedInput: input };
|
|
2119
2096
|
}
|
|
2120
2097
|
return {
|
|
@@ -2141,8 +2118,7 @@ function handleReviewToolAccess(toolName, input, isParentTask) {
|
|
|
2141
2118
|
return handleBuildingToolAccess(toolName, input);
|
|
2142
2119
|
}
|
|
2143
2120
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2144
|
-
|
|
2145
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2121
|
+
if (isPlanFile(input)) {
|
|
2146
2122
|
return { behavior: "allow", updatedInput: input };
|
|
2147
2123
|
}
|
|
2148
2124
|
return {
|
|
@@ -2163,8 +2139,7 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
|
|
|
2163
2139
|
return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
|
|
2164
2140
|
}
|
|
2165
2141
|
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
2166
|
-
|
|
2167
|
-
if (filePath.includes(".claude/plans/")) {
|
|
2142
|
+
if (isPlanFile(input)) {
|
|
2168
2143
|
return { behavior: "allow", updatedInput: input };
|
|
2169
2144
|
}
|
|
2170
2145
|
return {
|
|
@@ -2174,65 +2149,77 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
|
|
|
2174
2149
|
}
|
|
2175
2150
|
return { behavior: "allow", updatedInput: input };
|
|
2176
2151
|
}
|
|
2177
|
-
function
|
|
2152
|
+
async function handleExitPlanMode(host, input) {
|
|
2153
|
+
try {
|
|
2154
|
+
const taskProps = await host.connection.getTaskProperties();
|
|
2155
|
+
const missingProps = [];
|
|
2156
|
+
if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
|
|
2157
|
+
if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
|
|
2158
|
+
if (!taskProps.title || taskProps.title === "Untitled")
|
|
2159
|
+
missingProps.push("title (use set_task_title)");
|
|
2160
|
+
if (missingProps.length > 0) {
|
|
2161
|
+
return {
|
|
2162
|
+
behavior: "deny",
|
|
2163
|
+
message: [
|
|
2164
|
+
"Cannot exit plan mode yet. Required task properties are missing:",
|
|
2165
|
+
...missingProps.map((p) => `- ${p}`),
|
|
2166
|
+
"",
|
|
2167
|
+
"Fill these in using MCP tools, then try ExitPlanMode again."
|
|
2168
|
+
].join("\n")
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
await host.connection.triggerIdentification();
|
|
2172
|
+
host.hasExitedPlanMode = true;
|
|
2173
|
+
if (host.agentMode === "discovery") {
|
|
2174
|
+
host.connection.postChatMessage(
|
|
2175
|
+
"Task identified and moved to Open. Switch to Build mode when ready to start implementation."
|
|
2176
|
+
);
|
|
2177
|
+
return { behavior: "allow", updatedInput: input };
|
|
2178
|
+
}
|
|
2179
|
+
const newMode = host.isParentTask ? "review" : "building";
|
|
2180
|
+
host.pendingModeRestart = true;
|
|
2181
|
+
if (host.onModeTransition) {
|
|
2182
|
+
host.onModeTransition(newMode);
|
|
2183
|
+
}
|
|
2184
|
+
return { behavior: "allow", updatedInput: input };
|
|
2185
|
+
} catch (err) {
|
|
2186
|
+
return {
|
|
2187
|
+
behavior: "deny",
|
|
2188
|
+
message: `Identification failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function handleAskUserQuestion(host, input) {
|
|
2178
2193
|
const QUESTION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2194
|
+
const questions = input.questions;
|
|
2195
|
+
const requestId = randomUUID();
|
|
2196
|
+
host.connection.emitStatus("waiting_for_input");
|
|
2197
|
+
host.connection.sendEvent({
|
|
2198
|
+
type: "tool_use",
|
|
2199
|
+
tool: "AskUserQuestion",
|
|
2200
|
+
input: JSON.stringify(input)
|
|
2201
|
+
});
|
|
2202
|
+
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
2203
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
2204
|
+
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
2205
|
+
});
|
|
2206
|
+
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
2207
|
+
host.connection.emitStatus("running");
|
|
2208
|
+
if (!answers) {
|
|
2209
|
+
return {
|
|
2210
|
+
behavior: "deny",
|
|
2211
|
+
message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
|
|
2215
|
+
}
|
|
2216
|
+
function buildCanUseTool(host) {
|
|
2179
2217
|
return async (toolName, input) => {
|
|
2180
|
-
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
|
-
}
|
|
2218
|
+
if (toolName === "ExitPlanMode" && (host.agentMode === "auto" || host.agentMode === "discovery") && !host.hasExitedPlanMode) {
|
|
2219
|
+
return await handleExitPlanMode(host, input);
|
|
2213
2220
|
}
|
|
2214
2221
|
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 } };
|
|
2222
|
+
return await handleAskUserQuestion(host, input);
|
|
2236
2223
|
}
|
|
2237
2224
|
switch (host.agentMode) {
|
|
2238
2225
|
case "discovery":
|
|
@@ -2248,24 +2235,13 @@ function buildCanUseTool(host) {
|
|
|
2248
2235
|
}
|
|
2249
2236
|
};
|
|
2250
2237
|
}
|
|
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 = {
|
|
2238
|
+
|
|
2239
|
+
// src/execution/query-executor.ts
|
|
2240
|
+
var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
2241
|
+
var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
|
|
2242
|
+
var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
|
|
2243
|
+
function buildHooks(host) {
|
|
2244
|
+
return {
|
|
2269
2245
|
PostToolUse: [
|
|
2270
2246
|
{
|
|
2271
2247
|
hooks: [
|
|
@@ -2279,13 +2255,55 @@ function buildQueryOptions(host, context) {
|
|
|
2279
2255
|
isError: false
|
|
2280
2256
|
});
|
|
2281
2257
|
}
|
|
2282
|
-
return { continue: true };
|
|
2258
|
+
return await Promise.resolve({ continue: true });
|
|
2283
2259
|
}
|
|
2284
2260
|
],
|
|
2285
2261
|
timeout: 5
|
|
2286
2262
|
}
|
|
2287
2263
|
]
|
|
2288
2264
|
};
|
|
2265
|
+
}
|
|
2266
|
+
function buildSandboxConfig(host) {
|
|
2267
|
+
const apiHostname = new URL(host.config.conveyorApiUrl).hostname;
|
|
2268
|
+
return {
|
|
2269
|
+
enabled: true,
|
|
2270
|
+
autoAllowBashIfSandboxed: true,
|
|
2271
|
+
allowUnsandboxedCommands: false,
|
|
2272
|
+
filesystem: {
|
|
2273
|
+
allowWrite: [`${host.config.workspaceDir}/**`],
|
|
2274
|
+
denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
|
|
2275
|
+
denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
|
|
2276
|
+
},
|
|
2277
|
+
network: {
|
|
2278
|
+
allowedDomains: [apiHostname, "api.anthropic.com"],
|
|
2279
|
+
allowManagedDomainsOnly: true,
|
|
2280
|
+
allowLocalBinding: true
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
2285
|
+
return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
|
|
2286
|
+
}
|
|
2287
|
+
function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
|
|
2288
|
+
const modeDisallowed = isReadOnlyMode(mode, hasExitedPlanMode) ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
2289
|
+
const configured = settings.disallowedTools ?? [];
|
|
2290
|
+
const combined = [...configured, ...modeDisallowed];
|
|
2291
|
+
return combined.length > 0 ? combined : void 0;
|
|
2292
|
+
}
|
|
2293
|
+
function buildQueryOptions(host, context) {
|
|
2294
|
+
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
2295
|
+
const mode = host.agentMode;
|
|
2296
|
+
const isCloud = host.config.mode === "pm";
|
|
2297
|
+
const isReadOnly = isReadOnlyMode(mode, host.hasExitedPlanMode);
|
|
2298
|
+
const needsCanUseTool = isCloud && isReadOnly;
|
|
2299
|
+
const systemPromptText = buildSystemPrompt(
|
|
2300
|
+
host.config.mode,
|
|
2301
|
+
context,
|
|
2302
|
+
host.config,
|
|
2303
|
+
host.setupLog,
|
|
2304
|
+
mode
|
|
2305
|
+
);
|
|
2306
|
+
const settingSources = settings.settingSources ?? ["user", "project"];
|
|
2289
2307
|
const baseOptions = {
|
|
2290
2308
|
model: context.model || host.config.model,
|
|
2291
2309
|
systemPrompt: {
|
|
@@ -2295,37 +2313,22 @@ function buildQueryOptions(host, context) {
|
|
|
2295
2313
|
},
|
|
2296
2314
|
settingSources,
|
|
2297
2315
|
cwd: host.config.workspaceDir,
|
|
2298
|
-
permissionMode:
|
|
2299
|
-
allowDangerouslySkipPermissions: !
|
|
2316
|
+
permissionMode: needsCanUseTool ? "acceptEdits" : "bypassPermissions",
|
|
2317
|
+
allowDangerouslySkipPermissions: !needsCanUseTool,
|
|
2300
2318
|
canUseTool: buildCanUseTool(host),
|
|
2301
2319
|
tools: { type: "preset", preset: "claude_code" },
|
|
2302
|
-
mcpServers: { conveyor:
|
|
2303
|
-
hooks,
|
|
2320
|
+
mcpServers: { conveyor: createConveyorMcpServer(host.connection, host.config, context) },
|
|
2321
|
+
hooks: buildHooks(host),
|
|
2304
2322
|
maxTurns: settings.maxTurns,
|
|
2305
2323
|
effort: settings.effort,
|
|
2306
2324
|
thinking: settings.thinking,
|
|
2307
2325
|
betas: settings.betas,
|
|
2308
2326
|
maxBudgetUsd: settings.maxBudgetUsd ?? 50,
|
|
2309
|
-
disallowedTools:
|
|
2327
|
+
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
2310
2328
|
enableFileCheckpointing: settings.enableFileCheckpointing
|
|
2311
2329
|
};
|
|
2312
|
-
if (
|
|
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
|
-
};
|
|
2330
|
+
if (needsCanUseTool) {
|
|
2331
|
+
baseOptions.sandbox = buildSandboxConfig(host);
|
|
2329
2332
|
}
|
|
2330
2333
|
return baseOptions;
|
|
2331
2334
|
}
|
|
@@ -2350,34 +2353,47 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
|
|
|
2350
2353
|
source: {
|
|
2351
2354
|
type: "base64",
|
|
2352
2355
|
media_type: file.mimeType,
|
|
2353
|
-
data: file.content
|
|
2356
|
+
data: file.content ?? ""
|
|
2354
2357
|
}
|
|
2355
2358
|
});
|
|
2356
|
-
blocks.push({
|
|
2357
|
-
type: "text",
|
|
2358
|
-
text: `[Attached image: ${file.fileName} (${file.mimeType})]`
|
|
2359
|
-
});
|
|
2359
|
+
blocks.push({ type: "text", text: `[Attached image: ${file.fileName} (${file.mimeType})]` });
|
|
2360
2360
|
}
|
|
2361
2361
|
for (const file of chatImages) {
|
|
2362
2362
|
blocks.push({
|
|
2363
2363
|
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})]`
|
|
2364
|
+
source: { type: "base64", media_type: file.mimeType, data: file.content }
|
|
2373
2365
|
});
|
|
2366
|
+
blocks.push({ type: "text", text: `[Chat image: ${file.fileName} (${file.mimeType})]` });
|
|
2374
2367
|
}
|
|
2375
2368
|
return blocks;
|
|
2376
2369
|
}
|
|
2370
|
+
function buildFollowUpPrompt(host, context, followUpContent) {
|
|
2371
|
+
const isPmMode = host.config.mode === "pm";
|
|
2372
|
+
const followUpText = typeof followUpContent === "string" ? followUpContent : followUpContent.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
2373
|
+
const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
|
|
2374
|
+
(b) => b.type === "image"
|
|
2375
|
+
);
|
|
2376
|
+
const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode)}
|
|
2377
|
+
|
|
2378
|
+
---
|
|
2379
|
+
|
|
2380
|
+
The team says:
|
|
2381
|
+
${followUpText}` : followUpText;
|
|
2382
|
+
if (isPmMode) {
|
|
2383
|
+
const prompt = buildMultimodalPrompt(textPrompt, context);
|
|
2384
|
+
if (followUpImages.length > 0 && Array.isArray(prompt)) {
|
|
2385
|
+
prompt.push(...followUpImages);
|
|
2386
|
+
}
|
|
2387
|
+
return prompt;
|
|
2388
|
+
}
|
|
2389
|
+
if (followUpImages.length > 0) {
|
|
2390
|
+
return [{ type: "text", text: textPrompt }, ...followUpImages];
|
|
2391
|
+
}
|
|
2392
|
+
return textPrompt;
|
|
2393
|
+
}
|
|
2377
2394
|
async function runSdkQuery(host, context, followUpContent) {
|
|
2378
2395
|
if (host.isStopped()) return;
|
|
2379
2396
|
const mode = host.agentMode;
|
|
2380
|
-
const isPmMode = host.config.mode === "pm";
|
|
2381
2397
|
const isDiscoveryLike = mode === "discovery" || mode === "help";
|
|
2382
2398
|
if (isDiscoveryLike) {
|
|
2383
2399
|
host.snapshotPlanFiles();
|
|
@@ -2385,27 +2401,7 @@ async function runSdkQuery(host, context, followUpContent) {
|
|
|
2385
2401
|
const options = buildQueryOptions(host, context);
|
|
2386
2402
|
const resume = context.claudeSessionId ?? void 0;
|
|
2387
2403
|
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
|
-
}
|
|
2404
|
+
const prompt = buildFollowUpPrompt(host, context, followUpContent);
|
|
2409
2405
|
const agentQuery = query({
|
|
2410
2406
|
prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
|
|
2411
2407
|
options: { ...options, resume }
|
|
@@ -2426,55 +2422,105 @@ ${followUpText}` : followUpText;
|
|
|
2426
2422
|
host.syncPlanFile();
|
|
2427
2423
|
}
|
|
2428
2424
|
}
|
|
2425
|
+
function buildRetryQuery(host, context, options, lastErrorWasImage) {
|
|
2426
|
+
if (lastErrorWasImage) {
|
|
2427
|
+
host.connection.postChatMessage(
|
|
2428
|
+
"An attached image could not be processed. Retrying without images..."
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
const retryPrompt = buildMultimodalPrompt(
|
|
2432
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2433
|
+
context,
|
|
2434
|
+
lastErrorWasImage
|
|
2435
|
+
);
|
|
2436
|
+
return query({
|
|
2437
|
+
prompt: host.createInputStream(retryPrompt),
|
|
2438
|
+
options: { ...options, resume: void 0 }
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
function handleStaleSession(context, host, options) {
|
|
2442
|
+
context.claudeSessionId = null;
|
|
2443
|
+
host.connection.storeSessionId("");
|
|
2444
|
+
const freshPrompt = buildMultimodalPrompt(
|
|
2445
|
+
buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
|
|
2446
|
+
context
|
|
2447
|
+
);
|
|
2448
|
+
const freshQuery = query({
|
|
2449
|
+
prompt: host.createInputStream(freshPrompt),
|
|
2450
|
+
options: { ...options, resume: void 0 }
|
|
2451
|
+
});
|
|
2452
|
+
return runWithRetry(freshQuery, context, host, options);
|
|
2453
|
+
}
|
|
2454
|
+
async function waitForRetryDelay(host, delayMs) {
|
|
2455
|
+
await new Promise((resolve2) => {
|
|
2456
|
+
const timer = setTimeout(resolve2, delayMs);
|
|
2457
|
+
const checkStopped = setInterval(() => {
|
|
2458
|
+
if (host.isStopped()) {
|
|
2459
|
+
clearTimeout(timer);
|
|
2460
|
+
clearInterval(checkStopped);
|
|
2461
|
+
resolve2();
|
|
2462
|
+
}
|
|
2463
|
+
}, 1e3);
|
|
2464
|
+
setTimeout(() => clearInterval(checkStopped), delayMs + 100);
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
function isStaleOrExitedSession(error, context) {
|
|
2468
|
+
if (!(error instanceof Error)) return false;
|
|
2469
|
+
if (error.message.includes("No conversation found with session ID")) return true;
|
|
2470
|
+
return !!context.claudeSessionId && error.message.includes("process exited");
|
|
2471
|
+
}
|
|
2472
|
+
function isRetriableError(error) {
|
|
2473
|
+
if (!(error instanceof Error)) return false;
|
|
2474
|
+
return API_ERROR_PATTERN2.test(error.message) || IMAGE_ERROR_PATTERN2.test(error.message);
|
|
2475
|
+
}
|
|
2476
|
+
function classifyImageError(error) {
|
|
2477
|
+
return error instanceof Error && IMAGE_ERROR_PATTERN2.test(error.message);
|
|
2478
|
+
}
|
|
2479
|
+
async function emitRetryStatus(host, attempt, delayMs) {
|
|
2480
|
+
const delayMin = Math.round(delayMs / 6e4);
|
|
2481
|
+
host.connection.postChatMessage(
|
|
2482
|
+
`API error encountered. Retrying in ${delayMin} minute${delayMin > 1 ? "s" : ""}... (attempt ${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2483
|
+
);
|
|
2484
|
+
host.connection.sendEvent({
|
|
2485
|
+
type: "error",
|
|
2486
|
+
message: `API error, retrying in ${delayMin}m (${attempt + 1}/${RETRY_DELAYS_MS.length})`
|
|
2487
|
+
});
|
|
2488
|
+
host.connection.emitStatus("waiting_for_input");
|
|
2489
|
+
await host.callbacks.onStatusChange("waiting_for_input");
|
|
2490
|
+
await waitForRetryDelay(host, delayMs);
|
|
2491
|
+
host.connection.emitStatus("running");
|
|
2492
|
+
await host.callbacks.onStatusChange("running");
|
|
2493
|
+
}
|
|
2429
2494
|
async function runWithRetry(initialQuery, context, host, options) {
|
|
2430
2495
|
let lastErrorWasImage = false;
|
|
2431
2496
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
2432
2497
|
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
|
-
})();
|
|
2498
|
+
const agentQuery = attempt === 0 ? initialQuery : buildRetryQuery(host, context, options, lastErrorWasImage);
|
|
2449
2499
|
try {
|
|
2450
|
-
const { retriable, resultSummary, modeRestart } = await processEvents(
|
|
2500
|
+
const { retriable, resultSummary, modeRestart, rateLimitResetsAt } = await processEvents(
|
|
2451
2501
|
agentQuery,
|
|
2452
2502
|
context,
|
|
2453
2503
|
host
|
|
2454
2504
|
);
|
|
2455
|
-
if (modeRestart) return;
|
|
2456
|
-
if (
|
|
2457
|
-
|
|
2505
|
+
if (modeRestart || host.isStopped()) return;
|
|
2506
|
+
if (rateLimitResetsAt) {
|
|
2507
|
+
const resetMs = new Date(rateLimitResetsAt).getTime() - Date.now();
|
|
2508
|
+
if (resetMs > 5 * 6e4) {
|
|
2509
|
+
host.connection.emitRateLimitPause(rateLimitResetsAt);
|
|
2510
|
+
host.connection.postChatMessage(
|
|
2511
|
+
`Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
|
|
2512
|
+
);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (!retriable) return;
|
|
2517
|
+
lastErrorWasImage = IMAGE_ERROR_PATTERN2.test(resultSummary ?? "");
|
|
2458
2518
|
} 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);
|
|
2519
|
+
if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
|
|
2520
|
+
return handleStaleSession(context, host, options);
|
|
2473
2521
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
if (isImageError) lastErrorWasImage = true;
|
|
2477
|
-
if (!isApiError && !isImageError) throw error;
|
|
2522
|
+
if (classifyImageError(error)) lastErrorWasImage = true;
|
|
2523
|
+
if (!isRetriableError(error)) throw error;
|
|
2478
2524
|
}
|
|
2479
2525
|
if (attempt >= RETRY_DELAYS_MS.length) {
|
|
2480
2526
|
host.connection.postChatMessage(
|
|
@@ -2482,30 +2528,7 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
2482
2528
|
);
|
|
2483
2529
|
return;
|
|
2484
2530
|
}
|
|
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");
|
|
2531
|
+
await emitRetryStatus(host, attempt, RETRY_DELAYS_MS[attempt]);
|
|
2509
2532
|
}
|
|
2510
2533
|
}
|
|
2511
2534
|
|
|
@@ -2582,19 +2605,7 @@ var PlanSync = class {
|
|
|
2582
2605
|
}
|
|
2583
2606
|
}
|
|
2584
2607
|
}
|
|
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
|
-
}
|
|
2608
|
+
findNewestPlanFile() {
|
|
2598
2609
|
let newest = null;
|
|
2599
2610
|
for (const plansDir of this.getPlanDirs()) {
|
|
2600
2611
|
let files;
|
|
@@ -2617,24 +2628,240 @@ var PlanSync = class {
|
|
|
2617
2628
|
}
|
|
2618
2629
|
}
|
|
2619
2630
|
}
|
|
2631
|
+
return newest;
|
|
2632
|
+
}
|
|
2633
|
+
syncPlanFile() {
|
|
2634
|
+
if (this.lockedPlanFile) {
|
|
2635
|
+
try {
|
|
2636
|
+
const content = readFileSync(this.lockedPlanFile, "utf-8").trim();
|
|
2637
|
+
if (content) {
|
|
2638
|
+
this.connection.updateTaskFields({ plan: content });
|
|
2639
|
+
const fileName = this.lockedPlanFile.split("/").pop() ?? "plan";
|
|
2640
|
+
this.connection.postChatMessage(`Synced local plan file (${fileName}) to the task plan.`);
|
|
2641
|
+
}
|
|
2642
|
+
} catch {
|
|
2643
|
+
}
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
const newest = this.findNewestPlanFile();
|
|
2620
2647
|
if (newest) {
|
|
2621
2648
|
this.lockedPlanFile = newest.path;
|
|
2622
2649
|
const content = readFileSync(newest.path, "utf-8").trim();
|
|
2623
2650
|
if (content) {
|
|
2624
2651
|
this.connection.updateTaskFields({ plan: content });
|
|
2625
|
-
const fileName = newest.path.split("/").pop();
|
|
2652
|
+
const fileName = newest.path.split("/").pop() ?? "plan";
|
|
2626
2653
|
this.connection.postChatMessage(
|
|
2627
2654
|
`Detected local plan file (${fileName}) and synced it to the task plan.`
|
|
2628
2655
|
);
|
|
2629
2656
|
}
|
|
2630
2657
|
}
|
|
2631
2658
|
}
|
|
2632
|
-
};
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
// src/runner/runner-setup.ts
|
|
2662
|
+
var MAX_SETUP_LOG_LINES = 50;
|
|
2663
|
+
function pushSetupLog(setupLog, line) {
|
|
2664
|
+
setupLog.push(line);
|
|
2665
|
+
if (setupLog.length > MAX_SETUP_LOG_LINES) {
|
|
2666
|
+
setupLog.splice(0, setupLog.length - MAX_SETUP_LOG_LINES);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
async function executeSetupConfig(config, runnerConfig, connection, setupLog, effectiveAgentMode) {
|
|
2670
|
+
let deferredStartConfig = null;
|
|
2671
|
+
if (config.setupCommand) {
|
|
2672
|
+
pushSetupLog(setupLog, `$ ${config.setupCommand}`);
|
|
2673
|
+
await runSetupCommand(config.setupCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2674
|
+
connection.sendEvent({ type: "setup_output", stream, data });
|
|
2675
|
+
for (const line of data.split("\n").filter(Boolean)) {
|
|
2676
|
+
pushSetupLog(setupLog, `[${stream}] ${line}`);
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
pushSetupLog(setupLog, "(exit 0)");
|
|
2680
|
+
}
|
|
2681
|
+
if (config.startCommand) {
|
|
2682
|
+
if (effectiveAgentMode === "auto") {
|
|
2683
|
+
deferredStartConfig = config;
|
|
2684
|
+
pushSetupLog(setupLog, `[conveyor] startCommand deferred (auto mode)`);
|
|
2685
|
+
} else {
|
|
2686
|
+
pushSetupLog(setupLog, `$ ${config.startCommand} & (background)`);
|
|
2687
|
+
runStartCommand(config.startCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2688
|
+
connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
return deferredStartConfig;
|
|
2693
|
+
}
|
|
2694
|
+
async function runSetupSafe(runnerConfig, connection, callbacks, setupLog, effectiveAgentMode, setState) {
|
|
2695
|
+
await setState("setup");
|
|
2696
|
+
const ports = await loadForwardPorts(runnerConfig.workspaceDir);
|
|
2697
|
+
if (ports.length > 0 && process.env.CODESPACE_NAME) {
|
|
2698
|
+
const visibility = ports.map((p) => `${p}:public`).join(" ");
|
|
2699
|
+
runStartCommand(
|
|
2700
|
+
`gh codespace ports visibility ${visibility} -c "${process.env.CODESPACE_NAME}" 2>/dev/null`,
|
|
2701
|
+
runnerConfig.workspaceDir,
|
|
2702
|
+
() => void 0
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
const config = await loadConveyorConfig(runnerConfig.workspaceDir);
|
|
2706
|
+
if (!config) {
|
|
2707
|
+
connection.sendEvent({ type: "setup_complete" });
|
|
2708
|
+
await callbacks.onEvent({ type: "setup_complete" });
|
|
2709
|
+
return { ok: true, deferredStartConfig: null };
|
|
2710
|
+
}
|
|
2711
|
+
try {
|
|
2712
|
+
const deferredStartConfig = await executeSetupConfig(
|
|
2713
|
+
config,
|
|
2714
|
+
runnerConfig,
|
|
2715
|
+
connection,
|
|
2716
|
+
setupLog,
|
|
2717
|
+
effectiveAgentMode
|
|
2718
|
+
);
|
|
2719
|
+
const setupEvent = {
|
|
2720
|
+
type: "setup_complete",
|
|
2721
|
+
previewPort: config.previewPort ?? void 0,
|
|
2722
|
+
startCommandDeferred: deferredStartConfig === null ? void 0 : true
|
|
2723
|
+
};
|
|
2724
|
+
connection.sendEvent(setupEvent);
|
|
2725
|
+
await callbacks.onEvent(setupEvent);
|
|
2726
|
+
return { ok: true, deferredStartConfig };
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
const message = error instanceof Error ? error.message : "Setup failed";
|
|
2729
|
+
connection.sendEvent({ type: "setup_error", message });
|
|
2730
|
+
await callbacks.onEvent({ type: "setup_error", message });
|
|
2731
|
+
connection.postChatMessage(
|
|
2732
|
+
`Environment setup failed: ${message}
|
|
2733
|
+
The agent cannot start until this is resolved.`
|
|
2734
|
+
);
|
|
2735
|
+
return { ok: false, deferredStartConfig: null };
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
function runDeferredStartCommand(deferredStartConfig, runnerConfig, connection, setupLog) {
|
|
2739
|
+
if (!deferredStartConfig?.startCommand) return;
|
|
2740
|
+
pushSetupLog(setupLog, `$ ${deferredStartConfig.startCommand} & (background, deferred)`);
|
|
2741
|
+
connection.sendEvent({ type: "start_command_started" });
|
|
2742
|
+
runStartCommand(deferredStartConfig.startCommand, runnerConfig.workspaceDir, (stream, data) => {
|
|
2743
|
+
connection.sendEvent({ type: "start_command_output", stream, data });
|
|
2744
|
+
});
|
|
2745
|
+
const setupEvent = {
|
|
2746
|
+
type: "setup_complete",
|
|
2747
|
+
previewPort: deferredStartConfig.previewPort ?? void 0
|
|
2748
|
+
};
|
|
2749
|
+
connection.sendEvent(setupEvent);
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// src/runner/runner-helpers.ts
|
|
2753
|
+
function buildFileBlock(f) {
|
|
2754
|
+
if (f.content && f.contentEncoding === "base64") {
|
|
2755
|
+
return [
|
|
2756
|
+
{
|
|
2757
|
+
type: "image",
|
|
2758
|
+
source: { type: "base64", media_type: f.mimeType, data: f.content }
|
|
2759
|
+
},
|
|
2760
|
+
{ type: "text", text: `[Attached image: ${f.fileName} (${f.mimeType})]` }
|
|
2761
|
+
];
|
|
2762
|
+
}
|
|
2763
|
+
if (f.content && f.contentEncoding === "utf-8") {
|
|
2764
|
+
return [
|
|
2765
|
+
{
|
|
2766
|
+
type: "text",
|
|
2767
|
+
text: `[Attached file: ${f.fileName} (${f.mimeType})]
|
|
2768
|
+
\`\`\`
|
|
2769
|
+
${f.content}
|
|
2770
|
+
\`\`\``
|
|
2771
|
+
}
|
|
2772
|
+
];
|
|
2773
|
+
}
|
|
2774
|
+
return [
|
|
2775
|
+
{
|
|
2776
|
+
type: "text",
|
|
2777
|
+
text: `[Attached file: ${f.fileName} (${f.mimeType})] Download: ${f.downloadUrl}`
|
|
2778
|
+
}
|
|
2779
|
+
];
|
|
2780
|
+
}
|
|
2781
|
+
function buildMessageContent(content, files) {
|
|
2782
|
+
if (!files?.length) return content;
|
|
2783
|
+
const blocks = [{ type: "text", text: content }];
|
|
2784
|
+
for (const f of files) blocks.push(...buildFileBlock(f));
|
|
2785
|
+
return blocks;
|
|
2786
|
+
}
|
|
2787
|
+
function formatThinkingSetting(thinking) {
|
|
2788
|
+
if (thinking?.type === "enabled") return `enabled(${thinking.budgetTokens ?? "?"})`;
|
|
2789
|
+
return thinking?.type ?? "default";
|
|
2790
|
+
}
|
|
2791
|
+
function getModelForMode(context, mode, isParentTask) {
|
|
2792
|
+
if (mode === "building" && !isParentTask) {
|
|
2793
|
+
return context.builderModel ?? context.model;
|
|
2794
|
+
}
|
|
2795
|
+
return context.pmModel ?? context.model;
|
|
2796
|
+
}
|
|
2797
|
+
async function handleAutoModeRestart(state, connection, setState, runQuerySafe, fetchFreshContext) {
|
|
2798
|
+
const { taskContext } = state;
|
|
2799
|
+
const currentModel = taskContext.model;
|
|
2800
|
+
const currentSessionId = taskContext.claudeSessionId;
|
|
2801
|
+
if (currentSessionId && currentModel) {
|
|
2802
|
+
state.sessionIds.set(currentModel, currentSessionId);
|
|
2803
|
+
}
|
|
2804
|
+
const newMode = state.agentMode;
|
|
2805
|
+
const isParentTask = !!taskContext.isParentTask;
|
|
2806
|
+
const newModel = getModelForMode(taskContext, newMode, isParentTask);
|
|
2807
|
+
taskContext.claudeSessionId = null;
|
|
2808
|
+
connection.storeSessionId("");
|
|
2809
|
+
if (newModel) taskContext.model = newModel;
|
|
2810
|
+
taskContext.agentMode = newMode;
|
|
2811
|
+
connection.emitModeTransition({ fromMode: "auto", toMode: newMode ?? "building" });
|
|
2812
|
+
connection.emitModeChanged(newMode);
|
|
2813
|
+
connection.postChatMessage(
|
|
2814
|
+
`Transitioning to **${newMode}** mode${newModel ? ` with ${newModel}` : ""}. Restarting session...`
|
|
2815
|
+
);
|
|
2816
|
+
const freshContext = await fetchFreshContext();
|
|
2817
|
+
if (freshContext) {
|
|
2818
|
+
freshContext._runnerSessionId = taskContext._runnerSessionId;
|
|
2819
|
+
freshContext.claudeSessionId = taskContext.claudeSessionId;
|
|
2820
|
+
freshContext.agentMode = newMode;
|
|
2821
|
+
if (newModel) freshContext.model = newModel;
|
|
2822
|
+
state.taskContext = freshContext;
|
|
2823
|
+
}
|
|
2824
|
+
await setState("running");
|
|
2825
|
+
await runQuerySafe(state.taskContext);
|
|
2826
|
+
}
|
|
2827
|
+
function buildQueryHost(deps) {
|
|
2828
|
+
return {
|
|
2829
|
+
config: deps.config,
|
|
2830
|
+
connection: deps.connection,
|
|
2831
|
+
callbacks: deps.callbacks,
|
|
2832
|
+
setupLog: deps.setupLog,
|
|
2833
|
+
costTracker: deps.costTracker,
|
|
2834
|
+
get agentMode() {
|
|
2835
|
+
return deps.getEffectiveAgentMode();
|
|
2836
|
+
},
|
|
2837
|
+
get isParentTask() {
|
|
2838
|
+
return deps.getIsParentTask();
|
|
2839
|
+
},
|
|
2840
|
+
get hasExitedPlanMode() {
|
|
2841
|
+
return deps.getHasExitedPlanMode();
|
|
2842
|
+
},
|
|
2843
|
+
set hasExitedPlanMode(val) {
|
|
2844
|
+
deps.setHasExitedPlanMode(val);
|
|
2845
|
+
},
|
|
2846
|
+
get pendingModeRestart() {
|
|
2847
|
+
return deps.getPendingModeRestart();
|
|
2848
|
+
},
|
|
2849
|
+
set pendingModeRestart(val) {
|
|
2850
|
+
deps.setPendingModeRestart(val);
|
|
2851
|
+
},
|
|
2852
|
+
sessionIds: deps.sessionIds,
|
|
2853
|
+
isStopped: deps.isStopped,
|
|
2854
|
+
createInputStream: deps.createInputStream,
|
|
2855
|
+
snapshotPlanFiles: () => deps.planSync.snapshotPlanFiles(),
|
|
2856
|
+
syncPlanFile: () => deps.planSync.syncPlanFile(),
|
|
2857
|
+
onModeTransition: deps.onModeTransition
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2633
2860
|
|
|
2634
2861
|
// src/runner/agent-runner.ts
|
|
2635
2862
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
2636
2863
|
var IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
2637
|
-
var AgentRunner = class
|
|
2864
|
+
var AgentRunner = class {
|
|
2638
2865
|
config;
|
|
2639
2866
|
connection;
|
|
2640
2867
|
callbacks;
|
|
@@ -2653,11 +2880,10 @@ var AgentRunner = class _AgentRunner {
|
|
|
2653
2880
|
pendingModeRestart = false;
|
|
2654
2881
|
sessionIds = /* @__PURE__ */ new Map();
|
|
2655
2882
|
lastQueryModeRestart = false;
|
|
2656
|
-
deferredStartConfig = null;
|
|
2657
2883
|
startCommandStarted = false;
|
|
2658
2884
|
idleTimer = null;
|
|
2659
2885
|
idleCheckInterval = null;
|
|
2660
|
-
|
|
2886
|
+
deferredStartConfig = null;
|
|
2661
2887
|
constructor(config, callbacks) {
|
|
2662
2888
|
this.config = config;
|
|
2663
2889
|
this.connection = new ConveyorConnection(config);
|
|
@@ -2667,15 +2893,10 @@ var AgentRunner = class _AgentRunner {
|
|
|
2667
2893
|
get state() {
|
|
2668
2894
|
return this._state;
|
|
2669
2895
|
}
|
|
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
2896
|
get effectiveAgentMode() {
|
|
2675
2897
|
if (this.agentMode) return this.agentMode;
|
|
2676
2898
|
if (this.config.mode === "pm") {
|
|
2677
|
-
|
|
2678
|
-
return "discovery";
|
|
2899
|
+
return this.config.isAuto ? "auto" : "discovery";
|
|
2679
2900
|
}
|
|
2680
2901
|
return "building";
|
|
2681
2902
|
}
|
|
@@ -2686,9 +2907,7 @@ var AgentRunner = class _AgentRunner {
|
|
|
2686
2907
|
}
|
|
2687
2908
|
startHeartbeat() {
|
|
2688
2909
|
this.heartbeatTimer = setInterval(() => {
|
|
2689
|
-
if (!this.stopped)
|
|
2690
|
-
this.connection.sendHeartbeat();
|
|
2691
|
-
}
|
|
2910
|
+
if (!this.stopped) this.connection.sendHeartbeat();
|
|
2692
2911
|
}, HEARTBEAT_INTERVAL_MS);
|
|
2693
2912
|
}
|
|
2694
2913
|
stopHeartbeat() {
|
|
@@ -2715,32 +2934,44 @@ var AgentRunner = class _AgentRunner {
|
|
|
2715
2934
|
(message) => this.injectHumanMessage(message.content, message.files)
|
|
2716
2935
|
);
|
|
2717
2936
|
this.connection.onModeChange((data) => this.handleModeChange(data.agentMode));
|
|
2718
|
-
this.connection.onRunStartCommand(() => this.
|
|
2937
|
+
this.connection.onRunStartCommand(() => this.handleRunStartCommand());
|
|
2719
2938
|
await this.setState("connected");
|
|
2720
2939
|
this.connection.sendEvent({ type: "connected", taskId: this.config.taskId });
|
|
2721
2940
|
this.startHeartbeat();
|
|
2722
2941
|
if (this.config.mode !== "pm" && process.env.CODESPACES === "true") {
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2942
|
+
const result = await runSetupSafe(
|
|
2943
|
+
this.config,
|
|
2944
|
+
this.connection,
|
|
2945
|
+
this.callbacks,
|
|
2946
|
+
this.setupLog,
|
|
2947
|
+
this.effectiveAgentMode,
|
|
2948
|
+
(s) => this.setState(s)
|
|
2949
|
+
);
|
|
2950
|
+
if (!result.ok) {
|
|
2725
2951
|
this.stopHeartbeat();
|
|
2726
2952
|
await this.setState("error");
|
|
2727
2953
|
this.connection.disconnect();
|
|
2728
2954
|
return;
|
|
2729
2955
|
}
|
|
2956
|
+
this.deferredStartConfig = result.deferredStartConfig;
|
|
2730
2957
|
}
|
|
2731
2958
|
initRtk();
|
|
2959
|
+
this.tryInitWorktree();
|
|
2960
|
+
if (!await this.fetchAndInitContext()) return;
|
|
2961
|
+
this.tryPostContextWorktree();
|
|
2962
|
+
this.checkoutWorktreeBranch();
|
|
2963
|
+
await this.executeInitialMode();
|
|
2964
|
+
await this.runCoreLoop();
|
|
2965
|
+
this.stopHeartbeat();
|
|
2966
|
+
await this.setState("finished");
|
|
2967
|
+
this.connection.disconnect();
|
|
2968
|
+
}
|
|
2969
|
+
tryInitWorktree() {
|
|
2732
2970
|
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
|
-
}
|
|
2971
|
+
this.activateWorktree("[conveyor] Using worktree:");
|
|
2743
2972
|
}
|
|
2973
|
+
}
|
|
2974
|
+
async fetchAndInitContext() {
|
|
2744
2975
|
await this.setState("fetching_context");
|
|
2745
2976
|
try {
|
|
2746
2977
|
this.taskContext = await this.connection.fetchTaskContext();
|
|
@@ -2751,67 +2982,56 @@ var AgentRunner = class _AgentRunner {
|
|
|
2751
2982
|
this.stopHeartbeat();
|
|
2752
2983
|
await this.setState("error");
|
|
2753
2984
|
this.connection.disconnect();
|
|
2754
|
-
return;
|
|
2985
|
+
return false;
|
|
2755
2986
|
}
|
|
2756
2987
|
this.taskContext._runnerSessionId = randomUUID2();
|
|
2757
|
-
if (this.taskContext.agentMode)
|
|
2758
|
-
this.agentMode = this.taskContext.agentMode;
|
|
2759
|
-
}
|
|
2988
|
+
if (this.taskContext.agentMode) this.agentMode = this.taskContext.agentMode;
|
|
2760
2989
|
this.logEffectiveSettings();
|
|
2761
|
-
if (process.env.CODESPACES === "true")
|
|
2762
|
-
|
|
2990
|
+
if (process.env.CODESPACES === "true") unshallowRepo(this.config.workspaceDir);
|
|
2991
|
+
return true;
|
|
2992
|
+
}
|
|
2993
|
+
tryPostContextWorktree() {
|
|
2994
|
+
if (process.env.CODESPACES !== "true" && !this.worktreeActive && this.taskContext?.useWorktree) {
|
|
2995
|
+
this.activateWorktree("[conveyor] Using worktree (from task config):");
|
|
2763
2996
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
}
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2997
|
+
}
|
|
2998
|
+
activateWorktree(logPrefix) {
|
|
2999
|
+
try {
|
|
3000
|
+
const worktreePath = ensureWorktree(this.config.workspaceDir, this.config.taskId);
|
|
3001
|
+
this.config = { ...this.config, workspaceDir: worktreePath };
|
|
3002
|
+
this.planSync.updateWorkspaceDir(worktreePath);
|
|
3003
|
+
this.worktreeActive = true;
|
|
3004
|
+
pushSetupLog(this.setupLog, `${logPrefix} ${worktreePath}`);
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
3007
|
+
pushSetupLog(
|
|
3008
|
+
this.setupLog,
|
|
3009
|
+
`[conveyor] Worktree creation failed, using shared workspace: ${msg}`
|
|
3010
|
+
);
|
|
2775
3011
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
}
|
|
3012
|
+
}
|
|
3013
|
+
checkoutWorktreeBranch() {
|
|
3014
|
+
if (!this.worktreeActive || !this.taskContext?.githubBranch) return;
|
|
3015
|
+
try {
|
|
3016
|
+
const branch = this.taskContext.githubBranch;
|
|
3017
|
+
execSync3(`git fetch origin ${branch} && git checkout ${branch}`, {
|
|
3018
|
+
cwd: this.config.workspaceDir,
|
|
3019
|
+
stdio: "ignore"
|
|
3020
|
+
});
|
|
3021
|
+
} catch {
|
|
2785
3022
|
}
|
|
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;
|
|
3023
|
+
}
|
|
3024
|
+
async executeInitialMode() {
|
|
3025
|
+
if (!this.taskContext) return;
|
|
3026
|
+
const mode = this.effectiveAgentMode;
|
|
3027
|
+
const shouldRun = mode === "building" || mode === "auto" || mode === "review" && !!this.taskContext.isParentTask;
|
|
3028
|
+
if (shouldRun) {
|
|
3029
|
+
await this.setState("running");
|
|
3030
|
+
await this.runQuerySafe(this.taskContext);
|
|
3031
|
+
if (!this.stopped) await this.setState("idle");
|
|
3032
|
+
} else {
|
|
3033
|
+
await this.setState("idle");
|
|
2810
3034
|
}
|
|
2811
|
-
await this.runCoreLoop();
|
|
2812
|
-
this.stopHeartbeat();
|
|
2813
|
-
await this.setState("finished");
|
|
2814
|
-
this.connection.disconnect();
|
|
2815
3035
|
}
|
|
2816
3036
|
async runQuerySafe(context, followUp) {
|
|
2817
3037
|
this.lastQueryModeRestart = false;
|
|
@@ -2833,7 +3053,21 @@ var AgentRunner = class _AgentRunner {
|
|
|
2833
3053
|
while (!this.stopped) {
|
|
2834
3054
|
if (this.lastQueryModeRestart) {
|
|
2835
3055
|
this.lastQueryModeRestart = false;
|
|
2836
|
-
await
|
|
3056
|
+
await handleAutoModeRestart(
|
|
3057
|
+
{ agentMode: this.agentMode, sessionIds: this.sessionIds, taskContext: this.taskContext },
|
|
3058
|
+
this.connection,
|
|
3059
|
+
(s) => this.setState(s),
|
|
3060
|
+
(ctx) => this.runQuerySafe(ctx),
|
|
3061
|
+
async () => {
|
|
3062
|
+
try {
|
|
3063
|
+
return await this.connection.fetchTaskContext();
|
|
3064
|
+
} catch {
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
);
|
|
3069
|
+
this.taskContext = await this.connection.fetchTaskContext().catch(() => null) ?? this.taskContext;
|
|
3070
|
+
if (!this.stopped && this._state !== "error") await this.setState("idle");
|
|
2837
3071
|
continue;
|
|
2838
3072
|
}
|
|
2839
3073
|
if (this._state === "idle") {
|
|
@@ -2849,108 +3083,17 @@ var AgentRunner = class _AgentRunner {
|
|
|
2849
3083
|
}
|
|
2850
3084
|
}
|
|
2851
3085
|
}
|
|
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
|
-
}
|
|
3086
|
+
handleRunStartCommand() {
|
|
3087
|
+
if (!this.deferredStartConfig?.startCommand || this.startCommandStarted) return;
|
|
3088
|
+
this.startCommandStarted = true;
|
|
3089
|
+
runDeferredStartCommand(this.deferredStartConfig, this.config, this.connection, this.setupLog);
|
|
3090
|
+
this.deferredStartConfig = null;
|
|
2948
3091
|
}
|
|
2949
3092
|
logEffectiveSettings() {
|
|
2950
3093
|
if (!this.taskContext) return;
|
|
2951
3094
|
const s = this.taskContext.agentSettings ?? this.config.agentSettings ?? {};
|
|
2952
3095
|
const model = this.taskContext.model || this.config.model;
|
|
2953
|
-
const thinking =
|
|
3096
|
+
const thinking = formatThinkingSetting(s.thinking);
|
|
2954
3097
|
const parts = [
|
|
2955
3098
|
`model=${model}`,
|
|
2956
3099
|
`mode=${this.config.mode ?? "task"}`,
|
|
@@ -2964,84 +3107,8 @@ The agent cannot start until this is resolved.`
|
|
|
2964
3107
|
if (s.enableFileCheckpointing) parts.push(`checkpointing=on`);
|
|
2965
3108
|
console.log(`[conveyor-agent] ${parts.join(", ")}`);
|
|
2966
3109
|
}
|
|
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
3110
|
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
|
-
}
|
|
3111
|
+
const messageContent = buildMessageContent(content, files);
|
|
3045
3112
|
const msg = {
|
|
3046
3113
|
type: "user",
|
|
3047
3114
|
session_id: "",
|
|
@@ -3085,16 +3152,11 @@ ${f.content}
|
|
|
3085
3152
|
}
|
|
3086
3153
|
async waitForUserContent() {
|
|
3087
3154
|
if (this.pendingMessages.length > 0) {
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
if (!content2) return null;
|
|
3091
|
-
return content2;
|
|
3155
|
+
const content = this.pendingMessages.shift()?.message.content;
|
|
3156
|
+
return content ?? null;
|
|
3092
3157
|
}
|
|
3093
3158
|
const msg = await this.waitForMessage();
|
|
3094
|
-
|
|
3095
|
-
const content = msg.message.content;
|
|
3096
|
-
if (!content) return null;
|
|
3097
|
-
return content;
|
|
3159
|
+
return msg?.message.content ?? null;
|
|
3098
3160
|
}
|
|
3099
3161
|
async *createInputStream(initialPrompt) {
|
|
3100
3162
|
const makeUserMessage = (content) => ({
|
|
@@ -3124,60 +3186,43 @@ ${f.content}
|
|
|
3124
3186
|
}
|
|
3125
3187
|
}
|
|
3126
3188
|
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 {
|
|
3189
|
+
return buildQueryHost({
|
|
3138
3190
|
config: this.config,
|
|
3139
3191
|
connection: this.connection,
|
|
3140
3192
|
callbacks: this.callbacks,
|
|
3141
3193
|
setupLog: this.setupLog,
|
|
3142
3194
|
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();
|
|
3195
|
+
planSync: this.planSync,
|
|
3196
|
+
sessionIds: this.sessionIds,
|
|
3197
|
+
getEffectiveAgentMode: () => this.effectiveAgentMode,
|
|
3198
|
+
getIsParentTask: () => !!this.taskContext?.isParentTask,
|
|
3199
|
+
getHasExitedPlanMode: () => this.hasExitedPlanMode,
|
|
3200
|
+
setHasExitedPlanMode: (val) => {
|
|
3201
|
+
this.hasExitedPlanMode = val;
|
|
3157
3202
|
},
|
|
3158
|
-
|
|
3159
|
-
|
|
3203
|
+
getPendingModeRestart: () => this.pendingModeRestart,
|
|
3204
|
+
setPendingModeRestart: (val) => {
|
|
3205
|
+
this.pendingModeRestart = val;
|
|
3160
3206
|
},
|
|
3161
|
-
sessionIds: this.sessionIds,
|
|
3162
3207
|
isStopped: () => this.stopped,
|
|
3163
3208
|
createInputStream: (prompt) => this.createInputStream(prompt),
|
|
3164
|
-
snapshotPlanFiles: () => this.planSync.snapshotPlanFiles(),
|
|
3165
|
-
syncPlanFile: () => this.planSync.syncPlanFile(),
|
|
3166
3209
|
onModeTransition: (newMode) => {
|
|
3167
3210
|
this.agentMode = newMode;
|
|
3168
3211
|
}
|
|
3169
|
-
};
|
|
3212
|
+
});
|
|
3170
3213
|
}
|
|
3171
3214
|
handleModeChange(newAgentMode) {
|
|
3172
3215
|
if (this.config.mode !== "pm") return;
|
|
3173
|
-
if (newAgentMode)
|
|
3174
|
-
this.agentMode = newAgentMode;
|
|
3175
|
-
}
|
|
3216
|
+
if (newAgentMode) this.agentMode = newAgentMode;
|
|
3176
3217
|
const effectiveMode = this.effectiveAgentMode;
|
|
3177
3218
|
this.connection.emitModeChanged(effectiveMode);
|
|
3178
3219
|
this.connection.postChatMessage(
|
|
3179
3220
|
`Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
|
|
3180
3221
|
);
|
|
3222
|
+
if (effectiveMode === "building" && this.taskContext?.status === "Open") {
|
|
3223
|
+
this.connection.updateStatus("InProgress");
|
|
3224
|
+
this.taskContext.status = "InProgress";
|
|
3225
|
+
}
|
|
3181
3226
|
}
|
|
3182
3227
|
stop() {
|
|
3183
3228
|
this.stopped = true;
|
|
@@ -3239,87 +3284,103 @@ function buildPrompt(message, chatHistory) {
|
|
|
3239
3284
|
${message.content}`);
|
|
3240
3285
|
return parts.join("\n");
|
|
3241
3286
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3287
|
+
function processContentBlock(block, responseParts, turnToolCalls) {
|
|
3288
|
+
if (block.type === "text" && block.text) {
|
|
3289
|
+
responseParts.push(block.text);
|
|
3290
|
+
} else if (block.type === "tool_use" && block.name) {
|
|
3291
|
+
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
3292
|
+
turnToolCalls.push({
|
|
3293
|
+
tool: block.name,
|
|
3294
|
+
input: inputStr.slice(0, 1e4),
|
|
3295
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3296
|
+
});
|
|
3297
|
+
console.log(`[project-chat] [tool_use] ${block.name}`);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
async function fetchContext(connection) {
|
|
3301
|
+
let agentCtx = null;
|
|
3244
3302
|
try {
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3303
|
+
agentCtx = await connection.fetchAgentContext();
|
|
3304
|
+
} catch {
|
|
3305
|
+
console.log("[project-chat] Could not fetch agent context, using defaults");
|
|
3306
|
+
}
|
|
3307
|
+
let chatHistory = [];
|
|
3308
|
+
try {
|
|
3309
|
+
chatHistory = await connection.fetchChatHistory(30);
|
|
3310
|
+
} catch {
|
|
3311
|
+
console.log("[project-chat] Could not fetch chat history, proceeding without it");
|
|
3312
|
+
}
|
|
3313
|
+
return { agentCtx, chatHistory };
|
|
3314
|
+
}
|
|
3315
|
+
function buildChatQueryOptions(agentCtx, projectDir) {
|
|
3316
|
+
const model = agentCtx?.model || FALLBACK_MODEL;
|
|
3317
|
+
const settings = agentCtx?.agentSettings ?? {};
|
|
3318
|
+
return {
|
|
3319
|
+
model,
|
|
3320
|
+
systemPrompt: {
|
|
3321
|
+
type: "preset",
|
|
3322
|
+
preset: "claude_code",
|
|
3323
|
+
append: buildSystemPrompt2(projectDir, agentCtx)
|
|
3324
|
+
},
|
|
3325
|
+
cwd: projectDir,
|
|
3326
|
+
permissionMode: "bypassPermissions",
|
|
3327
|
+
allowDangerouslySkipPermissions: true,
|
|
3328
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
3329
|
+
maxTurns: settings.maxTurns ?? 15,
|
|
3330
|
+
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
3331
|
+
effort: settings.effort,
|
|
3332
|
+
thinking: settings.thinking
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
function processEventStream(event, connection, responseParts, turnToolCalls, isTyping) {
|
|
3336
|
+
if (event.type === "assistant") {
|
|
3337
|
+
if (!isTyping.value) {
|
|
3338
|
+
setTimeout(() => connection.emitEvent({ type: "agent_typing_start" }), 200);
|
|
3339
|
+
isTyping.value = true;
|
|
3250
3340
|
}
|
|
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
|
-
}
|
|
3341
|
+
const assistantEvent = event;
|
|
3342
|
+
for (const block of assistantEvent.message.content) {
|
|
3343
|
+
processContentBlock(block, responseParts, turnToolCalls);
|
|
3315
3344
|
}
|
|
3316
|
-
if (
|
|
3317
|
-
connection.emitEvent({ type: "
|
|
3345
|
+
if (turnToolCalls.length > 0) {
|
|
3346
|
+
connection.emitEvent({ type: "activity_block", events: [...turnToolCalls] });
|
|
3347
|
+
turnToolCalls.length = 0;
|
|
3318
3348
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3349
|
+
return false;
|
|
3350
|
+
}
|
|
3351
|
+
if (event.type === "result") {
|
|
3352
|
+
if (isTyping.value) {
|
|
3353
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
3354
|
+
isTyping.value = false;
|
|
3322
3355
|
}
|
|
3356
|
+
return true;
|
|
3357
|
+
}
|
|
3358
|
+
return false;
|
|
3359
|
+
}
|
|
3360
|
+
async function runChatQuery(message, connection, projectDir) {
|
|
3361
|
+
const { agentCtx, chatHistory } = await fetchContext(connection);
|
|
3362
|
+
const options = buildChatQueryOptions(agentCtx, projectDir);
|
|
3363
|
+
const prompt = buildPrompt(message, chatHistory);
|
|
3364
|
+
const events = query2({ prompt, options });
|
|
3365
|
+
const responseParts = [];
|
|
3366
|
+
const turnToolCalls = [];
|
|
3367
|
+
const isTyping = { value: false };
|
|
3368
|
+
for await (const event of events) {
|
|
3369
|
+
const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
|
|
3370
|
+
if (done) break;
|
|
3371
|
+
}
|
|
3372
|
+
if (isTyping.value) {
|
|
3373
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
3374
|
+
}
|
|
3375
|
+
const responseText = responseParts.join("\n\n").trim();
|
|
3376
|
+
if (responseText) {
|
|
3377
|
+
await connection.emitChatMessage(responseText);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async function handleProjectChatMessage(message, connection, projectDir) {
|
|
3381
|
+
connection.emitAgentStatus("busy");
|
|
3382
|
+
try {
|
|
3383
|
+
await runChatQuery(message, connection, projectDir);
|
|
3323
3384
|
} catch (error) {
|
|
3324
3385
|
console.error(
|
|
3325
3386
|
"[project-chat] Failed to handle message:",
|
|
@@ -3342,6 +3403,72 @@ var __dirname = path.dirname(__filename);
|
|
|
3342
3403
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
3343
3404
|
var MAX_CONCURRENT = 5;
|
|
3344
3405
|
var STOP_TIMEOUT_MS = 3e4;
|
|
3406
|
+
function setupWorkDir(projectDir, assignment) {
|
|
3407
|
+
const { taskId, branch, devBranch, useWorktree } = assignment;
|
|
3408
|
+
const shortId = taskId.slice(0, 8);
|
|
3409
|
+
const shouldWorktree = useWorktree === true;
|
|
3410
|
+
let workDir;
|
|
3411
|
+
if (shouldWorktree) {
|
|
3412
|
+
workDir = ensureWorktree(projectDir, taskId, devBranch);
|
|
3413
|
+
} else {
|
|
3414
|
+
workDir = projectDir;
|
|
3415
|
+
}
|
|
3416
|
+
if (branch && branch !== devBranch) {
|
|
3417
|
+
try {
|
|
3418
|
+
execSync4(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3419
|
+
} catch {
|
|
3420
|
+
try {
|
|
3421
|
+
execSync4(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
|
|
3422
|
+
} catch {
|
|
3423
|
+
console.log(`[task:${shortId}] Warning: could not checkout branch ${branch}`);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return { workDir, usesWorktree: shouldWorktree };
|
|
3428
|
+
}
|
|
3429
|
+
function spawnChildAgent(assignment, workDir) {
|
|
3430
|
+
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox } = assignment;
|
|
3431
|
+
const cliPath = path.resolve(__dirname, "cli.js");
|
|
3432
|
+
const childEnv = { ...process.env };
|
|
3433
|
+
delete childEnv.CONVEYOR_PROJECT_TOKEN;
|
|
3434
|
+
delete childEnv.CONVEYOR_PROJECT_ID;
|
|
3435
|
+
const child = fork(cliPath, [], {
|
|
3436
|
+
env: {
|
|
3437
|
+
...childEnv,
|
|
3438
|
+
CONVEYOR_API_URL: apiUrl,
|
|
3439
|
+
CONVEYOR_TASK_TOKEN: taskToken,
|
|
3440
|
+
CONVEYOR_TASK_ID: taskId,
|
|
3441
|
+
CONVEYOR_MODE: mode,
|
|
3442
|
+
CONVEYOR_WORKSPACE: workDir,
|
|
3443
|
+
CONVEYOR_USE_WORKTREE: "false",
|
|
3444
|
+
CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
|
|
3445
|
+
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
3446
|
+
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false"
|
|
3447
|
+
},
|
|
3448
|
+
cwd: workDir,
|
|
3449
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
3450
|
+
});
|
|
3451
|
+
child.stdin?.on("error", () => {
|
|
3452
|
+
});
|
|
3453
|
+
child.stdout?.on("error", () => {
|
|
3454
|
+
});
|
|
3455
|
+
child.stderr?.on("error", () => {
|
|
3456
|
+
});
|
|
3457
|
+
const shortId = taskId.slice(0, 8);
|
|
3458
|
+
child.stdout?.on("data", (data) => {
|
|
3459
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
3460
|
+
for (const line of lines) {
|
|
3461
|
+
console.log(`[task:${shortId}] ${line}`);
|
|
3462
|
+
}
|
|
3463
|
+
});
|
|
3464
|
+
child.stderr?.on("data", (data) => {
|
|
3465
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
3466
|
+
for (const line of lines) {
|
|
3467
|
+
console.error(`[task:${shortId}] ${line}`);
|
|
3468
|
+
}
|
|
3469
|
+
});
|
|
3470
|
+
return child;
|
|
3471
|
+
}
|
|
3345
3472
|
var ProjectRunner = class {
|
|
3346
3473
|
connection;
|
|
3347
3474
|
projectDir;
|
|
@@ -3360,7 +3487,7 @@ var ProjectRunner = class {
|
|
|
3360
3487
|
async start() {
|
|
3361
3488
|
await this.connection.connect();
|
|
3362
3489
|
this.connection.onTaskAssignment((assignment) => {
|
|
3363
|
-
|
|
3490
|
+
this.handleAssignment(assignment);
|
|
3364
3491
|
});
|
|
3365
3492
|
this.connection.onStopTask((data) => {
|
|
3366
3493
|
this.handleStopTask(data.taskId);
|
|
@@ -3383,8 +3510,8 @@ var ProjectRunner = class {
|
|
|
3383
3510
|
process.on("SIGINT", () => void this.stop());
|
|
3384
3511
|
});
|
|
3385
3512
|
}
|
|
3386
|
-
|
|
3387
|
-
const { taskId,
|
|
3513
|
+
handleAssignment(assignment) {
|
|
3514
|
+
const { taskId, mode } = assignment;
|
|
3388
3515
|
const shortId = taskId.slice(0, 8);
|
|
3389
3516
|
if (this.activeAgents.has(taskId)) {
|
|
3390
3517
|
console.log(`[project-runner] Task ${shortId} already running, skipping`);
|
|
@@ -3403,67 +3530,13 @@ var ProjectRunner = class {
|
|
|
3403
3530
|
} catch {
|
|
3404
3531
|
console.log(`[task:${shortId}] Warning: git fetch failed`);
|
|
3405
3532
|
}
|
|
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
|
-
});
|
|
3533
|
+
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
3534
|
+
const child = spawnChildAgent(assignment, workDir);
|
|
3462
3535
|
this.activeAgents.set(taskId, {
|
|
3463
3536
|
process: child,
|
|
3464
3537
|
worktreePath: workDir,
|
|
3465
3538
|
mode,
|
|
3466
|
-
usesWorktree
|
|
3539
|
+
usesWorktree
|
|
3467
3540
|
});
|
|
3468
3541
|
this.connection.emitTaskStarted(taskId);
|
|
3469
3542
|
console.log(`[project-runner] Started task ${shortId} in ${mode} mode at ${workDir}`);
|
|
@@ -3472,7 +3545,7 @@ var ProjectRunner = class {
|
|
|
3472
3545
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
3473
3546
|
this.connection.emitTaskStopped(taskId, reason);
|
|
3474
3547
|
console.log(`[project-runner] Task ${shortId} ${reason}`);
|
|
3475
|
-
if (code === 0 &&
|
|
3548
|
+
if (code === 0 && usesWorktree) {
|
|
3476
3549
|
try {
|
|
3477
3550
|
removeWorktree(this.projectDir, taskId);
|
|
3478
3551
|
} catch {
|
|
@@ -3534,7 +3607,9 @@ var ProjectRunner = class {
|
|
|
3534
3607
|
);
|
|
3535
3608
|
await Promise.race([
|
|
3536
3609
|
Promise.all(stopPromises),
|
|
3537
|
-
new Promise((resolve2) =>
|
|
3610
|
+
new Promise((resolve2) => {
|
|
3611
|
+
setTimeout(resolve2, 6e4);
|
|
3612
|
+
})
|
|
3538
3613
|
]);
|
|
3539
3614
|
this.connection.disconnect();
|
|
3540
3615
|
console.log("[project-runner] Shutdown complete");
|
|
@@ -3612,13 +3687,13 @@ var FileCache = class {
|
|
|
3612
3687
|
export {
|
|
3613
3688
|
ConveyorConnection,
|
|
3614
3689
|
ProjectConnection,
|
|
3690
|
+
ensureWorktree,
|
|
3691
|
+
removeWorktree,
|
|
3615
3692
|
loadConveyorConfig,
|
|
3616
3693
|
runSetupCommand,
|
|
3617
3694
|
runStartCommand,
|
|
3618
|
-
ensureWorktree,
|
|
3619
|
-
removeWorktree,
|
|
3620
3695
|
AgentRunner,
|
|
3621
3696
|
ProjectRunner,
|
|
3622
3697
|
FileCache
|
|
3623
3698
|
};
|
|
3624
|
-
//# sourceMappingURL=chunk-
|
|
3699
|
+
//# sourceMappingURL=chunk-7Y3RP3ZA.js.map
|