@rallycry/conveyor-agent 7.2.16 → 7.2.18

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,149 +2394,19 @@ function buildGetTaskFileTool(connection) {
2408
2394
  { annotations: { readOnlyHint: true } }
2409
2395
  );
2410
2396
  }
2411
- function buildSearchIncidentsTool(connection) {
2412
- return defineTool(
2413
- "search_incidents",
2414
- "Search incidents in the current project. Optionally filter by status (Open, Acknowledged, Investigating, Resolved, Closed) or source.",
2415
- {
2416
- status: z.string().optional().describe("Filter by incident status"),
2417
- source: z.string().optional().describe("Filter by source (e.g., 'conveyor', 'agent', 'build')")
2418
- },
2419
- async ({ status, source }) => {
2420
- try {
2421
- const incidents = await connection.call("searchIncidents", {
2422
- sessionId: connection.sessionId,
2423
- status,
2424
- source
2425
- });
2426
- return textResult(JSON.stringify(incidents, null, 2));
2427
- } catch (error) {
2428
- return textResult(
2429
- `Failed to search incidents: ${error instanceof Error ? error.message : "Unknown error"}`
2430
- );
2431
- }
2432
- },
2433
- { annotations: { readOnlyHint: true } }
2434
- );
2435
- }
2436
- function buildGetTaskIncidentsTool(connection) {
2437
- return defineTool(
2438
- "get_task_incidents",
2439
- "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
- {
2441
- task_id: z.string().optional().describe("Task ID (defaults to current task)")
2442
- },
2443
- async ({ task_id }) => {
2444
- try {
2445
- const incidents = await connection.call("getTaskIncidents", {
2446
- sessionId: connection.sessionId,
2447
- taskId: task_id
2448
- });
2449
- return textResult(JSON.stringify(incidents, null, 2));
2450
- } catch (error) {
2451
- return textResult(
2452
- `Failed to get task incidents: ${error instanceof Error ? error.message : "Unknown error"}`
2453
- );
2454
- }
2455
- },
2456
- { annotations: { readOnlyHint: true } }
2457
- );
2458
- }
2459
- function buildGetIncidentDetailTool(connection) {
2460
- return defineTool(
2461
- "get_incident_detail",
2462
- "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
- {
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)")
2469
- },
2470
- async ({ incident_id, section, search, offset, limit }) => {
2471
- try {
2472
- const result = await connection.call("getIncidentDetail", {
2473
- sessionId: connection.sessionId,
2474
- incidentId: incident_id,
2475
- section,
2476
- search,
2477
- offset,
2478
- limit
2479
- });
2480
- if (result.matchCount !== void 0) {
2481
- return textResult(
2482
- `${result.content}
2483
-
2484
- --- ${result.matchCount} matches found (${result.totalLines} total lines in section) ---`
2485
- );
2486
- }
2487
- if (result.hasMore) {
2488
- return textResult(
2489
- `${result.content}
2490
-
2491
- --- Showing lines ${result.offset ?? 0}-${(result.offset ?? 0) + (limit ?? 100)} of ${result.totalLines} (more available \u2014 increase offset) ---`
2492
- );
2493
- }
2494
- return textResult(result.content);
2495
- } catch (error) {
2496
- return textResult(
2497
- `Failed to get incident detail: ${error instanceof Error ? error.message : "Unknown error"}`
2498
- );
2499
- }
2500
- },
2501
- { annotations: { readOnlyHint: true } }
2502
- );
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
+ ];
2503
2406
  }
