@rallycry/conveyor-agent 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -161,6 +161,21 @@ function getTaskIncidents(socket, taskId) {
161
161
  function requestScaleUp(socket, tier, reason) {
162
162
  return emitRpc(requireSocket(socket), "agentRunner:scaleUp", { tier, reason });
163
163
  }
164
+ function addDependency(socket, dependsOnSlugOrId) {
165
+ return emitRpcVoid(requireSocket(socket), "agentRunner:addDependency", { dependsOnSlugOrId });
166
+ }
167
+ function removeDependency(socket, dependsOnSlugOrId) {
168
+ return emitRpcVoid(requireSocket(socket), "agentRunner:removeDependency", { dependsOnSlugOrId });
169
+ }
170
+ function getDependencies(socket) {
171
+ return emitRpc(requireSocket(socket), "agentRunner:getDependencies", {});
172
+ }
173
+ function createFollowUpTask(socket, data) {
174
+ return emitRpc(requireSocket(socket), "agentRunner:createFollowUpTask", data);
175
+ }
176
+ function queryGcpLogs(socket, params) {
177
+ return emitRpc(requireSocket(socket), "agentRunner:queryGcpLogs", params);
178
+ }
164
179
 
165
180
  // src/connection/task-connection.ts
166
181
  var ConveyorConnection = class _ConveyorConnection {
@@ -464,7 +479,7 @@ var ConveyorConnection = class _ConveyorConnection {
464
479
  return triggerIdentification(this.socket);
465
480
  }
466
481
  async refreshAuthToken() {
467
- const codespaceName = process.env.CODESPACE_NAME;
482
+ const codespaceName = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
468
483
  const apiUrl = process.env.CONVEYOR_API_URL ?? this.config.conveyorApiUrl;
469
484
  if (!codespaceName || !apiUrl) return false;
470
485
  try {
@@ -509,10 +524,29 @@ var ConveyorConnection = class _ConveyorConnection {
509
524
  requestScaleUp(tier, reason) {
510
525
  return requestScaleUp(this.socket, tier, reason);
511
526
  }
527
+ addDependency(dependsOnSlugOrId) {
528
+ return addDependency(this.socket, dependsOnSlugOrId);
529
+ }
530
+ removeDependency(dependsOnSlugOrId) {
531
+ return removeDependency(this.socket, dependsOnSlugOrId);
532
+ }
533
+ getDependencies() {
534
+ return getDependencies(this.socket);
535
+ }
536
+ createFollowUpTask(data) {
537
+ return createFollowUpTask(this.socket, data);
538
+ }
539
+ queryGcpLogs(params) {
540
+ return queryGcpLogs(this.socket, params);
541
+ }
512
542
  disconnect() {
513
543
  this.flushEvents();
514
- this.socket?.disconnect();
515
- this.socket = null;
544
+ if (this.socket) {
545
+ this.socket.io.reconnection(false);
546
+ this.socket.removeAllListeners();
547
+ this.socket.disconnect();
548
+ this.socket = null;
549
+ }
516
550
  }
517
551
  };
518
552
 
@@ -805,8 +839,12 @@ var ProjectConnection = class {
805
839
  }
806
840
  }
807
841
  disconnect() {
808
- this.socket?.disconnect();
809
- this.socket = null;
842
+ if (this.socket) {
843
+ this.socket.io.reconnection(false);
844
+ this.socket.removeAllListeners();
845
+ this.socket.disconnect();
846
+ this.socket = null;
847
+ }
810
848
  }
811
849
  };
812
850
 
@@ -862,6 +900,82 @@ function getCurrentBranch(cwd) {
862
900
  return null;
863
901
  }
864
902
  }
903
+ function hasUnpushedCommits(cwd) {
904
+ try {
905
+ const currentBranch = getCurrentBranch(cwd);
906
+ if (!currentBranch) return false;
907
+ try {
908
+ execSync2(`git rev-parse origin/${currentBranch}`, {
909
+ cwd,
910
+ stdio: ["ignore", "pipe", "ignore"]
911
+ });
912
+ } catch {
913
+ try {
914
+ execSync2("git rev-parse HEAD", {
915
+ cwd,
916
+ stdio: ["ignore", "pipe", "ignore"]
917
+ });
918
+ return true;
919
+ } catch {
920
+ return false;
921
+ }
922
+ }
923
+ const ahead = execSync2(`git rev-list --count HEAD --not origin/${currentBranch}`, {
924
+ cwd,
925
+ stdio: ["ignore", "pipe", "ignore"]
926
+ }).toString().trim();
927
+ return parseInt(ahead, 10) > 0;
928
+ } catch {
929
+ return false;
930
+ }
931
+ }
932
+ function stageAndCommit(cwd, message) {
933
+ try {
934
+ execSync2("git add -A", {
935
+ cwd,
936
+ stdio: ["ignore", "pipe", "ignore"]
937
+ });
938
+ if (!hasUncommittedChanges(cwd)) {
939
+ return null;
940
+ }
941
+ execSync2(`git commit -m "${message}"`, {
942
+ cwd,
943
+ stdio: ["ignore", "pipe", "ignore"]
944
+ });
945
+ const hash = execSync2("git rev-parse HEAD", {
946
+ cwd,
947
+ stdio: ["ignore", "pipe", "ignore"]
948
+ }).toString().trim();
949
+ return hash;
950
+ } catch {
951
+ return null;
952
+ }
953
+ }
954
+ function pushToOrigin(cwd) {
955
+ try {
956
+ const currentBranch = getCurrentBranch(cwd);
957
+ if (!currentBranch) return false;
958
+ try {
959
+ execSync2(`git push origin ${currentBranch}`, {
960
+ cwd,
961
+ stdio: ["ignore", "pipe", "ignore"]
962
+ });
963
+ return true;
964
+ } catch {
965
+ try {
966
+ execSync2(`git push --force-with-lease origin ${currentBranch}`, {
967
+ cwd,
968
+ stdio: ["ignore", "pipe", "ignore"]
969
+ });
970
+ return true;
971
+ } catch {
972
+ return false;
973
+ }
974
+ }
975
+ } catch {
976
+ return false;
977
+ }
978
+ }
865
979
 
866
980
  // src/runner/worktree.ts
867
981
  var WORKTREE_DIR = ".worktrees";
@@ -1691,11 +1805,11 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1691
1805
  const parts = [
1692
1806
  `You are an autonomous Pack Runner managing child tasks for the "${context.title}" project.`,
1693
1807
  `You are running locally with full access to the repository and task management tools.`,
1694
- `Your job is to sequentially execute child tasks by firing cloud builds, reviewing their PRs, and merging them.`,
1808
+ `Your job is to execute child tasks by firing cloud builds, reviewing their PRs, and merging them \u2014 respecting dependency chains for parallel execution.`,
1695
1809
  ``,
1696
1810
  `## Child Task Status Lifecycle`,
1697
1811
  `- "Planning" \u2014 Not ready for execution. Skip it (or escalate if blocking).`,
1698
- `- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
1812
+ `- "Open" \u2014 Ready to execute (if dependencies are met). Use start_child_cloud_build to fire it.`,
1699
1813
  `- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
1700
1814
  `- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
1701
1815
  `- "Hold" \u2014 PR exists but is on hold for team review. Do not merge \u2014 skip and move on.`,
@@ -1706,32 +1820,35 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1706
1820
  `Follow this loop each time you are launched or relaunched:`,
1707
1821
  ``,
1708
1822
  `1. Call list_subtasks to see the current state of all child tasks.`,
1709
- ` The response includes PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId).`,
1823
+ ` The response includes PR info, agent assignment, and **dependency info** (dependencies array + allDependenciesMet flag).`,
1710
1824
  ``,
1711
- `2. Evaluate each child by status (in ordinal order):`,
1712
- ` - "ReviewPR": Review and merge its PR with approve_and_merge_pr.`,
1713
- ` - If merge fails due to pending CI: post a status update to chat, state you are going idle, and the system will relaunch you to try again.`,
1714
- ` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check what went wrong. Escalate to the team in chat.`,
1715
- ` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
1716
- ` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
1717
- ` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
1825
+ `2. Evaluate children by status and dependency readiness:`,
1826
+ ` - "ReviewPR": Review and merge its PR with approve_and_merge_pr. (Highest priority)`,
1827
+ ` - If merge fails due to pending CI: post a status update to chat, state you are going idle.`,
1828
+ ` - If merge fails due to failed CI: use get_task_cli(childTaskId) to check. Escalate to team.`,
1829
+ ` - "InProgress": A Task Runner is actively working. Do nothing \u2014 wait.`,
1830
+ ` - "Open" + allDependenciesMet=true: Ready to fire. Use start_child_cloud_build.`,
1831
+ ` - "Open" + allDependenciesMet=false: Blocked \u2014 skip for now. Will be unblocked when deps complete.`,
1718
1832
  ` - "Hold": On hold \u2014 team must review before merge. Skip.`,
1719
1833
  ` - "ReviewDev" / "Complete": Already done. Skip.`,
1720
- ` - "Planning": Not ready. If this is blocking progress, notify the team.`,
1834
+ ` - "Planning": Not ready. If blocking progress, notify team.`,
1835
+ ``,
1836
+ `3. Fire ALL ready "Open" tasks whose dependencies are met, not just one. Independent tasks can run in parallel.`,
1721
1837
  ``,
1722
- `3. After merging a PR: run \`git pull origin ${context.baseBranch}\` to get the merged changes before firing the next child.`,
1838
+ `4. After merging a PR: run \`git pull origin ${context.baseBranch}\` then re-check list_subtasks \u2014 previously blocked tasks may now be ready.`,
1723
1839
  ``,
1724
- `4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
1840
+ `5. After firing all ready tasks: report which tasks you fired to chat, then state you are going idle.`,
1725
1841
  ``,
1726
- `5. When ALL children are in "ReviewDev", "Complete", or "Hold" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
1842
+ `6. When ALL children are in "ReviewDev", "Complete", or "Hold" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
1727
1843
  ``,
1728
1844
  `## Important Rules`,
1729
- `- Process children ONE at a time, in ordinal order.`,
1730
- `- After firing a child build OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
1845
+ `- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel.`,
1846
+ `- When NO dependencies are set on any children, fall back to ordinal order (one at a time). This preserves backward compatibility.`,
1847
+ `- After firing builds OR when waiting on CI, explicitly state you are going idle. The system will disconnect you and relaunch when there's a status change.`,
1731
1848
  `- Do NOT attempt to write code yourself. Your role is coordination only.`,
1732
1849
  `- If a child is stuck in "InProgress" for an unusually long time, use get_task_cli(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
1733
1850
  `- You can use get_task(childTaskId) to get a child's full details including PR URL and branch.`,
1734
- `- list_subtasks returns PR info (githubPRNumber, githubPRUrl) and agent assignment (agentId) for each child \u2014 use this to verify readiness before firing builds.`,
1851
+ `- list_subtasks returns PR info (githubPRNumber, githubPRUrl), agent assignment (agentId), and dependency info for each child \u2014 use this to verify readiness before firing builds.`,
1735
1852
  `- You can use read_task_chat to check for team messages.`
1736
1853
  ];
