@rallycry/conveyor-agent 7.0.3 → 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.
@@ -263,6 +263,7 @@ var AgentConnection = class {
263
263
  };
264
264
  const heartbeatStatus = statusMap[this.lastEmittedStatus ?? "idle"] ?? "active";
265
265
  void this.call("heartbeat", {
266
+ sessionId: this.config.sessionId,
266
267
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
267
268
  status: heartbeatStatus
268
269
  }).catch(() => {
@@ -592,6 +593,34 @@ var Lifecycle = class {
592
593
 
593
594
  // src/runner/git-utils.ts
594
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
+ }
595
624
  function hasUncommittedChanges(cwd) {
596
625
  const status = execSync("git status --porcelain", {
597
626
  cwd,
@@ -832,7 +861,6 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
832
861
  `- "Open" \u2014 Ready to execute (if dependencies are met). Use start_child_cloud_build to fire it.`,
833
862
  `- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
834
863
  `- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
835
- `- "Hold" \u2014 PR exists but is on hold for team review. Do not merge \u2014 skip and move on.`,
836
864
  `- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
837
865
  `- "Complete" \u2014 Fully done. Move on.`,
838
866
  ``,
@@ -849,7 +877,6 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
849
877
  ` - "InProgress": A Task Runner is actively working. Do nothing \u2014 wait.`,
850
878
  ` - "Open" + allDependenciesMet=true: Ready to fire. Use start_child_cloud_build.`,
851
879
  ` - "Open" + allDependenciesMet=false: Blocked \u2014 skip for now. Will be unblocked when deps complete.`,
852
- ` - "Hold": On hold \u2014 team must review before merge. Skip.`,
853
880
  ` - "ReviewDev" / "Complete": Already done. Skip.`,
854
881
  ` - "Planning": Not ready. If blocking progress, notify team.`,
855
882
  ``,
@@ -859,7 +886,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
859
886
  ``,
860
887
  `5. After firing all ready tasks: report which tasks you fired to chat, then state you are going idle.`,
861
888
  ``,
862
- `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").`,
863
890
  ``,
864
891
  `## Important Rules`,
865
892
  `- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel.`,
@@ -1157,6 +1184,15 @@ function buildDiscoveryPrompt(context) {
1157
1184
  `- Goal: collaborate with the user to create a clear plan`,
1158
1185
  `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
1159
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
+ ``,
1160
1196
  `### Self-Identification Tools`,
1161
1197
  `Use these MCP tools to set your own task properties:`,
1162
1198
  `- \`update_task\` \u2014 save your plan and description`,
@@ -1168,15 +1204,31 @@ function buildDiscoveryPrompt(context) {
1168
1204
  `- Add matching tags using \`update_task_properties\` \u2014 this links relevant documentation and rules that help you plan more effectively`,
1169
1205
  `- Tags accelerate discovery by surfacing domain-specific context automatically`,
1170
1206
  ``,
1171
- `### Self-Update vs Subtasks`,
1172
- `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1173
- `- 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
+ ],
1217
+ ``,
1218
+ `### Subtask Plan Requirements`,
1219
+ `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1220
+ `- Plans should be multi-step implementation guides, not vague descriptions`,
1221
+ `- Include specific file paths, function names, and code patterns to modify`,
1222
+ `- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
1223
+ `- Include testing requirements and acceptance criteria`,
1224
+ `- Set \`storyPointValue\` based on estimated complexity`,
1174
1225
  ``,
1175
- `### Finishing Planning`,
1176
- `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.`,
1177
1228
  `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
1178
1229
  `- ExitPlanMode validates these properties and marks planning as complete`,
1179
- `- 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`
1180
1232
  ];
1181
1233
  if (context) parts.push(...buildPropertyInstructions(context));
1182
1234
  return parts.join("\n");
@@ -1203,6 +1255,22 @@ function buildAutoPrompt(context) {
1203
1255
  `- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
1204
1256
  `- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
1205
1257
  ``,
1258
+ `### Subtask Plan Requirements`,
1259
+ `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1260
+ `- Plans should be multi-step implementation guides, not vague descriptions`,
1261
+ `- Include specific file paths, function names, and code patterns to modify`,
1262
+ `- Reference existing implementations when relevant`,
1263
+ `- Include testing requirements and acceptance criteria`,
1264
+ `- Set \`storyPointValue\` based on estimated complexity`,
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
+ ``,
1206
1274
  `### Autonomous Guidelines:`,
1207
1275
  `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1208
1276
  `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
@@ -1222,34 +1290,31 @@ function buildModePrompt(agentMode, context) {
1222
1290
  `You are in Building mode \u2014 executing the plan.`,
1223
1291
  `- You have full coding access (read, write, edit, bash, git)`,
1224
1292
  `- Safety rules: no destructive operations, use --force-with-lease instead of --force`,
1225
- `- If this is a leaf task (no children): execute the plan directly`,
1226
- `- 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
+ ]
1227
1301
  ];
1228
1302
  if (process.env.CLAUDESPACE_NAME) {
1229
1303
  parts.push(
1230
1304
  ``,
1231
1305
  `### Resource Management`,
1232
- `Your pod starts with minimal resources (0.25 CPU / 1 Gi). You MUST call \`scale_up_resources\``,
1233
- `BEFORE running any of these operations \u2014 they WILL fail or OOM at baseline resources:`,
1234
- `- **light** (1 CPU / 4 Gi) \u2014 bun/npm/yarn install, pip install, basic dev servers, light builds`,
1235
- `- **standard** (2 CPU / 8 Gi) \u2014 full dev servers, test suites, typecheck, lint`,
1236
- `- **heavy** (4 CPU / 16 Gi) \u2014 E2E/browser automation, large parallel builds`,
1237
- `Scaling is one-way (up only) and capped by project limits.`,
1238
- `CRITICAL: Always scale to at least "light" before running any package install command.`
1306
+ `Your pod starts with minimal resources. You MUST call \`scale_up_resources\``,
1307
+ `BEFORE running any resource-intensive operations \u2014 they WILL fail or OOM at baseline resources:`,
1308
+ `- **setup** \u2014 package installs, basic dev servers, light builds`,
1309
+ `- **build** \u2014 full dev servers, test suites, typecheck, lint, E2E tests`,
1310
+ `Actual CPU/memory values are configured per-project. Scaling is one-way (up only).`,
1311
+ `CRITICAL: Always scale to at least "setup" before running any package install command.`
1239
1312
  );
1240
1313
  }
1241
1314
  return parts.join("\n");
1242
1315
  }
1243
1316
  case "review":
1244
- return [
1245
- `
1246
- ## Mode: Review`,
1247
- `You are in Review mode \u2014 reviewing and coordinating.`,
1248
- `- You have read-only access plus light edit capability (can suggest fixes, run tests, check linting)`,
1249
- `- For parent tasks: you can manage children, review child PRs, fire next child builds`,
1250
- `- You have Pack Runner coordination tools (list_subtasks, fire builds, approve PRs)`,
1251
- `- Goal: ensure quality, provide feedback, coordinate progression`
1252
- ].join("\n");
1317
+ return buildReviewPrompt(context);
1253
1318
  case "auto":
1254
1319
  return buildAutoPrompt(context);
1255
1320
  case "code-review":
@@ -1258,6 +1323,64 @@ function buildModePrompt(agentMode, context) {
1258
1323
  return null;
1259
1324
  }
1260
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
+ }
1261
1384
  function buildCodeReviewPrompt() {
1262
1385
  return [
1263
1386
  `
@@ -1488,6 +1611,7 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
1488
1611
  }
1489
1612
 
1490
1613
  // src/execution/prompt-builder.ts
1614
+ var CHAT_HISTORY_LIMIT = 40;
1491
1615
  function formatFileSize(bytes) {
1492
1616
  if (bytes === void 0) return "";
1493
1617
  if (bytes < 1024) return `${bytes}B`;
@@ -1633,7 +1757,7 @@ function formatTaskFile(file) {
1633
1757
  return [];
1634
1758
  }
1635
1759
  function formatChatHistory(chatHistory) {
1636
- const relevant = chatHistory.slice(-20);
1760
+ const relevant = chatHistory.slice(-CHAT_HISTORY_LIMIT);
1637
1761
  const parts = [`
1638
1762
  ## Recent Chat Context`];
1639
1763
  for (const msg of relevant) {
@@ -1660,6 +1784,12 @@ async function resolveTaskTagContext(context) {
1660
1784
  async function buildTaskBody(context) {
1661
1785
  const parts = [];
1662
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
+ }
1663
1793
  if (context.description) {
1664
1794
  parts.push(`
1665
1795
  ## Description
@@ -1739,7 +1869,8 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1739
1869
  if (isPm && context.isParentTask) {
1740
1870
  return [
1741
1871
  `You are the project manager for this task and its subtasks.`,
1742
- `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.`,
1743
1874
  `The task details are provided above. Wait for the team to provide instructions before taking action.`,
1744
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.`
1745
1876
  ];
@@ -1747,6 +1878,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
1747
1878
  if (isPm) {
1748
1879
  return [
1749
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.`,
1750
1882
  `The task details are provided above. Wait for the team to ask questions or provide additional requirements before starting to plan.`,
1751
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.`
1752
1884
  ];
@@ -2000,7 +2132,11 @@ function buildGetTaskCliTool(connection) {
2000
2132
  taskId: task_id,
2001
2133
  source
2002
2134
  });
2003
- 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");
2004
2140
  return textResult(formatted || "No CLI logs found.");
2005
2141
  } catch (error) {
2006
2142
  return textResult(
@@ -2248,7 +2384,7 @@ function buildForceUpdateTaskStatusTool(connection) {
2248
2384
  "force_update_task_status",
2249
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.",
2250
2386
  {
2251
- 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"),
2252
2388
  task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
2253
2389
  },
2254
2390
  async ({ status, task_id }) => {
@@ -2348,7 +2484,15 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
2348
2484
  return textResult(`Pull request #${result.prNumber} created: ${result.prUrl}`);
2349
2485
  } catch (error) {
2350
2486
  const msg = error instanceof Error ? error.message : "Unknown error";
2351
- return textResult(`Failed to create pull request: ${msg}`);
2487
+ return textResult(
2488
+ `Failed to create pull request: ${msg}
2489
+
2490
+ Troubleshooting:
2491
+ - Ensure all changes are committed and pushed to the remote branch
2492
+ - Check that the branch exists on the remote (run: git push -u origin HEAD)
2493
+ - Verify there isn't already an open PR for this branch
2494
+ - If git auth fails, the token may have expired \u2014 retry the operation`
2495
+ );
2352
2496
  }
2353
2497
  }
2354
2498
  );
@@ -2485,9 +2629,9 @@ function buildVoteSuggestionTool(connection) {
2485
2629
  function buildScaleUpResourcesTool(connection) {
2486
2630
  return defineTool(
2487
2631
  "scale_up_resources",
2488
- "Scale up the pod's CPU and memory resources. Use before running dev servers, tests, builds, or other resource-intensive operations. Tiers: 'light' (1 CPU / 4 Gi \u2014 installs, basic dev servers), 'standard' (2 CPU / 8 Gi \u2014 full dev servers, test suites, typecheck), 'heavy' (4 CPU / 16 Gi \u2014 E2E tests, large parallel builds). Scaling is one-way (up only) and capped by project limits.",
2632
+ "Scale up the pod's CPU and memory resources. Use before running dev servers, tests, builds, or other resource-intensive operations. Phases: 'setup' (installs, basic dev servers), 'build' (full dev servers, test suites, typecheck, builds). Actual CPU/memory values are configured per-project. Scaling is one-way (up only).",
2489
2633
  {
2490
- tier: z.enum(["initial", "light", "standard", "heavy"]).describe("The resource tier to scale up to"),
2634
+ tier: z.enum(["initial", "setup", "build"]).describe("The resource phase to scale up to"),
2491
2635
  reason: z.string().optional().describe("Brief reason for scaling (e.g., 'running test suite')")
2492
2636
  },
2493
2637
  async ({ tier, reason }) => {
@@ -2498,13 +2642,8 @@ function buildScaleUpResourcesTool(connection) {
2498
2642
  reason
2499
2643
  });
2500
2644
  if (result.success) {
2501
- if (result.currentTier === result.previousTier) {
2502
- return textResult(
2503
- `Already at ${result.currentTier} tier (${result.cpu} CPU / ${result.memory} Gi). No scaling needed.`
2504
- );
2505
- }
2506
2645
  return textResult(
2507
- `Scaled to ${result.cpu} CPU / ${result.memory} Gi (${result.currentTier} tier, was ${result.previousTier}).`
2646
+ `Scaled to ${result.currentTier} phase (${result.cpu}). Was ${result.previousTier}.`
2508
2647
  );
2509
2648
  }
2510
2649
  return textResult(
@@ -2581,14 +2720,15 @@ function buildCreateSubtaskTool(connection) {
2581
2720
  ordinal: z2.number().optional().describe("Step/order number (0-based)"),
2582
2721
  storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2583
2722
  },
2584
- async ({ title, description, plan: _plan, ordinal, storyPointValue }) => {
2723
+ async ({ title, description, plan, ordinal, storyPointValue }) => {
2585
2724
  try {
2586
2725
  const result = await connection.call("createSubtask", {
2587
2726
  sessionId: connection.sessionId,
2588
2727
  title,
2589
- description,
2590
- storyPoints: storyPointValue,
2591
- ordinal
2728
+ ...description !== void 0 && { description },
2729
+ ...plan !== void 0 && { plan },
2730
+ ...storyPointValue !== void 0 && { storyPointValue },
2731
+ ...ordinal !== void 0 && { ordinal }
2592
2732
  });
2593
2733
  return textResult(`Subtask created with ID: ${result.id}`);
2594
2734
  } catch (error) {
@@ -2611,20 +2751,15 @@ function buildUpdateSubtaskTool(connection) {
2611
2751
  ordinal: z2.number().optional(),
2612
2752
  storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2613
2753
  },
2614
- async ({
2615
- subtaskId,
2616
- title,
2617
- description,
2618
- plan: _plan,
2619
- ordinal: _ordinal,
2620
- storyPointValue: _sp
2621
- }) => {
2754
+ async ({ subtaskId, title, description, plan, storyPointValue }) => {
2622
2755
  try {
2623
2756
  await connection.call("updateSubtask", {
2624
2757
  sessionId: connection.sessionId,
2625
2758
  subtaskId,
2626
- title,
2627
- description
2759
+ ...title !== void 0 && { title },
2760
+ ...description !== void 0 && { description },
2761
+ ...plan !== void 0 && { plan },
2762
+ ...storyPointValue !== void 0 && { storyPointValue }
2628
2763
  });
2629
2764
  return textResult("Subtask updated.");
2630
2765
  } catch (error) {
@@ -4207,6 +4342,7 @@ function createConveyorMcpServer(harness, connection, config, context, agentMode
4207
4342
  }
4208
4343
 
4209
4344
  // src/execution/event-handlers.ts
4345
+ var logger = createServiceLogger("event-handlers");
4210
4346
  function safeVoid(promise, context) {
4211
4347
  if (promise && typeof promise.catch === "function") {
4212
4348
  promise.catch((err) => {
@@ -4384,6 +4520,7 @@ async function emitResultEvent(event, host, context, startTime, lastAssistantUsa
4384
4520
  }
4385
4521
  function handleRateLimitEvent(event, host) {
4386
4522
  const { rate_limit_info } = event;
4523
+ logger.info("Rate limit event received", { rate_limit_info });
4387
4524
  const status = rate_limit_info.status;
4388
4525
  const utilization = rate_limit_info.utilization ?? (status === "rejected" ? 1 : void 0);
4389
4526
  if (utilization !== void 0 && rate_limit_info.rateLimitType) {
@@ -4601,6 +4738,16 @@ async function processEvents(events, context, host) {
4601
4738
  };
4602
4739
  }
4603
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
+
4604
4751
  // src/execution/tool-access.ts
4605
4752
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
4606
4753
  var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
@@ -4690,24 +4837,43 @@ async function handleExitPlanMode(host, input) {
4690
4837
  if (host.hasExitedPlanMode) {
4691
4838
  return { behavior: "allow", updatedInput: input };
4692
4839
  }
4840
+ host.exitPlanAttempts++;
4693
4841
  try {
4694
4842
  host.syncPlanFile();
4695
4843
  const taskProps = await host.connection.getTaskProperties();
4696
- const missingProps = [];
4697
- if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
4698
- if (!taskProps.storyPointId) missingProps.push("story points (use update_task_properties)");
4699
- if (!taskProps.title || taskProps.title === "Untitled")
4700
- missingProps.push("title (use update_task_properties)");
4844
+ const missingProps = collectMissingProps(taskProps);
4845
+ if (host.isParentTask) {
4846
+ try {
4847
+ const subtasks = await host.connection.call("listSubtasks", {
4848
+ sessionId: host.connection.sessionId
4849
+ });
4850
+ const subtasksWithoutPlans = subtasks.filter(
4851
+ (s) => !s.plan?.trim()
4852
+ );
4853
+ if (subtasksWithoutPlans.length > 0) {
4854
+ const names = subtasksWithoutPlans.map((s) => s.title).join(", ");
4855
+ missingProps.push(
4856
+ `subtask plans \u2014 these subtasks are missing plans: ${names} (use update_subtask with plan field)`
4857
+ );
4858
+ }
4859
+ } catch {
4860
+ }
4861
+ }
4701
4862
  if (missingProps.length > 0) {
4702
- return {
4703
- behavior: "deny",
4704
- message: [
4705
- "Cannot exit plan mode yet. Required task properties are missing:",
4706
- ...missingProps.map((p) => `- ${p}`),
4707
- "",
4708
- "Fill these in using MCP tools, then try ExitPlanMode again."
4709
- ].join("\n")
4710
- };
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
+ );
4711
4877
  }
4712
4878
  if (host.agentMode === "discovery") {
4713
4879
  host.hasExitedPlanMode = true;
@@ -4812,7 +4978,7 @@ function buildCanUseTool(host) {
4812
4978
  }
4813
4979
 
4814
4980
  // src/execution/query-executor.ts
4815
- var logger = createServiceLogger("QueryExecutor");
4981
+ var logger2 = createServiceLogger("QueryExecutor");
4816
4982
  var API_ERROR_PATTERN3 = /API Error: [45]\d\d/;
4817
4983
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
4818
4984
  var RETRY_DELAYS_MS = [6e4, 12e4, 18e4, 3e5];
@@ -4832,6 +4998,18 @@ function buildHooks(host) {
4832
4998
  isError: false
4833
4999
  });
4834
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
+ }
4835
5013
  }
4836
5014
  return await Promise.resolve({ continue: true });
4837
5015
  }
@@ -4893,7 +5071,7 @@ function buildQueryOptions(host, context) {
4893
5071
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
4894
5072
  enableFileCheckpointing: settings.enableFileCheckpointing,
4895
5073
  stderr: (data) => {
4896
- logger.warn("Claude Code stderr", { data: data.trimEnd() });
5074
+ logger2.warn("Claude Code stderr", { data: data.trimEnd() });
4897
5075
  }
4898
5076
  };
4899
5077
  }
@@ -5203,7 +5381,7 @@ var CostTracker = class {
5203
5381
  };
5204
5382
 
5205
5383
  // src/runner/query-bridge.ts
5206
- var logger2 = createServiceLogger("QueryBridge");
5384
+ var logger3 = createServiceLogger("QueryBridge");
5207
5385
  var QueryBridge = class {
5208
5386
  constructor(connection, mode, runnerConfig, callbacks, workspaceDir) {
5209
5387
  this.connection = connection;
@@ -5254,8 +5432,13 @@ var QueryBridge = class {
5254
5432
  await runSdkQuery(host, context, followUpContent);
5255
5433
  } catch (err) {
5256
5434
  const msg = err instanceof Error ? err.message : String(err);
5257
- logger2.error("Query execution failed", { error: msg });
5258
- 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
+ }
5259
5442
  } finally {
5260
5443
  this.mode.pendingModeRestart = false;
5261
5444
  }
@@ -5285,6 +5468,7 @@ var QueryBridge = class {
5285
5468
  set hasExitedPlanMode(val) {
5286
5469
  bridge.mode.hasExitedPlanMode = val;
5287
5470
  },
5471
+ exitPlanAttempts: 0,
5288
5472
  get pendingModeRestart() {
5289
5473
  return bridge.mode.pendingModeRestart;
5290
5474
  },
@@ -5345,9 +5529,11 @@ var SessionRunner = class _SessionRunner {
5345
5529
  this.lifecycle = new Lifecycle(lifecycleConfig, {
5346
5530
  onHeartbeat: () => this.connection.sendHeartbeat(),
5347
5531
  onIdleTimeout: () => {
5532
+ process.stderr.write("[conveyor-agent] Idle timeout reached, entering sleep\n");
5348
5533
  this.connection.emitStatus("sleeping");
5349
5534
  },
5350
5535
  onSleep: () => {
5536
+ process.stderr.write("[conveyor-agent] Sleep mode active\n");
5351
5537
  this.connection.postChatMessage("Agent sleeping \u2014 send a message or click Resume to wake.");
5352
5538
  },
5353
5539
  onSleepGraceExpired: () => {
@@ -5359,6 +5545,7 @@ var SessionRunner = class _SessionRunner {
5359
5545
  }
5360
5546
  },
5361
5547
  onWake: () => {
5548
+ process.stderr.write("[conveyor-agent] Woken from sleep\n");
5362
5549
  this.lifecycle.cancelSleepShutdown();
5363
5550
  }
5364
5551
  });
@@ -5373,7 +5560,7 @@ var SessionRunner = class _SessionRunner {
5373
5560
  return this.stopped;
5374
5561
  }
5375
5562
  // ── Main lifecycle ─────────────────────────────────────────────────
5376
- // 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
5377
5564
  async start() {
5378
5565
  await this.setState("connecting");
5379
5566
  await this.connection.connect();
@@ -5411,6 +5598,9 @@ var SessionRunner = class _SessionRunner {
5411
5598
  await this.shutdown("error");
5412
5599
  return;
5413
5600
  }
5601
+ if (this.fullContext?.baseBranch) {
5602
+ syncWithBaseBranch(this.config.workspaceDir, this.fullContext.baseBranch);
5603
+ }
5414
5604
  this.mode.resolveInitialMode(this.taskContext);
5415
5605
  this.queryBridge = this.createQueryBridge();
5416
5606
  this.logInitialization();
@@ -5460,8 +5650,11 @@ var SessionRunner = class _SessionRunner {
5460
5650
  content: msg.content,
5461
5651
  userId: msg.userId
5462
5652
  });
5463
- if (this.fullContext && this.queryBridge) {
5464
- 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;
5465
5658
  }
5466
5659
  if (!this.stopped && this.pendingMessages.length === 0) {
5467
5660
  await this.maybeSendPRNudge();
@@ -5482,7 +5675,7 @@ var SessionRunner = class _SessionRunner {
5482
5675
  if (effectiveMode === "code-review") {
5483
5676
  await this.setState("running");
5484
5677
  await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode });
5485
- await this.queryBridge?.execute(this.fullContext);
5678
+ await this.executeQuery();
5486
5679
  this.stopped = true;
5487
5680
  return;
5488
5681
  }
@@ -5490,7 +5683,7 @@ var SessionRunner = class _SessionRunner {
5490
5683
  if (shouldRun) {
5491
5684
  await this.setState("running");
5492
5685
  await this.callbacks.onEvent({ type: "execute_mode", mode: effectiveMode });
5493
- await this.queryBridge?.execute(this.fullContext);
5686
+ await this.executeQuery();
5494
5687
  if (!this.stopped) await this.setState("idle");
5495
5688
  } else {
5496
5689
  await this.setState("idle");
@@ -5519,6 +5712,20 @@ var SessionRunner = class _SessionRunner {
5519
5712
  }
5520
5713
  }
5521
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
+ }
5522
5729
  // ── Stop / soft-stop ───────────────────────────────────────────────
5523
5730
  stop() {
5524
5731
  this.stopped = true;
@@ -5588,9 +5795,8 @@ var SessionRunner = class _SessionRunner {
5588
5795
  this.connection.postChatMessage(chatMsg);
5589
5796
  await this.setState("running");
5590
5797
  await this.callbacks.onEvent({ type: "pr_nudge", prompt });
5591
- if (this.fullContext && this.queryBridge) {
5592
- await this.queryBridge.execute(this.fullContext, prompt);
5593
- }
5798
+ await this.executeQuery(prompt);
5799
+ if (this.interrupted || this.stopped) return;
5594
5800
  await this.refreshTaskContext();
5595
5801
  }
5596
5802
  }
@@ -5627,6 +5833,8 @@ var SessionRunner = class _SessionRunner {
5627
5833
  model: ctx.model,
5628
5834
  githubBranch: ctx.githubBranch ?? "",
5629
5835
  baseBranch: ctx.baseBranch ?? "",
5836
+ projectName: ctx.projectName ?? null,
5837
+ projectDescription: ctx.projectDescription ?? null,
5630
5838
  githubPRUrl: ctx.githubPRUrl,
5631
5839
  claudeSessionId: ctx.claudeSessionId ?? null,
5632
5840
  isParentTask: !!ctx.parentTaskId,
@@ -5660,6 +5868,10 @@ var SessionRunner = class _SessionRunner {
5660
5868
  );
5661
5869
  bridge.isParentTask = this.fullContext?.isParentTask ?? false;
5662
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 });
5663
5875
  this.mode.pendingModeRestart = true;
5664
5876
  this.connection.emitModeChanged(newMode);
5665
5877
  this.softStop();
@@ -5695,6 +5907,9 @@ var SessionRunner = class _SessionRunner {
5695
5907
  await this.callbacks.onStatusChange(status);
5696
5908
  }
5697
5909
  async shutdown(finalState) {
5910
+ process.stderr.write(`[conveyor-agent] Shutdown: reason=${finalState}
5911
+ `);
5912
+ this.connection.sendEvent({ type: "shutdown", reason: finalState });
5698
5913
  this.lifecycle.destroy();
5699
5914
  await this.setState(finalState);
5700
5915
  this.connection.disconnect();
@@ -5702,21 +5917,29 @@ var SessionRunner = class _SessionRunner {
5702
5917
  logInitialization() {
5703
5918
  const context = {
5704
5919
  mode: this.mode.effectiveMode,
5705
- model: this.taskContext?.model,
5706
5920
  runnerMode: this.config.runnerMode ?? "task",
5707
5921
  sessionId: this.sessionId,
5922
+ // Task context
5708
5923
  isParentTask: this.fullContext?.isParentTask ?? false,
5709
- 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
5710
5933
  };
5711
5934
  process.stderr.write(`[conveyor-agent] Initialized: ${JSON.stringify(context)}
5712
5935
  `);
5713
- this.connection.sendEvent({ type: "initialization", ...context });
5936
+ this.connection.sendEvent({ type: "session_manifest", ...context });
5714
5937
  }
5715
5938
  };
5716
5939
 
5717
5940
  // src/connection/project-connection.ts
5718
5941
  import { io as io2 } from "socket.io-client";
5719
- var logger3 = createServiceLogger("ProjectConnection");
5942
+ var logger4 = createServiceLogger("ProjectConnection");
5720
5943
  var EVENT_BATCH_MS2 = 500;
5721
5944
  var ProjectConnection = class {
5722
5945
  socket = null;
@@ -5759,7 +5982,7 @@ var ProjectConnection = class {
5759
5982
  let settled = false;
5760
5983
  let attempts = 0;
5761
5984
  const maxInitialAttempts = 30;
5762
- logger3.info("Connecting", { apiUrl: this.config.apiUrl, projectId: this.config.projectId });
5985
+ logger4.info("Connecting", { apiUrl: this.config.apiUrl, projectId: this.config.projectId });
5763
5986
  this.socket = io2(this.config.apiUrl, {
5764
5987
  auth: {
5765
5988
  projectToken: this.config.projectToken,
@@ -5779,7 +6002,7 @@ var ProjectConnection = class {
5779
6002
  settled = true;
5780
6003
  resolve2();
5781
6004
  }
5782
- logger3.info("Connected to API");
6005
+ logger4.info("Connected to API");
5783
6006
  });
5784
6007
  this.socket.on("connect_error", (error) => {
5785
6008
  attempts++;
@@ -5791,10 +6014,10 @@ var ProjectConnection = class {
5791
6014
  }
5792
6015
  });
5793
6016
  this.socket.on("disconnect", (reason) => {
5794
- logger3.warn("Disconnected from API", { reason });
6017
+ logger4.warn("Disconnected from API", { reason });
5795
6018
  });
5796
6019
  this.socket.io.on("reconnect", () => {
5797
- logger3.info("Reconnected to API");
6020
+ logger4.info("Reconnected to API");
5798
6021
  for (const cb of this.reconnectCallbacks) cb();
5799
6022
  });
5800
6023
  });
@@ -5941,7 +6164,7 @@ function runStartCommand(cmd, cwd, onOutput) {
5941
6164
 
5942
6165
  // src/runner/commit-watcher.ts
5943
6166
  import { execSync as execSync3 } from "child_process";
5944
- var logger4 = createServiceLogger("CommitWatcher");
6167
+ var logger5 = createServiceLogger("CommitWatcher");
5945
6168
  var CommitWatcher = class {
5946
6169
  constructor(config, callbacks) {
5947
6170
  this.config = config;
@@ -5957,7 +6180,7 @@ var CommitWatcher = class {
5957
6180
  this.branch = branch;
5958
6181
  this.lastKnownRemoteSha = this.getLocalHeadSha();
5959
6182
  this.interval = setInterval(() => void this.poll(), this.config.pollIntervalMs);
5960
- logger4.info("Commit watcher started", {
6183
+ logger5.info("Commit watcher started", {
5961
6184
  branch,
5962
6185
  baseSha: this.lastKnownRemoteSha?.slice(0, 8)
5963
6186
  });
@@ -6022,7 +6245,7 @@ var CommitWatcher = class {
6022
6245
  }
6023
6246
  this.lastKnownRemoteSha = remoteSha;
6024
6247
  this.isSyncing = true;
6025
- logger4.info("New commits detected", {
6248
+ logger5.info("New commits detected", {
6026
6249
  branch: this.branch,
6027
6250
  commitCount,
6028
6251
  sha: remoteSha.slice(0, 8)
@@ -6038,7 +6261,7 @@ var CommitWatcher = class {
6038
6261
  });
6039
6262
  } catch (err) {
6040
6263
  const msg = err instanceof Error ? err.message : String(err);
6041
- logger4.error("Error handling new commits", { error: msg });
6264
+ logger5.error("Error handling new commits", { error: msg });
6042
6265
  } finally {
6043
6266
  this.isSyncing = false;
6044
6267
  }
@@ -6275,7 +6498,7 @@ function buildProjectTools(connection) {
6275
6498
  }
6276
6499
 
6277
6500
  // src/runner/project-chat-handler.ts
6278
- var logger5 = createServiceLogger("ProjectChat");
6501
+ var logger6 = createServiceLogger("ProjectChat");
6279
6502
  var FALLBACK_MODEL = "claude-sonnet-4-20250514";
6280
6503
  function buildSystemPrompt2(projectDir, agentCtx) {
6281
6504
  const parts = [];
@@ -6322,7 +6545,7 @@ async function fetchContext(connection, chatId) {
6322
6545
  projectId: connection.projectId
6323
6546
  });
6324
6547
  } catch {
6325
- logger5.warn("Could not fetch agent context, using defaults");
6548
+ logger6.warn("Could not fetch agent context, using defaults");
6326
6549
  }
6327
6550
  let chatHistory = [];
6328
6551
  try {
@@ -6332,7 +6555,7 @@ async function fetchContext(connection, chatId) {
6332
6555
  chatId
6333
6556
  });
6334
6557
  } catch {
6335
- logger5.warn("Could not fetch chat history, proceeding without it");
6558
+ logger6.warn("Could not fetch chat history, proceeding without it");
6336
6559
  }
6337
6560
  return { agentCtx, chatHistory };
6338
6561
  }
@@ -6450,7 +6673,7 @@ async function handleProjectChatMessage(message, connection, projectDir, session
6450
6673
  return await runChatQuery(message, connection, projectDir, sessionId);
6451
6674
  } catch (error) {
6452
6675
  const msg = error instanceof Error ? error.message : String(error);
6453
- logger5.error("Failed to handle message", { error: msg });
6676
+ logger6.error("Failed to handle message", { error: msg });
6454
6677
  try {
6455
6678
  await connection.call("postProjectAgentMessage", {
6456
6679
  projectId: connection.projectId,
@@ -6466,7 +6689,7 @@ async function handleProjectChatMessage(message, connection, projectDir, session
6466
6689
  }
6467
6690
 
6468
6691
  // src/runner/project-runner.ts
6469
- var logger6 = createServiceLogger("ProjectRunner");
6692
+ var logger7 = createServiceLogger("ProjectRunner");
6470
6693
  var __filename = fileURLToPath(import.meta.url);
6471
6694
  var __dirname = path.dirname(__filename);
6472
6695
  var HEARTBEAT_INTERVAL_MS = 3e4;
@@ -6485,7 +6708,7 @@ function setupWorkDir(projectDir, assignment) {
6485
6708
  }
6486
6709
  if (branch && branch !== devBranch) {
6487
6710
  if (hasUncommittedChanges(workDir)) {
6488
- logger6.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
6711
+ logger7.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
6489
6712
  } else {
6490
6713
  try {
6491
6714
  execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
@@ -6493,7 +6716,7 @@ function setupWorkDir(projectDir, assignment) {
6493
6716
  try {
6494
6717
  execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
6495
6718
  } catch {
6496
- logger6.warn("Could not checkout branch", { taskId: shortId, branch });
6719
+ logger7.warn("Could not checkout branch", { taskId: shortId, branch });
6497
6720
  }
6498
6721
  }
6499
6722
  }
@@ -6509,7 +6732,7 @@ function spawnChildAgent(assignment, workDir) {
6509
6732
  const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
6510
6733
  const effectiveApiUrl = apiUrl || process.env.CONVEYOR_API_URL || "";
6511
6734
  if (!effectiveApiUrl) {
6512
- 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) });
6513
6736
  }
6514
6737
  const child = fork(cliPath, [], {
6515
6738
  env: {
@@ -6537,12 +6760,12 @@ function spawnChildAgent(assignment, workDir) {
6537
6760
  const shortId = taskId.slice(0, 8);
6538
6761
  child.stdout?.on("data", (data) => {
6539
6762
  for (const line of data.toString().trimEnd().split("\n")) {
6540
- logger6.info(line, { taskId: shortId });
6763
+ logger7.info(line, { taskId: shortId });
6541
6764
  }
6542
6765
  });
6543
6766
  child.stderr?.on("data", (data) => {
6544
6767
  for (const line of data.toString().trimEnd().split("\n")) {
6545
- logger6.info(line, { taskId: shortId });
6768
+ logger7.info(line, { taskId: shortId });
6546
6769
  }
6547
6770
  });
6548
6771
  return child;
@@ -6590,7 +6813,7 @@ var ProjectRunner = class {
6590
6813
  capabilities: ["task", "pm", "code-review", "audit"]
6591
6814
  });
6592
6815
  this.branchSwitchCommand = registration.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
6593
- logger6.info("Registered as project agent", { agentName: registration.agentName });
6816
+ logger7.info("Registered as project agent", { agentName: registration.agentName });
6594
6817
  try {
6595
6818
  await this.executeSetupCommand();
6596
6819
  this.executeStartCommand();
@@ -6601,7 +6824,7 @@ var ProjectRunner = class {
6601
6824
  });
6602
6825
  } catch (error) {
6603
6826
  const msg = error instanceof Error ? error.message : String(error);
6604
- logger6.error("Environment setup failed", { error: msg });
6827
+ logger7.error("Environment setup failed", { error: msg });
6605
6828
  this.setupComplete = false;
6606
6829
  }
6607
6830
  this.wireEventHandlers();
@@ -6613,7 +6836,7 @@ var ProjectRunner = class {
6613
6836
  if (currentBranch) {
6614
6837
  this.commitWatcher.start(currentBranch);
6615
6838
  }
6616
- logger6.info("Connected, waiting for task assignments");
6839
+ logger7.info("Connected, waiting for task assignments");
6617
6840
  await new Promise((resolve2) => {
6618
6841
  this.resolveLifecycle = resolve2;
6619
6842
  });
@@ -6621,7 +6844,7 @@ var ProjectRunner = class {
6621
6844
  async stop() {
6622
6845
  if (this.stopping) return;
6623
6846
  this.stopping = true;
6624
- logger6.info("Shutting down");
6847
+ logger7.info("Shutting down");
6625
6848
  this.commitWatcher.stop();
6626
6849
  await this.killStartCommand();
6627
6850
  if (this.heartbeatTimer) {
@@ -6652,7 +6875,7 @@ var ProjectRunner = class {
6652
6875
  } catch {
6653
6876
  }
6654
6877
  this.connection.disconnect();
6655
- logger6.info("Shutdown complete");
6878
+ logger7.info("Shutdown complete");
6656
6879
  if (this.resolveLifecycle) {
6657
6880
  this.resolveLifecycle();
6658
6881
  this.resolveLifecycle = null;
@@ -6688,10 +6911,10 @@ var ProjectRunner = class {
6688
6911
  capabilities: ["task", "pm", "code-review", "audit"]
6689
6912
  });
6690
6913
  this.connection.emitStatus(this.activeAgents.size > 0 ? "busy" : "idle");
6691
- logger6.info("Re-registered after reconnect");
6914
+ logger7.info("Re-registered after reconnect");
6692
6915
  } catch (error) {
6693
6916
  const msg = error instanceof Error ? error.message : String(error);
6694
- logger6.error("Failed to re-register after reconnect", { error: msg });
6917
+ logger7.error("Failed to re-register after reconnect", { error: msg });
6695
6918
  }
6696
6919
  }
6697
6920
  // ── Tag audit ──────────────────────────────────────────────────────────
@@ -6702,7 +6925,7 @@ var ProjectRunner = class {
6702
6925
  await handleTagAudit(request, this.connection, this.projectDir);
6703
6926
  } catch (error) {
6704
6927
  const msg = error instanceof Error ? error.message : String(error);
6705
- logger6.error("Tag audit failed", { error: msg, requestId: request.requestId });
6928
+ logger7.error("Tag audit failed", { error: msg, requestId: request.requestId });
6706
6929
  try {
6707
6930
  await this.connection.call("reportTagAuditResult", {
6708
6931
  projectId: this.connection.projectId,
@@ -6721,15 +6944,15 @@ var ProjectRunner = class {
6721
6944
  async killAgent(agent, taskId) {
6722
6945
  const shortId = taskId.slice(0, 8);
6723
6946
  if (agent.process.exitCode !== null) {
6724
- logger6.info("Agent process already exited", { taskId: shortId });
6947
+ logger7.info("Agent process already exited", { taskId: shortId });
6725
6948
  return;
6726
6949
  }
6727
- logger6.info("Killing agent process", { taskId: shortId });
6950
+ logger7.info("Killing agent process", { taskId: shortId });
6728
6951
  agent.process.kill("SIGTERM");
6729
6952
  await new Promise((resolve2) => {
6730
6953
  const timer = setTimeout(() => {
6731
6954
  if (agent.process.exitCode === null) {
6732
- 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 });
6733
6956
  agent.process.kill("SIGKILL");
6734
6957
  }
6735
6958
  resolve2();
@@ -6748,15 +6971,15 @@ var ProjectRunner = class {
6748
6971
  const existing = this.activeAgents.get(agentKey);
6749
6972
  if (existing) {
6750
6973
  if (existing.process.exitCode === null) {
6751
- logger6.info("Re-assignment received, killing existing agent", { taskId: shortId });
6974
+ logger7.info("Re-assignment received, killing existing agent", { taskId: shortId });
6752
6975
  await this.killAgent(existing, taskId);
6753
6976
  } else {
6754
- 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 });
6755
6978
  }
6756
6979
  this.activeAgents.delete(agentKey);
6757
6980
  }
6758
6981
  if (this.activeAgents.size >= MAX_CONCURRENT) {
6759
- logger6.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
6982
+ logger7.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
6760
6983
  this.connection.emitTaskStopped(taskId, "max_concurrent_reached");
6761
6984
  return;
6762
6985
  }
@@ -6764,9 +6987,12 @@ var ProjectRunner = class {
6764
6987
  try {
6765
6988
  execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
6766
6989
  } catch {
6767
- logger6.warn("Git fetch failed", { taskId: shortId });
6990
+ logger7.warn("Git fetch failed", { taskId: shortId });
6768
6991
  }
6769
6992
  const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
6993
+ if (assignment.devBranch) {
6994
+ syncWithBaseBranch(workDir, assignment.devBranch);
6995
+ }
6770
6996
  const child = spawnChildAgent(assignment, workDir);
6771
6997
  this.activeAgents.set(agentKey, {
6772
6998
  process: child,
@@ -6775,12 +7001,12 @@ var ProjectRunner = class {
6775
7001
  usesWorktree
6776
7002
  });
6777
7003
  this.connection.emitTaskStarted(taskId);
6778
- logger6.info("Started task", { taskId: shortId, mode, workDir });
7004
+ logger7.info("Started task", { taskId: shortId, mode, workDir });
6779
7005
  child.on("exit", (code) => {
6780
7006
  this.activeAgents.delete(agentKey);
6781
7007
  const reason = code === 0 ? "completed" : `exited with code ${code}`;
6782
7008
  this.connection.emitTaskStopped(taskId, reason);
6783
- logger6.info("Task exited", { taskId: shortId, reason });
7009
+ logger7.info("Task exited", { taskId: shortId, reason });
6784
7010
  if (code === 0 && usesWorktree) {
6785
7011
  try {
6786
7012
  removeWorktree(this.projectDir, taskId);
@@ -6790,7 +7016,7 @@ var ProjectRunner = class {
6790
7016
  });
6791
7017
  } catch (error) {
6792
7018
  const msg = error instanceof Error ? error.message : "Unknown";
6793
- logger6.error("Failed to start task", { taskId: shortId, error: msg });
7019
+ logger7.error("Failed to start task", { taskId: shortId, error: msg });
6794
7020
  this.connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
6795
7021
  }
6796
7022
  }
@@ -6798,7 +7024,7 @@ var ProjectRunner = class {
6798
7024
  const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
6799
7025
  const agent = this.activeAgents.get(agentKey);
6800
7026
  if (!agent) return;
6801
- logger6.info("Stopping task", { taskId: taskId.slice(0, 8) });
7027
+ logger7.info("Stopping task", { taskId: taskId.slice(0, 8) });
6802
7028
  void this.killAgent(agent, taskId).then(() => {
6803
7029
  if (agent.usesWorktree) {
6804
7030
  try {
@@ -6815,30 +7041,30 @@ var ProjectRunner = class {
6815
7041
  const currentBranch = this.getCurrentBranch();
6816
7042
  if (currentBranch === workspaceBranch) return;
6817
7043
  if (hasUncommittedChanges(this.projectDir)) {
6818
- logger6.warn("Uncommitted changes, skipping workspace branch checkout");
7044
+ logger7.warn("Uncommitted changes, skipping workspace branch checkout");
6819
7045
  return;
6820
7046
  }
6821
7047
  try {
6822
7048
  execSync5(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
6823
7049
  execSync5(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
6824
- logger6.info("Checked out workspace branch", { workspaceBranch });
7050
+ logger7.info("Checked out workspace branch", { workspaceBranch });
6825
7051
  } catch {
6826
- logger6.warn("Failed to checkout workspace branch", { workspaceBranch });
7052
+ logger7.warn("Failed to checkout workspace branch", { workspaceBranch });
6827
7053
  }
6828
7054
  }
6829
7055
  async executeSetupCommand() {
6830
7056
  const cmd = process.env.CONVEYOR_SETUP_COMMAND;
6831
7057
  if (!cmd) return;
6832
- logger6.info("Running setup command", { command: cmd });
7058
+ logger7.info("Running setup command", { command: cmd });
6833
7059
  try {
6834
7060
  await runSetupCommand(cmd, this.projectDir, (stream, data) => {
6835
7061
  this.connection.sendEvent({ type: "setup_output", stream, data });
6836
7062
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
6837
7063
  });
6838
- logger6.info("Setup command completed");
7064
+ logger7.info("Setup command completed");
6839
7065
  } catch (error) {
6840
7066
  const msg = error instanceof Error ? error.message : "Setup command failed";
6841
- logger6.error("Setup command failed", { error: msg });
7067
+ logger7.error("Setup command failed", { error: msg });
6842
7068
  this.connection.sendEvent({ type: "setup_error", message: msg });
6843
7069
  throw error;
6844
7070
  }
@@ -6846,7 +7072,7 @@ var ProjectRunner = class {
6846
7072
  executeStartCommand() {
6847
7073
  const cmd = process.env.CONVEYOR_START_COMMAND;
6848
7074
  if (!cmd) return;
6849
- logger6.info("Running start command", { command: cmd });
7075
+ logger7.info("Running start command", { command: cmd });
6850
7076
  const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
6851
7077
  this.connection.sendEvent({ type: "start_command_output", stream, data });
6852
7078
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
@@ -6856,13 +7082,13 @@ var ProjectRunner = class {
6856
7082
  child.on("exit", (code, signal) => {
6857
7083
  this.startCommandRunning = false;
6858
7084
  this.startCommandChild = null;
6859
- logger6.info("Start command exited", { code, signal });
7085
+ logger7.info("Start command exited", { code, signal });
6860
7086
  this.connection.sendEvent({ type: "start_command_exited", code, signal });
6861
7087
  });
6862
7088
  child.on("error", (err) => {
6863
7089
  this.startCommandRunning = false;
6864
7090
  this.startCommandChild = null;
6865
- logger6.error("Start command error", { error: err.message });
7091
+ logger7.error("Start command error", { error: err.message });
6866
7092
  });
6867
7093
  }
6868
7094
  async killStartCommand() {
@@ -6905,7 +7131,7 @@ var ProjectRunner = class {
6905
7131
  try {
6906
7132
  execSync5("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
6907
7133
  } catch {
6908
- logger6.warn("Git fetch failed during branch switch");
7134
+ logger7.warn("Git fetch failed during branch switch");
6909
7135
  }
6910
7136
  detachWorktreeBranch(this.projectDir, data.branch);
6911
7137
  try {
@@ -6918,7 +7144,7 @@ var ProjectRunner = class {
6918
7144
  try {
6919
7145
  execSync5(`git pull origin ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
6920
7146
  } catch {
6921
- logger6.warn("Git pull failed during branch switch");
7147
+ logger7.warn("Git pull failed during branch switch");
6922
7148
  }
6923
7149
  if (data.syncAfter !== false) {
6924
7150
  await this.handleSyncEnvironment();
@@ -6927,7 +7153,7 @@ var ProjectRunner = class {
6927
7153
  callback({ ok: true });
6928
7154
  } catch (err) {
6929
7155
  const msg = err instanceof Error ? err.message : "Branch switch failed";
6930
- logger6.error("Branch switch failed", { error: msg });
7156
+ logger7.error("Branch switch failed", { error: msg });
6931
7157
  callback({ ok: false, error: msg });
6932
7158
  }
6933
7159
  }
@@ -6942,14 +7168,14 @@ var ProjectRunner = class {
6942
7168
  });
6943
7169
  } catch (err) {
6944
7170
  const msg = err instanceof Error ? err.message : "Sync command failed";
6945
- logger6.error("Branch switch sync command failed", { error: msg });
7171
+ logger7.error("Branch switch sync command failed", { error: msg });
6946
7172
  }
6947
7173
  }
6948
7174
  this.executeStartCommand();
6949
7175
  callback?.({ ok: true });
6950
7176
  } catch (err) {
6951
7177
  const msg = err instanceof Error ? err.message : "Sync failed";
6952
- logger6.error("Environment sync failed", { error: msg });
7178
+ logger7.error("Environment sync failed", { error: msg });
6953
7179
  callback?.({ ok: false, error: msg });
6954
7180
  }
6955
7181
  }
@@ -6974,7 +7200,7 @@ var ProjectRunner = class {
6974
7200
  setupComplete: this.setupComplete,
6975
7201
  startCommandRunning: this.startCommandRunning
6976
7202
  });
6977
- logger6.info("Commit sync complete", { steps: stepsRun.join(", ") });
7203
+ logger7.info("Commit sync complete", { steps: stepsRun.join(", ") });
6978
7204
  }
6979
7205
  async smartSync(previousSha, newSha, branch) {
6980
7206
  if (hasUncommittedChanges(this.projectDir)) {
@@ -6993,7 +7219,7 @@ var ProjectRunner = class {
6993
7219
  });
6994
7220
  } catch (err) {
6995
7221
  const msg = err instanceof Error ? err.message : "Pull failed";
6996
- logger6.error("Git pull failed during commit sync", { error: msg });
7222
+ logger7.error("Git pull failed during commit sync", { error: msg });
6997
7223
  this.executeStartCommand();
6998
7224
  return ["error:pull"];
6999
7225
  }
@@ -7020,7 +7246,7 @@ var ProjectRunner = class {
7020
7246
  stepsRun.push("branchSwitchCommand");
7021
7247
  } catch (err) {
7022
7248
  const msg = err instanceof Error ? err.message : "Sync command failed";
7023
- logger6.error("Branch switch command failed", { error: msg });
7249
+ logger7.error("Branch switch command failed", { error: msg });
7024
7250
  }
7025
7251
  } else if (!cmd) {
7026
7252
  this.runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun);
@@ -7033,7 +7259,7 @@ var ProjectRunner = class {
7033
7259
  stepsRun.push("install");
7034
7260
  } catch (err) {
7035
7261
  const msg = err instanceof Error ? err.message : "Install failed";
7036
- logger6.error("bun install failed", { error: msg });
7262
+ logger7.error("bun install failed", { error: msg });
7037
7263
  }
7038
7264
  }
7039
7265
  if (needsPrisma) {
@@ -7047,7 +7273,7 @@ var ProjectRunner = class {
7047
7273
  stepsRun.push("prisma");
7048
7274
  } catch (err) {
7049
7275
  const msg = err instanceof Error ? err.message : "Prisma sync failed";
7050
- logger6.error("Prisma sync failed", { error: msg });
7276
+ logger7.error("Prisma sync failed", { error: msg });
7051
7277
  }
7052
7278
  }
7053
7279
  }
@@ -7063,6 +7289,33 @@ var ProjectRunner = class {
7063
7289
  }
7064
7290
  };
7065
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
+
7066
7319
  export {
7067
7320
  AgentConnection,
7068
7321
  ModeController,
@@ -7085,6 +7338,8 @@ export {
7085
7338
  ensureWorktree,
7086
7339
  detachWorktreeBranch,
7087
7340
  removeWorktree,
7088
- ProjectRunner
7341
+ ProjectRunner,
7342
+ loadForwardPorts,
7343
+ loadConveyorConfig
7089
7344
  };
7090
- //# sourceMappingURL=chunk-L7ZH423N.js.map
7345
+ //# sourceMappingURL=chunk-FCRGYU2M.js.map