@rallycry/conveyor-agent 7.0.4 → 7.0.5

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.
@@ -593,6 +593,34 @@ var Lifecycle = class {
593
593
 
594
594
  // src/runner/git-utils.ts
595
595
  import { execSync } from "child_process";
596
+ function syncWithBaseBranch(cwd, baseBranch) {
597
+ if (!baseBranch) return true;
598
+ try {
599
+ execSync(`git fetch origin ${baseBranch}`, { cwd, stdio: "ignore", timeout: 6e4 });
600
+ } catch {
601
+ process.stderr.write(
602
+ `[conveyor-agent] Warning: git fetch origin ${baseBranch} failed, continuing with current base
603
+ `
604
+ );
605
+ return false;
606
+ }
607
+ try {
608
+ execSync(`git merge origin/${baseBranch} --no-edit`, { cwd, stdio: "ignore", timeout: 3e4 });
609
+ } catch {
610
+ process.stderr.write(
611
+ `[conveyor-agent] Warning: merge origin/${baseBranch} failed, aborting merge and continuing
612
+ `
613
+ );
614
+ try {
615
+ execSync("git merge --abort", { cwd, stdio: "ignore" });
616
+ } catch {
617
+ }
618
+ return false;
619
+ }
620
+ process.stderr.write(`[conveyor-agent] Synced with latest origin/${baseBranch}
621
+ `);
622
+ return true;
623
+ }
596
624
  function hasUncommittedChanges(cwd) {
597
625
  const status = execSync("git status --porcelain", {
598
626
  cwd,
@@ -833,7 +861,6 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
833
861
  `- "Open" \u2014 Ready to execute (if dependencies are met). Use start_child_cloud_build to fire it.`,
834
862
  `- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
835
863
  `- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
836
- `- "Hold" \u2014 PR exists but is on hold for team review. Do not merge \u2014 skip and move on.`,
837
864
  `- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
838
865
  `- "Complete" \u2014 Fully done. Move on.`,
839
866
  ``,
@@ -850,7 +877,6 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
850
877
  ` - "InProgress": A Task Runner is actively working. Do nothing \u2014 wait.`,
851
878
  ` - "Open" + allDependenciesMet=true: Ready to fire. Use start_child_cloud_build.`,
852
879
  ` - "Open" + allDependenciesMet=false: Blocked \u2014 skip for now. Will be unblocked when deps complete.`,
853
- ` - "Hold": On hold \u2014 team must review before merge. Skip.`,
854
880
  ` - "ReviewDev" / "Complete": Already done. Skip.`,
855
881
  ` - "Planning": Not ready. If blocking progress, notify team.`,
856
882
  ``,
@@ -860,7 +886,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
860
886
  ``,
861
887
  `5. After firing all ready tasks: report which tasks you fired to chat, then state you are going idle.`,
862
888
  ``,
863
- `6. When ALL children are in "ReviewDev", "Complete", or "Hold" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
889
+ `6. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
864
890
  ``,
865
891
  `## Important Rules`,
866
892
  `- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel.`,
@@ -1158,6 +1184,15 @@ function buildDiscoveryPrompt(context) {
1158
1184
  `- Goal: collaborate with the user to create a clear plan`,
1159
1185
  `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
1160
1186
  ``,
1187
+ `### Planning Checklist (complete ALL before calling ExitPlanMode)`,
1188
+ `Your PRIMARY goal is to create a thorough plan. Complete these steps in order:`,
1189
+ `1. Read the task description and chat history \u2014 respond to what's been discussed`,
1190
+ `2. Explore the codebase to understand relevant files and patterns`,
1191
+ `3. Save a detailed plan via \`update_task\``,
1192
+ `4. Set story points, tags, and title via \`update_task_properties\``,
1193
+ `5. Discuss the plan with the team if they're engaged, incorporate feedback`,
1194
+ `6. THEN call ExitPlanMode \u2014 it is the LAST step, not the first`,
1195
+ ``,
1161
1196
  `### Self-Identification Tools`,
1162
1197
  `Use these MCP tools to set your own task properties:`,
1163
1198
  `- \`update_task\` \u2014 save your plan and description`,
@@ -1169,9 +1204,16 @@ function buildDiscoveryPrompt(context) {
1169
1204
  `- Add matching tags using \`update_task_properties\` \u2014 this links relevant documentation and rules that help you plan more effectively`,
1170
1205
  `- Tags accelerate discovery by surfacing domain-specific context automatically`,
1171
1206
  ``,
1172
- `### Self-Update vs Subtasks`,
1173
- `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1174
- `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
1207
+ ...context?.isParentTask ? [
1208
+ `### Parent Task Coordination`,
1209
+ `You are a parent task with child tasks. Focus on breaking work into child tasks with detailed plans, not planning implementation for yourself.`,
1210
+ `- Use \`list_subtasks\` to review existing children. Create or update child tasks using \`create_subtask\` / \`update_subtask\`.`,
1211
+ `- Each child task should be a self-contained unit of work with a clear plan.`
1212
+ ] : [
1213
+ `### Self-Update vs Subtasks`,
1214
+ `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1215
+ `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`
1216
+ ],
1175
1217
  ``,
1176
1218
  `### Subtask Plan Requirements`,
1177
1219
  `When creating subtasks, each MUST include a detailed \`plan\` field:`,
@@ -1181,11 +1223,12 @@ function buildDiscoveryPrompt(context) {
1181
1223
  `- Include testing requirements and acceptance criteria`,
1182
1224
  `- Set \`storyPointValue\` based on estimated complexity`,
1183
1225
  ``,
1184
- `### Finishing Planning`,
1185
- `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1226
+ `### Completing Planning`,
1227
+ `Once ALL checklist items above are done, call the **ExitPlanMode** tool.`,
1186
1228
  `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
1187
1229
  `- ExitPlanMode validates these properties and marks planning as complete`,
1188
- `- It does NOT start building \u2014 the team controls when to switch to Build mode`
1230
+ `- It does NOT start building \u2014 the team controls when to switch to Build mode`,
1231
+ `- Do NOT call ExitPlanMode until you have thoroughly explored the codebase and saved a detailed plan`
1189
1232
  ];
1190
1233
  if (context) parts.push(...buildPropertyInstructions(context));
1191
1234
  return parts.join("\n");
@@ -1220,6 +1263,14 @@ function buildAutoPrompt(context) {
1220
1263
  `- Include testing requirements and acceptance criteria`,
1221
1264
  `- Set \`storyPointValue\` based on estimated complexity`,
1222
1265
  ``,
1266
+ ...context?.isParentTask ? [
1267
+ ``,
1268
+ `### Parent Task Guidance`,
1269
+ `You are a parent task. Your plan should define child tasks with detailed plans, not direct implementation steps.`,
1270
+ `After ExitPlanMode, you'll transition to Review mode to coordinate child task execution.`,
1271
+ `Child task status lifecycle: Open \u2192 InProgress \u2192 ReviewPR \u2192 ReviewDev \u2192 Complete.`
1272
+ ] : [],
1273
+ ``,
1223
1274
  `### Autonomous Guidelines:`,
1224
1275
  `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1225
1276
  `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
@@ -1239,8 +1290,14 @@ function buildModePrompt(agentMode, context) {
1239
1290
  `You are in Building mode \u2014 executing the plan.`,
1240
1291
  `- You have full coding access (read, write, edit, bash, git)`,
1241
1292
  `- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
1242
- `- If this is a leaf task (no children): execute the plan directly`,
1243
- `- Goal: implement the plan, run tests, open a PR when done`
1293
+ ...context?.isParentTask ? [
1294
+ `- You are a parent task. Use \`list_subtasks\`, \`start_child_cloud_build\`, and subtask management tools to coordinate children.`,
1295
+ `- Do NOT implement code directly \u2014 fire child builds and review their work.`,
1296
+ `- Goal: coordinate child task execution and ensure all children complete successfully`
1297
+ ] : [
1298
+ `- If this is a leaf task (no children): execute the plan directly`,
1299
+ `- Goal: implement the plan, run tests, open a PR when done`
1300
+ ]
1244
1301
  ];
1245
1302
  if (process.env.CLAUDESPACE_NAME) {
1246
1303
  parts.push(
@@ -1257,15 +1314,7 @@ function buildModePrompt(agentMode, context) {
1257
1314
  return parts.join("\n");
1258
1315
  }
1259
1316
  case "review":
1260
- return [
1261
- `
1262
- ## Mode: Review`,
1263
- `You are in Review mode \u2014 reviewing and coordinating.`,
1264
- `- You have read-only access plus light edit capability (can suggest fixes, run tests, check linting)`,
1265
- `- For parent tasks: you can manage children, review child PRs, fire next child builds`,
1266
- `- You have Pack Runner coordination tools (list_subtasks, fire builds, approve PRs)`,
1267
- `- Goal: ensure quality, provide feedback, coordinate progression`
1268
- ].join("\n");
1317
+ return buildReviewPrompt(context);
1269
1318
  case "auto":
1270
1319
  return buildAutoPrompt(context);
1271
1320
  case "code-review":
@@ -1274,6 +1323,64 @@ function buildModePrompt(agentMode, context) {
1274
1323
  return null;
1275
1324
  }
1276
1325
  }
1326
+ function buildReviewPrompt(context) {
1327
+ const parts = [
1328
+ `
1329
+ ## Mode: Review`,
1330
+ `You are in Review mode \u2014 reviewing and coordinating.`,
1331
+ `- You have read-only access plus light edit capability (can suggest fixes, run tests, check linting)`,
1332
+ ``
1333
+ ];
1334
+ if (context?.isParentTask) {
1335
+ parts.push(
1336
+ `### Parent Task Review`,
1337
+ `You are reviewing and coordinating child tasks.`,
1338
+ `- Use \`list_subtasks\` to see current child task state and progress.`,
1339
+ `- For children in ReviewPR status: review their code quality and merge with \`approve_and_merge_pr\`.`,
1340
+ `- For children with failing CI: check with \`get_task_cli(childTaskId)\` and escalate if stuck.`,
1341
+ `- Fire next child builds with \`start_child_cloud_build\` when ready.`,
1342
+ `- Create follow-up tasks for issues discovered during review.`,
1343
+ ``,
1344
+ `### Coordination Workflow`,
1345
+ `1. Check child task statuses with \`list_subtasks\``,
1346
+ `2. Review completed children \u2014 check PRs, run tests if needed`,
1347
+ `3. Approve and merge passing PRs`,
1348
+ `4. Fire builds for children that are ready`,
1349
+ `5. Create follow-up tasks for anything out of scope`
1350
+ );
1351
+ } else {
1352
+ parts.push(
1353
+ `### Leaf Task Review`,
1354
+ `You are reviewing your own work before completion.`,
1355
+ `- Run tests and check linting to verify the PR is ready.`,
1356
+ `- If the PR is already open, review the diff for correctness.`,
1357
+ `- You can get hands dirty \u2014 if a fix is small, make it directly.`,
1358
+ `- If follow-up work is needed, use \`create_follow_up_task\`.`
1359
+ );
1360
+ }
1361
+ parts.push(
1362
+ ``,
1363
+ `### General Review Guidelines`,
1364
+ `- For larger issues, create a follow-up task rather than fixing directly.`,
1365
+ `- Focus on correctness, pattern consistency, and test coverage.`,
1366
+ `- Be concise in feedback \u2014 actionable specifics over general observations.`,
1367
+ `- Goal: ensure quality, provide feedback, coordinate progression.`
1368
+ );
1369
+ if (process.env.CLAUDESPACE_NAME) {
1370
+ parts.push(
1371
+ ``,
1372
+ `### Resource Management`,
1373
+ `Your pod starts with minimal resources (0.25 CPU / 1 Gi). You MUST call \`scale_up_resources\``,
1374
+ `BEFORE running any of these operations \u2014 they WILL fail or OOM at baseline resources:`,
1375
+ `- **light** (1 CPU / 4 Gi) \u2014 bun/npm/yarn install, pip install, basic dev servers, light builds`,
1376
+ `- **standard** (2 CPU / 8 Gi) \u2014 full dev servers, test suites, typecheck, lint`,
1377
+ `- **heavy** (4 CPU / 16 Gi) \u2014 E2E/browser automation, large parallel builds`,
1378
+ `Scaling is one-way (up only) and capped by project limits.`,
1379
+ `CRITICAL: Always scale to at least "light" before running any package install command.`
1380
+ );
1381
+ }
1382
+ return parts.join("\n");
1383
+ }
1277
1384
  function buildCodeReviewPrompt() {
1278
1385
  return [
1279
1386
  `
@@ -1504,6 +1611,7 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
1504
1611
  }
1505
1612
 
1506
1613
  // src/execution/prompt-builder.ts
1614
+ var CHAT_HISTORY_LIMIT = 40;
1507
1615
  function formatFileSize(bytes) {
1508
1616
  if (bytes === void 0) return "";
1509
1617
  if (bytes < 1024) return `${bytes}B`;
@@ -1649,7 +1757,7 @@ function formatTaskFile(file) {
1649
1757
  return [];
1650
1758
  }
1651
1759
  function formatChatHistory(chatHistory) {
1652
- const relevant = chatHistory.slice(-20);
1760
+ const relevant = chatHistory.slice(-CHAT_HISTORY_LIMIT);
1653
1761
  const parts = [`
1654
1762
  ## Recent Chat Context`];
1655
1763
  for (const msg of relevant) {
@@ -1676,6 +1784,12 @@ async function resolveTaskTagContext(context) {
1676
1784
  async function buildTaskBody(context) {
1677
1785
  const parts = [];
1678
1786
  parts.push(`# Task: ${context.title}`);
1787
+ if (context.projectName) {
1788
+ const descLine = context.projectDescription ? `
1789
+ ${context.projectDescription}` : "";
1790
+ parts.push(`
1791
+ ## Project: ${context.projectName}${descLine}`);
1792
+ }
1679
1793
  if (context.description) {
1680
1794
  parts.push(`
1681
1795
  ## Description
@@ -1755,7 +1869,8 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1755
1869
  if (isPm && context.isParentTask) {
1756
1870
  return [
1757
1871
  `You are the project manager for this task and its subtasks.`,
1758
- `Use list_subtasks to review the current state of child tasks.`,
1872
+ `Review existing subtasks via \`list_subtasks\` and the chat history before taking action.`,
1873
+ `Read the task description and chat history carefully \u2014 the team may have already provided context and requirements. Respond to what's been discussed rather than starting from scratch.`,
1759
1874
  `The task details are provided above. Wait for the team to provide instructions before taking action.`,
1760
1875
  `When you finish planning, save the plan with update_task and end your turn. Your reply will be visible to the team in chat.`
1761
1876
  ];
@@ -1763,6 +1878,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1763
1878
  if (isPm) {
1764
1879
  return [
1765
1880
  `You are the project manager for this task.`,
1881
+ `Read the task description and chat history carefully \u2014 the team may have already provided context and requirements. Respond to what's been discussed rather than starting from scratch.`,
1766
1882
  `The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
1767
1883
  `When you finish planning, save the plan with update_task and end your turn. Your reply summarizing the plan will be visible in chat. A separate task agent will execute the plan after review.`
1768
1884
  ];
@@ -2016,7 +2132,11 @@ function buildGetTaskCliTool(connection) {
2016
2132
  taskId: task_id,
2017
2133
  source
2018
2134
  });
2019
- const formatted = result.map((entry) => `[${entry.time}] [${entry.event.type}] ${formatCliEvent(entry.event)}`).join("\n");
2135
+ const formatted = result.map((entry) => {
2136
+ const type = entry.type;
2137
+ const time = entry.timestamp;
2138
+ return `[${time}] [${type}] ${formatCliEvent(entry)}`;
2139
+ }).join("\n");
2020
2140
  return textResult(formatted || "No CLI logs found.");
2021
2141
  } catch (error) {
2022
2142
  return textResult(
@@ -2264,7 +2384,7 @@ function buildForceUpdateTaskStatusTool(connection) {
2264
2384
  "force_update_task_status",
2265
2385
  "EMERGENCY ONLY: Force-override a task's Kanban status. Status transitions happen automatically (building sets InProgress, PR creation sets ReviewPR, merge sets ReviewDev). Only use this if an automatic transition failed or a task is stuck in the wrong status. Omit task_id to update the current task, or provide a child task ID.",
2266
2386
  {
2267
- status: z.enum(["InProgress", "ReviewPR", "Hold", "ReviewDev", "Complete"]).describe("The new status for the task"),
2387
+ status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2268
2388
  task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
2269
2389
  },
2270
2390
  async ({ status, task_id }) => {
@@ -4222,6 +4342,7 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
4222
4342
  }
4223
4343
 
4224
4344
  // src/execution/event-handlers.ts
4345
+ var logger = createServiceLogger("event-handlers");
4225
4346
  function safeVoid(promise, context) {
4226
4347
  if (promise && typeof promise.catch === "function") {
4227
4348
  promise.catch((err) => {
@@ -4399,6 +4520,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
4399
4520
  }
4400
4521
  function handleRateLimitEvent(event, host) {
4401
4522
  const { rate_limit_info } = event;
4523
+ logger.info("Rate limit event received", { rate_limit_info });
4402
4524
  const status = rate_limit_info.status;
4403
4525
  const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
4404
4526
  if (utilization !== void 0 && rate_limit_info.rateLimitType) {
@@ -4616,6 +4738,16 @@ async function processEvents(events, context, host) {
4616
4738
  };
4617
4739
  }
4618
4740
 
4741
+ // src/execution/task-property-utils.ts
4742
+ function collectMissingProps(taskProps) {
4743
+ const missing = [];
4744
+ if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
4745
+ if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
4746
+ if (!taskProps.title || taskProps.title === "Untitled")
4747
+ missing.push("title (use update_task_properties)");
4748
+ return missing;
4749
+ }
4750
+
4619
4751
  // src/execution/tool-access.ts
4620
4752
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
4621
4753
  var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
@@ -4701,18 +4833,11 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
4701
4833
  }
4702
4834
  return { behavior: "allow", updatedInput: input };
4703
4835
  }
4704
- function collectMissingProps(taskProps) {
4705
- const missing = [];
4706
- if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
4707
- if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
4708
- if (!taskProps.title || taskProps.title === "Untitled")
4709
- missing.push("title (use update_task_properties)");
4710
- return missing;
4711
- }
4712
4836
  async function handleExitPlanMode(host, input) {
4713
4837
  if (host.hasExitedPlanMode) {
4714
4838
  return { behavior: "allow", updatedInput: input };
4715
4839
  }
4840
+ host.exitPlanAttempts++;
4716
4841
  try {
4717
4842
  host.syncPlanFile();
4718
4843
  const taskProps = await host.connection.getTaskProperties();
@@ -4735,15 +4860,20 @@ async function handleExitPlanMode(host, input) {
4735
4860
  }
4736
4861
  }
4737
4862
  if (missingProps.length > 0) {
4738
- return {
4739
- behavior: "deny",
4740
- message: [
4741
- "Cannot exit plan mode yet. Required task properties are missing:",
4742
- ...missingProps.map((p) => `- ${p}`),
4743
- "",
4744
- "Fill these in using MCP tools, then try ExitPlanMode again."
4745
- ].join("\n")
4746
- };
4863
+ if (host.exitPlanAttempts <= 1) {
4864
+ return {
4865
+ behavior: "deny",
4866
+ message: [
4867
+ "Cannot exit plan mode yet. Required task properties are missing:",
4868
+ ...missingProps.map((p) => `- ${p}`),
4869
+ "",
4870
+ "Fill these in using MCP tools, then try ExitPlanMode again."
4871
+ ].join("\n")
4872
+ };
4873
+ }
4874
+ host.connection.postChatMessage(
4875
+ `\u26A0\uFE0F ExitPlanMode allowed with missing properties: ${missingProps.join(", ")}. Consider backfilling these later.`
4876
+ );
4747
4877
  }
4748
4878
  if (host.agentMode === "discovery") {
4749
4879
  host.hasExitedPlanMode = true;
@@ -4848,7 +4978,7 @@ function buildCanUseTool(host) {
4848
4978
  }
4849
4979
 
4850
4980
  // src/execution/query-executor.ts
4851
- var logger = createServiceLogger("QueryExecutor");
4981
+ var logger2 = createServiceLogger("QueryExecutor");
4852
4982
  var API_ERROR_PATTERN3 = /API Error: [45]\d\d/;
4853
4983
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
4854
4984
  var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
@@ -4868,6 +4998,18 @@ function buildHooks(host) {
4868
4998
  isError: false
4869
4999
  });
4870
5000
  host.pendingToolOutputs.push(output);
5001
+ if (input.tool_name === "mcp__conveyor__create_pull_request") {
5002
+ try {
5003
+ const props = await host.connection.getTaskProperties();
5004
+ const missing = collectMissingProps(props);
5005
+ if (missing.length > 0) {
5006
+ host.connection.postChatMessage(
5007
+ `PR created! Please backfill missing task properties: ${missing.join(", ")}`
5008
+ );
5009
+ }
5010
+ } catch {
5011
+ }
5012
+ }
4871
5013
  }
4872
5014
  return await Promise.resolve({ continue: true });
4873
5015
  }
@@ -4929,7 +5071,7 @@ function buildQueryOptions(host, context) {
4929
5071
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
4930
5072
  enableFileCheckpointing: settings.enableFileCheckpointing,
4931
5073
  stderr: (data) => {
4932
- logger.warn("Claude Code stderr", { data: data.trimEnd() });
5074
+ logger2.warn("Claude Code stderr", { data: data.trimEnd() });
4933
5075
  }
4934
5076
  };
4935
5077
  }
@@ -5239,7 +5381,7 @@ var CostTracker = class {
5239
5381
  };
5240
5382
 
5241
5383
  // src/runner/query-bridge.ts
5242
- var logger2 = createServiceLogger("QueryBridge");
5384
+ var logger3 = createServiceLogger("QueryBridge");
5243
5385
  var QueryBridge = class {
5244
5386
  constructor(connection, mode, runnerConfig, callbacks, workspaceDir) {
5245
5387
  this.connection = connection;
@@ -5290,8 +5432,13 @@ var QueryBridge = class {
5290
5432
  await runSdkQuery(host, context, followUpContent);
5291
5433
  } catch (err) {
5292
5434
  const msg = err instanceof Error ? err.message : String(err);
5293
- logger2.error("Query execution failed", { error: msg });
5294
- this.connection.sendEvent({ type: "error", message: msg });
5435
+ const isAbort = this._stopped || /abort/i.test(msg);
5436
+ if (isAbort) {
5437
+ logger3.info("Query stopped by user", { error: msg });
5438
+ } else {
5439
+ logger3.error("Query execution failed", { error: msg });
5440
+ this.connection.sendEvent({ type: "error", message: msg });
5441
+ }
5295
5442
  } finally {
5296
5443
  this.mode.pendingModeRestart = false;
5297
5444
  }
@@ -5321,6 +5468,7 @@ var QueryBridge = class {
5321
5468
  set hasExitedPlanMode(val) {
5322
5469
  bridge.mode.hasExitedPlanMode = val;
5323
5470
  },
5471
+ exitPlanAttempts: 0,
5324
5472
  get pendingModeRestart() {
5325
5473
  return bridge.mode.pendingModeRestart;
5326
5474
  },
@@ -5381,9 +5529,11 @@ var SessionRunner = class _SessionRunner {
5381
5529
  this.lifecycle = new Lifecycle(lifecycleConfig, {
5382
5530
  onHeartbeat: () => this.connection.sendHeartbeat(),
5383
5531
  onIdleTimeout: () => {
5532
+ process.stderr.write("[conveyor-agent] Idle timeout reached, entering sleep\n");
5384
5533
  this.connection.emitStatus("sleeping");
5385
5534
  },
5386
5535
  onSleep: () => {
5536
+ process.stderr.write("[conveyor-agent] Sleep mode active\n");
5387
5537
  this.connection.postChatMessage("Agent sleeping \u2014 send a message or click Resume to wake.");
5388
5538
  },
5389
5539
  onSleepGraceExpired: () => {
@@ -5395,6 +5545,7 @@ var SessionRunner = class _SessionRunner {
5395
5545
  }
5396
5546
  },
5397
5547
  onWake: () => {
5548
+ process.stderr.write("[conveyor-agent] Woken from sleep\n");
5398
5549
  this.lifecycle.cancelSleepShutdown();
5399
5550
  }
5400
5551
  });
@@ -5409,7 +5560,7 @@ var SessionRunner = class _SessionRunner {
5409
5560
  return this.stopped;
5410
5561
  }
5411
5562
  // ── Main lifecycle ─────────────────────────────────────────────────
5412
- // oxlint-disable-next-line max-lines-per-function -- lifecycle orchestration is inherently sequential
5563
+ // oxlint-disable-next-line max-lines-per-function, complexity -- lifecycle orchestration is inherently sequential
5413
5564
  async start() {
5414
5565
  await this.setState("connecting");
5415
5566
  await this.connection.connect();
@@ -5447,6 +5598,9 @@ var SessionRunner = class _SessionRunner {
5447
5598
  await this.shutdown("error");
5448
5599
  return;
5449
5600
  }
5601
+ if (this.fullContext?.baseBranch) {
5602
+ syncWithBaseBranch(this.config.workspaceDir, this.fullContext.baseBranch);
5603
+ }
5450
5604
  this.mode.resolveInitialMode(this.taskContext);
5451
5605
  this.queryBridge = this.createQueryBridge();
5452
5606
  this.logInitialization();
@@ -5496,8 +5650,11 @@ var SessionRunner = class _SessionRunner {
5496
5650
  content: msg.content,
5497
5651
  userId: msg.userId
5498
5652
  });
5499
- if (this.fullContext && this.queryBridge) {
5500
- await this.queryBridge.execute(this.fullContext, msg.content);
5653
+ await this.executeQuery(msg.content);
5654
+ if (this.stopped) break;
5655
+ if (this.interrupted) {
5656
+ this.interrupted = false;
5657
+ continue;
5501
5658
  }
5502
5659
  if (!this.stopped && this.pendingMessages.length === 0) {
5503
5660
  await this.maybeSendPRNudge();
@@ -5518,7 +5675,7 @@ var SessionRunner = class _SessionRunner {
5518
5675
  if (effectiveMode === "code-review") {
5519
5676
  await this.setState("running");
5520
5677
  await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode });
5521
- await this.queryBridge?.execute(this.fullContext);
5678
+ await this.executeQuery();
5522
5679
  this.stopped = true;
5523
5680
  return;
5524
5681
  }
@@ -5526,7 +5683,7 @@ var SessionRunner = class _SessionRunner {
5526
5683
  if (shouldRun) {
5527
5684
  await this.setState("running");
5528
5685
  await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode });
5529
- await this.queryBridge?.execute(this.fullContext);
5686
+ await this.executeQuery();
5530
5687
  if (!this.stopped) await this.setState("idle");
5531
5688
  } else {
5532
5689
  await this.setState("idle");
@@ -5555,6 +5712,20 @@ var SessionRunner = class _SessionRunner {
5555
5712
  }
5556
5713
  }
5557
5714
  }
5715
+ // ── Query execution with abort handling ────────────────────────────
5716
+ /** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
5717
+ async executeQuery(followUpContent) {
5718
+ if (!this.fullContext || !this.queryBridge) return;
5719
+ try {
5720
+ await this.queryBridge.execute(this.fullContext, followUpContent);
5721
+ } catch (err) {
5722
+ if (this.interrupted || this.stopped) {
5723
+ process.stderr.write("[conveyor-agent] Query aborted by stop/softStop signal\n");
5724
+ return;
5725
+ }
5726
+ throw err;
5727
+ }
5728
+ }
5558
5729
  // ── Stop / soft-stop ───────────────────────────────────────────────
5559
5730
  stop() {
5560
5731
  this.stopped = true;
@@ -5624,9 +5795,8 @@ var SessionRunner = class _SessionRunner {
5624
5795
  this.connection.postChatMessage(chatMsg);
5625
5796
  await this.setState("running");
5626
5797
  await this.callbacks.onEvent({ type: "pr_nudge", prompt });
5627
- if (this.fullContext && this.queryBridge) {
5628
- await this.queryBridge.execute(this.fullContext, prompt);
5629
- }
5798
+ await this.executeQuery(prompt);
5799
+ if (this.interrupted || this.stopped) return;
5630
5800
  await this.refreshTaskContext();
5631
5801
  }
5632
5802
  }
@@ -5663,6 +5833,8 @@ var SessionRunner = class _SessionRunner {
5663
5833
  model: ctx.model,
5664
5834
  githubBranch: ctx.githubBranch ?? "",
5665
5835
  baseBranch: ctx.baseBranch ?? "",
5836
+ projectName: ctx.projectName ?? null,
5837
+ projectDescription: ctx.projectDescription ?? null,
5666
5838
  githubPRUrl: ctx.githubPRUrl,
5667
5839
  claudeSessionId: ctx.claudeSessionId ?? null,
5668
5840
  isParentTask: !!ctx.parentTaskId,
@@ -5696,6 +5868,10 @@ var SessionRunner = class _SessionRunner {
5696
5868
  );
5697
5869
  bridge.isParentTask = this.fullContext?.isParentTask ?? false;
5698
5870
  bridge.onModeTransition = (newMode) => {
5871
+ const oldMode = this.mode.effectiveMode;
5872
+ process.stderr.write(`[conveyor-agent] Mode transition: ${oldMode} \u2192 ${newMode}
5873
+ `);
5874
+ this.connection.sendEvent({ type: "mode_transition", from: oldMode, to: newMode });
5699
5875
  this.mode.pendingModeRestart = true;
5700
5876
  this.connection.emitModeChanged(newMode);
5701
5877
  this.softStop();
@@ -5731,6 +5907,9 @@ var SessionRunner = class _SessionRunner {
5731
5907
  await this.callbacks.onStatusChange(status);
5732
5908
  }
5733
5909
  async shutdown(finalState) {
5910
+ process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
5911
+ `);
5912
+ this.connection.sendEvent({ type: "shutdown", reason: finalState });
5734
5913
  this.lifecycle.destroy();
5735
5914
  await this.setState(finalState);
5736
5915
  this.connection.disconnect();
@@ -5738,21 +5917,29 @@ var SessionRunner = class _SessionRunner {
5738
5917
  logInitialization() {
5739
5918
  const context = {
5740
5919
  mode: this.mode.effectiveMode,
5741
- model: this.taskContext?.model,
5742
5920
  runnerMode: this.config.runnerMode ?? "task",
5743
5921
  sessionId: this.sessionId,
5922
+ // Task context
5744
5923
  isParentTask: this.fullContext?.isParentTask ?? false,
5745
- status: this.taskContext?.status
5924
+ status: this.taskContext?.status,
5925
+ taskTitle: this.fullContext?.title,
5926
+ hasExistingPR: !!this.fullContext?.githubPRUrl,
5927
+ hasExistingSession: !!this.fullContext?.claudeSessionId,
5928
+ chatHistoryLength: this.fullContext?.chatHistory?.length ?? 0,
5929
+ tagIds: this.fullContext?.taskTagIds ?? [],
5930
+ // Agent config
5931
+ model: this.taskContext?.model,
5932
+ isAuto: this.config.isAuto ?? false
5746
5933
  };
5747
5934
  process.stderr.write(`[conveyor-agent] Initialized: ${JSON.stringify(context)}
5748
5935
  `);
5749
- this.connection.sendEvent({ type: "initialization", ...context });
5936
+ this.connection.sendEvent({ type: "session_manifest", ...context });
5750
5937
  }
5751
5938
  };
5752
5939
 
5753
5940
  // src/connection/project-connection.ts
5754
5941
  import { io as io2 } from "socket.io-client";
5755
- var logger3 = createServiceLogger("ProjectConnection");
5942
+ var logger4 = createServiceLogger("ProjectConnection");
5756
5943
  var EVENT_BATCH_MS2 = 500;
5757
5944
  var ProjectConnection = class {
5758
5945
  socket = null;
@@ -5795,7 +5982,7 @@ var ProjectConnection = class {
5795
5982
  let settled = false;
5796
5983
  let attempts = 0;
5797
5984
  const maxInitialAttempts = 30;
5798
- logger3.info("Connecting", { apiUrl: this.config.apiUrl, projectId: this.config.projectId });
5985
+ logger4.info("Connecting", { apiUrl: this.config.apiUrl, projectId: this.config.projectId });
5799
5986
  this.socket = io2(this.config.apiUrl, {
5800
5987
  auth: {
5801
5988
  projectToken: this.config.projectToken,
@@ -5815,7 +6002,7 @@ var ProjectConnection = class {
5815
6002
  settled = true;
5816
6003
  resolve2();
5817
6004
  }
5818
- logger3.info("Connected to API");
6005
+ logger4.info("Connected to API");
5819
6006
  });
5820
6007
  this.socket.on("connect_error", (error) => {
5821
6008
  attempts++;
@@ -5827,10 +6014,10 @@ var ProjectConnection = class {
5827
6014
  }
5828
6015
  });
5829
6016
  this.socket.on("disconnect", (reason) => {
5830
- logger3.warn("Disconnected from API", { reason });
6017
+ logger4.warn("Disconnected from API", { reason });
5831
6018
  });
5832
6019
  this.socket.io.on("reconnect", () => {
5833
- logger3.info("Reconnected to API");
6020
+ logger4.info("Reconnected to API");
5834
6021
  for (const cb of this.reconnectCallbacks) cb();
5835
6022
  });
5836
6023
  });
@@ -5977,7 +6164,7 @@ function runStartCommand(cmd, cwd, onOutput) {
5977
6164
 
5978
6165
  // src/runner/commit-watcher.ts
5979
6166
  import { execSync as execSync3 } from "child_process";
5980
- var logger4 = createServiceLogger("CommitWatcher");
6167
+ var logger5 = createServiceLogger("CommitWatcher");
5981
6168
  var CommitWatcher = class {
5982
6169
  constructor(config, callbacks) {
5983
6170
  this.config = config;
@@ -5993,7 +6180,7 @@ var CommitWatcher = class {
5993
6180
  this.branch = branch;
5994
6181
  this.lastKnownRemoteSha = this.getLocalHeadSha();
5995
6182
  this.interval = setInterval(() => void this.poll(), this.config.pollIntervalMs);
5996
- logger4.info("Commit watcher started", {
6183
+ logger5.info("Commit watcher started", {
5997
6184
  branch,
5998
6185
  baseSha: this.lastKnownRemoteSha?.slice(0, 8)
5999
6186
  });
@@ -6058,7 +6245,7 @@ var CommitWatcher = class {
6058
6245
  }
6059
6246
  this.lastKnownRemoteSha = remoteSha;
6060
6247
  this.isSyncing = true;
6061
- logger4.info("New commits detected", {
6248
+ logger5.info("New commits detected", {
6062
6249
  branch: this.branch,
6063
6250
  commitCount,
6064
6251
  sha: remoteSha.slice(0, 8)
@@ -6074,7 +6261,7 @@ var CommitWatcher = class {
6074
6261
  });
6075
6262
  } catch (err) {
6076
6263
  const msg = err instanceof Error ? err.message : String(err);
6077
- logger4.error("Error handling new commits", { error: msg });
6264
+ logger5.error("Error handling new commits", { error: msg });
6078
6265
  } finally {
6079
6266
  this.isSyncing = false;
6080
6267
  }
@@ -6311,7 +6498,7 @@ function buildProjectTools(connection) {
6311
6498
  }
6312
6499
 
6313
6500
  // src/runner/project-chat-handler.ts
6314
- var logger5 = createServiceLogger("ProjectChat");
6501
+ var logger6 = createServiceLogger("ProjectChat");
6315
6502
  var FALLBACK_MODEL = "claude-sonnet-4-20250514";
6316
6503
  function buildSystemPrompt2(projectDir, agentCtx) {
6317
6504
  const parts = [];
@@ -6358,7 +6545,7 @@ async function fetchContext(connection, chatId) {
6358
6545
  projectId: connection.projectId
6359
6546
  });
6360
6547
  } catch {
6361
- logger5.warn("Could not fetch agent context, using defaults");
6548
+ logger6.warn("Could not fetch agent context, using defaults");
6362
6549
  }
6363
6550
  let chatHistory = [];
6364
6551
  try {
@@ -6368,7 +6555,7 @@ async function fetchContext(connection, chatId) {
6368
6555
  chatId
6369
6556
  });
6370
6557
  } catch {
6371
- logger5.warn("Could not fetch chat history, proceeding without it");
6558
+ logger6.warn("Could not fetch chat history, proceeding without it");
6372
6559
  }
6373
6560
  return { agentCtx, chatHistory };
6374
6561
  }
@@ -6486,7 +6673,7 @@ async function handleProjectChatMessage(message, connection, projectDir, session
6486
6673
  return await runChatQuery(message, connection, projectDir, sessionId);
6487
6674
  } catch (error) {
6488
6675
  const msg = error instanceof Error ? error.message : String(error);
6489
- logger5.error("Failed to handle message", { error: msg });
6676
+ logger6.error("Failed to handle message", { error: msg });
6490
6677
  try {
6491
6678
  await connection.call("postProjectAgentMessage", {
6492
6679
  projectId: connection.projectId,
@@ -6502,7 +6689,7 @@ async function handleProjectChatMessage(message, connection, projectDir, session
6502
6689
  }
6503
6690
 
6504
6691
  // src/runner/project-runner.ts
6505
- var logger6 = createServiceLogger("ProjectRunner");
6692
+ var logger7 = createServiceLogger("ProjectRunner");
6506
6693
  var __filename = fileURLToPath(import.meta.url);
6507
6694
  var __dirname = path.dirname(__filename);
6508
6695
  var HEARTBEAT_INTERVAL_MS = 3e4;
@@ -6521,7 +6708,7 @@ function setupWorkDir(projectDir, assignment) {
6521
6708
  }
6522
6709
  if (branch && branch !== devBranch) {
6523
6710
  if (hasUncommittedChanges(workDir)) {
6524
- logger6.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
6711
+ logger7.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
6525
6712
  } else {
6526
6713
  try {
6527
6714
  execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
@@ -6529,7 +6716,7 @@ function setupWorkDir(projectDir, assignment) {
6529
6716
  try {
6530
6717
  execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
6531
6718
  } catch {
6532
- logger6.warn("Could not checkout branch", { taskId: shortId, branch });
6719
+ logger7.warn("Could not checkout branch", { taskId: shortId, branch });
6533
6720
  }
6534
6721
  }
6535
6722
  }
@@ -6545,7 +6732,7 @@ function spawnChildAgent(assignment, workDir) {
6545
6732
  const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
6546
6733
  const effectiveApiUrl = apiUrl || process.env.CONVEYOR_API_URL || "";
6547
6734
  if (!effectiveApiUrl) {
6548
- logger6.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
6735
+ logger7.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
6549
6736
  }
6550
6737
  const child = fork(cliPath, [], {
6551
6738
  env: {
@@ -6573,12 +6760,12 @@ function spawnChildAgent(assignment, workDir) {
6573
6760
  const shortId = taskId.slice(0, 8);
6574
6761
  child.stdout?.on("data", (data) => {
6575
6762
  for (const line of data.toString().trimEnd().split("\n")) {
6576
- logger6.info(line, { taskId: shortId });
6763
+ logger7.info(line, { taskId: shortId });
6577
6764
  }
6578
6765
  });
6579
6766
  child.stderr?.on("data", (data) => {
6580
6767
  for (const line of data.toString().trimEnd().split("\n")) {
6581
- logger6.info(line, { taskId: shortId });
6768
+ logger7.info(line, { taskId: shortId });
6582
6769
  }
6583
6770
  });
6584
6771
  return child;
@@ -6626,7 +6813,7 @@ var ProjectRunner = class {
6626
6813
  capabilities: ["task", "pm", "code-review", "audit"]
6627
6814
  });
6628
6815
  this.branchSwitchCommand = registration.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
6629
- logger6.info("Registered as project agent", { agentName: registration.agentName });
6816
+ logger7.info("Registered as project agent", { agentName: registration.agentName });
6630
6817
  try {
6631
6818
  await this.executeSetupCommand();
6632
6819
  this.executeStartCommand();
@@ -6637,7 +6824,7 @@ var ProjectRunner = class {
6637
6824
  });
6638
6825
  } catch (error) {
6639
6826
  const msg = error instanceof Error ? error.message : String(error);
6640
- logger6.error("Environment setup failed", { error: msg });
6827
+ logger7.error("Environment setup failed", { error: msg });
6641
6828
  this.setupComplete = false;
6642
6829
  }
6643
6830
  this.wireEventHandlers();
@@ -6649,7 +6836,7 @@ var ProjectRunner = class {
6649
6836
  if (currentBranch) {
6650
6837
  this.commitWatcher.start(currentBranch);
6651
6838
  }
6652
- logger6.info("Connected, waiting for task assignments");
6839
+ logger7.info("Connected, waiting for task assignments");
6653
6840
  await new Promise((resolve2) => {
6654
6841
  this.resolveLifecycle = resolve2;
6655
6842
  });
@@ -6657,7 +6844,7 @@ var ProjectRunner = class {
6657
6844
  async stop() {
6658
6845
  if (this.stopping) return;
6659
6846
  this.stopping = true;
6660
- logger6.info("Shutting down");
6847
+ logger7.info("Shutting down");
6661
6848
  this.commitWatcher.stop();
6662
6849
  await this.killStartCommand();
6663
6850
  if (this.heartbeatTimer) {
@@ -6688,7 +6875,7 @@ var ProjectRunner = class {
6688
6875
  } catch {
6689
6876
  }
6690
6877
  this.connection.disconnect();
6691
- logger6.info("Shutdown complete");
6878
+ logger7.info("Shutdown complete");
6692
6879
  if (this.resolveLifecycle) {
6693
6880
  this.resolveLifecycle();
6694
6881
  this.resolveLifecycle = null;
@@ -6724,10 +6911,10 @@ var ProjectRunner = class {
6724
6911
  capabilities: ["task", "pm", "code-review", "audit"]
6725
6912
  });
6726
6913
  this.connection.emitStatus(this.activeAgents.size > 0 ? "busy" : "idle");
6727
- logger6.info("Re-registered after reconnect");
6914
+ logger7.info("Re-registered after reconnect");
6728
6915
  } catch (error) {
6729
6916
  const msg = error instanceof Error ? error.message : String(error);
6730
- logger6.error("Failed to re-register after reconnect", { error: msg });
6917
+ logger7.error("Failed to re-register after reconnect", { error: msg });
6731
6918
  }
6732
6919
  }
6733
6920
  // ── Tag audit ──────────────────────────────────────────────────────────
@@ -6738,7 +6925,7 @@ var ProjectRunner = class {
6738
6925
  await handleTagAudit(request, this.connection, this.projectDir);
6739
6926
  } catch (error) {
6740
6927
  const msg = error instanceof Error ? error.message : String(error);
6741
- logger6.error("Tag audit failed", { error: msg, requestId: request.requestId });
6928
+ logger7.error("Tag audit failed", { error: msg, requestId: request.requestId });
6742
6929
  try {
6743
6930
  await this.connection.call("reportTagAuditResult", {
6744
6931
  projectId: this.connection.projectId,
@@ -6757,15 +6944,15 @@ var ProjectRunner = class {
6757
6944
  async killAgent(agent, taskId) {
6758
6945
  const shortId = taskId.slice(0, 8);
6759
6946
  if (agent.process.exitCode !== null) {
6760
- logger6.info("Agent process already exited", { taskId: shortId });
6947
+ logger7.info("Agent process already exited", { taskId: shortId });
6761
6948
  return;
6762
6949
  }
6763
- logger6.info("Killing agent process", { taskId: shortId });
6950
+ logger7.info("Killing agent process", { taskId: shortId });
6764
6951
  agent.process.kill("SIGTERM");
6765
6952
  await new Promise((resolve2) => {
6766
6953
  const timer = setTimeout(() => {
6767
6954
  if (agent.process.exitCode === null) {
6768
- logger6.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
6955
+ logger7.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
6769
6956
  agent.process.kill("SIGKILL");
6770
6957
  }
6771
6958
  resolve2();
@@ -6784,15 +6971,15 @@ var ProjectRunner = class {
6784
6971
  const existing = this.activeAgents.get(agentKey);
6785
6972
  if (existing) {
6786
6973
  if (existing.process.exitCode === null) {
6787
- logger6.info("Re-assignment received, killing existing agent", { taskId: shortId });
6974
+ logger7.info("Re-assignment received, killing existing agent", { taskId: shortId });
6788
6975
  await this.killAgent(existing, taskId);
6789
6976
  } else {
6790
- logger6.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
6977
+ logger7.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
6791
6978
  }
6792
6979
  this.activeAgents.delete(agentKey);
6793
6980
  }
6794
6981
  if (this.activeAgents.size >= MAX_CONCURRENT) {
6795
- logger6.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
6982
+ logger7.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
6796
6983
  this.connection.emitTaskStopped(taskId, "max_concurrent_reached");
6797
6984
  return;
6798
6985
  }
@@ -6800,9 +6987,12 @@ var ProjectRunner = class {
6800
6987
  try {
6801
6988
  execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
6802
6989
  } catch {
6803
- logger6.warn("Git fetch failed", { taskId: shortId });
6990
+ logger7.warn("Git fetch failed", { taskId: shortId });
6804
6991
  }
6805
6992
  const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
6993
+ if (assignment.devBranch) {
6994
+ syncWithBaseBranch(workDir, assignment.devBranch);
6995
+ }
6806
6996
  const child = spawnChildAgent(assignment, workDir);
6807
6997
  this.activeAgents.set(agentKey, {
6808
6998
  process: child,
@@ -6811,12 +7001,12 @@ var ProjectRunner = class {
6811
7001
  usesWorktree
6812
7002
  });
6813
7003
  this.connection.emitTaskStarted(taskId);
6814
- logger6.info("Started task", { taskId: shortId, mode, workDir });
7004
+ logger7.info("Started task", { taskId: shortId, mode, workDir });
6815
7005
  child.on("exit", (code) => {
6816
7006
  this.activeAgents.delete(agentKey);
6817
7007
  const reason = code === 0 ? "completed" : `exited with code ${code}`;
6818
7008
  this.connection.emitTaskStopped(taskId, reason);
6819
- logger6.info("Task exited", { taskId: shortId, reason });
7009
+ logger7.info("Task exited", { taskId: shortId, reason });
6820
7010
  if (code === 0 && usesWorktree) {
6821
7011
  try {
6822
7012
  removeWorktree(this.projectDir, taskId);
@@ -6826,7 +7016,7 @@ var ProjectRunner = class {
6826
7016
  });
6827
7017
  } catch (error) {
6828
7018
  const msg = error instanceof Error ? error.message : "Unknown";
6829
- logger6.error("Failed to start task", { taskId: shortId, error: msg });
7019
+ logger7.error("Failed to start task", { taskId: shortId, error: msg });
6830
7020
  this.connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
6831
7021
  }
6832
7022
  }
@@ -6834,7 +7024,7 @@ var ProjectRunner = class {
6834
7024
  const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
6835
7025
  const agent = this.activeAgents.get(agentKey);
6836
7026
  if (!agent) return;
6837
- logger6.info("Stopping task", { taskId: taskId.slice(0, 8) });
7027
+ logger7.info("Stopping task", { taskId: taskId.slice(0, 8) });
6838
7028
  void this.killAgent(agent, taskId).then(() => {
6839
7029
  if (agent.usesWorktree) {
6840
7030
  try {
@@ -6851,30 +7041,30 @@ var ProjectRunner = class {
6851
7041
  const currentBranch = this.getCurrentBranch();
6852
7042
  if (currentBranch === workspaceBranch) return;
6853
7043
  if (hasUncommittedChanges(this.projectDir)) {
6854
- logger6.warn("Uncommitted changes, skipping workspace branch checkout");
7044
+ logger7.warn("Uncommitted changes, skipping workspace branch checkout");
6855
7045
  return;
6856
7046
  }
6857
7047
  try {
6858
7048
  execSync5(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
6859
7049
  execSync5(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
6860
- logger6.info("Checked out workspace branch", { workspaceBranch });
7050
+ logger7.info("Checked out workspace branch", { workspaceBranch });
6861
7051
  } catch {
6862
- logger6.warn("Failed to checkout workspace branch", { workspaceBranch });
7052
+ logger7.warn("Failed to checkout workspace branch", { workspaceBranch });
6863
7053
  }
6864
7054
  }
6865
7055
  async executeSetupCommand() {
6866
7056
  const cmd = process.env.CONVEYOR_SETUP_COMMAND;
6867
7057
  if (!cmd) return;
6868
- logger6.info("Running setup command", { command: cmd });
7058
+ logger7.info("Running setup command", { command: cmd });
6869
7059
  try {
6870
7060
  await runSetupCommand(cmd, this.projectDir, (stream, data) => {
6871
7061
  this.connection.sendEvent({ type: "setup_output", stream, data });
6872
7062
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
6873
7063
  });
6874
- logger6.info("Setup command completed");
7064
+ logger7.info("Setup command completed");
6875
7065
  } catch (error) {
6876
7066
  const msg = error instanceof Error ? error.message : "Setup command failed";
6877
- logger6.error("Setup command failed", { error: msg });
7067
+ logger7.error("Setup command failed", { error: msg });
6878
7068
  this.connection.sendEvent({ type: "setup_error", message: msg });
6879
7069
  throw error;
6880
7070
  }
@@ -6882,7 +7072,7 @@ var ProjectRunner = class {
6882
7072
  executeStartCommand() {
6883
7073
  const cmd = process.env.CONVEYOR_START_COMMAND;
6884
7074
  if (!cmd) return;
6885
- logger6.info("Running start command", { command: cmd });
7075
+ logger7.info("Running start command", { command: cmd });
6886
7076
  const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
6887
7077
  this.connection.sendEvent({ type: "start_command_output", stream, data });
6888
7078
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
@@ -6892,13 +7082,13 @@ var ProjectRunner = class {
6892
7082
  child.on("exit", (code, signal) => {
6893
7083
  this.startCommandRunning = false;
6894
7084
  this.startCommandChild = null;
6895
- logger6.info("Start command exited", { code, signal });
7085
+ logger7.info("Start command exited", { code, signal });
6896
7086
  this.connection.sendEvent({ type: "start_command_exited", code, signal });
6897
7087
  });
6898
7088
  child.on("error", (err) => {
6899
7089
  this.startCommandRunning = false;
6900
7090
  this.startCommandChild = null;
6901
- logger6.error("Start command error", { error: err.message });
7091
+ logger7.error("Start command error", { error: err.message });
6902
7092
  });
6903
7093
  }
6904
7094
  async killStartCommand() {
@@ -6941,7 +7131,7 @@ var ProjectRunner = class {
6941
7131
  try {
6942
7132
  execSync5("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
6943
7133
  } catch {
6944
- logger6.warn("Git fetch failed during branch switch");
7134
+ logger7.warn("Git fetch failed during branch switch");
6945
7135
  }
6946
7136
  detachWorktreeBranch(this.projectDir, data.branch);
6947
7137
  try {
@@ -6954,7 +7144,7 @@ var ProjectRunner = class {
6954
7144
  try {
6955
7145
  execSync5(`git pull origin ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
6956
7146
  } catch {
6957
- logger6.warn("Git pull failed during branch switch");
7147
+ logger7.warn("Git pull failed during branch switch");
6958
7148
  }
6959
7149
  if (data.syncAfter !== false) {
6960
7150
  await this.handleSyncEnvironment();
@@ -6963,7 +7153,7 @@ var ProjectRunner = class {
6963
7153
  callback({ ok: true });
6964
7154
  } catch (err) {
6965
7155
  const msg = err instanceof Error ? err.message : "Branch switch failed";
6966
- logger6.error("Branch switch failed", { error: msg });
7156
+ logger7.error("Branch switch failed", { error: msg });
6967
7157
  callback({ ok: false, error: msg });
6968
7158
  }
6969
7159
  }
@@ -6978,14 +7168,14 @@ var ProjectRunner = class {
6978
7168
  });
6979
7169
  } catch (err) {
6980
7170
  const msg = err instanceof Error ? err.message : "Sync command failed";
6981
- logger6.error("Branch switch sync command failed", { error: msg });
7171
+ logger7.error("Branch switch sync command failed", { error: msg });
6982
7172
  }
6983
7173
  }
6984
7174
  this.executeStartCommand();
6985
7175
  callback?.({ ok: true });
6986
7176
  } catch (err) {
6987
7177
  const msg = err instanceof Error ? err.message : "Sync failed";
6988
- logger6.error("Environment sync failed", { error: msg });
7178
+ logger7.error("Environment sync failed", { error: msg });
6989
7179
  callback?.({ ok: false, error: msg });
6990
7180
  }
6991
7181
  }
@@ -7010,7 +7200,7 @@ var ProjectRunner = class {
7010
7200
  setupComplete: this.setupComplete,
7011
7201
  startCommandRunning: this.startCommandRunning
7012
7202
  });
7013
- logger6.info("Commit sync complete", { steps: stepsRun.join(", ") });
7203
+ logger7.info("Commit sync complete", { steps: stepsRun.join(", ") });
7014
7204
  }
7015
7205
  async smartSync(previousSha, newSha, branch) {
7016
7206
  if (hasUncommittedChanges(this.projectDir)) {
@@ -7029,7 +7219,7 @@ var ProjectRunner = class {
7029
7219
  });
7030
7220
  } catch (err) {
7031
7221
  const msg = err instanceof Error ? err.message : "Pull failed";
7032
- logger6.error("Git pull failed during commit sync", { error: msg });
7222
+ logger7.error("Git pull failed during commit sync", { error: msg });
7033
7223
  this.executeStartCommand();
7034
7224
  return ["error:pull"];
7035
7225
  }
@@ -7056,7 +7246,7 @@ var ProjectRunner = class {
7056
7246
  stepsRun.push("branchSwitchCommand");
7057
7247
  } catch (err) {
7058
7248
  const msg = err instanceof Error ? err.message : "Sync command failed";
7059
- logger6.error("Branch switch command failed", { error: msg });
7249
+ logger7.error("Branch switch command failed", { error: msg });
7060
7250
  }
7061
7251
  } else if (!cmd) {
7062
7252
  this.runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun);
@@ -7069,7 +7259,7 @@ var ProjectRunner = class {
7069
7259
  stepsRun.push("install");
7070
7260
  } catch (err) {
7071
7261
  const msg = err instanceof Error ? err.message : "Install failed";
7072
- logger6.error("bun install failed", { error: msg });
7262
+ logger7.error("bun install failed", { error: msg });
7073
7263
  }
7074
7264
  }
7075
7265
  if (needsPrisma) {
@@ -7083,7 +7273,7 @@ var ProjectRunner = class {
7083
7273
  stepsRun.push("prisma");
7084
7274
  } catch (err) {
7085
7275
  const msg = err instanceof Error ? err.message : "Prisma sync failed";
7086
- logger6.error("Prisma sync failed", { error: msg });
7276
+ logger7.error("Prisma sync failed", { error: msg });
7087
7277
  }
7088
7278
  }
7089
7279
  }
@@ -7099,6 +7289,33 @@ var ProjectRunner = class {
7099
7289
  }
7100
7290
  };
7101
7291
 
7292
+ // src/setup/config.ts
7293
+ import { readFile as readFile2 } from "fs/promises";
7294
+ import { join as join3 } from "path";
7295
+ var DEVCONTAINER_PATH = ".devcontainer/conveyor/devcontainer.json";
7296
+ async function loadForwardPorts(workspaceDir) {
7297
+ try {
7298
+ const raw = await readFile2(join3(workspaceDir, DEVCONTAINER_PATH), "utf-8");
7299
+ const parsed = JSON.parse(raw);
7300
+ return parsed.forwardPorts ?? [];
7301
+ } catch {
7302
+ return [];
7303
+ }
7304
+ }
7305
+ function loadConveyorConfig() {
7306
+ const envSetup = process.env.CONVEYOR_SETUP_COMMAND;
7307
+ const envStart = process.env.CONVEYOR_START_COMMAND;
7308
+ const envPort = process.env.CONVEYOR_PREVIEW_PORT;
7309
+ if (envSetup || envStart) {
7310
+ return {
7311
+ setupCommand: envSetup,
7312
+ startCommand: envStart,
7313
+ previewPort: envPort ? Number(envPort) : void 0
7314
+ };
7315
+ }
7316
+ return null;
7317
+ }
7318
+
7102
7319
  export {
7103
7320
  AgentConnection,
7104
7321
  ModeController,
@@ -7121,6 +7338,8 @@ export {
7121
7338
  ensureWorktree,
7122
7339
  detachWorktreeBranch,
7123
7340
  removeWorktree,
7124
- ProjectRunner
7341
+ ProjectRunner,
7342
+ loadForwardPorts,
7343
+ loadConveyorConfig
7125
7344
  };
7126
- //# sourceMappingURL=chunk-K2TJHPMA.js.map
7345
+ //# sourceMappingURL=chunk-FCRGYU2M.js.map