@rallycry/conveyor-agent 6.0.3 → 6.0.4

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
  // ---------------------------------------------------------------------------
@@ -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
  ];
@@ -2960,7 +3010,7 @@ function buildIconTools(connection) {
2960
3010
  return [
2961
3011
  tool3(
2962
3012
  "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.",
3013
+ "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
3014
  {},
2965
3015
  async () => {
2966
3016
  try {
@@ -2974,23 +3024,6 @@ function buildIconTools(connection) {
2974
3024
  },
2975
3025
  { annotations: { readOnlyHint: true } }
2976
3026
  ),
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
3027
  tool3(
2995
3028
  "generate_task_icon",
2996
3029
  "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 +3050,1288 @@ function buildDiscoveryTools(connection, context) {
3017
3050
  const spDescription = buildStoryPointDescription(context?.storyPoints);
3018
3051
  return [
3019
3052
  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 }) => {
3053
+ "update_task_properties",
3054
+ "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
3055
+ {
3056
+ title: z3.string().optional().describe("The new task title"),
3057
+ storyPointValue: z3.number().optional().describe(spDescription),
3058
+ tagIds: z3.array(z3.string()).optional().describe("Array of tag IDs to assign"),
3059
+ iconId: z3.string().optional().describe("Icon ID to assign (use list_icons first)")
3060
+ },
3061
+ async ({ title, storyPointValue, tagIds, iconId }) => {
3024
3062
  try {
3025
- await Promise.resolve(connection.updateTaskProperties({ storyPointValue: value }));
3026
- return textResult(`Story points set to ${value}`);
3063
+ const updateFields = {};
3064
+ if (title !== void 0) updateFields.title = title;
3065
+ if (storyPointValue !== void 0) updateFields.storyPointValue = storyPointValue;
3066
+ if (tagIds !== void 0) updateFields.tagIds = tagIds;
3067
+ if (iconId !== void 0) updateFields.iconId = iconId;
3068
+ await Promise.resolve(connection.updateTaskProperties(updateFields));
3069
+ const updatedFields = [];
3070
+ if (title !== void 0) updatedFields.push(`title to "${title}"`);
3071
+ if (storyPointValue !== void 0)
3072
+ updatedFields.push(`story points to ${storyPointValue}`);
3073
+ if (tagIds !== void 0) updatedFields.push(`tags (${tagIds.length} tag(s))`);
3074
+ if (iconId !== void 0) updatedFields.push(`icon`);
3075
+ return textResult(`Task properties updated: ${updatedFields.join(", ")}`);
3027
3076
  } catch (error) {
3028
3077
  return textResult(
3029
- `Failed to set story points: ${error instanceof Error ? error.message : "Unknown error"}`
3078
+ `Failed to update task properties: ${error instanceof Error ? error.message : "Unknown error"}`
3030
3079
  );
3031
3080
  }
3032
3081
  }
3033
3082
  ),
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.",
3083
+ ...buildIconTools(connection)
3084
+ ];
3085
+ }
3086
+
3087
+ // src/tools/debug-tools.ts
3088
+ import { tool as tool6 } from "@anthropic-ai/claude-agent-sdk";
3089
+ import { z as z6 } from "zod";
3090
+
3091
+ // src/tools/telemetry-tools.ts
3092
+ import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
3093
+ import { z as z4 } from "zod";
3094
+
3095
+ // src/debug/telemetry-injector.ts
3096
+ var BUFFER_SIZE = 200;
3097
+ var BODY_MAX_BYTES = 1024;
3098
+ var MAX_DEPTH = 3;
3099
+ var EXCLUDED_EXTENSIONS = [
3100
+ ".js",
3101
+ ".css",
3102
+ ".ico",
3103
+ ".png",
3104
+ ".jpg",
3105
+ ".jpeg",
3106
+ ".gif",
3107
+ ".svg",
3108
+ ".woff",
3109
+ ".woff2",
3110
+ ".ttf",
3111
+ ".eot",
3112
+ ".map",
3113
+ ".webp"
3114
+ ];
3115
+ var EXCLUDED_PATHS = ["/_next/", "/__nextjs", "/favicon", "/_healthz", "/healthz", "/health"];
3116
+ var EXCLUDED_SOCKET_EVENTS = ["ping", "pong", "connection", "disconnect", "websocket", "upgrade"];
3117
+ function scriptPreamble() {
3118
+ return `(function() {
3119
+ if (globalThis.__conveyorTelemetry) {
3120
+ return JSON.stringify({ success: true, alreadyInjected: true, patches: globalThis.__conveyorTelemetry._patches });
3121
+ }
3122
+
3123
+ var BUFFER_SIZE = ${BUFFER_SIZE};
3124
+ var BODY_MAX_BYTES = ${BODY_MAX_BYTES};
3125
+ var MAX_DEPTH = ${MAX_DEPTH};
3126
+ var excludedExtensions = ${JSON.stringify(EXCLUDED_EXTENSIONS)};
3127
+ var excludedPaths = ${JSON.stringify(EXCLUDED_PATHS)};
3128
+ var excludedSocketEvents = ${JSON.stringify(EXCLUDED_SOCKET_EVENTS)};`;
3129
+ }
3130
+ function scriptHelpers() {
3131
+ return `
3132
+ function truncate(str, max) {
3133
+ if (typeof str !== 'string') return '';
3134
+ if (str.length <= max) return str;
3135
+ return str.slice(0, max) + '...[truncated]';
3136
+ }
3137
+
3138
+ function safeStringify(obj, depth) {
3139
+ if (depth === undefined) depth = MAX_DEPTH;
3140
+ try {
3141
+ if (obj === null || obj === undefined) return String(obj);
3142
+ if (typeof obj === 'string') return obj;
3143
+ if (typeof obj !== 'object') return String(obj);
3144
+ if (depth <= 0) return '[Object]';
3145
+ var seen = new Set();
3146
+ return JSON.stringify(obj, function(key, value) {
3147
+ if (typeof value === 'object' && value !== null) {
3148
+ if (seen.has(value)) return '[Circular]';
3149
+ seen.add(value);
3150
+ }
3151
+ return value;
3152
+ }, 0).slice(0, BODY_MAX_BYTES);
3153
+ } catch (e) {
3154
+ return '[unserializable]';
3155
+ }
3156
+ }
3157
+
3158
+ function isExcludedUrl(url) {
3159
+ if (!url) return false;
3160
+ var lower = url.toLowerCase();
3161
+ for (var i = 0; i < excludedExtensions.length; i++) {
3162
+ if (lower.endsWith(excludedExtensions[i])) return true;
3163
+ }
3164
+ for (var j = 0; j < excludedPaths.length; j++) {
3165
+ if (lower.indexOf(excludedPaths[j]) !== -1) return true;
3166
+ }
3167
+ return false;
3168
+ }
3169
+
3170
+ function isExcludedSocketEvent(event) {
3171
+ return excludedSocketEvents.indexOf(event) !== -1;
3172
+ }
3173
+
3174
+ function nowMs() { return Date.now(); }`;
3175
+ }
3176
+ function scriptBuffer() {
3177
+ return `
3178
+ var buffer = [];
3179
+
3180
+ function pushEvent(event) {
3181
+ buffer.push(event);
3182
+ if (buffer.length > BUFFER_SIZE) { buffer.shift(); }
3183
+ }
3184
+
3185
+ function getEvents(filter, limit) {
3186
+ var result = buffer;
3187
+ if (filter) {
3188
+ if (filter.type) {
3189
+ result = result.filter(function(e) { return e.type === filter.type; });
3190
+ }
3191
+ if (filter.urlPattern) {
3192
+ var re;
3193
+ try { re = new RegExp(filter.urlPattern, 'i'); } catch(e) { re = null; }
3194
+ if (re) {
3195
+ result = result.filter(function(e) { return e.type === 'http' && re.test(e.url); });
3196
+ }
3197
+ }
3198
+ if (filter.minDuration) {
3199
+ var minD = filter.minDuration;
3200
+ result = result.filter(function(e) { return ('duration' in e) && e.duration >= minD; });
3201
+ }
3202
+ if (filter.errorOnly) {
3203
+ result = result.filter(function(e) {
3204
+ return e.type === 'error' || (e.type === 'http' && e.status >= 400);
3205
+ });
3206
+ }
3207
+ if (filter.since) {
3208
+ var since = filter.since;
3209
+ result = result.filter(function(e) { return e.timestamp >= since; });
3210
+ }
3211
+ }
3212
+ if (limit && limit > 0) { result = result.slice(-limit); }
3213
+ return result;
3214
+ }
3215
+
3216
+ function clear() { buffer = []; }
3217
+
3218
+ var patches = { express: false, prisma: false, socketIo: false, errorHandler: false };`;
3219
+ }
3220
+ function scriptPatchExpress() {
3221
+ return `
3222
+ function patchExpress() {
3223
+ try {
3224
+ var http = require('http');
3225
+ var originalEmit = http.Server.prototype.emit;
3226
+ http.Server.prototype.emit = function(event) {
3227
+ if (event === 'request') {
3228
+ var req = arguments[1];
3229
+ var res = arguments[2];
3230
+ if (req && res && !isExcludedUrl(req.url)) {
3231
+ var start = nowMs();
3232
+ var reqBody = '';
3233
+ if (req.readable) {
3234
+ var chunks = [];
3235
+ var origPush = req.push;
3236
+ req.push = function(chunk) {
3237
+ if (chunk) { chunks.push(typeof chunk === 'string' ? chunk : chunk.toString()); }
3238
+ return origPush.apply(this, arguments);
3239
+ };
3240
+ var origReqEmit = req.emit;
3241
+ req.emit = function(evt) {
3242
+ if (evt === 'end') { reqBody = truncate(chunks.join(''), BODY_MAX_BYTES); }
3243
+ return origReqEmit.apply(this, arguments);
3244
+ };
3245
+ }
3246
+ var origEnd = res.end;
3247
+ res.end = function(chunk) {
3248
+ var resBody = '';
3249
+ if (chunk) { resBody = truncate(typeof chunk === 'string' ? chunk : chunk.toString(), BODY_MAX_BYTES); }
3250
+ var duration = nowMs() - start;
3251
+ pushEvent({
3252
+ type: 'http', timestamp: start, method: req.method || 'UNKNOWN',
3253
+ url: req.url || '/', status: res.statusCode || 0, duration: duration,
3254
+ requestBody: reqBody || undefined, responseBody: resBody || undefined,
3255
+ error: res.statusCode >= 400 ? ('HTTP ' + res.statusCode) : undefined
3256
+ });
3257
+ return origEnd.apply(this, arguments);
3258
+ };
3259
+ }
3260
+ }
3261
+ return originalEmit.apply(this, arguments);
3262
+ };
3263
+ patches.express = true;
3264
+ } catch (e) {}
3265
+ }`;
3266
+ }
3267
+ function scriptPatchPrisma() {
3268
+ return `
3269
+ function patchPrisma() {
3270
+ try {
3271
+ var prismaClient = globalThis.prisma || globalThis.__prisma;
3272
+ if (!prismaClient) {
3273
+ var cacheKeys = Object.keys(require.cache || {});
3274
+ for (var i = 0; i < cacheKeys.length; i++) {
3275
+ var mod = require.cache[cacheKeys[i]];
3276
+ if (mod && mod.exports && typeof mod.exports.$on === 'function') {
3277
+ prismaClient = mod.exports;
3278
+ break;
3279
+ }
3280
+ }
3281
+ }
3282
+ if (prismaClient && typeof prismaClient.$on === 'function') {
3283
+ prismaClient.$on('query', function(e) {
3284
+ pushEvent({
3285
+ type: 'db', timestamp: nowMs(),
3286
+ query: truncate(e.query || '', BODY_MAX_BYTES),
3287
+ params: truncate(safeStringify(e.params), BODY_MAX_BYTES),
3288
+ duration: e.duration || 0
3289
+ });
3290
+ });
3291
+ patches.prisma = true;
3292
+ }
3293
+ } catch (e) {}
3294
+ }`;
3295
+ }
3296
+ function scriptPatchSocketIo() {
3297
+ return `
3298
+ function patchSocketIo() {
3299
+ try {
3300
+ var io = globalThis.io || globalThis.__io;
3301
+ if (io && io.sockets) {
3302
+ var origOn = io.sockets.constructor.prototype.on;
3303
+ if (origOn) {
3304
+ io.sockets.constructor.prototype.on = function(event, handler) {
3305
+ if (!isExcludedSocketEvent(event)) {
3306
+ var wrappedHandler = function() {
3307
+ var args = Array.prototype.slice.call(arguments);
3308
+ pushEvent({
3309
+ type: 'socket', timestamp: nowMs(), event: event, direction: 'in',
3310
+ data: truncate(safeStringify(args[0]), BODY_MAX_BYTES),
3311
+ namespace: (this.nsp && this.nsp.name) || '/'
3312
+ });
3313
+ return handler.apply(this, arguments);
3314
+ };
3315
+ return origOn.call(this, event, wrappedHandler);
3316
+ }
3317
+ return origOn.call(this, event, handler);
3318
+ };
3319
+ }
3320
+ var origEmit = io.sockets.constructor.prototype.emit;
3321
+ if (origEmit) {
3322
+ io.sockets.constructor.prototype.emit = function(event) {
3323
+ if (!isExcludedSocketEvent(event)) {
3324
+ var args = Array.prototype.slice.call(arguments, 1);
3325
+ pushEvent({
3326
+ type: 'socket', timestamp: nowMs(), event: event, direction: 'out',
3327
+ data: truncate(safeStringify(args[0]), BODY_MAX_BYTES),
3328
+ namespace: (this.nsp && this.nsp.name) || '/'
3329
+ });
3330
+ }
3331
+ return origEmit.apply(this, arguments);
3332
+ };
3333
+ }
3334
+ patches.socketIo = true;
3335
+ }
3336
+ } catch (e) {}
3337
+ }`;
3338
+ }
3339
+ function scriptPatchErrors() {
3340
+ return `
3341
+ function patchErrorHandler() {
3342
+ try {
3343
+ process.on('uncaughtException', function(err) {
3344
+ pushEvent({
3345
+ type: 'error', timestamp: nowMs(),
3346
+ message: err.message || String(err),
3347
+ stack: truncate(err.stack || '', BODY_MAX_BYTES),
3348
+ context: 'uncaughtException'
3349
+ });
3350
+ });
3351
+ process.on('unhandledRejection', function(reason) {
3352
+ var msg = (reason && reason.message) ? reason.message : String(reason);
3353
+ var stack = (reason && reason.stack) ? reason.stack : '';
3354
+ pushEvent({
3355
+ type: 'error', timestamp: nowMs(), message: msg,
3356
+ stack: truncate(stack, BODY_MAX_BYTES), context: 'unhandledRejection'
3357
+ });
3358
+ });
3359
+ patches.errorHandler = true;
3360
+ } catch (e) {}
3361
+ }`;
3362
+ }
3363
+ function scriptInit() {
3364
+ return `
3365
+ patchExpress();
3366
+ patchPrisma();
3367
+ patchSocketIo();
3368
+ patchErrorHandler();
3369
+
3370
+ globalThis.__conveyorTelemetry = {
3371
+ getEvents: getEvents,
3372
+ clear: clear,
3373
+ getStatus: function() {
3374
+ return { active: true, eventCount: buffer.length, patches: patches };
3375
+ },
3376
+ _patches: patches,
3377
+ _buffer: buffer
3378
+ };
3379
+
3380
+ return JSON.stringify({ success: true, alreadyInjected: false, patches: patches });
3381
+ })();`;
3382
+ }
3383
+ function generateTelemetryScript() {
3384
+ return [
3385
+ scriptPreamble(),
3386
+ scriptHelpers(),
3387
+ scriptBuffer(),
3388
+ scriptPatchExpress(),
3389
+ scriptPatchPrisma(),
3390
+ scriptPatchSocketIo(),
3391
+ scriptPatchErrors(),
3392
+ scriptInit()
3393
+ ].join("\n");
3394
+ }
3395
+ async function injectTelemetry(cdpClient) {
3396
+ try {
3397
+ const script = generateTelemetryScript();
3398
+ const result = await cdpClient.evaluate(script);
3399
+ if (result.type === "error") {
3400
+ return { success: false, error: result.value };
3401
+ }
3402
+ try {
3403
+ const parsed = JSON.parse(result.value);
3404
+ return {
3405
+ success: parsed.success === true,
3406
+ patches: parsed.patches
3407
+ };
3408
+ } catch {
3409
+ return { success: true };
3410
+ }
3411
+ } catch (error) {
3412
+ return {
3413
+ success: false,
3414
+ error: error instanceof Error ? error.message : "Unknown injection error"
3415
+ };
3416
+ }
3417
+ }
3418
+ async function queryTelemetry(cdpClient, filter, limit) {
3419
+ const filterJson = JSON.stringify(filter ?? {});
3420
+ const limitVal = limit ?? 20;
3421
+ const result = await cdpClient.evaluate(
3422
+ `JSON.stringify(globalThis.__conveyorTelemetry ? globalThis.__conveyorTelemetry.getEvents(${filterJson}, ${limitVal}) : [])`
3423
+ );
3424
+ if (result.type === "error") {
3425
+ throw new Error(`Telemetry query failed: ${result.value}`);
3426
+ }
3427
+ try {
3428
+ let val = result.value;
3429
+ if (val.startsWith('"') && val.endsWith('"')) {
3430
+ val = JSON.parse(val);
3431
+ }
3432
+ return JSON.parse(val);
3433
+ } catch {
3434
+ return [];
3435
+ }
3436
+ }
3437
+ async function clearTelemetry(cdpClient) {
3438
+ await cdpClient.evaluate(
3439
+ `globalThis.__conveyorTelemetry ? (globalThis.__conveyorTelemetry.clear(), 'cleared') : 'not active'`
3440
+ );
3441
+ }
3442
+ async function getTelemetryStatus(cdpClient) {
3443
+ const result = await cdpClient.evaluate(
3444
+ `JSON.stringify(globalThis.__conveyorTelemetry ? globalThis.__conveyorTelemetry.getStatus() : { active: false, eventCount: 0, patches: { express: false, prisma: false, socketIo: false, errorHandler: false } })`
3445
+ );
3446
+ if (result.type === "error") {
3447
+ return {
3448
+ active: false,
3449
+ eventCount: 0,
3450
+ patches: { express: false, prisma: false, socketIo: false, errorHandler: false }
3451
+ };
3452
+ }
3453
+ try {
3454
+ let val = result.value;
3455
+ if (val.startsWith('"') && val.endsWith('"')) {
3456
+ val = JSON.parse(val);
3457
+ }
3458
+ return JSON.parse(val);
3459
+ } catch {
3460
+ return {
3461
+ active: false,
3462
+ eventCount: 0,
3463
+ patches: { express: false, prisma: false, socketIo: false, errorHandler: false }
3464
+ };
3465
+ }
3466
+ }
3467
+
3468
+ // src/tools/telemetry-tools.ts
3469
+ function requireDebugClient(manager) {
3470
+ if (!manager.isDebugMode()) {
3471
+ return "Debug mode is not active. Use debug_enter_mode first.";
3472
+ }
3473
+ const client = manager.getClient();
3474
+ if (!client?.isConnected()) {
3475
+ return "CDP client is not connected. Try exiting and re-entering debug mode.";
3476
+ }
3477
+ return client;
3478
+ }
3479
+ function formatError(error) {
3480
+ return error instanceof Error ? error.message : "Unknown error";
3481
+ }
3482
+ function buildGetTelemetryTool(manager) {
3483
+ return tool4(
3484
+ "debug_get_telemetry",
3485
+ "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.",
3486
+ {
3487
+ type: z4.enum(["http", "db", "socket", "error"]).optional().describe("Filter by event type"),
3488
+ urlPattern: z4.string().optional().describe("Regex pattern to filter HTTP events by URL"),
3489
+ minDuration: z4.number().optional().describe("Minimum duration in ms \u2014 only return events slower than this"),
3490
+ errorOnly: z4.boolean().optional().describe("Only return error events and HTTP 4xx/5xx responses"),
3491
+ since: z4.number().optional().describe("Only return events after this timestamp (ms since epoch)"),
3492
+ limit: z4.number().optional().describe("Max events to return (default: 20, from most recent)")
3493
+ },
3494
+ async ({ type, urlPattern, minDuration, errorOnly, since, limit }) => {
3495
+ const clientOrErr = requireDebugClient(manager);
3496
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3497
+ try {
3498
+ const filter = {
3499
+ ...type && { type },
3500
+ ...urlPattern && { urlPattern },
3501
+ ...minDuration && { minDuration },
3502
+ ...errorOnly && { errorOnly },
3503
+ ...since && { since }
3504
+ };
3505
+ const hasFilter = Object.keys(filter).length > 0;
3506
+ const events = await queryTelemetry(clientOrErr, hasFilter ? filter : void 0, limit);
3507
+ if (events.length === 0) {
3508
+ return textResult("No telemetry events found matching the filter.");
3509
+ }
3510
+ return textResult(JSON.stringify(events, null, 2));
3511
+ } catch (error) {
3512
+ return textResult(`Failed to query telemetry: ${formatError(error)}`);
3513
+ }
3514
+ },
3515
+ { annotations: { readOnlyHint: true } }
3516
+ );
3517
+ }
3518
+ function buildClearTelemetryTool(manager) {
3519
+ return tool4(
3520
+ "debug_clear_telemetry",
3521
+ "Clear all captured telemetry events from the buffer. Useful to reset before reproducing a specific issue.",
3522
+ {},
3523
+ async () => {
3524
+ const clientOrErr = requireDebugClient(manager);
3525
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3526
+ try {
3527
+ await clearTelemetry(clientOrErr);
3528
+ return textResult("Telemetry buffer cleared.");
3529
+ } catch (error) {
3530
+ return textResult(`Failed to clear telemetry: ${formatError(error)}`);
3531
+ }
3532
+ }
3533
+ );
3534
+ }
3535
+ function buildTelemetryStatusTool(manager) {
3536
+ return tool4(
3537
+ "debug_telemetry_status",
3538
+ "Check if telemetry is active, how many events have been captured, and which framework patches (Express, Prisma, Socket.IO) were successfully applied.",
3539
+ {},
3540
+ async () => {
3541
+ const clientOrErr = requireDebugClient(manager);
3542
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3543
+ try {
3544
+ const status = await getTelemetryStatus(clientOrErr);
3545
+ return textResult(JSON.stringify(status, null, 2));
3546
+ } catch (error) {
3547
+ return textResult(`Failed to get telemetry status: ${formatError(error)}`);
3548
+ }
3549
+ },
3550
+ { annotations: { readOnlyHint: true } }
3551
+ );
3552
+ }
3553
+ function buildTelemetryTools(manager) {
3554
+ return [
3555
+ buildGetTelemetryTool(manager),
3556
+ buildClearTelemetryTool(manager),
3557
+ buildTelemetryStatusTool(manager)
3558
+ ];
3559
+ }
3560
+
3561
+ // src/tools/client-debug-tools.ts
3562
+ import { tool as tool5 } from "@anthropic-ai/claude-agent-sdk";
3563
+ import { z as z5 } from "zod";
3564
+ function requirePlaywrightClient(manager) {
3565
+ if (!manager.isClientDebugMode()) {
3566
+ return "Client debug mode is not active. Use debug_enter_mode with clientSide: true first.";
3567
+ }
3568
+ const client = manager.getPlaywrightClient();
3569
+ if (!client?.isConnected()) {
3570
+ return "Playwright client is not connected. Try exiting and re-entering debug mode.";
3571
+ }
3572
+ return client;
3573
+ }
3574
+ function formatError2(error) {
3575
+ return error instanceof Error ? error.message : "Unknown error";
3576
+ }
3577
+ function buildClientBreakpointTools(manager) {
3578
+ return [
3579
+ tool5(
3580
+ "debug_set_client_breakpoint",
3581
+ "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.",
3582
+ {
3583
+ file: z5.string().describe(
3584
+ "Original source file path (e.g., src/components/App.tsx) \u2014 source maps resolve automatically"
3585
+ ),
3586
+ line: z5.number().describe("Line number (1-based) in the original source file"),
3587
+ condition: z5.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3588
+ },
3589
+ async ({ file, line, condition }) => {
3590
+ const clientOrErr = requirePlaywrightClient(manager);
3591
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3592
+ try {
3593
+ const breakpointId = await clientOrErr.setBreakpoint(file, line, condition);
3594
+ const condStr = condition ? ` (condition: ${condition})` : "";
3595
+ const sourceMapNote = clientOrErr.hasSourceMaps() === false ? "\n\u26A0\uFE0F Source maps not detected \u2014 breakpoints will reference bundled code." : "";
3596
+ return textResult(
3597
+ `Client breakpoint set: ${file}:${line}${condStr}
3598
+ Breakpoint ID: ${breakpointId}${sourceMapNote}`
3599
+ );
3600
+ } catch (error) {
3601
+ return textResult(`Failed to set client breakpoint: ${formatError2(error)}`);
3602
+ }
3603
+ }
3604
+ ),
3605
+ tool5(
3606
+ "debug_remove_client_breakpoint",
3607
+ "Remove a previously set client-side breakpoint by its ID.",
3608
+ {
3609
+ breakpointId: z5.string().describe("The breakpoint ID returned by debug_set_client_breakpoint")
3610
+ },
3611
+ async ({ breakpointId }) => {
3612
+ const clientOrErr = requirePlaywrightClient(manager);
3613
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3614
+ try {
3615
+ await clientOrErr.removeBreakpoint(breakpointId);
3616
+ return textResult(`Client breakpoint ${breakpointId} removed.`);
3617
+ } catch (error) {
3618
+ return textResult(`Failed to remove client breakpoint: ${formatError2(error)}`);
3619
+ }
3620
+ }
3621
+ ),
3622
+ tool5(
3623
+ "debug_list_client_breakpoints",
3624
+ "List all active client-side breakpoints with their file, line, and condition.",
3625
+ {},
3626
+ // oxlint-disable-next-line require-await
3627
+ async () => {
3628
+ const clientOrErr = requirePlaywrightClient(manager);
3629
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3630
+ const breakpoints = clientOrErr.listBreakpoints();
3631
+ if (breakpoints.length === 0) {
3632
+ return textResult("No client breakpoints set.");
3633
+ }
3634
+ return textResult(JSON.stringify(breakpoints, null, 2));
3635
+ },
3636
+ { annotations: { readOnlyHint: true } }
3637
+ )
3638
+ ];
3639
+ }
3640
+ function buildClientInspectionTools(manager) {
3641
+ return [
3642
+ tool5(
3643
+ "debug_inspect_client_paused",
3644
+ "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.",
3645
+ {},
3646
+ async () => {
3647
+ const clientOrErr = requirePlaywrightClient(manager);
3648
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3649
+ const queuedHits = manager.drainClientBreakpointHitQueue();
3650
+ if (!clientOrErr.isPaused()) {
3651
+ if (queuedHits.length > 0) {
3652
+ return textResult(
3653
+ `Client debugger was paused but has since resumed. Recent breakpoint hits:
3654
+ ${JSON.stringify(queuedHits, null, 2)}`
3655
+ );
3656
+ }
3657
+ return textResult(
3658
+ "Client debugger is not currently paused. Set client breakpoints and trigger the code path in the browser to pause execution."
3659
+ );
3660
+ }
3661
+ try {
3662
+ const callStack = clientOrErr.getCallStack();
3663
+ const topFrame = callStack[0];
3664
+ let variables = [];
3665
+ if (topFrame) {
3666
+ try {
3667
+ variables = await clientOrErr.getScopeVariables(topFrame.callFrameId);
3668
+ } catch {
3669
+ }
3670
+ }
3671
+ const result = {
3672
+ side: "client",
3673
+ reason: clientOrErr.getPausedState()?.reason,
3674
+ hitBreakpoints: clientOrErr.getPausedState()?.hitBreakpoints,
3675
+ callStack,
3676
+ localVariables: variables
3677
+ };
3678
+ return textResult(JSON.stringify(result, null, 2));
3679
+ } catch (error) {
3680
+ return textResult(`Failed to inspect client paused state: ${formatError2(error)}`);
3681
+ }
3682
+ },
3683
+ { annotations: { readOnlyHint: true } }
3684
+ ),
3685
+ tool5(
3686
+ "debug_evaluate_client",
3687
+ "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.",
3037
3688
  {
3038
- tagIds: z3.array(z3.string()).describe("Array of tag IDs to assign")
3689
+ expression: z5.string().describe("JavaScript expression to evaluate in the browser context"),
3690
+ frameIndex: z5.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3039
3691
  },
3040
- async ({ tagIds }) => {
3692
+ async ({ expression, frameIndex }) => {
3693
+ const clientOrErr = requirePlaywrightClient(manager);
3694
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3695
+ try {
3696
+ let callFrameId;
3697
+ if (clientOrErr.isPaused()) {
3698
+ const callStack = clientOrErr.getCallStack();
3699
+ const frame = callStack[frameIndex ?? 0];
3700
+ callFrameId = frame?.callFrameId;
3701
+ }
3702
+ const result = await clientOrErr.evaluate(expression, callFrameId);
3703
+ return textResult(`(${result.type}) ${result.value}`);
3704
+ } catch (error) {
3705
+ return textResult(`Client evaluation failed: ${formatError2(error)}`);
3706
+ }
3707
+ }
3708
+ )
3709
+ ];
3710
+ }
3711
+ function buildClientExecutionTools(manager) {
3712
+ return [
3713
+ tool5(
3714
+ "debug_continue_client",
3715
+ "Resume client-side execution after the browser debugger has paused at a breakpoint.",
3716
+ {},
3717
+ async () => {
3718
+ const clientOrErr = requirePlaywrightClient(manager);
3719
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3720
+ if (!clientOrErr.isPaused()) {
3721
+ return textResult("Client debugger is not paused.");
3722
+ }
3041
3723
  try {
3042
- await Promise.resolve(connection.updateTaskProperties({ tagIds }));
3043
- return textResult(`Tags assigned: ${tagIds.length} tag(s)`);
3724
+ await clientOrErr.resume();
3725
+ return textResult("Client execution resumed.");
3044
3726
  } catch (error) {
3727
+ return textResult(`Failed to resume client: ${formatError2(error)}`);
3728
+ }
3729
+ }
3730
+ )
3731
+ ];
3732
+ }
3733
+ function buildClientInteractionTools(manager) {
3734
+ return [
3735
+ tool5(
3736
+ "debug_client_screenshot",
3737
+ "Take a screenshot of the current page state in the headless browser. Returns the image as base64-encoded PNG.",
3738
+ {},
3739
+ async () => {
3740
+ const clientOrErr = requirePlaywrightClient(manager);
3741
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3742
+ try {
3743
+ const base64 = await clientOrErr.screenshot();
3744
+ return {
3745
+ content: [
3746
+ imageBlock(base64, "image/png"),
3747
+ {
3748
+ type: "text",
3749
+ text: `Screenshot captured (${clientOrErr.getCurrentUrl()})`
3750
+ }
3751
+ ]
3752
+ };
3753
+ } catch (error) {
3754
+ return textResult(`Failed to capture screenshot: ${formatError2(error)}`);
3755
+ }
3756
+ },
3757
+ { annotations: { readOnlyHint: true } }
3758
+ ),
3759
+ tool5(
3760
+ "debug_navigate_client",
3761
+ "Navigate the headless browser to a specific URL. Use this to reproduce specific flows or visit different pages.",
3762
+ {
3763
+ url: z5.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3764
+ },
3765
+ async ({ url }) => {
3766
+ const clientOrErr = requirePlaywrightClient(manager);
3767
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3768
+ try {
3769
+ await clientOrErr.navigate(url);
3770
+ return textResult(`Navigated to: ${clientOrErr.getCurrentUrl()}`);
3771
+ } catch (error) {
3772
+ return textResult(`Failed to navigate: ${formatError2(error)}`);
3773
+ }
3774
+ }
3775
+ ),
3776
+ tool5(
3777
+ "debug_click_client",
3778
+ "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.",
3779
+ {
3780
+ selector: z5.string().describe(
3781
+ "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
3782
+ )
3783
+ },
3784
+ async ({ selector }) => {
3785
+ const clientOrErr = requirePlaywrightClient(manager);
3786
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3787
+ try {
3788
+ await clientOrErr.click(selector);
3789
+ return textResult(`Clicked: ${selector}`);
3790
+ } catch (error) {
3791
+ return textResult(`Failed to click "${selector}": ${formatError2(error)}`);
3792
+ }
3793
+ }
3794
+ )
3795
+ ];
3796
+ }
3797
+ function buildClientConsoleTool(manager) {
3798
+ return tool5(
3799
+ "debug_get_client_console",
3800
+ "Get console messages captured from the headless browser. Includes console.log, warn, error, etc.",
3801
+ {
3802
+ level: z5.string().optional().describe("Filter by console level: log, warn, error, info, debug"),
3803
+ limit: z5.number().optional().describe("Maximum number of recent messages to return (default: all)")
3804
+ },
3805
+ // oxlint-disable-next-line require-await
3806
+ async ({ level, limit }) => {
3807
+ const clientOrErr = requirePlaywrightClient(manager);
3808
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3809
+ const messages = clientOrErr.getConsoleMessages(level, limit);
3810
+ if (messages.length === 0) {
3811
+ const filterDesc = level ? ` (level: ${level})` : "";
3812
+ return textResult(`No console messages captured${filterDesc}.`);
3813
+ }
3814
+ const formatted = messages.map((m) => {
3815
+ const time = new Date(m.timestamp).toLocaleTimeString("en-US", { hour12: false });
3816
+ const loc = m.url ? ` [${m.url}${m.line ? `:${m.line}` : ""}]` : "";
3817
+ return `[${time}] ${m.level.toUpperCase()}: ${m.text}${loc}`;
3818
+ }).join("\n");
3819
+ return textResult(`${messages.length} console message(s):
3820
+ ${formatted}`);
3821
+ },
3822
+ { annotations: { readOnlyHint: true } }
3823
+ );
3824
+ }
3825
+ function buildClientNetworkTool(manager) {
3826
+ return tool5(
3827
+ "debug_get_client_network",
3828
+ "Get network requests captured from the headless browser. Shows URLs, methods, status codes, and timing.",
3829
+ {
3830
+ filter: z5.string().optional().describe("Regex pattern to filter requests by URL"),
3831
+ limit: z5.number().optional().describe("Maximum number of recent requests to return (default: all)")
3832
+ },
3833
+ // oxlint-disable-next-line require-await
3834
+ async ({ filter, limit }) => {
3835
+ const clientOrErr = requirePlaywrightClient(manager);
3836
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3837
+ const requests = clientOrErr.getNetworkRequests(filter, limit);
3838
+ if (requests.length === 0) {
3839
+ const filterDesc = filter ? ` matching "${filter}"` : "";
3840
+ return textResult(`No network requests captured${filterDesc}.`);
3841
+ }
3842
+ const formatted = requests.map((r) => {
3843
+ const time = new Date(r.timestamp).toLocaleTimeString("en-US", { hour12: false });
3844
+ const status = r.status ? ` \u2192 ${r.status}` : " \u2192 (pending)";
3845
+ const dur = r.duration ? ` (${r.duration}ms)` : "";
3846
+ return `[${time}] ${r.method} ${r.url}${status}${dur}`;
3847
+ }).join("\n");
3848
+ return textResult(`${requests.length} network request(s):
3849
+ ${formatted}`);
3850
+ },
3851
+ { annotations: { readOnlyHint: true } }
3852
+ );
3853
+ }
3854
+ function buildClientErrorsTool(manager) {
3855
+ return tool5(
3856
+ "debug_get_client_errors",
3857
+ "Get uncaught errors captured from the headless browser. Includes error messages and source-mapped stack traces.",
3858
+ {
3859
+ limit: z5.number().optional().describe("Maximum number of recent errors to return (default: all)")
3860
+ },
3861
+ // oxlint-disable-next-line require-await
3862
+ async ({ limit }) => {
3863
+ const clientOrErr = requirePlaywrightClient(manager);
3864
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3865
+ const errors = clientOrErr.getPageErrors(limit);
3866
+ if (errors.length === 0) {
3867
+ return textResult("No uncaught page errors captured.");
3868
+ }
3869
+ const formatted = errors.map((e) => {
3870
+ const time = new Date(e.timestamp).toLocaleTimeString("en-US", { hour12: false });
3871
+ const stack = e.stack ? `
3872
+ ${e.stack.split("\n").slice(0, 5).join("\n ")}` : "";
3873
+ return `[${time}] ${e.message}${stack}`;
3874
+ }).join("\n\n");
3875
+ return textResult(`${errors.length} page error(s):
3876
+ ${formatted}`);
3877
+ },
3878
+ { annotations: { readOnlyHint: true } }
3879
+ );
3880
+ }
3881
+ function buildClientCaptureTools(manager) {
3882
+ return [
3883
+ buildClientConsoleTool(manager),
3884
+ buildClientNetworkTool(manager),
3885
+ buildClientErrorsTool(manager)
3886
+ ];
3887
+ }
3888
+ function buildClientDebugTools(manager) {
3889
+ return [
3890
+ ...buildClientBreakpointTools(manager),
3891
+ ...buildClientInspectionTools(manager),
3892
+ ...buildClientExecutionTools(manager),
3893
+ ...buildClientInteractionTools(manager),
3894
+ ...buildClientCaptureTools(manager)
3895
+ ];
3896
+ }
3897
+
3898
+ // src/tools/debug-tools.ts
3899
+ function requireDebugClient2(manager) {
3900
+ if (!manager.isDebugMode()) {
3901
+ return "Debug mode is not active. Use debug_enter_mode first.";
3902
+ }
3903
+ const client = manager.getClient();
3904
+ if (!client?.isConnected()) {
3905
+ return "CDP client is not connected. Try exiting and re-entering debug mode.";
3906
+ }
3907
+ return client;
3908
+ }
3909
+ function formatError3(error) {
3910
+ return error instanceof Error ? error.message : "Unknown error";
3911
+ }
3912
+ async function handleEnterDebugMode(manager, {
3913
+ hypothesis,
3914
+ serverSide,
3915
+ clientSide,
3916
+ previewUrl
3917
+ }) {
3918
+ const wantServer = serverSide ?? !clientSide;
3919
+ const wantClient = clientSide ?? false;
3920
+ const alreadyMsg = checkAlreadyActive(manager, wantServer, wantClient);
3921
+ if (alreadyMsg) return textResult(alreadyMsg);
3922
+ await manager.enterDebugMode(void 0, {
3923
+ serverSide: wantServer && !manager.isDebugMode(),
3924
+ clientSide: wantClient && !manager.isClientDebugMode(),
3925
+ previewUrl
3926
+ });
3927
+ return textResult(buildActivationMessage(manager, hypothesis));
3928
+ }
3929
+ function checkAlreadyActive(manager, wantServer, wantClient) {
3930
+ if (wantServer && manager.isDebugMode() && !wantClient) return "Already in server debug mode.";
3931
+ if (wantClient && manager.isClientDebugMode() && !wantServer)
3932
+ return "Already in client debug mode.";
3933
+ if (wantServer && manager.isDebugMode() && wantClient && manager.isClientDebugMode()) {
3934
+ return "Already in both server and client debug mode.";
3935
+ }
3936
+ return null;
3937
+ }
3938
+ function buildActivationMessage(manager, hypothesis) {
3939
+ const modes = [];
3940
+ if (manager.isDebugMode()) modes.push("server");
3941
+ if (manager.isClientDebugMode()) modes.push("client");
3942
+ const modeStr = modes.join(" + ");
3943
+ const sourceMapWarning = manager.getPlaywrightClient()?.hasSourceMaps() === false ? "\n\u26A0\uFE0F Source maps not detected in the client \u2014 client breakpoints will reference bundled code." : "";
3944
+ return hypothesis ? `Debug mode activated (${modeStr}). Hypothesis: ${hypothesis}
3945
+ Set breakpoints to test your hypothesis.${sourceMapWarning}` : `Debug mode activated (${modeStr}). Set breakpoints to begin debugging.${sourceMapWarning}`;
3946
+ }
3947
+ function buildDebugLifecycleTools(manager) {
3948
+ return [
3949
+ tool6(
3950
+ "debug_enter_mode",
3951
+ "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.",
3952
+ {
3953
+ hypothesis: z6.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3954
+ serverSide: z6.boolean().optional().describe(
3955
+ "Enable server-side Node.js debugging (default: true if clientSide is not set)"
3956
+ ),
3957
+ clientSide: z6.boolean().optional().describe("Enable client-side browser debugging via headless Chromium + Playwright"),
3958
+ previewUrl: z6.string().optional().describe(
3959
+ "Preview URL for client-side debugging (e.g., http://localhost:3000). Required when clientSide is true."
3960
+ )
3961
+ },
3962
+ async (params) => {
3963
+ try {
3964
+ return await handleEnterDebugMode(manager, params);
3965
+ } catch (error) {
3966
+ return textResult(`Failed to enter debug mode: ${formatError3(error)}`);
3967
+ }
3968
+ }
3969
+ ),
3970
+ tool6(
3971
+ "debug_exit_mode",
3972
+ "Exit debug mode: removes all breakpoints, disconnects the debugger, and restarts the dev server normally.",
3973
+ {},
3974
+ async () => {
3975
+ try {
3976
+ if (!manager.isDebugMode()) {
3977
+ return textResult("Not in debug mode.");
3978
+ }
3979
+ await manager.exitDebugMode();
3980
+ return textResult("Debug mode deactivated. Dev server restarted normally.");
3981
+ } catch (error) {
3982
+ return textResult(`Failed to exit debug mode: ${formatError3(error)}`);
3983
+ }
3984
+ }
3985
+ )
3986
+ ];
3987
+ }
3988
+ function buildBreakpointTools(manager) {
3989
+ return [
3990
+ tool6(
3991
+ "debug_set_breakpoint",
3992
+ "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.",
3993
+ {
3994
+ file: z6.string().describe("Absolute or relative file path to set the breakpoint in"),
3995
+ line: z6.number().describe("Line number (1-based) to set the breakpoint on"),
3996
+ condition: z6.string().optional().describe("JavaScript condition expression \u2014 breakpoint only triggers when truthy")
3997
+ },
3998
+ async ({ file, line, condition }) => {
3999
+ const clientOrErr = requireDebugClient2(manager);
4000
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4001
+ try {
4002
+ const client = clientOrErr;
4003
+ const breakpointId = await client.setBreakpoint(file, line, condition);
4004
+ const condStr = condition ? ` (condition: ${condition})` : "";
3045
4005
  return textResult(
3046
- `Failed to set tags: ${error instanceof Error ? error.message : "Unknown error"}`
4006
+ `Breakpoint set: ${file}:${line}${condStr}
4007
+ Breakpoint ID: ${breakpointId}`
3047
4008
  );
4009
+ } catch (error) {
4010
+ return textResult(`Failed to set breakpoint: ${formatError3(error)}`);
3048
4011
  }
3049
4012
  }
3050
4013
  ),
3051
- tool3(
3052
- "set_task_title",
3053
- "Update the task title to better reflect the planned work.",
4014
+ tool6(
4015
+ "debug_remove_breakpoint",
4016
+ "Remove a previously set breakpoint by its ID.",
4017
+ {
4018
+ breakpointId: z6.string().describe("The breakpoint ID returned by debug_set_breakpoint")
4019
+ },
4020
+ async ({ breakpointId }) => {
4021
+ const clientOrErr = requireDebugClient2(manager);
4022
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4023
+ try {
4024
+ const client = clientOrErr;
4025
+ await client.removeBreakpoint(breakpointId);
4026
+ return textResult(`Breakpoint ${breakpointId} removed.`);
4027
+ } catch (error) {
4028
+ return textResult(`Failed to remove breakpoint: ${formatError3(error)}`);
4029
+ }
4030
+ }
4031
+ ),
4032
+ tool6(
4033
+ "debug_list_breakpoints",
4034
+ "List all currently active breakpoints with their file, line, and condition.",
4035
+ {},
4036
+ // oxlint-disable-next-line require-await
4037
+ async () => {
4038
+ const clientOrErr = requireDebugClient2(manager);
4039
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4040
+ const breakpoints = clientOrErr.listBreakpoints();
4041
+ if (breakpoints.length === 0) {
4042
+ return textResult("No breakpoints set.");
4043
+ }
4044
+ return textResult(JSON.stringify(breakpoints, null, 2));
4045
+ },
4046
+ { annotations: { readOnlyHint: true } }
4047
+ )
4048
+ ];
4049
+ }
4050
+ function buildInspectionTools(manager) {
4051
+ return [
4052
+ tool6(
4053
+ "debug_inspect_paused",
4054
+ "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.",
4055
+ {},
4056
+ async () => {
4057
+ const clientOrErr = requireDebugClient2(manager);
4058
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4059
+ const client = clientOrErr;
4060
+ const queuedHits = manager.drainBreakpointHitQueue();
4061
+ if (!client.isPaused()) {
4062
+ if (queuedHits.length > 0) {
4063
+ return textResult(
4064
+ `Debugger was paused but has since resumed. Recent breakpoint hits:
4065
+ ${JSON.stringify(queuedHits, null, 2)}`
4066
+ );
4067
+ }
4068
+ return textResult(
4069
+ "Debugger is not currently paused. Set breakpoints and trigger the code path to pause execution."
4070
+ );
4071
+ }
4072
+ try {
4073
+ const callStack = client.getCallStack();
4074
+ const topFrame = callStack[0];
4075
+ let variables = [];
4076
+ if (topFrame) {
4077
+ try {
4078
+ variables = await client.getScopeVariables(topFrame.callFrameId);
4079
+ } catch {
4080
+ }
4081
+ }
4082
+ const result = {
4083
+ reason: client.getPausedState()?.reason,
4084
+ hitBreakpoints: client.getPausedState()?.hitBreakpoints,
4085
+ callStack,
4086
+ localVariables: variables
4087
+ };
4088
+ return textResult(JSON.stringify(result, null, 2));
4089
+ } catch (error) {
4090
+ return textResult(`Failed to inspect paused state: ${formatError3(error)}`);
4091
+ }
4092
+ },
4093
+ { annotations: { readOnlyHint: true } }
4094
+ ),
4095
+ tool6(
4096
+ "debug_evaluate",
4097
+ "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.",
3054
4098
  {
3055
- title: z3.string().describe("The new task title")
4099
+ expression: z6.string().describe("The JavaScript expression to evaluate"),
4100
+ frameIndex: z6.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
3056
4101
  },
3057
- async ({ title }) => {
4102
+ async ({ expression, frameIndex }) => {
4103
+ const clientOrErr = requireDebugClient2(manager);
4104
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
3058
4105
  try {
3059
- await Promise.resolve(connection.updateTaskProperties({ title }));
3060
- return textResult(`Task title updated to: ${title}`);
4106
+ const client = clientOrErr;
4107
+ let callFrameId;
4108
+ if (client.isPaused()) {
4109
+ const callStack = client.getCallStack();
4110
+ const frame = callStack[frameIndex ?? 0];
4111
+ callFrameId = frame?.callFrameId;
4112
+ }
4113
+ const result = await client.evaluate(expression, callFrameId);
4114
+ return textResult(`(${result.type}) ${result.value}`);
3061
4115
  } catch (error) {
4116
+ return textResult(`Evaluation failed: ${formatError3(error)}`);
4117
+ }
4118
+ }
4119
+ )
4120
+ ];
4121
+ }
4122
+ function buildProbeManagementTools(manager) {
4123
+ return [
4124
+ tool6(
4125
+ "debug_add_probe",
4126
+ "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.",
4127
+ {
4128
+ file: z6.string().describe("File path to probe"),
4129
+ line: z6.number().describe("Line number (1-based) to probe"),
4130
+ expressions: z6.array(z6.string()).describe(
4131
+ 'JavaScript expressions to capture when the line executes (e.g., ["req.params.id", "user.role"])'
4132
+ ),
4133
+ label: z6.string().optional().describe("Optional label for this probe (defaults to file:line)")
4134
+ },
4135
+ async ({ file, line, expressions, label }) => {
4136
+ const clientOrErr = requireDebugClient2(manager);
4137
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4138
+ try {
4139
+ const probeId = await clientOrErr.setLogpoint(file, line, expressions, label);
4140
+ const probeLabel = label ?? `${file}:${line}`;
4141
+ const exprList = expressions.join(", ");
3062
4142
  return textResult(
3063
- `Failed to update title: ${error instanceof Error ? error.message : "Unknown error"}`
4143
+ `Probe "${probeLabel}" set at ${file}:${line}
4144
+ Capturing: ${exprList}
4145
+ Probe ID: ${probeId}
4146
+
4147
+ Trigger the code path, then use debug_get_probe_results to see captured values.`
3064
4148
  );
4149
+ } catch (error) {
4150
+ return textResult(`Failed to add probe: ${formatError3(error)}`);
3065
4151
  }
3066
4152
  }
3067
4153
  ),
3068
- ...buildIconTools(connection)
4154
+ tool6(
4155
+ "debug_remove_probe",
4156
+ "Remove a previously set debug probe by its ID.",
4157
+ {
4158
+ probeId: z6.string().describe("The probe ID returned by debug_add_probe")
4159
+ },
4160
+ async ({ probeId }) => {
4161
+ const clientOrErr = requireDebugClient2(manager);
4162
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4163
+ try {
4164
+ await clientOrErr.removeProbe(probeId);
4165
+ return textResult(`Probe ${probeId} removed.`);
4166
+ } catch (error) {
4167
+ return textResult(`Failed to remove probe: ${formatError3(error)}`);
4168
+ }
4169
+ }
4170
+ ),
4171
+ tool6(
4172
+ "debug_list_probes",
4173
+ "List all active debug probes with their file, line, expressions, and labels.",
4174
+ {},
4175
+ // oxlint-disable-next-line require-await
4176
+ async () => {
4177
+ const clientOrErr = requireDebugClient2(manager);
4178
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4179
+ const probes = clientOrErr.listProbes();
4180
+ if (probes.length === 0) {
4181
+ return textResult("No probes set.");
4182
+ }
4183
+ const lines = probes.map(
4184
+ (p) => `${p.probeId}: "${p.label}" at ${p.file}:${p.line} \u2014 [${p.expressions.join(", ")}]`
4185
+ );
4186
+ return textResult(lines.join("\n"));
4187
+ },
4188
+ { annotations: { readOnlyHint: true } }
4189
+ )
4190
+ ];
4191
+ }
4192
+ function buildProbeResultTools(manager) {
4193
+ return [
4194
+ tool6(
4195
+ "debug_get_probe_results",
4196
+ "Fetch captured probe hit data. Returns expression values from each time a probed line executed.",
4197
+ {
4198
+ probeId: z6.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4199
+ label: z6.string().optional().describe("Filter results by probe label"),
4200
+ limit: z6.number().optional().describe("Maximum number of recent hits to return (default: all)")
4201
+ },
4202
+ async ({ probeId, label, limit }) => {
4203
+ const clientOrErr = requireDebugClient2(manager);
4204
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4205
+ try {
4206
+ let filterLabel = label;
4207
+ if (probeId && !filterLabel) {
4208
+ const probe = clientOrErr.listProbes().find((p) => p.probeId === probeId);
4209
+ if (probe) filterLabel = probe.label;
4210
+ }
4211
+ const hits = await clientOrErr.getProbeResults(filterLabel, limit);
4212
+ if (hits.length === 0) {
4213
+ const filterDesc = filterLabel ? ` for "${filterLabel}"` : "";
4214
+ return textResult(
4215
+ `No probe hits${filterDesc}. Make sure the probed code path has been triggered.`
4216
+ );
4217
+ }
4218
+ const formatted = formatProbeHits(hits);
4219
+ return textResult(formatted);
4220
+ } catch (error) {
4221
+ return textResult(`Failed to get probe results: ${formatError3(error)}`);
4222
+ }
4223
+ },
4224
+ { annotations: { readOnlyHint: true } }
4225
+ )
4226
+ ];
4227
+ }
4228
+ function formatProbeHits(hits) {
4229
+ const grouped = /* @__PURE__ */ new Map();
4230
+ for (const hit of hits) {
4231
+ const group = grouped.get(hit.label) ?? [];
4232
+ group.push(hit);
4233
+ grouped.set(hit.label, group);
4234
+ }
4235
+ const sections = [];
4236
+ for (const [label, labelHits] of grouped) {
4237
+ const header = `Probe "${label}" \u2014 hit ${labelHits.length} time${labelHits.length === 1 ? "" : "s"}:`;
4238
+ const lines = labelHits.map((hit) => {
4239
+ const time = new Date(hit.timestamp).toLocaleTimeString("en-US", { hour12: false });
4240
+ const entries = Object.entries(hit.data).map(([key, val]) => `${key}=${formatProbeValue(val)}`).join(", ");
4241
+ return ` [${time}] ${entries}`;
4242
+ });
4243
+ sections.push([header, ...lines].join("\n"));
4244
+ }
4245
+ return sections.join("\n\n");
4246
+ }
4247
+ function formatProbeValue(value) {
4248
+ if (value === null) return "null";
4249
+ if (value === void 0) return "undefined";
4250
+ if (typeof value === "string") {
4251
+ return value.length > 100 ? `"${value.slice(0, 97)}..."` : `"${value}"`;
4252
+ }
4253
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
4254
+ if (Array.isArray(value)) {
4255
+ return `Array(${value.length})`;
4256
+ }
4257
+ if (typeof value === "object") {
4258
+ const keys = Object.keys(value);
4259
+ if (keys.length <= 3) {
4260
+ const preview = keys.map((k) => `${k}: ${formatProbeValue(value[k])}`).join(", ");
4261
+ return `{${preview}}`;
4262
+ }
4263
+ return `Object(${keys.length} keys)`;
4264
+ }
4265
+ return String(value);
4266
+ }
4267
+ function buildExecutionControlTools(manager) {
4268
+ return [
4269
+ tool6(
4270
+ "debug_continue",
4271
+ "Resume execution after the debugger has paused at a breakpoint.",
4272
+ {},
4273
+ async () => {
4274
+ const clientOrErr = requireDebugClient2(manager);
4275
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4276
+ if (!clientOrErr.isPaused()) {
4277
+ return textResult("Debugger is not paused.");
4278
+ }
4279
+ try {
4280
+ await clientOrErr.resume();
4281
+ return textResult("Execution resumed.");
4282
+ } catch (error) {
4283
+ return textResult(`Failed to resume: ${formatError3(error)}`);
4284
+ }
4285
+ }
4286
+ ),
4287
+ tool6(
4288
+ "debug_step_over",
4289
+ "Step over the current line while paused at a breakpoint. Executes the current line and pauses at the next line in the same function.",
4290
+ {},
4291
+ async () => {
4292
+ const clientOrErr = requireDebugClient2(manager);
4293
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4294
+ if (!clientOrErr.isPaused()) {
4295
+ return textResult("Debugger is not paused.");
4296
+ }
4297
+ try {
4298
+ await clientOrErr.stepOver();
4299
+ return textResult("Stepped over. Use debug_inspect_paused to see current state.");
4300
+ } catch (error) {
4301
+ return textResult(`Failed to step over: ${formatError3(error)}`);
4302
+ }
4303
+ }
4304
+ ),
4305
+ tool6(
4306
+ "debug_step_into",
4307
+ "Step into the function call on the current line while paused at a breakpoint. Pauses at the first line inside the called function.",
4308
+ {},
4309
+ async () => {
4310
+ const clientOrErr = requireDebugClient2(manager);
4311
+ if (typeof clientOrErr === "string") return textResult(clientOrErr);
4312
+ if (!clientOrErr.isPaused()) {
4313
+ return textResult("Debugger is not paused.");
4314
+ }
4315
+ try {
4316
+ await clientOrErr.stepInto();
4317
+ return textResult("Stepped into. Use debug_inspect_paused to see current state.");
4318
+ } catch (error) {
4319
+ return textResult(`Failed to step into: ${formatError3(error)}`);
4320
+ }
4321
+ }
4322
+ )
4323
+ ];
4324
+ }
4325
+ function buildDebugTools(manager) {
4326
+ return [
4327
+ ...buildDebugLifecycleTools(manager),
4328
+ ...buildBreakpointTools(manager),
4329
+ ...buildProbeManagementTools(manager),
4330
+ ...buildProbeResultTools(manager),
4331
+ ...buildInspectionTools(manager),
4332
+ ...buildExecutionControlTools(manager),
4333
+ ...buildTelemetryTools(manager),
4334
+ ...buildClientDebugTools(manager)
3069
4335
  ];
3070
4336
  }
3071
4337
 
@@ -3098,14 +4364,15 @@ function getModeTools(agentMode, connection, config, context) {
3098
4364
  return config.mode === "pm" ? buildPmTools(connection, context?.storyPoints, { includePackTools: false }) : [];
3099
4365
  }
3100
4366
  }
3101
- function createConveyorMcpServer(connection, config, context, agentMode) {
4367
+ function createConveyorMcpServer(connection, config, context, agentMode, debugManager) {
3102
4368
  const commonTools = buildCommonTools(connection, config);
3103
4369
  const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
3104
4370
  const modeTools = getModeTools(effectiveMode, connection, config, context);
3105
4371
  const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
4372
+ const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
3106
4373
  return createSdkMcpServer({
3107
4374
  name: "conveyor",
3108
- tools: [...commonTools, ...modeTools, ...discoveryTools]
4375
+ tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools]
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",
@@ -3361,7 +4628,7 @@ function buildQueryOptions(host, context) {
3361
4628
  },
3362
4629
  settingSources,
3363
4630
  cwd: host.config.workspaceDir,
3364
- permissionMode: needsCanUseTool ? "acceptEdits" : "bypassPermissions",
4631
+ permissionMode: needsCanUseTool ? "plan" : "bypassPermissions",
3365
4632
  allowDangerouslySkipPermissions: !needsCanUseTool,
3366
4633
  canUseTool: buildCanUseTool(host),
3367
4634
  tools: { type: "preset", preset: "claude_code" },
@@ -4645,17 +5912,17 @@ import {
4645
5912
  } from "@anthropic-ai/claude-agent-sdk";
4646
5913
 
4647
5914
  // src/tools/project-tools.ts
4648
- import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
4649
- import { z as z4 } from "zod";
5915
+ import { tool as tool7 } from "@anthropic-ai/claude-agent-sdk";
5916
+ import { z as z7 } from "zod";
4650
5917
  function buildReadTools(connection) {
4651
5918
  return [
4652
- tool4(
5919
+ tool7(
4653
5920
  "list_tasks",
4654
5921
  "List tasks in the project. Optionally filter by status or assignee.",
4655
5922
  {
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)")
5923
+ status: z7.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
5924
+ assigneeId: z7.string().optional().describe("Filter by assigned user ID"),
5925
+ limit: z7.number().optional().describe("Max number of tasks to return (default 50)")
4659
5926
  },
4660
5927
  async (params) => {
4661
5928
  try {
@@ -4669,10 +5936,10 @@ function buildReadTools(connection) {
4669
5936
  },
4670
5937
  { annotations: { readOnlyHint: true } }
4671
5938
  ),
4672
- tool4(
5939
+ tool7(
4673
5940
  "get_task",
4674
5941
  "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") },
5942
+ { task_id: z7.string().describe("The task ID to look up") },
4676
5943
  async ({ task_id }) => {
4677
5944
  try {
4678
5945
  const task = await connection.requestGetTask(task_id);
@@ -4685,14 +5952,14 @@ function buildReadTools(connection) {
4685
5952
  },
4686
5953
  { annotations: { readOnlyHint: true } }
4687
5954
  ),
4688
- tool4(
5955
+ tool7(
4689
5956
  "search_tasks",
4690
5957
  "Search tasks by tags, text query, or status filters.",
4691
5958
  {
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)")
5959
+ tagNames: z7.array(z7.string()).optional().describe("Filter by tag names"),
5960
+ searchQuery: z7.string().optional().describe("Text search in title/description"),
5961
+ statusFilters: z7.array(z7.string()).optional().describe("Filter by statuses"),
5962
+ limit: z7.number().optional().describe("Max results (default 20)")
4696
5963
  },
4697
5964
  async (params) => {
4698
5965
  try {
@@ -4706,7 +5973,7 @@ function buildReadTools(connection) {
4706
5973
  },
4707
5974
  { annotations: { readOnlyHint: true } }
4708
5975
  ),
4709
- tool4(
5976
+ tool7(
4710
5977
  "list_tags",
4711
5978
  "List all tags available in the project.",
4712
5979
  {},
@@ -4722,7 +5989,7 @@ function buildReadTools(connection) {
4722
5989
  },
4723
5990
  { annotations: { readOnlyHint: true } }
4724
5991
  ),
4725
- tool4(
5992
+ tool7(
4726
5993
  "get_project_summary",
4727
5994
  "Get a summary of the project including task counts by status and active builds.",
4728
5995
  {},
@@ -4742,15 +6009,15 @@ function buildReadTools(connection) {
4742
6009
  }
4743
6010
  function buildMutationTools(connection) {
4744
6011
  return [
4745
- tool4(
6012
+ tool7(
4746
6013
  "create_task",
4747
6014
  "Create a new task in the project.",
4748
6015
  {
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")
6016
+ title: z7.string().describe("Task title"),
6017
+ description: z7.string().optional().describe("Task description"),
6018
+ plan: z7.string().optional().describe("Implementation plan in markdown"),
6019
+ status: z7.string().optional().describe("Initial status (default: Planning)"),
6020
+ isBug: z7.boolean().optional().describe("Whether this is a bug report")
4754
6021
  },
4755
6022
  async (params) => {
4756
6023
  try {
@@ -4763,16 +6030,16 @@ function buildMutationTools(connection) {
4763
6030
  }
4764
6031
  }
4765
6032
  ),
4766
- tool4(
6033
+ tool7(
4767
6034
  "update_task",
4768
6035
  "Update an existing task's title, description, plan, status, or assignee.",
4769
6036
  {
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")
6037
+ task_id: z7.string().describe("The task ID to update"),
6038
+ title: z7.string().optional().describe("New title"),
6039
+ description: z7.string().optional().describe("New description"),
6040
+ plan: z7.string().optional().describe("New plan in markdown"),
6041
+ status: z7.string().optional().describe("New status"),
6042
+ assignedUserId: z7.string().nullable().optional().describe("Assign to user ID, or null to unassign")
4776
6043
  },
4777
6044
  async ({ task_id, ...fields }) => {
4778
6045
  try {
@@ -5004,8 +6271,8 @@ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
5004
6271
 
5005
6272
  // src/tools/audit-tools.ts
5006
6273
  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";
6274
+ import { tool as tool8, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
6275
+ import { z as z8 } from "zod";
5009
6276
  function mapCreateTag(input) {
5010
6277
  return {
5011
6278
  type: "create_tag",
@@ -5087,14 +6354,14 @@ function collectRecommendation(toolName, input, collector, onRecommendation) {
5087
6354
  }
5088
6355
  function createAuditMcpServer(collector, onRecommendation) {
5089
6356
  const auditTools = [
5090
- tool5(
6357
+ tool8(
5091
6358
  "recommend_create_tag",
5092
6359
  "Recommend creating a new tag for an uncovered subsystem or area",
5093
6360
  {
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")
6361
+ name: z8.string().describe("Proposed tag name (lowercase, hyphenated)"),
6362
+ color: z8.string().optional().describe("Hex color code"),
6363
+ description: z8.string().describe("What this tag covers"),
6364
+ reasoning: z8.string().describe("Why this tag should be created")
5098
6365
  },
5099
6366
  async (args) => {
5100
6367
  const result = collectRecommendation(
@@ -5106,14 +6373,14 @@ function createAuditMcpServer(collector, onRecommendation) {
5106
6373
  return { content: [{ type: "text", text: result }] };
5107
6374
  }
5108
6375
  ),
5109
- tool5(
6376
+ tool8(
5110
6377
  "recommend_update_description",
5111
6378
  "Recommend updating a tag's description to better reflect its scope",
5112
6379
  {
5113
- tagId: z5.string(),
5114
- tagName: z5.string(),
5115
- description: z5.string().describe("Proposed new description"),
5116
- reasoning: z5.string()
6380
+ tagId: z8.string(),
6381
+ tagName: z8.string(),
6382
+ description: z8.string().describe("Proposed new description"),
6383
+ reasoning: z8.string()
5117
6384
  },
5118
6385
  async (args) => {
5119
6386
  const result = collectRecommendation(
@@ -5125,16 +6392,16 @@ function createAuditMcpServer(collector, onRecommendation) {
5125
6392
  return { content: [{ type: "text", text: result }] };
5126
6393
  }
5127
6394
  ),
5128
- tool5(
6395
+ tool8(
5129
6396
  "recommend_context_link",
5130
6397
  "Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
5131
6398
  {
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()
6399
+ tagId: z8.string(),
6400
+ tagName: z8.string(),
6401
+ linkType: z8.enum(["rule", "doc", "file", "folder"]),
6402
+ path: z8.string(),
6403
+ label: z8.string().optional(),
6404
+ reasoning: z8.string()
5138
6405
  },
5139
6406
  async (args) => {
5140
6407
  const result = collectRecommendation(
@@ -5146,16 +6413,16 @@ function createAuditMcpServer(collector, onRecommendation) {
5146
6413
  return { content: [{ type: "text", text: result }] };
5147
6414
  }
5148
6415
  ),
5149
- tool5(
6416
+ tool8(
5150
6417
  "flag_documentation_gap",
5151
6418
  "Flag a file that agents read heavily but has no tag documentation linked",
5152
6419
  {
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()
6420
+ tagName: z8.string().describe("Tag whose agents read this file"),
6421
+ tagId: z8.string().optional(),
6422
+ filePath: z8.string(),
6423
+ readCount: z8.number(),
6424
+ suggestedAction: z8.string().describe("What doc or rule should be created"),
6425
+ reasoning: z8.string()
5159
6426
  },
5160
6427
  async (args) => {
5161
6428
  const result = collectRecommendation(
@@ -5167,15 +6434,15 @@ function createAuditMcpServer(collector, onRecommendation) {
5167
6434
  return { content: [{ type: "text", text: result }] };
5168
6435
  }
5169
6436
  ),
5170
- tool5(
6437
+ tool8(
5171
6438
  "recommend_merge_tags",
5172
6439
  "Recommend merging one tag into another",
5173
6440
  {
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()
6441
+ tagId: z8.string().describe("Tag ID to be merged (removed after merge)"),
6442
+ tagName: z8.string().describe("Name of the tag to be merged"),
6443
+ mergeIntoTagId: z8.string().describe("Tag ID to merge into (kept)"),
6444
+ mergeIntoTagName: z8.string(),
6445
+ reasoning: z8.string()
5179
6446
  },
5180
6447
  async (args) => {
5181
6448
  const result = collectRecommendation(
@@ -5187,14 +6454,14 @@ function createAuditMcpServer(collector, onRecommendation) {
5187
6454
  return { content: [{ type: "text", text: result }] };
5188
6455
  }
5189
6456
  ),
5190
- tool5(
6457
+ tool8(
5191
6458
  "recommend_rename_tag",
5192
6459
  "Recommend renaming a tag",
5193
6460
  {
5194
- tagId: z5.string(),
5195
- tagName: z5.string().describe("Current tag name"),
5196
- newName: z5.string().describe("Proposed new name"),
5197
- reasoning: z5.string()
6461
+ tagId: z8.string(),
6462
+ tagName: z8.string().describe("Current tag name"),
6463
+ newName: z8.string().describe("Proposed new name"),
6464
+ reasoning: z8.string()
5198
6465
  },
5199
6466
  async (args) => {
5200
6467
  const result = collectRecommendation(
@@ -5206,10 +6473,10 @@ function createAuditMcpServer(collector, onRecommendation) {
5206
6473
  return { content: [{ type: "text", text: result }] };
5207
6474
  }
5208
6475
  ),
5209
- tool5(
6476
+ tool8(
5210
6477
  "complete_audit",
5211
6478
  "Signal that the audit is complete with a summary of all findings",
5212
- { summary: z5.string().describe("Brief overview of all findings") },
6479
+ { summary: z8.string().describe("Brief overview of all findings") },
5213
6480
  async (args) => {
5214
6481
  collector.complete = true;
5215
6482
  collector.summary = args.summary ?? "Audit completed.";
@@ -6055,6 +7322,7 @@ export {
6055
7322
  ProjectConnection,
6056
7323
  createServiceLogger,
6057
7324
  errorMeta,
7325
+ injectTelemetry,
6058
7326
  ensureWorktree,
6059
7327
  removeWorktree,
6060
7328
  loadConveyorConfig,
@@ -6062,4 +7330,4 @@ export {
6062
7330
  ProjectRunner,
6063
7331
  FileCache
6064
7332
  };
6065
- //# sourceMappingURL=chunk-SL5MRNSI.js.map
7333
+ //# sourceMappingURL=chunk-NKZSUGND.js.map