@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.
@@ -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 parts = isPm ? [
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 buildPmTools(connection) {
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("Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)")
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
- const lastCost = context._lastReportedCostUsd ?? 0;
947
- deltaCost = totalCostUsd - lastCost;
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
- inputTokens: Math.round(estimatedDeltaTokens * 0.7),
955
- outputTokens: Math.round(estimatedDeltaTokens * 0.3),
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, deltaCost, retriable };
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
- if (result.deltaCost > 0 && context.agentId) {
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: "Task completed.",
978
- costUsd: result.deltaCost,
1102
+ summary,
1103
+ costUsd: result.totalCostUsd,
979
1104
  durationMs
980
1105
  });
981
1106
  } else {
982
- const resultEvent = event;
983
- if (resultEvent.subtype === "success") {
984
- const summary = "result" in resultEvent ? String(resultEvent.result) : "Task completed.";
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 prompt = isPm ? `${buildInitialPrompt(host.config.mode, context)}
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 agentQuery = query({ prompt, options: { ...options, resume } });
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(initialPrompt),
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
- context._lastReportedCostUsd = 0;
1313
+ const retryPrompt = buildMultimodalPrompt(
1314
+ buildInitialPrompt(host.config.mode, context),
1315
+ context
1316
+ );
1154
1317
  return query({
1155
- prompt: host.createInputStream(buildInitialPrompt(host.config.mode, context)),
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(buildInitialPrompt(host.config.mode, context)),
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
- if (this.taskContext.claudeSessionId && this.taskContext._existingSpendingTotal !== null) {
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-7XJAQPDE.js.map
2278
+ //# sourceMappingURL=chunk-MKSZQWR3.js.map