@rallycry/conveyor-agent 7.2.15 → 7.2.17

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.
@@ -1,8 +1,9 @@
1
1
  import {
2
+ formatCliEvent,
2
3
  imageBlock,
3
4
  isImageMimeType,
4
5
  textResult
5
- } from "./chunk-C5YAMQJ2.js";
6
+ } from "./chunk-FDWECEDJ.js";
6
7
  import {
7
8
  createHarness,
8
9
  createServiceLogger,
@@ -2230,23 +2231,8 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
2230
2231
  return [...body, ...instructions].join("\n");
2231
2232
  }
2232
2233
 
2233
- // src/tools/common-tools.ts
2234
+ // src/tools/task-context-tools.ts
2234
2235
  import { z } from "zod";
2235
- var cliEventFormatters = {
2236
- thinking: (e) => e.message ?? "",
2237
- tool_use: (e) => `${e.tool}: ${e.input?.slice(0, 1e3) ?? ""}`,
2238
- tool_result: (e) => `${e.tool} \u2192 ${e.output?.slice(0, 500) ?? ""}${e.isError ? " [ERROR]" : ""}`,
2239
- message: (e) => e.content ?? "",
2240
- error: (e) => `ERROR: ${e.message ?? ""}`,
2241
- completed: (e) => `Completed: ${e.summary ?? ""} (cost: $${e.costUsd ?? "?"}, duration: ${e.durationMs ?? "?"}ms)`,
2242
- setup_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
2243
- start_command_output: (e) => `[${e.stream ?? "stdout"}] ${e.data ?? ""}`,
2244
- turn_end: (e) => `Turn complete (${e.toolCalls?.length ?? 0} tool calls)`
2245
- };
2246
- function formatCliEvent(e) {
2247
- const formatter = cliEventFormatters[e.type];
2248
- return formatter ? formatter(e) : JSON.stringify(e);
2249
- }
2250
2236
  function buildReadTaskChatTool(connection) {
2251
2237
  return defineTool(
2252
2238
  "read_task_chat",
@@ -2408,13 +2394,26 @@ function buildGetTaskFileTool(connection) {
2408
2394
  { annotations: { readOnlyHint: true } }
2409
2395
  );
2410
2396
  }
2397
+ function buildTaskContextTools(connection) {
2398
+ return [
2399
+ buildReadTaskChatTool(connection),
2400
+ buildGetTaskPlanTool(connection),
2401
+ buildGetTaskTool(connection),
2402
+ buildGetTaskCliTool(connection),
2403
+ buildListTaskFilesTool(connection),
2404
+ buildGetTaskFileTool(connection)
2405
+ ];
2406
+ }
2407
+
2408
+ // src/tools/incident-tools.ts
2409
+ import { z as z2 } from "zod";
2411
2410
  function buildSearchIncidentsTool(connection) {
2412
2411
  return defineTool(
2413
2412
  "search_incidents",
2414
2413
  "Search incidents in the current project. Optionally filter by status (Open, Acknowledged, Investigating, Resolved, Closed) or source.",
2415
2414
  {
2416
- status: z.string().optional().describe("Filter by incident status"),
2417
- source: z.string().optional().describe("Filter by source (e.g., 'conveyor', 'agent', 'build')")
2415
+ status: z2.string().optional().describe("Filter by incident status"),
2416
+ source: z2.string().optional().describe("Filter by source (e.g., 'conveyor', 'agent', 'build')")
2418
2417
  },
2419
2418
  async ({ status, source }) => {
2420
2419
  try {
@@ -2438,7 +2437,7 @@ function buildGetTaskIncidentsTool(connection) {
2438
2437
  "get_task_incidents",
2439
2438
  "Get incidents linked to the current task. Returns a summary of each incident with available sections. Use get_incident_detail to drill into specific sections (messages, logs, task details).",
2440
2439
  {
2441
- task_id: z.string().optional().describe("Task ID (defaults to current task)")
2440
+ task_id: z2.string().optional().describe("Task ID (defaults to current task)")
2442
2441
  },
2443
2442
  async ({ task_id }) => {
2444
2443
  try {
@@ -2461,11 +2460,11 @@ function buildGetIncidentDetailTool(connection) {
2461
2460
  "get_incident_detail",
2462
2461
  "Get detailed incident data by section. Use after get_task_incidents to drill into specific sections of an incident. Supports pagination and text search within sections.",
2463
2462
  {
2464
- incident_id: z.string().describe("The incident ID to get details for"),
2465
- section: z.enum(["all", "user_report", "task_details", "subtasks", "messages", "logs"]).optional().default("all").describe("Which section to retrieve (default: all)"),
2466
- search: z.string().optional().describe("Search for specific text within the section (case-insensitive)"),
2467
- offset: z.number().optional().describe("Line offset for pagination (default 0)"),
2468
- limit: z.number().optional().describe("Max lines to return (default 100, max 500)")
2463
+ incident_id: z2.string().describe("The incident ID to get details for"),
2464
+ section: z2.enum(["all", "user_report", "task_details", "subtasks", "messages", "logs"]).optional().default("all").describe("Which section to retrieve (default: all)"),
2465
+ search: z2.string().optional().describe("Search for specific text within the section (case-insensitive)"),
2466
+ offset: z2.number().optional().describe("Line offset for pagination (default 0)"),
2467
+ limit: z2.number().optional().describe("Max lines to return (default 100, max 500)")
2469
2468
  },
2470
2469
  async ({ incident_id, section, search, offset, limit }) => {
2471
2470
  try {
@@ -2506,12 +2505,12 @@ function buildQueryGcpLogsTool(connection) {
2506
2505
  "query_gcp_logs",
2507
2506
  "Query GCP Cloud Logging for the current project. Returns log entries matching the given filters. Use severity to filter by minimum log level. The project's GCP credentials are used automatically.",
2508
2507
  {
2509
- filter: z.string().optional().describe(
2508
+ filter: z2.string().optional().describe(
2510
2509
  `Cloud Logging filter expression (e.g., 'resource.type="gce_instance"'). Max 1000 chars.`
2511
2510
  ),
2512
- start_time: z.string().optional().describe("Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z')"),
2513
- end_time: z.string().optional().describe("End time in ISO 8601 format (e.g., '2024-01-02T00:00:00Z')"),
2514
- severity: z.enum([
2511
+ start_time: z2.string().optional().describe("Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z')"),
2512
+ end_time: z2.string().optional().describe("End time in ISO 8601 format (e.g., '2024-01-02T00:00:00Z')"),
2513
+ severity: z2.enum([
2515
2514
  "DEFAULT",
2516
2515
  "DEBUG",
2517
2516
  "INFO",
@@ -2522,7 +2521,7 @@ function buildQueryGcpLogsTool(connection) {
2522
2521
  "ALERT",
2523
2522
  "EMERGENCY"
2524
2523
  ]).optional().describe("Minimum severity level to filter by (default: all levels)"),
2525
- page_size: z.number().optional().describe("Number of log entries to return (default 100, max 500)")
2524
+ page_size: z2.number().optional().describe("Number of log entries to return (default 100, max 500)")
2526
2525
  },
2527
2526
  async ({ filter, start_time, end_time, severity, page_size }) => {
2528
2527
  try {
@@ -2551,6 +2550,17 @@ ${formatted}`);
2551
2550
  { annotations: { readOnlyHint: true } }
2552
2551
  );
2553
2552
  }
2553
+ function buildIncidentTools(connection) {
2554
+ return [
2555
+ buildSearchIncidentsTool(connection),
2556
+ buildGetTaskIncidentsTool(connection),
2557
+ buildGetIncidentDetailTool(connection),
2558
+ buildQueryGcpLogsTool(connection)
2559
+ ];
2560
+ }
2561
+
2562
+ // src/tools/dependency-suggestion-tools.ts
2563
+ import { z as z3 } from "zod";
2554
2564
  function buildGetDependenciesTool(connection) {
2555
2565
  return defineTool(
2556
2566
  "get_dependencies",
@@ -2576,8 +2586,8 @@ function buildGetSuggestionsTool(connection) {
2576
2586
  "get_suggestions",
2577
2587
  "List project suggestions sorted by vote score. Use this to see what the team thinks is important.",
2578
2588
  {
2579
- status: z.string().optional().describe("Filter by status: Open, Accepted, Rejected, Implemented"),
2580
- limit: z.number().optional().describe("Max results (default 20)")
2589
+ status: z3.string().optional().describe("Filter by status: Open, Accepted, Rejected, Implemented"),
2590
+ limit: z3.number().optional().describe("Max results (default 20)")
2581
2591
  },
2582
2592
  async ({ status: _status, limit: _limit }) => {
2583
2593
  try {
@@ -2597,13 +2607,16 @@ function buildGetSuggestionsTool(connection) {
2597
2607
  { annotations: { readOnlyHint: true } }
2598
2608
  );
2599
2609
  }
2610
+
2611
+ // src/tools/mutation-tools.ts
2612
+ import { z as z4 } from "zod";
2600
2613
  function buildPostToChatTool(connection) {
2601
2614
  return defineTool(
2602
2615
  "post_to_chat",
2603
2616
  "Post a message to a task chat. Your normal replies already appear in chat \u2014 only use this for explicit out-of-band updates or posting to a child task's chat.",
2604
2617
  {
2605
- message: z.string().describe("The message to post to the team"),
2606
- task_id: z.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
2618
+ message: z4.string().describe("The message to post to the team"),
2619
+ task_id: z4.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
2607
2620
  },
2608
2621
  async ({ message, task_id }) => {
2609
2622
  try {
@@ -2630,8 +2643,8 @@ function buildForceUpdateTaskStatusTool(connection) {
2630
2643
  "force_update_task_status",
2631
2644
  "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.",
2632
2645
  {
2633
- status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2634
- task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
2646
+ status: z4.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2647
+ task_id: z4.string().optional().describe("Child task ID to update. Omit to update the current task.")
2635
2648
  },
2636
2649
  async ({ status, task_id }) => {
2637
2650
  try {
@@ -2662,15 +2675,15 @@ function buildCreatePullRequestTool(connection, config) {
2662
2675
  "create_pull_request",
2663
2676
  "Create a GitHub pull request for this task. Automatically stages uncommitted changes, commits them, and pushes before creating the PR. Use this instead of gh CLI or git commands to create PRs.",
2664
2677
  {
2665
- title: z.string().describe("The PR title"),
2666
- body: z.string().describe("The PR description/body in markdown"),
2667
- branch: z.string().optional().describe(
2678
+ title: z4.string().describe("The PR title"),
2679
+ body: z4.string().describe("The PR description/body in markdown"),
2680
+ branch: z4.string().optional().describe(
2668
2681
  "The head branch name for the PR. If the task doesn't have a branch set, this will be used. Defaults to the task's existing branch."
2669
2682
  ),
2670
- baseBranch: z.string().optional().describe(
2683
+ baseBranch: z4.string().optional().describe(
2671
2684
  "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
2672
2685
  ),
2673
- commitMessage: z.string().optional().describe(
2686
+ commitMessage: z4.string().optional().describe(
2674
2687
  "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
2675
2688
  )
2676
2689
  },
@@ -2748,7 +2761,7 @@ function buildAddDependencyTool(connection) {
2748
2761
  "add_dependency",
2749
2762
  "Add a dependency \u2014 this task cannot start until the specified task is merged to dev",
2750
2763
  {
2751
- depends_on_slug_or_id: z.string().describe("Slug or ID of the task this task depends on")
2764
+ depends_on_slug_or_id: z4.string().describe("Slug or ID of the task this task depends on")
2752
2765
  },
2753
2766
  async ({ depends_on_slug_or_id }) => {
2754
2767
  try {
@@ -2770,7 +2783,7 @@ function buildRemoveDependencyTool(connection) {
2770
2783
  "remove_dependency",
2771
2784
  "Remove a dependency from this task",
2772
2785
  {
2773
- depends_on_slug_or_id: z.string().describe("Slug or ID of the task to remove as dependency")
2786
+ depends_on_slug_or_id: z4.string().describe("Slug or ID of the task to remove as dependency")
2774
2787
  },
2775
2788
  async ({ depends_on_slug_or_id }) => {
2776
2789
  try {
@@ -2792,10 +2805,10 @@ function buildCreateFollowUpTaskTool(connection) {
2792
2805
  "create_follow_up_task",
2793
2806
  "Create a follow-up task in this project that depends on the current task. Use for out-of-scope work, v1.1 features, or cleanup that should happen after this task merges.",
2794
2807
  {
2795
- title: z.string().describe("Follow-up task title"),
2796
- description: z.string().optional().describe("Brief description of the follow-up work"),
2797
- plan: z.string().optional().describe("Implementation plan if known"),
2798
- story_point_value: z.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
2808
+ title: z4.string().describe("Follow-up task title"),
2809
+ description: z4.string().optional().describe("Brief description of the follow-up work"),
2810
+ plan: z4.string().optional().describe("Implementation plan if known"),
2811
+ story_point_value: z4.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
2799
2812
  },
2800
2813
  async ({ title, description, plan, story_point_value }) => {
2801
2814
  try {
@@ -2822,11 +2835,11 @@ function buildCreateSuggestionTool(connection) {
2822
2835
  "create_suggestion",
2823
2836
  "Suggest a feature, improvement, or idea for the project. If you want to recommend something \u2014 a document, a rule, a feature, a task, an optimization \u2014 use this tool. If a similar suggestion already exists, your vote will be added to it instead of creating a duplicate.",
2824
2837
  {
2825
- title: z.string().describe("Short title for the suggestion"),
2826
- description: z.string().optional().describe(
2838
+ title: z4.string().describe("Short title for the suggestion"),
2839
+ description: z4.string().optional().describe(
2827
2840
  "1-2 sentence description of what should change and why. Keep concise and project-focused."
2828
2841
  ),
2829
- tag_names: z.array(z.string()).optional().describe("Tag names to categorize the suggestion")
2842
+ tag_names: z4.array(z4.string()).optional().describe("Tag names to categorize the suggestion")
2830
2843
  },
2831
2844
  async ({ title, description, tag_names }) => {
2832
2845
  try {
@@ -2855,8 +2868,8 @@ function buildVoteSuggestionTool(connection) {
2855
2868
  "vote_suggestion",
2856
2869
  "Vote on a project suggestion. Use +1 to upvote or -1 to downvote.",
2857
2870
  {
2858
- suggestion_id: z.string().describe("The suggestion ID to vote on"),
2859
- value: z.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
2871
+ suggestion_id: z4.string().describe("The suggestion ID to vote on"),
2872
+ value: z4.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
2860
2873
  },
2861
2874
  async ({ suggestion_id, value }) => {
2862
2875
  try {
@@ -2874,41 +2887,39 @@ function buildVoteSuggestionTool(connection) {
2874
2887
  }
2875
2888
  );
2876
2889
  }
2877
- function buildCommonTools(connection, config) {
2878
- const tools = [
2879
- buildReadTaskChatTool(connection),
2890
+ function buildMutationTools(connection, config) {
2891
+ return [
2880
2892
  buildPostToChatTool(connection),
2881
- buildGetTaskPlanTool(connection),
2882
- buildGetTaskTool(connection),
2883
- buildGetTaskCliTool(connection),
2884
- buildListTaskFilesTool(connection),
2885
- buildGetTaskFileTool(connection),
2886
- buildSearchIncidentsTool(connection),
2887
- buildGetTaskIncidentsTool(connection),
2888
- buildGetIncidentDetailTool(connection),
2889
- buildQueryGcpLogsTool(connection),
2890
2893
  buildCreatePullRequestTool(connection, config),
2891
2894
  buildAddDependencyTool(connection),
2892
2895
  buildRemoveDependencyTool(connection),
2893
- buildGetDependenciesTool(connection),
2894
2896
  buildCreateFollowUpTaskTool(connection),
2895
2897
  buildCreateSuggestionTool(connection),
2896
- buildVoteSuggestionTool(connection),
2897
- buildGetSuggestionsTool(connection)
2898
+ buildVoteSuggestionTool(connection)
2899
+ ];
2900
+ }
2901
+
2902
+ // src/tools/common-tools.ts
2903
+ function buildCommonTools(connection, config) {
2904
+ return [
2905
+ ...buildTaskContextTools(connection),
2906
+ ...buildIncidentTools(connection),
2907
+ buildGetDependenciesTool(connection),
2908
+ buildGetSuggestionsTool(connection),
2909
+ ...buildMutationTools(connection, config)
2898
2910
  ];
2899
- return tools;
2900
2911
  }
2901
2912
 
2902
2913
  // src/tools/pm-tools.ts
2903
- import { z as z2 } from "zod";
2914
+ import { z as z5 } from "zod";
2904
2915
  var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2905
2916
  function buildUpdateTaskTool(connection) {
2906
2917
  return defineTool(
2907
2918
  "update_task",
2908
2919
  "Save the finalized task plan and/or description",
2909
2920
  {
2910
- plan: z2.string().optional().describe("The task plan in markdown"),
2911
- description: z2.string().optional().describe("Updated task description")
2921
+ plan: z5.string().optional().describe("The task plan in markdown"),
2922
+ description: z5.string().optional().describe("Updated task description")
2912
2923
  },
2913
2924
  async ({ plan, description }) => {
2914
2925
  try {
@@ -2929,11 +2940,11 @@ function buildCreateSubtaskTool(connection) {
2929
2940
  "create_subtask",
2930
2941
  "Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
2931
2942
  {
2932
- title: z2.string().describe("Subtask title"),
2933
- description: z2.string().optional().describe("Brief description"),
2934
- plan: z2.string().optional().describe("Implementation plan in markdown"),
2935
- ordinal: z2.number().optional().describe("Step/order number (0-based)"),
2936
- storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2943
+ title: z5.string().describe("Subtask title"),
2944
+ description: z5.string().optional().describe("Brief description"),
2945
+ plan: z5.string().optional().describe("Implementation plan in markdown"),
2946
+ ordinal: z5.number().optional().describe("Step/order number (0-based)"),
2947
+ storyPointValue: z5.number().optional().describe(SP_DESCRIPTION)
2937
2948
  },
2938
2949
  async ({ title, description, plan, ordinal, storyPointValue }) => {
2939
2950
  try {
@@ -2959,12 +2970,12 @@ function buildUpdateSubtaskTool(connection) {
2959
2970
  "update_subtask",
2960
2971
  "Update an existing subtask's fields",
2961
2972
  {
2962
- subtaskId: z2.string().describe("The subtask ID to update"),
2963
- title: z2.string().optional(),
2964
- description: z2.string().optional(),
2965
- plan: z2.string().optional(),
2966
- ordinal: z2.number().optional(),
2967
- storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2973
+ subtaskId: z5.string().describe("The subtask ID to update"),
2974
+ title: z5.string().optional(),
2975
+ description: z5.string().optional(),
2976
+ plan: z5.string().optional(),
2977
+ ordinal: z5.number().optional(),
2978
+ storyPointValue: z5.number().optional().describe(SP_DESCRIPTION)
2968
2979
  },
2969
2980
  async ({ subtaskId, title, description, plan, storyPointValue }) => {
2970
2981
  try {
@@ -2987,7 +2998,7 @@ function buildDeleteSubtaskTool(connection) {
2987
2998
  return defineTool(
2988
2999
  "delete_subtask",
2989
3000
  "Delete a subtask",
2990
- { subtaskId: z2.string().describe("The subtask ID to delete") },
3001
+ { subtaskId: z5.string().describe("The subtask ID to delete") },
2991
3002
  async ({ subtaskId }) => {
2992
3003
  try {
2993
3004
  await connection.call("deleteSubtask", {
@@ -3025,7 +3036,7 @@ function buildPackTools(connection) {
3025
3036
  "start_child_cloud_build",
3026
3037
  "Start a cloud build for a child task. The child must be in Open status with story points and an agent assigned.",
3027
3038
  {
3028
- childTaskId: z2.string().describe("The child task ID to start a cloud build for")
3039
+ childTaskId: z5.string().describe("The child task ID to start a cloud build for")
3029
3040
  },
3030
3041
  async ({ childTaskId }) => {
3031
3042
  try {
@@ -3045,7 +3056,7 @@ function buildPackTools(connection) {
3045
3056
  "stop_child_build",
3046
3057
  "Stop a running cloud build for a child task. Sends a stop signal to the child agent.",
3047
3058
  {
3048
- childTaskId: z2.string().describe("The child task ID whose build should be stopped")
3059
+ childTaskId: z5.string().describe("The child task ID whose build should be stopped")
3049
3060
  },
3050
3061
  async ({ childTaskId }) => {
3051
3062
  try {
@@ -3065,7 +3076,7 @@ function buildPackTools(connection) {
3065
3076
  "approve_and_merge_pr",
3066
3077
  "Approve and merge a child task's pull request. Only succeeds if all CI/CD checks are passing. Returns an error if checks are pending (retry after waiting) or failed (investigate). The child task must be in ReviewPR status.",
3067
3078
  {
3068
- childTaskId: z2.string().describe("The child task ID whose PR should be approved and merged")
3079
+ childTaskId: z5.string().describe("The child task ID whose PR should be approved and merged")
3069
3080
  },
3070
3081
  async ({ childTaskId }) => {
3071
3082
  try {
@@ -3103,7 +3114,7 @@ function buildPmTools(connection, options) {
3103
3114
  }
3104
3115
 
3105
3116
  // src/tools/discovery-tools.ts
3106
- import { z as z3 } from "zod";
3117
+ import { z as z6 } from "zod";
3107
3118
  var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
3108
3119
  function buildDiscoveryTools(connection) {
3109
3120
  return [
@@ -3111,11 +3122,11 @@ function buildDiscoveryTools(connection) {
3111
3122
  "update_task_properties",
3112
3123
  "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
3113
3124
  {
3114
- title: z3.string().optional().describe("The new task title"),
3115
- storyPointValue: z3.number().optional().describe(SP_DESCRIPTION2),
3116
- tagNames: z3.array(z3.string()).optional().describe("Array of tag names to assign"),
3117
- githubPRUrl: z3.string().url().optional().describe("GitHub pull request URL to link to this task"),
3118
- githubBranch: z3.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3125
+ title: z6.string().optional().describe("The new task title"),
3126
+ storyPointValue: z6.number().optional().describe(SP_DESCRIPTION2),
3127
+ tagNames: z6.array(z6.string()).optional().describe("Array of tag names to assign"),
3128
+ githubPRUrl: z6.string().url().optional().describe("GitHub pull request URL to link to this task"),
3129
+ githubBranch: z6.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3119
3130
  },
3120
3131
  async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
3121
3132
  try {
@@ -3146,14 +3157,14 @@ function buildDiscoveryTools(connection) {
3146
3157
  }
3147
3158
 
3148
3159
  // src/tools/code-review-tools.ts
3149
- import { z as z4 } from "zod";
3160
+ import { z as z7 } from "zod";
3150
3161
  function buildCodeReviewTools(connection) {
3151
3162
  return [
3152
3163
  defineTool(
3153
3164
  "approve_code_review",
3154
3165
  "Approve the code review. Use this when the code passes all review criteria and is ready to merge.",
3155
3166
  {
3156
- summary: z4.string().describe("Brief summary of what was reviewed and why it looks good")
3167
+ summary: z7.string().describe("Brief summary of what was reviewed and why it looks good")
3157
3168
  },
3158
3169
  async ({ summary }) => {
3159
3170
  const content = `**Code Review: Approved** :white_check_mark:
@@ -3176,15 +3187,15 @@ ${summary}`;
3176
3187
  "request_code_changes",
3177
3188
  "Request changes during code review. Use this when substantive issues are found that need to be fixed before merge.",
3178
3189
  {
3179
- issues: z4.array(
3180
- z4.object({
3181
- file: z4.string().describe("File path where the issue was found"),
3182
- line: z4.number().optional().describe("Line number (if applicable)"),
3183
- severity: z4.enum(["critical", "major", "minor"]).describe("Issue severity"),
3184
- description: z4.string().describe("What is wrong and how to fix it")
3190
+ issues: z7.array(
3191
+ z7.object({
3192
+ file: z7.string().describe("File path where the issue was found"),
3193
+ line: z7.number().optional().describe("Line number (if applicable)"),
3194
+ severity: z7.enum(["critical", "major", "minor"]).describe("Issue severity"),
3195
+ description: z7.string().describe("What is wrong and how to fix it")
3185
3196
  })
3186
3197
  ).describe("List of issues found during review"),
3187
- summary: z4.string().describe("Brief overall summary of the review findings")
3198
+ summary: z7.string().describe("Brief overall summary of the review findings")
3188
3199
  },
3189
3200
  async ({ issues, summary }) => {
3190
3201
  const issueLines = issues.map((issue) => {
@@ -3214,10 +3225,10 @@ ${issueLines}`;
3214
3225
  }
3215
3226
 
3216
3227
  // src/tools/debug-tools.ts
3217
- import { z as z7 } from "zod";
3228
+ import { z as z10 } from "zod";
3218
3229
 
3219
3230
  // src/tools/telemetry-tools.ts
3220
- import { z as z5 } from "zod";
3231
+ import { z as z8 } from "zod";
3221
3232
 
3222
3233
  // src/debug/telemetry-injector.ts
3223
3234
  var BUFFER_SIZE = 200;
@@ -3612,12 +3623,12 @@ function buildGetTelemetryTool(manager) {
3612
3623
  "debug_get_telemetry",
3613
3624
  "Query structured telemetry events (HTTP requests, database queries, Socket.IO events, errors) captured from the running dev server. Returns filtered, structured data instead of raw logs.",
3614
3625
  {
3615
- type: z5.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3616
- urlPattern: z5.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3617
- minDuration: z5.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3618
- errorOnly: z5.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3619
- since: z5.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3620
- limit: z5.number().optional().describe("Max events to return (default: 20, from most recent)")
3626
+ type: z8.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3627
+ urlPattern: z8.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3628
+ minDuration: z8.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3629
+ errorOnly: z8.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3630
+ since: z8.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3631
+ limit: z8.number().optional().describe("Max events to return (default: 20, from most recent)")
3621
3632
  },
3622
3633
  async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3623
3634
  const clientOrErr = requireDebugClient(manager);
@@ -3687,7 +3698,7 @@ function buildTelemetryTools(manager) {
3687
3698
  }
3688
3699
 
3689
3700
  // src/tools/client-debug-tools.ts
3690
- import { z as z6 } from "zod";
3701
+ import { z as z9 } from "zod";
3691
3702
  function requirePlaywrightClient(manager) {
3692
3703
  if (!manager.isClientDebugMode()) {
3693
3704
  return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
@@ -3707,11 +3718,11 @@ function buildClientBreakpointTools(manager) {
3707
3718
  "debug_set_client_breakpoint",
3708
3719
  "Set a breakpoint in client-side code running in the headless Chromium browser. V8 resolves source maps automatically, so original .tsx/.ts file paths work. Use this for React components, client utilities, and browser-side code.",
3709
3720
  {
3710
- file: z6.string().describe(
3721
+ file: z9.string().describe(
3711
3722
  "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3712
3723
  ),
3713
- line: z6.number().describe("Line number (1-based) in the original source file"),
3714
- condition: z6.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3724
+ line: z9.number().describe("Line number (1-based) in the original source file"),
3725
+ condition: z9.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3715
3726
  },
3716
3727
  async ({ file, line, condition }) => {
3717
3728
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3733,7 +3744,7 @@ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3733
3744
  "debug_remove_client_breakpoint",
3734
3745
  "Remove a previously set client-side breakpoint by its ID.",
3735
3746
  {
3736
- breakpointId: z6.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3747
+ breakpointId: z9.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3737
3748
  },
3738
3749
  async ({ breakpointId }) => {
3739
3750
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3813,8 +3824,8 @@ ${JSON.stringify(queuedHits, null, 2)}`
3813
3824
  "debug_evaluate_client",
3814
3825
  "Evaluate a JavaScript expression in the client-side browser context. When paused at a client breakpoint, evaluates in the paused scope. Can access DOM, window, React internals, etc.",
3815
3826
  {
3816
- expression: z6.string().describe("JavaScript expression to evaluate in the browser context"),
3817
- frameIndex: z6.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3827
+ expression: z9.string().describe("JavaScript expression to evaluate in the browser context"),
3828
+ frameIndex: z9.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3818
3829
  },
3819
3830
  async ({ expression, frameIndex }) => {
3820
3831
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3887,7 +3898,7 @@ function buildClientInteractionTools(manager) {
3887
3898
  "debug_navigate_client",
3888
3899
  "Navigate the headless browser to a specific URL. Use this to reproduce specific flows or visit different pages.",
3889
3900
  {
3890
- url: z6.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3901
+ url: z9.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3891
3902
  },
3892
3903
  async ({ url }) => {
3893
3904
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3904,7 +3915,7 @@ function buildClientInteractionTools(manager) {
3904
3915
  "debug_click_client",
3905
3916
  "Click an element on the page in the headless browser. Use CSS selectors to target elements. Useful for reproducing bugs by interacting with the UI programmatically.",
3906
3917
  {
3907
- selector: z6.string().describe(
3918
+ selector: z9.string().describe(
3908
3919
  "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3909
3920
  )
3910
3921
  },
@@ -3926,8 +3937,8 @@ function buildClientConsoleTool(manager) {
3926
3937
  "debug_get_client_console",
3927
3938
  "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3928
3939
  {
3929
- level: z6.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3930
- limit: z6.number().optional().describe("Maximum number of recent messages to return (default: all)")
3940
+ level: z9.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3941
+ limit: z9.number().optional().describe("Maximum number of recent messages to return (default: all)")
3931
3942
  },
3932
3943
  // oxlint-disable-next-line require-await
3933
3944
  async ({ level, limit }) => {
@@ -3954,8 +3965,8 @@ function buildClientNetworkTool(manager) {
3954
3965
  "debug_get_client_network",
3955
3966
  "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3956
3967
  {
3957
- filter: z6.string().optional().describe("Regex pattern to filter requests by URL"),
3958
- limit: z6.number().optional().describe("Maximum number of recent requests to return (default: all)")
3968
+ filter: z9.string().optional().describe("Regex pattern to filter requests by URL"),
3969
+ limit: z9.number().optional().describe("Maximum number of recent requests to return (default: all)")
3959
3970
  },
3960
3971
  // oxlint-disable-next-line require-await
3961
3972
  async ({ filter, limit }) => {
@@ -3983,7 +3994,7 @@ function buildClientErrorsTool(manager) {
3983
3994
  "debug_get_client_errors",
3984
3995
  "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3985
3996
  {
3986
- limit: z6.number().optional().describe("Maximum number of recent errors to return (default: all)")
3997
+ limit: z9.number().optional().describe("Maximum number of recent errors to return (default: all)")
3987
3998
  },
3988
3999
  // oxlint-disable-next-line require-await
3989
4000
  async ({ limit }) => {
@@ -4077,12 +4088,12 @@ function buildDebugLifecycleTools(manager) {
4077
4088
  "debug_enter_mode",
4078
4089
  "Activate debug mode: restarts the dev server with Node.js --inspect flag and connects the CDP debugger. Optionally launch a headless Chromium browser for client-side debugging. Use serverSide for backend breakpoints, clientSide for frontend breakpoints, or both for full-stack.",
4079
4090
  {
4080
- hypothesis: z7.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
4081
- serverSide: z7.boolean().optional().describe(
4091
+ hypothesis: z10.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
4092
+ serverSide: z10.boolean().optional().describe(
4082
4093
  "Enable server-side Node.js debugging (default: true if clientSide is not set)"
4083
4094
  ),
4084
- clientSide: z7.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
4085
- previewUrl: z7.string().optional().describe(
4095
+ clientSide: z10.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
4096
+ previewUrl: z10.string().optional().describe(
4086
4097
  "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
4087
4098
  )
4088
4099
  },
@@ -4118,9 +4129,9 @@ function buildBreakpointTools(manager) {
4118
4129
  "debug_set_breakpoint",
4119
4130
  "Set a breakpoint at the specified file and line number. Optionally provide a condition expression that must evaluate to true for the breakpoint to pause execution.",
4120
4131
  {
4121
- file: z7.string().describe("Absolute or relative file path to set the breakpoint in"),
4122
- line: z7.number().describe("Line number (1-based) to set the breakpoint on"),
4123
- condition: z7.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4132
+ file: z10.string().describe("Absolute or relative file path to set the breakpoint in"),
4133
+ line: z10.number().describe("Line number (1-based) to set the breakpoint on"),
4134
+ condition: z10.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4124
4135
  },
4125
4136
  async ({ file, line, condition }) => {
4126
4137
  const clientOrErr = requireDebugClient2(manager);
@@ -4142,7 +4153,7 @@ Breakpoint ID: ${breakpointId}`
4142
4153
  "debug_remove_breakpoint",
4143
4154
  "Remove a previously set breakpoint by its ID.",
4144
4155
  {
4145
- breakpointId: z7.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4156
+ breakpointId: z10.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4146
4157
  },
4147
4158
  async ({ breakpointId }) => {
4148
4159
  const clientOrErr = requireDebugClient2(manager);
@@ -4223,8 +4234,8 @@ ${JSON.stringify(queuedHits, null, 2)}`
4223
4234
  "debug_evaluate",
4224
4235
  "Evaluate a JavaScript expression in the current paused scope (or globally if not paused). When paused, use frameIndex to evaluate in a specific call frame.",
4225
4236
  {
4226
- expression: z7.string().describe("The JavaScript expression to evaluate"),
4227
- frameIndex: z7.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4237
+ expression: z10.string().describe("The JavaScript expression to evaluate"),
4238
+ frameIndex: z10.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4228
4239
  },
4229
4240
  async ({ expression, frameIndex }) => {
4230
4241
  const clientOrErr = requireDebugClient2(manager);
@@ -4252,12 +4263,12 @@ function buildProbeManagementTools(manager) {
4252
4263
  "debug_add_probe",
4253
4264
  "Add a debug probe at a specific code location. Captures expression values each time the line executes \u2014 without pausing or modifying source files. Like console.log but better: structured, no diff pollution, auto-cleaned on debug exit.",
4254
4265
  {
4255
- file: z7.string().describe("File path to probe"),
4256
- line: z7.number().describe("Line number (1-based) to probe"),
4257
- expressions: z7.array(z7.string()).describe(
4266
+ file: z10.string().describe("File path to probe"),
4267
+ line: z10.number().describe("Line number (1-based) to probe"),
4268
+ expressions: z10.array(z10.string()).describe(
4258
4269
  'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4259
4270
  ),
4260
- label: z7.string().optional().describe("Optional label for this probe (defaults to file:line)")
4271
+ label: z10.string().optional().describe("Optional label for this probe (defaults to file:line)")
4261
4272
  },
4262
4273
  async ({ file, line, expressions, label }) => {
4263
4274
  const clientOrErr = requireDebugClient2(manager);
@@ -4282,7 +4293,7 @@ Trigger the code path, then use debug_get_probe_results to see captured values.`
4282
4293
  "debug_remove_probe",
4283
4294
  "Remove a previously set debug probe by its ID.",
4284
4295
  {
4285
- probeId: z7.string().describe("The probe ID returned by debug_add_probe")
4296
+ probeId: z10.string().describe("The probe ID returned by debug_add_probe")
4286
4297
  },
4287
4298
  async ({ probeId }) => {
4288
4299
  const clientOrErr = requireDebugClient2(manager);
@@ -4322,9 +4333,9 @@ function buildProbeResultTools(manager) {
4322
4333
  "debug_get_probe_results",
4323
4334
  "Fetch captured probe hit data. Returns expression values from each time a probed line executed.",
4324
4335
  {
4325
- probeId: z7.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4326
- label: z7.string().optional().describe("Filter results by probe label"),
4327
- limit: z7.number().optional().describe("Maximum number of recent hits to return (default: all)")
4336
+ probeId: z10.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4337
+ label: z10.string().optional().describe("Filter results by probe label"),
4338
+ limit: z10.number().optional().describe("Maximum number of recent hits to return (default: all)")
4328
4339
  },
4329
4340
  async ({ probeId, label, limit }) => {
4330
4341
  const clientOrErr = requireDebugClient2(manager);
@@ -4795,10 +4806,20 @@ function stopTypingIfNeeded(host, isTyping) {
4795
4806
  if (isTyping) host.connection.sendTypingStop();
4796
4807
  }
4797
4808
  function flushPendingToolCalls(host, turnToolCalls) {
4798
- if (turnToolCalls.length === 0) return;
4799
- for (let i = 0; i < turnToolCalls.length; i++) {
4800
- if (i < host.pendingToolOutputs.length) {
4801
- turnToolCalls[i].output = host.pendingToolOutputs[i];
4809
+ if (turnToolCalls.length === 0) {
4810
+ host.pendingToolOutputs.length = 0;
4811
+ return;
4812
+ }
4813
+ const outputsByTool = /* @__PURE__ */ new Map();
4814
+ for (const entry of host.pendingToolOutputs) {
4815
+ const list = outputsByTool.get(entry.tool) ?? [];
4816
+ list.push(entry.output);
4817
+ outputsByTool.set(entry.tool, list);
4818
+ }
4819
+ for (const call of turnToolCalls) {
4820
+ const list = outputsByTool.get(call.tool);
4821
+ if (list && list.length > 0) {
4822
+ call.output = list.shift();
4802
4823
  }
4803
4824
  }
4804
4825
  host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
@@ -5120,7 +5141,7 @@ function buildHooks(host) {
5120
5141
  output,
5121
5142
  isError: false
5122
5143
  });
5123
- host.pendingToolOutputs.push(output);
5144
+ host.pendingToolOutputs.push({ tool: input.tool_name, output });
5124
5145
  if (input.tool_name === "mcp__conveyor__create_pull_request") {
5125
5146
  try {
5126
5147
  const props = await host.connection.getTaskProperties();
@@ -6441,67 +6462,8 @@ var ProjectConnection = class {
6441
6462
  }
6442
6463
  };
6443
6464
 
6444
- // src/setup/commands.ts
6445
- import { spawn, execSync as execSync2 } from "child_process";
6446
- function runSetupCommand(cmd, cwd, onOutput) {
6447
- return new Promise((resolve2, reject) => {
6448
- const child = spawn("sh", ["-c", cmd], {
6449
- cwd,
6450
- stdio: ["ignore", "pipe", "pipe"],
6451
- env: { ...process.env }
6452
- });
6453
- child.stdout.on("data", (chunk) => {
6454
- onOutput("stdout", chunk.toString());
6455
- });
6456
- child.stderr.on("data", (chunk) => {
6457
- onOutput("stderr", chunk.toString());
6458
- });
6459
- child.on("close", (code) => {
6460
- if (code === 0) {
6461
- resolve2();
6462
- } else {
6463
- reject(new Error(`Setup command exited with code ${code}`));
6464
- }
6465
- });
6466
- child.on("error", (err) => {
6467
- reject(err);
6468
- });
6469
- });
6470
- }
6471
- var AUTH_TOKEN_TIMEOUT_MS = 3e4;
6472
- function runAuthTokenCommand(cmd, userEmail, cwd) {
6473
- try {
6474
- const output = execSync2(`${cmd} ${JSON.stringify(userEmail)}`, {
6475
- cwd,
6476
- timeout: AUTH_TOKEN_TIMEOUT_MS,
6477
- stdio: ["ignore", "pipe", "ignore"],
6478
- env: { ...process.env }
6479
- });
6480
- const token = output.toString().trim();
6481
- return token || null;
6482
- } catch {
6483
- return null;
6484
- }
6485
- }
6486
- function runStartCommand(cmd, cwd, onOutput) {
6487
- const child = spawn("sh", ["-c", cmd], {
6488
- cwd,
6489
- stdio: ["ignore", "pipe", "pipe"],
6490
- detached: true,
6491
- env: { ...process.env }
6492
- });
6493
- child.stdout.on("data", (chunk) => {
6494
- onOutput("stdout", chunk.toString());
6495
- });
6496
- child.stderr.on("data", (chunk) => {
6497
- onOutput("stderr", chunk.toString());
6498
- });
6499
- child.unref();
6500
- return child;
6501
- }
6502
-
6503
6465
  // src/runner/commit-watcher.ts
6504
- import { execSync as execSync3 } from "child_process";
6466
+ import { execSync as execSync2 } from "child_process";
6505
6467
  var logger5 = createServiceLogger("CommitWatcher");
6506
6468
  var CommitWatcher = class {
6507
6469
  constructor(config, callbacks) {
@@ -6535,7 +6497,7 @@ var CommitWatcher = class {
6535
6497
  this.isSyncing = false;
6536
6498
  }
6537
6499
  getLocalHeadSha() {
6538
- return execSync3("git rev-parse HEAD", {
6500
+ return execSync2("git rev-parse HEAD", {
6539
6501
  cwd: this.config.projectDir,
6540
6502
  stdio: ["ignore", "pipe", "ignore"]
6541
6503
  }).toString().trim();
@@ -6543,12 +6505,12 @@ var CommitWatcher = class {
6543
6505
  poll() {
6544
6506
  if (!this.branch || this.isSyncing) return;
6545
6507
  try {
6546
- execSync3(`git fetch origin ${this.branch} --quiet`, {
6508
+ execSync2(`git fetch origin ${this.branch} --quiet`, {
6547
6509
  cwd: this.config.projectDir,
6548
6510
  stdio: "ignore",
6549
6511
  timeout: 3e4
6550
6512
  });
6551
- const remoteSha = execSync3(`git rev-parse origin/${this.branch}`, {
6513
+ const remoteSha = execSync2(`git rev-parse origin/${this.branch}`, {
6552
6514
  cwd: this.config.projectDir,
6553
6515
  stdio: ["ignore", "pipe", "ignore"]
6554
6516
  }).toString().trim();
@@ -6569,12 +6531,12 @@ var CommitWatcher = class {
6569
6531
  let latestMessage = "";
6570
6532
  let latestAuthor = "";
6571
6533
  try {
6572
- const countOutput = execSync3(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
6534
+ const countOutput = execSync2(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
6573
6535
  cwd: this.config.projectDir,
6574
6536
  stdio: ["ignore", "pipe", "ignore"]
6575
6537
  }).toString().trim();
6576
6538
  commitCount = parseInt(countOutput, 10) || 1;
6577
- const logOutput = execSync3(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
6539
+ const logOutput = execSync2(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
6578
6540
  cwd: this.config.projectDir,
6579
6541
  stdio: ["ignore", "pipe", "ignore"]
6580
6542
  }).toString().trim();
@@ -6609,7 +6571,7 @@ var CommitWatcher = class {
6609
6571
  };
6610
6572
 
6611
6573
  // src/runner/worktree.ts
6612
- import { execSync as execSync4 } from "child_process";
6574
+ import { execSync as execSync3 } from "child_process";
6613
6575
  import { existsSync as existsSync2 } from "fs";
6614
6576
  import { join as join4 } from "path";
6615
6577
  var WORKTREE_DIR = ".worktrees";
@@ -6624,7 +6586,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6624
6586
  return worktreePath;
6625
6587
  }
6626
6588
  try {
6627
- execSync4(`git checkout --detach origin/${branch}`, {
6589
+ execSync3(`git checkout --detach origin/${branch}`, {
6628
6590
  cwd: worktreePath,
6629
6591
  stdio: "ignore"
6630
6592
  });
@@ -6634,7 +6596,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6634
6596
  return worktreePath;
6635
6597
  }
6636
6598
  const ref = branch ? `origin/${branch}` : "HEAD";
6637
- execSync4(`git worktree add --detach "${worktreePath}" ${ref}`, {
6599
+ execSync3(`git worktree add --detach "${worktreePath}" ${ref}`, {
6638
6600
  cwd: projectDir,
6639
6601
  stdio: "ignore"
6640
6602
  });
@@ -6642,7 +6604,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6642
6604
  }
6643
6605
  function detachWorktreeBranch(projectDir, branch) {
6644
6606
  try {
6645
- const output = execSync4("git worktree list --porcelain", {
6607
+ const output = execSync3("git worktree list --porcelain", {
6646
6608
  cwd: projectDir,
6647
6609
  encoding: "utf-8"
6648
6610
  });
@@ -6655,7 +6617,7 @@ function detachWorktreeBranch(projectDir, branch) {
6655
6617
  const worktreePath = worktreeLine.replace("worktree ", "");
6656
6618
  if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
6657
6619
  try {
6658
- execSync4("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
6620
+ execSync3("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
6659
6621
  } catch {
6660
6622
  }
6661
6623
  }
@@ -6666,7 +6628,7 @@ function removeWorktree(projectDir, taskId) {
6666
6628
  const worktreePath = join4(projectDir, WORKTREE_DIR, taskId);
6667
6629
  if (!existsSync2(worktreePath)) return;
6668
6630
  try {
6669
- execSync4(`git worktree remove "${worktreePath}" --force`, {
6631
+ execSync3(`git worktree remove "${worktreePath}" --force`, {
6670
6632
  cwd: projectDir,
6671
6633
  stdio: "ignore"
6672
6634
  });
@@ -6674,14 +6636,67 @@ function removeWorktree(projectDir, taskId) {
6674
6636
  }
6675
6637
  }
6676
6638
 
6677
- // src/runner/project-runner.ts
6678
- import { fork } from "child_process";
6679
- import { execSync as execSync5 } from "child_process";
6680
- import * as path from "path";
6681
- import { fileURLToPath as fileURLToPath2 } from "url";
6639
+ // src/setup/commands.ts
6640
+ import { spawn, execSync as execSync4 } from "child_process";
6641
+ function runSetupCommand(cmd, cwd, onOutput) {
6642
+ return new Promise((resolve2, reject) => {
6643
+ const child = spawn("sh", ["-c", cmd], {
6644
+ cwd,
6645
+ stdio: ["ignore", "pipe", "pipe"],
6646
+ env: { ...process.env }
6647
+ });
6648
+ child.stdout.on("data", (chunk) => {
6649
+ onOutput("stdout", chunk.toString());
6650
+ });
6651
+ child.stderr.on("data", (chunk) => {
6652
+ onOutput("stderr", chunk.toString());
6653
+ });
6654
+ child.on("close", (code) => {
6655
+ if (code === 0) {
6656
+ resolve2();
6657
+ } else {
6658
+ reject(new Error(`Setup command exited with code ${code}`));
6659
+ }
6660
+ });
6661
+ child.on("error", (err) => {
6662
+ reject(err);
6663
+ });
6664
+ });
6665
+ }
6666
+ var AUTH_TOKEN_TIMEOUT_MS = 3e4;
6667
+ function runAuthTokenCommand(cmd, userEmail, cwd) {
6668
+ try {
6669
+ const output = execSync4(`${cmd} ${JSON.stringify(userEmail)}`, {
6670
+ cwd,
6671
+ timeout: AUTH_TOKEN_TIMEOUT_MS,
6672
+ stdio: ["ignore", "pipe", "ignore"],
6673
+ env: { ...process.env }
6674
+ });
6675
+ const token = output.toString().trim();
6676
+ return token || null;
6677
+ } catch {
6678
+ return null;
6679
+ }
6680
+ }
6681
+ function runStartCommand(cmd, cwd, onOutput) {
6682
+ const child = spawn("sh", ["-c", cmd], {
6683
+ cwd,
6684
+ stdio: ["ignore", "pipe", "pipe"],
6685
+ detached: true,
6686
+ env: { ...process.env }
6687
+ });
6688
+ child.stdout.on("data", (chunk) => {
6689
+ onOutput("stdout", chunk.toString());
6690
+ });
6691
+ child.stderr.on("data", (chunk) => {
6692
+ onOutput("stderr", chunk.toString());
6693
+ });
6694
+ child.unref();
6695
+ return child;
6696
+ }
6682
6697
 
6683
6698
  // src/tools/project-tools.ts
6684
- import { z as z8 } from "zod";
6699
+ import { z as z11 } from "zod";
6685
6700
  function buildTaskListTools(connection) {
6686
6701
  const projectId = connection.projectId;
6687
6702
  return [
@@ -6689,9 +6704,9 @@ function buildTaskListTools(connection) {
6689
6704
  "list_tasks",
6690
6705
  "List tasks in the project. Optionally filter by status or assignee.",
6691
6706
  {
6692
- status: z8.string().optional().describe("Filter by task status"),
6693
- assigneeId: z8.string().optional().describe("Filter by assigned user ID"),
6694
- limit: z8.number().optional().describe("Max number of tasks to return (default 50)")
6707
+ status: z11.string().optional().describe("Filter by task status"),
6708
+ assigneeId: z11.string().optional().describe("Filter by assigned user ID"),
6709
+ limit: z11.number().optional().describe("Max number of tasks to return (default 50)")
6695
6710
  },
6696
6711
  async (params) => {
6697
6712
  try {
@@ -6708,7 +6723,7 @@ function buildTaskListTools(connection) {
6708
6723
  defineTool(
6709
6724
  "get_task",
6710
6725
  "Get detailed information about a task including chat messages, child tasks, and session.",
6711
- { task_id: z8.string().describe("The task ID to look up") },
6726
+ { task_id: z11.string().describe("The task ID to look up") },
6712
6727
  async ({ task_id }) => {
6713
6728
  try {
6714
6729
  const task = await connection.call("getProjectTask", { projectId, taskId: task_id });
@@ -6725,10 +6740,10 @@ function buildTaskListTools(connection) {
6725
6740
  "search_tasks",
6726
6741
  "Search tasks by tags, text query, or status filters.",
6727
6742
  {
6728
- tagNames: z8.array(z8.string()).optional().describe("Filter by tag names"),
6729
- searchQuery: z8.string().optional().describe("Text search in title/description"),
6730
- statusFilters: z8.array(z8.string()).optional().describe("Filter by statuses"),
6731
- limit: z8.number().optional().describe("Max results (default 20)")
6743
+ tagNames: z11.array(z11.string()).optional().describe("Filter by tag names"),
6744
+ searchQuery: z11.string().optional().describe("Text search in title/description"),
6745
+ statusFilters: z11.array(z11.string()).optional().describe("Filter by statuses"),
6746
+ limit: z11.number().optional().describe("Max results (default 20)")
6732
6747
  },
6733
6748
  async (params) => {
6734
6749
  try {
@@ -6781,18 +6796,17 @@ function buildProjectInfoTools(connection) {
6781
6796
  )
6782
6797
  ];
6783
6798
  }
6784
- function buildMutationTools(connection) {
6799
+ function buildMutationTools2(connection) {
6785
6800
  const projectId = connection.projectId;
6786
6801
  return [
6787
6802
  defineTool(
6788
6803
  "create_task",
6789
6804
  "Create a new task in the project.",
6790
6805
  {
6791
- title: z8.string().describe("Task title"),
6792
- description: z8.string().optional().describe("Task description"),
6793
- plan: z8.string().optional().describe("Implementation plan in markdown"),
6794
- status: z8.string().optional().describe("Initial status (default: Planning)"),
6795
- isBug: z8.boolean().optional().describe("Whether this is a bug report")
6806
+ title: z11.string().describe("Task title"),
6807
+ description: z11.string().optional().describe("Task description"),
6808
+ plan: z11.string().optional().describe("Implementation plan in markdown"),
6809
+ status: z11.string().optional().describe("Initial status (default: Planning)")
6796
6810
  },
6797
6811
  async (params) => {
6798
6812
  try {
@@ -6809,12 +6823,12 @@ function buildMutationTools(connection) {
6809
6823
  "update_task",
6810
6824
  "Update an existing task's title, description, plan, status, or assignee.",
6811
6825
  {
6812
- task_id: z8.string().describe("The task ID to update"),
6813
- title: z8.string().optional().describe("New title"),
6814
- description: z8.string().optional().describe("New description"),
6815
- plan: z8.string().optional().describe("New plan in markdown"),
6816
- status: z8.string().optional().describe("New status"),
6817
- assignedUserId: z8.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6826
+ task_id: z11.string().describe("The task ID to update"),
6827
+ title: z11.string().optional().describe("New title"),
6828
+ description: z11.string().optional().describe("New description"),
6829
+ plan: z11.string().optional().describe("New plan in markdown"),
6830
+ status: z11.string().optional().describe("New status"),
6831
+ assignedUserId: z11.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6818
6832
  },
6819
6833
  async ({ task_id, ...fields }) => {
6820
6834
  try {
@@ -6833,7 +6847,7 @@ function buildProjectTools(connection) {
6833
6847
  return [
6834
6848
  ...buildTaskListTools(connection),
6835
6849
  ...buildProjectInfoTools(connection),
6836
- ...buildMutationTools(connection)
6850
+ ...buildMutationTools2(connection)
6837
6851
  ];
6838
6852
  }
6839
6853
 
@@ -7028,14 +7042,204 @@ async function handleProjectChatMessage(message, connection, projectDir, session
7028
7042
  }
7029
7043
  }
7030
7044
 
7031
- // src/runner/project-runner.ts
7045
+ // src/runner/project-runner-helpers.ts
7046
+ function parseErrorMessage(error) {
7047
+ return error instanceof Error ? error.message : String(error);
7048
+ }
7049
+
7050
+ // src/runner/project-runner-children.ts
7051
+ import { fork } from "child_process";
7052
+ import { execSync as execSync7 } from "child_process";
7053
+ import * as path from "path";
7054
+ import { fileURLToPath as fileURLToPath2 } from "url";
7055
+
7056
+ // src/runner/project-runner-git.ts
7057
+ import { execSync as execSync6 } from "child_process";
7058
+
7059
+ // src/runner/project-runner-sync.ts
7060
+ import { execSync as execSync5 } from "child_process";
7032
7061
  var logger7 = createServiceLogger("ProjectRunner");
7033
- var __filename = fileURLToPath2(import.meta.url);
7034
- var __dirname = path.dirname(__filename);
7035
- var HEARTBEAT_INTERVAL_MS = 3e4;
7036
- var MAX_CONCURRENT = Math.max(1, parseInt(process.env.CONVEYOR_MAX_CONCURRENT ?? "10", 10) || 10);
7037
- var STOP_TIMEOUT_MS = 3e4;
7038
7062
  var START_CMD_KILL_TIMEOUT_MS = 5e3;
7063
+ async function executeSetupCommand(projectDir, connection) {
7064
+ const cmd = process.env.CONVEYOR_SETUP_COMMAND;
7065
+ if (!cmd) return;
7066
+ logger7.info("Running setup command", { command: cmd });
7067
+ try {
7068
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
7069
+ connection.sendEvent({ type: "setup_output", stream, data });
7070
+ (stream === "stderr" ? process.stderr : process.stdout).write(data);
7071
+ });
7072
+ logger7.info("Setup command completed");
7073
+ } catch (error) {
7074
+ const msg = parseErrorMessage(error);
7075
+ logger7.error("Setup command failed", { error: msg });
7076
+ connection.sendEvent({ type: "setup_error", message: msg });
7077
+ throw error;
7078
+ }
7079
+ }
7080
+ function executeStartCommand(projectDir, state, connection) {
7081
+ const cmd = process.env.CONVEYOR_START_COMMAND;
7082
+ if (!cmd) return;
7083
+ logger7.info("Running start command", { command: cmd });
7084
+ const child = runStartCommand(cmd, projectDir, (stream, data) => {
7085
+ connection.sendEvent({ type: "start_command_output", stream, data });
7086
+ (stream === "stderr" ? process.stderr : process.stdout).write(data);
7087
+ });
7088
+ state.child = child;
7089
+ state.running = true;
7090
+ child.on("exit", (code, signal) => {
7091
+ state.running = false;
7092
+ state.child = null;
7093
+ logger7.info("Start command exited", { code, signal });
7094
+ connection.sendEvent({ type: "start_command_exited", code, signal });
7095
+ });
7096
+ child.on("error", (err) => {
7097
+ state.running = false;
7098
+ state.child = null;
7099
+ logger7.error("Start command error", { error: err.message });
7100
+ });
7101
+ }
7102
+ async function killStartCommand(state) {
7103
+ const child = state.child;
7104
+ if (!child || !state.running) return;
7105
+ try {
7106
+ if (child.pid) process.kill(-child.pid, "SIGTERM");
7107
+ } catch {
7108
+ child.kill("SIGTERM");
7109
+ }
7110
+ await new Promise((resolve2) => {
7111
+ const timer = setTimeout(() => {
7112
+ if (state.running && child.pid) {
7113
+ try {
7114
+ process.kill(-child.pid, "SIGKILL");
7115
+ } catch {
7116
+ child.kill("SIGKILL");
7117
+ }
7118
+ }
7119
+ resolve2();
7120
+ }, START_CMD_KILL_TIMEOUT_MS);
7121
+ child.on("exit", () => {
7122
+ clearTimeout(timer);
7123
+ resolve2();
7124
+ });
7125
+ });
7126
+ state.child = null;
7127
+ state.running = false;
7128
+ }
7129
+ async function restartStartCommand(projectDir, state, connection) {
7130
+ await killStartCommand(state);
7131
+ executeStartCommand(projectDir, state, connection);
7132
+ }
7133
+ async function handleSyncEnvironment(projectDir, branchSwitchCommand, state, connection, callback) {
7134
+ try {
7135
+ await killStartCommand(state);
7136
+ const cmd = branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7137
+ if (cmd) {
7138
+ try {
7139
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
7140
+ connection.sendEvent({ type: "sync_output", stream, data });
7141
+ });
7142
+ } catch (err) {
7143
+ const msg = parseErrorMessage(err);
7144
+ logger7.error("Branch switch sync command failed", { error: msg });
7145
+ }
7146
+ }
7147
+ executeStartCommand(projectDir, state, connection);
7148
+ callback?.({ ok: true });
7149
+ } catch (err) {
7150
+ const msg = parseErrorMessage(err);
7151
+ logger7.error("Environment sync failed", { error: msg });
7152
+ callback?.({ ok: false, error: msg });
7153
+ }
7154
+ }
7155
+ function getChangedFiles(projectDir, previousSha, newSha) {
7156
+ try {
7157
+ return execSync5(`git diff --name-only ${previousSha}..${newSha}`, {
7158
+ cwd: projectDir,
7159
+ stdio: ["ignore", "pipe", "ignore"]
7160
+ }).toString().trim().split("\n").filter(Boolean);
7161
+ } catch {
7162
+ return [];
7163
+ }
7164
+ }
7165
+ function runIndividualSyncSteps(projectDir, needsInstall, needsPrisma, stepsRun) {
7166
+ if (needsInstall) {
7167
+ try {
7168
+ execSync5("bun install", { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
7169
+ stepsRun.push("install");
7170
+ } catch (err) {
7171
+ const msg = parseErrorMessage(err);
7172
+ logger7.error("bun install failed", { error: msg });
7173
+ }
7174
+ }
7175
+ if (needsPrisma) {
7176
+ try {
7177
+ execSync5("bunx prisma generate", { cwd: projectDir, timeout: 6e4, stdio: "pipe" });
7178
+ execSync5("bunx prisma db push --accept-data-loss", {
7179
+ cwd: projectDir,
7180
+ timeout: 6e4,
7181
+ stdio: "pipe"
7182
+ });
7183
+ stepsRun.push("prisma");
7184
+ } catch (err) {
7185
+ const msg = parseErrorMessage(err);
7186
+ logger7.error("Prisma sync failed", { error: msg });
7187
+ }
7188
+ }
7189
+ }
7190
+ async function syncDependencies(projectDir, branchSwitchCommand, connection, changedFiles, stepsRun) {
7191
+ const needsInstall = changedFiles.some(
7192
+ (f) => f === "package.json" || f === "bun.lockb" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
7193
+ );
7194
+ const needsPrisma = changedFiles.some(
7195
+ (f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
7196
+ );
7197
+ const cmd = branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7198
+ if (cmd && (needsInstall || needsPrisma)) {
7199
+ try {
7200
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
7201
+ connection.sendEvent({ type: "sync_output", stream, data });
7202
+ });
7203
+ stepsRun.push("branchSwitchCommand");
7204
+ } catch (err) {
7205
+ const msg = parseErrorMessage(err);
7206
+ logger7.error("Branch switch command failed", { error: msg });
7207
+ }
7208
+ } else if (!cmd) {
7209
+ runIndividualSyncSteps(projectDir, needsInstall, needsPrisma, stepsRun);
7210
+ }
7211
+ }
7212
+ async function smartSync(projectDir, branchSwitchCommand, state, connection, previousSha, newSha, branch) {
7213
+ if (hasUncommittedChanges(projectDir)) {
7214
+ connection.sendEvent({
7215
+ type: "commit_watch_warning",
7216
+ message: "Working tree has uncommitted changes. Auto-pull skipped."
7217
+ });
7218
+ return ["skipped:dirty_tree"];
7219
+ }
7220
+ await killStartCommand(state);
7221
+ try {
7222
+ execSync5(`git pull origin ${branch}`, {
7223
+ cwd: projectDir,
7224
+ stdio: "pipe",
7225
+ timeout: 6e4
7226
+ });
7227
+ } catch (err) {
7228
+ const msg = parseErrorMessage(err);
7229
+ logger7.error("Git pull failed during commit sync", { error: msg });
7230
+ executeStartCommand(projectDir, state, connection);
7231
+ return ["error:pull"];
7232
+ }
7233
+ const stepsRun = ["pull"];
7234
+ const changedFiles = getChangedFiles(projectDir, previousSha, newSha);
7235
+ await syncDependencies(projectDir, branchSwitchCommand, connection, changedFiles, stepsRun);
7236
+ executeStartCommand(projectDir, state, connection);
7237
+ stepsRun.push("startCommand");
7238
+ return stepsRun;
7239
+ }
7240
+
7241
+ // src/runner/project-runner-git.ts
7242
+ var logger8 = createServiceLogger("ProjectRunner");
7039
7243
  function setupWorkDir(projectDir, assignment) {
7040
7244
  const { taskId, branch, devBranch, useWorktree } = assignment;
7041
7245
  const shortId = taskId.slice(0, 8);
@@ -7048,21 +7252,105 @@ function setupWorkDir(projectDir, assignment) {
7048
7252
  }
7049
7253
  if (branch && branch !== devBranch) {
7050
7254
  if (hasUncommittedChanges(workDir)) {
7051
- logger7.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
7255
+ logger8.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
7052
7256
  } else {
7053
7257
  try {
7054
- execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
7258
+ execSync6(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
7055
7259
  } catch {
7056
7260
  try {
7057
- execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
7261
+ execSync6(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
7058
7262
  } catch {
7059
- logger7.warn("Could not checkout branch", { taskId: shortId, branch });
7263
+ logger8.warn("Could not checkout branch", { taskId: shortId, branch });
7060
7264
  }
7061
7265
  }
7062
7266
  }
7063
7267
  }
7064
7268
  return { workDir, usesWorktree: shouldWorktree };
7065
7269
  }
7270
+ function checkoutWorkspaceBranch(projectDir) {
7271
+ const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
7272
+ if (!workspaceBranch) return;
7273
+ const currentBranch = getCurrentBranch(projectDir);
7274
+ if (currentBranch === workspaceBranch) return;
7275
+ if (hasUncommittedChanges(projectDir)) {
7276
+ logger8.warn("Uncommitted changes, skipping workspace branch checkout");
7277
+ return;
7278
+ }
7279
+ try {
7280
+ execSync6(`git fetch origin ${workspaceBranch}`, { cwd: projectDir, stdio: "pipe" });
7281
+ execSync6(`git checkout ${workspaceBranch}`, { cwd: projectDir, stdio: "pipe" });
7282
+ logger8.info("Checked out workspace branch", { workspaceBranch });
7283
+ } catch {
7284
+ logger8.warn("Failed to checkout workspace branch", { workspaceBranch });
7285
+ }
7286
+ }
7287
+ async function handleSwitchBranch(projectDir, branchSwitchCommand, startCmd, connection, commitWatcher, data, callback) {
7288
+ try {
7289
+ try {
7290
+ execSync6("git fetch origin", { cwd: projectDir, stdio: "pipe" });
7291
+ } catch {
7292
+ logger8.warn("Git fetch failed during branch switch");
7293
+ }
7294
+ detachWorktreeBranch(projectDir, data.branch);
7295
+ try {
7296
+ execSync6(`git checkout ${data.branch}`, { cwd: projectDir, stdio: "pipe" });
7297
+ } catch (err) {
7298
+ const msg = parseErrorMessage(err);
7299
+ callback({ ok: false, error: `Failed to checkout branch: ${msg}` });
7300
+ return;
7301
+ }
7302
+ try {
7303
+ execSync6(`git pull origin ${data.branch}`, { cwd: projectDir, stdio: "pipe" });
7304
+ } catch {
7305
+ logger8.warn("Git pull failed during branch switch");
7306
+ }
7307
+ if (data.syncAfter !== false) {
7308
+ await handleSyncEnvironment(projectDir, branchSwitchCommand, startCmd, connection);
7309
+ }
7310
+ commitWatcher.start(data.branch);
7311
+ callback({ ok: true });
7312
+ } catch (err) {
7313
+ const msg = parseErrorMessage(err);
7314
+ logger8.error("Branch switch failed", { error: msg });
7315
+ callback({ ok: false, error: msg });
7316
+ }
7317
+ }
7318
+ async function handleNewCommits(projectDir, branchSwitchCommand, startCmd, connection, setupComplete, data) {
7319
+ await connection.call("reportNewCommitsDetected", {
7320
+ projectId: connection.projectId,
7321
+ branch: data.branch,
7322
+ commits: [
7323
+ {
7324
+ sha: data.newCommitSha,
7325
+ message: data.latestMessage,
7326
+ author: data.latestAuthor
7327
+ }
7328
+ ]
7329
+ });
7330
+ const stepsRun = await smartSync(
7331
+ projectDir,
7332
+ branchSwitchCommand,
7333
+ startCmd,
7334
+ connection,
7335
+ data.previousSha,
7336
+ data.newCommitSha,
7337
+ data.branch
7338
+ );
7339
+ await connection.call("reportEnvironmentReady", {
7340
+ projectId: connection.projectId,
7341
+ branch: data.branch,
7342
+ setupComplete,
7343
+ startCommandRunning: startCmd.running
7344
+ });
7345
+ logger8.info("Commit sync complete", { steps: stepsRun.join(", ") });
7346
+ }
7347
+
7348
+ // src/runner/project-runner-children.ts
7349
+ var logger9 = createServiceLogger("ProjectRunner");
7350
+ var __filename = fileURLToPath2(import.meta.url);
7351
+ var __dirname = path.dirname(__filename);
7352
+ var STOP_TIMEOUT_MS = 3e4;
7353
+ var MAX_CONCURRENT = Math.max(1, parseInt(process.env.CONVEYOR_MAX_CONCURRENT ?? "10", 10) || 10);
7066
7354
  function spawnChildAgent(assignment, workDir) {
7067
7355
  const { taskToken, apiUrl, taskId, sessionId, mode, isAuto, useSandbox, agentMode } = assignment;
7068
7356
  const cliPath = path.resolve(__dirname, "cli.js");
@@ -7074,7 +7362,7 @@ function spawnChildAgent(assignment, workDir) {
7074
7362
  const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
7075
7363
  const effectiveApiUrl = apiUrl || process.env.CONVEYOR_API_URL || "";
7076
7364
  if (!effectiveApiUrl) {
7077
- logger7.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
7365
+ logger9.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
7078
7366
  }
7079
7367
  const child = fork(cliPath, [], {
7080
7368
  env: {
@@ -7102,16 +7390,112 @@ function spawnChildAgent(assignment, workDir) {
7102
7390
  const shortId = taskId.slice(0, 8);
7103
7391
  child.stdout?.on("data", (data) => {
7104
7392
  for (const line of data.toString().trimEnd().split("\n")) {
7105
- logger7.info(line, { taskId: shortId });
7393
+ logger9.info(line, { taskId: shortId });
7106
7394
  }
7107
7395
  });
7108
7396
  child.stderr?.on("data", (data) => {
7109
7397
  for (const line of data.toString().trimEnd().split("\n")) {
7110
- logger7.info(line, { taskId: shortId });
7398
+ logger9.info(line, { taskId: shortId });
7111
7399
  }
7112
7400
  });
7113
7401
  return child;
7114
7402
  }
7403
+ async function killAgent(agent, taskId) {
7404
+ const shortId = taskId.slice(0, 8);
7405
+ if (agent.process.exitCode !== null) {
7406
+ logger9.info("Agent process already exited", { taskId: shortId });
7407
+ return;
7408
+ }
7409
+ logger9.info("Killing agent process", { taskId: shortId });
7410
+ agent.process.kill("SIGTERM");
7411
+ await new Promise((resolve2) => {
7412
+ const timer = setTimeout(() => {
7413
+ if (agent.process.exitCode === null) {
7414
+ logger9.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
7415
+ agent.process.kill("SIGKILL");
7416
+ }
7417
+ resolve2();
7418
+ }, STOP_TIMEOUT_MS);
7419
+ agent.process.on("exit", () => {
7420
+ clearTimeout(timer);
7421
+ resolve2();
7422
+ });
7423
+ });
7424
+ }
7425
+ async function handleAssignment(assignment, activeAgents, projectDir, connection) {
7426
+ const { taskId, mode } = assignment;
7427
+ const shortId = taskId.slice(0, 8);
7428
+ const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
7429
+ const existing = activeAgents.get(agentKey);
7430
+ if (existing) {
7431
+ if (existing.process.exitCode === null) {
7432
+ logger9.info("Re-assignment received, killing existing agent", { taskId: shortId });
7433
+ await killAgent(existing, taskId);
7434
+ } else {
7435
+ logger9.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
7436
+ }
7437
+ activeAgents.delete(agentKey);
7438
+ }
7439
+ if (activeAgents.size >= MAX_CONCURRENT) {
7440
+ logger9.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
7441
+ connection.emitTaskStopped(taskId, "max_concurrent_reached");
7442
+ return;
7443
+ }
7444
+ try {
7445
+ try {
7446
+ execSync7("git fetch origin", { cwd: projectDir, stdio: "ignore" });
7447
+ } catch {
7448
+ logger9.warn("Git fetch failed", { taskId: shortId });
7449
+ }
7450
+ const { workDir, usesWorktree } = setupWorkDir(projectDir, assignment);
7451
+ if (assignment.devBranch) {
7452
+ syncWithBaseBranch(workDir, assignment.devBranch);
7453
+ }
7454
+ const child = spawnChildAgent(assignment, workDir);
7455
+ activeAgents.set(agentKey, {
7456
+ process: child,
7457
+ worktreePath: workDir,
7458
+ mode,
7459
+ usesWorktree
7460
+ });
7461
+ connection.emitTaskStarted(taskId);
7462
+ logger9.info("Started task", { taskId: shortId, mode, workDir });
7463
+ child.on("exit", (code) => {
7464
+ activeAgents.delete(agentKey);
7465
+ const reason = code === 0 ? "completed" : `exited with code ${code}`;
7466
+ connection.emitTaskStopped(taskId, reason);
7467
+ logger9.info("Task exited", { taskId: shortId, reason });
7468
+ if (code === 0 && usesWorktree) {
7469
+ try {
7470
+ removeWorktree(projectDir, taskId);
7471
+ } catch {
7472
+ }
7473
+ }
7474
+ });
7475
+ } catch (error) {
7476
+ const msg = parseErrorMessage(error);
7477
+ logger9.error("Failed to start task", { taskId: shortId, error: msg });
7478
+ connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
7479
+ }
7480
+ }
7481
+ function handleStopTask(taskId, activeAgents, projectDir) {
7482
+ const agentKey = activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
7483
+ const agent = activeAgents.get(agentKey);
7484
+ if (!agent) return;
7485
+ logger9.info("Stopping task", { taskId: taskId.slice(0, 8) });
7486
+ void killAgent(agent, taskId).then(() => {
7487
+ if (agent.usesWorktree) {
7488
+ try {
7489
+ removeWorktree(projectDir, taskId);
7490
+ } catch {
7491
+ }
7492
+ }
7493
+ });
7494
+ }
7495
+
7496
+ // src/runner/project-runner.ts
7497
+ var logger10 = createServiceLogger("ProjectRunner");
7498
+ var HEARTBEAT_INTERVAL_MS = 3e4;
7115
7499
  var ProjectRunner = class {
7116
7500
  connection;
7117
7501
  projectDir;
@@ -7120,8 +7504,7 @@ var ProjectRunner = class {
7120
7504
  stopping = false;
7121
7505
  resolveLifecycle = null;
7122
7506
  chatSessionIds = /* @__PURE__ */ new Map();
7123
- startCommandChild = null;
7124
- startCommandRunning = false;
7507
+ startCmd = { child: null, running: false };
7125
7508
  setupComplete = false;
7126
7509
  branchSwitchCommand;
7127
7510
  commitWatcher;
@@ -7140,33 +7523,39 @@ var ProjectRunner = class {
7140
7523
  },
7141
7524
  {
7142
7525
  onNewCommits: async (data) => {
7143
- await this.handleNewCommits(data);
7526
+ await handleNewCommits(
7527
+ this.projectDir,
7528
+ this.branchSwitchCommand,
7529
+ this.startCmd,
7530
+ this.connection,
7531
+ this.setupComplete,
7532
+ data
7533
+ );
7144
7534
  }
7145
7535
  }
7146
7536
  );
7147
7537
  }
7148
7538
  // ── Public lifecycle ───────────────────────────────────────────────────
7149
- // oxlint-disable-next-line max-lines-per-function -- sequential lifecycle orchestration
7150
7539
  async start() {
7151
- this.checkoutWorkspaceBranch();
7540
+ checkoutWorkspaceBranch(this.projectDir);
7152
7541
  await this.connection.connect();
7153
7542
  const registration = await this.connection.call("registerProjectAgent", {
7154
7543
  projectId: this.connection.projectId,
7155
7544
  capabilities: ["task", "pm", "code-review", "audit"]
7156
7545
  });
7157
7546
  this.branchSwitchCommand = registration.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7158
- logger7.info("Registered as project agent", { agentName: registration.agentName });
7547
+ logger10.info("Registered as project agent", { agentName: registration.agentName });
7159
7548
  try {
7160
- await this.executeSetupCommand();
7161
- this.executeStartCommand();
7549
+ await executeSetupCommand(this.projectDir, this.connection);
7550
+ executeStartCommand(this.projectDir, this.startCmd, this.connection);
7162
7551
  this.setupComplete = true;
7163
7552
  this.connection.sendEvent({
7164
7553
  type: "setup_complete",
7165
- startCommandRunning: this.startCommandRunning
7554
+ startCommandRunning: this.startCmd.running
7166
7555
  });
7167
7556
  } catch (error) {
7168
- const msg = error instanceof Error ? error.message : String(error);
7169
- logger7.error("Environment setup failed", { error: msg });
7557
+ const msg = parseErrorMessage(error);
7558
+ logger10.error("Environment setup failed", { error: msg });
7170
7559
  this.setupComplete = false;
7171
7560
  }
7172
7561
  this.wireEventHandlers();
@@ -7178,7 +7567,7 @@ var ProjectRunner = class {
7178
7567
  if (currentBranch) {
7179
7568
  this.commitWatcher.start(currentBranch);
7180
7569
  }
7181
- logger7.info("Connected, waiting for task assignments");
7570
+ logger10.info("Connected, waiting for task assignments");
7182
7571
  await new Promise((resolve2) => {
7183
7572
  this.resolveLifecycle = resolve2;
7184
7573
  });
@@ -7197,24 +7586,24 @@ var ProjectRunner = class {
7197
7586
  wipMessage: "WIP: auto-commit on conveyor-agent shutdown"
7198
7587
  });
7199
7588
  if (result.hadWork) {
7200
- logger7.info("Shutdown git flush", {
7589
+ logger10.info("Shutdown git flush", {
7201
7590
  dir,
7202
7591
  committed: result.committed,
7203
7592
  pushed: result.pushed
7204
7593
  });
7205
7594
  }
7206
7595
  } catch (err) {
7207
- const msg = err instanceof Error ? err.message : String(err);
7208
- logger7.warn("Shutdown git flush failed", { dir, error: msg });
7596
+ const msg = parseErrorMessage(err);
7597
+ logger10.warn("Shutdown git flush failed", { dir, error: msg });
7209
7598
  }
7210
7599
  }
7211
7600
  }
7212
7601
  async stop() {
7213
7602
  if (this.stopping) return;
7214
7603
  this.stopping = true;
7215
- logger7.info("Shutting down");
7604
+ logger10.info("Shutting down");
7216
7605
  this.commitWatcher.stop();
7217
- await this.killStartCommand();
7606
+ await killStartCommand(this.startCmd);
7218
7607
  if (this.heartbeatTimer) {
7219
7608
  clearInterval(this.heartbeatTimer);
7220
7609
  this.heartbeatTimer = null;
@@ -7227,7 +7616,7 @@ var ProjectRunner = class {
7227
7616
  return;
7228
7617
  }
7229
7618
  agent.process.on("exit", () => resolve2());
7230
- this.handleStopTask(key);
7619
+ handleStopTask(key, this.activeAgents, this.projectDir);
7231
7620
  })
7232
7621
  );
7233
7622
  await Promise.race([
@@ -7244,7 +7633,7 @@ var ProjectRunner = class {
7244
7633
  } catch {
7245
7634
  }
7246
7635
  this.connection.disconnect();
7247
- logger7.info("Shutdown complete");
7636
+ logger10.info("Shutdown complete");
7248
7637
  if (this.resolveLifecycle) {
7249
7638
  this.resolveLifecycle();
7250
7639
  this.resolveLifecycle = null;
@@ -7252,8 +7641,12 @@ var ProjectRunner = class {
7252
7641
  }
7253
7642
  // ── Event wiring ───────────────────────────────────────────────────────
7254
7643
  wireEventHandlers() {
7255
- this.connection.onTaskAssignment((assignment) => void this.handleAssignment(assignment));
7256
- this.connection.onStopTask((data) => this.handleStopTask(data.taskId));
7644
+ this.connection.onTaskAssignment(
7645
+ (assignment) => void handleAssignment(assignment, this.activeAgents, this.projectDir, this.connection)
7646
+ );
7647
+ this.connection.onStopTask(
7648
+ (data) => handleStopTask(data.taskId, this.activeAgents, this.projectDir)
7649
+ );
7257
7650
  this.connection.onShutdown(() => void this.stop());
7258
7651
  this.connection.onChatMessage((msg) => {
7259
7652
  const chatId = msg.chatId ?? "default";
@@ -7266,10 +7659,28 @@ var ProjectRunner = class {
7266
7659
  });
7267
7660
  this.connection.onAuditTags((request) => void this.handleAuditTags(request));
7268
7661
  this.connection.onAuditTasks((request) => void this.handleAuditTasks(request));
7269
- this.connection.onSwitchBranch((data, cb) => void this.handleSwitchBranch(data, cb));
7270
- this.connection.onSyncEnvironment((cb) => void this.handleSyncEnvironment(cb));
7662
+ this.connection.onSwitchBranch(
7663
+ (data, cb) => void handleSwitchBranch(
7664
+ this.projectDir,
7665
+ this.branchSwitchCommand,
7666
+ this.startCmd,
7667
+ this.connection,
7668
+ this.commitWatcher,
7669
+ data,
7670
+ cb
7671
+ )
7672
+ );
7673
+ this.connection.onSyncEnvironment(
7674
+ (cb) => void handleSyncEnvironment(
7675
+ this.projectDir,
7676
+ this.branchSwitchCommand,
7677
+ this.startCmd,
7678
+ this.connection,
7679
+ cb
7680
+ )
7681
+ );
7271
7682
  this.connection.onRestartStartCommand((cb) => {
7272
- void this.restartStartCommand().then(() => cb({ ok: true })).catch(
7683
+ void restartStartCommand(this.projectDir, this.startCmd, this.connection).then(() => cb({ ok: true })).catch(
7273
7684
  (err) => cb({ ok: false, error: err instanceof Error ? err.message : "Restart failed" })
7274
7685
  );
7275
7686
  });
@@ -7281,10 +7692,10 @@ var ProjectRunner = class {
7281
7692
  capabilities: ["task", "pm", "code-review", "audit"]
7282
7693
  });
7283
7694
  this.connection.emitStatus(this.activeAgents.size > 0 ? "busy" : "idle");
7284
- logger7.info("Re-registered after reconnect");
7695
+ logger10.info("Re-registered after reconnect");
7285
7696
  } catch (error) {
7286
- const msg = error instanceof Error ? error.message : String(error);
7287
- logger7.error("Failed to re-register after reconnect", { error: msg });
7697
+ const msg = parseErrorMessage(error);
7698
+ logger10.error("Failed to re-register after reconnect", { error: msg });
7288
7699
  }
7289
7700
  }
7290
7701
  // ── Tag audit ──────────────────────────────────────────────────────────
@@ -7294,8 +7705,8 @@ var ProjectRunner = class {
7294
7705
  const { handleTagAudit } = await import("./tag-audit-handler-I54W7ZD7.js");
7295
7706
  await handleTagAudit(request, this.connection, this.projectDir);
7296
7707
  } catch (error) {
7297
- const msg = error instanceof Error ? error.message : String(error);
7298
- logger7.error("Tag audit failed", { error: msg, requestId: request.requestId });
7708
+ const msg = parseErrorMessage(error);
7709
+ logger10.error("Tag audit failed", { error: msg, requestId: request.requestId });
7299
7710
  try {
7300
7711
  await this.connection.call("reportTagAuditResult", {
7301
7712
  projectId: this.connection.projectId,
@@ -7314,11 +7725,11 @@ var ProjectRunner = class {
7314
7725
  async handleAuditTasks(request) {
7315
7726
  this.connection.emitStatus("busy");
7316
7727
  try {
7317
- const { handleTaskAudit } = await import("./task-audit-handler-KGTVMVAP.js");
7728
+ const { handleTaskAudit } = await import("./task-audit-handler-TJOM5OJS.js");
7318
7729
  await handleTaskAudit(request, this.connection, this.projectDir);
7319
7730
  } catch (error) {
7320
- const msg = error instanceof Error ? error.message : String(error);
7321
- logger7.error("Task audit failed", { error: msg, requestId: request.requestId });
7731
+ const msg = parseErrorMessage(error);
7732
+ logger10.error("Task audit failed", { error: msg, requestId: request.requestId });
7322
7733
  for (const task of request.tasks) {
7323
7734
  try {
7324
7735
  await this.connection.call("reportTaskAuditResult", {
@@ -7356,353 +7767,9 @@ var ProjectRunner = class {
7356
7767
  this.connection.emitStatus("idle");
7357
7768
  }
7358
7769
  }
7359
- // ── Task management ────────────────────────────────────────────────────
7360
- async killAgent(agent, taskId) {
7361
- const shortId = taskId.slice(0, 8);
7362
- if (agent.process.exitCode !== null) {
7363
- logger7.info("Agent process already exited", { taskId: shortId });
7364
- return;
7365
- }
7366
- logger7.info("Killing agent process", { taskId: shortId });
7367
- agent.process.kill("SIGTERM");
7368
- await new Promise((resolve2) => {
7369
- const timer = setTimeout(() => {
7370
- if (agent.process.exitCode === null) {
7371
- logger7.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
7372
- agent.process.kill("SIGKILL");
7373
- }
7374
- resolve2();
7375
- }, STOP_TIMEOUT_MS);
7376
- agent.process.on("exit", () => {
7377
- clearTimeout(timer);
7378
- resolve2();
7379
- });
7380
- });
7381
- }
7382
- // oxlint-disable-next-line max-lines-per-function -- re-assignment logic requires sequential checks
7383
- async handleAssignment(assignment) {
7384
- const { taskId, mode } = assignment;
7385
- const shortId = taskId.slice(0, 8);
7386
- const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
7387
- const existing = this.activeAgents.get(agentKey);
7388
- if (existing) {
7389
- if (existing.process.exitCode === null) {
7390
- logger7.info("Re-assignment received, killing existing agent", { taskId: shortId });
7391
- await this.killAgent(existing, taskId);
7392
- } else {
7393
- logger7.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
7394
- }
7395
- this.activeAgents.delete(agentKey);
7396
- }
7397
- if (this.activeAgents.size >= MAX_CONCURRENT) {
7398
- logger7.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
7399
- this.connection.emitTaskStopped(taskId, "max_concurrent_reached");
7400
- return;
7401
- }
7402
- try {
7403
- try {
7404
- execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
7405
- } catch {
7406
- logger7.warn("Git fetch failed", { taskId: shortId });
7407
- }
7408
- const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
7409
- if (assignment.devBranch) {
7410
- syncWithBaseBranch(workDir, assignment.devBranch);
7411
- }
7412
- const child = spawnChildAgent(assignment, workDir);
7413
- this.activeAgents.set(agentKey, {
7414
- process: child,
7415
- worktreePath: workDir,
7416
- mode,
7417
- usesWorktree
7418
- });
7419
- this.connection.emitTaskStarted(taskId);
7420
- logger7.info("Started task", { taskId: shortId, mode, workDir });
7421
- child.on("exit", (code) => {
7422
- this.activeAgents.delete(agentKey);
7423
- const reason = code === 0 ? "completed" : `exited with code ${code}`;
7424
- this.connection.emitTaskStopped(taskId, reason);
7425
- logger7.info("Task exited", { taskId: shortId, reason });
7426
- if (code === 0 && usesWorktree) {
7427
- try {
7428
- removeWorktree(this.projectDir, taskId);
7429
- } catch {
7430
- }
7431
- }
7432
- });
7433
- } catch (error) {
7434
- const msg = error instanceof Error ? error.message : "Unknown";
7435
- logger7.error("Failed to start task", { taskId: shortId, error: msg });
7436
- this.connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
7437
- }
7438
- }
7439
- handleStopTask(taskId) {
7440
- const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
7441
- const agent = this.activeAgents.get(agentKey);
7442
- if (!agent) return;
7443
- logger7.info("Stopping task", { taskId: taskId.slice(0, 8) });
7444
- void this.killAgent(agent, taskId).then(() => {
7445
- if (agent.usesWorktree) {
7446
- try {
7447
- removeWorktree(this.projectDir, taskId);
7448
- } catch {
7449
- }
7450
- }
7451
- });
7452
- }
7453
- // ── Environment management ─────────────────────────────────────────────
7454
- checkoutWorkspaceBranch() {
7455
- const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
7456
- if (!workspaceBranch) return;
7457
- const currentBranch = this.getCurrentBranch();
7458
- if (currentBranch === workspaceBranch) return;
7459
- if (hasUncommittedChanges(this.projectDir)) {
7460
- logger7.warn("Uncommitted changes, skipping workspace branch checkout");
7461
- return;
7462
- }
7463
- try {
7464
- execSync5(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
7465
- execSync5(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
7466
- logger7.info("Checked out workspace branch", { workspaceBranch });
7467
- } catch {
7468
- logger7.warn("Failed to checkout workspace branch", { workspaceBranch });
7469
- }
7470
- }
7471
- async executeSetupCommand() {
7472
- const cmd = process.env.CONVEYOR_SETUP_COMMAND;
7473
- if (!cmd) return;
7474
- logger7.info("Running setup command", { command: cmd });
7475
- try {
7476
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7477
- this.connection.sendEvent({ type: "setup_output", stream, data });
7478
- (stream === "stderr" ? process.stderr : process.stdout).write(data);
7479
- });
7480
- logger7.info("Setup command completed");
7481
- } catch (error) {
7482
- const msg = error instanceof Error ? error.message : "Setup command failed";
7483
- logger7.error("Setup command failed", { error: msg });
7484
- this.connection.sendEvent({ type: "setup_error", message: msg });
7485
- throw error;
7486
- }
7487
- }
7488
- executeStartCommand() {
7489
- const cmd = process.env.CONVEYOR_START_COMMAND;
7490
- if (!cmd) return;
7491
- logger7.info("Running start command", { command: cmd });
7492
- const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
7493
- this.connection.sendEvent({ type: "start_command_output", stream, data });
7494
- (stream === "stderr" ? process.stderr : process.stdout).write(data);
7495
- });
7496
- this.startCommandChild = child;
7497
- this.startCommandRunning = true;
7498
- child.on("exit", (code, signal) => {
7499
- this.startCommandRunning = false;
7500
- this.startCommandChild = null;
7501
- logger7.info("Start command exited", { code, signal });
7502
- this.connection.sendEvent({ type: "start_command_exited", code, signal });
7503
- });
7504
- child.on("error", (err) => {
7505
- this.startCommandRunning = false;
7506
- this.startCommandChild = null;
7507
- logger7.error("Start command error", { error: err.message });
7508
- });
7509
- }
7510
- async killStartCommand() {
7511
- const child = this.startCommandChild;
7512
- if (!child || !this.startCommandRunning) return;
7513
- try {
7514
- if (child.pid) process.kill(-child.pid, "SIGTERM");
7515
- } catch {
7516
- child.kill("SIGTERM");
7517
- }
7518
- await new Promise((resolve2) => {
7519
- const timer = setTimeout(() => {
7520
- if (this.startCommandRunning && child.pid) {
7521
- try {
7522
- process.kill(-child.pid, "SIGKILL");
7523
- } catch {
7524
- child.kill("SIGKILL");
7525
- }
7526
- }
7527
- resolve2();
7528
- }, START_CMD_KILL_TIMEOUT_MS);
7529
- child.on("exit", () => {
7530
- clearTimeout(timer);
7531
- resolve2();
7532
- });
7533
- });
7534
- this.startCommandChild = null;
7535
- this.startCommandRunning = false;
7536
- }
7537
- async restartStartCommand() {
7538
- await this.killStartCommand();
7539
- this.executeStartCommand();
7540
- }
7541
7770
  getCurrentBranch() {
7542
7771
  return getCurrentBranch(this.projectDir);
7543
7772
  }
7544
- // ── Branch switching ───────────────────────────────────────────────────
7545
- async handleSwitchBranch(data, callback) {
7546
- try {
7547
- try {
7548
- execSync5("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
7549
- } catch {
7550
- logger7.warn("Git fetch failed during branch switch");
7551
- }
7552
- detachWorktreeBranch(this.projectDir, data.branch);
7553
- try {
7554
- execSync5(`git checkout ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
7555
- } catch (err) {
7556
- const msg = err instanceof Error ? err.message : "Checkout failed";
7557
- callback({ ok: false, error: `Failed to checkout branch: ${msg}` });
7558
- return;
7559
- }
7560
- try {
7561
- execSync5(`git pull origin ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
7562
- } catch {
7563
- logger7.warn("Git pull failed during branch switch");
7564
- }
7565
- if (data.syncAfter !== false) {
7566
- await this.handleSyncEnvironment();
7567
- }
7568
- this.commitWatcher.start(data.branch);
7569
- callback({ ok: true });
7570
- } catch (err) {
7571
- const msg = err instanceof Error ? err.message : "Branch switch failed";
7572
- logger7.error("Branch switch failed", { error: msg });
7573
- callback({ ok: false, error: msg });
7574
- }
7575
- }
7576
- async handleSyncEnvironment(callback) {
7577
- try {
7578
- await this.killStartCommand();
7579
- const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7580
- if (cmd) {
7581
- try {
7582
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7583
- this.connection.sendEvent({ type: "sync_output", stream, data });
7584
- });
7585
- } catch (err) {
7586
- const msg = err instanceof Error ? err.message : "Sync command failed";
7587
- logger7.error("Branch switch sync command failed", { error: msg });
7588
- }
7589
- }
7590
- this.executeStartCommand();
7591
- callback?.({ ok: true });
7592
- } catch (err) {
7593
- const msg = err instanceof Error ? err.message : "Sync failed";
7594
- logger7.error("Environment sync failed", { error: msg });
7595
- callback?.({ ok: false, error: msg });
7596
- }
7597
- }
7598
- // ── Commit watching ────────────────────────────────────────────────────
7599
- // oxlint-disable-next-line max-lines-per-function -- sequential sync steps
7600
- async handleNewCommits(data) {
7601
- await this.connection.call("reportNewCommitsDetected", {
7602
- projectId: this.connection.projectId,
7603
- branch: data.branch,
7604
- commits: [
7605
- {
7606
- sha: data.newCommitSha,
7607
- message: data.latestMessage,
7608
- author: data.latestAuthor
7609
- }
7610
- ]
7611
- });
7612
- const stepsRun = await this.smartSync(data.previousSha, data.newCommitSha, data.branch);
7613
- await this.connection.call("reportEnvironmentReady", {
7614
- projectId: this.connection.projectId,
7615
- branch: data.branch,
7616
- setupComplete: this.setupComplete,
7617
- startCommandRunning: this.startCommandRunning
7618
- });
7619
- logger7.info("Commit sync complete", { steps: stepsRun.join(", ") });
7620
- }
7621
- async smartSync(previousSha, newSha, branch) {
7622
- if (hasUncommittedChanges(this.projectDir)) {
7623
- this.connection.sendEvent({
7624
- type: "commit_watch_warning",
7625
- message: "Working tree has uncommitted changes. Auto-pull skipped."
7626
- });
7627
- return ["skipped:dirty_tree"];
7628
- }
7629
- await this.killStartCommand();
7630
- try {
7631
- execSync5(`git pull origin ${branch}`, {
7632
- cwd: this.projectDir,
7633
- stdio: "pipe",
7634
- timeout: 6e4
7635
- });
7636
- } catch (err) {
7637
- const msg = err instanceof Error ? err.message : "Pull failed";
7638
- logger7.error("Git pull failed during commit sync", { error: msg });
7639
- this.executeStartCommand();
7640
- return ["error:pull"];
7641
- }
7642
- const stepsRun = ["pull"];
7643
- const changedFiles = this.getChangedFiles(previousSha, newSha);
7644
- await this.syncDependencies(changedFiles, stepsRun);
7645
- this.executeStartCommand();
7646
- stepsRun.push("startCommand");
7647
- return stepsRun;
7648
- }
7649
- async syncDependencies(changedFiles, stepsRun) {
7650
- const needsInstall = changedFiles.some(
7651
- (f) => f === "package.json" || f === "bun.lockb" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
7652
- );
7653
- const needsPrisma = changedFiles.some(
7654
- (f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
7655
- );
7656
- const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7657
- if (cmd && (needsInstall || needsPrisma)) {
7658
- try {
7659
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7660
- this.connection.sendEvent({ type: "sync_output", stream, data });
7661
- });
7662
- stepsRun.push("branchSwitchCommand");
7663
- } catch (err) {
7664
- const msg = err instanceof Error ? err.message : "Sync command failed";
7665
- logger7.error("Branch switch command failed", { error: msg });
7666
- }
7667
- } else if (!cmd) {
7668
- this.runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun);
7669
- }
7670
- }
7671
- runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun) {
7672
- if (needsInstall) {
7673
- try {
7674
- execSync5("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
7675
- stepsRun.push("install");
7676
- } catch (err) {
7677
- const msg = err instanceof Error ? err.message : "Install failed";
7678
- logger7.error("bun install failed", { error: msg });
7679
- }
7680
- }
7681
- if (needsPrisma) {
7682
- try {
7683
- execSync5("bunx prisma generate", { cwd: this.projectDir, timeout: 6e4, stdio: "pipe" });
7684
- execSync5("bunx prisma db push --accept-data-loss", {
7685
- cwd: this.projectDir,
7686
- timeout: 6e4,
7687
- stdio: "pipe"
7688
- });
7689
- stepsRun.push("prisma");
7690
- } catch (err) {
7691
- const msg = err instanceof Error ? err.message : "Prisma sync failed";
7692
- logger7.error("Prisma sync failed", { error: msg });
7693
- }
7694
- }
7695
- }
7696
- getChangedFiles(previousSha, newSha) {
7697
- try {
7698
- return execSync5(`git diff --name-only ${previousSha}..${newSha}`, {
7699
- cwd: this.projectDir,
7700
- stdio: ["ignore", "pipe", "ignore"]
7701
- }).toString().trim().split("\n").filter(Boolean);
7702
- } catch {
7703
- return [];
7704
- }
7705
- }
7706
7773
  };
7707
7774
 
7708
7775
  // src/setup/config.ts
@@ -7748,15 +7815,15 @@ export {
7748
7815
  PlanSync,
7749
7816
  SessionRunner,
7750
7817
  ProjectConnection,
7751
- runSetupCommand,
7752
- runAuthTokenCommand,
7753
- runStartCommand,
7754
7818
  CommitWatcher,
7755
7819
  ensureWorktree,
7756
7820
  detachWorktreeBranch,
7757
7821
  removeWorktree,
7822
+ runSetupCommand,
7823
+ runAuthTokenCommand,
7824
+ runStartCommand,
7758
7825
  ProjectRunner,
7759
7826
  loadForwardPorts,
7760
7827
  loadConveyorConfig
7761
7828
  };
7762
- //# sourceMappingURL=chunk-TN24QXF5.js.map
7829
+ //# sourceMappingURL=chunk-WYIXEIRS.js.map