@rallycry/conveyor-agent 3.9.0 → 4.0.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.
@@ -12,6 +12,7 @@ var ConveyorConnection = class _ConveyorConnection {
12
12
  chatMessageCallback = null;
13
13
  stopCallback = null;
14
14
  modeChangeCallback = null;
15
+ runStartCommandCallback = null;
15
16
  pendingQuestionResolvers = /* @__PURE__ */ new Map();
16
17
  constructor(config) {
17
18
  this.config = config;
@@ -61,6 +62,11 @@ var ConveyorConnection = class _ConveyorConnection {
61
62
  this.earlyModeChanges.push(data);
62
63
  }
63
64
  });
65
+ this.socket.on("agentRunner:runStartCommand", () => {
66
+ if (this.runStartCommandCallback) {
67
+ this.runStartCommandCallback();
68
+ }
69
+ });
64
70
  this.socket.on("connect", () => {
65
71
  if (!settled) {
66
72
  settled = true;
@@ -226,9 +232,12 @@ var ConveyorConnection = class _ConveyorConnection {
226
232
  }
227
233
  this.earlyModeChanges = [];
228
234
  }
229
- emitModeChanged(mode) {
235
+ onRunStartCommand(callback) {
236
+ this.runStartCommandCallback = callback;
237
+ }
238
+ emitModeChanged(agentMode) {
230
239
  if (!this.socket) return;
231
- this.socket.emit("agentRunner:modeChanged", { mode });
240
+ this.socket.emit("agentRunner:modeChanged", { agentMode });
232
241
  }
233
242
  trackSpending(params) {
234
243
  if (!this.socket) throw new Error("Not connected");
@@ -347,6 +356,66 @@ var ConveyorConnection = class _ConveyorConnection {
347
356
  );
348
357
  });
349
358
  }
359
+ updateTaskProperties(data) {
360
+ if (!this.socket) throw new Error("Not connected");
361
+ this.socket.emit("agentRunner:updateTaskProperties", data);
362
+ }
363
+ listIcons() {
364
+ const socket = this.socket;
365
+ if (!socket) throw new Error("Not connected");
366
+ return new Promise((resolve2, reject) => {
367
+ socket.emit("agentRunner:listIcons", {}, (response) => {
368
+ if (response.success && response.data) resolve2(response.data);
369
+ else reject(new Error(response.error ?? "Failed to list icons"));
370
+ });
371
+ });
372
+ }
373
+ generateTaskIcon(prompt, aspectRatio) {
374
+ const socket = this.socket;
375
+ if (!socket) throw new Error("Not connected");
376
+ return new Promise((resolve2, reject) => {
377
+ socket.emit(
378
+ "agentRunner:generateTaskIcon",
379
+ { prompt, aspectRatio },
380
+ (response) => {
381
+ if (response.success && response.data) resolve2(response.data);
382
+ else reject(new Error(response.error ?? "Failed to generate icon"));
383
+ }
384
+ );
385
+ });
386
+ }
387
+ getTaskProperties() {
388
+ const socket = this.socket;
389
+ if (!socket) throw new Error("Not connected");
390
+ return new Promise((resolve2, reject) => {
391
+ socket.emit(
392
+ "agentRunner:getTaskProperties",
393
+ {},
394
+ (response) => {
395
+ if (response.success && response.data) resolve2(response.data);
396
+ else reject(new Error(response.error ?? "Failed to get task properties"));
397
+ }
398
+ );
399
+ });
400
+ }
401
+ triggerIdentification() {
402
+ const socket = this.socket;
403
+ if (!socket) throw new Error("Not connected");
404
+ return new Promise((resolve2, reject) => {
405
+ socket.emit(
406
+ "agentRunner:triggerIdentification",
407
+ {},
408
+ (response) => {
409
+ if (response.success && response.data) resolve2(response.data);
410
+ else reject(new Error(response.error ?? "Identification failed"));
411
+ }
412
+ );
413
+ });
414
+ }
415
+ emitModeTransition(payload) {
416
+ if (!this.socket) return;
417
+ this.socket.emit("agentRunner:modeTransition", payload);
418
+ }
350
419
  disconnect() {
351
420
  this.flushEvents();
352
421
  this.socket?.disconnect();
@@ -799,6 +868,13 @@ async function processEvents(events, context, host) {
799
868
  const turnToolCalls = [];
800
869
  for await (const event of events) {
801
870
  if (host.isStopped()) break;
871
+ if (host.pendingModeRestart) {
872
+ host.pendingModeRestart = false;
873
+ if (isTyping) {
874
+ host.connection.sendTypingStop();
875
+ }
876
+ return { retriable: false, modeRestart: true };
877
+ }
802
878
  switch (event.type) {
803
879
  case "system": {
804
880
  const systemEvent = event;
@@ -1007,12 +1083,22 @@ ${context.plan}`);
1007
1083
  }
1008
1084
  return parts;
1009
1085
  }
1010
- function buildInstructions(mode, context, scenario) {
1086
+ function buildInstructions(mode, context, scenario, agentMode) {
1011
1087
  const parts = [`
1012
1088
  ## Instructions`];
1013
1089
  const isPm = mode === "pm";
1090
+ const isAutoMode = agentMode === "auto";
1014
1091
  if (scenario === "fresh") {
1015
- if (isPm && context.isParentTask) {
1092
+ if (isAutoMode && isPm) {
1093
+ parts.push(
1094
+ `You are operating autonomously. Begin planning immediately.`,
1095
+ `1. Explore the codebase to understand the architecture and relevant files`,
1096
+ `2. Draft a clear implementation plan and save it with update_task`,
1097
+ `3. Set story points (set_story_points), tags (set_task_tags), and title (set_task_title)`,
1098
+ `4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
1099
+ `Do NOT wait for team input \u2014 proceed autonomously.`
1100
+ );
1101
+ } else if (isPm && context.isParentTask) {
1016
1102
  parts.push(
1017
1103
  `You are the project manager for this task and its subtasks.`,
1018
1104
  `Use list_subtasks to review the current state of child tasks.`,
@@ -1091,6 +1177,112 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
1091
1177
  }
1092
1178
  return parts;
1093
1179
  }
1180
+ function buildPropertyInstructions(context) {
1181
+ const parts = [];
1182
+ parts.push(
1183
+ ``,
1184
+ `### Proactive Property Management`,
1185
+ `As you plan this task, proactively fill in task properties when you have enough context:`,
1186
+ `- Once you understand the scope, use set_story_points to assign a value`,
1187
+ `- Use set_task_tags to categorize the work`,
1188
+ `- For icons: FIRST call list_icons to check for existing matches. Use set_task_icon if one fits.`,
1189
+ ` Only call generate_task_icon if no existing icon is a good fit.`,
1190
+ `- Use set_task_title if the current title doesn't accurately reflect the plan`,
1191
+ ``,
1192
+ `Don't wait for the user to ask \u2014 fill these in naturally as the plan takes shape.`,
1193
+ `If the user adjusts the plan significantly, update the properties to match.`
1194
+ );
1195
+ if (context.storyPoints && context.storyPoints.length > 0) {
1196
+ parts.push(``, `Available story point tiers:`);
1197
+ for (const sp of context.storyPoints) {
1198
+ const desc = sp.description ? ` \u2014 ${sp.description}` : "";
1199
+ parts.push(`- Value ${sp.value}: "${sp.name}"${desc}`);
1200
+ }
1201
+ }
1202
+ if (context.projectTags && context.projectTags.length > 0) {
1203
+ parts.push(``, `Available project tags:`);
1204
+ for (const tag of context.projectTags) {
1205
+ parts.push(`- ID: "${tag.id}", Name: "${tag.name}"`);
1206
+ }
1207
+ }
1208
+ return parts;
1209
+ }
1210
+ function buildModePrompt(agentMode, context) {
1211
+ switch (agentMode) {
1212
+ case "discovery": {
1213
+ const parts = [
1214
+ `
1215
+ ## Mode: Discovery`,
1216
+ `You are in Discovery mode \u2014 helping plan and scope this task.`,
1217
+ `- You have read-only codebase access (can read files, run git commands, search code)`,
1218
+ `- You have MCP tools: update_task (title, description, plan, tags, SP, icon)`,
1219
+ `- You can create and manage subtasks`,
1220
+ `- You cannot write code or edit files (except .claude/plans/)`,
1221
+ `- Goal: collaborate with the user to create a clear plan`,
1222
+ `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`
1223
+ ];
1224
+ if (context) {
1225
+ parts.push(...buildPropertyInstructions(context));
1226
+ }
1227
+ return parts.join("\n");
1228
+ }
1229
+ case "building":
1230
+ return [
1231
+ `
1232
+ ## Mode: Building`,
1233
+ `You are in Building mode \u2014 executing the plan.`,
1234
+ `- You have full coding access (read, write, edit, bash, git)`,
1235
+ `- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
1236
+ `- If this is a leaf task (no children): execute the plan directly`,
1237
+ `- Goal: implement the plan, run tests, open a PR when done`
1238
+ ].join("\n");
1239
+ case "review":
1240
+ return [
1241
+ `
1242
+ ## Mode: Review`,
1243
+ `You are in Review mode \u2014 reviewing and coordinating.`,
1244
+ `- You have read-only access plus light edit capability (can suggest fixes, run tests, check linting)`,
1245
+ `- For parent tasks: you can manage children, review child PRs, fire next child builds`,
1246
+ `- You have Pack Runner coordination tools (list_subtasks, fire builds, approve PRs)`,
1247
+ `- Goal: ensure quality, provide feedback, coordinate progression`
1248
+ ].join("\n");
1249
+ case "auto": {
1250
+ const parts = [
1251
+ `
1252
+ ## Mode: Auto`,
1253
+ `You are in Auto mode \u2014 operating autonomously through planning \u2192 building \u2192 PR.`,
1254
+ ``,
1255
+ `### Phase 1: Discovery & Planning (current)`,
1256
+ `- You have read-only codebase access (can read files, run git commands, search code)`,
1257
+ `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1258
+ `- You have MCP tools for task properties: update_task, set_story_points, set_task_tags, set_task_icon, set_task_title`,
1259
+ ``,
1260
+ `### Required before transitioning:`,
1261
+ `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1262
+ `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
1263
+ `2. **Story Points** \u2014 Assign via set_story_points`,
1264
+ `3. **Title** \u2014 Set an accurate title via set_task_title (if the current one is vague or "Untitled")`,
1265
+ ``,
1266
+ `### Transitioning to Building:`,
1267
+ `When your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1268
+ `- If any required properties are missing, ExitPlanMode will be denied with details on what's missing`,
1269
+ `- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
1270
+ `- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
1271
+ ``,
1272
+ `### Autonomous Guidelines:`,
1273
+ `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1274
+ `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
1275
+ `- Be thorough in discovery: read relevant files, understand the codebase architecture, then plan`
1276
+ ];
1277
+ if (context) {
1278
+ parts.push(...buildPropertyInstructions(context));
1279
+ }
1280
+ return parts.join("\n");
1281
+ }
1282
+ default:
1283
+ return null;
1284
+ }
1285
+ }
1094
1286
  function buildPackRunnerSystemPrompt(context, config, setupLog) {
1095
1287
  const parts = [
1096
1288
  `You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
@@ -1201,7 +1393,7 @@ After addressing the feedback, resume your autonomous loop: call list_subtasks a
1201
1393
  }
1202
1394
  return parts;
1203
1395
  }
1204
- function buildInitialPrompt(mode, context, isAuto) {
1396
+ function buildInitialPrompt(mode, context, isAuto, agentMode) {
1205
1397
  const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
1206
1398
  if (!isPackRunner) {
1207
1399
  const sessionRelaunch = buildRelaunchWithSession(mode, context);
@@ -1209,12 +1401,12 @@ function buildInitialPrompt(mode, context, isAuto) {
1209
1401
  }
1210
1402
  const scenario = detectRelaunchScenario(context);
1211
1403
  const body = buildTaskBody(context);
1212
- const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario);
1404
+ const instructions = isPackRunner ? buildPackRunnerInstructions(context, scenario) : buildInstructions(mode, context, scenario, agentMode);
1213
1405
  return [...body, ...instructions].join("\n");
1214
1406
  }
1215
- function buildSystemPrompt(mode, context, config, setupLog, pmSubMode = "planning") {
1407
+ function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
1216
1408
  const isPm = mode === "pm";
1217
- const isPmActive = isPm && pmSubMode === "active";
1409
+ const isPmActive = isPm && agentMode === "building";
1218
1410
  const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
1219
1411
  if (isPackRunner) {
1220
1412
  return buildPackRunnerSystemPrompt(context, config, setupLog);
@@ -1340,6 +1532,10 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
1340
1532
  `Use the create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
1341
1533
  );
1342
1534
  }
1535
+ const modePrompt = buildModePrompt(agentMode, context);
1536
+ if (modePrompt) {
1537
+ parts.push(modePrompt);
1538
+ }
1343
1539
  return parts.join("\n");
1344
1540
  }
1345
1541
 
@@ -1707,6 +1903,117 @@ function buildTaskTools(connection) {
1707
1903
  ];
1708
1904
  }
1709
1905
 
1906
+ // src/tools/discovery-tools.ts
1907
+ import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
1908
+ import { z as z4 } from "zod";
1909
+ function buildDiscoveryTools(connection, context) {
1910
+ const spDescription = buildStoryPointDescription(context?.storyPoints);
1911
+ return [
1912
+ tool4(
1913
+ "set_story_points",
1914
+ "Set the story point estimate for this task. Use after understanding the scope of the work.",
1915
+ { value: z4.number().describe(spDescription) },
1916
+ async ({ value }) => {
1917
+ try {
1918
+ connection.updateTaskProperties({ storyPointValue: value });
1919
+ return textResult(`Story points set to ${value}`);
1920
+ } catch (error) {
1921
+ return textResult(
1922
+ `Failed to set story points: ${error instanceof Error ? error.message : "Unknown error"}`
1923
+ );
1924
+ }
1925
+ }
1926
+ ),
1927
+ tool4(
1928
+ "set_task_tags",
1929
+ "Assign tags to this task from the project's available tags. Use the tag IDs from the project tags list.",
1930
+ {
1931
+ tagIds: z4.array(z4.string()).describe("Array of tag IDs to assign")
1932
+ },
1933
+ async ({ tagIds }) => {
1934
+ try {
1935
+ connection.updateTaskProperties({ tagIds });
1936
+ return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
1937
+ } catch (error) {
1938
+ return textResult(
1939
+ `Failed to set tags: ${error instanceof Error ? error.message : "Unknown error"}`
1940
+ );
1941
+ }
1942
+ }
1943
+ ),
1944
+ tool4(
1945
+ "set_task_title",
1946
+ "Update the task title to better reflect the planned work.",
1947
+ {
1948
+ title: z4.string().describe("The new task title")
1949
+ },
1950
+ async ({ title }) => {
1951
+ try {
1952
+ connection.updateTaskProperties({ title });
1953
+ return textResult(`Task title updated to: ${title}`);
1954
+ } catch (error) {
1955
+ return textResult(
1956
+ `Failed to update title: ${error instanceof Error ? error.message : "Unknown error"}`
1957
+ );
1958
+ }
1959
+ }
1960
+ ),
1961
+ tool4(
1962
+ "list_icons",
1963
+ "List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before set_task_icon to check for existing matches.",
1964
+ {},
1965
+ async () => {
1966
+ try {
1967
+ const icons = await connection.listIcons();
1968
+ return textResult(JSON.stringify(icons, null, 2));
1969
+ } catch (error) {
1970
+ return textResult(
1971
+ `Failed to list icons: ${error instanceof Error ? error.message : "Unknown error"}`
1972
+ );
1973
+ }
1974
+ },
1975
+ { annotations: { readOnlyHint: true } }
1976
+ ),
1977
+ tool4(
1978
+ "set_task_icon",
1979
+ "Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
1980
+ {
1981
+ iconId: z4.string().describe("The icon ID to assign")
1982
+ },
1983
+ async ({ iconId }) => {
1984
+ try {
1985
+ connection.updateTaskProperties({ iconId });
1986
+ return textResult("Icon assigned to task.");
1987
+ } catch (error) {
1988
+ return textResult(
1989
+ `Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
1990
+ );
1991
+ }
1992
+ }
1993
+ ),
1994
+ tool4(
1995
+ "generate_task_icon",
1996
+ "Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
1997
+ {
1998
+ prompt: z4.string().describe(
1999
+ "Description of the icon to generate (e.g. 'minimal flat gear and wrench icon')"
2000
+ ),
2001
+ aspectRatio: z4.enum(["auto", "portrait", "landscape", "square"]).optional().describe("Icon aspect ratio, defaults to square")
2002
+ },
2003
+ async ({ prompt, aspectRatio }) => {
2004
+ try {
2005
+ const result = await connection.generateTaskIcon(prompt, aspectRatio ?? "square");
2006
+ return textResult(`Icon generated and assigned: ${result.iconId}`);
2007
+ } catch (error) {
2008
+ return textResult(
2009
+ `Failed to generate icon: ${error instanceof Error ? error.message : "Unknown error"}`
2010
+ );
2011
+ }
2012
+ }
2013
+ )
2014
+ ];
2015
+ }
2016
+
1710
2017
  // src/tools/index.ts
1711
2018
  function textResult(text) {
1712
2019
  return { content: [{ type: "text", text }] };
@@ -1716,11 +2023,30 @@ function imageBlock(data, mimeType) {
1716
2023
  }
1717
2024
  function createConveyorMcpServer(connection, config, context) {
1718
2025
  const commonTools = buildCommonTools(connection, config);
1719
- const isPackRunner = config.mode === "pm" && !!config.isAuto && !!context?.isParentTask;
1720
- const modeTools = config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: isPackRunner }) : buildTaskTools(connection);
2026
+ const agentMode = context?.agentMode;
2027
+ let modeTools;
2028
+ switch (agentMode) {
2029
+ case "building":
2030
+ modeTools = buildTaskTools(connection);
2031
+ break;
2032
+ case "review":
2033
+ modeTools = buildPmTools(connection, context?.storyPoints, {
2034
+ includePackTools: !!context?.isParentTask
2035
+ });
2036
+ break;
2037
+ case "auto":
2038
+ case "discovery":
2039
+ case "help":
2040
+ modeTools = buildPmTools(connection, context?.storyPoints, { includePackTools: false });
2041
+ break;
2042
+ default:
2043
+ modeTools = config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : buildTaskTools(connection);
2044
+ break;
2045
+ }
2046
+ const discoveryTools = agentMode === "discovery" || agentMode === "auto" ? buildDiscoveryTools(connection, context) : [];
1721
2047
  return createSdkMcpServer({
1722
2048
  name: "conveyor",
1723
- tools: [...commonTools, ...modeTools]
2049
+ tools: [...commonTools, ...modeTools, ...discoveryTools]
1724
2050
  });
1725
2051
  }
1726
2052
 
@@ -1730,84 +2056,159 @@ var IMAGE_ERROR_PATTERN = /Could not process image/i;
1730
2056
  var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
1731
2057
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
1732
2058
  var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
2059
+ function handleDiscoveryToolAccess(toolName, input) {
2060
+ if (PM_PLAN_FILE_TOOLS.has(toolName)) {
2061
+ const filePath = String(input.file_path ?? input.path ?? "");
2062
+ if (filePath.includes(".claude/plans/")) {
2063
+ return { behavior: "allow", updatedInput: input };
2064
+ }
2065
+ return {
2066
+ behavior: "deny",
2067
+ message: "Discovery mode is read-only. File writes are restricted to plan files."
2068
+ };
2069
+ }
2070
+ return { behavior: "allow", updatedInput: input };
2071
+ }
2072
+ function handleBuildingToolAccess(toolName, input) {
2073
+ if (toolName === "Bash") {
2074
+ const cmd = String(input.command ?? "");
2075
+ if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
2076
+ return {
2077
+ behavior: "deny",
2078
+ message: "Destructive operation blocked. Use safer alternatives."
2079
+ };
2080
+ }
2081
+ }
2082
+ return { behavior: "allow", updatedInput: input };
2083
+ }
2084
+ function handleReviewToolAccess(toolName, input, isParentTask) {
2085
+ if (isParentTask) {
2086
+ return handleBuildingToolAccess(toolName, input);
2087
+ }
2088
+ if (PM_PLAN_FILE_TOOLS.has(toolName)) {
2089
+ const filePath = String(input.file_path ?? input.path ?? "");
2090
+ if (filePath.includes(".claude/plans/")) {
2091
+ return { behavior: "allow", updatedInput: input };
2092
+ }
2093
+ return {
2094
+ behavior: "deny",
2095
+ message: "Review mode restricts file writes. Use bash to run tests and linting instead."
2096
+ };
2097
+ }
2098
+ if (toolName === "Bash") {
2099
+ const cmd = String(input.command ?? "");
2100
+ if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
2101
+ return { behavior: "deny", message: "Destructive operation blocked in review mode." };
2102
+ }
2103
+ }
2104
+ return { behavior: "allow", updatedInput: input };
2105
+ }
2106
+ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
2107
+ if (hasExitedPlanMode) {
2108
+ return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
2109
+ }
2110
+ if (PM_PLAN_FILE_TOOLS.has(toolName)) {
2111
+ const filePath = String(input.file_path ?? input.path ?? "");
2112
+ if (filePath.includes(".claude/plans/")) {
2113
+ return { behavior: "allow", updatedInput: input };
2114
+ }
2115
+ return {
2116
+ behavior: "deny",
2117
+ message: "You are in auto plan mode. File writes are restricted to plan files. Call ExitPlanMode when your plan is ready to start building."
2118
+ };
2119
+ }
2120
+ return { behavior: "allow", updatedInput: input };
2121
+ }
1733
2122
  function buildCanUseTool(host) {
1734
2123
  const QUESTION_TIMEOUT_MS = 5 * 60 * 1e3;
1735
2124
  return async (toolName, input) => {
1736
- const isPackRunner = host.config.mode === "pm" && !!host.config.isAuto;
1737
- const isPmPlanning = host.config.mode === "pm" && host.activeMode === "planning";
1738
- const isPmActive = host.config.mode === "pm" && host.activeMode === "active";
1739
- if (isPackRunner) {
1740
- if (toolName === "Bash") {
1741
- const cmd = String(input.command ?? "");
1742
- if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
2125
+ if (toolName === "ExitPlanMode" && host.agentMode === "auto" && !host.hasExitedPlanMode) {
2126
+ try {
2127
+ const taskProps = await host.connection.getTaskProperties();
2128
+ const missingProps = [];
2129
+ if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
2130
+ if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
2131
+ if (!taskProps.title || taskProps.title === "Untitled")
2132
+ missingProps.push("title (use set_task_title)");
2133
+ if (missingProps.length > 0) {
1743
2134
  return {
1744
2135
  behavior: "deny",
1745
- message: "Destructive operation blocked. Use safer alternatives."
2136
+ message: [
2137
+ "Cannot exit plan mode yet. Required task properties are missing:",
2138
+ ...missingProps.map((p) => `- ${p}`),
2139
+ "",
2140
+ "Fill these in using MCP tools, then try ExitPlanMode again."
2141
+ ].join("\n")
1746
2142
  };
1747
2143
  }
1748
- }
1749
- return { behavior: "allow", updatedInput: input };
1750
- }
1751
- if (isPmPlanning && PM_PLAN_FILE_TOOLS.has(toolName)) {
1752
- const filePath = String(input.file_path ?? input.path ?? "");
1753
- if (filePath.includes(".claude/plans/")) {
2144
+ await host.connection.triggerIdentification();
2145
+ host.hasExitedPlanMode = true;
2146
+ const newMode = host.isParentTask ? "review" : "building";
2147
+ host.pendingModeRestart = true;
2148
+ if (host.onModeTransition) {
2149
+ host.onModeTransition(newMode);
2150
+ }
1754
2151
  return { behavior: "allow", updatedInput: input };
2152
+ } catch (err) {
2153
+ return {
2154
+ behavior: "deny",
2155
+ message: `Identification failed: ${err instanceof Error ? err.message : String(err)}. Fix the issue and try again.`
2156
+ };
1755
2157
  }
1756
- return {
1757
- behavior: "deny",
1758
- message: "File write tools are only available for plan files in PM mode."
1759
- };
1760
2158
  }
1761
- if (isPmActive && toolName === "Bash") {
1762
- const cmd = String(input.command ?? "");
1763
- if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
2159
+ if (toolName === "AskUserQuestion") {
2160
+ const questions = input.questions;
2161
+ const requestId = randomUUID();
2162
+ host.connection.emitStatus("waiting_for_input");
2163
+ host.connection.sendEvent({
2164
+ type: "tool_use",
2165
+ tool: "AskUserQuestion",
2166
+ input: JSON.stringify(input)
2167
+ });
2168
+ const answerPromise = host.connection.askUserQuestion(requestId, questions);
2169
+ const timeoutPromise = new Promise((resolve2) => {
2170
+ setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
2171
+ });
2172
+ const answers = await Promise.race([answerPromise, timeoutPromise]);
2173
+ host.connection.emitStatus("running");
2174
+ if (!answers) {
1764
2175
  return {
1765
2176
  behavior: "deny",
1766
- message: "Destructive operation blocked in active mode. Use safer alternatives."
2177
+ message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
1767
2178
  };
1768
2179
  }
2180
+ return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
2181
+ }
2182
+ switch (host.agentMode) {
2183
+ case "discovery":
2184
+ return handleDiscoveryToolAccess(toolName, input);
2185
+ case "building":
2186
+ return handleBuildingToolAccess(toolName, input);
2187
+ case "review":
2188
+ return handleReviewToolAccess(toolName, input, host.isParentTask);
2189
+ case "auto":
2190
+ return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
2191
+ default:
2192
+ return { behavior: "allow", updatedInput: input };
1769
2193
  }
1770
- if (toolName !== "AskUserQuestion") {
1771
- return { behavior: "allow", updatedInput: input };
1772
- }
1773
- const questions = input.questions;
1774
- const requestId = randomUUID();
1775
- host.connection.emitStatus("waiting_for_input");
1776
- host.connection.sendEvent({
1777
- type: "tool_use",
1778
- tool: "AskUserQuestion",
1779
- input: JSON.stringify(input)
1780
- });
1781
- const answerPromise = host.connection.askUserQuestion(requestId, questions);
1782
- const timeoutPromise = new Promise((resolve2) => {
1783
- setTimeout(() => resolve2(null), QUESTION_TIMEOUT_MS);
1784
- });
1785
- const answers = await Promise.race([answerPromise, timeoutPromise]);
1786
- host.connection.emitStatus("running");
1787
- if (!answers) {
1788
- return {
1789
- behavior: "deny",
1790
- message: "User did not respond to clarifying questions in time. Proceed with your best judgment."
1791
- };
1792
- }
1793
- return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
1794
2194
  };
1795
2195
  }
1796
2196
  function buildQueryOptions(host, context) {
1797
2197
  const settings = context.agentSettings ?? host.config.agentSettings ?? {};
1798
- const isPmActive = host.config.mode === "pm" && host.activeMode === "active";
1799
- const shouldSandbox = isPmActive && context.useSandbox !== false;
2198
+ const mode = host.agentMode;
2199
+ const isActiveMode = mode === "building" || mode === "review" || mode === "auto" && host.hasExitedPlanMode;
2200
+ const shouldSandbox = host.config.mode === "pm" && isActiveMode && context.useSandbox !== false;
1800
2201
  const systemPromptText = buildSystemPrompt(
1801
2202
  host.config.mode,
1802
2203
  context,
1803
2204
  host.config,
1804
2205
  host.setupLog,
1805
- isPmActive ? "active" : "planning"
2206
+ mode
1806
2207
  );
1807
2208
  const conveyorMcp = createConveyorMcpServer(host.connection, host.config, context);
1808
- const isPm = host.config.mode === "pm";
1809
- const pmDisallowedTools = isPm && !isPmActive ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
1810
- const disallowedTools = [...settings.disallowedTools ?? [], ...pmDisallowedTools];
2209
+ const isReadOnlyMode = mode === "discovery" || mode === "help" || mode === "auto" && !host.hasExitedPlanMode;
2210
+ const modeDisallowedTools = isReadOnlyMode ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
2211
+ const disallowedTools = [...settings.disallowedTools ?? [], ...modeDisallowedTools];
1811
2212
  const settingSources = settings.settingSources ?? ["user", "project"];
1812
2213
  const hooks = {
1813
2214
  PostToolUse: [
@@ -1920,10 +2321,10 @@ function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
1920
2321
  }
1921
2322
  async function runSdkQuery(host, context, followUpContent) {
1922
2323
  if (host.isStopped()) return;
1923
- const isPm = host.config.mode === "pm";
1924
- const isPackRunner = isPm && !!host.config.isAuto;
1925
- const isPmPlanning = isPm && host.activeMode === "planning" && !isPackRunner;
1926
- if (isPmPlanning) {
2324
+ const mode = host.agentMode;
2325
+ const isPmMode = host.config.mode === "pm";
2326
+ const isDiscoveryLike = mode === "discovery" || mode === "help";
2327
+ if (isDiscoveryLike) {
1927
2328
  host.snapshotPlanFiles();
1928
2329
  }
1929
2330
  const options = buildQueryOptions(host, context);
@@ -1933,14 +2334,14 @@ async function runSdkQuery(host, context, followUpContent) {
1933
2334
  const followUpImages = typeof followUpContent === "string" ? [] : followUpContent.filter(
1934
2335
  (b) => b.type === "image"
1935
2336
  );
1936
- const textPrompt = isPm ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto)}
2337
+ const textPrompt = isPmMode ? `${buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode)}
1937
2338
 
1938
2339
  ---
1939
2340
 
1940
2341
  The team says:
1941
2342
  ${followUpText}` : followUpText;
1942
2343
  let prompt;
1943
- if (isPm) {
2344
+ if (isPmMode) {
1944
2345
  prompt = buildMultimodalPrompt(textPrompt, context);
1945
2346
  if (followUpImages.length > 0 && Array.isArray(prompt)) {
1946
2347
  prompt.push(...followUpImages);
@@ -1955,10 +2356,10 @@ ${followUpText}` : followUpText;
1955
2356
  options: { ...options, resume }
1956
2357
  });
1957
2358
  await runWithRetry(agentQuery, context, host, options);
1958
- } else if (isPmPlanning) {
2359
+ } else if (isDiscoveryLike) {
1959
2360
  return;
1960
2361
  } else {
1961
- const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto);
2362
+ const initialPrompt = buildInitialPrompt(host.config.mode, context, host.config.isAuto, mode);
1962
2363
  const prompt = buildMultimodalPrompt(initialPrompt, context);
1963
2364
  const agentQuery = query({
1964
2365
  prompt: host.createInputStream(prompt),
@@ -1966,7 +2367,7 @@ ${followUpText}` : followUpText;
1966
2367
  });
1967
2368
  await runWithRetry(agentQuery, context, host, options);
1968
2369
  }
1969
- if (isPmPlanning) {
2370
+ if (isDiscoveryLike) {
1970
2371
  host.syncPlanFile();
1971
2372
  }
1972
2373
  }
@@ -1981,7 +2382,7 @@ async function runWithRetry(initialQuery, context, host, options) {
1981
2382
  );
1982
2383
  }
1983
2384
  const retryPrompt = buildMultimodalPrompt(
1984
- buildInitialPrompt(host.config.mode, context, host.config.isAuto),
2385
+ buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
1985
2386
  context,
1986
2387
  lastErrorWasImage
1987
2388
  );
@@ -1991,7 +2392,12 @@ async function runWithRetry(initialQuery, context, host, options) {
1991
2392
  });
1992
2393
  })();
1993
2394
  try {
1994
- const { retriable, resultSummary } = await processEvents(agentQuery, context, host);
2395
+ const { retriable, resultSummary, modeRestart } = await processEvents(
2396
+ agentQuery,
2397
+ context,
2398
+ host
2399
+ );
2400
+ if (modeRestart) return;
1995
2401
  if (!retriable || host.isStopped()) return;
1996
2402
  lastErrorWasImage = IMAGE_ERROR_PATTERN.test(resultSummary ?? "");
1997
2403
  } catch (error) {
@@ -2001,7 +2407,7 @@ async function runWithRetry(initialQuery, context, host, options) {
2001
2407
  context.claudeSessionId = null;
2002
2408
  host.connection.storeSessionId("");
2003
2409
  const freshPrompt = buildMultimodalPrompt(
2004
- buildInitialPrompt(host.config.mode, context, host.config.isAuto),
2410
+ buildInitialPrompt(host.config.mode, context, host.config.isAuto, host.agentMode),
2005
2411
  context
2006
2412
  );
2007
2413
  const freshQuery = query({
@@ -2187,7 +2593,13 @@ var AgentRunner = class _AgentRunner {
2187
2593
  planSync;
2188
2594
  costTracker = new CostTracker();
2189
2595
  worktreeActive = false;
2190
- activeMode = "planning";
2596
+ agentMode = null;
2597
+ hasExitedPlanMode = false;
2598
+ pendingModeRestart = false;
2599
+ sessionIds = /* @__PURE__ */ new Map();
2600
+ lastQueryModeRestart = false;
2601
+ deferredStartConfig = null;
2602
+ startCommandStarted = false;
2191
2603
  static MAX_SETUP_LOG_LINES = 50;
2192
2604
  constructor(config, callbacks) {
2193
2605
  this.config = config;
@@ -2198,6 +2610,18 @@ var AgentRunner = class _AgentRunner {
2198
2610
  get state() {
2199
2611
  return this._state;
2200
2612
  }
2613
+ /**
2614
+ * Resolve the effective AgentMode from explicit agentMode or legacy config flags.
2615
+ * This is the single axis of behavior for all execution path decisions.
2616
+ */
2617
+ get effectiveAgentMode() {
2618
+ if (this.agentMode) return this.agentMode;
2619
+ if (this.config.mode === "pm") {
2620
+ if (this.config.isAuto) return "auto";
2621
+ return "discovery";
2622
+ }
2623
+ return "building";
2624
+ }
2201
2625
  async setState(status) {
2202
2626
  this._state = status;
2203
2627
  this.connection.emitStatus(status);
@@ -2223,7 +2647,8 @@ var AgentRunner = class _AgentRunner {
2223
2647
  this.connection.onChatMessage(
2224
2648
  (message) => this.injectHumanMessage(message.content, message.files)
2225
2649
  );
2226
- this.connection.onModeChange((data) => this.handleModeChange(data.mode));
2650
+ this.connection.onModeChange((data) => this.handleModeChange(data.agentMode));
2651
+ this.connection.onRunStartCommand(() => this.runDeferredStartCommand());
2227
2652
  await this.setState("connected");
2228
2653
  this.connection.sendEvent({ type: "connected", taskId: this.config.taskId });
2229
2654
  this.startHeartbeat();
@@ -2262,6 +2687,9 @@ var AgentRunner = class _AgentRunner {
2262
2687
  return;
2263
2688
  }
2264
2689
  this.taskContext._runnerSessionId = randomUUID2();
2690
+ if (this.taskContext.agentMode) {
2691
+ this.agentMode = this.taskContext.agentMode;
2692
+ }
2265
2693
  this.logEffectiveSettings();
2266
2694
  if (process.env.CODESPACES === "true") {
2267
2695
  unshallowRepo(this.config.workspaceDir);
@@ -2298,18 +2726,30 @@ var AgentRunner = class _AgentRunner {
2298
2726
  } catch {
2299
2727
  }
2300
2728
  }
2301
- const isPm = this.config.mode === "pm";
2302
- const isPackRunner = isPm && !!this.config.isAuto && !!this.taskContext.isParentTask;
2303
- if (isPackRunner) {
2304
- await this.setState("running");
2305
- await this.runQuerySafe(this.taskContext);
2306
- if (!this.stopped) await this.setState("idle");
2307
- } else if (isPm) {
2308
- await this.setState("idle");
2309
- } else {
2310
- await this.setState("running");
2311
- await this.runQuerySafe(this.taskContext);
2312
- if (!this.stopped) await this.setState("idle");
2729
+ switch (this.effectiveAgentMode) {
2730
+ case "discovery":
2731
+ case "help":
2732
+ await this.setState("idle");
2733
+ break;
2734
+ case "building":
2735
+ await this.setState("running");
2736
+ await this.runQuerySafe(this.taskContext);
2737
+ if (!this.stopped) await this.setState("idle");
2738
+ break;
2739
+ case "review":
2740
+ if (this.taskContext.isParentTask) {
2741
+ await this.setState("running");
2742
+ await this.runQuerySafe(this.taskContext);
2743
+ if (!this.stopped) await this.setState("idle");
2744
+ } else {
2745
+ await this.setState("idle");
2746
+ }
2747
+ break;
2748
+ case "auto":
2749
+ await this.setState("running");
2750
+ await this.runQuerySafe(this.taskContext);
2751
+ if (!this.stopped) await this.setState("idle");
2752
+ break;
2313
2753
  }
2314
2754
  await this.runCoreLoop();
2315
2755
  this.stopHeartbeat();
@@ -2317,8 +2757,13 @@ var AgentRunner = class _AgentRunner {
2317
2757
  this.connection.disconnect();
2318
2758
  }
2319
2759
  async runQuerySafe(context, followUp) {
2760
+ this.lastQueryModeRestart = false;
2320
2761
  try {
2321
2762
  await runSdkQuery(this.asQueryHost(), context, followUp);
2763
+ if (this.pendingModeRestart) {
2764
+ this.lastQueryModeRestart = true;
2765
+ this.pendingModeRestart = false;
2766
+ }
2322
2767
  } catch (error) {
2323
2768
  const message = error instanceof Error ? error.message : "Unknown error";
2324
2769
  this.connection.sendEvent({ type: "error", message });
@@ -2329,6 +2774,11 @@ var AgentRunner = class _AgentRunner {
2329
2774
  async runCoreLoop() {
2330
2775
  if (!this.taskContext) return;
2331
2776
  while (!this.stopped) {
2777
+ if (this.lastQueryModeRestart) {
2778
+ this.lastQueryModeRestart = false;
2779
+ await this.handleAutoModeRestart();
2780
+ continue;
2781
+ }
2332
2782
  if (this._state === "idle") {
2333
2783
  const msg = await this.waitForUserContent();
2334
2784
  if (!msg) break;
@@ -2342,6 +2792,65 @@ var AgentRunner = class _AgentRunner {
2342
2792
  }
2343
2793
  }
2344
2794
  }
2795
+ /**
2796
+ * Handle auto mode transition after ExitPlanMode.
2797
+ * Saves the current session, switches model/mode, and restarts with a fresh query.
2798
+ */
2799
+ async handleAutoModeRestart() {
2800
+ if (!this.taskContext) return;
2801
+ const currentModel = this.taskContext.model;
2802
+ const currentSessionId = this.taskContext.claudeSessionId;
2803
+ if (currentSessionId && currentModel) {
2804
+ this.sessionIds.set(currentModel, currentSessionId);
2805
+ }
2806
+ const newMode = this.agentMode;
2807
+ const isParentTask = !!this.taskContext.isParentTask;
2808
+ const newModel = this.getModelForMode(newMode, isParentTask);
2809
+ const resumeSessionId = newModel ? this.sessionIds.get(newModel) : null;
2810
+ if (resumeSessionId) {
2811
+ this.taskContext.claudeSessionId = resumeSessionId;
2812
+ } else {
2813
+ this.taskContext.claudeSessionId = null;
2814
+ this.connection.storeSessionId("");
2815
+ }
2816
+ if (newModel) {
2817
+ this.taskContext.model = newModel;
2818
+ }
2819
+ this.taskContext.agentMode = newMode;
2820
+ this.connection.emitModeTransition({
2821
+ fromMode: "auto",
2822
+ toMode: newMode ?? "building"
2823
+ });
2824
+ this.connection.emitModeChanged(newMode);
2825
+ this.connection.postChatMessage(
2826
+ `Transitioning to **${newMode}** mode${newModel ? ` with ${newModel}` : ""}. Restarting session...`
2827
+ );
2828
+ try {
2829
+ const freshContext = await this.connection.fetchTaskContext();
2830
+ freshContext._runnerSessionId = this.taskContext._runnerSessionId;
2831
+ freshContext.claudeSessionId = this.taskContext.claudeSessionId;
2832
+ freshContext.agentMode = newMode;
2833
+ if (newModel) freshContext.model = newModel;
2834
+ this.taskContext = freshContext;
2835
+ } catch {
2836
+ }
2837
+ await this.setState("running");
2838
+ await this.runQuerySafe(this.taskContext);
2839
+ if (!this.stopped && this._state !== "error") {
2840
+ await this.setState("idle");
2841
+ }
2842
+ }
2843
+ /**
2844
+ * Get the appropriate model for a given mode.
2845
+ * Building uses the builder model (Sonnet), Discovery/Review use PM model (Opus).
2846
+ */
2847
+ getModelForMode(mode, isParentTask) {
2848
+ if (!this.taskContext) return null;
2849
+ if (mode === "building" && !isParentTask) {
2850
+ return this.taskContext.builderModel ?? this.taskContext.model;
2851
+ }
2852
+ return this.taskContext.pmModel ?? this.taskContext.model;
2853
+ }
2345
2854
  async runSetupSafe() {
2346
2855
  await this.setState("setup");
2347
2856
  const ports = await loadForwardPorts(this.config.workspaceDir);
@@ -2363,7 +2872,8 @@ var AgentRunner = class _AgentRunner {
2363
2872
  await this.executeSetupConfig(config);
2364
2873
  const setupEvent = {
2365
2874
  type: "setup_complete",
2366
- previewPort: config.previewPort ?? void 0
2875
+ previewPort: config.previewPort ?? void 0,
2876
+ startCommandDeferred: this.deferredStartConfig !== null ? true : void 0
2367
2877
  };
2368
2878
  this.connection.sendEvent(setupEvent);
2369
2879
  await this.callbacks.onEvent(setupEvent);
@@ -2415,12 +2925,33 @@ The agent cannot start until this is resolved.`
2415
2925
  this.pushSetupLog("(exit 0)");
2416
2926
  }
2417
2927
  if (config.startCommand) {
2418
- this.pushSetupLog(`$ ${config.startCommand} & (background)`);
2419
- runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
2420
- this.connection.sendEvent({ type: "start_command_output", stream, data });
2421
- });
2928
+ if (this.effectiveAgentMode === "auto") {
2929
+ this.deferredStartConfig = config;
2930
+ this.pushSetupLog(`[conveyor] startCommand deferred (auto mode)`);
2931
+ } else {
2932
+ this.pushSetupLog(`$ ${config.startCommand} & (background)`);
2933
+ runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
2934
+ this.connection.sendEvent({ type: "start_command_output", stream, data });
2935
+ });
2936
+ }
2422
2937
  }
2423
2938
  }
2939
+ runDeferredStartCommand() {
2940
+ if (!this.deferredStartConfig?.startCommand || this.startCommandStarted) return;
2941
+ this.startCommandStarted = true;
2942
+ const config = this.deferredStartConfig;
2943
+ this.deferredStartConfig = null;
2944
+ this.pushSetupLog(`$ ${config.startCommand} & (background, deferred)`);
2945
+ this.connection.sendEvent({ type: "start_command_started" });
2946
+ runStartCommand(config.startCommand, this.config.workspaceDir, (stream, data) => {
2947
+ this.connection.sendEvent({ type: "start_command_output", stream, data });
2948
+ });
2949
+ const setupEvent = {
2950
+ type: "setup_complete",
2951
+ previewPort: config.previewPort ?? void 0
2952
+ };
2953
+ this.connection.sendEvent(setupEvent);
2954
+ }
2424
2955
  injectHumanMessage(content, files) {
2425
2956
  let messageContent;
2426
2957
  if (files?.length) {
@@ -2533,28 +3064,59 @@ ${f.content}
2533
3064
  }
2534
3065
  }
2535
3066
  asQueryHost() {
2536
- const getActiveMode = () => this.activeMode;
3067
+ const getEffectiveAgentMode = () => this.effectiveAgentMode;
3068
+ const getHasExitedPlanMode = () => this.hasExitedPlanMode;
3069
+ const setHasExitedPlanMode = (val) => {
3070
+ this.hasExitedPlanMode = val;
3071
+ };
3072
+ const getPendingModeRestart = () => this.pendingModeRestart;
3073
+ const setPendingModeRestart = (val) => {
3074
+ this.pendingModeRestart = val;
3075
+ };
3076
+ const getIsParentTask = () => !!this.taskContext?.isParentTask;
2537
3077
  return {
2538
3078
  config: this.config,
2539
3079
  connection: this.connection,
2540
3080
  callbacks: this.callbacks,
2541
3081
  setupLog: this.setupLog,
2542
3082
  costTracker: this.costTracker,
2543
- get activeMode() {
2544
- return getActiveMode();
3083
+ get agentMode() {
3084
+ return getEffectiveAgentMode();
2545
3085
  },
3086
+ get isParentTask() {
3087
+ return getIsParentTask();
3088
+ },
3089
+ get hasExitedPlanMode() {
3090
+ return getHasExitedPlanMode();
3091
+ },
3092
+ set hasExitedPlanMode(val) {
3093
+ setHasExitedPlanMode(val);
3094
+ },
3095
+ get pendingModeRestart() {
3096
+ return getPendingModeRestart();
3097
+ },
3098
+ set pendingModeRestart(val) {
3099
+ setPendingModeRestart(val);
3100
+ },
3101
+ sessionIds: this.sessionIds,
2546
3102
  isStopped: () => this.stopped,
2547
3103
  createInputStream: (prompt) => this.createInputStream(prompt),
2548
3104
  snapshotPlanFiles: () => this.planSync.snapshotPlanFiles(),
2549
- syncPlanFile: () => this.planSync.syncPlanFile()
3105
+ syncPlanFile: () => this.planSync.syncPlanFile(),
3106
+ onModeTransition: (newMode) => {
3107
+ this.agentMode = newMode;
3108
+ }
2550
3109
  };
2551
3110
  }
2552
- handleModeChange(mode) {
3111
+ handleModeChange(newAgentMode) {
2553
3112
  if (this.config.mode !== "pm") return;
2554
- this.activeMode = mode;
2555
- this.connection.emitModeChanged(mode);
3113
+ if (newAgentMode) {
3114
+ this.agentMode = newAgentMode;
3115
+ }
3116
+ const effectiveMode = this.effectiveAgentMode;
3117
+ this.connection.emitModeChanged(effectiveMode);
2556
3118
  this.connection.postChatMessage(
2557
- `Mode switched to **${mode}**${mode === "active" ? " \u2014 I now have direct coding access." : " \u2014 back to planning only."}`
3119
+ `Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
2558
3120
  );
2559
3121
  }
2560
3122
  stop() {
@@ -2811,6 +3373,7 @@ var ProjectRunner = class {
2811
3373
  CONVEYOR_MODE: mode,
2812
3374
  CONVEYOR_WORKSPACE: workDir,
2813
3375
  CONVEYOR_USE_WORKTREE: "false",
3376
+ CONVEYOR_AGENT_MODE: isAuto ? "auto" : "",
2814
3377
  CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
2815
3378
  CONVEYOR_USE_SANDBOX: useSandbox === false ? "false" : "true"
2816
3379
  },
@@ -2990,4 +3553,4 @@ export {
2990
3553
  ProjectRunner,
2991
3554
  FileCache
2992
3555
  };
2993
- //# sourceMappingURL=chunk-6Z262BNH.js.map
3556
+ //# sourceMappingURL=chunk-Y4TAVPZV.js.map