@rallycry/conveyor-agent 7.3.1 → 7.3.3

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.
@@ -411,12 +411,22 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
411
411
  triggerIdentification() {
412
412
  return this.call("triggerIdentification", { sessionId: this.config.sessionId });
413
413
  }
414
+ getCumulativeSpending() {
415
+ return this.call("getCumulativeSpending", { sessionId: this.config.sessionId });
416
+ }
414
417
  async refreshAuthToken() {
415
418
  const codespaceName = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
416
419
  const apiUrl = this.config.apiUrl;
417
420
  if (!codespaceName || !apiUrl) return false;
418
421
  try {
419
- const response = await fetch(`${apiUrl}/api/codespace/bootstrap/${codespaceName}`);
422
+ const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
423
+ const headers = {};
424
+ if (bootstrapToken) {
425
+ headers["x-codespace-token"] = bootstrapToken;
426
+ }
427
+ const response = await fetch(`${apiUrl}/api/codespace/bootstrap/${codespaceName}`, {
428
+ headers
429
+ });
420
430
  if (!response.ok) return false;
421
431
  const config = await response.json();
422
432
  if (config.envVars?.CLAUDE_CODE_OAUTH_TOKEN) {
@@ -2253,7 +2263,7 @@ import { z } from "zod";
2253
2263
  function buildReadTaskChatTool(connection) {
2254
2264
  return defineTool(
2255
2265
  "read_task_chat",
2256
- "Read recent messages from a task chat. Omit task_id to read the current task's chat, or provide a child task ID to read a child's chat.",
2266
+ "Read recent human/user chat messages for a task. When to use: you need to see user instructions, questions, or feedback posted to the task chat. Omit task_id for the current task, or pass a child task ID to read a child's chat. When NOT to use: for agent reasoning, tool calls, or setup/dev-server output \u2014 use get_task_cli instead. Returns: JSON array of recent chat messages (default 20).",
2257
2267
  {
2258
2268
  limit: z.number().optional().describe("Number of recent messages to fetch (default 20)"),
2259
2269
  task_id: z.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
@@ -2280,7 +2290,7 @@ function buildReadTaskChatTool(connection) {
2280
2290
  function buildGetTaskPlanTool(connection) {
2281
2291
  return defineTool(
2282
2292
  "get_task_plan",
2283
- "Re-read the latest task plan in case it was updated",
2293
+ "Re-read the current task's plan. When to use: the user just said they updated the plan mid-session or explicitly asked you to re-read it. When NOT to use: to fetch general task info (title, status, description) \u2014 use get_task. The plan was already included in your initial context, so do not call this speculatively. Returns: the plan markdown string.",
2284
2294
  {},
2285
2295
  async () => {
2286
2296
  try {
@@ -2298,7 +2308,7 @@ function buildGetTaskPlanTool(connection) {
2298
2308
  function buildGetTaskTool(connection) {
2299
2309
  return defineTool(
2300
2310
  "get_task",
2301
- "Look up a task by slug or ID to get its title, description, plan, and status",
2311
+ "Look up any task by slug or ID. When to use: you need the current title, description, plan, status, branch, PR info, or story points for this or a related task. When NOT to use: to list child tasks (use list_subtasks), or to re-read only the current plan (use get_task_plan). Returns: JSON task object including id, slug, title, description, plan, status, branch, githubPRNumber, githubPRUrl, storyPointValue.",
2302
2312
  {
2303
2313
  slug_or_id: z.string().describe("The task slug (e.g. 'my-task') or CUID")
2304
2314
  },
@@ -2321,7 +2331,7 @@ function buildGetTaskTool(connection) {
2321
2331
  function buildGetTaskCliTool(connection) {
2322
2332
  return defineTool(
2323
2333
  "get_task_cli",
2324
- "Read CLI execution logs from a task. Returns agent reasoning, tool calls, setup output, and other execution events. Use 'source' to filter: 'agent' for agent reasoning/tool calls only, 'application' for setup/dev-server output only.",
2334
+ "Read CLI execution logs from a task \u2014 agent reasoning, tool calls, and setup/dev-server output. When to use: inspecting what another agent did, debugging build/setup output, or reviewing prior tool calls. Filter with source='agent' for reasoning and tool calls only, source='application' for setup/dev-server output only. When NOT to use: for human/user chat \u2014 use read_task_chat. Returns: newline-joined event log lines each prefixed with `[timestamp] [type]` and formatted content.",
2325
2335
  {
2326
2336
  task_id: z.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
2327
2337
  source: z.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
@@ -2354,7 +2364,7 @@ function buildGetTaskCliTool(connection) {
2354
2364
  function buildListTaskFilesTool(connection) {
2355
2365
  return defineTool(
2356
2366
  "list_task_files",
2357
- "List all files attached to this task with metadata (name, type, size) and download URLs",
2367
+ "List all files attached to this task with metadata. When to use: before fetching a specific file, to see what is available and how large each is. When NOT to use: to read a file's contents \u2014 use get_task_file. Returns: JSON array of file metadata (id, name, mimeType, size, downloadUrl), plus inline image blocks for any attached images.",
2358
2368
  {},
2359
2369
  async () => {
2360
2370
  try {
@@ -2384,7 +2394,7 @@ function buildListTaskFilesTool(connection) {
2384
2394
  function buildGetTaskFileTool(connection) {
2385
2395
  return defineTool(
2386
2396
  "get_task_file",
2387
- "Get a specific task file's content and download URL by file ID",
2397
+ "Fetch one task file's content plus metadata by file ID. When to use: you have a file ID from list_task_files and need its content or download URL. Call list_task_files first to discover IDs and check sizes \u2014 large binaries may be truncated by the service's size limit. When NOT to use: to enumerate attachments \u2014 use list_task_files. Returns: JSON metadata; for images an inline image block is also returned, and text content is embedded inline when available.",
2388
2398
  { fileId: z.string().describe("The file ID to retrieve") },
2389
2399
  async ({ fileId }) => {
2390
2400
  try {
@@ -2427,7 +2437,7 @@ import { z as z2 } from "zod";
2427
2437
  function buildGetDependenciesTool(connection) {
2428
2438
  return defineTool(
2429
2439
  "get_dependencies",
2430
- "Get this task's dependencies and their current status (met = merged to dev)",
2440
+ "Get this task's dependencies and their current met/unmet status. When to use: confirming that blockers have been merged before starting work, or investigating why a task cannot start. When NOT to use: to look up a specific task's state \u2014 use get_task. Returns: JSON array of dependency objects (slug, title, status, and whether the dependency is met \u2014 met means merged to dev).",
2431
2441
  {},
2432
2442
  async () => {
2433
2443
  try {
@@ -2447,15 +2457,19 @@ function buildGetDependenciesTool(connection) {
2447
2457
  function buildGetSuggestionsTool(connection) {
2448
2458
  return defineTool(
2449
2459
  "get_suggestions",
2450
- "List project suggestions sorted by vote score. Use this to see what the team thinks is important.",
2460
+ "List project suggestions sorted by vote score. When to use: seeing what the team thinks is important, finding a suggestion to vote on, or checking for duplicates before create_suggestion. Filter by status (Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled) or cap results with limit (default 20). When NOT to use: for task lists \u2014 suggestions are project-level idea records, not tasks. Returns: JSON array of suggestions with id, title, description, status, score.",
2451
2461
  {
2452
- status: z2.string().optional().describe("Filter by status: Open, Accepted, Rejected, Implemented"),
2453
- limit: z2.number().optional().describe("Max results (default 20)")
2462
+ status: z2.string().optional().describe(
2463
+ "Filter by status: Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled"
2464
+ ),
2465
+ limit: z2.number().int().min(1).max(100).optional().describe("Max results (default 20)")
2454
2466
  },
2455
- async ({ status: _status, limit: _limit }) => {
2467
+ async ({ status, limit }) => {
2456
2468
  try {
2457
2469
  const suggestions = await connection.call("getSuggestions", {
2458
- sessionId: connection.sessionId
2470
+ sessionId: connection.sessionId,
2471
+ status,
2472
+ limit
2459
2473
  });
2460
2474
  if (suggestions.length === 0) {
2461
2475
  return textResult("No suggestions found.");
@@ -2476,7 +2490,7 @@ import { z as z3 } from "zod";
2476
2490
  function buildPostToChatTool(connection) {
2477
2491
  return defineTool(
2478
2492
  "post_to_chat",
2479
- "Post a message to a task chat. Your normal replies already appear in chat \u2014 only use this for explicit out-of-band updates or posting to a child task's chat.",
2493
+ "Post an out-of-band message to a task chat. When to use: the user explicitly asked for a status update that must appear in chat, or you need to message a child task's chat (pass its ID as task_id). When NOT to use: for normal responses \u2014 your regular replies already appear in the current task's chat automatically. Returns: confirmation string.",
2480
2494
  {
2481
2495
  message: z3.string().describe("The message to post to the team"),
2482
2496
  task_id: z3.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
@@ -2504,7 +2518,7 @@ function buildPostToChatTool(connection) {
2504
2518
  function buildForceUpdateTaskStatusTool(connection) {
2505
2519
  return defineTool(
2506
2520
  "force_update_task_status",
2507
- "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.",
2521
+ "EMERGENCY ONLY: force-override a task's Kanban status. When to use: an automatic transition failed and the task is wedged \u2014 e.g. a PR was merged but status did not advance to ReviewDev, a build completed but status is still InProgress with no session running, or a child is stuck in ReviewPR after its PR was closed. When NOT to use: in normal flow \u2014 building, PR creation, and merge each transition status automatically. Omit task_id for the current task; pass a child task ID to update a child. Returns: confirmation string.",
2508
2522
  {
2509
2523
  status: z3.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2510
2524
  task_id: z3.string().optional().describe("Child task ID to update. Omit to update the current task.")
@@ -2536,7 +2550,7 @@ function buildForceUpdateTaskStatusTool(connection) {
2536
2550
  function buildCreatePullRequestTool(connection, config) {
2537
2551
  return defineTool(
2538
2552
  "create_pull_request",
2539
- "Create a GitHub pull request for this task. Automatically stages uncommitted changes, commits them, and pushes before creating the PR. Use this instead of gh CLI or git commands to create PRs.",
2553
+ "Create a GitHub pull request for this task. Runs in order: 1) stage any uncommitted changes, 2) commit them (using commitMessage, or a default derived from title), 3) push to origin, 4) create the PR via GitHub API. When to use: opening the PR for this task \u2014 always use this instead of the gh CLI or raw git. Common failures: missing remote branch, a PR already open for this branch, or an expired git token (retry after the refresh). Returns: confirmation string with the new PR number and URL.",
2540
2554
  {
2541
2555
  title: z3.string().describe("The PR title"),
2542
2556
  body: z3.string().describe("The PR description/body in markdown"),
@@ -2622,7 +2636,7 @@ Troubleshooting:
2622
2636
  function buildAddDependencyTool(connection) {
2623
2637
  return defineTool(
2624
2638
  "add_dependency",
2625
- "Add a dependency \u2014 this task cannot start until the specified task is merged to dev",
2639
+ "Add a blocking dependency \u2014 this task cannot start until the named task is merged to dev. When to use: you discovered work that must land before this task can proceed. When NOT to use: to track work that should happen after this task \u2014 use create_follow_up_task instead. Returns: confirmation string.",
2626
2640
  {
2627
2641
  depends_on_slug_or_id: z3.string().describe("Slug or ID of the task this task depends on")
2628
2642
  },
@@ -2644,7 +2658,7 @@ function buildAddDependencyTool(connection) {
2644
2658
  function buildRemoveDependencyTool(connection) {
2645
2659
  return defineTool(
2646
2660
  "remove_dependency",
2647
- "Remove a dependency from this task",
2661
+ "Remove a previously added dependency from this task. When to use: the dependency was added in error or is no longer relevant. Returns: confirmation string.",
2648
2662
  {
2649
2663
  depends_on_slug_or_id: z3.string().describe("Slug or ID of the task to remove as dependency")
2650
2664
  },
@@ -2666,7 +2680,7 @@ function buildRemoveDependencyTool(connection) {
2666
2680
  function buildCreateFollowUpTaskTool(connection) {
2667
2681
  return defineTool(
2668
2682
  "create_follow_up_task",
2669
- "Create a follow-up task in this project that depends on the current task. Use for out-of-scope work, v1.1 features, or cleanup that should happen after this task merges.",
2683
+ "Create a follow-up task that depends on the current task. When to use: out-of-scope work, v1.1 features, or cleanup that should happen after this task merges. The new task will be unblocked once the current task is merged. When NOT to use: to add a blocker to this task \u2014 use add_dependency instead. Returns: confirmation string with the new task's slug.",
2670
2684
  {
2671
2685
  title: z3.string().describe("Follow-up task title"),
2672
2686
  description: z3.string().optional().describe("Brief description of the follow-up work"),
@@ -2696,7 +2710,7 @@ function buildCreateFollowUpTaskTool(connection) {
2696
2710
  function buildCreateSuggestionTool(connection) {
2697
2711
  return defineTool(
2698
2712
  "create_suggestion",
2699
- "Suggest a feature, improvement, or idea for the project. If you want to recommend something \u2014 a document, a rule, a feature, a task, an optimization \u2014 use this tool. If a similar suggestion already exists, your vote will be added to it instead of creating a duplicate.",
2713
+ "Suggest a feature, improvement, rule, or idea for the project. When to use: recommending anything project-scoped \u2014 a document, a rule, a feature, a task, an optimization. If a similar suggestion already exists the service will dedupe and record your upvote instead. When NOT to use: for actionable work scoped to the current task \u2014 open a follow-up task. Returns: confirmation string with the suggestion id (and mergedIntoId if deduped).",
2700
2714
  {
2701
2715
  title: z3.string().describe("Short title for the suggestion"),
2702
2716
  description: z3.string().optional().describe(
@@ -2729,7 +2743,7 @@ function buildCreateSuggestionTool(connection) {
2729
2743
  function buildVoteSuggestionTool(connection) {
2730
2744
  return defineTool(
2731
2745
  "vote_suggestion",
2732
- "Vote on a project suggestion. Use +1 to upvote or -1 to downvote.",
2746
+ "Vote +1 or -1 on a project suggestion. When to use: expressing support or disagreement with a specific suggestion returned from get_suggestions. Returns: confirmation string with the suggestion's updated score.",
2733
2747
  {
2734
2748
  suggestion_id: z3.string().describe("The suggestion ID to vote on"),
2735
2749
  value: z3.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
@@ -2778,7 +2792,7 @@ var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
2778
2792
  function buildUpdateTaskTool(connection) {
2779
2793
  return defineTool(
2780
2794
  "update_task",
2781
- "Save the finalized task plan and/or description",
2795
+ "Save the finalized plan and/or description to the current task. When to use: in Plan mode, after reaching alignment with the user on the approach. When NOT to use: for child/subtasks \u2014 use update_subtask. This tool does not change status or other properties. Returns: confirmation string.",
2782
2796
  {
2783
2797
  plan: z4.string().optional().describe("The task plan in markdown"),
2784
2798
  description: z4.string().optional().describe("Updated task description")
@@ -2800,7 +2814,7 @@ function buildUpdateTaskTool(connection) {
2800
2814
  function buildCreateSubtaskTool(connection) {
2801
2815
  return defineTool(
2802
2816
  "create_subtask",
2803
- "Create a subtask under the current parent task. Use for breaking complex tasks into smaller pieces.",
2817
+ "Create a subtask under the current parent task. When to use: breaking a complex parent task into smaller pieces during planning. When NOT to use: for follow-ups that depend on this task but are not children \u2014 use create_follow_up_task. Returns: confirmation string with the new subtask id.",
2804
2818
  {
2805
2819
  title: z4.string().describe("Subtask title"),
2806
2820
  description: z4.string().optional().describe("Brief description"),
@@ -2830,7 +2844,7 @@ function buildCreateSubtaskTool(connection) {
2830
2844
  function buildUpdateSubtaskTool(connection) {
2831
2845
  return defineTool(
2832
2846
  "update_subtask",
2833
- "Update an existing subtask's fields",
2847
+ "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue). When to use: refining a child's plan or reordering the subtask queue. When NOT to use: to change status (pack tools do that automatically) or to update the current task itself (use update_task). Returns: confirmation string.",
2834
2848
  {
2835
2849
  subtaskId: z4.string().describe("The subtask ID to update"),
2836
2850
  title: z4.string().optional(),
@@ -2859,7 +2873,7 @@ function buildUpdateSubtaskTool(connection) {
2859
2873
  function buildDeleteSubtaskTool(connection) {
2860
2874
  return defineTool(
2861
2875
  "delete_subtask",
2862
- "Delete a subtask",
2876
+ "Delete a subtask by id. When to use: a subtask was created in error or is no longer needed. Returns: confirmation string.",
2863
2877
  { subtaskId: z4.string().describe("The subtask ID to delete") },
2864
2878
  async ({ subtaskId }) => {
2865
2879
  try {
@@ -2877,7 +2891,7 @@ function buildDeleteSubtaskTool(connection) {
2877
2891
  function buildListSubtasksTool(connection) {
2878
2892
  return defineTool(
2879
2893
  "list_subtasks",
2880
- "List all subtasks under the current parent task. Returns status, PR info (githubPRNumber, githubPRUrl), agent assignment (agentId), and plan for each child.",
2894
+ "List all subtasks under the current parent task. When to use: coordinating child work \u2014 check who is running, which children are ready for review, or which are blocked. When NOT to use: for non-child related tasks \u2014 use get_task. Returns: JSON array with id, slug, title, status, storyPointValue, githubPRNumber, githubPRUrl, agentId, and plan for each child.",
2881
2895
  {},
2882
2896
  async () => {
2883
2897
  try {
@@ -2896,7 +2910,7 @@ function buildPackTools(connection) {
2896
2910
  return [
2897
2911
  defineTool(
2898
2912
  "start_child_cloud_build",
2899
- "Start a cloud build for a child task. The child must be in Open status with story points and an agent assigned.",
2913
+ "Start a cloud build (codespace) for a child task. Preconditions: the child must be in Open status, have a story point value, and have an agent assigned. When NOT to use: if the child is already InProgress or past Open \u2014 it is already building or done. Returns: confirmation string including the child task id that the build was started for.",
2900
2914
  {
2901
2915
  childTaskId: z4.string().describe("The child task ID to start a cloud build for")
2902
2916
  },
@@ -2916,7 +2930,7 @@ function buildPackTools(connection) {
2916
2930
  ),
2917
2931
  defineTool(
2918
2932
  "stop_child_build",
2919
- "Stop a running cloud build for a child task. Sends a stop signal to the child agent.",
2933
+ "Send a graceful stop signal to a running child build's agent. When to use: you decided the child should halt (plan changed, deadlock, scope pivot) and want the agent to exit cleanly. This is not a force-kill \u2014 the agent may take a moment to wind down. Returns: confirmation string.",
2920
2934
  {
2921
2935
  childTaskId: z4.string().describe("The child task ID whose build should be stopped")
2922
2936
  },
@@ -2936,7 +2950,7 @@ function buildPackTools(connection) {
2936
2950
  ),
2937
2951
  defineTool(
2938
2952
  "approve_and_merge_pr",
2939
- "Approve and merge a child task's pull request. Only succeeds if all CI/CD checks are passing. Returns an error if checks are pending (retry after waiting) or failed (investigate). The child task must be in ReviewPR status.",
2953
+ "Approve and merge a child task's PR. Preconditions: child is in ReviewPR and you have reviewed the diff. When NOT to use: if review surfaced issues \u2014 post to the child's chat or escalate instead. Returns: { childTaskId, prNumber, merged }. merged=true means the merge happened and status is now ReviewDev. merged=false means the merge is queued for GitHub automerge (CI still running) \u2014 do NOT proceed as if merged; wait for the child's status to transition to ReviewDev. If checks have failed, this call errors \u2014 investigate the failure.",
2940
2954
  {
2941
2955
  childTaskId: z4.string().describe("The child task ID whose PR should be approved and merged")
2942
2956
  },
@@ -3024,7 +3038,7 @@ function buildCodeReviewTools(connection) {
3024
3038
  return [
3025
3039
  defineTool(
3026
3040
  "approve_code_review",
3027
- "Approve the code review. Use this when the code passes all review criteria and is ready to merge.",
3041
+ "Approve the code review and exit. When to use: the diff passes all review criteria and is ready to merge. When NOT to use: if any substantive issue remains \u2014 call request_code_changes with a structured issues[] list. This tool takes only a summary; it does not accept an issues array (by design \u2014 an approval should have no blocking issues). Returns: confirmation string.",
3028
3042
  {
3029
3043
  summary: z6.string().describe("Brief summary of what was reviewed and why it looks good")
3030
3044
  },
@@ -3047,7 +3061,7 @@ ${summary}`;
3047
3061
  ),
3048
3062
  defineTool(
3049
3063
  "request_code_changes",
3050
- "Request changes during code review. Use this when substantive issues are found that need to be fixed before merge.",
3064
+ "Request changes during code review and exit. When to use: substantive issues were found that must be fixed before merge. Each entry in issues[] is { file: string, line?: number, severity: 'critical' | 'major' | 'minor', description: string }. When NOT to use: for an approval \u2014 use approve_code_review. Returns: confirmation string.",
3051
3065
  {
3052
3066
  issues: z6.array(
3053
3067
  z6.object({
@@ -3641,7 +3655,7 @@ function buildClientInspectionTools(manager) {
3641
3655
  return [
3642
3656
  defineTool(
3643
3657
  "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.",
3658
+ "When the client-side (browser) debugger is paused at a breakpoint, returns the call stack and local variables from the paused frame. When to use: after a client breakpoint hits, to see what is in scope in the browser. When NOT to use: for server pauses \u2014 use debug_inspect_paused. Returns: JSON with side='client', reason, hitBreakpoints, callStack, and localVariables. If the debugger paused between turns and has since resumed, queued breakpoint hits are returned instead.",
3645
3659
  {},
3646
3660
  async () => {
3647
3661
  const clientOrErr = requirePlaywrightClient(manager);
@@ -3684,7 +3698,7 @@ ${JSON.stringify(queuedHits, null, 2)}`
3684
3698
  ),
3685
3699
  defineTool(
3686
3700
  "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.",
3701
+ "Evaluate a JavaScript expression in the browser context. When paused at a client breakpoint, runs in the paused scope (use frameIndex to pick a frame); otherwise runs in the page's global scope with access to DOM, window, and React internals. Side effects are real \u2014 navigations, DOM mutations, and function calls execute. Prefer read-only expressions unless you specifically want to mutate page state. Returns: `(type) value`.",
3688
3702
  {
3689
3703
  expression: z8.string().describe("JavaScript expression to evaluate in the browser context"),
3690
3704
  frameIndex: z8.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
@@ -3758,7 +3772,7 @@ function buildClientInteractionTools(manager) {
3758
3772
  ),
3759
3773
  defineTool(
3760
3774
  "debug_navigate_client",
3761
- "Navigate the headless browser to a specific URL. Use this to reproduce specific flows or visit different pages.",
3775
+ "Navigate the headless browser to a URL. When to use: reproducing a specific flow or visiting a different page. Waits for `domcontentloaded` before returning (Playwright's default ~30s navigation timeout applies). Returns: confirmation string with the resolved current URL.",
3762
3776
  {
3763
3777
  url: z8.string().describe("URL to navigate to (e.g., http://localhost:3000/dashboard)")
3764
3778
  },
@@ -3775,7 +3789,7 @@ function buildClientInteractionTools(manager) {
3775
3789
  ),
3776
3790
  defineTool(
3777
3791
  "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.",
3792
+ "Click an element in the headless browser by CSS selector. When to use: reproducing bugs by interacting with the UI programmatically. Playwright auto-waits for the element to be visible, stable, and enabled before clicking, up to a 10s timeout \u2014 a miss throws and is reported in the failure message. Returns: confirmation string.",
3779
3793
  {
3780
3794
  selector: z8.string().describe(
3781
3795
  "CSS selector of the element to click (e.g., 'button.submit', '#login-form input[type=submit]')"
@@ -3948,7 +3962,7 @@ function buildDebugLifecycleTools(manager) {
3948
3962
  return [
3949
3963
  defineTool(
3950
3964
  "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.",
3965
+ "Activate debug mode. When to use: at the start of a debug session before setting breakpoints or probes. Restarts the dev server with Node.js --inspect for server-side CDP, and/or launches a headless Chromium via Playwright for client-side. Default: if neither serverSide nor clientSide is passed, server-side only is activated. Pass clientSide=true for frontend debugging (previewUrl required), or set both for full-stack. Returns: activation status string listing active modes, an echoed hypothesis if given, and a warning if source maps are missing.",
3952
3966
  {
3953
3967
  hypothesis: z9.string().optional().describe("Your hypothesis about the bug \u2014 helps track debugging intent"),
3954
3968
  serverSide: z9.boolean().optional().describe(
@@ -4094,7 +4108,7 @@ ${JSON.stringify(queuedHits, null, 2)}`
4094
4108
  ),
4095
4109
  defineTool(
4096
4110
  "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.",
4111
+ "Evaluate a JavaScript expression server-side in the Node process. When paused at a breakpoint the expression runs in that frame's scope (use frameIndex to pick a different frame); otherwise it runs in the global scope. Side effects are real \u2014 assignments, function calls, and I/O all execute. Prefer read-only expressions unless you specifically want to mutate state. Returns: `(type) value`.",
4098
4112
  {
4099
4113
  expression: z9.string().describe("The JavaScript expression to evaluate"),
4100
4114
  frameIndex: z9.number().optional().describe("Call stack frame index (0 = top frame). Defaults to the top frame.")
@@ -4193,7 +4207,7 @@ function buildProbeResultTools(manager) {
4193
4207
  return [
4194
4208
  defineTool(
4195
4209
  "debug_get_probe_results",
4196
- "Fetch captured probe hit data. Returns expression values from each time a probed line executed.",
4210
+ "Fetch captured probe hit data. When to use: after triggering the code path for a probe set by debug_add_probe. Filtering: pass label to filter directly, or probeId to look up and filter by that probe's label; if both are passed, label wins and probeId is ignored. Returns: grouped text with per-probe hit count, timestamps, and captured expression values.",
4197
4211
  {
4198
4212
  probeId: z9.string().optional().describe("Filter results by probe ID (resolves to its label)"),
4199
4213
  label: z9.string().optional().describe("Filter results by probe label"),
@@ -4800,7 +4814,31 @@ function collectMissingProps(taskProps) {
4800
4814
 
4801
4815
  // src/execution/tool-access.ts
4802
4816
  var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
4803
- var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
4817
+ var DESTRUCTIVE_PATTERNS = [
4818
+ {
4819
+ name: "git push --force (without --force-with-lease)",
4820
+ re: /git\s+push\s+(?:-f\b|--force(?!-with-lease))/
4821
+ },
4822
+ { name: "git push --delete", re: /git\s+push\s+(?:-d\b|--delete\b)/ },
4823
+ { name: "git reset --hard", re: /git\s+reset\s+--hard\b/ },
4824
+ {
4825
+ name: "rm -rf /",
4826
+ re: /rm\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r|--recursive\s+--force)\s+\/(?!\S)/
4827
+ },
4828
+ { name: "sudo rm", re: /\bsudo\s+rm\b/ },
4829
+ { name: "chmod world-writable", re: /\bchmod\s+(?:-R\s+)?[0-7]*7{2,3}\b/ },
4830
+ { name: "dd to device", re: /\bdd\s+.*\bof=\/dev\// },
4831
+ { name: "redirect to block device", re: />\s*\/dev\/(?:sd[a-z]|nvme\d|xvd[a-z])/ },
4832
+ { name: "mkfs filesystem creation", re: /\bmkfs(?:\.|\s)/ },
4833
+ { name: "shutdown/poweroff/halt/reboot", re: /\b(?:shutdown|poweroff|halt|reboot)\b/ },
4834
+ { name: "fork bomb", re: /:\(\)\s*\{\s*:\|\s*:&\s*\}\s*;\s*:/ }
4835
+ ];
4836
+ function matchesDestructive(cmd) {
4837
+ for (const { name, re } of DESTRUCTIVE_PATTERNS) {
4838
+ if (re.test(cmd)) return name;
4839
+ }
4840
+ return null;
4841
+ }
4804
4842
  function isPlanFile(input) {
4805
4843
  const filePath = String(input.file_path ?? input.path ?? "");
4806
4844
  return filePath.includes(".claude/plans/");
@@ -4820,10 +4858,11 @@ function handleDiscoveryToolAccess(toolName, input) {
4820
4858
  function handleBuildingToolAccess(toolName, input) {
4821
4859
  if (toolName === "Bash") {
4822
4860
  const cmd = String(input.command ?? "");
4823
- if (DESTRUCTIVE_CMD_PATTERN.test(cmd)) {
4861
+ const matched = matchesDestructive(cmd);
4862
+ if (matched) {
4824
4863
  return {
4825
4864
  behavior: "deny",
4826
- message: "Destructive operation blocked. Use safer alternatives."
4865
+ message: `Destructive operation blocked (${matched}). Use safer alternatives.`
4827
4866
  };
4828
4867
  }
4829
4868
  }
@@ -5006,6 +5045,65 @@ function buildCanUseTool(host) {
5006
5045
  };
5007
5046
  }
5008
5047
 
5048
+ // src/execution/redactor.ts
5049
+ var REDACTED = "<redacted>";
5050
+ var BEARER_RE = /\b(Bearer\s+)[A-Za-z0-9_\-.]{20,}/g;
5051
+ var VENDOR_KEY_RE = /\b(?:sk-[a-zA-Z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|xoxb-[A-Za-z0-9-]+|xai-[A-Za-z0-9-]{20,})\b/g;
5052
+ var AWS_ACCESS_KEY_RE = /\bAKIA[0-9A-Z]{16}\b/g;
5053
+ var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
5054
+ var ENV_SECRET_RE = /^(\s*(?:export\s+)?([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|CREDENTIAL|CREDENTIALS)[A-Z0-9_]*)\s*=\s*)['"]?([^\s'"]+)['"]?/gm;
5055
+ var COOKIE_HEADER_RE = /(Cookie:\s*)[^\r\n]+/gi;
5056
+ var BASIC_AUTH_URL_RE = /(https?:\/\/)([^:@\s/]+):([^@\s]+)@/g;
5057
+ var AWS_SECRET_LINE_RE = /^.*(?:secret|aws_secret).*$/gim;
5058
+ var AWS_SECRET_VALUE_RE = /\b([A-Za-z0-9/+=]{40})\b/g;
5059
+ var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/gi;
5060
+ function redact(input) {
5061
+ if (!input) return { output: input, redacted: 0 };
5062
+ let count = 0;
5063
+ let output = input;
5064
+ output = output.replace(SYSTEM_REMINDER_RE, () => {
5065
+ count++;
5066
+ return "<!-- stripped injection -->";
5067
+ });
5068
+ output = output.replace(BEARER_RE, (_match, prefix) => {
5069
+ count++;
5070
+ return `${prefix}${REDACTED}`;
5071
+ });
5072
+ output = output.replace(VENDOR_KEY_RE, () => {
5073
+ count++;
5074
+ return REDACTED;
5075
+ });
5076
+ output = output.replace(AWS_ACCESS_KEY_RE, () => {
5077
+ count++;
5078
+ return REDACTED;
5079
+ });
5080
+ output = output.replace(JWT_RE, () => {
5081
+ count++;
5082
+ return REDACTED;
5083
+ });
5084
+ output = output.replace(
5085
+ AWS_SECRET_LINE_RE,
5086
+ (line) => line.replace(AWS_SECRET_VALUE_RE, (match) => {
5087
+ if (/^[a-f0-9]+$/i.test(match)) return match;
5088
+ count++;
5089
+ return REDACTED;
5090
+ })
5091
+ );
5092
+ output = output.replace(ENV_SECRET_RE, (_match, prefix, _key, _value) => {
5093
+ count++;
5094
+ return `${prefix}${REDACTED}`;
5095
+ });
5096
+ output = output.replace(COOKIE_HEADER_RE, (_match, prefix) => {
5097
+ count++;
5098
+ return `${prefix}${REDACTED}`;
5099
+ });
5100
+ output = output.replace(BASIC_AUTH_URL_RE, (_match, scheme, user) => {
5101
+ count++;
5102
+ return `${scheme}${user}:${REDACTED}@`;
5103
+ });
5104
+ return { output, redacted: count };
5105
+ }
5106
+
5009
5107
  // src/execution/query-executor.ts
5010
5108
  var logger2 = createServiceLogger("QueryExecutor");
5011
5109
  var IMAGE_ERROR_PATTERN2 = /Could not process image/i;
@@ -5018,12 +5116,15 @@ function buildHooks(host) {
5018
5116
  async (input) => {
5019
5117
  if (host.isStopped()) return await Promise.resolve({ continue: false });
5020
5118
  if (input.hook_event_name === "PostToolUse") {
5021
- const output = typeof input.tool_response === "string" ? input.tool_response.slice(0, 500) : JSON.stringify(input.tool_response).slice(0, 500);
5119
+ const raw = typeof input.tool_response === "string" ? input.tool_response : JSON.stringify(input.tool_response);
5120
+ const { output: redacted, redacted: redactedCount } = redact(raw);
5121
+ const output = redacted.slice(0, 500);
5022
5122
  host.connection.sendEvent({
5023
5123
  type: "tool_result",
5024
5124
  tool: input.tool_name,
5025
5125
  output,
5026
- isError: false
5126
+ isError: false,
5127
+ ...redactedCount > 0 ? { redactedCount } : {}
5027
5128
  });
5028
5129
  host.pendingToolOutputs.push({ tool: input.tool_name, output });
5029
5130
  if (input.tool_name === "mcp__conveyor__create_pull_request") {
@@ -5390,6 +5491,22 @@ async function runWithRetry(initialQuery, context, host, options) {
5390
5491
  var CostTracker = class {
5391
5492
  cumulativeCostUsd = 0;
5392
5493
  modelUsage = /* @__PURE__ */ new Map();
5494
+ seeded = false;
5495
+ /**
5496
+ * Rehydrate cumulative spend from the server so `maxBudgetUsd` enforcement
5497
+ * accounts for costs incurred by prior agent runs on the same task. Must be
5498
+ * called before any `addQueryCost` / `addModelUsage` — re-seeding after
5499
+ * costs have accumulated would clobber in-process totals.
5500
+ */
5501
+ seed(totalCostUsd, modelUsage) {
5502
+ if (this.seeded) return;
5503
+ if (this.cumulativeCostUsd > 0 || this.modelUsage.size > 0) return;
5504
+ this.cumulativeCostUsd = totalCostUsd;
5505
+ for (const entry of modelUsage) {
5506
+ this.modelUsage.set(entry.model, { ...entry });
5507
+ }
5508
+ this.seeded = true;
5509
+ }
5393
5510
  /** Add cost from a completed query and return the running total */
5394
5511
  addQueryCost(queryCostUsd) {
5395
5512
  this.cumulativeCostUsd += queryCostUsd;
@@ -5580,6 +5697,10 @@ var QueryBridge = class {
5580
5697
  resume() {
5581
5698
  this._stopped = false;
5582
5699
  }
5700
+ /** Rehydrate CostTracker from server-side cumulative spend on agent boot. */
5701
+ seedCostTracker(totalCostUsd, modelUsage) {
5702
+ this.costTracker.seed(totalCostUsd, modelUsage);
5703
+ }
5583
5704
  /**
5584
5705
  * Execute a Claude SDK query.
5585
5706
  * Without followUpContent: runs initial mode execution (build/plan).
@@ -5845,6 +5966,7 @@ var SessionRunner = class _SessionRunner {
5845
5966
  });
5846
5967
  }
5847
5968
  this.queryBridge = this.createQueryBridge();
5969
+ await this.seedCostTrackerFromServer();
5848
5970
  this.logInitialization();
5849
5971
  const staleMessageCount = this.pendingMessages.length;
5850
5972
  const didExecuteInitialQuery = await this.executeInitialMode();
@@ -6206,6 +6328,36 @@ var SessionRunner = class _SessionRunner {
6206
6328
  }
6207
6329
  });
6208
6330
  }
6331
+ /**
6332
+ * Rehydrate CostTracker from server. Caps at 3s so a slow API call never
6333
+ * blocks agent startup — on timeout/error we fall through with a $0 tracker
6334
+ * (the existing behavior before this rehydration path was added).
6335
+ */
6336
+ async seedCostTrackerFromServer() {
6337
+ if (!this.queryBridge) return;
6338
+ const TIMEOUT_MS = 3e3;
6339
+ try {
6340
+ const timeout = new Promise((resolve2) => {
6341
+ setTimeout(() => resolve2(null), TIMEOUT_MS);
6342
+ });
6343
+ const fetched = await Promise.race([this.connection.getCumulativeSpending(), timeout]);
6344
+ if (fetched) {
6345
+ this.queryBridge.seedCostTracker(fetched.totalCostUsd, fetched.modelUsage);
6346
+ process.stderr.write(
6347
+ `[conveyor-agent] CostTracker seeded: $${fetched.totalCostUsd.toFixed(4)} across ${fetched.modelUsage.length} model(s)
6348
+ `
6349
+ );
6350
+ } else {
6351
+ process.stderr.write(
6352
+ "[conveyor-agent] CostTracker seed timed out after 3s \u2014 starting at $0\n"
6353
+ );
6354
+ }
6355
+ } catch (err) {
6356
+ const msg = err instanceof Error ? err.message : String(err);
6357
+ process.stderr.write(`[conveyor-agent] CostTracker seed failed: ${msg} \u2014 starting at $0
6358
+ `);
6359
+ }
6360
+ }
6209
6361
  /** Proactively refresh the GitHub token before the 1-hour expiry. */
6210
6362
  async refreshGithubToken() {
6211
6363
  try {
@@ -6709,8 +6861,8 @@ function buildTaskListTools(connection) {
6709
6861
  { annotations: { readOnlyHint: true } }
6710
6862
  ),
6711
6863
  defineTool(
6712
- "get_task",
6713
- "Get detailed information about a task including chat messages, child tasks, and session.",
6864
+ "get_project_task",
6865
+ "Get detailed information about a task in this project (chat messages, child tasks, session). Project-runner scope.",
6714
6866
  { task_id: z10.string().describe("The task ID to look up") },
6715
6867
  async ({ task_id }) => {
6716
6868
  try {
@@ -6808,8 +6960,8 @@ function buildMutationTools2(connection) {
6808
6960
  }
6809
6961
  ),
6810
6962
  defineTool(
6811
- "update_task",
6812
- "Update an existing task's title, description, plan, status, or assignee.",
6963
+ "update_project_task",
6964
+ "Update an existing task's title, description, plan, status, or assignee. Project-runner scope.",
6813
6965
  {
6814
6966
  task_id: z10.string().describe("The task ID to update"),
6815
6967
  title: z10.string().optional().describe("New title"),
@@ -7359,7 +7511,7 @@ function spawnChildAgent(assignment, workDir) {
7359
7511
  CONVEYOR_TASK_TOKEN: taskToken,
7360
7512
  CONVEYOR_TASK_ID: taskId,
7361
7513
  ...sessionId ? { CONVEYOR_SESSION_ID: sessionId } : {},
7362
- CONVEYOR_MODE: agentMode === "code-review" ? "code-review" : mode,
7514
+ CONVEYOR_MODE: mode,
7363
7515
  CONVEYOR_WORKSPACE: workDir,
7364
7516
  CONVEYOR_USE_WORKTREE: "false",
7365
7517
  CONVEYOR_AGENT_MODE: effectiveAgentMode,
@@ -7413,7 +7565,7 @@ async function killAgent(agent, taskId) {
7413
7565
  async function handleAssignment(assignment, activeAgents, projectDir, connection) {
7414
7566
  const { taskId, mode } = assignment;
7415
7567
  const shortId = taskId.slice(0, 8);
7416
- const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
7568
+ const agentKey = mode === "code-review" ? `${taskId}:code-review` : taskId;
7417
7569
  const existing = activeAgents.get(agentKey);
7418
7570
  if (existing) {
7419
7571
  if (existing.process.exitCode === null) {
@@ -7814,4 +7966,4 @@ export {
7814
7966
  loadForwardPorts,
7815
7967
  loadConveyorConfig
7816
7968
  };
7817
- //# sourceMappingURL=chunk-2YHZHOR2.js.map
7969
+ //# sourceMappingURL=chunk-COJPX2QI.js.map