1737
1854
  if (context.storyPoints && context.storyPoints.length > 0) {
@@ -1770,11 +1887,12 @@ function buildPackRunnerInstructions(context, scenario) {
1770
1887
  );
1771
1888
  } else if (scenario === "idle_relaunch") {
1772
1889
  parts.push(
1773
- `You have been relaunched \u2014 a child task likely changed status (completed work, opened a PR, or was merged).`,
1890
+ `You have been relaunched \u2014 a child task likely changed status.`,
1774
1891
  `Call list_subtasks to check the current state of all children.`,
1775
1892
  `Look for children in "ReviewPR" status first \u2014 review and merge their PRs.`,
1776
- `If a child you previously fired is now in "ReviewDev", it was merged. Pull latest with \`git pull origin ${context.baseBranch}\` and continue to the next "Open" child.`,
1777
- `If no children need action (all InProgress or waiting), state you are going idle.`
1893
+ `Check if any previously blocked tasks now have allDependenciesMet=true \u2014 fire them.`,
1894
+ `If a child you previously fired is now in "ReviewDev", pull latest with \`git pull origin ${context.baseBranch}\`.`,
1895
+ `If no children need action, state you are going idle.`
1778
1896
  );
1779
1897
  } else {
1780
1898
  const lastAgentIdx = findLastAgentMessageIndex(context.chatHistory);
@@ -2032,7 +2150,7 @@ function buildDiscoveryPrompt(context) {
2032
2150
  `### Finishing Planning`,
2033
2151
  `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
2034
2152
  `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
2035
- `- ExitPlanMode validates these properties and moves the task to Open status`,
2153
+ `- ExitPlanMode validates these properties and marks planning as complete`,
2036
2154
  `- It does NOT start building \u2014 the team controls when to switch to Build mode`
2037
2155
  ];
2038
2156
  if (context) parts.push(...buildPropertyInstructions(context));
@@ -2087,13 +2205,13 @@ function buildModePrompt(agentMode, context) {
2087
2205
  parts.push(
2088
2206
  ``,
2089
2207
  `### Resource Management`,
2090
- `Your pod starts with minimal resources (0.25 CPU / 1 Gi). Before running resource-intensive`,
2091
- `operations, use the \`scale_up_resources\` tool to request more capacity:`,
2092
- `- **light** (1 CPU / 4 Gi) \u2014 package installs, basic dev servers, light builds`,
2208
+ `Your pod starts with minimal resources (0.25 CPU / 1 Gi). You MUST call \`scale_up_resources\``,
2209
+ `BEFORE running any of these operations \u2014 they WILL fail or OOM at baseline resources:`,
2210
+ `- **light** (1 CPU / 4 Gi) \u2014 bun/npm/yarn install, pip install, basic dev servers, light builds`,
2093
2211
  `- **standard** (2 CPU / 8 Gi) \u2014 full dev servers, test suites, typecheck, lint`,
2094
2212
  `- **heavy** (4 CPU / 16 Gi) \u2014 E2E/browser automation, large parallel builds`,
2095
- `Scaling is one-way (up only) and capped by project limits. If you forget to scale,`,
2096
- `operations may be slow or OOM \u2014 scale up proactively before running builds or tests.`
2213
+ `Scaling is one-way (up only) and capped by project limits.`,
2214
+ `CRITICAL: Always scale to at least "light" before running any package install command.`
2097
2215
  );
2098
2216
  }
2099
2217
  return parts.join("\n");
@@ -2332,7 +2450,7 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
2332
2450
  );
