@rallycry/conveyor-agent 2.13.0 → 2.14.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-SBAEYHPL.js → chunk-KFCGF2SX.js} +444 -55
- package/dist/chunk-KFCGF2SX.js.map +1 -0
- package/dist/cli.js +78 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SBAEYHPL.js.map +0 -1
|
@@ -15,7 +15,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
15
15
|
this.config = config;
|
|
16
16
|
}
|
|
17
17
|
connect() {
|
|
18
|
-
return new Promise((
|
|
18
|
+
return new Promise((resolve2, reject) => {
|
|
19
19
|
let settled = false;
|
|
20
20
|
let attempts = 0;
|
|
21
21
|
const maxInitialAttempts = 30;
|
|
@@ -61,7 +61,7 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
61
61
|
this.socket.on("connect", () => {
|
|
62
62
|
if (!settled) {
|
|
63
63
|
settled = true;
|
|
64
|
-
|
|
64
|
+
resolve2();
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
this.socket.io.on("reconnect_attempt", () => {
|
|
@@ -76,13 +76,13 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
76
76
|
fetchChatMessages(limit) {
|
|
77
77
|
const socket = this.socket;
|
|
78
78
|
if (!socket) throw new Error("Not connected");
|
|
79
|
-
return new Promise((
|
|
79
|
+
return new Promise((resolve2, reject) => {
|
|
80
80
|
socket.emit(
|
|
81
81
|
"agentRunner:getChatMessages",
|
|
82
82
|
{ taskId: this.config.taskId, limit },
|
|
83
83
|
(response) => {
|
|
84
84
|
if (response.success && response.data) {
|
|
85
|
-
|
|
85
|
+
resolve2(response.data);
|
|
86
86
|
} else {
|
|
87
87
|
reject(new Error(response.error ?? "Failed to fetch chat messages"));
|
|
88
88
|
}
|
|
@@ -93,13 +93,13 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
93
93
|
fetchTaskContext() {
|
|
94
94
|
const socket = this.socket;
|
|
95
95
|
if (!socket) throw new Error("Not connected");
|
|
96
|
-
return new Promise((
|
|
96
|
+
return new Promise((resolve2, reject) => {
|
|
97
97
|
socket.emit(
|
|
98
98
|
"agentRunner:getTaskContext",
|
|
99
99
|
{ taskId: this.config.taskId },
|
|
100
100
|
(response) => {
|
|
101
101
|
if (response.success && response.data) {
|
|
102
|
-
|
|
102
|
+
resolve2(response.data);
|
|
103
103
|
} else {
|
|
104
104
|
reject(new Error(response.error ?? "Failed to fetch task context"));
|
|
105
105
|
}
|
|
@@ -142,13 +142,13 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
142
142
|
createPR(params) {
|
|
143
143
|
const socket = this.socket;
|
|
144
144
|
if (!socket) throw new Error("Not connected");
|
|
145
|
-
return new Promise((
|
|
145
|
+
return new Promise((resolve2, reject) => {
|
|
146
146
|
socket.emit(
|
|
147
147
|
"agentRunner:createPR",
|
|
148
148
|
{ taskId: this.config.taskId, ...params },
|
|
149
149
|
(response) => {
|
|
150
150
|
if (response.success && response.data) {
|
|
151
|
-
|
|
151
|
+
resolve2(response.data);
|
|
152
152
|
} else {
|
|
153
153
|
reject(new Error(response.error ?? "Failed to create pull request"));
|
|
154
154
|
}
|
|
@@ -163,8 +163,8 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
163
163
|
requestId,
|
|
164
164
|
questions
|
|
165
165
|
});
|
|
166
|
-
return new Promise((
|
|
167
|
-
this.pendingQuestionResolvers.set(requestId,
|
|
166
|
+
return new Promise((resolve2) => {
|
|
167
|
+
this.pendingQuestionResolvers.set(requestId, resolve2);
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
cancelPendingQuestions() {
|
|
@@ -224,6 +224,45 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
224
224
|
sendTypingStop() {
|
|
225
225
|
this.sendEvent({ type: "agent_typing_stop" });
|
|
226
226
|
}
|
|
227
|
+
createSubtask(data) {
|
|
228
|
+
const socket = this.socket;
|
|
229
|
+
if (!socket) throw new Error("Not connected");
|
|
230
|
+
return new Promise((resolve2, reject) => {
|
|
231
|
+
socket.emit(
|
|
232
|
+
"agentRunner:createSubtask",
|
|
233
|
+
data,
|
|
234
|
+
(response) => {
|
|
235
|
+
if (response.success && response.data) resolve2(response.data);
|
|
236
|
+
else reject(new Error(response.error ?? "Failed to create subtask"));
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
updateSubtask(subtaskId, fields) {
|
|
242
|
+
if (!this.socket) throw new Error("Not connected");
|
|
243
|
+
this.socket.emit("agentRunner:updateSubtask", {
|
|
244
|
+
subtaskId,
|
|
245
|
+
fields
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
deleteSubtask(subtaskId) {
|
|
249
|
+
if (!this.socket) throw new Error("Not connected");
|
|
250
|
+
this.socket.emit("agentRunner:deleteSubtask", { subtaskId });
|
|
251
|
+
}
|
|
252
|
+
listSubtasks() {
|
|
253
|
+
const socket = this.socket;
|
|
254
|
+
if (!socket) throw new Error("Not connected");
|
|
255
|
+
return new Promise((resolve2, reject) => {
|
|
256
|
+
socket.emit(
|
|
257
|
+
"agentRunner:listSubtasks",
|
|
258
|
+
{},
|
|
259
|
+
(response) => {
|
|
260
|
+
if (response.success && response.data) resolve2(response.data);
|
|
261
|
+
else reject(new Error(response.error ?? "Failed to list subtasks"));
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
227
266
|
disconnect() {
|
|
228
267
|
this.flushEvents();
|
|
229
268
|
this.socket?.disconnect();
|
|
@@ -265,7 +304,7 @@ async function loadConveyorConfig(workspaceDir) {
|
|
|
265
304
|
return null;
|
|
266
305
|
}
|
|
267
306
|
function runSetupCommand(cmd, cwd, onOutput) {
|
|
268
|
-
return new Promise((
|
|
307
|
+
return new Promise((resolve2, reject) => {
|
|
269
308
|
const child = spawn("sh", ["-c", cmd], {
|
|
270
309
|
cwd,
|
|
271
310
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -279,7 +318,7 @@ function runSetupCommand(cmd, cwd, onOutput) {
|
|
|
279
318
|
});
|
|
280
319
|
child.on("close", (code) => {
|
|
281
320
|
if (code === 0) {
|
|
282
|
-
|
|
321
|
+
resolve2();
|
|
283
322
|
} else {
|
|
284
323
|
reject(new Error(`Setup command exited with code ${code}`));
|
|
285
324
|
}
|
|
@@ -385,6 +424,7 @@ import { randomUUID } from "crypto";
|
|
|
385
424
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
386
425
|
|
|
387
426
|
// src/prompt-builder.ts
|
|
427
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["InProgress", "ReviewPR", "ReviewDev", "ReviewLive"]);
|
|
388
428
|
function findLastAgentMessageIndex(history) {
|
|
389
429
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
390
430
|
if (history[i].role === "assistant") return i;
|
|
@@ -394,7 +434,7 @@ function findLastAgentMessageIndex(history) {
|
|
|
394
434
|
function detectRelaunchScenario(context) {
|
|
395
435
|
const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
|
|
396
436
|
if (lastAgentIdx === -1) return "fresh";
|
|
397
|
-
const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId;
|
|
437
|
+
const hasPriorWork = !!context.githubPRUrl || !!context.claudeSessionId || ACTIVE_STATUSES.has(context.status ?? "");
|
|
398
438
|
if (!hasPriorWork) return "fresh";
|
|
399
439
|
const messagesAfterAgent = context.chatHistory.slice(lastAgentIdx + 1);
|
|
400
440
|
const hasNewUserMessages = messagesAfterAgent.some((m) => m.role === "user");
|
|
@@ -424,12 +464,13 @@ You are the project manager for this task.`,
|
|
|
424
464
|
const newMessages = context.chatHistory.slice(lastAgentIdx + 1).filter((m) => m.role === "user");
|
|
425
465
|
parts.push(
|
|
426
466
|
`You have been relaunched with new feedback.`,
|
|
427
|
-
`Work on the git branch "${context.githubBranch}".`,
|
|
467
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
428
468
|
`
|
|
429
469
|
New messages since your last run:`,
|
|
430
470
|
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
431
471
|
`
|
|
432
|
-
Address the requested changes.
|
|
472
|
+
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.`,
|
|
473
|
+
`Commit and push your updates.`
|
|
433
474
|
);
|
|
434
475
|
if (context.githubPRUrl) {
|
|
435
476
|
parts.push(
|
|
@@ -443,7 +484,8 @@ Address the requested changes. Commit and push your updates.`
|
|
|
443
484
|
} else {
|
|
444
485
|
parts.push(
|
|
445
486
|
`You were relaunched but no new instructions have been given since your last run.`,
|
|
446
|
-
`Work on the git branch "${context.githubBranch}".`,
|
|
487
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
488
|
+
`Run \`git log --oneline -10\` to review what you already committed.`,
|
|
447
489
|
`Review the current state of the codebase and verify everything is working correctly.`,
|
|
448
490
|
`Post a brief status update to the chat, then wait for further instructions.`
|
|
449
491
|
);
|
|
@@ -515,7 +557,7 @@ function buildInstructions(mode, context, scenario) {
|
|
|
515
557
|
parts.push(
|
|
516
558
|
`Begin executing the task plan above immediately.`,
|
|
517
559
|
`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.`,
|
|
518
|
-
`Work on the git branch "${context.githubBranch}".`,
|
|
560
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch for the entire task. Do not checkout or create other branches.`,
|
|
519
561
|
`Post a brief message to chat when you begin meaningful implementation, and again when the PR is ready.`,
|
|
520
562
|
`When finished, commit your changes, push the branch, and use the create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
|
|
521
563
|
);
|
|
@@ -530,9 +572,9 @@ function buildInstructions(mode, context, scenario) {
|
|
|
530
572
|
} else {
|
|
531
573
|
parts.push(
|
|
532
574
|
`You were relaunched but no new instructions have been given since your last run.`,
|
|
533
|
-
`Work on the git branch "${context.githubBranch}".`,
|
|
534
|
-
`
|
|
535
|
-
`Post a brief status update to the chat summarizing
|
|
575
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
576
|
+
`Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
|
|
577
|
+
`Post a brief status update to the chat summarizing where things stand.`,
|
|
536
578
|
`Then wait for further instructions \u2014 do NOT redo work that was already completed.`
|
|
537
579
|
);
|
|
538
580
|
if (context.githubPRUrl) {
|
|
@@ -554,13 +596,15 @@ Review these messages and wait for the team to provide instructions before takin
|
|
|
554
596
|
);
|
|
555
597
|
} else {
|
|
556
598
|
parts.push(
|
|
557
|
-
`You
|
|
558
|
-
`Work on the git branch "${context.githubBranch}".`,
|
|
599
|
+
`You have been relaunched to address feedback on your previous work.`,
|
|
600
|
+
`Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
|
|
601
|
+
`Start by running \`git log --oneline -10\` and \`git diff HEAD~3 HEAD --stat\` to review what you already committed.`,
|
|
559
602
|
`
|
|
560
603
|
New messages since your last run:`,
|
|
561
604
|
...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
|
|
562
605
|
`
|
|
563
|
-
Address the requested changes.
|
|
606
|
+
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.`,
|
|
607
|
+
`Commit and push your updates.`
|
|
564
608
|
);
|
|
565
609
|
if (context.githubPRUrl) {
|
|
566
610
|
parts.push(
|
|
@@ -617,7 +661,14 @@ IMPORTANT \u2014 Skip all environment verification. Do NOT run any of the follow
|
|
|
617
661
|
`- bun dev, npm start, or any dev server startup commands`,
|
|
618
662
|
`- pwd, ls, echo, or exploratory shell commands to "check" the environment`,
|
|
619
663
|
`Only run these if you encounter a specific error that requires it.`,
|
|
620
|
-
`Start reading the task plan and writing code immediately
|
|
664
|
+
`Start reading the task plan and writing code immediately.`,
|
|
665
|
+
`
|
|
666
|
+
Git safety \u2014 STRICT rules:`,
|
|
667
|
+
`- NEVER run \`git checkout main\`, \`git checkout dev\`, or switch to any branch other than \`${context.githubBranch}\`.`,
|
|
668
|
+
`- NEVER create new branches (no \`git checkout -b\`, \`git switch -c\`, etc.).`,
|
|
669
|
+
`- This branch was created from \`${context.baseBranch}\`. PRs will automatically target that branch.`,
|
|
670
|
+
`- If \`git push\` fails with "non-fast-forward" or similar, run \`git pull --rebase origin ${context.githubBranch}\` and retry. If that also fails, use \`git push --force-with-lease origin ${context.githubBranch}\`.`,
|
|
671
|
+
`- If you encounter merge conflicts during rebase, resolve them in place \u2014 do NOT abandon the branch.`
|
|
621
672
|
];
|
|
622
673
|
if (setupLog.length > 0) {
|
|
623
674
|
parts.push(
|
|
@@ -675,7 +726,7 @@ function buildCommonTools(connection, config) {
|
|
|
675
726
|
);
|
|
676
727
|
}
|
|
677
728
|
},
|
|
678
|
-
{ annotations: {
|
|
729
|
+
{ annotations: { readOnlyHint: true } }
|
|
679
730
|
),
|
|
680
731
|
tool(
|
|
681
732
|
"post_to_chat",
|
|
@@ -709,7 +760,7 @@ function buildCommonTools(connection, config) {
|
|
|
709
760
|
return textResult(`Task ID: ${config.taskId} - could not fetch updated plan.`);
|
|
710
761
|
}
|
|
711
762
|
},
|
|
712
|
-
{ annotations: {
|
|
763
|
+
{ annotations: { readOnlyHint: true } }
|
|
713
764
|
)
|
|
714
765
|
];
|
|
715
766
|
}
|
|
@@ -730,6 +781,74 @@ function buildPmTools(connection) {
|
|
|
730
781
|
return textResult("Failed to update task.");
|
|
731
782
|
}
|
|
732
783
|
}
|
|
784
|
+
),
|
|
785
|
+
tool(
|
|
786
|
+
"create_subtask",
|
|
787
|
+
"Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
|
|
788
|
+
{
|
|
789
|
+
title: z.string().describe("Subtask title"),
|
|
790
|
+
description: z.string().optional().describe("Brief description"),
|
|
791
|
+
plan: z.string().optional().describe("Implementation plan in markdown"),
|
|
792
|
+
ordinal: z.number().optional().describe("Step/order number (0-based)"),
|
|
793
|
+
storyPointValue: z.number().optional().describe("Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)")
|
|
794
|
+
},
|
|
795
|
+
async (params) => {
|
|
796
|
+
try {
|
|
797
|
+
const result = await connection.createSubtask(params);
|
|
798
|
+
return textResult(`Subtask created with ID: ${result.id}`);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
return textResult(
|
|
801
|
+
`Failed to create subtask: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
tool(
|
|
807
|
+
"update_subtask",
|
|
808
|
+
"Update an existing subtask's fields",
|
|
809
|
+
{
|
|
810
|
+
subtaskId: z.string().describe("The subtask ID to update"),
|
|
811
|
+
title: z.string().optional(),
|
|
812
|
+
description: z.string().optional(),
|
|
813
|
+
plan: z.string().optional(),
|
|
814
|
+
ordinal: z.number().optional(),
|
|
815
|
+
storyPointValue: z.number().optional()
|
|
816
|
+
},
|
|
817
|
+
async ({ subtaskId, ...fields }) => {
|
|
818
|
+
try {
|
|
819
|
+
await Promise.resolve(connection.updateSubtask(subtaskId, fields));
|
|
820
|
+
return textResult("Subtask updated.");
|
|
821
|
+
} catch (error) {
|
|
822
|
+
return textResult(`Failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
),
|
|
826
|
+
tool(
|
|
827
|
+
"delete_subtask",
|
|
828
|
+
"Delete a subtask",
|
|
829
|
+
{ subtaskId: z.string().describe("The subtask ID to delete") },
|
|
830
|
+
async ({ subtaskId }) => {
|
|
831
|
+
try {
|
|
832
|
+
await Promise.resolve(connection.deleteSubtask(subtaskId));
|
|
833
|
+
return textResult("Subtask deleted.");
|
|
834
|
+
} catch (error) {
|
|
835
|
+
return textResult(`Failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
),
|
|
839
|
+
tool(
|
|
840
|
+
"list_subtasks",
|
|
841
|
+
"List all subtasks under the current parent task",
|
|
842
|
+
{},
|
|
843
|
+
async () => {
|
|
844
|
+
try {
|
|
845
|
+
const subtasks = await connection.listSubtasks();
|
|
846
|
+
return textResult(JSON.stringify(subtasks, null, 2));
|
|
847
|
+
} catch {
|
|
848
|
+
return textResult("Failed to list subtasks.");
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
{ annotations: { readOnlyHint: true } }
|
|
733
852
|
)
|
|
734
853
|
];
|
|
735
854
|
}
|
|
@@ -812,6 +931,7 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
812
931
|
function handleResultEvent(event, host, context, startTime) {
|
|
813
932
|
const resultEvent = event;
|
|
814
933
|
let totalCostUsd = 0;
|
|
934
|
+
let deltaCost = 0;
|
|
815
935
|
let retriable = false;
|
|
816
936
|
if (resultEvent.subtype === "success") {
|
|
817
937
|
totalCostUsd = "total_cost_usd" in resultEvent ? resultEvent.total_cost_usd : 0;
|
|
@@ -820,15 +940,18 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
820
940
|
if (API_ERROR_PATTERN.test(summary) && durationMs < 3e4) {
|
|
821
941
|
retriable = true;
|
|
822
942
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
943
|
+
const lastCost = context._lastReportedCostUsd ?? 0;
|
|
944
|
+
deltaCost = totalCostUsd - lastCost;
|
|
945
|
+
context._lastReportedCostUsd = totalCostUsd;
|
|
946
|
+
host.connection.sendEvent({ type: "completed", summary, costUsd: deltaCost, durationMs });
|
|
947
|
+
if (deltaCost > 0 && context.agentId) {
|
|
948
|
+
const estimatedDeltaTokens = Math.round(deltaCost * 1e5);
|
|
826
949
|
host.connection.trackSpending({
|
|
827
950
|
agentId: context.agentId,
|
|
828
|
-
inputTokens: Math.round(
|
|
829
|
-
outputTokens: Math.round(
|
|
830
|
-
totalTokens:
|
|
831
|
-
totalCostUsd,
|
|
951
|
+
inputTokens: Math.round(estimatedDeltaTokens * 0.7),
|
|
952
|
+
outputTokens: Math.round(estimatedDeltaTokens * 0.3),
|
|
953
|
+
totalTokens: estimatedDeltaTokens,
|
|
954
|
+
totalCostUsd: deltaCost,
|
|
832
955
|
onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
833
956
|
});
|
|
834
957
|
}
|
|
@@ -840,16 +963,16 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
840
963
|
}
|
|
841
964
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
842
965
|
}
|
|
843
|
-
return { totalCostUsd, retriable };
|
|
966
|
+
return { totalCostUsd, deltaCost, retriable };
|
|
844
967
|
}
|
|
845
968
|
async function emitResultEvent(event, host, context, startTime) {
|
|
846
969
|
const result = handleResultEvent(event, host, context, startTime);
|
|
847
970
|
const durationMs = Date.now() - startTime;
|
|
848
|
-
if (result.
|
|
971
|
+
if (result.deltaCost > 0 && context.agentId) {
|
|
849
972
|
await host.callbacks.onEvent({
|
|
850
973
|
type: "completed",
|
|
851
974
|
summary: "Task completed.",
|
|
852
|
-
costUsd: result.
|
|
975
|
+
costUsd: result.deltaCost,
|
|
853
976
|
durationMs
|
|
854
977
|
});
|
|
855
978
|
} else {
|
|
@@ -885,6 +1008,9 @@ async function processEvents(events, context, host) {
|
|
|
885
1008
|
if (sessionId && !sessionIdStored) {
|
|
886
1009
|
sessionIdStored = true;
|
|
887
1010
|
host.connection.storeSessionId(sessionId);
|
|
1011
|
+
if (sessionId !== context.claudeSessionId) {
|
|
1012
|
+
context._lastReportedCostUsd = 0;
|
|
1013
|
+
}
|
|
888
1014
|
}
|
|
889
1015
|
await host.callbacks.onEvent({
|
|
890
1016
|
type: "thinking",
|
|
@@ -941,8 +1067,8 @@ function buildCanUseTool(host) {
|
|
|
941
1067
|
input: JSON.stringify(input)
|
|
942
1068
|
});
|
|
943
1069
|
const answerPromise = host.connection.askUserQuestion(requestId, questions);
|
|
944
|
-
const timeoutPromise = new Promise((
|
|
945
|
-
setTimeout(() =>
|
|
1070
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
1071
|
+
setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
|
|
946
1072
|
});
|
|
947
1073
|
const answers = await Promise.race([answerPromise, timeoutPromise]);
|
|
948
1074
|
host.connection.emitStatus("running");
|
|
@@ -1020,23 +1146,27 @@ ${followUpContent}` : followUpContent;
|
|
|
1020
1146
|
async function runWithRetry(initialQuery, context, host, options) {
|
|
1021
1147
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
1022
1148
|
if (host.isStopped()) return;
|
|
1023
|
-
const agentQuery = attempt === 0 ? initialQuery :
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1149
|
+
const agentQuery = attempt === 0 ? initialQuery : (() => {
|
|
1150
|
+
context._lastReportedCostUsd = 0;
|
|
1151
|
+
return query({
|
|
1152
|
+
prompt: host.createInputStream(buildInitialPrompt(host.config.mode, context)),
|
|
1153
|
+
options: { ...options, resume: void 0 }
|
|
1154
|
+
});
|
|
1155
|
+
})();
|
|
1027
1156
|
try {
|
|
1028
1157
|
const { retriable } = await processEvents(agentQuery, context, host);
|
|
1029
1158
|
if (!retriable || host.isStopped()) return;
|
|
1030
1159
|
} catch (error) {
|
|
1031
1160
|
const isStaleSession = error instanceof Error && error.message.includes("No conversation found with session ID");
|
|
1032
1161
|
if (isStaleSession && context.claudeSessionId) {
|
|
1162
|
+
context.claudeSessionId = null;
|
|
1163
|
+
context._lastReportedCostUsd = 0;
|
|
1033
1164
|
host.connection.storeSessionId("");
|
|
1034
|
-
const freshCtx = { ...context, claudeSessionId: null };
|
|
1035
1165
|
const freshQuery = query({
|
|
1036
|
-
prompt: host.createInputStream(buildInitialPrompt(host.config.mode,
|
|
1166
|
+
prompt: host.createInputStream(buildInitialPrompt(host.config.mode, context)),
|
|
1037
1167
|
options: { ...options, resume: void 0 }
|
|
1038
1168
|
});
|
|
1039
|
-
return runWithRetry(freshQuery,
|
|
1169
|
+
return runWithRetry(freshQuery, context, host, options);
|
|
1040
1170
|
}
|
|
1041
1171
|
const isApiError = error instanceof Error && API_ERROR_PATTERN.test(error.message);
|
|
1042
1172
|
if (!isApiError) throw error;
|
|
@@ -1058,13 +1188,13 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1058
1188
|
});
|
|
1059
1189
|
host.connection.emitStatus("waiting_for_input");
|
|
1060
1190
|
await host.callbacks.onStatusChange("waiting_for_input");
|
|
1061
|
-
await new Promise((
|
|
1062
|
-
const timer = setTimeout(
|
|
1191
|
+
await new Promise((resolve2) => {
|
|
1192
|
+
const timer = setTimeout(resolve2, delayMs);
|
|
1063
1193
|
const checkStopped = setInterval(() => {
|
|
1064
1194
|
if (host.isStopped()) {
|
|
1065
1195
|
clearTimeout(timer);
|
|
1066
1196
|
clearInterval(checkStopped);
|
|
1067
|
-
|
|
1197
|
+
resolve2();
|
|
1068
1198
|
}
|
|
1069
1199
|
}, 1e3);
|
|
1070
1200
|
setTimeout(() => clearInterval(checkStopped), delayMs + 100);
|
|
@@ -1160,6 +1290,9 @@ var AgentRunner = class _AgentRunner {
|
|
|
1160
1290
|
this.connection.disconnect();
|
|
1161
1291
|
return;
|
|
1162
1292
|
}
|
|
1293
|
+
if (this.taskContext.claudeSessionId && this.taskContext._existingSpendingTotal !== null) {
|
|
1294
|
+
this.taskContext._lastReportedCostUsd = this.taskContext._existingSpendingTotal;
|
|
1295
|
+
}
|
|
1163
1296
|
if (process.env.CODESPACES === "true" && this.taskContext.baseBranch) {
|
|
1164
1297
|
const result = cleanDevcontainerFromGit(
|
|
1165
1298
|
this.config.workspaceDir,
|
|
@@ -1328,25 +1461,25 @@ ${f.content}
|
|
|
1328
1461
|
parent_tool_use_id: null
|
|
1329
1462
|
};
|
|
1330
1463
|
if (this.inputResolver) {
|
|
1331
|
-
const
|
|
1464
|
+
const resolve2 = this.inputResolver;
|
|
1332
1465
|
this.inputResolver = null;
|
|
1333
|
-
|
|
1466
|
+
resolve2(msg);
|
|
1334
1467
|
} else {
|
|
1335
1468
|
this.pendingMessages.push(msg);
|
|
1336
1469
|
}
|
|
1337
1470
|
}
|
|
1338
1471
|
waitForMessage() {
|
|
1339
|
-
return new Promise((
|
|
1472
|
+
return new Promise((resolve2) => {
|
|
1340
1473
|
const checkStopped = setInterval(() => {
|
|
1341
1474
|
if (this.stopped) {
|
|
1342
1475
|
clearInterval(checkStopped);
|
|
1343
1476
|
this.inputResolver = null;
|
|
1344
|
-
|
|
1477
|
+
resolve2(null);
|
|
1345
1478
|
}
|
|
1346
1479
|
}, 1e3);
|
|
1347
1480
|
this.inputResolver = (msg) => {
|
|
1348
1481
|
clearInterval(checkStopped);
|
|
1349
|
-
|
|
1482
|
+
resolve2(msg);
|
|
1350
1483
|
};
|
|
1351
1484
|
});
|
|
1352
1485
|
}
|
|
@@ -1478,6 +1611,260 @@ ${f.content}
|
|
|
1478
1611
|
}
|
|
1479
1612
|
};
|
|
1480
1613
|
|
|
1614
|
+
// src/project-connection.ts
|
|
1615
|
+
import { io as io2 } from "socket.io-client";
|
|
1616
|
+
var ProjectConnection = class {
|
|
1617
|
+
socket = null;
|
|
1618
|
+
config;
|
|
1619
|
+
taskAssignmentCallback = null;
|
|
1620
|
+
stopTaskCallback = null;
|
|
1621
|
+
constructor(config) {
|
|
1622
|
+
this.config = config;
|
|
1623
|
+
}
|
|
1624
|
+
connect() {
|
|
1625
|
+
return new Promise((resolve2, reject) => {
|
|
1626
|
+
let settled = false;
|
|
1627
|
+
let attempts = 0;
|
|
1628
|
+
const maxInitialAttempts = 30;
|
|
1629
|
+
this.socket = io2(this.config.apiUrl, {
|
|
1630
|
+
auth: { projectToken: this.config.projectToken },
|
|
1631
|
+
transports: ["websocket"],
|
|
1632
|
+
reconnection: true,
|
|
1633
|
+
reconnectionAttempts: Infinity,
|
|
1634
|
+
reconnectionDelay: 2e3,
|
|
1635
|
+
reconnectionDelayMax: 3e4,
|
|
1636
|
+
randomizationFactor: 0.3,
|
|
1637
|
+
extraHeaders: {
|
|
1638
|
+
"ngrok-skip-browser-warning": "true"
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
this.socket.on("projectRunner:assignTask", (data) => {
|
|
1642
|
+
if (this.taskAssignmentCallback) {
|
|
1643
|
+
this.taskAssignmentCallback(data);
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
this.socket.on("projectRunner:stopTask", (data) => {
|
|
1647
|
+
if (this.stopTaskCallback) {
|
|
1648
|
+
this.stopTaskCallback(data);
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
this.socket.on("connect", () => {
|
|
1652
|
+
if (!settled) {
|
|
1653
|
+
settled = true;
|
|
1654
|
+
resolve2();
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
this.socket.io.on("reconnect_attempt", () => {
|
|
1658
|
+
attempts++;
|
|
1659
|
+
if (!settled && attempts >= maxInitialAttempts) {
|
|
1660
|
+
settled = true;
|
|
1661
|
+
reject(new Error(`Failed to connect after ${maxInitialAttempts} attempts`));
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
onTaskAssignment(callback) {
|
|
1667
|
+
this.taskAssignmentCallback = callback;
|
|
1668
|
+
}
|
|
1669
|
+
onStopTask(callback) {
|
|
1670
|
+
this.stopTaskCallback = callback;
|
|
1671
|
+
}
|
|
1672
|
+
sendHeartbeat() {
|
|
1673
|
+
if (!this.socket) return;
|
|
1674
|
+
this.socket.emit("projectRunner:heartbeat", {});
|
|
1675
|
+
}
|
|
1676
|
+
emitTaskStarted(taskId) {
|
|
1677
|
+
if (!this.socket) return;
|
|
1678
|
+
this.socket.emit("projectRunner:taskStarted", { taskId });
|
|
1679
|
+
}
|
|
1680
|
+
emitTaskStopped(taskId, reason) {
|
|
1681
|
+
if (!this.socket) return;
|
|
1682
|
+
this.socket.emit("projectRunner:taskStopped", { taskId, reason });
|
|
1683
|
+
}
|
|
1684
|
+
disconnect() {
|
|
1685
|
+
this.socket?.disconnect();
|
|
1686
|
+
this.socket = null;
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
// src/project-runner.ts
|
|
1691
|
+
import { fork } from "child_process";
|
|
1692
|
+
import { execSync as execSync4 } from "child_process";
|
|
1693
|
+
import * as path from "path";
|
|
1694
|
+
import { fileURLToPath } from "url";
|
|
1695
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
1696
|
+
var __dirname = path.dirname(__filename);
|
|
1697
|
+
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
1698
|
+
var MAX_CONCURRENT = 3;
|
|
1699
|
+
var STOP_TIMEOUT_MS = 3e4;
|
|
1700
|
+
var ProjectRunner = class {
|
|
1701
|
+
connection;
|
|
1702
|
+
projectDir;
|
|
1703
|
+
activeAgents = /* @__PURE__ */ new Map();
|
|
1704
|
+
heartbeatTimer = null;
|
|
1705
|
+
stopping = false;
|
|
1706
|
+
constructor(config) {
|
|
1707
|
+
this.projectDir = config.projectDir;
|
|
1708
|
+
this.connection = new ProjectConnection({
|
|
1709
|
+
apiUrl: config.conveyorApiUrl,
|
|
1710
|
+
projectToken: config.projectToken,
|
|
1711
|
+
projectId: config.projectId
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
async start() {
|
|
1715
|
+
await this.connection.connect();
|
|
1716
|
+
this.connection.onTaskAssignment((assignment) => {
|
|
1717
|
+
void this.handleAssignment(assignment);
|
|
1718
|
+
});
|
|
1719
|
+
this.connection.onStopTask((data) => {
|
|
1720
|
+
this.handleStopTask(data.taskId);
|
|
1721
|
+
});
|
|
1722
|
+
this.heartbeatTimer = setInterval(() => {
|
|
1723
|
+
this.connection.sendHeartbeat();
|
|
1724
|
+
}, HEARTBEAT_INTERVAL_MS2);
|
|
1725
|
+
console.log("[project-runner] Connected, waiting for task assignments...");
|
|
1726
|
+
await new Promise((resolve2) => {
|
|
1727
|
+
process.on("SIGTERM", () => {
|
|
1728
|
+
void this.stop().then(resolve2);
|
|
1729
|
+
});
|
|
1730
|
+
process.on("SIGINT", () => {
|
|
1731
|
+
void this.stop().then(resolve2);
|
|
1732
|
+
});
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
async handleAssignment(assignment) {
|
|
1736
|
+
const { taskId, taskToken, apiUrl, mode, branch, devBranch } = assignment;
|
|
1737
|
+
const shortId = taskId.slice(0, 8);
|
|
1738
|
+
if (this.activeAgents.has(taskId)) {
|
|
1739
|
+
console.log(`[project-runner] Task ${shortId} already running, skipping`);
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (this.activeAgents.size >= MAX_CONCURRENT) {
|
|
1743
|
+
console.log(
|
|
1744
|
+
`[project-runner] Max concurrent agents (${MAX_CONCURRENT}) reached, rejecting task ${shortId}`
|
|
1745
|
+
);
|
|
1746
|
+
this.connection.emitTaskStopped(taskId, "max_concurrent_reached");
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
try {
|
|
1750
|
+
try {
|
|
1751
|
+
execSync4("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
|
|
1752
|
+
} catch {
|
|
1753
|
+
console.log(`[task:${shortId}] Warning: git fetch failed`);
|
|
1754
|
+
}
|
|
1755
|
+
const worktreePath = ensureWorktree(this.projectDir, taskId, devBranch);
|
|
1756
|
+
if (branch && branch !== devBranch) {
|
|
1757
|
+
try {
|
|
1758
|
+
execSync4(`git checkout ${branch}`, { cwd: worktreePath, stdio: "ignore" });
|
|
1759
|
+
} catch {
|
|
1760
|
+
try {
|
|
1761
|
+
execSync4(`git checkout -b ${branch}`, { cwd: worktreePath, stdio: "ignore" });
|
|
1762
|
+
} catch {
|
|
1763
|
+
console.log(`[task:${shortId}] Warning: could not checkout branch ${branch}`);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
const cliPath = path.resolve(__dirname, "cli.js");
|
|
1768
|
+
const child = fork(cliPath, [], {
|
|
1769
|
+
env: {
|
|
1770
|
+
...process.env,
|
|
1771
|
+
CONVEYOR_API_URL: apiUrl,
|
|
1772
|
+
CONVEYOR_TASK_TOKEN: taskToken,
|
|
1773
|
+
CONVEYOR_TASK_ID: taskId,
|
|
1774
|
+
CONVEYOR_MODE: mode,
|
|
1775
|
+
CONVEYOR_WORKSPACE: worktreePath,
|
|
1776
|
+
CONVEYOR_USE_WORKTREE: "false"
|
|
1777
|
+
},
|
|
1778
|
+
cwd: worktreePath,
|
|
1779
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"]
|
|
1780
|
+
});
|
|
1781
|
+
child.stdout?.on("data", (data) => {
|
|
1782
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
1783
|
+
for (const line of lines) {
|
|
1784
|
+
console.log(`[task:${shortId}] ${line}`);
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
child.stderr?.on("data", (data) => {
|
|
1788
|
+
const lines = data.toString().trimEnd().split("\n");
|
|
1789
|
+
for (const line of lines) {
|
|
1790
|
+
console.error(`[task:${shortId}] ${line}`);
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
this.activeAgents.set(taskId, { process: child, worktreePath, mode });
|
|
1794
|
+
this.connection.emitTaskStarted(taskId);
|
|
1795
|
+
console.log(`[project-runner] Started task ${shortId} in ${mode} mode at ${worktreePath}`);
|
|
1796
|
+
child.on("exit", (code) => {
|
|
1797
|
+
this.activeAgents.delete(taskId);
|
|
1798
|
+
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
1799
|
+
this.connection.emitTaskStopped(taskId, reason);
|
|
1800
|
+
console.log(`[project-runner] Task ${shortId} ${reason}`);
|
|
1801
|
+
if (code === 0) {
|
|
1802
|
+
try {
|
|
1803
|
+
removeWorktree(this.projectDir, taskId);
|
|
1804
|
+
} catch {
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
console.error(
|
|
1810
|
+
`[project-runner] Failed to start task ${shortId}:`,
|
|
1811
|
+
error instanceof Error ? error.message : error
|
|
1812
|
+
);
|
|
1813
|
+
this.connection.emitTaskStopped(
|
|
1814
|
+
taskId,
|
|
1815
|
+
`start_failed: ${error instanceof Error ? error.message : "Unknown"}`
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
handleStopTask(taskId) {
|
|
1820
|
+
const agent = this.activeAgents.get(taskId);
|
|
1821
|
+
if (!agent) return;
|
|
1822
|
+
const shortId = taskId.slice(0, 8);
|
|
1823
|
+
console.log(`[project-runner] Stopping task ${shortId}`);
|
|
1824
|
+
agent.process.kill("SIGTERM");
|
|
1825
|
+
const timer = setTimeout(() => {
|
|
1826
|
+
if (this.activeAgents.has(taskId)) {
|
|
1827
|
+
agent.process.kill("SIGKILL");
|
|
1828
|
+
}
|
|
1829
|
+
}, STOP_TIMEOUT_MS);
|
|
1830
|
+
agent.process.on("exit", () => {
|
|
1831
|
+
clearTimeout(timer);
|
|
1832
|
+
try {
|
|
1833
|
+
removeWorktree(this.projectDir, taskId);
|
|
1834
|
+
} catch {
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
async stop() {
|
|
1839
|
+
if (this.stopping) return;
|
|
1840
|
+
this.stopping = true;
|
|
1841
|
+
console.log("[project-runner] Shutting down...");
|
|
1842
|
+
if (this.heartbeatTimer) {
|
|
1843
|
+
clearInterval(this.heartbeatTimer);
|
|
1844
|
+
this.heartbeatTimer = null;
|
|
1845
|
+
}
|
|
1846
|
+
const stopPromises = [...this.activeAgents.keys()].map(
|
|
1847
|
+
(taskId) => new Promise((resolve2) => {
|
|
1848
|
+
const agent = this.activeAgents.get(taskId);
|
|
1849
|
+
if (!agent) {
|
|
1850
|
+
resolve2();
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
agent.process.on("exit", () => {
|
|
1854
|
+
resolve2();
|
|
1855
|
+
});
|
|
1856
|
+
this.handleStopTask(taskId);
|
|
1857
|
+
})
|
|
1858
|
+
);
|
|
1859
|
+
await Promise.race([
|
|
1860
|
+
Promise.all(stopPromises),
|
|
1861
|
+
new Promise((resolve2) => setTimeout(resolve2, 6e4))
|
|
1862
|
+
]);
|
|
1863
|
+
this.connection.disconnect();
|
|
1864
|
+
console.log("[project-runner] Shutdown complete");
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1481
1868
|
export {
|
|
1482
1869
|
ConveyorConnection,
|
|
1483
1870
|
loadConveyorConfig,
|
|
@@ -1485,6 +1872,8 @@ export {
|
|
|
1485
1872
|
runStartCommand,
|
|
1486
1873
|
ensureWorktree,
|
|
1487
1874
|
removeWorktree,
|
|
1488
|
-
AgentRunner
|
|
1875
|
+
AgentRunner,
|
|
1876
|
+
ProjectConnection,
|
|
1877
|
+
ProjectRunner
|
|
1489
1878
|
};
|
|
1490
|
-
//# sourceMappingURL=chunk-
|
|
1879
|
+
//# sourceMappingURL=chunk-KFCGF2SX.js.map
|