@rallycry/conveyor-agent 2.14.2 → 2.16.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-7XJAQPDE.js → chunk-MKSZQWR3.js} +430 -52
- package/dist/chunk-MKSZQWR3.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +97 -7
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-7XJAQPDE.js.map +0 -1
|
@@ -90,6 +90,40 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
90
90
|
);
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
|
+
fetchTaskFiles() {
|
|
94
|
+
const socket = this.socket;
|
|
95
|
+
if (!socket) throw new Error("Not connected");
|
|
96
|
+
return new Promise((resolve2, reject) => {
|
|
97
|
+
socket.emit(
|
|
98
|
+
"agentRunner:getTaskFiles",
|
|
99
|
+
{ taskId: this.config.taskId },
|
|
100
|
+
(response) => {
|
|
101
|
+
if (response.success && response.data) {
|
|
102
|
+
resolve2(response.data);
|
|
103
|
+
} else {
|
|
104
|
+
reject(new Error(response.error ?? "Failed to fetch task files"));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
fetchTaskFile(fileId) {
|
|
111
|
+
const socket = this.socket;
|
|
112
|
+
if (!socket) throw new Error("Not connected");
|
|
113
|
+
return new Promise((resolve2, reject) => {
|
|
114
|
+
socket.emit(
|
|
115
|
+
"agentRunner:getTaskFile",
|
|
116
|
+
{ taskId: this.config.taskId, fileId },
|
|
117
|
+
(response) => {
|
|
118
|
+
if (response.success && response.data) {
|
|
119
|
+
resolve2(response.data);
|
|
120
|
+
} else {
|
|
121
|
+
reject(new Error(response.error ?? "Failed to fetch task file"));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
93
127
|
fetchTaskContext() {
|
|
94
128
|
const socket = this.socket;
|
|
95
129
|
if (!socket) throw new Error("Not connected");
|
|
@@ -417,6 +451,7 @@ function removeWorktree(projectDir, taskId) {
|
|
|
417
451
|
}
|
|
418
452
|
|
|
419
453
|
// src/runner.ts
|
|
454
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
420
455
|
import { execSync as execSync3 } from "child_process";
|
|
421
456
|
import { readdirSync, statSync, readFileSync } from "fs";
|
|
422
457
|
import { homedir } from "os";
|
|
@@ -428,6 +463,12 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
428
463
|
|
|
429
464
|
// src/prompt-builder.ts
|
|
430
465
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["InProgress", "ReviewPR", "ReviewDev", "ReviewLive"]);
|
|
466
|
+
function formatFileSize(bytes) {
|
|
467
|
+
if (bytes === void 0) return "";
|
|
468
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
469
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
470
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
471
|
+
}
|
|
431
472
|
function findLastAgentMessageIndex(history) {
|
|
432
473
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
433
474
|
if (history[i].role === "assistant") return i;
|
|
@@ -541,6 +582,23 @@ ${context.plan}`);
|
|
|
541
582
|
for (const msg of relevant) {
|
|
542
583
|
const sender = msg.userName ?? msg.role;
|
|
543
584
|
parts.push(`[${sender}]: ${msg.content}`);
|
|
585
|
+
if (msg.files?.length) {
|
|
586
|
+
for (const file of msg.files) {
|
|
587
|
+
const sizeStr = file.fileSize ? `, ${formatFileSize(file.fileSize)}` : "";
|
|
588
|
+
if (file.content && file.contentEncoding === "utf-8") {
|
|
589
|
+
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
590
|
+
parts.push("```");
|
|
591
|
+
parts.push(file.content);
|
|
592
|
+
parts.push("```");
|
|
593
|
+
} else if (!file.content) {
|
|
594
|
+
parts.push(
|
|
595
|
+
`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]: ${file.downloadUrl}`
|
|
596
|
+
);
|
|
597
|
+
} else {
|
|
598
|
+
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
544
602
|
}
|
|
545
603
|
}
|
|
546
604
|
return parts;
|
|
@@ -550,7 +608,14 @@ function buildInstructions(mode, context, scenario) {
|
|
|
550
608
|
## Instructions`];
|
|
551
609
|
const isPm = mode === "pm";
|
|
552
610
|
if (scenario === "fresh") {
|
|
553
|
-
if (isPm) {
|
|
611
|
+
if (isPm && context.isParentTask) {
|
|
612
|
+
parts.push(
|
|
613
|
+
`You are the project manager for this task and its subtasks.`,
|
|
614
|
+
`Use list_subtasks to review the current state of child tasks.`,
|
|
615
|
+
`The task details are provided above. Wait for the team to provide instructions before taking action.`,
|
|
616
|
+
`When you finish planning, save the plan with update_task, post a summary to chat, and end your turn.`
|
|
617
|
+
);
|
|
618
|
+
} else if (isPm) {
|
|
554
619
|
parts.push(
|
|
555
620
|
`You are the project manager for this task.`,
|
|
556
621
|
`The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
|
|
@@ -632,7 +697,7 @@ function buildInitialPrompt(mode, context) {
|
|
|
632
697
|
}
|
|
633
698
|
function buildSystemPrompt(mode, context, config, setupLog) {
|
|
634
699
|
const isPm = mode === "pm";
|
|
635
|
-
const
|
|
700
|
+
const pmParts = [
|
|
636
701
|
`You are an AI project manager helping to plan tasks for the "${context.title}" project.`,
|
|
637
702
|
`You are running locally with full access to the repository.`,
|
|
638
703
|
`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.`,
|
|
@@ -647,7 +712,34 @@ Workflow:`,
|
|
|
647
712
|
`- You can also use update_task directly to save the plan to the task.`,
|
|
648
713
|
`- After saving the plan, post a summary to chat and end your turn. Do NOT attempt to execute the plan yourself.`,
|
|
649
714
|
`- A separate task agent will handle execution after the team reviews and approves your plan.`
|
|
650
|
-
]
|
|
715
|
+
];
|
|
716
|
+
if (isPm && context.isParentTask) {
|
|
717
|
+
pmParts.push(
|
|
718
|
+
`
|
|
719
|
+
You are the Project Manager for this set of tasks.`,
|
|
720
|
+
`This task has child tasks (subtasks) that are tracked on the board.`,
|
|
721
|
+
`Your role is to coordinate, plan, and manage the subtasks \u2014 not to write code directly.`,
|
|
722
|
+
`Use the subtask tools (create_subtask, update_subtask, list_subtasks) to manage work breakdown.`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
if (isPm && context.storyPoints && context.storyPoints.length > 0) {
|
|
726
|
+
pmParts.push(`
|
|
727
|
+
Story Point Tiers:`);
|
|
728
|
+
for (const sp of context.storyPoints) {
|
|
729
|
+
const desc = sp.description ? ` \u2014 ${sp.description}` : "";
|
|
730
|
+
pmParts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (isPm && context.projectAgents && context.projectAgents.length > 0) {
|
|
734
|
+
pmParts.push(`
|
|
735
|
+
Project Agents:`);
|
|
736
|
+
for (const pa of context.projectAgents) {
|
|
737
|
+
const role = pa.role ? `role: ${pa.role}` : "role: unassigned";
|
|
738
|
+
const sp = pa.storyPoints != null ? `, story points: ${pa.storyPoints}` : "";
|
|
739
|
+
pmParts.push(`- ${pa.agent.name} (${role}${sp})`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const parts = isPm ? pmParts : [
|
|
651
743
|
`You are an AI agent working on a task for the "${context.title}" project.`,
|
|
652
744
|
`You are running inside a GitHub Codespace with full access to the repository.`,
|
|
653
745
|
`
|
|
@@ -764,10 +856,48 @@ function buildCommonTools(connection, config) {
|
|
|
764
856
|
}
|
|
765
857
|
},
|
|
766
858
|
{ annotations: { readOnlyHint: true } }
|
|
859
|
+
),
|
|
860
|
+
tool(
|
|
861
|
+
"list_task_files",
|
|
862
|
+
"List all files attached to this task with metadata (name, type, size) and download URLs",
|
|
863
|
+
{},
|
|
864
|
+
async () => {
|
|
865
|
+
try {
|
|
866
|
+
const files = await connection.fetchTaskFiles();
|
|
867
|
+
return textResult(JSON.stringify(files, null, 2));
|
|
868
|
+
} catch {
|
|
869
|
+
return textResult("Failed to list task files.");
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
{ annotations: { readOnlyHint: true } }
|
|
873
|
+
),
|
|
874
|
+
tool(
|
|
875
|
+
"get_task_file",
|
|
876
|
+
"Get a specific task file's content and download URL by file ID",
|
|
877
|
+
{ fileId: z.string().describe("The file ID to retrieve") },
|
|
878
|
+
async ({ fileId }) => {
|
|
879
|
+
try {
|
|
880
|
+
const file = await connection.fetchTaskFile(fileId);
|
|
881
|
+
return textResult(JSON.stringify(file, null, 2));
|
|
882
|
+
} catch (error) {
|
|
883
|
+
return textResult(
|
|
884
|
+
`Failed to get task file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
{ annotations: { readOnlyHint: true } }
|
|
767
889
|
)
|
|
768
890
|
];
|
|
769
891
|
}
|
|
770
|
-
function
|
|
892
|
+
function buildStoryPointDescription(storyPoints) {
|
|
893
|
+
if (storyPoints && storyPoints.length > 0) {
|
|
894
|
+
const tiers = storyPoints.map((sp) => `${sp.value}=${sp.name}`).join(", ");
|
|
895
|
+
return `Story point value (${tiers})`;
|
|
896
|
+
}
|
|
897
|
+
return "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
|
|
898
|
+
}
|
|
899
|
+
function buildPmTools(connection, storyPoints) {
|
|
900
|
+
const spDescription = buildStoryPointDescription(storyPoints);
|
|
771
901
|
return [
|
|
772
902
|
tool(
|
|
773
903
|
"update_task",
|
|
@@ -793,7 +923,7 @@ function buildPmTools(connection) {
|
|
|
793
923
|
description: z.string().optional().describe("Brief description"),
|
|
794
924
|
plan: z.string().optional().describe("Implementation plan in markdown"),
|
|
795
925
|
ordinal: z.number().optional().describe("Step/order number (0-based)"),
|
|
796
|
-
storyPointValue: z.number().optional().describe(
|
|
926
|
+
storyPointValue: z.number().optional().describe(spDescription)
|
|
797
927
|
},
|
|
798
928
|
async (params) => {
|
|
799
929
|
try {
|
|
@@ -815,7 +945,7 @@ function buildPmTools(connection) {
|
|
|
815
945
|
description: z.string().optional(),
|
|
816
946
|
plan: z.string().optional(),
|
|
817
947
|
ordinal: z.number().optional(),
|
|
818
|
-
storyPointValue: z.number().optional()
|
|
948
|
+
storyPointValue: z.number().optional().describe(spDescription)
|
|
819
949
|
},
|
|
820
950
|
async ({ subtaskId, ...fields }) => {
|
|
821
951
|
try {
|
|
@@ -884,9 +1014,9 @@ function buildTaskTools(connection) {
|
|
|
884
1014
|
function textResult(text) {
|
|
885
1015
|
return { content: [{ type: "text", text }] };
|
|
886
1016
|
}
|
|
887
|
-
function createConveyorMcpServer(connection, config) {
|
|
1017
|
+
function createConveyorMcpServer(connection, config, context) {
|
|
888
1018
|
const commonTools = buildCommonTools(connection, config);
|
|
889
|
-
const modeTools = config.mode === "pm" ? buildPmTools(connection) : buildTaskTools(connection);
|
|
1019
|
+
const modeTools = config.mode === "pm" ? buildPmTools(connection, context?.storyPoints) : buildTaskTools(connection);
|
|
890
1020
|
return createSdkMcpServer({
|
|
891
1021
|
name: "conveyor",
|
|
892
1022
|
tools: [...commonTools, ...modeTools]
|
|
@@ -934,7 +1064,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
934
1064
|
function handleResultEvent(event, host, context, startTime) {
|
|
935
1065
|
const resultEvent = event;
|
|
936
1066
|
let totalCostUsd = 0;
|
|
937
|
-
let deltaCost = 0;
|
|
938
1067
|
let retriable = false;
|
|
939
1068
|
if (resultEvent.subtype === "success") {
|
|
940
1069
|
totalCostUsd = "total_cost_usd" in resultEvent ? resultEvent.total_cost_usd : 0;
|
|
@@ -943,18 +1072,12 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
943
1072
|
if (API_ERROR_PATTERN.test(summary) && durationMs < 3e4) {
|
|
944
1073
|
retriable = true;
|
|
945
1074
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
context._lastReportedCostUsd = totalCostUsd;
|
|
949
|
-
host.connection.sendEvent({ type: "completed", summary, costUsd: deltaCost, durationMs });
|
|
950
|
-
if (deltaCost > 0 && context.agentId) {
|
|
951
|
-
const estimatedDeltaTokens = Math.round(deltaCost * 1e5);
|
|
1075
|
+
host.connection.sendEvent({ type: "completed", summary, costUsd: totalCostUsd, durationMs });
|
|
1076
|
+
if (totalCostUsd > 0 && context.agentId && context._runnerSessionId) {
|
|
952
1077
|
host.connection.trackSpending({
|
|
953
1078
|
agentId: context.agentId,
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
totalTokens: estimatedDeltaTokens,
|
|
957
|
-
totalCostUsd: deltaCost,
|
|
1079
|
+
sessionId: context._runnerSessionId,
|
|
1080
|
+
totalCostUsd,
|
|
958
1081
|
onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
959
1082
|
});
|
|
960
1083
|
}
|
|
@@ -966,33 +1089,24 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
966
1089
|
}
|
|
967
1090
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
968
1091
|
}
|
|
969
|
-
return { totalCostUsd,
|
|
1092
|
+
return { totalCostUsd, retriable };
|
|
970
1093
|
}
|
|
971
1094
|
async function emitResultEvent(event, host, context, startTime) {
|
|
972
1095
|
const result = handleResultEvent(event, host, context, startTime);
|
|
973
1096
|
const durationMs = Date.now() - startTime;
|
|
974
|
-
|
|
1097
|
+
const resultEvent = event;
|
|
1098
|
+
if (resultEvent.subtype === "success") {
|
|
1099
|
+
const summary = "result" in resultEvent ? String(resultEvent.result) : "Task completed.";
|
|
975
1100
|
await host.callbacks.onEvent({
|
|
976
1101
|
type: "completed",
|
|
977
|
-
summary
|
|
978
|
-
costUsd: result.
|
|
1102
|
+
summary,
|
|
1103
|
+
costUsd: result.totalCostUsd,
|
|
979
1104
|
durationMs
|
|
980
1105
|
});
|
|
981
1106
|
} else {
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
await host.callbacks.onEvent({
|
|
986
|
-
type: "completed",
|
|
987
|
-
summary,
|
|
988
|
-
costUsd: 0,
|
|
989
|
-
durationMs
|
|
990
|
-
});
|
|
991
|
-
} else {
|
|
992
|
-
const errors = "errors" in resultEvent ? resultEvent.errors : [];
|
|
993
|
-
const errorMsg = errors.length > 0 ? errors.join(", ") : `Agent stopped: ${resultEvent.subtype}`;
|
|
994
|
-
await host.callbacks.onEvent({ type: "error", message: errorMsg });
|
|
995
|
-
}
|
|
1107
|
+
const errors = "errors" in resultEvent ? resultEvent.errors : [];
|
|
1108
|
+
const errorMsg = errors.length > 0 ? errors.join(", ") : `Agent stopped: ${resultEvent.subtype}`;
|
|
1109
|
+
await host.callbacks.onEvent({ type: "error", message: errorMsg });
|
|
996
1110
|
}
|
|
997
1111
|
return result.retriable;
|
|
998
1112
|
}
|
|
@@ -1011,9 +1125,6 @@ async function processEvents(events, context, host) {
|
|
|
1011
1125
|
if (sessionId && !sessionIdStored) {
|
|
1012
1126
|
sessionIdStored = true;
|
|
1013
1127
|
host.connection.storeSessionId(sessionId);
|
|
1014
|
-
if (sessionId !== context.claudeSessionId) {
|
|
1015
|
-
context._lastReportedCostUsd = 0;
|
|
1016
|
-
}
|
|
1017
1128
|
}
|
|
1018
1129
|
await host.callbacks.onEvent({
|
|
1019
1130
|
type: "thinking",
|
|
@@ -1087,7 +1198,7 @@ function buildCanUseTool(host) {
|
|
|
1087
1198
|
function buildQueryOptions(host, context) {
|
|
1088
1199
|
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
1089
1200
|
const systemPromptText = buildSystemPrompt(host.config.mode, context, host.config, host.setupLog);
|
|
1090
|
-
const conveyorMcp = createConveyorMcpServer(host.connection, host.config);
|
|
1201
|
+
const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
|
|
1091
1202
|
const isPm = host.config.mode === "pm";
|
|
1092
1203
|
const pmDisallowedTools = isPm ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
1093
1204
|
const disallowedTools = [...settings.disallowedTools ?? [], ...pmDisallowedTools];
|
|
@@ -1115,6 +1226,50 @@ function buildQueryOptions(host, context) {
|
|
|
1115
1226
|
enableFileCheckpointing: settings.enableFileCheckpointing
|
|
1116
1227
|
};
|
|
1117
1228
|
}
|
|
1229
|
+
function buildMultimodalPrompt(textPrompt, context) {
|
|
1230
|
+
const taskImages = (context.files ?? []).filter(
|
|
1231
|
+
(f) => f.content && f.contentEncoding === "base64"
|
|
1232
|
+
);
|
|
1233
|
+
const chatImages = [];
|
|
1234
|
+
for (const msg of context.chatHistory) {
|
|
1235
|
+
for (const f of msg.files ?? []) {
|
|
1236
|
+
if (f.content && f.contentEncoding === "base64") {
|
|
1237
|
+
chatImages.push({ fileName: f.fileName, mimeType: f.mimeType, content: f.content });
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (taskImages.length === 0 && chatImages.length === 0) return textPrompt;
|
|
1242
|
+
const blocks = [{ type: "text", text: textPrompt }];
|
|
1243
|
+
for (const file of taskImages) {
|
|
1244
|
+
blocks.push({
|
|
1245
|
+
type: "image",
|
|
1246
|
+
source: {
|
|
1247
|
+
type: "base64",
|
|
1248
|
+
media_type: file.mimeType,
|
|
1249
|
+
data: file.content
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
blocks.push({
|
|
1253
|
+
type: "text",
|
|
1254
|
+
text: `[Attached image: ${file.fileName} (${file.mimeType})]`
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
for (const file of chatImages) {
|
|
1258
|
+
blocks.push({
|
|
1259
|
+
type: "image",
|
|
1260
|
+
source: {
|
|
1261
|
+
type: "base64",
|
|
1262
|
+
media_type: file.mimeType,
|
|
1263
|
+
data: file.content
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
blocks.push({
|
|
1267
|
+
type: "text",
|
|
1268
|
+
text: `[Chat image: ${file.fileName} (${file.mimeType})]`
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
return blocks;
|
|
1272
|
+
}
|
|
1118
1273
|
async function runSdkQuery(host, context, followUpContent) {
|
|
1119
1274
|
if (host.isStopped()) return;
|
|
1120
1275
|
const isPm = host.config.mode === "pm";
|
|
@@ -1124,20 +1279,25 @@ async function runSdkQuery(host, context, followUpContent) {
|
|
|
1124
1279
|
const options = buildQueryOptions(host, context);
|
|
1125
1280
|
const resume = context.claudeSessionId ?? void 0;
|
|
1126
1281
|
if (followUpContent) {
|
|
1127
|
-
const
|
|
1282
|
+
const textPrompt = isPm ? `${buildInitialPrompt(host.config.mode, context)}
|
|
1128
1283
|
|
|
1129
1284
|
---
|
|
1130
1285
|
|
|
1131
1286
|
The team says:
|
|
1132
1287
|
${followUpContent}` : followUpContent;
|
|
1133
|
-
const
|
|
1288
|
+
const prompt = isPm ? buildMultimodalPrompt(textPrompt, context) : textPrompt;
|
|
1289
|
+
const agentQuery = query({
|
|
1290
|
+
prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
|
|
1291
|
+
options: { ...options, resume }
|
|
1292
|
+
});
|
|
1134
1293
|
await runWithRetry(agentQuery, context, host, options);
|
|
1135
1294
|
} else if (isPm) {
|
|
1136
1295
|
return;
|
|
1137
1296
|
} else {
|
|
1138
1297
|
const initialPrompt = buildInitialPrompt(host.config.mode, context);
|
|
1298
|
+
const prompt = buildMultimodalPrompt(initialPrompt, context);
|
|
1139
1299
|
const agentQuery = query({
|
|
1140
|
-
prompt: host.createInputStream(
|
|
1300
|
+
prompt: host.createInputStream(prompt),
|
|
1141
1301
|
options: { ...options, resume }
|
|
1142
1302
|
});
|
|
1143
1303
|
await runWithRetry(agentQuery, context, host, options);
|
|
@@ -1150,9 +1310,12 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1150
1310
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
1151
1311
|
if (host.isStopped()) return;
|
|
1152
1312
|
const agentQuery = attempt === 0 ? initialQuery : (() => {
|
|
1153
|
-
|
|
1313
|
+
const retryPrompt = buildMultimodalPrompt(
|
|
1314
|
+
buildInitialPrompt(host.config.mode, context),
|
|
1315
|
+
context
|
|
1316
|
+
);
|
|
1154
1317
|
return query({
|
|
1155
|
-
prompt: host.createInputStream(
|
|
1318
|
+
prompt: host.createInputStream(retryPrompt),
|
|
1156
1319
|
options: { ...options, resume: void 0 }
|
|
1157
1320
|
});
|
|
1158
1321
|
})();
|
|
@@ -1163,10 +1326,13 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1163
1326
|
const isStaleSession = error instanceof Error && error.message.includes("No conversation found with session ID");
|
|
1164
1327
|
if (isStaleSession && context.claudeSessionId) {
|
|
1165
1328
|
context.claudeSessionId = null;
|
|
1166
|
-
context._lastReportedCostUsd = 0;
|
|
1167
1329
|
host.connection.storeSessionId("");
|
|
1330
|
+
const freshPrompt = buildMultimodalPrompt(
|
|
1331
|
+
buildInitialPrompt(host.config.mode, context),
|
|
1332
|
+
context
|
|
1333
|
+
);
|
|
1168
1334
|
const freshQuery = query({
|
|
1169
|
-
prompt: host.createInputStream(
|
|
1335
|
+
prompt: host.createInputStream(freshPrompt),
|
|
1170
1336
|
options: { ...options, resume: void 0 }
|
|
1171
1337
|
});
|
|
1172
1338
|
return runWithRetry(freshQuery, context, host, options);
|
|
@@ -1293,9 +1459,7 @@ var AgentRunner = class _AgentRunner {
|
|
|
1293
1459
|
this.connection.disconnect();
|
|
1294
1460
|
return;
|
|
1295
1461
|
}
|
|
1296
|
-
|
|
1297
|
-
this.taskContext._lastReportedCostUsd = this.taskContext._existingSpendingTotal;
|
|
1298
|
-
}
|
|
1462
|
+
this.taskContext._runnerSessionId = randomUUID2();
|
|
1299
1463
|
if (process.env.CODESPACES === "true" && this.taskContext.baseBranch) {
|
|
1300
1464
|
const result = cleanDevcontainerFromGit(
|
|
1301
1465
|
this.config.workspaceDir,
|
|
@@ -1622,6 +1786,8 @@ var ProjectConnection = class {
|
|
|
1622
1786
|
taskAssignmentCallback = null;
|
|
1623
1787
|
stopTaskCallback = null;
|
|
1624
1788
|
shutdownCallback = null;
|
|
1789
|
+
chatMessageCallback = null;
|
|
1790
|
+
earlyChatMessages = [];
|
|
1625
1791
|
constructor(config) {
|
|
1626
1792
|
this.config = config;
|
|
1627
1793
|
}
|
|
@@ -1657,6 +1823,13 @@ var ProjectConnection = class {
|
|
|
1657
1823
|
this.shutdownCallback();
|
|
1658
1824
|
}
|
|
1659
1825
|
});
|
|
1826
|
+
this.socket.on("projectRunner:incomingChatMessage", (msg) => {
|
|
1827
|
+
if (this.chatMessageCallback) {
|
|
1828
|
+
this.chatMessageCallback(msg);
|
|
1829
|
+
} else {
|
|
1830
|
+
this.earlyChatMessages.push(msg);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1660
1833
|
this.socket.on("connect", () => {
|
|
1661
1834
|
if (!settled) {
|
|
1662
1835
|
settled = true;
|
|
@@ -1681,6 +1854,13 @@ var ProjectConnection = class {
|
|
|
1681
1854
|
onShutdown(callback) {
|
|
1682
1855
|
this.shutdownCallback = callback;
|
|
1683
1856
|
}
|
|
1857
|
+
onChatMessage(callback) {
|
|
1858
|
+
this.chatMessageCallback = callback;
|
|
1859
|
+
for (const msg of this.earlyChatMessages) {
|
|
1860
|
+
callback(msg);
|
|
1861
|
+
}
|
|
1862
|
+
this.earlyChatMessages = [];
|
|
1863
|
+
}
|
|
1684
1864
|
sendHeartbeat() {
|
|
1685
1865
|
if (!this.socket) return;
|
|
1686
1866
|
this.socket.emit("projectRunner:heartbeat", {});
|
|
@@ -1693,6 +1873,55 @@ var ProjectConnection = class {
|
|
|
1693
1873
|
if (!this.socket) return;
|
|
1694
1874
|
this.socket.emit("projectRunner:taskStopped", { taskId, reason });
|
|
1695
1875
|
}
|
|
1876
|
+
emitEvent(event) {
|
|
1877
|
+
if (!this.socket) return;
|
|
1878
|
+
this.socket.emit("conveyor:projectAgentEvent", event);
|
|
1879
|
+
}
|
|
1880
|
+
emitChatMessage(content) {
|
|
1881
|
+
const socket = this.socket;
|
|
1882
|
+
if (!socket) return Promise.reject(new Error("Not connected"));
|
|
1883
|
+
return new Promise((resolve2, reject) => {
|
|
1884
|
+
socket.emit(
|
|
1885
|
+
"conveyor:projectAgentChatMessage",
|
|
1886
|
+
{ content },
|
|
1887
|
+
(response) => {
|
|
1888
|
+
if (response.success) resolve2();
|
|
1889
|
+
else reject(new Error(response.error ?? "Failed to send chat message"));
|
|
1890
|
+
}
|
|
1891
|
+
);
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
emitAgentStatus(status) {
|
|
1895
|
+
if (!this.socket) return;
|
|
1896
|
+
this.socket.emit("conveyor:projectAgentStatus", { status });
|
|
1897
|
+
}
|
|
1898
|
+
fetchAgentContext() {
|
|
1899
|
+
const socket = this.socket;
|
|
1900
|
+
if (!socket) return Promise.reject(new Error("Not connected"));
|
|
1901
|
+
return new Promise((resolve2, reject) => {
|
|
1902
|
+
socket.emit(
|
|
1903
|
+
"projectRunner:getAgentContext",
|
|
1904
|
+
(response) => {
|
|
1905
|
+
if (response.success) resolve2(response.data ?? null);
|
|
1906
|
+
else reject(new Error(response.error ?? "Failed to fetch agent context"));
|
|
1907
|
+
}
|
|
1908
|
+
);
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
fetchChatHistory(limit) {
|
|
1912
|
+
const socket = this.socket;
|
|
1913
|
+
if (!socket) return Promise.reject(new Error("Not connected"));
|
|
1914
|
+
return new Promise((resolve2, reject) => {
|
|
1915
|
+
socket.emit(
|
|
1916
|
+
"projectRunner:getChatHistory",
|
|
1917
|
+
{ limit },
|
|
1918
|
+
(response) => {
|
|
1919
|
+
if (response.success && response.data) resolve2(response.data);
|
|
1920
|
+
else reject(new Error(response.error ?? "Failed to fetch chat history"));
|
|
1921
|
+
}
|
|
1922
|
+
);
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1696
1925
|
disconnect() {
|
|
1697
1926
|
this.socket?.disconnect();
|
|
1698
1927
|
this.socket = null;
|
|
@@ -1704,6 +1933,151 @@ import { fork } from "child_process";
|
|
|
1704
1933
|
import { execSync as execSync4 } from "child_process";
|
|
1705
1934
|
import * as path from "path";
|
|
1706
1935
|
import { fileURLToPath } from "url";
|
|
1936
|
+
|
|
1937
|
+
// src/project-chat-handler.ts
|
|
1938
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
1939
|
+
var FALLBACK_MODEL = "claude-sonnet-4-20250514";
|
|
1940
|
+
function buildSystemPrompt2(projectDir, agentCtx) {
|
|
1941
|
+
const parts = [];
|
|
1942
|
+
if (agentCtx?.agentInstructions) {
|
|
1943
|
+
parts.push(agentCtx.agentInstructions);
|
|
1944
|
+
}
|
|
1945
|
+
const projectName = agentCtx?.projectName ?? "this project";
|
|
1946
|
+
parts.push(`
|
|
1947
|
+
You are the project management assistant for ${projectName}.`);
|
|
1948
|
+
if (agentCtx?.projectDescription) {
|
|
1949
|
+
parts.push(`Project description: ${agentCtx.projectDescription}`);
|
|
1950
|
+
}
|
|
1951
|
+
parts.push(
|
|
1952
|
+
`You are running locally on the developer's machine with full access to the codebase at: ${projectDir}`,
|
|
1953
|
+
``,
|
|
1954
|
+
`Your role is to help team members:`,
|
|
1955
|
+
`- Discuss project direction and priorities`,
|
|
1956
|
+
`- Answer questions about the codebase, architecture, and implementation`,
|
|
1957
|
+
`- Help plan new work and break it into tasks`,
|
|
1958
|
+
`- Review code and suggest improvements`,
|
|
1959
|
+
``,
|
|
1960
|
+
`You have access to the local filesystem through your tools. Use them to read code,`,
|
|
1961
|
+
`understand the project structure, and provide informed answers.`,
|
|
1962
|
+
``,
|
|
1963
|
+
`Keep responses concise and helpful. When referencing code, cite specific files and line numbers.`
|
|
1964
|
+
);
|
|
1965
|
+
return parts.join("\n");
|
|
1966
|
+
}
|
|
1967
|
+
function buildPrompt(message, chatHistory) {
|
|
1968
|
+
const parts = [];
|
|
1969
|
+
if (chatHistory.length > 0) {
|
|
1970
|
+
parts.push("Recent conversation history:");
|
|
1971
|
+
for (const msg of chatHistory.slice(-20)) {
|
|
1972
|
+
const prefix = msg.role === "assistant" ? "Agent" : msg.userName ?? "User";
|
|
1973
|
+
parts.push(`[${prefix}]: ${msg.content}`);
|
|
1974
|
+
}
|
|
1975
|
+
parts.push("");
|
|
1976
|
+
}
|
|
1977
|
+
parts.push(`Latest message from the user:
|
|
1978
|
+
${message.content}`);
|
|
1979
|
+
return parts.join("\n");
|
|
1980
|
+
}
|
|
1981
|
+
async function handleProjectChatMessage(message, connection, projectDir) {
|
|
1982
|
+
connection.emitAgentStatus("busy");
|
|
1983
|
+
try {
|
|
1984
|
+
let agentCtx = null;
|
|
1985
|
+
try {
|
|
1986
|
+
agentCtx = await connection.fetchAgentContext();
|
|
1987
|
+
} catch {
|
|
1988
|
+
console.log("[project-chat] Could not fetch agent context, using defaults");
|
|
1989
|
+
}
|
|
1990
|
+
let chatHistory = [];
|
|
1991
|
+
try {
|
|
1992
|
+
chatHistory = await connection.fetchChatHistory(30);
|
|
1993
|
+
} catch {
|
|
1994
|
+
console.log("[project-chat] Could not fetch chat history, proceeding without it");
|
|
1995
|
+
}
|
|
1996
|
+
const model = agentCtx?.model || FALLBACK_MODEL;
|
|
1997
|
+
const settings = agentCtx?.agentSettings ?? {};
|
|
1998
|
+
const systemPrompt = buildSystemPrompt2(projectDir, agentCtx);
|
|
1999
|
+
const prompt = buildPrompt(message, chatHistory);
|
|
2000
|
+
const events = query2({
|
|
2001
|
+
prompt,
|
|
2002
|
+
options: {
|
|
2003
|
+
model,
|
|
2004
|
+
systemPrompt: {
|
|
2005
|
+
type: "preset",
|
|
2006
|
+
preset: "claude_code",
|
|
2007
|
+
append: systemPrompt
|
|
2008
|
+
},
|
|
2009
|
+
cwd: projectDir,
|
|
2010
|
+
permissionMode: "bypassPermissions",
|
|
2011
|
+
allowDangerouslySkipPermissions: true,
|
|
2012
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
2013
|
+
maxTurns: settings.maxTurns ?? 15,
|
|
2014
|
+
maxBudgetUsd: settings.maxBudgetUsd ?? 5,
|
|
2015
|
+
effort: settings.effort,
|
|
2016
|
+
thinking: settings.thinking
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
2019
|
+
const responseParts = [];
|
|
2020
|
+
const turnToolCalls = [];
|
|
2021
|
+
let isTyping = false;
|
|
2022
|
+
for await (const event of events) {
|
|
2023
|
+
const eventType = event.type;
|
|
2024
|
+
if (eventType === "assistant") {
|
|
2025
|
+
if (!isTyping) {
|
|
2026
|
+
setTimeout(() => connection.emitEvent({ type: "agent_typing_start" }), 200);
|
|
2027
|
+
isTyping = true;
|
|
2028
|
+
}
|
|
2029
|
+
const msg = event.message;
|
|
2030
|
+
const content = msg.content;
|
|
2031
|
+
for (const block of content) {
|
|
2032
|
+
if (block.type === "text") {
|
|
2033
|
+
responseParts.push(block.text);
|
|
2034
|
+
} else if (block.type === "tool_use") {
|
|
2035
|
+
const name = block.name;
|
|
2036
|
+
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
2037
|
+
turnToolCalls.push({
|
|
2038
|
+
tool: name,
|
|
2039
|
+
input: inputStr.slice(0, 1e4),
|
|
2040
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2041
|
+
});
|
|
2042
|
+
console.log(`[project-chat] [tool_use] ${name}`);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
if (turnToolCalls.length > 0) {
|
|
2046
|
+
connection.emitEvent({ type: "activity_block", events: [...turnToolCalls] });
|
|
2047
|
+
turnToolCalls.length = 0;
|
|
2048
|
+
}
|
|
2049
|
+
} else if (eventType === "result") {
|
|
2050
|
+
if (isTyping) {
|
|
2051
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
2052
|
+
isTyping = false;
|
|
2053
|
+
}
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
if (isTyping) {
|
|
2058
|
+
connection.emitEvent({ type: "agent_typing_stop" });
|
|
2059
|
+
}
|
|
2060
|
+
const responseText = responseParts.join("\n\n").trim();
|
|
2061
|
+
if (responseText) {
|
|
2062
|
+
await connection.emitChatMessage(responseText);
|
|
2063
|
+
}
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
console.error(
|
|
2066
|
+
"[project-chat] Failed to handle message:",
|
|
2067
|
+
error instanceof Error ? error.message : error
|
|
2068
|
+
);
|
|
2069
|
+
try {
|
|
2070
|
+
await connection.emitChatMessage(
|
|
2071
|
+
"I encountered an error processing your message. Please try again."
|
|
2072
|
+
);
|
|
2073
|
+
} catch {
|
|
2074
|
+
}
|
|
2075
|
+
} finally {
|
|
2076
|
+
connection.emitAgentStatus("idle");
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// src/project-runner.ts
|
|
1707
2081
|
var __filename = fileURLToPath(import.meta.url);
|
|
1708
2082
|
var __dirname = path.dirname(__filename);
|
|
1709
2083
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
@@ -1736,6 +2110,10 @@ var ProjectRunner = class {
|
|
|
1736
2110
|
console.log("[project-runner] Received shutdown signal from server");
|
|
1737
2111
|
void this.stop();
|
|
1738
2112
|
});
|
|
2113
|
+
this.connection.onChatMessage((msg) => {
|
|
2114
|
+
console.log("[project-runner] Received project chat message");
|
|
2115
|
+
void handleProjectChatMessage(msg, this.connection, this.projectDir);
|
|
2116
|
+
});
|
|
1739
2117
|
this.heartbeatTimer = setInterval(() => {
|
|
1740
2118
|
this.connection.sendHeartbeat();
|
|
1741
2119
|
}, HEARTBEAT_INTERVAL_MS2);
|
|
@@ -1897,4 +2275,4 @@ export {
|
|
|
1897
2275
|
ProjectConnection,
|
|
1898
2276
|
ProjectRunner
|
|
1899
2277
|
};
|
|
1900
|
-
//# sourceMappingURL=chunk-
|
|
2278
|
+
//# sourceMappingURL=chunk-MKSZQWR3.js.map
|