2333
2451
  if (!isPm || isPmActive) {
2334
2452
  parts.push(
2335
- `Use the mcp__conveyor__create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
2453
+ `Use the mcp__conveyor__create_pull_request tool to open PRs \u2014 it automatically stages, commits, and pushes changes before creating the PR. Do NOT use gh CLI or shell commands for PR creation.`
2336
2454
  );
2337
2455
  }
2338
2456
  if (options?.hasDebugTools) {
@@ -2545,12 +2663,12 @@ function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
2545
2663
  `Your plan has been approved. Begin implementing it now.`,
2546
2664
  `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
2547
2665
  `Start by reading the relevant source files mentioned in the plan, then write code.`,
2548
- `When finished, commit and push your changes, then use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI.`,
2666
+ `When finished, use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI.`,
2549
2667
  `
2550
2668
  CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or go idle without making code changes.`,
2551
2669
  `Your FIRST action must be reading source files from the plan, then immediately writing code.`,
2552
2670
  `Do NOT summarize the plan or say "ready to implement" \u2014 start implementing.`,
2553
- `When all changes are committed and pushed, use mcp__conveyor__create_pull_request to open a PR.`,
2671
+ `When all changes are ready, use mcp__conveyor__create_pull_request to open a PR.`,
2554
2672
  `If you are genuinely blocked, explain the specific blocker \u2014 do not go idle silently.`
2555
2673
  ];
2556
2674
  }
