@rallycry/conveyor-agent 6.0.3 → 6.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -179,6 +179,7 @@ var ConveyorConnection = class _ConveyorConnection {
179
179
  constructor(config) {
180
180
  this.config = config;
181
181
  }
182
+ // oxlint-disable-next-line max-lines-per-function -- socket setup requires registering many event handlers
182
183
  connect() {
183
184
  return new Promise((resolve2, reject) => {
184
185
  let settled = false;
@@ -474,6 +475,18 @@ var ConveyorConnection = class _ConveyorConnection {
474
475
  if (!this.socket) return;
475
476
  this.socket.emit("agentRunner:modeTransition", payload);
476
477
  }
478
+ emitDebugStateChanged(state) {
479
+ if (!this.socket) return;
480
+ this.socket.emit("agentRunner:debugStateChanged", { state });
481
+ }
482
+ emitDebugSessionComplete(summary) {
483
+ if (!this.socket) return;
484
+ this.socket.emit("agentRunner:debugSessionComplete", { summary });
485
+ }
486
+ emitDebugReproduceRequested(hypothesis) {
487
+ if (!this.socket) return;
488
+ this.socket.emit("agentRunner:debugReproduceRequested", { hypothesis });
489
+ }
477
490
  searchIncidents(status, source) {
478
491
  return searchIncidents(this.socket, status, source);
479
492
  }
@@ -945,7 +958,8 @@ import { request as httpRequest } from "http";
945
958
  var logger2 = createServiceLogger("TunnelClient");
946
959
  var RECONNECT_BASE_MS = 1e3;
947
960
  var RECONNECT_MAX_MS = 3e4;
948
- var TunnelClient = class {
961
+ var TunnelClient = class _TunnelClient {
962
+ static STABLE_MS = 3e4;
949
963
  apiUrl;
950
964
  token;
951
965
  localPort;
@@ -953,6 +967,7 @@ var TunnelClient = class {
953
967
  stopped = false;
954
968
  reconnectAttempts = 0;
955
969
  reconnectTimer = null;
970
+ connectedAt = 0;
956
971
  constructor(apiUrl, token, localPort) {
957
972
  this.apiUrl = apiUrl;
958
973
  this.token = token;
@@ -971,15 +986,16 @@ var TunnelClient = class {
971
986
  }
972
987
  this.ws.binaryType = "arraybuffer";
973
988
  this.ws.addEventListener("open", () => {
974
- this.reconnectAttempts = 0;
989
+ this.connectedAt = Date.now();
975
990
  logger2.info("Tunnel connected", { port: this.localPort });
976
991
  });
977
- this.ws.addEventListener("close", () => {
978
- logger2.info("Tunnel disconnected");
992
+ this.ws.addEventListener("close", (event) => {
993
+ logger2.info("Tunnel disconnected", { code: event.code, reason: event.reason });
979
994
  this.scheduleReconnect();
980
995
  });
981
996
  this.ws.addEventListener("error", (event) => {
982
- logger2.warn("Tunnel error", { error: String(event) });
997
+ const msg = "message" in event ? event.message : "unknown";
998
+ logger2.warn("Tunnel error", { error: msg });
983
999
  });
984
1000
  this.ws.addEventListener("message", (event) => {
985
1001
  this.handleMessage(event.data);
@@ -998,8 +1014,12 @@ var TunnelClient = class {
998
1014
  }
999
1015
  scheduleReconnect() {
1000
1016
  if (this.stopped) return;
1017
+ if (Date.now() - this.connectedAt >= _TunnelClient.STABLE_MS) {
1018
+ this.reconnectAttempts = 0;
1019
+ }
1001
1020
  const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_MS);
1002
1021
  this.reconnectAttempts++;
1022
+ logger2.info("Tunnel reconnecting", { delay, attempt: this.reconnectAttempts });
1003
1023
  this.reconnectTimer = setTimeout(() => this.connect(), delay);
1004
1024
  }
1005
1025
  // ---------------------------------------------------------------------------
@@ -1613,7 +1633,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1613
1633
  ``,
1614
1634
  `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.`,
1615
1635
  ``,
1616
- `5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with update_task_status("Complete").`,
1636
+ `5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
1617
1637
  ``,
1618
1638
  `## Important Rules`,
1619
1639
  `- Process children ONE at a time, in ordinal order.`,
@@ -1866,11 +1886,10 @@ function buildPropertyInstructions(context) {
1866
1886
  ``,
1867
1887
  `### Proactive Property Management`,
1868
1888
  `As you plan this task, proactively fill in task properties when you have enough context:`,
1869
- `- Once you understand the scope, use set_story_points to assign a value`,
1870
- `- Use set_task_tags to categorize the work`,
1871
- `- For icons: FIRST call list_icons to check for existing matches. Use set_task_icon if one fits.`,
1889
+ `- Use update_task_properties to set any combination of: title, story points, tags, and icon`,
1890
+ `- You can update all properties at once or just one at a time as needed`,
1891
+ `- For icons: FIRST call list_icons to check for existing matches, then use update_task_properties with iconId.`,
1872
1892
  ` Only call generate_task_icon if no existing icon is a good fit.`,
1873
- `- Use set_task_title if the current title doesn't accurately reflect the plan`,
1874
1893
  ``,
1875
1894
  `Don't wait for the user to ask \u2014 fill these in naturally as the plan takes shape.`,
1876
1895
  `If the user adjusts the plan significantly, update the properties to match.`
@@ -1913,10 +1932,8 @@ function buildDiscoveryPrompt(context) {
1913
1932
  `### Self-Identification Tools`,
1914
1933
  `Use these MCP tools to set your own task properties:`,
1915
1934
  `- \`update_task\` \u2014 save your plan and description`,
1916
- `- \`set_story_points\` \u2014 assign story points`,
1917
- `- \`set_task_title\` \u2014 set an accurate title`,
1918
- `- \`set_task_tags\` \u2014 categorize the work`,
1919
- `- \`set_task_icon\` / \`generate_task_icon\` \u2014 set a task icon (call \`list_icons\` first)`,
1935
+ `- \`update_task_properties\` \u2014 set title, story points, tags, and icon (any combination)`,
1936
+ `- \`generate_task_icon\` \u2014 generate a new icon if needed (call \`list_icons\` first)`,
1920
1937
  ``,
1921
1938
  `### Self-Update vs Subtasks`,
1922
1939
  `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
@@ -1924,7 +1941,7 @@ function buildDiscoveryPrompt(context) {
1924
1941
  ``,
1925
1942
  `### Finishing Planning`,
1926
1943
  `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1927
- `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via set_story_points), **title** (via set_task_title)`,
1944
+ `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
1928
1945
  `- ExitPlanMode validates these properties and moves the task to Open status`,
1929
1946
  `- It does NOT start building \u2014 the team controls when to switch to Build mode`
1930
1947
  ];
@@ -1940,13 +1957,13 @@ function buildAutoPrompt(context) {
1940
1957
  `### Phase 1: Discovery & Planning (current)`,
1941
1958
  `- You have read-only codebase access (can read files, run git commands, search code)`,
1942
1959
  `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1943
- `- You have MCP tools for task properties: update_task, set_story_points, set_task_tags, set_task_icon, set_task_title`,
1960
+ `- You have MCP tools for task properties: update_task, update_task_properties`,
1944
1961
  ``,
1945
1962
  `### Required before transitioning:`,
1946
1963
  `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1947
1964
  `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
1948
- `2. **Story Points** \u2014 Assign via set_story_points`,
1949
- `3. **Title** \u2014 Set an accurate title via set_task_title (if the current one is vague or "Untitled")`,
1965
+ `2. **Story Points** \u2014 Assign via update_task_properties`,
1966
+ `3. **Title** \u2014 Set an accurate title via update_task_properties (if the current one is vague or "Untitled")`,
1950
1967
  ``,
1951
1968
  `### Transitioning to Building:`,
1952
1969
  `When your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
@@ -2102,7 +2119,37 @@ Git safety \u2014 STRICT rules:`,
2102
2119
  `- If \`git push\` fails with "non-fast-forward", run \`git push --force-with-lease origin ${context.githubBranch}\`. This branch is exclusively yours \u2014 force-with-lease is safe.`
2103
2120
  ];
2104
2121
  }
2105
- function buildSystemPrompt(mode, context, config, setupLog, agentMode) {
2122
+ function buildDebugModeSection(hypothesisTracker) {
2123
+ const lines = [
2124
+ `
2125
+ ## Debug Mode`,
2126
+ `You have access to debug tools that let you set breakpoints, inspect live state,`,
2127
+ `and capture client-side behavior. Use these when:`,
2128
+ `- You can't find the bug from code reading alone`,
2129
+ `- The bug is state-dependent or timing-dependent`,
2130
+ `- You need to verify a hypothesis about runtime behavior`,
2131
+ ``,
2132
+ `Debugging workflow:`,
2133
+ `1. Form a hypothesis about the root cause`,
2134
+ `2. Identify 2-5 strategic locations to observe (don't over-instrument)`,
2135
+ `3. Set breakpoints or probes at those locations`,
2136
+ `4. Ask the user to reproduce on the preview link`,
2137
+ `5. Analyze the captured data \u2014 confirm or refute your hypothesis`,
2138
+ `6. If refuted, refine hypothesis and repeat (max 3 iterations)`,
2139
+ `7. Exit debug mode and implement the fix`,
2140
+ ``,
2141
+ `Keep debug sessions focused. You don't need to see everything \u2014`,
2142
+ `just the data that tests your hypothesis.`
2143
+ ];
2144
+ if (hypothesisTracker?.isActive()) {
2145
+ const debugContext = hypothesisTracker.getDebugContext();
2146
+ if (debugContext) {
2147
+ lines.push(``, debugContext);
2148
+ }
2149
+ }
2150
+ return lines.join("\n");
2151
+ }
2152
+ function buildSystemPrompt(mode, context, config, setupLog, agentMode, options) {
2106
2153
  const isPm = mode === "pm";
2107
2154
  const isPmActive = isPm && agentMode === "building";
2108
2155
  const isPackRunner = isPm && !!config.isAuto && !!context.isParentTask;
@@ -2141,6 +2188,9 @@ Your responses are sent directly to the task chat \u2014 the team sees everythin
2141
2188
  `Use the mcp__conveyor__create_pull_request tool to open PRs \u2014 do NOT use gh CLI or shell commands for PR creation.`
2142
2189
  );
2143
2190
  }
2191
+ if (options?.hasDebugTools) {
2192
+ parts.push(buildDebugModeSection(options.hypothesisTracker));
2193
+ }
2144
2194
  const modePrompt = buildModePrompt(agentMode, context);
2145
2195
  if (modePrompt) {
2146
2196
  parts.push(modePrompt);
@@ -2350,7 +2400,7 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
2350
2400
  `You are operating autonomously. Begin planning immediately.`,
2351
2401
  `1. Explore the codebase to understand the architecture and relevant files`,
2352
2402
  `2. Draft a clear implementation plan and save it with update_task`,
2353
- `3. Set story points (set_story_points), tags (set_task_tags), and title (set_task_title)`,
2403
+ `3. Set story points, tags, and title (update_task_properties)`,
2354
2404
  `4. When the plan and all required properties are set, call ExitPlanMode to transition to building`,
2355
2405
  `Do NOT wait for team input \u2014 proceed autonomously.`
2356
2406
  ];
@@ -2560,10 +2610,10 @@ function buildPostToChatTool(connection) {
2560
2610
  }
2561
2611
  );
2562
2612
  }
2563
- function buildUpdateTaskStatusTool(connection) {
2613
+ function buildForceUpdateTaskStatusTool(connection) {
2564
2614
  return tool(
2565
- "update_task_status",
2566
- "Update a task's status on the Kanban board. Omit task_id to update the current task, or provide a child task ID to update a child's status.",
2615
+ "force_update_task_status",
2616
+ "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.",
2567
2617
  {
2568
2618
  status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2569
2619
  task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
@@ -2772,7 +2822,6 @@ function buildCommonTools(connection, config) {
2772
2822
  return [
2773
2823
  buildReadTaskChatTool(connection),
2774
2824
  buildPostToChatTool(connection),
2775
- buildUpdateTaskStatusTool(connection),
2776
2825
  buildGetTaskPlanTool(connection, config),
2777
2826
  buildGetTaskTool(connection),
2778
2827
  buildGetTaskCliTool(connection),
@@ -2960,7 +3009,7 @@ function buildIconTools(connection) {
2960
3009
  return [
2961
3010
  tool3(
2962
3011
  "list_icons",
2963
- "List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before set_task_icon to check for existing matches.",
3012
+ "List available icons (default library + user-created). Returns icon IDs, names, and whether they're defaults. Call this FIRST before update_task_properties to check for existing matches.",
2964
3013
  {},
2965
3014
  async () => {
2966
3015
  try {
@@ -2974,23 +3023,6 @@ function buildIconTools(connection) {
2974
3023
  },
2975
3024
  { annotations: { readOnlyHint: true } }
2976
3025
  ),
2977
- tool3(
2978
- "set_task_icon",
2979
- "Assign an existing icon to this task by its ID. Use list_icons first to find a matching icon.",
2980
- {
2981
- iconId: z3.string().describe("The icon ID to assign")
2982
- },
2983
- async ({ iconId }) => {
2984
- try {
2985
- await Promise.resolve(connection.updateTaskProperties({ iconId }));
2986
- return textResult("Icon assigned to task.");
2987
- } catch (error) {
2988
- return textResult(
2989
- `Failed to set icon: ${error instanceof Error ? error.message : "Unknown error"}`
2990
- );
2991
- }
2992
- }
2993
- ),
2994
3026
  tool3(
2995
3027
  "generate_task_icon",
2996
3028
  "Generate a new SVG icon using AI and assign it to this task. Only use if no existing icon from list_icons is a good fit. Provide a concise visual description.",
@@ -3017,55 +3049,1288 @@ function buildDiscoveryTools(connection, context) {
3017
3049
  const spDescription = buildStoryPointDescription(context?.storyPoints);
3018
3050
  return [
3019
3051
  tool3(
3020
- "set_story_points",
3021
- "Set the story point estimate for this task. Use after understanding the scope of the work.",
3022
- { value: z3.number().describe(spDescription) },
3023
- async ({ value }) => {
3052
+ "update_task_properties",
3053
+ "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
3054
+ {
3055
+ title: z3.string().optional().describe("The new task title"),
3056
+ storyPointValue: z3.number().optional().describe(spDescription),
3057
+ tagIds: z3.array(z3.string()).optional().describe("Array of tag IDs to assign"),
3058
+ iconId: z3.string().optional().describe("Icon ID to assign (use list_icons first)")
3059
+ },
3060
+ async ({ title, storyPointValue, tagIds, iconId }) => {
3024
3061
  try {
3025
- await Promise.resolve(connection.updateTaskProperties({ storyPointValue: value }));
3026
- return textResult(`Story points set to ${value}`);
3062
+ const updateFields = {};
3063
+ if (title !== void 0) updateFields.title = title;
3064
+ if (storyPointValue !== void 0) updateFields.storyPointValue = storyPointValue;
3065
+ if (tagIds !== void 0) updateFields.tagIds = tagIds;
3066
+ if (iconId !== void 0) updateFields.iconId = iconId;
3067
+ await Promise.resolve(connection.updateTaskProperties(updateFields));
3068
+ const updatedFields = [];
3069
+ if (title !== void 0) updatedFields.push(`title to "${title}"`);
3070
+ if (storyPointValue !== void 0)
3071
+ updatedFields.push(`story points to ${storyPointValue}`);
3072
+ if (tagIds !== void 0) updatedFields.push(`tags (${tagIds.length} tag(s))`);
3073
+ if (iconId !== void 0) updatedFields.push(`icon`);
3074
+ return textResult(`Task properties updated: ${updatedFields.join(", ")}`);
3027
3075
  } catch (error) {
3028
3076
  return textResult(
3029
- `Failed to set story points: ${error instanceof Error ? error.message : "Unknown error"}`
3077
+ `Failed to update task properties: ${error instanceof Error ? error.message : "Unknown error"}`
3030
3078
  );
3031
3079
  }
3032
3080
  }
3033
3081
  ),
3034
- tool3(
3035
- "set_task_tags",
3036
- "Assign tags to this task from the project's available tags. Use the tag IDs from the project tags list.",
3082
+ ...buildIconTools(connection)
3083
+ ];
3084
+ }
3085
+
3086
+ // src/tools/debug-tools.ts
3087
+ import { tool as tool6 } from "@anthropic-ai/claude-agent-sdk";
3088
+ import { z as z6 } from "zod";
3089
+
3090
+ // src/tools/telemetry-tools.ts
3091
+ import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
3092
+ import { z as z4 } from "zod";
3093
+
3094
+ // src/debug/telemetry-injector.ts
3095
+ var BUFFER_SIZE = 200;
3096
+ var BODY_MAX_BYTES = 1024;
3097
+ var MAX_DEPTH = 3;
3098
+ var EXCLUDED_EXTENSIONS = [
3099
+ ".js",
3100
+ ".css",
3101
+ ".ico",
3102
+ ".png",
3103
+ ".jpg",
3104
+ ".jpeg",
3105
+ ".gif",
3106
+ ".svg",
3107
+ ".woff",
3108
+ ".woff2",
3109
+ ".ttf",
3110
+ ".eot",
3111
+ ".map",
3112
+ ".webp"
3113
+ ];
3114
+ var EXCLUDED_PATHS = ["/_next/", "/__nextjs", "/favicon", "/_healthz", "/healthz", "/health"];
3115
+ var EXCLUDED_SOCKET_EVENTS = ["ping", "pong", "connection", "disconnect", "websocket", "upgrade"];
3116
+ function scriptPreamble() {
3117
+ return `(function() {
3118
+ if (globalThis.__conveyorTelemetry) {
3119
+ return JSON.stringify({ success: true, alreadyInjected: true, patches: globalThis.__conveyorTelemetry._patches });
3120
+ }
3121
+
3122
+ var BUFFER_SIZE = ${BUFFER_SIZE};
3123
+ var BODY_MAX_BYTES = ${BODY_MAX_BYTES};
3124
+ var MAX_DEPTH = ${MAX_DEPTH};
3125
+ var excludedExtensions = ${JSON.stringify(EXCLUDED_EXTENSIONS)};
3126
+ var excludedPaths = ${JSON.stringify(EXCLUDED_PATHS)};
3127
+ var excludedSocketEvents = ${JSON.stringify(EXCLUDED_SOCKET_EVENTS)};`;
3128
+ }
3129
+ function scriptHelpers() {
3130
+ return `
3131
+ function truncate(str, max) {
3132
+ if (typeof str !== 'string') return '';
3133
+ if (str.length <= max) return str;
3134
+ return str.slice(0, max) + '...[truncated]';
3135
+ }
3136
+
3137
+ function safeStringify(obj, depth) {
3138
+ if (depth === undefined) depth = MAX_DEPTH;
3139
+ try {
3140
+ if (obj === null || obj === undefined) return String(obj);
3141
+ if (typeof obj === 'string') return obj;
3142
+ if (typeof obj !== 'object') return String(obj);
3143
+ if (depth <= 0) return '[Object]';
3144
+ var seen = new Set();
3145
+ return JSON.stringify(obj, function(key, value) {
3146
+ if (typeof value === 'object' && value !== null) {
3147
+ if (seen.has(value)) return '[Circular]';
3148
+ seen.add(value);
3149
+ }
3150
+ return value;
3151
+ }, 0).slice(0, BODY_MAX_BYTES);
3152
+ } catch (e) {
3153
+ return '[unserializable]';
3154
+ }
3155
+ }
3156
+
3157
+ function isExcludedUrl(url) {
3158
+ if (!url) return false;
3159
+ var lower = url.toLowerCase();
3160
+ for (var i = 0; i < excludedExtensions.length; i++) {
3161
+ if (lower.endsWith(excludedExtensions[i])) return true;
3162
+ }
3163
+ for (var j = 0; j < excludedPaths.length; j++) {
3164
+ if (lower.indexOf(excludedPaths[j]) !== -1) return true;
3165
+ }
3166
+ return false;
3167
+ }
3168
+
3169
+ function isExcludedSocketEvent(event) {
3170
+ return excludedSocketEvents.indexOf(event) !== -1;
3171
+ }
3172
+
3173
+ function nowMs() { return Date.now(); }`;
3174
+ }
3175
+ function scriptBuffer() {
3176
+ return `
3177
+ var buffer = [];
3178
+
3179
+ function pushEvent(event) {
3180
+ buffer.push(event);
3181
+ if (buffer.length > BUFFER_SIZE) { buffer.shift(); }
3182
+ }
3183
+
3184
+ function getEvents(filter, limit) {
3185
+ var result = buffer;
3186
+ if (filter) {
3187
+ if (filter.type) {
3188
+ result = result.filter(function(e) { return e.type === filter.type; });
3189
+ }
3190
+ if (filter.urlPattern) {
3191
+ var re;
3192
+ try { re = new RegExp(filter.urlPattern, 'i'); } catch(e) { re = null; }
3193
+ if (re) {
3194
+ result = result.filter(function(e) { return e.type === 'http' && re.test(e.url); });
3195
+ }
3196
+ }
3197
+ if (filter.minDuration) {
3198
+ var minD = filter.minDuration;
3199
+ result = result.filter(function(e) { return ('duration' in e) && e.duration >= minD; });
3200
+ }
3201
+ if (filter.errorOnly) {
3202
+ result = result.filter(function(e) {
3203
+ return e.type === 'error' || (e.type === 'http' && e.status >= 400);
3204
+ });
3205
+ }
3206
+ if (filter.since) {
3207
+ var since = filter.since;
3208
+ result = result.filter(function(e) { return e.timestamp >= since; });
3209
+ }
3210
+ }
3211
+ if (limit && limit > 0) { result = result.slice(-limit); }
3212
+ return result;
3213
+ }
3214
+
3215
+ function clear() { buffer = []; }
3216
+
3217
+ var patches = { express: false, prisma: false, socketIo: false, errorHandler: false };`;
3218
+ }
3219
+ function scriptPatchExpress() {
3220
+ return `
3221
+ function patchExpress() {
3222
+ try {
3223
+ var http = require('http');
3224
+ var originalEmit = http.Server.prototype.emit;
3225
+ http.Server.prototype.emit = function(event) {
3226
+ if (event === 'request') {
3227
+ var req = arguments[1];
3228
+ var res = arguments[2];
3229
+ if (req && res && !isExcludedUrl(req.url)) {
3230
+ var start = nowMs();
3231
+ var reqBody = '';
3232
+ if (req.readable) {
3233
+ var chunks = [];
3234
+ var origPush = req.push;
3235
+ req.push = function(chunk) {
3236
+ if (chunk) { chunks.push(typeof chunk === 'string' ? chunk : chunk.toString()); }
3237
+ return origPush.apply(this, arguments);
3238
+ };
3239
+ var origReqEmit = req.emit;
3240
+ req.emit = function(evt) {
3241
+ if (evt === 'end') { reqBody = truncate(chunks.join(''), BODY_MAX_BYTES); }
3242
+ return origReqEmit.apply(this, arguments);
3243
+ };
3244
+ }
3245
+ var origEnd = res.end;
3246
+ res.end = function(chunk) {
3247
+ var resBody = '';
3248
+ if (chunk) { resBody = truncate(typeof chunk === 'string' ? chunk : chunk.toString(), BODY_MAX_BYTES); }
3249
+ var duration = nowMs() - start;
3250
+ pushEvent({
3251
+ type: 'http', timestamp: start, method: req.method || 'UNKNOWN',
3252
+ url: req.url || '/', status: res.statusCode || 0, duration: duration,
3253
+ requestBody: reqBody || undefined, responseBody: resBody || undefined,
3254
+ error: res.statusCode >= 400 ? ('HTTP ' + res.statusCode) : undefined
3255
+ });
3256
+ return origEnd.apply(this, arguments);
3257
+ };
3258
+ }
3259
+ }
3260
+ return originalEmit.apply(this, arguments);
3261
+ };
3262
+ patches.express = true;
3263
+ } catch (e) {}
3264
+ }`;
3265
+ }
3266
+ function scriptPatchPrisma() {
3267
+ return `
3268
+ function patchPrisma() {
3269
+ try {
3270
+ var prismaClient = globalThis.prisma || globalThis.__prisma;
3271
+ if (!prismaClient) {
3272
+ var cacheKeys = Object.keys(require.cache || {});
3273
+ for (var i = 0; i < cacheKeys.length; i++) {
3274
+ var mod = require.cache[cacheKeys[i]];
3275
+ if (mod && mod.exports && typeof mod.exports.$on === 'function') {
3276
+ prismaClient = mod.exports;
3277
+ break;
3278
+ }
3279
+ }
3280
+ }
3281
+ if (prismaClient && typeof prismaClient.$on === 'function') {
3282
+ prismaClient.$on('query', function(e) {
3283
+ pushEvent({
3284
+ type: 'db', timestamp: nowMs(),
3285
+ query: truncate(e.query || '', BODY_MAX_BYTES),
3286
+ params: truncate(safeStringify(e.params), BODY_MAX_BYTES),
3287
+ duration: e.duration || 0
3288
+ });
3289
+ });
3290
+ patches.prisma = true;
3291
+ }
3292
+ } catch (e) {}
3293
+ }`;
3294
+ }
3295
+ function scriptPatchSocketIo() {
3296
+ return `
3297
+ function patchSocketIo() {
3298
+ try {
3299
+ var io = globalThis.io || globalThis.__io;
3300
+ if (io && io.sockets) {
3301
+ var origOn = io.sockets.constructor.prototype.on;
3302
+ if (origOn) {
3303
+ io.sockets.constructor.prototype.on = function(event, handler) {
3304
+ if (!isExcludedSocketEvent(event)) {
3305
+ var wrappedHandler = function() {
3306
+ var args = Array.prototype.slice.call(arguments);
3307
+ pushEvent({
3308
+ type: 'socket', timestamp: nowMs(), event: event, direction: 'in',
3309
+ data: truncate(safeStringify(args[0]), BODY_MAX_BYTES),
3310
+ namespace: (this.nsp && this.nsp.name) || '/'
3311
+ });
3312
+ return handler.apply(this, arguments);
3313
+ };
3314
+ return origOn.call(this, event, wrappedHandler);
3315
+ }
3316
+ return origOn.call(this, event, handler);
3317
+ };
3318
+ }
3319
+ var origEmit = io.sockets.constructor.prototype.emit;
3320
+ if (origEmit) {
3321
+ io.sockets.constructor.prototype.emit = function(event) {
3322
+ if (!isExcludedSocketEvent(event)) {
3323
+ var args = Array.prototype.slice.call(arguments, 1);
3324
+ pushEvent({
3325
+ type: 'socket', timestamp: nowMs(), event: event, direction: 'out',
3326
+ data: truncate(safeStringify(args[0]), BODY_MAX_BYTES),
3327
+ namespace: (this.nsp && this.nsp.name) || '/'
3328
+ });
3329
+ }
3330
+ return origEmit.apply(this, arguments);
3331
+ };
3332
+ }
3333
+ patches.socketIo = true;
3334
+ }
3335
+ } catch (e) {}
3336
+ }`;
3337
+ }
3338
+ function scriptPatchErrors() {
3339
+ return `
3340
+ function patchErrorHandler() {
3341
+ try {
3342
+ process.on('uncaughtException', function(err) {
3343
+ pushEvent({
3344
+ type: 'error', timestamp: nowMs(),
3345
+ message: err.message || String(err),
3346
+ stack: truncate(err.stack || '', BODY_MAX_BYTES),
3347
+ context: 'uncaughtException'
3348
+ });
3349
+ });
3350
+ process.on('unhandledRejection', function(reason) {
3351
+ var msg = (reason && reason.message) ? reason.message : String(reason);
3352
+ var stack = (reason && reason.stack) ? reason.stack : '';
3353
+ pushEvent({
3354
+ type: 'error', timestamp: nowMs(), message: msg,
3355
+ stack: truncate(stack, BODY_MAX_BYTES), context: 'unhandledRejection'
3356
+ });
3357
+ });
3358
+ patches.errorHandler = true;
3359
+ } catch (e) {}
3360
+ }`;
3361
+ }
3362
+ function scriptInit() {
3363
+ return `
3364
+ patchExpress();
3365
+ patchPrisma();
3366
+ patchSocketIo();
3367
+ patchErrorHandler();
3368
+
3369
+ globalThis.__conveyorTelemetry = {
3370
+ getEvents: getEvents,
3371
+ clear: clear,
3372
+ getStatus: function() {
3373
+ return { active: true, eventCount: buffer.length, patches: patches };
3374
+ },
3375
+ _patches: patches,
3376
+ _buffer: buffer
3377
+ };
3378
+
3379
+ return JSON.stringify({ success: true, alreadyInjected: false, patches: patches });
3380
+ })();`;
3381
+ }
3382
+ function generateTelemetryScript() {
3383
+ return [
3384
+ scriptPreamble(),
3385
+ scriptHelpers(),
3386
+ scriptBuffer(),
3387
+ scriptPatchExpress(),
3388
+ scriptPatchPrisma(),
3389
+ scriptPatchSocketIo(),
3390
+ scriptPatchErrors(),
3391
+ scriptInit()
3392
+ ].join("\n");
3393
+ }
3394
+ async function injectTelemetry(cdpClient) {
3395
+ try {
3396
+ const script = generateTelemetryScript();
3397
+ const result = await cdpClient.evaluate(script);
3398
+ if (result.type === "error") {
3399
+ return { success: false, error: result.value };
3400
+ }
3401
+ try {
3402
+ const parsed = JSON.parse(result.value);
3403
+ return {
3404
+ success: parsed.success === true,
3405
+ patches: parsed.patches
3406
+ };
3407
+ } catch {
3408
+ return { success: true };
3409
+ }
3410
+ } catch (error) {
3411
+ return {
3412
+ success: false,
3413
+ error: error instanceof Error ? error.message : "Unknown injection error"
3414
+ };
3415
+ }
3416
+ }
3417
+ async function queryTelemetry(cdpClient, filter, limit) {
3418
+ const filterJson = JSON.stringify(filter ?? {});
3419
+ const limitVal = limit ?? 20;
3420
+ const result = await cdpClient.evaluate(
3421
+ `JSON.stringify(globalThis.__conveyorTelemetry ? globalThis.__conveyorTelemetry.getEvents(${filterJson}, ${limitVal}) : [])`
3422
+ );
3423
+ if (result.type === "error") {
3424
+ throw new Error(`Telemetry query failed: ${result.value}`);
3425
+ }
3426
+ try {
3427
+ let val = result.value;
3428
+ if (val.startsWith('"') && val.endsWith('"')) {
3429
+ val = JSON.parse(val);
3430
+ }
3431
+ return JSON.parse(val);
3432
+ } catch {
3433
+ return [];
3434
+ }
3435
+ }
3436
+ async function clearTelemetry(cdpClient) {
3437
+ await cdpClient.evaluate(
3438
+ `globalThis.__conveyorTelemetry ? (globalThis.__conveyorTelemetry.clear(), 'cleared') : 'not active'`
3439
+ );
3440
+ }
3441
+ async function getTelemetryStatus(cdpClient) {
3442
+ const result = await cdpClient.evaluate(
3443
+ `JSON.stringify(globalThis.__conveyorTelemetry ? globalThis.__conveyorTelemetry.getStatus() : { active: false, eventCount: 0, patches: { express: false, prisma: false, socketIo: false, errorHandler: false } })`
3444
+ );
3445
+ if (result.type === "error") {
3446
+ return {
3447
+ active: false,
3448
+ eventCount: 0,
3449
+ patches: { express: false, prisma: false, socketIo: false, errorHandler: false }
3450
+ };
3451
+ }
3452
+ try {
3453
+ let val = result.value;
3454
+ if (val.startsWith('"') && val.endsWith('"')) {
3455
+ val = JSON.parse(val);
3456
+ }
3457
+ return JSON.parse(val);
3458
+ } catch {
3459
+ return {
3460
+ active: false,
3461
+ eventCount: 0,
3462
+ patches: { express: false, prisma: false, socketIo: false, errorHandler: false }
3463
+ };
3464
+ }
3465
+ }
3466
+
3467
+ // src/tools/telemetry-tools.ts
3468
+ function requireDebugClient(manager) {
3469
+ if (!manager.isDebugMode()) {
3470
+ return "Debug mode is not active. Use debug_enter_mode first.";
3471
+ }
3472
+ const client = manager.getClient();
3473
+ if (!client?.isConnected()) {
3474
+ return "CDP client is not connected. Try exiting and re-entering debug mode.";
3475
+ }
3476
+ return client;
3477
+ }
3478
+ function formatError(error) {
3479
+ return error instanceof Error ? error.message : "Unknown error";
3480
+ }
3481
+ function buildGetTelemetryTool(manager) {
3482
+ return tool4(
3483
+ "debug_get_telemetry",
3484
+ "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.",
3485
+ {
3486
+ type: z4.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3487
+ urlPattern: z4.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3488
+ minDuration: z4.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3489
+ errorOnly: z4.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3490
+ since: z4.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3491
+ limit: z4.number().optional().describe("Max events to return (default: 20, from most recent)")
3492
+ },
3493
+ async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3494
+ const clientOrErr = requireDebugClient(manager);
3495
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3496
+ try {
3497
+ const filter = {
3498
+ ...type && { type },
3499
+ ...urlPattern && { urlPattern },
3500
+ ...minDuration && { minDuration },
3501
+ ...errorOnly && { errorOnly },
3502
+ ...since && { since }
3503
+ };
3504
+ const hasFilter = Object.keys(filter).length > 0;
3505
+ const events = await queryTelemetry(clientOrErr, hasFilter ? filter : void 0, limit);
3506
+ if (events.length === 0) {
3507
+ return textResult("No telemetry events found matching the filter.");
3508
+ }
3509
+ return textResult(JSON.stringify(events, null, 2));
3510
+ } catch (error) {
3511
+ return textResult(`Failed to query telemetry: ${formatError(error)}`);
3512
+ }
3513
+ },
3514
+ { annotations: { readOnlyHint: true } }
3515
+ );
3516
+ }
3517
+ function buildClearTelemetryTool(manager) {
3518
+ return tool4(
3519
+ "debug_clear_telemetry",
3520
+ "Clear all captured telemetry events from the buffer. Useful to reset before reproducing a specific issue.",
3521
+ {},
3522
+ async () => {
3523
+ const clientOrErr = requireDebugClient(manager);
3524
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3525
+ try {
3526
+ await clearTelemetry(clientOrErr);
3527
+ return textResult("Telemetry buffer cleared.");
3528
+ } catch (error) {
3529
+ return textResult(`Failed to clear telemetry: ${formatError(error)}`);
3530
+ }
3531
+ }
3532
+ );
3533
+ }
3534
+ function buildTelemetryStatusTool(manager) {
3535
+ return tool4(
3536
+ "debug_telemetry_status",
3537
+ "Check if telemetry is active, how many events have been captured, and which framework patches (Express, Prisma, Socket.IO) were successfully applied.",
3538
+ {},
3539
+ async () => {
3540
+ const clientOrErr = requireDebugClient(manager);
3541
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3542
+ try {
3543
+ const status = await getTelemetryStatus(clientOrErr);
3544
+ return textResult(JSON.stringify(status, null, 2));
3545
+ } catch (error) {
3546
+ return textResult(`Failed to get telemetry status: ${formatError(error)}`);
3547
+ }
3548
+ },
3549
+ { annotations: { readOnlyHint: true } }
3550
+ );
3551
+ }
3552
+ function buildTelemetryTools(manager) {
3553
+ return [
3554
+ buildGetTelemetryTool(manager),
3555
+ buildClearTelemetryTool(manager),
3556
+ buildTelemetryStatusTool(manager)
3557
+ ];
3558
+ }
3559
+
3560
+ // src/tools/client-debug-tools.ts
3561
+ import { tool as tool5 } from "@anthropic-ai/claude-agent-sdk";
3562
+ import { z as z5 } from "zod";
3563
+ function requirePlaywrightClient(manager) {
3564
+ if (!manager.isClientDebugMode()) {
3565
+ return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
3566
+ }
3567
+ const client = manager.getPlaywrightClient();
3568
+ if (!client?.isConnected()) {
3569
+ return "Playwright client is not connected. Try exiting and re-entering debug mode.";
3570
+ }
3571
+ return client;
3572
+ }
3573
+ function formatError2(error) {
3574
+ return error instanceof Error ? error.message : "Unknown error";
3575
+ }
3576
+ function buildClientBreakpointTools(manager) {
3577
+ return [
3578
+ tool5(
3579
+ "debug_set_client_breakpoint",
3580
+ "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.",
3581
+ {
3582
+ file: z5.string().describe(
3583
+ "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3584
+ ),
3585
+ line: z5.number().describe("Line number (1-based) in the original source file"),
3586
+ condition: z5.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3587
+ },
3588
+ async ({ file, line, condition }) => {
3589
+ const clientOrErr = requirePlaywrightClient(manager);
3590
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3591
+ try {
3592
+ const breakpointId = await clientOrErr.setBreakpoint(file, line, condition);
3593
+ const condStr = condition ? ` (condition: ${condition})` : "";
3594
+ const sourceMapNote = clientOrErr.hasSourceMaps() === false ? "\n\u26A0\uFE0F Source maps not detected \u2014 breakpoints will reference bundled code." : "";
3595
+ return textResult(
3596
+ `Client breakpoint set: ${file}:${line}${condStr}
3597
+ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3598
+ );
3599
+ } catch (error) {
3600
+ return textResult(`Failed to set client breakpoint: ${formatError2(error)}`);
3601
+ }
3602
+ }
3603
+ ),
3604
+ tool5(
3605
+ "debug_remove_client_breakpoint",
3606
+ "Remove a previously set client-side breakpoint by its ID.",
3607
+ {
3608
+ breakpointId: z5.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3609
+ },
3610
+ async ({ breakpointId }) => {
3611
+ const clientOrErr = requirePlaywrightClient(manager);
3612
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3613
+ try {
3614
+ await clientOrErr.removeBreakpoint(breakpointId);
3615
+ return textResult(`Client breakpoint ${breakpointId} removed.`);
3616
+ } catch (error) {
3617
+ return textResult(`Failed to remove client breakpoint: ${formatError2(error)}`);
3618
+ }
3619
+ }
3620
+ ),
3621
+ tool5(
3622
+ "debug_list_client_breakpoints",
3623
+ "List all active client-side breakpoints with their file, line, and condition.",
3624
+ {},
3625
+ // oxlint-disable-next-line require-await
3626
+ async () => {
3627
+ const clientOrErr = requirePlaywrightClient(manager);
3628
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3629
+ const breakpoints = clientOrErr.listBreakpoints();
3630
+ if (breakpoints.length === 0) {
3631
+ return textResult("No client breakpoints set.");
3632
+ }
3633
+ return textResult(JSON.stringify(breakpoints, null, 2));
3634
+ },
3635
+ { annotations: { readOnlyHint: true } }
3636
+ )
3637
+ ];
3638
+ }
3639
+ function buildClientInspectionTools(manager) {
3640
+ return [
3641
+ tool5(
3642
+ "debug_inspect_client_paused",
3643
+ "When the client-side debugger is paused at a breakpoint, returns the call stack and local variables. Includes React component state, props, and hooks when paused inside a component.",
3644
+ {},
3645
+ async () => {
3646
+ const clientOrErr = requirePlaywrightClient(manager);
3647
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3648
+ const queuedHits = manager.drainClientBreakpointHitQueue();
3649
+ if (!clientOrErr.isPaused()) {
3650
+ if (queuedHits.length > 0) {
3651
+ return textResult(
3652
+ `Client debugger was paused but has since resumed. Recent breakpoint hits:
3653
+ ${JSON.stringify(queuedHits, null, 2)}`
3654
+ );
3655
+ }
3656
+ return textResult(
3657
+ "Client debugger is not currently paused. Set client breakpoints and trigger the code path in the browser to pause execution."
3658
+ );
3659
+ }
3660
+ try {
3661
+ const callStack = clientOrErr.getCallStack();
3662
+ const topFrame = callStack[0];
3663
+ let variables = [];
3664
+ if (topFrame) {
3665
+ try {
3666
+ variables = await clientOrErr.getScopeVariables(topFrame.callFrameId);
3667
+ } catch {
3668
+ }
3669
+ }
3670
+ const result = {
3671
+ side: "client",
3672
+ reason: clientOrErr.getPausedState()?.reason,
3673
+ hitBreakpoints: clientOrErr.getPausedState()?.hitBreakpoints,
3674
+ callStack,
3675
+ localVariables: variables
3676
+ };
3677
+ return textResult(JSON.stringify(result, null, 2));
3678
+ } catch (error) {
3679
+ return textResult(`Failed to inspect client paused state: ${formatError2(error)}`);
3680
+ }
3681
+ },
3682
+ { annotations: { readOnlyHint: true } }
3683
+ ),
3684
+ tool5(
3685
+ "debug_evaluate_client",
3686
+ "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.",
3687
+ {
3688
+ expression: z5.string().describe("JavaScript expression to evaluate in the browser context"),
3689
+ frameIndex: z5.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3690
+ },
3691
+ async ({ expression, frameIndex }) => {
3692
+ const clientOrErr = requirePlaywrightClient(manager);
3693
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3694
+ try {
3695
+ let callFrameId;
3696
+ if (clientOrErr.isPaused()) {
3697
+ const callStack = clientOrErr.getCallStack();
3698
+ const frame = callStack[frameIndex ?? 0];
3699
+ callFrameId = frame?.callFrameId;
3700
+ }
3701
+ const result = await clientOrErr.evaluate(expression, callFrameId);
3702
+ return textResult(`(${result.type}) ${result.value}`);
3703
+ } catch (error) {
3704
+ return textResult(`Client evaluation failed: ${formatError2(error)}`);
3705
+ }
3706
+ }
3707
+ )
3708
+ ];
3709
+ }
3710
+ function buildClientExecutionTools(manager) {
3711
+ return [
3712
+ tool5(
3713
+ "debug_continue_client",
3714
+ "Resume client-side execution after the browser debugger has paused at a breakpoint.",
3715
+ {},
3716
+ async () => {
3717
+ const clientOrErr = requirePlaywrightClient(manager);
3718
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3719
+ if (!clientOrErr.isPaused()) {
3720
+ return textResult("Client debugger is not paused.");
3721
+ }
3722
+ try {
3723
+ await clientOrErr.resume();
3724
+ return textResult("Client execution resumed.");
3725
+ } catch (error) {
3726
+ return textResult(`Failed to resume client: ${formatError2(error)}`);
3727
+ }
3728
+ }
3729
+ )
3730
+ ];
3731
+ }
3732
+ function buildClientInteractionTools(manager) {
3733
+ return [
3734
+ tool5(
3735
+ "debug_client_screenshot",
3736
+ "Take a screenshot of the current page state in the headless browser. Returns the image as base64-encoded PNG.",
3737
+ {},
3738
+ async () => {
3739
+ const clientOrErr = requirePlaywrightClient(manager);
3740
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3741
+ try {
3742
+ const base64 = await clientOrErr.screenshot();
3743
+ return {
3744
+ content: [
3745
+ imageBlock(base64, "image/png"),
3746
+ {
3747
+ type: "text",
3748
+ text: `Screenshot captured (${clientOrErr.getCurrentUrl()})`
3749
+ }
3750
+ ]
3751
+ };
3752
+ } catch (error) {
3753
+ return textResult(`Failed to capture screenshot: ${formatError2(error)}`);
3754
+ }
3755
+ },
3756
+ { annotations: { readOnlyHint: true } }
3757
+ ),
3758
+ tool5(
3759
+ "debug_navigate_client",
3760
+ "Navigate the headless browser to a specific URL. Use this to reproduce specific flows or visit different pages.",
3037
3761
  {
3038
- tagIds: z3.array(z3.string()).describe("Array of tag IDs to assign")
3762
+ url: z5.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3039
3763
  },
3040
- async ({ tagIds }) => {
3764
+ async ({ url }) => {
3765
+ const clientOrErr = requirePlaywrightClient(manager);
3766
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3041
3767
  try {
3042
- await Promise.resolve(connection.updateTaskProperties({ tagIds }));
3043
- return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
3768
+ await clientOrErr.navigate(url);
3769
+ return textResult(`Navigated to: ${clientOrErr.getCurrentUrl()}`);
3044
3770
  } catch (error) {
3771
+ return textResult(`Failed to navigate: ${formatError2(error)}`);
3772
+ }
3773
+ }
3774
+ ),
3775
+ tool5(
3776
+ "debug_click_client",
3777
+ "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.",
3778
+ {
3779
+ selector: z5.string().describe(
3780
+ "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3781
+ )
3782
+ },
3783
+ async ({ selector }) => {
3784
+ const clientOrErr = requirePlaywrightClient(manager);
3785
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3786
+ try {
3787
+ await clientOrErr.click(selector);
3788
+ return textResult(`Clicked: ${selector}`);
3789
+ } catch (error) {
3790
+ return textResult(`Failed to click "${selector}": ${formatError2(error)}`);
3791
+ }
3792
+ }
3793
+ )
3794
+ ];
3795
+ }
3796
+ function buildClientConsoleTool(manager) {
3797
+ return tool5(
3798
+ "debug_get_client_console",
3799
+ "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3800
+ {
3801
+ level: z5.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3802
+ limit: z5.number().optional().describe("Maximum number of recent messages to return (default: all)")
3803
+ },
3804
+ // oxlint-disable-next-line require-await
3805
+ async ({ level, limit }) => {
3806
+ const clientOrErr = requirePlaywrightClient(manager);
3807
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3808
+ const messages = clientOrErr.getConsoleMessages(level, limit);
3809
+ if (messages.length === 0) {
3810
+ const filterDesc = level ? ` (level: ${level})` : "";
3811
+ return textResult(`No console messages captured${filterDesc}.`);
3812
+ }
3813
+ const formatted = messages.map((m) => {
3814
+ const time = new Date(m.timestamp).toLocaleTimeString("en-US", { hour12: false });
3815
+ const loc = m.url ? ` [${m.url}${m.line ? `:${m.line}` : ""}]` : "";
3816
+ return `[${time}] ${m.level.toUpperCase()}: ${m.text}${loc}`;
3817
+ }).join("\n");
3818
+ return textResult(`${messages.length} console message(s):
3819
+ ${formatted}`);
3820
+ },
3821
+ { annotations: { readOnlyHint: true } }
3822
+ );
3823
+ }
3824
+ function buildClientNetworkTool(manager) {
3825
+ return tool5(
3826
+ "debug_get_client_network",
3827
+ "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3828
+ {
3829
+ filter: z5.string().optional().describe("Regex pattern to filter requests by URL"),
3830
+ limit: z5.number().optional().describe("Maximum number of recent requests to return (default: all)")
3831
+ },
3832
+ // oxlint-disable-next-line require-await
3833
+ async ({ filter, limit }) => {
3834
+ const clientOrErr = requirePlaywrightClient(manager);
3835
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3836
+ const requests = clientOrErr.getNetworkRequests(filter, limit);
3837
+ if (requests.length === 0) {
3838
+ const filterDesc = filter ? ` matching "${filter}"` : "";
3839
+ return textResult(`No network requests captured${filterDesc}.`);
3840
+ }
3841
+ const formatted = requests.map((r) => {
3842
+ const time = new Date(r.timestamp).toLocaleTimeString("en-US", { hour12: false });
3843
+ const status = r.status ? ` \u2192 ${r.status}` : " \u2192 (pending)";
3844
+ const dur = r.duration ? ` (${r.duration}ms)` : "";
3845
+ return `[${time}] ${r.method} ${r.url}${status}${dur}`;
3846
+ }).join("\n");
3847
+ return textResult(`${requests.length} network request(s):
3848
+ ${formatted}`);
3849
+ },
3850
+ { annotations: { readOnlyHint: true } }
3851
+ );
3852
+ }
3853
+ function buildClientErrorsTool(manager) {
3854
+ return tool5(
3855
+ "debug_get_client_errors",
3856
+ "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3857
+ {
3858
+ limit: z5.number().optional().describe("Maximum number of recent errors to return (default: all)")
3859
+ },
3860
+ // oxlint-disable-next-line require-await
3861
+ async ({ limit }) => {
3862
+ const clientOrErr = requirePlaywrightClient(manager);
3863
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3864
+ const errors = clientOrErr.getPageErrors(limit);
3865
+ if (errors.length === 0) {
3866
+ return textResult("No uncaught page errors captured.");
3867
+ }
3868
+ const formatted = errors.map((e) => {
3869
+ const time = new Date(e.timestamp).toLocaleTimeString("en-US", { hour12: false });
3870
+ const stack = e.stack ? `
3871
+ ${e.stack.split("\n").slice(0, 5).join("\n ")}` : "";
3872
+ return `[${time}] ${e.message}${stack}`;
3873
+ }).join("\n\n");
3874
+ return textResult(`${errors.length} page error(s):
3875
+ ${formatted}`);
3876
+ },
3877
+ { annotations: { readOnlyHint: true } }
3878
+ );
3879
+ }
3880
+ function buildClientCaptureTools(manager) {
3881
+ return [
3882
+ buildClientConsoleTool(manager),
3883
+ buildClientNetworkTool(manager),
3884
+ buildClientErrorsTool(manager)
3885
+ ];
3886
+ }
3887
+ function buildClientDebugTools(manager) {
3888
+ return [
3889
+ ...buildClientBreakpointTools(manager),
3890
+ ...buildClientInspectionTools(manager),
3891
+ ...buildClientExecutionTools(manager),
3892
+ ...buildClientInteractionTools(manager),
3893
+ ...buildClientCaptureTools(manager)
3894
+ ];
3895
+ }
3896
+
3897
+ // src/tools/debug-tools.ts
3898
+ function requireDebugClient2(manager) {
3899
+ if (!manager.isDebugMode()) {
3900
+ return "Debug mode is not active. Use debug_enter_mode first.";
3901
+ }
3902
+ const client = manager.getClient();
3903
+ if (!client?.isConnected()) {
3904
+ return "CDP client is not connected. Try exiting and re-entering debug mode.";
3905
+ }
3906
+ return client;
3907
+ }
3908
+ function formatError3(error) {
3909
+ return error instanceof Error ? error.message : "Unknown error";
3910
+ }
3911
+ async function handleEnterDebugMode(manager, {
3912
+ hypothesis,
3913
+ serverSide,
3914
+ clientSide,
3915
+ previewUrl
3916
+ }) {
3917
+ const wantServer = serverSide ?? !clientSide;
3918
+ const wantClient = clientSide ?? false;
3919
+ const alreadyMsg = checkAlreadyActive(manager, wantServer, wantClient);
3920
+ if (alreadyMsg) return textResult(alreadyMsg);
3921
+ await manager.enterDebugMode(void 0, {
3922
+ serverSide: wantServer && !manager.isDebugMode(),
3923
+ clientSide: wantClient && !manager.isClientDebugMode(),
3924
+ previewUrl
3925
+ });
3926
+ return textResult(buildActivationMessage(manager, hypothesis));
3927
+ }
3928
+ function checkAlreadyActive(manager, wantServer, wantClient) {
3929
+ if (wantServer && manager.isDebugMode() && !wantClient) return "Already in server debug mode.";
3930
+ if (wantClient && manager.isClientDebugMode() && !wantServer)
3931
+ return "Already in client debug mode.";
3932
+ if (wantServer && manager.isDebugMode() && wantClient && manager.isClientDebugMode()) {
3933
+ return "Already in both server and client debug mode.";
3934
+ }
3935
+ return null;
3936
+ }
3937
+ function buildActivationMessage(manager, hypothesis) {
3938
+ const modes = [];
3939
+ if (manager.isDebugMode()) modes.push("server");
3940
+ if (manager.isClientDebugMode()) modes.push("client");
3941
+ const modeStr = modes.join(" + ");
3942
+ const sourceMapWarning = manager.getPlaywrightClient()?.hasSourceMaps() === false ? "\n\u26A0\uFE0F Source maps not detected in the client \u2014 client breakpoints will reference bundled code." : "";
3943
+ return hypothesis ? `Debug mode activated (${modeStr}). Hypothesis: ${hypothesis}
3944
+ Set breakpoints to test your hypothesis.${sourceMapWarning}` : `Debug mode activated (${modeStr}). Set breakpoints to begin debugging.${sourceMapWarning}`;
3945
+ }
3946
+ function buildDebugLifecycleTools(manager) {
3947
+ return [
3948
+ tool6(
3949
+ "debug_enter_mode",
3950
+ "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.",
3951
+ {
3952
+ hypothesis: z6.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3953
+ serverSide: z6.boolean().optional().describe(
3954
+ "Enable server-side Node.js debugging (default: true if clientSide is not set)"
3955
+ ),
3956
+ clientSide: z6.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
3957
+ previewUrl: z6.string().optional().describe(
3958
+ "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
3959
+ )
3960
+ },
3961
+ async (params) => {
3962
+ try {
3963
+ return await handleEnterDebugMode(manager, params);
3964
+ } catch (error) {
3965
+ return textResult(`Failed to enter debug mode: ${formatError3(error)}`);
3966
+ }
3967
+ }
3968
+ ),
3969
+ tool6(
3970
+ "debug_exit_mode",
3971
+ "Exit debug mode: removes all breakpoints, disconnects the debugger, and restarts the dev server normally.",
3972
+ {},
3973
+ async () => {
3974
+ try {
3975
+ if (!manager.isDebugMode()) {
3976
+ return textResult("Not in debug mode.");
3977
+ }
3978
+ await manager.exitDebugMode();
3979
+ return textResult("Debug mode deactivated. Dev server restarted normally.");
3980
+ } catch (error) {
3981
+ return textResult(`Failed to exit debug mode: ${formatError3(error)}`);
3982
+ }
3983
+ }
3984
+ )
3985
+ ];
3986
+ }
3987
+ function buildBreakpointTools(manager) {
3988
+ return [
3989
+ tool6(
3990
+ "debug_set_breakpoint",
3991
+ "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.",
3992
+ {
3993
+ file: z6.string().describe("Absolute or relative file path to set the breakpoint in"),
3994
+ line: z6.number().describe("Line number (1-based) to set the breakpoint on"),
3995
+ condition: z6.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3996
+ },
3997
+ async ({ file, line, condition }) => {
3998
+ const clientOrErr = requireDebugClient2(manager);
3999
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4000
+ try {
4001
+ const client = clientOrErr;
4002
+ const breakpointId = await client.setBreakpoint(file, line, condition);
4003
+ const condStr = condition ? ` (condition: ${condition})` : "";
3045
4004
  return textResult(
3046
- `Failed to set tags: ${error instanceof Error ? error.message : "Unknown error"}`
4005
+ `Breakpoint set: ${file}:${line}${condStr}
4006
+ Breakpoint ID: ${breakpointId}`
3047
4007
  );
4008
+ } catch (error) {
4009
+ return textResult(`Failed to set breakpoint: ${formatError3(error)}`);
3048
4010
  }
3049
4011
  }
3050
4012
  ),
3051
- tool3(
3052
- "set_task_title",
3053
- "Update the task title to better reflect the planned work.",
4013
+ tool6(
4014
+ "debug_remove_breakpoint",
4015
+ "Remove a previously set breakpoint by its ID.",
3054
4016
  {
3055
- title: z3.string().describe("The new task title")
4017
+ breakpointId: z6.string().describe("The breakpoint ID returned by debug_set_breakpoint")
3056
4018
  },
3057
- async ({ title }) => {
4019
+ async ({ breakpointId }) => {
4020
+ const clientOrErr = requireDebugClient2(manager);
4021
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3058
4022
  try {
3059
- await Promise.resolve(connection.updateTaskProperties({ title }));
3060
- return textResult(`Task title updated to: ${title}`);
4023
+ const client = clientOrErr;
4024
+ await client.removeBreakpoint(breakpointId);
4025
+ return textResult(`Breakpoint ${breakpointId} removed.`);
3061
4026
  } catch (error) {
4027
+ return textResult(`Failed to remove breakpoint: ${formatError3(error)}`);
4028
+ }
4029
+ }
4030
+ ),
4031
+ tool6(
4032
+ "debug_list_breakpoints",
4033
+ "List all currently active breakpoints with their file, line, and condition.",
4034
+ {},
4035
+ // oxlint-disable-next-line require-await
4036
+ async () => {
4037
+ const clientOrErr = requireDebugClient2(manager);
4038
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4039
+ const breakpoints = clientOrErr.listBreakpoints();
4040
+ if (breakpoints.length === 0) {
4041
+ return textResult("No breakpoints set.");
4042
+ }
4043
+ return textResult(JSON.stringify(breakpoints, null, 2));
4044
+ },
4045
+ { annotations: { readOnlyHint: true } }
4046
+ )
4047
+ ];
4048
+ }
4049
+ function buildInspectionTools(manager) {
4050
+ return [
4051
+ tool6(
4052
+ "debug_inspect_paused",
4053
+ "When the debugger is paused at a breakpoint, returns the call stack and local variables. Check this after a breakpoint is hit to understand the current execution state.",
4054
+ {},
4055
+ async () => {
4056
+ const clientOrErr = requireDebugClient2(manager);
4057
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4058
+ const client = clientOrErr;
4059
+ const queuedHits = manager.drainBreakpointHitQueue();
4060
+ if (!client.isPaused()) {
4061
+ if (queuedHits.length > 0) {
4062
+ return textResult(
4063
+ `Debugger was paused but has since resumed. Recent breakpoint hits:
4064
+ ${JSON.stringify(queuedHits, null, 2)}`
4065
+ );
4066
+ }
3062
4067
  return textResult(
3063
- `Failed to update title: ${error instanceof Error ? error.message : "Unknown error"}`
4068
+ "Debugger is not currently paused. Set breakpoints and trigger the code path to pause execution."
3064
4069
  );
3065
4070
  }
4071
+ try {
4072
+ const callStack = client.getCallStack();
4073
+ const topFrame = callStack[0];
4074
+ let variables = [];
4075
+ if (topFrame) {
4076
+ try {
4077
+ variables = await client.getScopeVariables(topFrame.callFrameId);
4078
+ } catch {
4079
+ }
4080
+ }
4081
+ const result = {
4082
+ reason: client.getPausedState()?.reason,
4083
+ hitBreakpoints: client.getPausedState()?.hitBreakpoints,
4084
+ callStack,
4085
+ localVariables: variables
4086
+ };
4087
+ return textResult(JSON.stringify(result, null, 2));
4088
+ } catch (error) {
4089
+ return textResult(`Failed to inspect paused state: ${formatError3(error)}`);
4090
+ }
4091
+ },
4092
+ { annotations: { readOnlyHint: true } }
4093
+ ),
4094
+ tool6(
4095
+ "debug_evaluate",
4096
+ "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.",
4097
+ {
4098
+ expression: z6.string().describe("The JavaScript expression to evaluate"),
4099
+ frameIndex: z6.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
4100
+ },
4101
+ async ({ expression, frameIndex }) => {
4102
+ const clientOrErr = requireDebugClient2(manager);
4103
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4104
+ try {
4105
+ const client = clientOrErr;
4106
+ let callFrameId;
4107
+ if (client.isPaused()) {
4108
+ const callStack = client.getCallStack();
4109
+ const frame = callStack[frameIndex ?? 0];
4110
+ callFrameId = frame?.callFrameId;
4111
+ }
4112
+ const result = await client.evaluate(expression, callFrameId);
4113
+ return textResult(`(${result.type}) ${result.value}`);
4114
+ } catch (error) {
4115
+ return textResult(`Evaluation failed: ${formatError3(error)}`);
4116
+ }
4117
+ }
4118
+ )
4119
+ ];
4120
+ }
4121
+ function buildProbeManagementTools(manager) {
4122
+ return [
4123
+ tool6(
4124
+ "debug_add_probe",
4125
+ "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.",
4126
+ {
4127
+ file: z6.string().describe("File path to probe"),
4128
+ line: z6.number().describe("Line number (1-based) to probe"),
4129
+ expressions: z6.array(z6.string()).describe(
4130
+ 'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4131
+ ),
4132
+ label: z6.string().optional().describe("Optional label for this probe (defaults to file:line)")
4133
+ },
4134
+ async ({ file, line, expressions, label }) => {
4135
+ const clientOrErr = requireDebugClient2(manager);
4136
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4137
+ try {
4138
+ const probeId = await clientOrErr.setLogpoint(file, line, expressions, label);
4139
+ const probeLabel = label ?? `${file}:${line}`;
4140
+ const exprList = expressions.join(", ");
4141
+ return textResult(
4142
+ `Probe "${probeLabel}" set at ${file}:${line}
4143
+ Capturing: ${exprList}
4144
+ Probe ID: ${probeId}
4145
+
4146
+ Trigger the code path, then use debug_get_probe_results to see captured values.`
4147
+ );
4148
+ } catch (error) {
4149
+ return textResult(`Failed to add probe: ${formatError3(error)}`);
4150
+ }
3066
4151
  }
3067
4152
  ),
3068
- ...buildIconTools(connection)
4153
+ tool6(
4154
+ "debug_remove_probe",
4155
+ "Remove a previously set debug probe by its ID.",
4156
+ {
4157
+ probeId: z6.string().describe("The probe ID returned by debug_add_probe")
4158
+ },
4159
+ async ({ probeId }) => {
4160
+ const clientOrErr = requireDebugClient2(manager);
4161
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4162
+ try {
4163
+ await clientOrErr.removeProbe(probeId);
4164
+ return textResult(`Probe ${probeId} removed.`);
4165
+ } catch (error) {
4166
+ return textResult(`Failed to remove probe: ${formatError3(error)}`);
4167
+ }
4168
+ }
4169
+ ),
4170
+ tool6(
4171
+ "debug_list_probes",
4172
+ "List all active debug probes with their file, line, expressions, and labels.",
4173
+ {},
4174
+ // oxlint-disable-next-line require-await
4175
+ async () => {
4176
+ const clientOrErr = requireDebugClient2(manager);
4177
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4178
+ const probes = clientOrErr.listProbes();
4179
+ if (probes.length === 0) {
4180
+ return textResult("No probes set.");
4181
+ }
4182
+ const lines = probes.map(
4183
+ (p) => `${p.probeId}: "${p.label}" at ${p.file}:${p.line} \u2014 [${p.expressions.join(", ")}]`
4184
+ );
4185
+ return textResult(lines.join("\n"));
4186
+ },
4187
+ { annotations: { readOnlyHint: true } }
4188
+ )
4189
+ ];
4190
+ }
4191
+ function buildProbeResultTools(manager) {
4192
+ return [
4193
+ tool6(
4194
+ "debug_get_probe_results",
4195
+ "Fetch captured probe hit data. Returns expression values from each time a probed line executed.",
4196
+ {
4197
+ probeId: z6.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4198
+ label: z6.string().optional().describe("Filter results by probe label"),
4199
+ limit: z6.number().optional().describe("Maximum number of recent hits to return (default: all)")
4200
+ },
4201
+ async ({ probeId, label, limit }) => {
4202
+ const clientOrErr = requireDebugClient2(manager);
4203
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4204
+ try {
4205
+ let filterLabel = label;
4206
+ if (probeId && !filterLabel) {
4207
+ const probe = clientOrErr.listProbes().find((p) => p.probeId === probeId);
4208
+ if (probe) filterLabel = probe.label;
4209
+ }
4210
+ const hits = await clientOrErr.getProbeResults(filterLabel, limit);
4211
+ if (hits.length === 0) {
4212
+ const filterDesc = filterLabel ? ` for "${filterLabel}"` : "";
4213
+ return textResult(
4214
+ `No probe hits${filterDesc}. Make sure the probed code path has been triggered.`
4215
+ );
4216
+ }
4217
+ const formatted = formatProbeHits(hits);
4218
+ return textResult(formatted);
4219
+ } catch (error) {
4220
+ return textResult(`Failed to get probe results: ${formatError3(error)}`);
4221
+ }
4222
+ },
4223
+ { annotations: { readOnlyHint: true } }
4224
+ )
4225
+ ];
4226
+ }
4227
+ function formatProbeHits(hits) {
4228
+ const grouped = /* @__PURE__ */ new Map();
4229
+ for (const hit of hits) {
4230
+ const group = grouped.get(hit.label) ?? [];
4231
+ group.push(hit);
4232
+ grouped.set(hit.label, group);
4233
+ }
4234
+ const sections = [];
4235
+ for (const [label, labelHits] of grouped) {
4236
+ const header = `Probe "${label}" \u2014 hit ${labelHits.length} time${labelHits.length === 1 ? "" : "s"}:`;
4237
+ const lines = labelHits.map((hit) => {
4238
+ const time = new Date(hit.timestamp).toLocaleTimeString("en-US", { hour12: false });
4239
+ const entries = Object.entries(hit.data).map(([key, val]) => `${key}=${formatProbeValue(val)}`).join(", ");
4240
+ return ` [${time}] ${entries}`;
4241
+ });
4242
+ sections.push([header, ...lines].join("\n"));
4243
+ }
4244
+ return sections.join("\n\n");
4245
+ }
4246
+ function formatProbeValue(value) {
4247
+ if (value === null) return "null";
4248
+ if (value === void 0) return "undefined";
4249
+ if (typeof value === "string") {
4250
+ return value.length > 100 ? `"${value.slice(0, 97)}..."` : `"${value}"`;
4251
+ }
4252
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4253
+ if (Array.isArray(value)) {
4254
+ return `Array(${value.length})`;
4255
+ }
4256
+ if (typeof value === "object") {
4257
+ const keys = Object.keys(value);
4258
+ if (keys.length <= 3) {
4259
+ const preview = keys.map((k) => `${k}: ${formatProbeValue(value[k])}`).join(", ");
4260
+ return `{${preview}}`;
4261
+ }
4262
+ return `Object(${keys.length} keys)`;
4263
+ }
4264
+ return String(value);
4265
+ }
4266
+ function buildExecutionControlTools(manager) {
4267
+ return [
4268
+ tool6(
4269
+ "debug_continue",
4270
+ "Resume execution after the debugger has paused at a breakpoint.",
4271
+ {},
4272
+ async () => {
4273
+ const clientOrErr = requireDebugClient2(manager);
4274
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4275
+ if (!clientOrErr.isPaused()) {
4276
+ return textResult("Debugger is not paused.");
4277
+ }
4278
+ try {
4279
+ await clientOrErr.resume();
4280
+ return textResult("Execution resumed.");
4281
+ } catch (error) {
4282
+ return textResult(`Failed to resume: ${formatError3(error)}`);
4283
+ }
4284
+ }
4285
+ ),
4286
+ tool6(
4287
+ "debug_step_over",
4288
+ "Step over the current line while paused at a breakpoint. Executes the current line and pauses at the next line in the same function.",
4289
+ {},
4290
+ async () => {
4291
+ const clientOrErr = requireDebugClient2(manager);
4292
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4293
+ if (!clientOrErr.isPaused()) {
4294
+ return textResult("Debugger is not paused.");
4295
+ }
4296
+ try {
4297
+ await clientOrErr.stepOver();
4298
+ return textResult("Stepped over. Use debug_inspect_paused to see current state.");
4299
+ } catch (error) {
4300
+ return textResult(`Failed to step over: ${formatError3(error)}`);
4301
+ }
4302
+ }
4303
+ ),
4304
+ tool6(
4305
+ "debug_step_into",
4306
+ "Step into the function call on the current line while paused at a breakpoint. Pauses at the first line inside the called function.",
4307
+ {},
4308
+ async () => {
4309
+ const clientOrErr = requireDebugClient2(manager);
4310
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4311
+ if (!clientOrErr.isPaused()) {
4312
+ return textResult("Debugger is not paused.");
4313
+ }
4314
+ try {
4315
+ await clientOrErr.stepInto();
4316
+ return textResult("Stepped into. Use debug_inspect_paused to see current state.");
4317
+ } catch (error) {
4318
+ return textResult(`Failed to step into: ${formatError3(error)}`);
4319
+ }
4320
+ }
4321
+ )
4322
+ ];
4323
+ }
4324
+ function buildDebugTools(manager) {
4325
+ return [
4326
+ ...buildDebugLifecycleTools(manager),
4327
+ ...buildBreakpointTools(manager),
4328
+ ...buildProbeManagementTools(manager),
4329
+ ...buildProbeResultTools(manager),
4330
+ ...buildInspectionTools(manager),
4331
+ ...buildExecutionControlTools(manager),
4332
+ ...buildTelemetryTools(manager),
4333
+ ...buildClientDebugTools(manager)
3069
4334
  ];
3070
4335
  }
3071
4336
 
@@ -3098,14 +4363,16 @@ function getModeTools(agentMode, connection, config, context) {
3098
4363
  return config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : [];
3099
4364
  }
3100
4365
  }
3101
- function createConveyorMcpServer(connection, config, context, agentMode) {
4366
+ function createConveyorMcpServer(connection, config, context, agentMode, debugManager) {
3102
4367
  const commonTools = buildCommonTools(connection, config);
3103
4368
  const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
3104
4369
  const modeTools = getModeTools(effectiveMode, connection, config, context);
3105
4370
  const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
4371
+ const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
4372
+ const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
3106
4373
  return createSdkMcpServer({
3107
4374
  name: "conveyor",
3108
- tools: [...commonTools, ...modeTools, ...discoveryTools]
4375
+ tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools, ...emergencyTools]
3109
4376
  });
3110
4377
  }
3111
4378
 
@@ -3183,9 +4450,9 @@ async function handleExitPlanMode(host, input) {
3183
4450
  const taskProps = await host.connection.getTaskProperties();
3184
4451
  const missingProps = [];
3185
4452
  if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
3186
- if (!taskProps.storyPointId) missingProps.push("story points (use set_story_points)");
4453
+ if (!taskProps.storyPointId) missingProps.push("story points (use update_task_properties)");
3187
4454
  if (!taskProps.title || taskProps.title === "Untitled")
3188
- missingProps.push("title (use set_task_title)");
4455
+ missingProps.push("title (use update_task_properties)");
3189
4456
  if (missingProps.length > 0) {
3190
4457
  return {
3191
4458
  behavior: "deny",
@@ -3311,24 +4578,6 @@ function buildHooks(host) {
3311
4578
  ]
3312
4579
  };
3313
4580
  }
3314
- function buildSandboxConfig(host) {
3315
- const apiHostname = new URL(host.config.conveyorApiUrl).hostname;
3316
- return {
3317
- enabled: true,
3318
- autoAllowBashIfSandboxed: true,
3319
- allowUnsandboxedCommands: false,
3320
- filesystem: {
3321
- allowWrite: [`${host.config.workspaceDir}/**`],
3322
- denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
3323
- denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
3324
- },
3325
- network: {
3326
- allowedDomains: [apiHostname, "api.anthropic.com"],
3327
- allowManagedDomainsOnly: true,
3328
- allowLocalBinding: true
3329
- }
3330
- };
3331
- }
3332
4581
  function isReadOnlyMode(mode, hasExitedPlanMode) {
3333
4582
  return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
3334
4583
  }
@@ -3341,7 +4590,6 @@ function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
3341
4590
  function buildQueryOptions(host, context) {
3342
4591
  const settings = context.agentSettings ?? host.config.agentSettings ?? {};
3343
4592
  const mode = host.agentMode;
3344
- const isCloud = host.config.mode === "pm";
3345
4593
  const isReadOnly = isReadOnlyMode(mode, host.hasExitedPlanMode);
3346
4594
  const needsCanUseTool = isReadOnly;
3347
4595
  const systemPromptText = buildSystemPrompt(
@@ -3361,7 +4609,7 @@ function buildQueryOptions(host, context) {
3361
4609
  },
3362
4610
  settingSources,
3363
4611
  cwd: host.config.workspaceDir,
3364
- permissionMode: needsCanUseTool ? "acceptEdits" : "bypassPermissions",
4612
+ permissionMode: needsCanUseTool ? "plan" : "bypassPermissions",
3365
4613
  allowDangerouslySkipPermissions: !needsCanUseTool,
3366
4614
  canUseTool: buildCanUseTool(host),
3367
4615
  tools: { type: "preset", preset: "claude_code" },
@@ -3378,9 +4626,6 @@ function buildQueryOptions(host, context) {
3378
4626
  logger3.warn("Claude Code stderr", { data: data.trimEnd() });
3379
4627
  }
3380
4628
  };
3381
- if (isCloud && isReadOnly) {
3382
- baseOptions.sandbox = buildSandboxConfig(host);
3383
- }
3384
4629
  return baseOptions;
3385
4630
  }
3386
4631
  function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
@@ -4645,17 +5890,17 @@ import {
4645
5890
  } from "@anthropic-ai/claude-agent-sdk";
4646
5891
 
4647
5892
  // src/tools/project-tools.ts
4648
- import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
4649
- import { z as z4 } from "zod";
5893
+ import { tool as tool7 } from "@anthropic-ai/claude-agent-sdk";
5894
+ import { z as z7 } from "zod";
4650
5895
  function buildReadTools(connection) {
4651
5896
  return [
4652
- tool4(
5897
+ tool7(
4653
5898
  "list_tasks",
4654
5899
  "List tasks in the project. Optionally filter by status or assignee.",
4655
5900
  {
4656
- status: z4.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
4657
- assigneeId: z4.string().optional().describe("Filter by assigned user ID"),
4658
- limit: z4.number().optional().describe("Max number of tasks to return (default 50)")
5901
+ status: z7.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
5902
+ assigneeId: z7.string().optional().describe("Filter by assigned user ID"),
5903
+ limit: z7.number().optional().describe("Max number of tasks to return (default 50)")
4659
5904
  },
4660
5905
  async (params) => {
4661
5906
  try {
@@ -4669,10 +5914,10 @@ function buildReadTools(connection) {
4669
5914
  },
4670
5915
  { annotations: { readOnlyHint: true } }
4671
5916
  ),
4672
- tool4(
5917
+ tool7(
4673
5918
  "get_task",
4674
5919
  "Get detailed information about a task including its chat messages, child tasks, and codespace status.",
4675
- { task_id: z4.string().describe("The task ID to look up") },
5920
+ { task_id: z7.string().describe("The task ID to look up") },
4676
5921
  async ({ task_id }) => {
4677
5922
  try {
4678
5923
  const task = await connection.requestGetTask(task_id);
@@ -4685,14 +5930,14 @@ function buildReadTools(connection) {
4685
5930
  },
4686
5931
  { annotations: { readOnlyHint: true } }
4687
5932
  ),
4688
- tool4(
5933
+ tool7(
4689
5934
  "search_tasks",
4690
5935
  "Search tasks by tags, text query, or status filters.",
4691
5936
  {
4692
- tagNames: z4.array(z4.string()).optional().describe("Filter by tag names"),
4693
- searchQuery: z4.string().optional().describe("Text search in title/description"),
4694
- statusFilters: z4.array(z4.string()).optional().describe("Filter by statuses"),
4695
- limit: z4.number().optional().describe("Max results (default 20)")
5937
+ tagNames: z7.array(z7.string()).optional().describe("Filter by tag names"),
5938
+ searchQuery: z7.string().optional().describe("Text search in title/description"),
5939
+ statusFilters: z7.array(z7.string()).optional().describe("Filter by statuses"),
5940
+ limit: z7.number().optional().describe("Max results (default 20)")
4696
5941
  },
4697
5942
  async (params) => {
4698
5943
  try {
@@ -4706,7 +5951,7 @@ function buildReadTools(connection) {
4706
5951
  },
4707
5952
  { annotations: { readOnlyHint: true } }
4708
5953
  ),
4709
- tool4(
5954
+ tool7(
4710
5955
  "list_tags",
4711
5956
  "List all tags available in the project.",
4712
5957
  {},
@@ -4722,7 +5967,7 @@ function buildReadTools(connection) {
4722
5967
  },
4723
5968
  { annotations: { readOnlyHint: true } }
4724
5969
  ),
4725
- tool4(
5970
+ tool7(
4726
5971
  "get_project_summary",
4727
5972
  "Get a summary of the project including task counts by status and active builds.",
4728
5973
  {},
@@ -4742,15 +5987,15 @@ function buildReadTools(connection) {
4742
5987
  }
4743
5988
  function buildMutationTools(connection) {
4744
5989
  return [
4745
- tool4(
5990
+ tool7(
4746
5991
  "create_task",
4747
5992
  "Create a new task in the project.",
4748
5993
  {
4749
- title: z4.string().describe("Task title"),
4750
- description: z4.string().optional().describe("Task description"),
4751
- plan: z4.string().optional().describe("Implementation plan in markdown"),
4752
- status: z4.string().optional().describe("Initial status (default: Planning)"),
4753
- isBug: z4.boolean().optional().describe("Whether this is a bug report")
5994
+ title: z7.string().describe("Task title"),
5995
+ description: z7.string().optional().describe("Task description"),
5996
+ plan: z7.string().optional().describe("Implementation plan in markdown"),
5997
+ status: z7.string().optional().describe("Initial status (default: Planning)"),
5998
+ isBug: z7.boolean().optional().describe("Whether this is a bug report")
4754
5999
  },
4755
6000
  async (params) => {
4756
6001
  try {
@@ -4763,16 +6008,16 @@ function buildMutationTools(connection) {
4763
6008
  }
4764
6009
  }
4765
6010
  ),
4766
- tool4(
6011
+ tool7(
4767
6012
  "update_task",
4768
6013
  "Update an existing task's title, description, plan, status, or assignee.",
4769
6014
  {
4770
- task_id: z4.string().describe("The task ID to update"),
4771
- title: z4.string().optional().describe("New title"),
4772
- description: z4.string().optional().describe("New description"),
4773
- plan: z4.string().optional().describe("New plan in markdown"),
4774
- status: z4.string().optional().describe("New status"),
4775
- assignedUserId: z4.string().nullable().optional().describe("Assign to user ID, or null to unassign")
6015
+ task_id: z7.string().describe("The task ID to update"),
6016
+ title: z7.string().optional().describe("New title"),
6017
+ description: z7.string().optional().describe("New description"),
6018
+ plan: z7.string().optional().describe("New plan in markdown"),
6019
+ status: z7.string().optional().describe("New status"),
6020
+ assignedUserId: z7.string().nullable().optional().describe("Assign to user ID, or null to unassign")
4776
6021
  },
4777
6022
  async ({ task_id, ...fields }) => {
4778
6023
  try {
@@ -5004,8 +6249,8 @@ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
5004
6249
 
5005
6250
  // src/tools/audit-tools.ts
5006
6251
  import { randomUUID as randomUUID3 } from "crypto";
5007
- import { tool as tool5, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
5008
- import { z as z5 } from "zod";
6252
+ import { tool as tool8, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
6253
+ import { z as z8 } from "zod";
5009
6254
  function mapCreateTag(input) {
5010
6255
  return {
5011
6256
  type: "create_tag",
@@ -5087,14 +6332,14 @@ function collectRecommendation(toolName, input, collector, onRecommendation) {
5087
6332
  }
5088
6333
  function createAuditMcpServer(collector, onRecommendation) {
5089
6334
  const auditTools = [
5090
- tool5(
6335
+ tool8(
5091
6336
  "recommend_create_tag",
5092
6337
  "Recommend creating a new tag for an uncovered subsystem or area",
5093
6338
  {
5094
- name: z5.string().describe("Proposed tag name (lowercase, hyphenated)"),
5095
- color: z5.string().optional().describe("Hex color code"),
5096
- description: z5.string().describe("What this tag covers"),
5097
- reasoning: z5.string().describe("Why this tag should be created")
6339
+ name: z8.string().describe("Proposed tag name (lowercase, hyphenated)"),
6340
+ color: z8.string().optional().describe("Hex color code"),
6341
+ description: z8.string().describe("What this tag covers"),
6342
+ reasoning: z8.string().describe("Why this tag should be created")
5098
6343
  },
5099
6344
  async (args) => {
5100
6345
  const result = collectRecommendation(
@@ -5106,14 +6351,14 @@ function createAuditMcpServer(collector, onRecommendation) {
5106
6351
  return { content: [{ type: "text", text: result }] };
5107
6352
  }
5108
6353
  ),
5109
- tool5(
6354
+ tool8(
5110
6355
  "recommend_update_description",
5111
6356
  "Recommend updating a tag's description to better reflect its scope",
5112
6357
  {
5113
- tagId: z5.string(),
5114
- tagName: z5.string(),
5115
- description: z5.string().describe("Proposed new description"),
5116
- reasoning: z5.string()
6358
+ tagId: z8.string(),
6359
+ tagName: z8.string(),
6360
+ description: z8.string().describe("Proposed new description"),
6361
+ reasoning: z8.string()
5117
6362
  },
5118
6363
  async (args) => {
5119
6364
  const result = collectRecommendation(
@@ -5125,16 +6370,16 @@ function createAuditMcpServer(collector, onRecommendation) {
5125
6370
  return { content: [{ type: "text", text: result }] };
5126
6371
  }
5127
6372
  ),
5128
- tool5(
6373
+ tool8(
5129
6374
  "recommend_context_link",
5130
6375
  "Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
5131
6376
  {
5132
- tagId: z5.string(),
5133
- tagName: z5.string(),
5134
- linkType: z5.enum(["rule", "doc", "file", "folder"]),
5135
- path: z5.string(),
5136
- label: z5.string().optional(),
5137
- reasoning: z5.string()
6377
+ tagId: z8.string(),
6378
+ tagName: z8.string(),
6379
+ linkType: z8.enum(["rule", "doc", "file", "folder"]),
6380
+ path: z8.string(),
6381
+ label: z8.string().optional(),
6382
+ reasoning: z8.string()
5138
6383
  },
5139
6384
  async (args) => {
5140
6385
  const result = collectRecommendation(
@@ -5146,16 +6391,16 @@ function createAuditMcpServer(collector, onRecommendation) {
5146
6391
  return { content: [{ type: "text", text: result }] };
5147
6392
  }
5148
6393
  ),
5149
- tool5(
6394
+ tool8(
5150
6395
  "flag_documentation_gap",
5151
6396
  "Flag a file that agents read heavily but has no tag documentation linked",
5152
6397
  {
5153
- tagName: z5.string().describe("Tag whose agents read this file"),
5154
- tagId: z5.string().optional(),
5155
- filePath: z5.string(),
5156
- readCount: z5.number(),
5157
- suggestedAction: z5.string().describe("What doc or rule should be created"),
5158
- reasoning: z5.string()
6398
+ tagName: z8.string().describe("Tag whose agents read this file"),
6399
+ tagId: z8.string().optional(),
6400
+ filePath: z8.string(),
6401
+ readCount: z8.number(),
6402
+ suggestedAction: z8.string().describe("What doc or rule should be created"),
6403
+ reasoning: z8.string()
5159
6404
  },
5160
6405
  async (args) => {
5161
6406
  const result = collectRecommendation(
@@ -5167,15 +6412,15 @@ function createAuditMcpServer(collector, onRecommendation) {
5167
6412
  return { content: [{ type: "text", text: result }] };
5168
6413
  }
5169
6414
  ),
5170
- tool5(
6415
+ tool8(
5171
6416
  "recommend_merge_tags",
5172
6417
  "Recommend merging one tag into another",
5173
6418
  {
5174
- tagId: z5.string().describe("Tag ID to be merged (removed after merge)"),
5175
- tagName: z5.string().describe("Name of the tag to be merged"),
5176
- mergeIntoTagId: z5.string().describe("Tag ID to merge into (kept)"),
5177
- mergeIntoTagName: z5.string(),
5178
- reasoning: z5.string()
6419
+ tagId: z8.string().describe("Tag ID to be merged (removed after merge)"),
6420
+ tagName: z8.string().describe("Name of the tag to be merged"),
6421
+ mergeIntoTagId: z8.string().describe("Tag ID to merge into (kept)"),
6422
+ mergeIntoTagName: z8.string(),
6423
+ reasoning: z8.string()
5179
6424
  },
5180
6425
  async (args) => {
5181
6426
  const result = collectRecommendation(
@@ -5187,14 +6432,14 @@ function createAuditMcpServer(collector, onRecommendation) {
5187
6432
  return { content: [{ type: "text", text: result }] };
5188
6433
  }
5189
6434
  ),
5190
- tool5(
6435
+ tool8(
5191
6436
  "recommend_rename_tag",
5192
6437
  "Recommend renaming a tag",
5193
6438
  {
5194
- tagId: z5.string(),
5195
- tagName: z5.string().describe("Current tag name"),
5196
- newName: z5.string().describe("Proposed new name"),
5197
- reasoning: z5.string()
6439
+ tagId: z8.string(),
6440
+ tagName: z8.string().describe("Current tag name"),
6441
+ newName: z8.string().describe("Proposed new name"),
6442
+ reasoning: z8.string()
5198
6443
  },
5199
6444
  async (args) => {
5200
6445
  const result = collectRecommendation(
@@ -5206,10 +6451,10 @@ function createAuditMcpServer(collector, onRecommendation) {
5206
6451
  return { content: [{ type: "text", text: result }] };
5207
6452
  }
5208
6453
  ),
5209
- tool5(
6454
+ tool8(
5210
6455
  "complete_audit",
5211
6456
  "Signal that the audit is complete with a summary of all findings",
5212
- { summary: z5.string().describe("Brief overview of all findings") },
6457
+ { summary: z8.string().describe("Brief overview of all findings") },
5213
6458
  async (args) => {
5214
6459
  collector.complete = true;
5215
6460
  collector.summary = args.summary ?? "Audit completed.";
@@ -6055,6 +7300,7 @@ export {
6055
7300
  ProjectConnection,
6056
7301
  createServiceLogger,
6057
7302
  errorMeta,
7303
+ injectTelemetry,
6058
7304
  ensureWorktree,
6059
7305
  removeWorktree,
6060
7306
  loadConveyorConfig,
@@ -6062,4 +7308,4 @@ export {
6062
7308
  ProjectRunner,
6063
7309
  FileCache
6064
7310
  };
6065
- //# sourceMappingURL=chunk-SL5MRNSI.js.map
7311
+ //# sourceMappingURL=chunk-PLSKXQTB.js.map