@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.
@@ -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 file: ${file.fileName} (${file.mimeType})]`);
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(`[Attached file: ${file.fileName} (${file.mimeType})]: ${file.downloadUrl}`);
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 parts = isPm ? [
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 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);
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("Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)")
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
- const lastCost = context._lastReportedCostUsd ?? 0;
959
- deltaCost = totalCostUsd - lastCost;
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
- inputTokens: Math.round(estimatedDeltaTokens * 0.7),
967
- outputTokens: Math.round(estimatedDeltaTokens * 0.3),
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, deltaCost, retriable };
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
- 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.";
987
1100
  await host.callbacks.onEvent({
988
1101
  type: "completed",
989
- summary: "Task completed.",
990
- costUsd: result.deltaCost,
1102
+ summary,
1103
+ costUsd: result.totalCostUsd,
991
1104
  durationMs
992
1105
  });
993
1106
  } else {
994
- const resultEvent = event;
995
- if (resultEvent.subtype === "success") {
996
- const summary = "result" in resultEvent ? String(resultEvent.result) : "Task completed.";
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
- if (this.taskContext.claudeSessionId && this.taskContext._existingSpendingTotal !== null) {
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-JJBVPGTW.js.map
2278
+ //# sourceMappingURL=chunk-MKSZQWR3.js.map