2504
- function buildQueryGcpLogsTool(connection) {
2505
- return defineTool(
2506
- "query_gcp_logs",
2507
- "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
- {
2509
- filter: z.string().optional().describe(
2510
- `Cloud Logging filter expression (e.g., 'resource.type="gce_instance"'). Max 1000 chars.`
2511
- ),
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([
2515
- "DEFAULT",
2516
- "DEBUG",
2517
- "INFO",
2518
- "NOTICE",
2519
- "WARNING",
2520
- "ERROR",
2521
- "CRITICAL",
2522
- "ALERT",
2523
- "EMERGENCY"
2524
- ]).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)")
2526
- },
2527
- async ({ filter, start_time, end_time, severity, page_size }) => {
2528
- try {
2529
- const result = await connection.call("queryGcpLogs", {
2530
- sessionId: connection.sessionId,
2531
- filter,
2532
- startTime: start_time,
2533
- endTime: end_time,
2534
- severity,
2535
- pageSize: page_size
2536
- });
2537
- if (result.entries.length === 0) {
2538
- return textResult("No log entries found matching the given filters.");
2539
- }
2540
- const summary = `Found ${result.entries.length} log entries${result.hasMore ? " (more available \u2014 refine filters or increase page_size)" : ""}.`;
2541
- const formatted = result.entries.map((e) => `[${e.timestamp}] [${e.severity}] ${e.message}`).join("\n");
2542
- return textResult(`${summary}
2543
2407
 
2544
- ${formatted}`);
2545
- } catch (error) {
2546
- return textResult(
2547
- `Failed to query GCP logs: ${error instanceof Error ? error.message : "Unknown error"}`
2548
- );
2549
- }
2550
- },
2551
- { annotations: { readOnlyHint: true } }
2552
- );
2553
- }
2408
+ // src/tools/dependency-suggestion-tools.ts
2409
+ import { z as z2 } from "zod";
2554
2410
  function buildGetDependenciesTool(connection) {
2555
2411
  return defineTool(
2556
2412
  "get_dependencies",
@@ -2576,8 +2432,8 @@ function buildGetSuggestionsTool(connection) {
2576
2432
  "get_suggestions",
2577
2433
  "List project suggestions sorted by vote score. Use this to see what the team thinks is important.",
2578
2434
  {
2579
- status: z.string().optional().describe("Filter by status: Open, Accepted, Rejected, Implemented"),
2580
- limit: z.number().optional().describe("Max results (default 20)")
2435
+ status: z2.string().optional().describe("Filter by status: Open, Accepted, Rejected, Implemented"),
2436
+ limit: z2.number().optional().describe("Max results (default 20)")
2581
2437
  },
2582
2438
  async ({ status: _status, limit: _limit }) => {
2583
2439
  try {
@@ -2597,13 +2453,16 @@ function buildGetSuggestionsTool(connection) {
2597
2453
  { annotations: { readOnlyHint: true } }
2598
2454
  );
2599
2455
  }
2456
+
2457
+ // src/tools/mutation-tools.ts
2458
+ import { z as z3 } from "zod";
2600
2459
  function buildPostToChatTool(connection) {
2601
2460
  return defineTool(
2602
2461
  "post_to_chat",
2603
2462
  "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
2463
  {
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.")
2464
+ message: z3.string().describe("The message to post to the team"),
2465
+ task_id: z3.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
2607
2466
  },
2608
2467
  async ({ message, task_id }) => {
2609
2468
  try {
@@ -2630,8 +2489,8 @@ function buildForceUpdateTaskStatusTool(connection) {
2630
2489
  "force_update_task_status",
2631
2490
  "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
2491
  {
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.")
2492
+ status: z3.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2493
+ task_id: z3.string().optional().describe("Child task ID to update. Omit to update the current task.")
2635
2494
  },
2636
2495
  async ({ status, task_id }) => {
2637
2496
  try {
@@ -2662,15 +2521,15 @@ function buildCreatePullRequestTool(connection, config) {
2662
2521
  "create_pull_request",
2663
2522
  "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
2523
  {
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(
2524
+ title: z3.string().describe("The PR title"),
2525
+ body: z3.string().describe("The PR description/body in markdown"),
2526
+ branch: z3.string().optional().describe(
2668
2527
  "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
2528
  ),
2670
- baseBranch: z.string().optional().describe(
2529
+ baseBranch: z3.string().optional().describe(
2671
2530
  "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
2672
2531
  ),
2673
- commitMessage: z.string().optional().describe(
2532
+ commitMessage: z3.string().optional().describe(
2674
2533
  "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
2675
2534
  )
2676
2535
  },
@@ -2748,7 +2607,7 @@ function buildAddDependencyTool(connection) {
2748
2607
  "add_dependency",
2749
2608
  "Add a dependency \u2014 this task cannot start until the specified task is merged to dev",
2750
2609
  {
2751
- depends_on_slug_or_id: z.string().describe("Slug or ID of the task this task depends on")
2610
+ depends_on_slug_or_id: z3.string().describe("Slug or ID of the task this task depends on")
2752
2611
  },
2753
2612
  async ({ depends_on_slug_or_id }) => {
2754
2613
  try {
@@ -2770,7 +2629,7 @@ function buildRemoveDependencyTool(connection) {
2770
2629
  "remove_dependency",
2771
2630
  "Remove a dependency from this task",
2772
2631
  {
2773
- depends_on_slug_or_id: z.string().describe("Slug or ID of the task to remove as dependency")
2632
+ depends_on_slug_or_id: z3.string().describe("Slug or ID of the task to remove as dependency")
2774
2633
  },
2775
2634
  async ({ depends_on_slug_or_id }) => {
2776
2635
  try {
@@ -2792,10 +2651,10 @@ function buildCreateFollowUpTaskTool(connection) {
2792
2651
  "create_follow_up_task",
2793
2652
  "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
2653
  {
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)")
2654
+ title: z3.string().describe("Follow-up task title"),
2655
+ description: z3.string().optional().describe("Brief description of the follow-up work"),
2656
+ plan: z3.string().optional().describe("Implementation plan if known"),
2657
+ story_point_value: z3.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
2799
2658
  },
2800
2659
  async ({ title, description, plan, story_point_value }) => {
2801
2660
  try {
@@ -2822,11 +2681,11 @@ function buildCreateSuggestionTool(connection) {
2822
2681
  "create_suggestion",
2823
2682
  "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
2683
  {
2825
- title: z.string().describe("Short title for the suggestion"),
2826
- description: z.string().optional().describe(
2684
+ title: z3.string().describe("Short title for the suggestion"),
2685
+ description: z3.string().optional().describe(
2827
2686
  "1-2 sentence description of what should change and why. Keep concise and project-focused."
2828
2687
  ),
2829
- tag_names: z.array(z.string()).optional().describe("Tag names to categorize the suggestion")
2688
+ tag_names: z3.array(z3.string()).optional().describe("Tag names to categorize the suggestion")
2830
2689
  },
2831
2690
  async ({ title, description, tag_names }) => {
2832
2691
  try {
@@ -2855,8 +2714,8 @@ function buildVoteSuggestionTool(connection) {
2855
2714
  "vote_suggestion",
2856
2715
  "Vote on a project suggestion. Use +1 to upvote or -1 to downvote.",
2857
2716
  {
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")
2717
+ suggestion_id: z3.string().describe("The suggestion ID to vote on"),
2718
+ value: z3.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
2860
2719
  },
2861
2720
  async ({ suggestion_id, value }) => {
2862
2721
  try {
@@ -2874,41 +2733,38 @@ function buildVoteSuggestionTool(connection) {
2874
2733
  }
2875
2734
  );
2876
2735
  }
2877
- function buildCommonTools(connection, config) {
2878
- const tools = [
2879
- buildReadTaskChatTool(connection),
2736
+ function buildMutationTools(connection, config) {
2737
+ return [
2880
2738
  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
2739
  buildCreatePullRequestTool(connection, config),
2891
2740
  buildAddDependencyTool(connection),
2892
2741
  buildRemoveDependencyTool(connection),
2893
- buildGetDependenciesTool(connection),
2894
2742
  buildCreateFollowUpTaskTool(connection),
2895
2743
  buildCreateSuggestionTool(connection),
2896
- buildVoteSuggestionTool(connection),
2897
- buildGetSuggestionsTool(connection)
2744
+ buildVoteSuggestionTool(connection)
2745
+ ];
2746
+ }
2747
+
2748
+ // src/tools/common-tools.ts
2749
+ function buildCommonTools(connection, config) {
2750
+ return [
2751
+ ...buildTaskContextTools(connection),
2752
+ buildGetDependenciesTool(connection),
2753
+ buildGetSuggestionsTool(connection),
2754
+ ...buildMutationTools(connection, config)
2898
2755
  ];
2899
- return tools;
2900
2756
  }
2901
2757
 
2902
2758
  // src/tools/pm-tools.ts
2903
- import { z as z2 } from "zod";
2759
+ import { z as z4 } from "zod";
2904
2760
  var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2905
2761
  function buildUpdateTaskTool(connection) {
2906
2762
  return defineTool(
2907
2763
  "update_task",
2908
2764
  "Save the finalized task plan and/or description",
2909
2765
  {
2910
- plan: z2.string().optional().describe("The task plan in markdown"),
2911
- description: z2.string().optional().describe("Updated task description")
2766
+ plan: z4.string().optional().describe("The task plan in markdown"),
2767
+ description: z4.string().optional().describe("Updated task description")
2912
2768
  },
2913
2769
  async ({ plan, description }) => {
2914
2770
  try {
@@ -2929,11 +2785,11 @@ function buildCreateSubtaskTool(connection) {
2929
2785
  "create_subtask",
2930
2786
  "Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
2931
2787
  {
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)
2788
+ title: z4.string().describe("Subtask title"),
2789
+ description: z4.string().optional().describe("Brief description"),
2790
+ plan: z4.string().optional().describe("Implementation plan in markdown"),
2791
+ ordinal: z4.number().optional().describe("Step/order number (0-based)"),
2792
+ storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
2937
2793
  },
2938
2794
  async ({ title, description, plan, ordinal, storyPointValue }) => {
2939
2795
  try {
@@ -2959,12 +2815,12 @@ function buildUpdateSubtaskTool(connection) {
2959
2815
  "update_subtask",
2960
2816
  "Update an existing subtask's fields",
2961
2817
  {
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)
2818
+ subtaskId: z4.string().describe("The subtask ID to update"),
2819
+ title: z4.string().optional(),
2820
+ description: z4.string().optional(),
2821
+ plan: z4.string().optional(),
2822
+ ordinal: z4.number().optional(),
2823
+ storyPointValue: z4.number().optional().describe(SP_DESCRIPTION)
2968
2824
  },
2969
2825
  async ({ subtaskId, title, description, plan, storyPointValue }) => {
2970
2826
  try {
@@ -2987,7 +2843,7 @@ function buildDeleteSubtaskTool(connection) {
2987
2843
  return defineTool(
2988
2844
  "delete_subtask",
2989
2845
  "Delete a subtask",
2990
- { subtaskId: z2.string().describe("The subtask ID to delete") },
2846
+ { subtaskId: z4.string().describe("The subtask ID to delete") },
2991
2847
  async ({ subtaskId }) => {
2992
2848
  try {
2993
2849
  await connection.call("deleteSubtask", {
@@ -3025,7 +2881,7 @@ function buildPackTools(connection) {
3025
2881
  "start_child_cloud_build",
3026
2882
  "Start a cloud build for a child task. The child must be in Open status with story points and an agent assigned.",
3027
2883
  {
3028
- childTaskId: z2.string().describe("The child task ID to start a cloud build for")
2884
+ childTaskId: z4.string().describe("The child task ID to start a cloud build for")
3029
2885
  },
3030
2886
  async ({ childTaskId }) => {
3031
2887
  try {
@@ -3045,7 +2901,7 @@ function buildPackTools(connection) {
3045
2901
  "stop_child_build",
3046
2902
  "Stop a running cloud build for a child task. Sends a stop signal to the child agent.",
3047
2903
  {
3048
- childTaskId: z2.string().describe("The child task ID whose build should be stopped")
2904
+ childTaskId: z4.string().describe("The child task ID whose build should be stopped")
3049
2905
  },
3050
2906
  async ({ childTaskId }) => {
3051
2907
  try {
@@ -3065,7 +2921,7 @@ function buildPackTools(connection) {
3065
2921
  "approve_and_merge_pr",
3066
2922
  "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
2923
  {
3068
- childTaskId: z2.string().describe("The child task ID whose PR should be approved and merged")
2924
+ childTaskId: z4.string().describe("The child task ID whose PR should be approved and merged")
3069
2925
  },
3070
2926
  async ({ childTaskId }) => {
3071
2927
  try {
@@ -3103,7 +2959,7 @@ function buildPmTools(connection, options) {
3103
2959
  }
3104
2960
 
3105
2961
  // src/tools/discovery-tools.ts
3106
- import { z as z3 } from "zod";
2962
+ import { z as z5 } from "zod";
3107
2963
  var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
3108
2964
  function buildDiscoveryTools(connection) {
3109
2965
  return [
@@ -3111,11 +2967,11 @@ function buildDiscoveryTools(connection) {
3111
2967
  "update_task_properties",
3112
2968
  "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
3113
2969
  {
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')")
2970
+ title: z5.string().optional().describe("The new task title"),
2971
+ storyPointValue: z5.number().optional().describe(SP_DESCRIPTION2),
2972
+ tagNames: z5.array(z5.string()).optional().describe("Array of tag names to assign"),
2973
+ githubPRUrl: z5.string().url().optional().describe("GitHub pull request URL to link to this task"),
2974
+ githubBranch: z5.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
3119
2975
  },
3120
2976
  async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
3121
2977
  try {
@@ -3146,14 +3002,14 @@ function buildDiscoveryTools(connection) {
3146
3002
  }
3147
3003
 
3148
3004
  // src/tools/code-review-tools.ts
3149
- import { z as z4 } from "zod";
3005
+ import { z as z6 } from "zod";
3150
3006
  function buildCodeReviewTools(connection) {
3151
3007
  return [
3152
3008
  defineTool(
3153
3009
  "approve_code_review",
3154
3010
  "Approve the code review. Use this when the code passes all review criteria and is ready to merge.",
3155
3011
  {
3156
- summary: z4.string().describe("Brief summary of what was reviewed and why it looks good")
3012
+ summary: z6.string().describe("Brief summary of what was reviewed and why it looks good")
3157
3013
  },
3158
3014
  async ({ summary }) => {
3159
3015
  const content = `**Code Review: Approved** :white_check_mark:
@@ -3176,15 +3032,15 @@ ${summary}`;
3176
3032
  "request_code_changes",
3177
3033
  "Request changes during code review. Use this when substantive issues are found that need to be fixed before merge.",
3178
3034
  {
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")
3035
+ issues: z6.array(
3036
+ z6.object({
3037
+ file: z6.string().describe("File path where the issue was found"),
3038
+ line: z6.number().optional().describe("Line number (if applicable)"),
3039
+ severity: z6.enum(["critical", "major", "minor"]).describe("Issue severity"),
3040
+ description: z6.string().describe("What is wrong and how to fix it")
3185
3041
  })
3186
3042
  ).describe("List of issues found during review"),
3187
- summary: z4.string().describe("Brief overall summary of the review findings")
3043
+ summary: z6.string().describe("Brief overall summary of the review findings")
3188
3044
  },
3189
3045
  async ({ issues, summary }) => {
3190
3046
  const issueLines = issues.map((issue) => {
@@ -3214,10 +3070,10 @@ ${issueLines}`;
3214
3070
  }
3215
3071
 
3216
3072
  // src/tools/debug-tools.ts
3217
- import { z as z7 } from "zod";
3073
+ import { z as z9 } from "zod";
3218
3074
 
3219
3075
  // src/tools/telemetry-tools.ts
3220
- import { z as z5 } from "zod";
3076
+ import { z as z7 } from "zod";
3221
3077
 
3222
3078
  // src/debug/telemetry-injector.ts
3223
3079
  var BUFFER_SIZE = 200;
@@ -3612,12 +3468,12 @@ function buildGetTelemetryTool(manager) {
3612
3468
  "debug_get_telemetry",
3613
3469
  "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
3470
  {
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)")
3471
+ type: z7.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3472
+ urlPattern: z7.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3473
+ minDuration: z7.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3474
+ errorOnly: z7.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3475
+ since: z7.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3476
+ limit: z7.number().optional().describe("Max events to return (default: 20, from most recent)")
3621
3477
  },
3622
3478
  async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3623
3479
  const clientOrErr = requireDebugClient(manager);
@@ -3687,7 +3543,7 @@ function buildTelemetryTools(manager) {
3687
3543
  }
3688
3544
 
3689
3545
  // src/tools/client-debug-tools.ts
3690
- import { z as z6 } from "zod";
3546
+ import { z as z8 } from "zod";
3691
3547
  function requirePlaywrightClient(manager) {
3692
3548
  if (!manager.isClientDebugMode()) {
3693
3549
  return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
@@ -3707,11 +3563,11 @@ function buildClientBreakpointTools(manager) {
3707
3563
  "debug_set_client_breakpoint",
3708
3564
  "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
3565
  {
3710
- file: z6.string().describe(
3566
+ file: z8.string().describe(
3711
3567
  "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3712
3568
  ),
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")
3569
+ line: z8.number().describe("Line number (1-based) in the original source file"),
3570
+ condition: z8.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3715
3571
  },
3716
3572
  async ({ file, line, condition }) => {
3717
3573
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3733,7 +3589,7 @@ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3733
3589
  "debug_remove_client_breakpoint",
3734
3590
  "Remove a previously set client-side breakpoint by its ID.",
3735
3591
  {
3736
- breakpointId: z6.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3592
+ breakpointId: z8.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3737
3593
  },
3738
3594
  async ({ breakpointId }) => {
3739
3595
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3813,8 +3669,8 @@ ${JSON.stringify(queuedHits, null, 2)}`
3813
3669
  "debug_evaluate_client",
3814
3670
  "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
3671
  {
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.")
3672
+ expression: z8.string().describe("JavaScript expression to evaluate in the browser context"),
3673
+ frameIndex: z8.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3818
3674
  },
3819
3675
  async ({ expression, frameIndex }) => {
3820
3676
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3887,7 +3743,7 @@ function buildClientInteractionTools(manager) {
3887
3743
  "debug_navigate_client",
3888
3744
  "Navigate the headless browser to a specific URL. Use this to reproduce specific flows or visit different pages.",
3889
3745
  {
3890
- url: z6.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3746
+ url: z8.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3891
3747
  },
3892
3748
  async ({ url }) => {
3893
3749
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3904,7 +3760,7 @@ function buildClientInteractionTools(manager) {
3904
3760
  "debug_click_client",
3905
3761
  "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
3762
  {
3907
- selector: z6.string().describe(
3763
+ selector: z8.string().describe(
3908
3764
  "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3909
3765
  )
3910
3766
  },
@@ -3926,8 +3782,8 @@ function buildClientConsoleTool(manager) {
3926
3782
  "debug_get_client_console",
3927
3783
  "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3928
3784
  {
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)")
3785
+ level: z8.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3786
+ limit: z8.number().optional().describe("Maximum number of recent messages to return (default: all)")
3931
3787
  },
3932
3788
  // oxlint-disable-next-line require-await
3933
3789
  async ({ level, limit }) => {
@@ -3954,8 +3810,8 @@ function buildClientNetworkTool(manager) {
3954
3810
  "debug_get_client_network",
3955
3811
  "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3956
3812
  {
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)")
3813
+ filter: z8.string().optional().describe("Regex pattern to filter requests by URL"),
3814
+ limit: z8.number().optional().describe("Maximum number of recent requests to return (default: all)")
3959
3815
  },
3960
3816
  // oxlint-disable-next-line require-await
3961
3817
  async ({ filter, limit }) => {
@@ -3983,7 +3839,7 @@ function buildClientErrorsTool(manager) {
3983
3839
  "debug_get_client_errors",
3984
3840
  "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3985
3841
  {
3986
- limit: z6.number().optional().describe("Maximum number of recent errors to return (default: all)")
3842
+ limit: z8.number().optional().describe("Maximum number of recent errors to return (default: all)")
3987
3843
  },
3988
3844
  // oxlint-disable-next-line require-await
3989
3845
  async ({ limit }) => {
@@ -4077,12 +3933,12 @@ function buildDebugLifecycleTools(manager) {
4077
3933
  "debug_enter_mode",
4078
3934
  "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
3935
  {
4080
- hypothesis: z7.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
4081
- serverSide: z7.boolean().optional().describe(
3936
+ hypothesis: z9.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3937
+ serverSide: z9.boolean().optional().describe(
4082
3938
  "Enable server-side Node.js debugging (default: true if clientSide is not set)"
4083
3939
  ),
4084
- clientSide: z7.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
4085
- previewUrl: z7.string().optional().describe(
3940
+ clientSide: z9.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
3941
+ previewUrl: z9.string().optional().describe(
4086
3942
  "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
4087
3943
  )
4088
3944
  },
@@ -4118,9 +3974,9 @@ function buildBreakpointTools(manager) {
4118
3974
  "debug_set_breakpoint",
4119
3975
  "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
3976
  {
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")
3977
+ file: z9.string().describe("Absolute or relative file path to set the breakpoint in"),
3978
+ line: z9.number().describe("Line number (1-based) to set the breakpoint on"),
3979
+ condition: z9.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
4124
3980
  },
4125
3981
  async ({ file, line, condition }) => {
4126
3982
  const clientOrErr = requireDebugClient2(manager);
@@ -4142,7 +3998,7 @@ Breakpoint ID: ${breakpointId}`
4142
3998
  "debug_remove_breakpoint",
4143
3999
  "Remove a previously set breakpoint by its ID.",
4144
4000
  {
4145
- breakpointId: z7.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4001
+ breakpointId: z9.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4146
4002
  },
4147
4003
  async ({ breakpointId }) => {
4148
4004
  const clientOrErr = requireDebugClient2(manager);
@@ -4223,8 +4079,8 @@ ${JSON.stringify(queuedHits, null, 2)}`
4223
4079
  "debug_evaluate",
4224
4080
  "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
4081
  {
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.")
4082
+ expression: z9.string().describe("The JavaScript expression to evaluate"),
4083
+ frameIndex: z9.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4228
4084
  },
4229
4085
  async ({ expression, frameIndex }) => {
4230
4086
  const clientOrErr = requireDebugClient2(manager);
@@ -4252,12 +4108,12 @@ function buildProbeManagementTools(manager) {
4252
4108
  "debug_add_probe",
4253
4109
  "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
4110
  {
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(
4111
+ file: z9.string().describe("File path to probe"),
4112
+ line: z9.number().describe("Line number (1-based) to probe"),
4113
+ expressions: z9.array(z9.string()).describe(
4258
4114
  'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4259
4115
  ),
4260
- label: z7.string().optional().describe("Optional label for this probe (defaults to file:line)")
4116
+ label: z9.string().optional().describe("Optional label for this probe (defaults to file:line)")
4261
4117
  },
4262
4118
  async ({ file, line, expressions, label }) => {
4263
4119
  const clientOrErr = requireDebugClient2(manager);
@@ -4282,7 +4138,7 @@ Trigger the code path, then use debug_get_probe_results to see captured values.`
4282
4138
  "debug_remove_probe",
4283
4139
  "Remove a previously set debug probe by its ID.",
4284
4140
  {
4285
- probeId: z7.string().describe("The probe ID returned by debug_add_probe")
4141
+ probeId: z9.string().describe("The probe ID returned by debug_add_probe")
4286
4142
  },
4287
4143
  async ({ probeId }) => {
4288
4144
  const clientOrErr = requireDebugClient2(manager);
@@ -4322,9 +4178,9 @@ function buildProbeResultTools(manager) {
4322
4178
  "debug_get_probe_results",
4323
4179
  "Fetch captured probe hit data. Returns expression values from each time a probed line executed.",
4324
4180
  {
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)")
4181
+ probeId: z9.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4182
+ label: z9.string().optional().describe("Filter results by probe label"),
4183
+ limit: z9.number().optional().describe("Maximum number of recent hits to return (default: all)")
4328
4184
  },
4329
4185
  async ({ probeId, label, limit }) => {
4330
4186
  const clientOrErr = requireDebugClient2(manager);
@@ -4795,10 +4651,20 @@ function stopTypingIfNeeded(host, isTyping) {
4795
4651
  if (isTyping) host.connection.sendTypingStop();
4796
4652
  }
4797
4653
  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];
4654
+ if (turnToolCalls.length === 0) {
4655
+ host.pendingToolOutputs.length = 0;
4656
+ return;
4657
+ }
4658
+ const outputsByTool = /* @__PURE__ */ new Map();
4659
+ for (const entry of host.pendingToolOutputs) {
4660
+ const list = outputsByTool.get(entry.tool) ?? [];
4661
+ list.push(entry.output);
4662
+ outputsByTool.set(entry.tool, list);
4663
+ }
4664
+ for (const call of turnToolCalls) {
4665
+ const list = outputsByTool.get(call.tool);
4666
+ if (list && list.length > 0) {
4667
+ call.output = list.shift();
4802
4668
  }
4803
4669
  }
4804
4670
  host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
@@ -5120,7 +4986,7 @@ function buildHooks(host) {
5120
4986
  output,
5121
4987
  isError: false
5122
4988
  });
5123
- host.pendingToolOutputs.push(output);
4989
+ host.pendingToolOutputs.push({ tool: input.tool_name, output });
5124
4990
  if (input.tool_name === "mcp__conveyor__create_pull_request") {
5125
4991
  try {
5126
4992
  const props = await host.connection.getTaskProperties();
@@ -6441,67 +6307,8 @@ var ProjectConnection = class {
6441
6307
  }
6442
6308
  };
6443
6309
 
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
6310
  // src/runner/commit-watcher.ts
6504
- import { execSync as execSync3 } from "child_process";
6311
+ import { execSync as execSync2 } from "child_process";
6505
6312
  var logger5 = createServiceLogger("CommitWatcher");
6506
6313
  var CommitWatcher = class {
6507
6314
  constructor(config, callbacks) {
@@ -6535,7 +6342,7 @@ var CommitWatcher = class {
6535
6342
  this.isSyncing = false;
6536
6343
  }
6537
6344
  getLocalHeadSha() {
6538
- return execSync3("git rev-parse HEAD", {
6345
+ return execSync2("git rev-parse HEAD", {
6539
6346
  cwd: this.config.projectDir,
6540
6347
  stdio: ["ignore", "pipe", "ignore"]
6541
6348
  }).toString().trim();
@@ -6543,12 +6350,12 @@ var CommitWatcher = class {
6543
6350
  poll() {
6544
6351
  if (!this.branch || this.isSyncing) return;
6545
6352
  try {
6546
- execSync3(`git fetch origin ${this.branch} --quiet`, {
6353
+ execSync2(`git fetch origin ${this.branch} --quiet`, {
6547
6354
  cwd: this.config.projectDir,
6548
6355
  stdio: "ignore",
6549
6356
  timeout: 3e4
6550
6357
  });
6551
- const remoteSha = execSync3(`git rev-parse origin/${this.branch}`, {
6358
+ const remoteSha = execSync2(`git rev-parse origin/${this.branch}`, {
6552
6359
  cwd: this.config.projectDir,
6553
6360
  stdio: ["ignore", "pipe", "ignore"]
6554
6361
  }).toString().trim();
@@ -6569,12 +6376,12 @@ var CommitWatcher = class {
6569
6376
  let latestMessage = "";
6570
6377
  let latestAuthor = "";
6571
6378
  try {
6572
- const countOutput = execSync3(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
6379
+ const countOutput = execSync2(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
6573
6380
  cwd: this.config.projectDir,
6574
6381
  stdio: ["ignore", "pipe", "ignore"]
6575
6382
  }).toString().trim();
6576
6383
  commitCount = parseInt(countOutput, 10) || 1;
6577
- const logOutput = execSync3(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
6384
+ const logOutput = execSync2(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
6578
6385
  cwd: this.config.projectDir,
6579
6386
  stdio: ["ignore", "pipe", "ignore"]
6580
6387
  }).toString().trim();
@@ -6609,7 +6416,7 @@ var CommitWatcher = class {
6609
6416
  };
6610
6417
 
6611
6418
  // src/runner/worktree.ts
6612
- import { execSync as execSync4 } from "child_process";
6419
+ import { execSync as execSync3 } from "child_process";
6613
6420
  import { existsSync as existsSync2 } from "fs";
6614
6421
  import { join as join4 } from "path";
6615
6422
  var WORKTREE_DIR = ".worktrees";
@@ -6624,7 +6431,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6624
6431
  return worktreePath;
6625
6432
  }
6626
6433
  try {
6627
- execSync4(`git checkout --detach origin/${branch}`, {
6434
+ execSync3(`git checkout --detach origin/${branch}`, {
6628
6435
  cwd: worktreePath,
6629
6436
  stdio: "ignore"
6630
6437
  });
@@ -6634,7 +6441,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6634
6441
  return worktreePath;
6635
6442
  }
6636
6443
  const ref = branch ? `origin/${branch}` : "HEAD";
6637
- execSync4(`git worktree add --detach "${worktreePath}" ${ref}`, {
6444
+ execSync3(`git worktree add --detach "${worktreePath}" ${ref}`, {
6638
6445
  cwd: projectDir,
6639
6446
  stdio: "ignore"
6640
6447
  });
@@ -6642,7 +6449,7 @@ function ensureWorktree(projectDir, taskId, branch) {
6642
6449
  }
6643
6450
  function detachWorktreeBranch(projectDir, branch) {
6644
6451
  try {
6645
- const output = execSync4("git worktree list --porcelain", {
6452
+ const output = execSync3("git worktree list --porcelain", {
6646
6453
  cwd: projectDir,
6647
6454
  encoding: "utf-8"
6648
6455
  });
@@ -6655,7 +6462,7 @@ function detachWorktreeBranch(projectDir, branch) {
6655
6462
  const worktreePath = worktreeLine.replace("worktree ", "");
6656
6463
  if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
6657
6464
  try {
6658
- execSync4("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
6465
+ execSync3("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
6659
6466
  } catch {
6660
6467
  }
6661
6468
  }
@@ -6666,7 +6473,7 @@ function removeWorktree(projectDir, taskId) {
6666
6473
  const worktreePath = join4(projectDir, WORKTREE_DIR, taskId);
6667
6474
  if (!existsSync2(worktreePath)) return;
6668
6475
  try {
6669
- execSync4(`git worktree remove "${worktreePath}" --force`, {
6476
+ execSync3(`git worktree remove "${worktreePath}" --force`, {
6670
6477
  cwd: projectDir,
6671
6478
  stdio: "ignore"
6672
6479
  });
@@ -6674,14 +6481,67 @@ function removeWorktree(projectDir, taskId) {
6674
6481
  }
6675
6482
  }
6676
6483
 
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";
6484
+ // src/setup/commands.ts
6485
+ import { spawn, execSync as execSync4 } from "child_process";
6486
+ function runSetupCommand(cmd, cwd, onOutput) {
6487
+ return new Promise((resolve2, reject) => {
6488
+ const child = spawn("sh", ["-c", cmd], {
6489
+ cwd,
6490
+ stdio: ["ignore", "pipe", "pipe"],
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.on("close", (code) => {
6500
+ if (code === 0) {
6501
+ resolve2();
6502
+ } else {
6503
+ reject(new Error(`Setup command exited with code ${code}`));
6504
+ }
6505
+ });
6506
+ child.on("error", (err) => {
6507
+ reject(err);
6508
+ });
6509
+ });
6510
+ }
6511
+ var AUTH_TOKEN_TIMEOUT_MS = 3e4;
6512
+ function runAuthTokenCommand(cmd, userEmail, cwd) {
6513
+ try {
6514
+ const output = execSync4(`${cmd} ${JSON.stringify(userEmail)}`, {
6515
+ cwd,
6516
+ timeout: AUTH_TOKEN_TIMEOUT_MS,
6517
+ stdio: ["ignore", "pipe", "ignore"],
6518
+ env: { ...process.env }
6519
+ });
6520
+ const token = output.toString().trim();
6521
+ return token || null;
6522
+ } catch {
6523
+ return null;
6524
+ }
6525
+ }
6526
+ function runStartCommand(cmd, cwd, onOutput) {
6527
+ const child = spawn("sh", ["-c", cmd], {
6528
+ cwd,
6529
+ stdio: ["ignore", "pipe", "pipe"],
6530
+ detached: true,
6531
+ env: { ...process.env }
6532
+ });
6533
+ child.stdout.on("data", (chunk) => {
6534
+ onOutput("stdout", chunk.toString());
6535
+ });
6536
+ child.stderr.on("data", (chunk) => {
6537
+ onOutput("stderr", chunk.toString());
6538
+ });
6539
+ child.unref();
6540
+ return child;
6541
+ }
6682
6542
 
6683
6543
  // src/tools/project-tools.ts
6684
- import { z as z8 } from "zod";
6544
+ import { z as z10 } from "zod";
6685
6545
  function buildTaskListTools(connection) {
6686
6546
  const projectId = connection.projectId;
6687
6547
  return [
@@ -6689,9 +6549,9 @@ function buildTaskListTools(connection) {
6689
6549
  "list_tasks",
6690
6550
  "List tasks in the project. Optionally filter by status or assignee.",
6691
6551
  {
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)")
6552
+ status: z10.string().optional().describe("Filter by task status"),
6553
+ assigneeId: z10.string().optional().describe("Filter by assigned user ID"),
6554
+ limit: z10.number().optional().describe("Max number of tasks to return (default 50)")
6695
6555
  },
6696
6556
  async (params) => {
6697
6557
  try {
@@ -6708,7 +6568,7 @@ function buildTaskListTools(connection) {
6708
6568
  defineTool(
6709
6569
  "get_task",
6710
6570
  "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") },
6571
+ { task_id: z10.string().describe("The task ID to look up") },
6712
6572
  async ({ task_id }) => {
6713
6573
  try {
6714
6574
  const task = await connection.call("getProjectTask", { projectId, taskId: task_id });
@@ -6725,10 +6585,10 @@ function buildTaskListTools(connection) {
6725
6585
  "search_tasks",
6726
6586
  "Search tasks by tags, text query, or status filters.",
6727
6587
  {
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)")
6588
+ tagNames: z10.array(z10.string()).optional().describe("Filter by tag names"),
6589
+ searchQuery: z10.string().optional().describe("Text search in title/description"),
6590
+ statusFilters: z10.array(z10.string()).optional().describe("Filter by statuses"),
6591
+ limit: z10.number().optional().describe("Max results (default 20)")
6732
6592
  },
6733
6593
  async (params) => {
6734
6594
  try {
@@ -6781,17 +6641,17 @@ function buildProjectInfoTools(connection) {
6781
6641
  )
6782
6642
  ];
6783
6643
  }
6784
- function buildMutationTools(connection) {
6644
+ function buildMutationTools2(connection) {
6785
6645
  const projectId = connection.projectId;
6786
6646
  return [
6787
6647
  defineTool(
6788
6648
  "create_task",
6789
6649
  "Create a new task in the project.",
6790
6650
  {
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)")
6651
+ title: z10.string().describe("Task title"),
6652
+ description: z10.string().optional().describe("Task description"),
6653
+ plan: z10.string().optional().describe("Implementation plan in markdown"),
6654
+ status: z10.string().optional().describe("Initial status (default: Planning)")
6795
6655
  },
6796
6656
  async (params) => {
6797
6657
  try {
@@ -6808,12 +6668,12 @@ function buildMutationTools(connection) {
6808
6668
  "update_task",
6809
6669
  "Update an existing task's title, description, plan, status, or assignee.",
6810
6670
  {
6811
- task_id: z8.string().describe("The task ID to update"),
6812
- title: z8.string().optional().describe("New title"),
6813
- description: z8.string().optional().describe("New description"),
6814
- plan: z8.string().optional().describe("New plan in markdown"),
6815
- status: z8.string().optional().describe("New status"),
6816
- assignedUserId: z8.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6671
+ task_id: z10.string().describe("The task ID to update"),
6672
+ title: z10.string().optional().describe("New title"),
6673
+ description: z10.string().optional().describe("New description"),
6674
+ plan: z10.string().optional().describe("New plan in markdown"),
6675
+ status: z10.string().optional().describe("New status"),
6676
+ assignedUserId: z10.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6817
6677
  },
6818
6678
  async ({ task_id, ...fields }) => {
6819
6679
  try {
@@ -6832,7 +6692,7 @@ function buildProjectTools(connection) {
6832
6692
  return [
6833
6693
  ...buildTaskListTools(connection),
6834
6694
  ...buildProjectInfoTools(connection),
6835
- ...buildMutationTools(connection)
6695
+ ...buildMutationTools2(connection)
6836
6696
  ];
6837
6697
  }
6838
6698
 
@@ -7027,14 +6887,204 @@ async function handleProjectChatMessage(message, connection, projectDir, session
7027
6887
  }
7028
6888
  }
7029
6889
 
7030
- // src/runner/project-runner.ts
6890
+ // src/runner/project-runner-helpers.ts
6891
+ function parseErrorMessage(error) {
6892
+ return error instanceof Error ? error.message : String(error);
6893
+ }
6894
+
6895
+ // src/runner/project-runner-children.ts
6896
+ import { fork } from "child_process";
6897
+ import { execSync as execSync7 } from "child_process";
6898
+ import * as path from "path";
6899
+ import { fileURLToPath as fileURLToPath2 } from "url";
6900
+
6901
+ // src/runner/project-runner-git.ts
6902
+ import { execSync as execSync6 } from "child_process";
6903
+
6904
+ // src/runner/project-runner-sync.ts
6905
+ import { execSync as execSync5 } from "child_process";
7031
6906
  var logger7 = createServiceLogger("ProjectRunner");
7032
- var __filename = fileURLToPath2(import.meta.url);
7033
- var __dirname = path.dirname(__filename);
7034
- var HEARTBEAT_INTERVAL_MS = 3e4;
7035
- var MAX_CONCURRENT = Math.max(1, parseInt(process.env.CONVEYOR_MAX_CONCURRENT ?? "10", 10) || 10);
7036
- var STOP_TIMEOUT_MS = 3e4;
7037
6907
  var START_CMD_KILL_TIMEOUT_MS = 5e3;
6908
+ async function executeSetupCommand(projectDir, connection) {
6909
+ const cmd = process.env.CONVEYOR_SETUP_COMMAND;
6910
+ if (!cmd) return;
6911
+ logger7.info("Running setup command", { command: cmd });
6912
+ try {
6913
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
6914
+ connection.sendEvent({ type: "setup_output", stream, data });
6915
+ (stream === "stderr" ? process.stderr : process.stdout).write(data);
6916
+ });
6917
+ logger7.info("Setup command completed");
6918
+ } catch (error) {
6919
+ const msg = parseErrorMessage(error);
6920
+ logger7.error("Setup command failed", { error: msg });
6921
+ connection.sendEvent({ type: "setup_error", message: msg });
6922
+ throw error;
6923
+ }
6924
+ }
6925
+ function executeStartCommand(projectDir, state, connection) {
6926
+ const cmd = process.env.CONVEYOR_START_COMMAND;
6927
+ if (!cmd) return;
6928
+ logger7.info("Running start command", { command: cmd });
6929
+ const child = runStartCommand(cmd, projectDir, (stream, data) => {
6930
+ connection.sendEvent({ type: "start_command_output", stream, data });
6931
+ (stream === "stderr" ? process.stderr : process.stdout).write(data);
6932
+ });
6933
+ state.child = child;
6934
+ state.running = true;
6935
+ child.on("exit", (code, signal) => {
6936
+ state.running = false;
6937
+ state.child = null;
6938
+ logger7.info("Start command exited", { code, signal });
6939
+ connection.sendEvent({ type: "start_command_exited", code, signal });
6940
+ });
6941
+ child.on("error", (err) => {
6942
+ state.running = false;
6943
+ state.child = null;
6944
+ logger7.error("Start command error", { error: err.message });
6945
+ });
6946
+ }
6947
+ async function killStartCommand(state) {
6948
+ const child = state.child;
6949
+ if (!child || !state.running) return;
6950
+ try {
6951
+ if (child.pid) process.kill(-child.pid, "SIGTERM");
6952
+ } catch {
6953
+ child.kill("SIGTERM");
6954
+ }
6955
+ await new Promise((resolve2) => {
6956
+ const timer = setTimeout(() => {
6957
+ if (state.running && child.pid) {
6958
+ try {
6959
+ process.kill(-child.pid, "SIGKILL");
6960
+ } catch {
6961
+ child.kill("SIGKILL");
6962
+ }
6963
+ }
6964
+ resolve2();
6965
+ }, START_CMD_KILL_TIMEOUT_MS);
6966
+ child.on("exit", () => {
6967
+ clearTimeout(timer);
6968
+ resolve2();
6969
+ });
6970
+ });
6971
+ state.child = null;
6972
+ state.running = false;
6973
+ }
6974
+ async function restartStartCommand(projectDir, state, connection) {
6975
+ await killStartCommand(state);
6976
+ executeStartCommand(projectDir, state, connection);
6977
+ }
6978
+ async function handleSyncEnvironment(projectDir, branchSwitchCommand, state, connection, callback) {
6979
+ try {
6980
+ await killStartCommand(state);
6981
+ const cmd = branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
6982
+ if (cmd) {
6983
+ try {
6984
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
6985
+ connection.sendEvent({ type: "sync_output", stream, data });
6986
+ });
6987
+ } catch (err) {
6988
+ const msg = parseErrorMessage(err);
6989
+ logger7.error("Branch switch sync command failed", { error: msg });
6990
+ }
6991
+ }
6992
+ executeStartCommand(projectDir, state, connection);
6993
+ callback?.({ ok: true });
6994
+ } catch (err) {
6995
+ const msg = parseErrorMessage(err);
6996
+ logger7.error("Environment sync failed", { error: msg });
6997
+ callback?.({ ok: false, error: msg });
6998
+ }
6999
+ }
7000
+ function getChangedFiles(projectDir, previousSha, newSha) {
7001
+ try {
7002
+ return execSync5(`git diff --name-only ${previousSha}..${newSha}`, {
7003
+ cwd: projectDir,
7004
+ stdio: ["ignore", "pipe", "ignore"]
7005
+ }).toString().trim().split("\n").filter(Boolean);
7006
+ } catch {
7007
+ return [];
7008
+ }
7009
+ }
7010
+ function runIndividualSyncSteps(projectDir, needsInstall, needsPrisma, stepsRun) {
7011
+ if (needsInstall) {
7012
+ try {
7013
+ execSync5("bun install", { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
7014
+ stepsRun.push("install");
7015
+ } catch (err) {
7016
+ const msg = parseErrorMessage(err);
7017
+ logger7.error("bun install failed", { error: msg });
7018
+ }
7019
+ }
7020
+ if (needsPrisma) {
7021
+ try {
7022
+ execSync5("bunx prisma generate", { cwd: projectDir, timeout: 6e4, stdio: "pipe" });
7023
+ execSync5("bunx prisma db push --accept-data-loss", {
7024
+ cwd: projectDir,
7025
+ timeout: 6e4,
7026
+ stdio: "pipe"
7027
+ });
7028
+ stepsRun.push("prisma");
7029
+ } catch (err) {
7030
+ const msg = parseErrorMessage(err);
7031
+ logger7.error("Prisma sync failed", { error: msg });
7032
+ }
7033
+ }
7034
+ }
7035
+ async function syncDependencies(projectDir, branchSwitchCommand, connection, changedFiles, stepsRun) {
7036
+ const needsInstall = changedFiles.some(
7037
+ (f) => f === "package.json" || f === "bun.lockb" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
7038
+ );
7039
+ const needsPrisma = changedFiles.some(
7040
+ (f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
7041
+ );
7042
+ const cmd = branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7043
+ if (cmd && (needsInstall || needsPrisma)) {
7044
+ try {
7045
+ await runSetupCommand(cmd, projectDir, (stream, data) => {
7046
+ connection.sendEvent({ type: "sync_output", stream, data });
7047
+ });
7048
+ stepsRun.push("branchSwitchCommand");
7049
+ } catch (err) {
7050
+ const msg = parseErrorMessage(err);
7051
+ logger7.error("Branch switch command failed", { error: msg });
7052
+ }
7053
+ } else if (!cmd) {
7054
+ runIndividualSyncSteps(projectDir, needsInstall, needsPrisma, stepsRun);
7055
+ }
7056
+ }
7057
+ async function smartSync(projectDir, branchSwitchCommand, state, connection, previousSha, newSha, branch) {
7058
+ if (hasUncommittedChanges(projectDir)) {
7059
+ connection.sendEvent({
7060
+ type: "commit_watch_warning",
7061
+ message: "Working tree has uncommitted changes. Auto-pull skipped."
7062
+ });
7063
+ return ["skipped:dirty_tree"];
7064
+ }
7065
+ await killStartCommand(state);
7066
+ try {
7067
+ execSync5(`git pull origin ${branch}`, {
7068
+ cwd: projectDir,
7069
+ stdio: "pipe",
7070
+ timeout: 6e4
7071
+ });
7072
+ } catch (err) {
7073
+ const msg = parseErrorMessage(err);
7074
+ logger7.error("Git pull failed during commit sync", { error: msg });
7075
+ executeStartCommand(projectDir, state, connection);
7076
+ return ["error:pull"];
7077
+ }
7078
+ const stepsRun = ["pull"];
7079
+ const changedFiles = getChangedFiles(projectDir, previousSha, newSha);
7080
+ await syncDependencies(projectDir, branchSwitchCommand, connection, changedFiles, stepsRun);
7081
+ executeStartCommand(projectDir, state, connection);
7082
+ stepsRun.push("startCommand");
7083
+ return stepsRun;
7084
+ }
7085
+
7086
+ // src/runner/project-runner-git.ts
7087
+ var logger8 = createServiceLogger("ProjectRunner");
7038
7088
  function setupWorkDir(projectDir, assignment) {
7039
7089
  const { taskId, branch, devBranch, useWorktree } = assignment;
7040
7090
  const shortId = taskId.slice(0, 8);
@@ -7047,21 +7097,105 @@ function setupWorkDir(projectDir, assignment) {
7047
7097
  }
7048
7098
  if (branch && branch !== devBranch) {
7049
7099
  if (hasUncommittedChanges(workDir)) {
7050
- logger7.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
7100
+ logger8.warn("Uncommitted changes, skipping checkout", { taskId: shortId, branch });
7051
7101
  } else {
7052
7102
  try {
7053
- execSync5(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
7103
+ execSync6(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
7054
7104
  } catch {
7055
7105
  try {
7056
- execSync5(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
7106
+ execSync6(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
7057
7107
  } catch {
7058
- logger7.warn("Could not checkout branch", { taskId: shortId, branch });
7108
+ logger8.warn("Could not checkout branch", { taskId: shortId, branch });
7059
7109
  }
7060
7110
  }
7061
7111
  }
7062
7112
  }
7063
7113
  return { workDir, usesWorktree: shouldWorktree };
7064
7114
  }
7115
+ function checkoutWorkspaceBranch(projectDir) {
7116
+ const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
7117
+ if (!workspaceBranch) return;
7118
+ const currentBranch = getCurrentBranch(projectDir);
7119
+ if (currentBranch === workspaceBranch) return;
7120
+ if (hasUncommittedChanges(projectDir)) {
7121
+ logger8.warn("Uncommitted changes, skipping workspace branch checkout");
7122
+ return;
7123
+ }
7124
+ try {
7125
+ execSync6(`git fetch origin ${workspaceBranch}`, { cwd: projectDir, stdio: "pipe" });
7126
+ execSync6(`git checkout ${workspaceBranch}`, { cwd: projectDir, stdio: "pipe" });
7127
+ logger8.info("Checked out workspace branch", { workspaceBranch });
7128
+ } catch {
7129
+ logger8.warn("Failed to checkout workspace branch", { workspaceBranch });
7130
+ }
7131
+ }
7132
+ async function handleSwitchBranch(projectDir, branchSwitchCommand, startCmd, connection, commitWatcher, data, callback) {
7133
+ try {
7134
+ try {
7135
+ execSync6("git fetch origin", { cwd: projectDir, stdio: "pipe" });
7136
+ } catch {
7137
+ logger8.warn("Git fetch failed during branch switch");
7138
+ }
7139
+ detachWorktreeBranch(projectDir, data.branch);
7140
+ try {
7141
+ execSync6(`git checkout ${data.branch}`, { cwd: projectDir, stdio: "pipe" });
7142
+ } catch (err) {
7143
+ const msg = parseErrorMessage(err);
7144
+ callback({ ok: false, error: `Failed to checkout branch: ${msg}` });
7145
+ return;
7146
+ }
7147
+ try {
7148
+ execSync6(`git pull origin ${data.branch}`, { cwd: projectDir, stdio: "pipe" });
7149
+ } catch {
7150
+ logger8.warn("Git pull failed during branch switch");
7151
+ }
7152
+ if (data.syncAfter !== false) {
7153
+ await handleSyncEnvironment(projectDir, branchSwitchCommand, startCmd, connection);
7154
+ }
7155
+ commitWatcher.start(data.branch);
7156
+ callback({ ok: true });
7157
+ } catch (err) {
7158
+ const msg = parseErrorMessage(err);
7159
+ logger8.error("Branch switch failed", { error: msg });
7160
+ callback({ ok: false, error: msg });
7161
+ }
7162
+ }
7163
+ async function handleNewCommits(projectDir, branchSwitchCommand, startCmd, connection, setupComplete, data) {
7164
+ await connection.call("reportNewCommitsDetected", {
7165
+ projectId: connection.projectId,
7166
+ branch: data.branch,
7167
+ commits: [
7168
+ {
7169
+ sha: data.newCommitSha,
7170
+ message: data.latestMessage,
7171
+ author: data.latestAuthor
7172
+ }
7173
+ ]
7174
+ });
7175
+ const stepsRun = await smartSync(
7176
+ projectDir,
7177
+ branchSwitchCommand,
7178
+ startCmd,
7179
+ connection,
7180
+ data.previousSha,
7181
+ data.newCommitSha,
7182
+ data.branch
7183
+ );
7184
+ await connection.call("reportEnvironmentReady", {
7185
+ projectId: connection.projectId,
7186
+ branch: data.branch,
7187
+ setupComplete,
7188
+ startCommandRunning: startCmd.running
7189
+ });
7190
+ logger8.info("Commit sync complete", { steps: stepsRun.join(", ") });
7191
+ }
7192
+
7193
+ // src/runner/project-runner-children.ts
7194
+ var logger9 = createServiceLogger("ProjectRunner");
7195
+ var __filename = fileURLToPath2(import.meta.url);
7196
+ var __dirname = path.dirname(__filename);
7197
+ var STOP_TIMEOUT_MS = 3e4;
7198
+ var MAX_CONCURRENT = Math.max(1, parseInt(process.env.CONVEYOR_MAX_CONCURRENT ?? "10", 10) || 10);
7065
7199
  function spawnChildAgent(assignment, workDir) {
7066
7200
  const { taskToken, apiUrl, taskId, sessionId, mode, isAuto, useSandbox, agentMode } = assignment;
7067
7201
  const cliPath = path.resolve(__dirname, "cli.js");
@@ -7073,7 +7207,7 @@ function spawnChildAgent(assignment, workDir) {
7073
7207
  const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
7074
7208
  const effectiveApiUrl = apiUrl || process.env.CONVEYOR_API_URL || "";
7075
7209
  if (!effectiveApiUrl) {
7076
- logger7.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
7210
+ logger9.error("No API URL available for child agent", { taskId: taskId.slice(0, 8) });
7077
7211
  }
7078
7212
  const child = fork(cliPath, [], {
7079
7213
  env: {
@@ -7101,16 +7235,112 @@ function spawnChildAgent(assignment, workDir) {
7101
7235
  const shortId = taskId.slice(0, 8);
7102
7236
  child.stdout?.on("data", (data) => {
7103
7237
  for (const line of data.toString().trimEnd().split("\n")) {
7104
- logger7.info(line, { taskId: shortId });
7238
+ logger9.info(line, { taskId: shortId });
7105
7239
  }
7106
7240
  });
7107
7241
  child.stderr?.on("data", (data) => {
7108
7242
  for (const line of data.toString().trimEnd().split("\n")) {
7109
- logger7.info(line, { taskId: shortId });
7243
+ logger9.info(line, { taskId: shortId });
7110
7244
  }
7111
7245
  });
7112
7246
  return child;
7113
7247
  }
7248
+ async function killAgent(agent, taskId) {
7249
+ const shortId = taskId.slice(0, 8);
7250
+ if (agent.process.exitCode !== null) {
7251
+ logger9.info("Agent process already exited", { taskId: shortId });
7252
+ return;
7253
+ }
7254
+ logger9.info("Killing agent process", { taskId: shortId });
7255
+ agent.process.kill("SIGTERM");
7256
+ await new Promise((resolve2) => {
7257
+ const timer = setTimeout(() => {
7258
+ if (agent.process.exitCode === null) {
7259
+ logger9.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
7260
+ agent.process.kill("SIGKILL");
7261
+ }
7262
+ resolve2();
7263
+ }, STOP_TIMEOUT_MS);
7264
+ agent.process.on("exit", () => {
7265
+ clearTimeout(timer);
7266
+ resolve2();
7267
+ });
7268
+ });
7269
+ }
7270
+ async function handleAssignment(assignment, activeAgents, projectDir, connection) {
7271
+ const { taskId, mode } = assignment;
7272
+ const shortId = taskId.slice(0, 8);
7273
+ const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
7274
+ const existing = activeAgents.get(agentKey);
7275
+ if (existing) {
7276
+ if (existing.process.exitCode === null) {
7277
+ logger9.info("Re-assignment received, killing existing agent", { taskId: shortId });
7278
+ await killAgent(existing, taskId);
7279
+ } else {
7280
+ logger9.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
7281
+ }
7282
+ activeAgents.delete(agentKey);
7283
+ }
7284
+ if (activeAgents.size >= MAX_CONCURRENT) {
7285
+ logger9.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
7286
+ connection.emitTaskStopped(taskId, "max_concurrent_reached");
7287
+ return;
7288
+ }
7289
+ try {
7290
+ try {
7291
+ execSync7("git fetch origin", { cwd: projectDir, stdio: "ignore" });
7292
+ } catch {
7293
+ logger9.warn("Git fetch failed", { taskId: shortId });
7294
+ }
7295
+ const { workDir, usesWorktree } = setupWorkDir(projectDir, assignment);
7296
+ if (assignment.devBranch) {
7297
+ syncWithBaseBranch(workDir, assignment.devBranch);
7298
+ }
7299
+ const child = spawnChildAgent(assignment, workDir);
7300
+ activeAgents.set(agentKey, {
7301
+ process: child,
7302
+ worktreePath: workDir,
7303
+ mode,
7304
+ usesWorktree
7305
+ });
7306
+ connection.emitTaskStarted(taskId);
7307
+ logger9.info("Started task", { taskId: shortId, mode, workDir });
7308
+ child.on("exit", (code) => {
7309
+ activeAgents.delete(agentKey);
7310
+ const reason = code === 0 ? "completed" : `exited with code ${code}`;
7311
+ connection.emitTaskStopped(taskId, reason);
7312
+ logger9.info("Task exited", { taskId: shortId, reason });
7313
+ if (code === 0 && usesWorktree) {
7314
+ try {
7315
+ removeWorktree(projectDir, taskId);
7316
+ } catch {
7317
+ }
7318
+ }
7319
+ });
7320
+ } catch (error) {
7321
+ const msg = parseErrorMessage(error);
7322
+ logger9.error("Failed to start task", { taskId: shortId, error: msg });
7323
+ connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
7324
+ }
7325
+ }
7326
+ function handleStopTask(taskId, activeAgents, projectDir) {
7327
+ const agentKey = activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
7328
+ const agent = activeAgents.get(agentKey);
7329
+ if (!agent) return;
7330
+ logger9.info("Stopping task", { taskId: taskId.slice(0, 8) });
7331
+ void killAgent(agent, taskId).then(() => {
7332
+ if (agent.usesWorktree) {
7333
+ try {
7334
+ removeWorktree(projectDir, taskId);
7335
+ } catch {
7336
+ }
7337
+ }
7338
+ });
7339
+ }
7340
+
7341
+ // src/runner/project-runner.ts
7342
+ var logger10 = createServiceLogger("ProjectRunner");
7343
+ var HEARTBEAT_INTERVAL_MS = 3e4;
7114
7344
  var ProjectRunner = class {
7115
7345
  connection;
7116
7346
  projectDir;
@@ -7119,8 +7349,7 @@ var ProjectRunner = class {
7119
7349
  stopping = false;
7120
7350
  resolveLifecycle = null;
7121
7351
  chatSessionIds = /* @__PURE__ */ new Map();
7122
- startCommandChild = null;
7123
- startCommandRunning = false;
7352
+ startCmd = { child: null, running: false };
7124
7353
  setupComplete = false;
7125
7354
  branchSwitchCommand;
7126
7355
  commitWatcher;
@@ -7139,33 +7368,39 @@ var ProjectRunner = class {
7139
7368
  },
7140
7369
  {
7141
7370
  onNewCommits: async (data) => {
7142
- await this.handleNewCommits(data);
7371
+ await handleNewCommits(
7372
+ this.projectDir,
7373
+ this.branchSwitchCommand,
7374
+ this.startCmd,
7375
+ this.connection,
7376
+ this.setupComplete,
7377
+ data
7378
+ );
7143
7379
  }
7144
7380
  }
7145
7381
  );
7146
7382
  }
7147
7383
  // ── Public lifecycle ───────────────────────────────────────────────────
7148
- // oxlint-disable-next-line max-lines-per-function -- sequential lifecycle orchestration
7149
7384
  async start() {
7150
- this.checkoutWorkspaceBranch();
7385
+ checkoutWorkspaceBranch(this.projectDir);
7151
7386
  await this.connection.connect();
7152
7387
  const registration = await this.connection.call("registerProjectAgent", {
7153
7388
  projectId: this.connection.projectId,
7154
7389
  capabilities: ["task", "pm", "code-review", "audit"]
7155
7390
  });
7156
7391
  this.branchSwitchCommand = registration.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7157
- logger7.info("Registered as project agent", { agentName: registration.agentName });
7392
+ logger10.info("Registered as project agent", { agentName: registration.agentName });
7158
7393
  try {
7159
- await this.executeSetupCommand();
7160
- this.executeStartCommand();
7394
+ await executeSetupCommand(this.projectDir, this.connection);
7395
+ executeStartCommand(this.projectDir, this.startCmd, this.connection);
7161
7396
  this.setupComplete = true;
7162
7397
  this.connection.sendEvent({
7163
7398
  type: "setup_complete",
7164
- startCommandRunning: this.startCommandRunning
7399
+ startCommandRunning: this.startCmd.running
7165
7400
  });
7166
7401
  } catch (error) {
7167
- const msg = error instanceof Error ? error.message : String(error);
7168
- logger7.error("Environment setup failed", { error: msg });
7402
+ const msg = parseErrorMessage(error);
7403
+ logger10.error("Environment setup failed", { error: msg });
7169
7404
  this.setupComplete = false;
7170
7405
  }
7171
7406
  this.wireEventHandlers();
@@ -7177,7 +7412,7 @@ var ProjectRunner = class {
7177
7412
  if (currentBranch) {
7178
7413
  this.commitWatcher.start(currentBranch);
7179
7414
  }
7180
- logger7.info("Connected, waiting for task assignments");
7415
+ logger10.info("Connected, waiting for task assignments");
7181
7416
  await new Promise((resolve2) => {
7182
7417
  this.resolveLifecycle = resolve2;
7183
7418
  });
@@ -7196,24 +7431,24 @@ var ProjectRunner = class {
7196
7431
  wipMessage: "WIP: auto-commit on conveyor-agent shutdown"
7197
7432
  });
7198
7433
  if (result.hadWork) {
7199
- logger7.info("Shutdown git flush", {
7434
+ logger10.info("Shutdown git flush", {
7200
7435
  dir,
7201
7436
  committed: result.committed,
7202
7437
  pushed: result.pushed
7203
7438
  });
7204
7439
  }
7205
7440
  } catch (err) {
7206
- const msg = err instanceof Error ? err.message : String(err);
7207
- logger7.warn("Shutdown git flush failed", { dir, error: msg });
7441
+ const msg = parseErrorMessage(err);
7442
+ logger10.warn("Shutdown git flush failed", { dir, error: msg });
7208
7443
  }
7209
7444
  }
7210
7445
  }
7211
7446
  async stop() {
7212
7447
  if (this.stopping) return;
7213
7448
  this.stopping = true;
7214
- logger7.info("Shutting down");
7449
+ logger10.info("Shutting down");
7215
7450
  this.commitWatcher.stop();
7216
- await this.killStartCommand();
7451
+ await killStartCommand(this.startCmd);
7217
7452
  if (this.heartbeatTimer) {
7218
7453
  clearInterval(this.heartbeatTimer);
7219
7454
  this.heartbeatTimer = null;
@@ -7226,7 +7461,7 @@ var ProjectRunner = class {
7226
7461
  return;
7227
7462
  }
7228
7463
  agent.process.on("exit", () => resolve2());
7229
- this.handleStopTask(key);
7464
+ handleStopTask(key, this.activeAgents, this.projectDir);
7230
7465
  })
7231
7466
  );
7232
7467
  await Promise.race([
@@ -7243,7 +7478,7 @@ var ProjectRunner = class {
7243
7478
  } catch {
7244
7479
  }
7245
7480
  this.connection.disconnect();
7246
- logger7.info("Shutdown complete");
7481
+ logger10.info("Shutdown complete");
7247
7482
  if (this.resolveLifecycle) {
7248
7483
  this.resolveLifecycle();
7249
7484
  this.resolveLifecycle = null;
@@ -7251,8 +7486,12 @@ var ProjectRunner = class {
7251
7486
  }
7252
7487
  // ── Event wiring ───────────────────────────────────────────────────────
7253
7488
  wireEventHandlers() {
7254
- this.connection.onTaskAssignment((assignment) => void this.handleAssignment(assignment));
7255
- this.connection.onStopTask((data) => this.handleStopTask(data.taskId));
7489
+ this.connection.onTaskAssignment(
7490
+ (assignment) => void handleAssignment(assignment, this.activeAgents, this.projectDir, this.connection)
7491
+ );
7492
+ this.connection.onStopTask(
7493
+ (data) => handleStopTask(data.taskId, this.activeAgents, this.projectDir)
7494
+ );
7256
7495
  this.connection.onShutdown(() => void this.stop());
7257
7496
  this.connection.onChatMessage((msg) => {
7258
7497
  const chatId = msg.chatId ?? "default";
@@ -7265,10 +7504,28 @@ var ProjectRunner = class {
7265
7504
  });
7266
7505
  this.connection.onAuditTags((request) => void this.handleAuditTags(request));
7267
7506
  this.connection.onAuditTasks((request) => void this.handleAuditTasks(request));
7268
- this.connection.onSwitchBranch((data, cb) => void this.handleSwitchBranch(data, cb));
7269
- this.connection.onSyncEnvironment((cb) => void this.handleSyncEnvironment(cb));
7507
+ this.connection.onSwitchBranch(
7508
+ (data, cb) => void handleSwitchBranch(
7509
+ this.projectDir,
7510
+ this.branchSwitchCommand,
7511
+ this.startCmd,
7512
+ this.connection,
7513
+ this.commitWatcher,
7514
+ data,
7515
+ cb
7516
+ )
7517
+ );
7518
+ this.connection.onSyncEnvironment(
7519
+ (cb) => void handleSyncEnvironment(
7520
+ this.projectDir,
7521
+ this.branchSwitchCommand,
7522
+ this.startCmd,
7523
+ this.connection,
7524
+ cb
7525
+ )
7526
+ );
7270
7527
  this.connection.onRestartStartCommand((cb) => {
7271
- void this.restartStartCommand().then(() => cb({ ok: true })).catch(
7528
+ void restartStartCommand(this.projectDir, this.startCmd, this.connection).then(() => cb({ ok: true })).catch(
7272
7529
  (err) => cb({ ok: false, error: err instanceof Error ? err.message : "Restart failed" })
7273
7530
  );
7274
7531
  });
@@ -7280,10 +7537,10 @@ var ProjectRunner = class {
7280
7537
  capabilities: ["task", "pm", "code-review", "audit"]
7281
7538
  });
7282
7539
  this.connection.emitStatus(this.activeAgents.size > 0 ? "busy" : "idle");
7283
- logger7.info("Re-registered after reconnect");
7540
+ logger10.info("Re-registered after reconnect");
7284
7541
  } catch (error) {
7285
- const msg = error instanceof Error ? error.message : String(error);
7286
- logger7.error("Failed to re-register after reconnect", { error: msg });
7542
+ const msg = parseErrorMessage(error);
7543
+ logger10.error("Failed to re-register after reconnect", { error: msg });
7287
7544
  }
7288
7545
  }
7289
7546
  // ── Tag audit ──────────────────────────────────────────────────────────
@@ -7293,8 +7550,8 @@ var ProjectRunner = class {
7293
7550
  const { handleTagAudit } = await import("./tag-audit-handler-I54W7ZD7.js");
7294
7551
  await handleTagAudit(request, this.connection, this.projectDir);
7295
7552
  } catch (error) {
7296
- const msg = error instanceof Error ? error.message : String(error);
7297
- logger7.error("Tag audit failed", { error: msg, requestId: request.requestId });
7553
+ const msg = parseErrorMessage(error);
7554
+ logger10.error("Tag audit failed", { error: msg, requestId: request.requestId });
7298
7555
  try {
7299
7556
  await this.connection.call("reportTagAuditResult", {
7300
7557
  projectId: this.connection.projectId,
@@ -7313,11 +7570,11 @@ var ProjectRunner = class {
7313
7570
  async handleAuditTasks(request) {
7314
7571
  this.connection.emitStatus("busy");
7315
7572
  try {
7316
- const { handleTaskAudit } = await import("./task-audit-handler-KGTVMVAP.js");
7573
+ const { handleTaskAudit } = await import("./task-audit-handler-TJOM5OJS.js");
7317
7574
  await handleTaskAudit(request, this.connection, this.projectDir);
7318
7575
  } catch (error) {
7319
- const msg = error instanceof Error ? error.message : String(error);
7320
- logger7.error("Task audit failed", { error: msg, requestId: request.requestId });
7576
+ const msg = parseErrorMessage(error);
7577
+ logger10.error("Task audit failed", { error: msg, requestId: request.requestId });
7321
7578
  for (const task of request.tasks) {
7322
7579
  try {
7323
7580
  await this.connection.call("reportTaskAuditResult", {
@@ -7355,353 +7612,9 @@ var ProjectRunner = class {
7355
7612
  this.connection.emitStatus("idle");
7356
7613
  }
7357
7614
  }
7358
- // ── Task management ────────────────────────────────────────────────────
7359
- async killAgent(agent, taskId) {
7360
- const shortId = taskId.slice(0, 8);
7361
- if (agent.process.exitCode !== null) {
7362
- logger7.info("Agent process already exited", { taskId: shortId });
7363
- return;
7364
- }
7365
- logger7.info("Killing agent process", { taskId: shortId });
7366
- agent.process.kill("SIGTERM");
7367
- await new Promise((resolve2) => {
7368
- const timer = setTimeout(() => {
7369
- if (agent.process.exitCode === null) {
7370
- logger7.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
7371
- agent.process.kill("SIGKILL");
7372
- }
7373
- resolve2();
7374
- }, STOP_TIMEOUT_MS);
7375
- agent.process.on("exit", () => {
7376
- clearTimeout(timer);
7377
- resolve2();
7378
- });
7379
- });
7380
- }
7381
- // oxlint-disable-next-line max-lines-per-function -- re-assignment logic requires sequential checks
7382
- async handleAssignment(assignment) {
7383
- const { taskId, mode } = assignment;
7384
- const shortId = taskId.slice(0, 8);
7385
- const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
7386
- const existing = this.activeAgents.get(agentKey);
7387
- if (existing) {
7388
- if (existing.process.exitCode === null) {
7389
- logger7.info("Re-assignment received, killing existing agent", { taskId: shortId });
7390
- await this.killAgent(existing, taskId);
7391
- } else {
7392
- logger7.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
7393
- }
7394
- this.activeAgents.delete(agentKey);
7395
- }
7396
- if (this.activeAgents.size >= MAX_CONCURRENT) {
7397
- logger7.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
7398
- this.connection.emitTaskStopped(taskId, "max_concurrent_reached");
7399
- return;
7400
- }
7401
- try {
7402
- try {
7403
- execSync5("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
7404
- } catch {
7405
- logger7.warn("Git fetch failed", { taskId: shortId });
7406
- }
7407
- const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
7408
- if (assignment.devBranch) {
7409
- syncWithBaseBranch(workDir, assignment.devBranch);
7410
- }
7411
- const child = spawnChildAgent(assignment, workDir);
7412
- this.activeAgents.set(agentKey, {
7413
- process: child,
7414
- worktreePath: workDir,
7415
- mode,
7416
- usesWorktree
7417
- });
7418
- this.connection.emitTaskStarted(taskId);
7419
- logger7.info("Started task", { taskId: shortId, mode, workDir });
7420
- child.on("exit", (code) => {
7421
- this.activeAgents.delete(agentKey);
7422
- const reason = code === 0 ? "completed" : `exited with code ${code}`;
7423
- this.connection.emitTaskStopped(taskId, reason);
7424
- logger7.info("Task exited", { taskId: shortId, reason });
7425
- if (code === 0 && usesWorktree) {
7426
- try {
7427
- removeWorktree(this.projectDir, taskId);
7428
- } catch {
7429
- }
7430
- }
7431
- });
7432
- } catch (error) {
7433
- const msg = error instanceof Error ? error.message : "Unknown";
7434
- logger7.error("Failed to start task", { taskId: shortId, error: msg });
7435
- this.connection.emitTaskStopped(taskId, `start_failed: ${msg}`);
7436
- }
7437
- }
7438
- handleStopTask(taskId) {
7439
- const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
7440
- const agent = this.activeAgents.get(agentKey);
7441
- if (!agent) return;
7442
- logger7.info("Stopping task", { taskId: taskId.slice(0, 8) });
7443
- void this.killAgent(agent, taskId).then(() => {
7444
- if (agent.usesWorktree) {
7445
- try {
7446
- removeWorktree(this.projectDir, taskId);
7447
- } catch {
7448
- }
7449
- }
7450
- });
7451
- }
7452
- // ── Environment management ─────────────────────────────────────────────
7453
- checkoutWorkspaceBranch() {
7454
- const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
7455
- if (!workspaceBranch) return;
7456
- const currentBranch = this.getCurrentBranch();
7457
- if (currentBranch === workspaceBranch) return;
7458
- if (hasUncommittedChanges(this.projectDir)) {
7459
- logger7.warn("Uncommitted changes, skipping workspace branch checkout");
7460
- return;
7461
- }
7462
- try {
7463
- execSync5(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
7464
- execSync5(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
7465
- logger7.info("Checked out workspace branch", { workspaceBranch });
7466
- } catch {
7467
- logger7.warn("Failed to checkout workspace branch", { workspaceBranch });
7468
- }
7469
- }
7470
- async executeSetupCommand() {
7471
- const cmd = process.env.CONVEYOR_SETUP_COMMAND;
7472
- if (!cmd) return;
7473
- logger7.info("Running setup command", { command: cmd });
7474
- try {
7475
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7476
- this.connection.sendEvent({ type: "setup_output", stream, data });
7477
- (stream === "stderr" ? process.stderr : process.stdout).write(data);
7478
- });
7479
- logger7.info("Setup command completed");
7480
- } catch (error) {
7481
- const msg = error instanceof Error ? error.message : "Setup command failed";
7482
- logger7.error("Setup command failed", { error: msg });
7483
- this.connection.sendEvent({ type: "setup_error", message: msg });
7484
- throw error;
7485
- }
7486
- }
7487
- executeStartCommand() {
7488
- const cmd = process.env.CONVEYOR_START_COMMAND;
7489
- if (!cmd) return;
7490
- logger7.info("Running start command", { command: cmd });
7491
- const child = runStartCommand(cmd, this.projectDir, (stream, data) => {
7492
- this.connection.sendEvent({ type: "start_command_output", stream, data });
7493
- (stream === "stderr" ? process.stderr : process.stdout).write(data);
7494
- });
7495
- this.startCommandChild = child;
7496
- this.startCommandRunning = true;
7497
- child.on("exit", (code, signal) => {
7498
- this.startCommandRunning = false;
7499
- this.startCommandChild = null;
7500
- logger7.info("Start command exited", { code, signal });
7501
- this.connection.sendEvent({ type: "start_command_exited", code, signal });
7502
- });
7503
- child.on("error", (err) => {
7504
- this.startCommandRunning = false;
7505
- this.startCommandChild = null;
7506
- logger7.error("Start command error", { error: err.message });
7507
- });
7508
- }
7509
- async killStartCommand() {
7510
- const child = this.startCommandChild;
7511
- if (!child || !this.startCommandRunning) return;
7512
- try {
7513
- if (child.pid) process.kill(-child.pid, "SIGTERM");
7514
- } catch {
7515
- child.kill("SIGTERM");
7516
- }
7517
- await new Promise((resolve2) => {
7518
- const timer = setTimeout(() => {
7519
- if (this.startCommandRunning && child.pid) {
7520
- try {
7521
- process.kill(-child.pid, "SIGKILL");
7522
- } catch {
7523
- child.kill("SIGKILL");
7524
- }
7525
- }
7526
- resolve2();
7527
- }, START_CMD_KILL_TIMEOUT_MS);
7528
- child.on("exit", () => {
7529
- clearTimeout(timer);
7530
- resolve2();
7531
- });
7532
- });
7533
- this.startCommandChild = null;
7534
- this.startCommandRunning = false;
7535
- }
7536
- async restartStartCommand() {
7537
- await this.killStartCommand();
7538
- this.executeStartCommand();
7539
- }
7540
7615
  getCurrentBranch() {
7541
7616
  return getCurrentBranch(this.projectDir);
7542
7617
  }
7543
- // ── Branch switching ───────────────────────────────────────────────────
7544
- async handleSwitchBranch(data, callback) {
7545
- try {
7546
- try {
7547
- execSync5("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
7548
- } catch {
7549
- logger7.warn("Git fetch failed during branch switch");
7550
- }
7551
- detachWorktreeBranch(this.projectDir, data.branch);
7552
- try {
7553
- execSync5(`git checkout ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
7554
- } catch (err) {
7555
- const msg = err instanceof Error ? err.message : "Checkout failed";
7556
- callback({ ok: false, error: `Failed to checkout branch: ${msg}` });
7557
- return;
7558
- }
7559
- try {
7560
- execSync5(`git pull origin ${data.branch}`, { cwd: this.projectDir, stdio: "pipe" });
7561
- } catch {
7562
- logger7.warn("Git pull failed during branch switch");
7563
- }
7564
- if (data.syncAfter !== false) {
7565
- await this.handleSyncEnvironment();
7566
- }
7567
- this.commitWatcher.start(data.branch);
7568
- callback({ ok: true });
7569
- } catch (err) {
7570
- const msg = err instanceof Error ? err.message : "Branch switch failed";
7571
- logger7.error("Branch switch failed", { error: msg });
7572
- callback({ ok: false, error: msg });
7573
- }
7574
- }
7575
- async handleSyncEnvironment(callback) {
7576
- try {
7577
- await this.killStartCommand();
7578
- const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7579
- if (cmd) {
7580
- try {
7581
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7582
- this.connection.sendEvent({ type: "sync_output", stream, data });
7583
- });
7584
- } catch (err) {
7585
- const msg = err instanceof Error ? err.message : "Sync command failed";
7586
- logger7.error("Branch switch sync command failed", { error: msg });
7587
- }
7588
- }
7589
- this.executeStartCommand();
7590
- callback?.({ ok: true });
7591
- } catch (err) {
7592
- const msg = err instanceof Error ? err.message : "Sync failed";
7593
- logger7.error("Environment sync failed", { error: msg });
7594
- callback?.({ ok: false, error: msg });
7595
- }
7596
- }
7597
- // ── Commit watching ────────────────────────────────────────────────────
7598
- // oxlint-disable-next-line max-lines-per-function -- sequential sync steps
7599
- async handleNewCommits(data) {
7600
- await this.connection.call("reportNewCommitsDetected", {
7601
- projectId: this.connection.projectId,
7602
- branch: data.branch,
7603
- commits: [
7604
- {
7605
- sha: data.newCommitSha,
7606
- message: data.latestMessage,
7607
- author: data.latestAuthor
7608
- }
7609
- ]
7610
- });
7611
- const stepsRun = await this.smartSync(data.previousSha, data.newCommitSha, data.branch);
7612
- await this.connection.call("reportEnvironmentReady", {
7613
- projectId: this.connection.projectId,
7614
- branch: data.branch,
7615
- setupComplete: this.setupComplete,
7616
- startCommandRunning: this.startCommandRunning
7617
- });
7618
- logger7.info("Commit sync complete", { steps: stepsRun.join(", ") });
7619
- }
7620
- async smartSync(previousSha, newSha, branch) {
7621
- if (hasUncommittedChanges(this.projectDir)) {
7622
- this.connection.sendEvent({
7623
- type: "commit_watch_warning",
7624
- message: "Working tree has uncommitted changes. Auto-pull skipped."
7625
- });
7626
- return ["skipped:dirty_tree"];
7627
- }
7628
- await this.killStartCommand();
7629
- try {
7630
- execSync5(`git pull origin ${branch}`, {
7631
- cwd: this.projectDir,
7632
- stdio: "pipe",
7633
- timeout: 6e4
7634
- });
7635
- } catch (err) {
7636
- const msg = err instanceof Error ? err.message : "Pull failed";
7637
- logger7.error("Git pull failed during commit sync", { error: msg });
7638
- this.executeStartCommand();
7639
- return ["error:pull"];
7640
- }
7641
- const stepsRun = ["pull"];
7642
- const changedFiles = this.getChangedFiles(previousSha, newSha);
7643
- await this.syncDependencies(changedFiles, stepsRun);
7644
- this.executeStartCommand();
7645
- stepsRun.push("startCommand");
7646
- return stepsRun;
7647
- }
7648
- async syncDependencies(changedFiles, stepsRun) {
7649
- const needsInstall = changedFiles.some(
7650
- (f) => f === "package.json" || f === "bun.lockb" || f.endsWith("/package.json") || f.endsWith("/bun.lockb")
7651
- );
7652
- const needsPrisma = changedFiles.some(
7653
- (f) => f.includes("prisma/schema.prisma") || f.includes("prisma/migrations/")
7654
- );
7655
- const cmd = this.branchSwitchCommand ?? process.env.CONVEYOR_BRANCH_SWITCH_COMMAND;
7656
- if (cmd && (needsInstall || needsPrisma)) {
7657
- try {
7658
- await runSetupCommand(cmd, this.projectDir, (stream, data) => {
7659
- this.connection.sendEvent({ type: "sync_output", stream, data });
7660
- });
7661
- stepsRun.push("branchSwitchCommand");
7662
- } catch (err) {
7663
- const msg = err instanceof Error ? err.message : "Sync command failed";
7664
- logger7.error("Branch switch command failed", { error: msg });
7665
- }
7666
- } else if (!cmd) {
7667
- this.runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun);
7668
- }
7669
- }
7670
- runIndividualSyncSteps(needsInstall, needsPrisma, stepsRun) {
7671
- if (needsInstall) {
7672
- try {
7673
- execSync5("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
7674
- stepsRun.push("install");
7675
- } catch (err) {
7676
- const msg = err instanceof Error ? err.message : "Install failed";
7677
- logger7.error("bun install failed", { error: msg });
7678
- }
7679
- }
7680
- if (needsPrisma) {
7681
- try {
7682
- execSync5("bunx prisma generate", { cwd: this.projectDir, timeout: 6e4, stdio: "pipe" });
7683
- execSync5("bunx prisma db push --accept-data-loss", {
7684
- cwd: this.projectDir,
7685
- timeout: 6e4,
7686
- stdio: "pipe"
7687
- });
7688
- stepsRun.push("prisma");
7689
- } catch (err) {
7690
- const msg = err instanceof Error ? err.message : "Prisma sync failed";
7691
- logger7.error("Prisma sync failed", { error: msg });
7692
- }
7693
- }
7694
- }
7695
- getChangedFiles(previousSha, newSha) {
7696
- try {
7697
- return execSync5(`git diff --name-only ${previousSha}..${newSha}`, {
7698
- cwd: this.projectDir,
7699
- stdio: ["ignore", "pipe", "ignore"]
7700
- }).toString().trim().split("\n").filter(Boolean);
7701
- } catch {
7702
- return [];
7703
- }
7704
- }
7705
7618
  };
7706
7619
 
7707
7620
  // src/setup/config.ts
@@ -7747,15 +7660,15 @@ export {
7747
7660
  PlanSync,
7748
7661
  SessionRunner,
7749
7662
  ProjectConnection,
7750
- runSetupCommand,
7751
- runAuthTokenCommand,
7752
- runStartCommand,
7753
7663
  CommitWatcher,
7754
7664
  ensureWorktree,
7755
7665
  detachWorktreeBranch,
7756
7666
  removeWorktree,
7667
+ runSetupCommand,
7668
+ runAuthTokenCommand,
7669
+ runStartCommand,
7757
7670
  ProjectRunner,
7758
7671
  loadForwardPorts,
7759
7672
  loadConveyorConfig
7760
7673
  };
7761
- //# sourceMappingURL=chunk-4XJPZGXU.js.map
7674
+ //# sourceMappingURL=chunk-5OMUMWQR.js.map