@@ -2584,14 +2702,14 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2584
2702
  `Your FIRST action should be reading the relevant source files mentioned in the plan, then writing code. Do NOT run install, build, lint, test, or dev server commands first \u2014 the environment is already set up.`,
2585
2703
  `Work on the git branch "${context.githubBranch}". Stay on this branch for the entire task. Do not checkout or create other branches.`,
2586
2704
  `Your replies are visible to the team in chat \u2014 briefly describe what you're doing when you begin meaningful implementation, and again when the PR is ready.`,
2587
- `When finished, commit your changes, then run \`git fetch origin ${context.githubBranch}\` and \`git push origin ${context.githubBranch}\` (use --force-with-lease if push fails). Then use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
2705
+ `When finished, use the mcp__conveyor__create_pull_request tool to open a PR. Do NOT use gh CLI or any other method to create PRs.`
2588
2706
  ];
2589
2707
  if (isAutoMode) {
2590
2708
  parts.push(
2591
2709
  `
2592
2710
  CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or go idle without making code changes.`,
2593
2711
  `Do NOT summarize the plan or say "ready to implement" \u2014 start implementing immediately.`,
2594
- `When all changes are committed and pushed, you MUST use mcp__conveyor__create_pull_request to open a PR before finishing.`,
2712
+ `When all changes are ready, you MUST use mcp__conveyor__create_pull_request to open a PR before finishing.`,
2595
2713
  `If you are genuinely blocked, explain the specific blocker \u2014 do not go idle silently.`
2596
2714
  );
2597
2715
  }
@@ -2620,7 +2738,7 @@ New messages since your last run:`,
2620
2738
  ...newMessages.map((m) => `[${m.userName ?? "user"}]: ${m.content}`),
2621
2739
  `
2622
2740
  Address the requested changes directly. Do NOT re-investigate the codebase from scratch or write a new plan \u2014 go straight to implementing the feedback.`,
2623
- `Commit and push your updates.`
2741
+ `Implement your updates and open a PR when finished.`
2624
2742
  ];
