@rallycry/conveyor-agent 2.15.0 → 2.17.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-JJBVPGTW.js → chunk-MKSZQWR3.js} +358 -49
- package/dist/chunk-MKSZQWR3.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +76 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-JJBVPGTW.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;
|
|
@@ -543,13 +584,18 @@ ${context.plan}`);
|
|
|
543
584
|
parts.push(`[${sender}]: ${msg.content}`);
|
|
544
585
|
if (msg.files?.length) {
|
|
545
586
|
for (const file of msg.files) {
|
|
587
|
+
const sizeStr = file.fileSize ? `, ${formatFileSize(file.fileSize)}` : "";
|
|
546
588
|
if (file.content && file.contentEncoding === "utf-8") {
|
|
547
|
-
parts.push(`[Attached
|
|
589
|
+
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
548
590
|
parts.push("```");
|
|
549
591
|
parts.push(file.content);
|
|
550
592
|
parts.push("```");
|
|
551
593
|
} else if (!file.content) {
|
|
552
|
-
parts.push(
|
|
594
|
+
parts.push(
|
|
595
|
+
`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]: ${file.downloadUrl}`
|
|
596
|
+
);
|
|
597
|
+
} else {
|
|
598
|
+
parts.push(`[Attached: ${file.fileName} (${file.mimeType}${sizeStr})]`);
|
|
553
599
|
}
|
|
554
600
|
}
|
|
555
601
|
}
|
|
@@ -562,7 +608,14 @@ function buildInstructions(mode, context, scenario) {
|
|
|
562
608
|
## Instructions`];
|
|
563
609
|
const isPm = mode === "pm";
|
|
564
610
|
if (scenario === "fresh") {
|
|
565
|
-
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) {
|
|
566
619
|
parts.push(
|
|
567
620
|
`You are the project manager for this task.`,
|
|
568
621
|
`The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
|
|
@@ -644,7 +697,7 @@ function buildInitialPrompt(mode, context) {
|
|
|
644
697
|
}
|
|
645
698
|
function buildSystemPrompt(mode, context, config, setupLog) {
|
|
646
699
|
const isPm = mode === "pm";
|
|
647
|
-
const
|
|
700
|
+
const pmParts = [
|
|
648
701
|
`You are an AI project manager helping to plan tasks for the "${context.title}" project.`,
|
|
649
702
|
`You are running locally with full access to the repository.`,
|
|
650
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.`,
|
|
@@ -659,7 +712,34 @@ Workflow:`,
|
|
|
659
712
|
`- You can also use update_task directly to save the plan to the task.`,
|
|
660
713
|
`- After saving the plan, post a summary to chat and end your turn. Do NOT attempt to execute the plan yourself.`,
|
|
661
714
|
`- A separate task agent will handle execution after the team reviews and approves your plan.`
|
|
662
|
-
]
|
|
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 : [
|
|
663
743
|
`You are an AI agent working on a task for the "${context.title}" project.`,
|
|
664
744
|
`You are running inside a GitHub Codespace with full access to the repository.`,
|
|
665
745
|
`
|
|
@@ -776,10 +856,48 @@ function buildCommonTools(connection, config) {
|
|
|
776
856
|
}
|
|
777
857
|
},
|
|
778
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 } }
|
|
779
889
|
)
|
|
780
890
|
];
|
|
781
891
|
}
|
|
782
|
-
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);
|
|
783
901
|
return [
|
|
784
902
|
tool(
|
|
785
903
|
"update_task",
|
|
@@ -805,7 +923,7 @@ function buildPmTools(connection) {
|
|
|
805
923
|
description: z.string().optional().describe("Brief description"),
|
|
806
924
|
plan: z.string().optional().describe("Implementation plan in markdown"),
|
|
807
925
|
ordinal: z.number().optional().describe("Step/order number (0-based)"),
|
|
808
|
-
storyPointValue: z.number().optional().describe(
|
|
926
|
+
storyPointValue: z.number().optional().describe(spDescription)
|
|
809
927
|
},
|
|
810
928
|
async (params) => {
|
|
811
929
|
try {
|
|
@@ -827,7 +945,7 @@ function buildPmTools(connection) {
|
|
|
827
945
|
description: z.string().optional(),
|
|
828
946
|
plan: z.string().optional(),
|
|
829
947
|
ordinal: z.number().optional(),
|
|
830
|
-
storyPointValue: z.number().optional()
|
|
948
|
+
storyPointValue: z.number().optional().describe(spDescription)
|
|
831
949
|
},
|
|
832
950
|
async ({ subtaskId, ...fields }) => {
|
|
833
951
|
try {
|
|
@@ -896,9 +1014,9 @@ function buildTaskTools(connection) {
|
|
|
896
1014
|
function textResult(text) {
|
|
897
1015
|
return { content: [{ type: "text", text }] };
|
|
898
1016
|
}
|
|
899
|
-
function createConveyorMcpServer(connection, config) {
|
|
1017
|
+
function createConveyorMcpServer(connection, config, context) {
|
|
900
1018
|
const commonTools = buildCommonTools(connection, config);
|
|
901
|
-
const modeTools = config.mode === "pm" ? buildPmTools(connection) : buildTaskTools(connection);
|
|
1019
|
+
const modeTools = config.mode === "pm" ? buildPmTools(connection, context?.storyPoints) : buildTaskTools(connection);
|
|
902
1020
|
return createSdkMcpServer({
|
|
903
1021
|
name: "conveyor",
|
|
904
1022
|
tools: [...commonTools, ...modeTools]
|
|
@@ -946,7 +1064,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
|
|
|
946
1064
|
function handleResultEvent(event, host, context, startTime) {
|
|
947
1065
|
const resultEvent = event;
|
|
948
1066
|
let totalCostUsd = 0;
|
|
949
|
-
let deltaCost = 0;
|
|
950
1067
|
let retriable = false;
|
|
951
1068
|
if (resultEvent.subtype === "success") {
|
|
952
1069
|
totalCostUsd = "total_cost_usd" in resultEvent ? resultEvent.total_cost_usd : 0;
|
|
@@ -955,18 +1072,12 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
955
1072
|
if (API_ERROR_PATTERN.test(summary) && durationMs < 3e4) {
|
|
956
1073
|
retriable = true;
|
|
957
1074
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
context._lastReportedCostUsd = totalCostUsd;
|
|
961
|
-
host.connection.sendEvent({ type: "completed", summary, costUsd: deltaCost, durationMs });
|
|
962
|
-
if (deltaCost > 0 && context.agentId) {
|
|
963
|
-
const estimatedDeltaTokens = Math.round(deltaCost * 1e5);
|
|
1075
|
+
host.connection.sendEvent({ type: "completed", summary, costUsd: totalCostUsd, durationMs });
|
|
1076
|
+
if (totalCostUsd > 0 && context.agentId && context._runnerSessionId) {
|
|
964
1077
|
host.connection.trackSpending({
|
|
965
1078
|
agentId: context.agentId,
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
totalTokens: estimatedDeltaTokens,
|
|
969
|
-
totalCostUsd: deltaCost,
|
|
1079
|
+
sessionId: context._runnerSessionId,
|
|
1080
|
+
totalCostUsd,
|
|
970
1081
|
onSubscription: host.config.mode === "pm" || !!process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
971
1082
|
});
|
|
972
1083
|
}
|
|
@@ -978,33 +1089,24 @@ function handleResultEvent(event, host, context, startTime) {
|
|
|
978
1089
|
}
|
|
979
1090
|
host.connection.sendEvent({ type: "error", message: errorMsg });
|
|
980
1091
|
}
|
|
981
|
-
return { totalCostUsd,
|
|
1092
|
+
return { totalCostUsd, retriable };
|
|
982
1093
|
}
|
|
983
1094
|
async function emitResultEvent(event, host, context, startTime) {
|
|
984
1095
|
const result = handleResultEvent(event, host, context, startTime);
|
|
985
1096
|
const durationMs = Date.now() - startTime;
|
|
986
|
-
|
|
1097
|
+
const resultEvent = event;
|
|
1098
|
+
if (resultEvent.subtype === "success") {
|
|
1099
|
+
const summary = "result" in resultEvent ? String(resultEvent.result) : "Task completed.";
|
|
987
1100
|
await host.callbacks.onEvent({
|
|
988
1101
|
type: "completed",
|
|
989
|
-
summary
|
|
990
|
-
costUsd: result.
|
|
1102
|
+
summary,
|
|
1103
|
+
costUsd: result.totalCostUsd,
|
|
991
1104
|
durationMs
|
|
992
1105
|
});
|
|
993
1106
|
} else {
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
await host.callbacks.onEvent({
|
|
998
|
-
type: "completed",
|
|
999
|
-
summary,
|
|
1000
|
-
costUsd: 0,
|
|
1001
|
-
durationMs
|
|
1002
|
-
});
|
|
1003
|
-
} else {
|
|
1004
|
-
const errors = "errors" in resultEvent ? resultEvent.errors : [];
|
|
1005
|
-
const errorMsg = errors.length > 0 ? errors.join(", ") : `Agent stopped: ${resultEvent.subtype}`;
|
|
1006
|
-
await host.callbacks.onEvent({ type: "error", message: errorMsg });
|
|
1007
|
-
}
|
|
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 });
|
|
1008
1110
|
}
|
|
1009
1111
|
return result.retriable;
|
|
1010
1112
|
}
|
|
@@ -1023,9 +1125,6 @@ async function processEvents(events, context, host) {
|
|
|
1023
1125
|
if (sessionId && !sessionIdStored) {
|
|
1024
1126
|
sessionIdStored = true;
|
|
1025
1127
|
host.connection.storeSessionId(sessionId);
|
|
1026
|
-
if (sessionId !== context.claudeSessionId) {
|
|
1027
|
-
context._lastReportedCostUsd = 0;
|
|
1028
|
-
}
|
|
1029
1128
|
}
|
|
1030
1129
|
await host.callbacks.onEvent({
|
|
1031
1130
|
type: "thinking",
|
|
@@ -1099,7 +1198,7 @@ function buildCanUseTool(host) {
|
|
|
1099
1198
|
function buildQueryOptions(host, context) {
|
|
1100
1199
|
const settings = context.agentSettings ?? host.config.agentSettings ?? {};
|
|
1101
1200
|
const systemPromptText = buildSystemPrompt(host.config.mode, context, host.config, host.setupLog);
|
|
1102
|
-
const conveyorMcp = createConveyorMcpServer(host.connection, host.config);
|
|
1201
|
+
const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
|
|
1103
1202
|
const isPm = host.config.mode === "pm";
|
|
1104
1203
|
const pmDisallowedTools = isPm ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
1105
1204
|
const disallowedTools = [...settings.disallowedTools ?? [], ...pmDisallowedTools];
|
|
@@ -1211,7 +1310,6 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1211
1310
|
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
|
|
1212
1311
|
if (host.isStopped()) return;
|
|
1213
1312
|
const agentQuery = attempt === 0 ? initialQuery : (() => {
|
|
1214
|
-
context._lastReportedCostUsd = 0;
|
|
1215
1313
|
const retryPrompt = buildMultimodalPrompt(
|
|
1216
1314
|
buildInitialPrompt(host.config.mode, context),
|
|
1217
1315
|
context
|
|
@@ -1228,7 +1326,6 @@ async function runWithRetry(initialQuery, context, host, options) {
|
|
|
1228
1326
|
const isStaleSession = error instanceof Error && error.message.includes("No conversation found with session ID");
|
|
1229
1327
|
if (isStaleSession && context.claudeSessionId) {
|
|
1230
1328
|
context.claudeSessionId = null;
|
|
1231
|
-
context._lastReportedCostUsd = 0;
|
|
1232
1329
|
host.connection.storeSessionId("");
|
|
1233
1330
|
const freshPrompt = buildMultimodalPrompt(
|
|
1234
1331
|
buildInitialPrompt(host.config.mode, context),
|
|
@@ -1362,9 +1459,7 @@ var AgentRunner = class _AgentRunner {
|
|
|
1362
1459
|
this.connection.disconnect();
|
|
1363
1460
|
return;
|
|
1364
1461
|
}
|
|
1365
|
-
|
|
1366
|
-
this.taskContext._lastReportedCostUsd = this.taskContext._existingSpendingTotal;
|
|
1367
|
-
}
|
|
1462
|
+
this.taskContext._runnerSessionId = randomUUID2();
|
|
1368
1463
|
if (process.env.CODESPACES === "true" && this.taskContext.baseBranch) {
|
|
1369
1464
|
const result = cleanDevcontainerFromGit(
|
|
1370
1465
|
this.config.workspaceDir,
|
|
@@ -1691,6 +1786,8 @@ var ProjectConnection = class {
|
|
|
1691
1786
|
taskAssignmentCallback = null;
|
|
1692
1787
|
stopTaskCallback = null;
|
|
1693
1788
|
shutdownCallback = null;
|
|
1789
|
+
chatMessageCallback = null;
|
|
1790
|
+
earlyChatMessages = [];
|
|
1694
1791
|
constructor(config) {
|
|
1695
1792
|
this.config = config;
|
|
1696
1793
|
}
|
|
@@ -1726,6 +1823,13 @@ var ProjectConnection = class {
|
|
|
1726
1823
|
this.shutdownCallback();
|
|
1727
1824
|
}
|
|
1728
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
|
+
});
|
|
1729
1833
|
this.socket.on("connect", () => {
|
|
1730
1834
|
if (!settled) {
|
|
1731
1835
|
settled = true;
|
|
@@ -1750,6 +1854,13 @@ var ProjectConnection = class {
|
|
|
1750
1854
|
onShutdown(callback) {
|
|
1751
1855
|
this.shutdownCallback = callback;
|
|
1752
1856
|
}
|
|
1857
|
+
onChatMessage(callback) {
|
|
1858
|
+
this.chatMessageCallback = callback;
|
|
1859
|
+
for (const msg of this.earlyChatMessages) {
|
|
1860
|
+
callback(msg);
|
|
1861
|
+
}
|
|
1862
|
+
this.earlyChatMessages = [];
|
|
1863
|
+
}
|
|
1753
1864
|
sendHeartbeat() {
|
|
1754
1865
|
if (!this.socket) return;
|
|
1755
1866
|
this.socket.emit("projectRunner:heartbeat", {});
|
|
@@ -1762,6 +1873,55 @@ var ProjectConnection = class {
|
|
|
1762
1873
|
if (!this.socket) return;
|
|
1763
1874
|
this.socket.emit("projectRunner:taskStopped", { taskId, reason });
|
|
1764
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
|
+
}
|
|
1765
1925
|
disconnect() {
|
|
1766
1926
|
this.socket?.disconnect();
|
|
1767
1927
|
this.socket = null;
|
|
@@ -1773,6 +1933,151 @@ import { fork } from "child_process";
|
|
|
1773
1933
|
import { execSync as execSync4 } from "child_process";
|
|
1774
1934
|
import * as path from "path";
|
|
1775
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
|
|
1776
2081
|
var __filename = fileURLToPath(import.meta.url);
|
|
1777
2082
|
var __dirname = path.dirname(__filename);
|
|
1778
2083
|
var HEARTBEAT_INTERVAL_MS2 = 3e4;
|
|
@@ -1805,6 +2110,10 @@ var ProjectRunner = class {
|
|
|
1805
2110
|
console.log("[project-runner] Received shutdown signal from server");
|
|
1806
2111
|
void this.stop();
|
|
1807
2112
|
});
|
|
2113
|
+
this.connection.onChatMessage((msg) => {
|
|
2114
|
+
console.log("[project-runner] Received project chat message");
|
|
2115
|
+
void handleProjectChatMessage(msg, this.connection, this.projectDir);
|
|
2116
|
+
});
|
|
1808
2117
|
this.heartbeatTimer = setInterval(() => {
|
|
1809
2118
|
this.connection.sendHeartbeat();
|
|
1810
2119
|
}, HEARTBEAT_INTERVAL_MS2);
|
|
@@ -1966,4 +2275,4 @@ export {
|
|
|
1966
2275
|
ProjectConnection,
|
|
1967
2276
|
ProjectRunner
|
|
1968
2277
|
};
|
|
1969
|
-
//# sourceMappingURL=chunk-
|
|
2278
|
+
//# sourceMappingURL=chunk-MKSZQWR3.js.map
|