2625
2743
  if (context.githubPRUrl) {
2626
2744
  parts.push(
@@ -2671,7 +2789,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
2671
2789
  );
2672
2790
  if (agentMode === "auto" || agentMode === "building" && isAuto) {
2673
2791
  parts.push(
2674
- `If work is incomplete, continue implementing the plan. When finished, commit, push, and use mcp__conveyor__create_pull_request to open a PR.`,
2792
+ `If work is incomplete, continue implementing the plan. When finished, use mcp__conveyor__create_pull_request to open a PR.`,
2675
2793
  `Do NOT go idle or wait for instructions \u2014 you are in auto mode.`
2676
2794
  );
2677
2795
  } else {
@@ -2950,20 +3068,56 @@ function buildGetTaskIncidentsTool(connection) {
2950
3068
  { annotations: { readOnlyHint: true } }
2951
3069
  );
2952
3070
  }
2953
- function buildCreatePullRequestTool(connection) {
3071
+ function buildCreatePullRequestTool(connection, config) {
2954
3072
  return defineTool(
2955
3073
  "create_pull_request",
2956
- "Create a GitHub pull request for this task. Use this instead of gh CLI or git commands to create PRs.",
3074
+ "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.",
2957
3075
  {
2958
3076
  title: z.string().describe("The PR title"),
2959
3077
  body: z.string().describe("The PR description/body in markdown"),
2960
3078
  branch: z.string().optional().describe(
2961
3079
  "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."
3080
+ ),
3081
+ baseBranch: z.string().optional().describe(
3082
+ "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
3083
+ ),
3084
+ commitMessage: z.string().optional().describe(
3085
+ "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
2962
3086
  )
2963
3087
  },
2964
- async ({ title, body, branch }) => {
3088
+ async ({ title, body, branch, baseBranch, commitMessage }) => {
2965
3089
  try {
2966
- const result = await connection.createPR({ title, body, branch });
3090
+ const cwd = config.workspaceDir;
3091
+ if (hasUncommittedChanges(cwd)) {
3092
+ const message = commitMessage || `${title}
3093
+
3094
+ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
3095
+ const commitHash = stageAndCommit(cwd, message);
3096
+ if (commitHash) {
3097
+ connection.sendEvent({
3098
+ type: "message",
3099
+ content: `Auto-committed changes: ${commitHash.slice(0, 7)}`
3100
+ });
3101
+ } else {
3102
+ return textResult(
3103
+ "Failed to stage and commit changes. Please check git status and commit manually before creating PR."
3104
+ );
3105
+ }
3106
+ }
3107
+ if (hasUnpushedCommits(cwd)) {
3108
+ const pushSuccess = pushToOrigin(cwd);
3109
+ if (pushSuccess) {
3110
+ connection.sendEvent({
3111
+ type: "message",
3112
+ content: "Auto-pushed committed changes to origin"
3113
+ });
3114
+ } else {
3115
+ return textResult(
3116
+ "Failed to push changes to origin. Please check git status and push manually before creating PR."
3117
+ );
3118
+ }
3119
+ }
3120
+ const result = await connection.createPR({ title, body, branch, baseBranch });
2967
3121
  connection.sendEvent({
2968
3122
  type: "pr_created",
2969
3123
  url: result.url,
@@ -2977,6 +3131,55 @@ function buildCreatePullRequestTool(connection) {
2977
3131
  }
2978
3132
  );
2979
3133
  }
3134
+ function buildQueryGcpLogsTool(connection) {
3135
+ return defineTool(
3136
+ "query_gcp_logs",
3137
+ "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.",
3138
+ {
3139
+ filter: z.string().optional().describe(
3140
+ `Cloud Logging filter expression (e.g., 'resource.type="gce_instance"'). Max 1000 chars.`
3141
+ ),
3142
+ start_time: z.string().optional().describe("Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z')"),
3143
+ end_time: z.string().optional().describe("End time in ISO 8601 format (e.g., '2024-01-02T00:00:00Z')"),
3144
+ severity: z.enum([
3145
+ "DEFAULT",
3146
+ "DEBUG",
3147
+ "INFO",
3148
+ "NOTICE",
3149
+ "WARNING",
3150
+ "ERROR",
3151
+ "CRITICAL",
3152
+ "ALERT",
3153
+ "EMERGENCY"
3154
+ ]).optional().describe("Minimum severity level to filter by (default: all levels)"),
3155
+ page_size: z.number().optional().describe("Number of log entries to return (default 100, max 500)")
3156
+ },
3157
+ async ({ filter, start_time, end_time, severity, page_size }) => {
3158
+ try {
3159
+ const result = await connection.queryGcpLogs({
3160
+ filter,
3161
+ startTime: start_time,
3162
+ endTime: end_time,
3163
+ severity,
3164
+ pageSize: page_size
3165
+ });
3166
+ if (result.entries.length === 0) {
3167
+ return textResult("No log entries found matching the given filters.");
3168
+ }
3169
+ const summary = `Found ${result.entries.length} log entries${result.hasMore ? " (more available \u2014 refine filters or increase page_size)" : ""}.`;
3170
+ const formatted = result.entries.map((e) => `[${e.timestamp}] [${e.severity}] ${e.message}`).join("\n");
3171
+ return textResult(`${summary}
3172
+
3173
+ ${formatted}`);
3174
+ } catch (error) {
3175
+ return textResult(
3176
+ `Failed to query GCP logs: ${error instanceof Error ? error.message : "Unknown error"}`
3177
+ );
3178
+ }
3179
+ },
3180
+ { annotations: { readOnlyHint: true } }
3181
+ );
3182
+ }
2980
3183
  function buildScaleUpResourcesTool(connection) {
2981
3184
  return defineTool(
2982
3185
  "scale_up_resources",
@@ -3009,6 +3212,91 @@ function buildScaleUpResourcesTool(connection) {
3009
3212
  }
3010
3213
  );
3011
3214
  }
3215
+ function buildAddDependencyTool(connection) {
3216
+ return defineTool(
3217
+ "add_dependency",
3218
+ "Add a dependency \u2014 this task cannot start until the specified task is merged to dev",
3219
+ {
3220
+ depends_on_slug_or_id: z.string().describe("Slug or ID of the task this task depends on")
3221
+ },
3222
+ async ({ depends_on_slug_or_id }) => {
3223
+ try {
3224
+ await connection.addDependency(depends_on_slug_or_id);
3225
+ return textResult(`Dependency added: this task now depends on "${depends_on_slug_or_id}"`);
3226
+ } catch (error) {
3227
+ return textResult(
3228
+ `Failed to add dependency: ${error instanceof Error ? error.message : "Unknown error"}`
3229
+ );
3230
+ }
3231
+ }
3232
+ );
3233
+ }
3234
+ function buildRemoveDependencyTool(connection) {
3235
+ return defineTool(
3236
+ "remove_dependency",
3237
+ "Remove a dependency from this task",
3238
+ {
3239
+ depends_on_slug_or_id: z.string().describe("Slug or ID of the task to remove as dependency")
3240
+ },
3241
+ async ({ depends_on_slug_or_id }) => {
3242
+ try {
3243
+ await connection.removeDependency(depends_on_slug_or_id);
3244
+ return textResult("Dependency removed");
3245
+ } catch (error) {
3246
+ return textResult(
3247
+ `Failed to remove dependency: ${error instanceof Error ? error.message : "Unknown error"}`
3248
+ );
3249
+ }
3250
+ }
3251
+ );
3252
+ }
3253
+ function buildGetDependenciesTool(connection) {
3254
+ return defineTool(
3255
+ "get_dependencies",
3256
+ "Get this task's dependencies and their current status (met = merged to dev)",
3257
+ {},
3258
+ async () => {
3259
+ try {
3260
+ const deps = await connection.getDependencies();
3261
+ return textResult(JSON.stringify(deps, null, 2));
3262
+ } catch (error) {
3263
+ return textResult(
3264
+ `Failed to get dependencies: ${error instanceof Error ? error.message : "Unknown error"}`
3265
+ );
3266
+ }
3267
+ },
3268
+ { annotations: { readOnlyHint: true } }
3269
+ );
3270
+ }
3271
+ function buildCreateFollowUpTaskTool(connection) {
3272
+ return defineTool(
3273
+ "create_follow_up_task",
3274
+ "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.",
3275
+ {
3276
+ title: z.string().describe("Follow-up task title"),
3277
+ description: z.string().optional().describe("Brief description of the follow-up work"),
3278
+ plan: z.string().optional().describe("Implementation plan if known"),
3279
+ story_point_value: z.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
3280
+ },
3281
+ async ({ title, description, plan, story_point_value }) => {
3282
+ try {
3283
+ const result = await connection.createFollowUpTask({
3284
+ title,
3285
+ description,
3286
+ plan,
3287
+ storyPointValue: story_point_value
3288
+ });
3289
+ return textResult(
3290
+ `Follow-up task created: "${title}" (slug: ${result.slug}). It depends on the current task and will be unblocked when this task is merged.`
3291
+ );
3292
+ } catch (error) {
3293
+ return textResult(
3294
+ `Failed to create follow-up task: ${error instanceof Error ? error.message : "Unknown error"}`
3295
+ );
3296
+ }
3297
+ }
3298
+ );
3299
+ }
3012
3300
  function buildCommonTools(connection, config) {
3013
3301
  const tools = [
3014
3302
  buildReadTaskChatTool(connection),
@@ -3020,7 +3308,12 @@ function buildCommonTools(connection, config) {
3020
3308
  buildGetTaskFileTool(connection),
3021
3309
  buildSearchIncidentsTool(connection),
3022
3310
  buildGetTaskIncidentsTool(connection),
3023
- buildCreatePullRequestTool(connection)
3311
+ buildQueryGcpLogsTool(connection),
3312
+ buildCreatePullRequestTool(connection, config),
3313
+ buildAddDependencyTool(connection),
3314
+ buildRemoveDependencyTool(connection),
3315
+ buildGetDependenciesTool(connection),
3316
+ buildCreateFollowUpTaskTool(connection)
3024
3317
  ];
3025
3318
  if (process.env.CLAUDESPACE_NAME) {
3026
3319
  tools.push(buildScaleUpResourcesTool(connection));
@@ -4652,6 +4945,14 @@ import { randomUUID } from "crypto";
4652
4945
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
4653
4946
  var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
4654
4947
  var CODE_REVIEW_WRITE_CMD_PATTERN = /git\s+push|git\s+commit|git\s+add|rm\s+|mv\s+|cp\s+|mkdir\s+|touch\s+|chmod\s+|chown\s+/;
4948
+ var RESOURCE_HEAVY_PATTERNS = [
4949
+ /\bbun\s+(install|i|add)\b/,
4950
+ /\bnpm\s+(install|ci|i)\b/,
4951
+ /\byarn(\s+install)?\s/,
4952
+ /\bpnpm\s+(install|i|add)\b/,
4953
+ /\bpip\s+install\b/,
4954
+ /\bcargo\s+build\b/
4955
+ ];
4655
4956
  function isPlanFile(input) {
4656
4957
  const filePath = String(input.file_path ?? input.path ?? "");
4657
4958
  return filePath.includes(".claude/plans/");
@@ -4754,14 +5055,15 @@ async function handleExitPlanMode(host, input) {
4754
5055
  ].join("\n")
4755
5056
  };
4756
5057
  }
4757
- await host.connection.triggerIdentification();
4758
- host.hasExitedPlanMode = true;
4759
5058
  if (host.agentMode === "discovery") {
5059
+ host.hasExitedPlanMode = true;
4760
5060
  host.connection.postChatMessage(
4761
- "Task identified and moved to Open. Switch to Build mode when ready to start implementation."
5061
+ "Plan complete. The task stays in Planning \u2014 identify it or switch to Build mode when ready."
4762
5062
  );
4763
5063
  return { behavior: "allow", updatedInput: input };
4764
5064
  }
5065
+ await host.connection.triggerIdentification();
5066
+ host.hasExitedPlanMode = true;
4765
5067
  const newMode = host.isParentTask ? "review" : "building";
4766
5068
  host.pendingModeRestart = true;
4767
5069
  if (host.onModeTransition) {
@@ -4800,35 +5102,47 @@ async function handleAskUserQuestion(host, input) {
4800
5102
  return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
4801
5103
  }
4802
5104
  var DENIAL_WARNING_THRESHOLD = 3;
5105
+ function isResourceHeavyCommand(command) {
5106
+ return RESOURCE_HEAVY_PATTERNS.some((p) => p.test(command));
5107
+ }
5108
+ var autoScaled = false;
5109
+ async function maybeAutoScale(host, toolName, input) {
5110
+ if (autoScaled || !process.env.CLAUDESPACE_NAME || toolName !== "Bash" || typeof input.command !== "string" || !isResourceHeavyCommand(input.command)) {
5111
+ return;
5112
+ }
5113
+ autoScaled = true;
5114
+ try {
5115
+ await host.connection.requestScaleUp("light", `auto-scale: ${input.command.slice(0, 60)}`);
5116
+ } catch {
5117
+ }
5118
+ }
5119
+ function resolveToolAccess(host, toolName, input) {
5120
+ switch (host.agentMode) {
5121
+ case "discovery":
5122
+ return handleDiscoveryToolAccess(toolName, input);
5123
+ case "building":
5124
+ return handleBuildingToolAccess(toolName, input);
5125
+ case "review":
5126
+ return handleReviewToolAccess(toolName, input, host.isParentTask);
5127
+ case "auto":
5128
+ return handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
5129
+ case "code-review":
5130
+ return handleCodeReviewToolAccess(toolName, input);
5131
+ default:
5132
+ return { behavior: "allow", updatedInput: input };
5133
+ }
5134
+ }
4803
5135
  function buildCanUseTool(host) {
4804
5136
  let consecutiveDenials = 0;
4805
5137
  return async (toolName, input) => {
5138
+ await maybeAutoScale(host, toolName, input);
4806
5139
  if (toolName === "ExitPlanMode" && (host.agentMode === "auto" || host.agentMode === "discovery") && !host.hasExitedPlanMode) {
4807
5140
  return await handleExitPlanMode(host, input);
4808
5141
  }
4809
5142
  if (toolName === "AskUserQuestion") {
4810
5143
  return await handleAskUserQuestion(host, input);
4811
5144
  }
4812
- let result;
4813
- switch (host.agentMode) {
4814
- case "discovery":
4815
- result = handleDiscoveryToolAccess(toolName, input);
4816
- break;
4817
- case "building":
4818
- result = handleBuildingToolAccess(toolName, input);
4819
- break;
4820
- case "review":
4821
- result = handleReviewToolAccess(toolName, input, host.isParentTask);
4822
- break;
4823
- case "auto":
4824
- result = handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
4825
- break;
4826
- case "code-review":
4827
- result = handleCodeReviewToolAccess(toolName, input);
4828
- break;
4829
- default:
4830
- result = { behavior: "allow", updatedInput: input };
4831
- }
5145
+ const result = resolveToolAccess(host, toolName, input);
4832
5146
  if (result.behavior === "deny") {
4833
5147
  consecutiveDenials++;
4834
5148
  if (consecutiveDenials === DENIAL_WARNING_THRESHOLD) {
@@ -4854,6 +5168,7 @@ function buildHooks(host) {
4854
5168
  {
4855
5169
  hooks: [
4856
5170
  async (input) => {
5171
+ if (host.isStopped()) return await Promise.resolve({ continue: false });
4857
5172
  if (input.hook_event_name === "PostToolUse") {
4858
5173
  const output = typeof input.tool_response === "string" ? input.tool_response.slice(0, 500) : JSON.stringify(input.tool_response).slice(0, 500);
4859
5174
  host.connection.sendEvent({
@@ -5773,6 +6088,9 @@ var AgentRunner = class {
5773
6088
  const pastPlanning = this.taskContext.status !== "Planning" && this.taskContext.status !== "Unidentified";
5774
6089
  if (this.agentMode === "auto" && pastPlanning) {
5775
6090
  this.hasExitedPlanMode = true;
6091
+ this.agentMode = "building";
6092
+ const builderModel = this.taskContext.builderModel ?? this.taskContext.model;
6093
+ if (builderModel) this.taskContext.model = builderModel;
5776
6094
  }
5777
6095
  this.logEffectiveSettings();
5778
6096
  if (process.env.CODESPACES === "true") unshallowRepo(this.config.workspaceDir);
@@ -6077,16 +6395,21 @@ var AgentRunner = class {
6077
6395
  this._queryHost = host;
6078
6396
  return host;
6079
6397
  }
6398
+ lastAnnouncedMode = null;
6080
6399
  handleModeChange(newAgentMode) {
6081
6400
  if (this.config.mode !== "pm") return;
6082
- if (newAgentMode) this.agentMode = newAgentMode;
6401
+ if (!newAgentMode || newAgentMode === this.agentMode) return;
6402
+ this.agentMode = newAgentMode;
6083
6403
  this.updateExitedPlanModeFlag(newAgentMode);
6084
6404
  const effectiveMode = this.effectiveAgentMode;
6085
6405
  const isBuildCapable = effectiveMode === "building" || effectiveMode === "auto" && this.hasExitedPlanMode;
6086
6406
  this.connection.emitModeChanged(effectiveMode);
6087
- this.connection.postChatMessage(
6088
- `Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
6089
- );
6407
+ if (effectiveMode !== this.lastAnnouncedMode) {
6408
+ this.lastAnnouncedMode = effectiveMode;
6409
+ this.connection.postChatMessage(
6410
+ `Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
6411
+ );
6412
+ }
6090
6413
  if (isBuildCapable && this.taskContext?.status === "Open") {
6091
6414
  this.connection.updateStatus("InProgress");
6092
6415
  this.taskContext.status = "InProgress";
@@ -6124,8 +6447,19 @@ var AgentRunner = class {
6124
6447
  }
6125
6448
  stop() {
6126
6449
  this.stopped = true;
6450
+ this.stopHeartbeat();
6127
6451
  this.clearIdleTimers();
6452
+ const host = this._queryHost;
6453
+ if (host?.activeQuery) {
6454
+ const q = host.activeQuery;
6455
+ if (typeof q.abort === "function") {
6456
+ q.abort();
6457
+ }
6458
+ void host.activeQuery.return(void 0);
6459
+ host.activeQuery = null;
6460
+ }
6128
6461
  this.tunnelClient?.disconnect();
6462
+ this.connection.disconnect();
6129
6463
  if (this.inputResolver) {
6130
6464
  this.inputResolver(null);
6131
6465
  this.inputResolver = null;
@@ -7468,8 +7802,23 @@ var ProjectRunner = class {
7468
7802
  logger8.info("Connected, waiting for task assignments");
7469
7803
  await new Promise((resolve2) => {
7470
7804
  this.resolveLifecycle = resolve2;
7471
- process.on("SIGTERM", () => void this.stop());
7472
- process.on("SIGINT", () => void this.stop());
7805
+ this.setupSignalHandlers();
7806
+ });
7807
+ }
7808
+ setupSignalHandlers() {
7809
+ process.on("SIGTERM", () => {
7810
+ setTimeout(() => {
7811
+ logger8.warn("Forcing exit after SIGTERM timeout");
7812
+ process.exit(1);
7813
+ }, STOP_TIMEOUT_MS + 1e4).unref();
7814
+ void this.stop();
7815
+ });
7816
+ process.on("SIGINT", () => {
7817
+ setTimeout(() => {
7818
+ logger8.warn("Forcing exit after SIGINT timeout");
7819
+ process.exit(1);
7820
+ }, STOP_TIMEOUT_MS + 1e4).unref();
7821
+ void this.stop();
7473
7822
  });
7474
7823
  }
7475
7824
  handleAssignment(assignment) {
@@ -7577,7 +7926,7 @@ var ProjectRunner = class {
7577
7926
  await Promise.race([
7578
7927
  Promise.all(stopPromises),
7579
7928
  new Promise((resolve2) => {
7580
- setTimeout(resolve2, 6e4);
7929
+ setTimeout(resolve2, STOP_TIMEOUT_MS + 5e3);
7581
7930
  })
7582
7931
  ]);
7583
7932
  this.connection.disconnect();
@@ -7668,4 +8017,4 @@ export {
7668
8017
  ProjectRunner,
7669
8018
  FileCache
7670
8019
  };
7671
- //# sourceMappingURL=chunk-RMNWEID4.js.map
8020
+ //# sourceMappingURL=chunk-WBBX5AIX.js.map