@rallycry/conveyor-agent 10.3.1 → 10.4.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.
@@ -719,13 +719,16 @@ var AgentConnection = class _AgentConnection {
719
719
  };
720
720
  }
721
721
  // ── Convenience methods (thin wrappers around call / emit) ─────────
722
- async emitStatus(status, reason) {
722
+ async emitStatus(status, reason, questionText) {
723
723
  this.lastEmittedStatus = status;
724
724
  await this.flushEvents();
725
725
  const payload = {
726
726
  sessionId: this.config.sessionId,
727
727
  status,
728
- ...reason ? { reason } : {}
728
+ ...reason ? { reason } : {},
729
+ // Only sent with a pending TUI questionnaire (reason "user_question") so
730
+ // the server can surface the real question text in the notification.
731
+ ...questionText ? { questionText } : {}
729
732
  };
730
733
  const AWAIT_STATUSES = ["idle", "waiting_for_input", "connected"];
731
734
  if (AWAIT_STATUSES.includes(status)) {
@@ -2046,7 +2049,14 @@ var ReportAgentStatusRequestSchema = z.object({
2046
2049
  sessionId: z.string(),
2047
2050
  status: z.string(),
2048
2051
  /** Why the agent reports this status (e.g. "user_question" while an AskUserQuestion questionnaire is pending in the TUI). */
2049
- reason: z.string().optional()
2052
+ reason: z.string().optional(),
2053
+ /**
2054
+ * The pending question text, sent only alongside `reason: "user_question"`
2055
+ * so the server can surface it in the user-question notification body (and
2056
+ * thus the Attention feed) instead of a generic string. Optional: older
2057
+ * agents omit it and the server falls back to the generic wording.
2058
+ */
2059
+ questionText: z.string().optional()
2050
2060
  });
2051
2061
  var NotifyAgentVersionRequestSchema = z.object({
2052
2062
  sessionId: z.string(),
@@ -2069,7 +2079,10 @@ var CreateSubtaskRequestSchema = z.object({
2069
2079
  plan: z.string().optional(),
2070
2080
  storyPointValue: z.number().int().positive().optional(),
2071
2081
  ordinal: z.number().int().nonnegative().optional(),
2072
- followParentStatus: z.boolean().optional()
2082
+ followParentStatus: z.boolean().optional(),
2083
+ /** Sibling subtask ids or slugs this subtask blocks on (explicit dependency
2084
+ * metadata — preferred over encoding order in plan text / ordinal). */
2085
+ dependsOn: z.array(z.string().min(1)).max(32).optional()
2073
2086
  });
2074
2087
  var UpdateSubtaskRequestSchema = z.object({
2075
2088
  sessionId: z.string(),
@@ -2079,7 +2092,10 @@ var UpdateSubtaskRequestSchema = z.object({
2079
2092
  plan: z.string().optional(),
2080
2093
  status: z.string().optional(),
2081
2094
  storyPointValue: z.number().int().positive().optional(),
2082
- followParentStatus: z.boolean().optional()
2095
+ followParentStatus: z.boolean().optional(),
2096
+ /** Replace this subtask's dependency edges with these sibling ids/slugs.
2097
+ * Empty array clears all. Omit to leave dependencies unchanged. */
2098
+ dependsOn: z.array(z.string().min(1)).max(32).optional()
2083
2099
  });
2084
2100
  var DeleteSubtaskRequestSchema = z.object({
2085
2101
  sessionId: z.string(),
@@ -2500,6 +2516,9 @@ var CreateProjectSubtaskRequestSchema = z2.object({
2500
2516
  ordinal: z2.number().int().nonnegative().optional(),
2501
2517
  storyPointValue: z2.number().int().positive().optional(),
2502
2518
  followParentStatus: z2.boolean().optional(),
2519
+ /** Sibling subtask ids or slugs this subtask blocks on (explicit dependency
2520
+ * metadata — preferred over encoding order in plan text / ordinal). */
2521
+ dependsOn: z2.array(z2.string().min(1)).max(32).optional(),
2503
2522
  requestingUserId: z2.string().optional()
2504
2523
  });
2505
2524
  var UpdateProjectSubtaskRequestSchema = z2.object({
@@ -2962,7 +2981,8 @@ function defineTool(name, description, schema, handler, options) {
2962
2981
  description,
2963
2982
  schema,
2964
2983
  handler,
2965
- annotations: options?.annotations
2984
+ annotations: options?.annotations,
2985
+ strict: options?.strict
2966
2986
  };
2967
2987
  }
2968
2988
 
@@ -3759,6 +3779,7 @@ var PtyOutputCoalescer = class {
3759
3779
 
3760
3780
  // src/harness/pty/tool-server.ts
3761
3781
  import { createServer as createServer2 } from "http";
3782
+ import { z as z3 } from "zod";
3762
3783
  import { writeFile as writeFile3 } from "fs/promises";
3763
3784
  import { join as join2 } from "path";
3764
3785
  import { randomBytes } from "crypto";
@@ -3813,9 +3834,14 @@ var PtyToolServer = class {
3813
3834
  /** Fresh McpServer with every tool registered — one per MCP session. */
3814
3835
  buildMcpServer() {
3815
3836
  const mcp = new McpServer({ name: this.name, version: "1.0.0" });
3816
- const register = mcp.tool.bind(mcp);
3837
+ const register = mcp.registerTool.bind(mcp);
3817
3838
  for (const tool2 of this.tools) {
3818
- register(tool2.name, tool2.description, tool2.schema, (args) => tool2.handler(args));
3839
+ const inputSchema = tool2.strict ? z3.strictObject(tool2.schema) : tool2.schema;
3840
+ register(
3841
+ tool2.name,
3842
+ { description: tool2.description, inputSchema },
3843
+ (args) => tool2.handler(args)
3844
+ );
3819
3845
  }
3820
3846
  return mcp;
3821
3847
  }
@@ -3943,6 +3969,9 @@ async function startToolServers(mcpServers, tempDir) {
3943
3969
  }
3944
3970
 
3945
3971
  // src/harness/pty/spawn-args.ts
3972
+ function resolveClaudeBinary() {
3973
+ return process.env.CONVEYOR_CLAUDE_BIN ?? "claude";
3974
+ }
3946
3975
  function buildSpawnArgs(input) {
3947
3976
  const args = [];
3948
3977
  if (input.resume) {
@@ -4232,9 +4261,9 @@ function extractSpawn(mod) {
4232
4261
  }
4233
4262
  async function loadPtySpawn() {
4234
4263
  const mod = await import("node-pty");
4235
- const spawn2 = extractSpawn(mod);
4236
- if (!spawn2) throw new Error("node-pty: spawn export not found");
4237
- return spawn2;
4264
+ const spawn3 = extractSpawn(mod);
4265
+ if (!spawn3) throw new Error("node-pty: spawn export not found");
4266
+ return spawn3;
4238
4267
  }
4239
4268
  function inheritedEnv(socketPath) {
4240
4269
  const env = {};
@@ -4733,8 +4762,8 @@ var PtySession = class {
4733
4762
  // server doesn't load).
4734
4763
  ...this.mcpConfigPath ? { mcpConfigPath: this.mcpConfigPath } : {}
4735
4764
  });
4736
- const spawn2 = await loadPtySpawn();
4737
- const pty = spawn2(spec.file, spec.args, {
4765
+ const spawn3 = await loadPtySpawn();
4766
+ const pty = spawn3(spec.file, spec.args, {
4738
4767
  name: "xterm-color",
4739
4768
  cols: this.cols,
4740
4769
  rows: this.rows,
@@ -5188,7 +5217,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
5188
5217
  ``,
5189
5218
  `## Important Rules`,
5190
5219
  `- When dependencies are set on children, use them to determine execution order. Fire all ready tasks in parallel (up to the PACK_CHILD_LIMIT backpressure \u2014 see the loop above).`,
5191
- `- When NO dependencies are set on any children, fall back to ordinal order (one at a time). This preserves backward compatibility.`,
5220
+ `- Dependencies are explicit card metadata (set at creation via create_subtask's dependsOn, or rewired with update_subtask). Prefer them over reading order out of plan text. When NO dependencies are set on any children, fall back to ordinal order (one at a time) \u2014 this is legacy behavior; set dependsOn when a child truly blocks on another.`,
5192
5221
  `- After firing builds OR when waiting on CI, explicitly state you are going idle. Go idle when waiting \u2014 the system wakes you (or relaunches this environment) when a child changes status.`,
5193
5222
  `- Do NOT attempt to write code yourself. Your role is coordination only.`,
5194
5223
  `- If a child is stuck in "InProgress" for an unusually long time, use get_execution_logs(childTaskId) to check its logs and escalate to the team if it appears stuck.`,
@@ -5809,7 +5838,8 @@ function buildDiscoveryPrompt(context, runnerMode) {
5809
5838
  `### Parent Task Coordination`,
5810
5839
  `You are a parent task with child tasks. Focus on breaking work into child tasks with detailed plans, not planning implementation for yourself.`,
5811
5840
  `- Use \`list_subtasks\` to review existing children. Create or update child tasks using \`create_subtask\` / \`update_subtask\`.`,
5812
- `- Each child task should be a self-contained unit of work with a clear plan.`
5841
+ `- Each child task should be a self-contained unit of work with a clear plan.`,
5842
+ `- Set ordering as explicit **card metadata**, not prose: pass \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` for any child that blocks on another. Leave independent children with no dependencies so the pack runner fans them out in parallel. Do NOT encode "do X after Y" only in the plan text \u2014 the runner schedules off dependsOn, not narrative.`
5813
5843
  ] : [
5814
5844
  `### Self-Update vs Subtasks`,
5815
5845
  `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
@@ -5823,6 +5853,7 @@ function buildDiscoveryPrompt(context, runnerMode) {
5823
5853
  `- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
5824
5854
  `- Include testing requirements and acceptance criteria`,
5825
5855
  `- Set \`storyPointValue\` based on estimated complexity`,
5856
+ `- Express cross-child ordering as \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit dependency metadata, not "after task 2" phrasing in the plan. Independent children get no dependencies so they run in parallel.`,
5826
5857
  ``,
5827
5858
  `### Plan Verification Requirements`,
5828
5859
  `Every plan MUST include a **Testing / Verification** section so the build agent has a clear definition of "done". Enumerate:`,
@@ -5869,6 +5900,7 @@ function buildAutoPrompt(context, runnerMode) {
5869
5900
  `- Reference existing implementations when relevant`,
5870
5901
  `- Include testing requirements and acceptance criteria`,
5871
5902
  `- Set \`storyPointValue\` based on estimated complexity`,
5903
+ `- Express cross-child ordering as \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit dependency metadata, not "after task 2" phrasing in the plan. Independent children get no dependencies so they run in parallel.`,
5872
5904
  ``,
5873
5905
  ...buildPlanCitationFormat(),
5874
5906
  ``,
@@ -5877,7 +5909,8 @@ function buildAutoPrompt(context, runnerMode) {
5877
5909
  `### Parent Task Guidance`,
5878
5910
  `You are a parent task. Your plan should define child tasks with detailed plans, not direct implementation steps.`,
5879
5911
  `After ExitPlanMode, you'll transition to Review mode to coordinate child task execution.`,
5880
- `Child task status lifecycle: Open \u2192 InProgress \u2192 ReviewPR \u2192 ReviewDev \u2192 Complete.`
5912
+ `Child task status lifecycle: Open \u2192 InProgress \u2192 ReviewPR \u2192 ReviewDev \u2192 Complete.`,
5913
+ `Set child ordering with \`dependsOn\` (sibling ids/slugs) on \`create_subtask\` \u2014 explicit metadata the pack runner schedules off, not order described in plan text. Independent children get none and run in parallel.`
5881
5914
  ] : [],
5882
5915
  ``,
5883
5916
  `### Autonomous Guidelines:`,
@@ -6078,7 +6111,9 @@ Environment (fully ready \u2014 do NOT verify or set up):`,
6078
6111
  `- All dependencies are installed, database is migrated, and the dev server is running.`,
6079
6112
  `- Git is configured. Commit and push directly to this branch.`,
6080
6113
  `- The shell cwd resets between Bash calls \u2014 always use absolute paths (or \`git -C\`, \`bun run --cwd\`); never spend a tool call on a bare \`cd\`.`,
6081
- `- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output. Don't poll background jobs with sleep/ps/tail loops.`,
6114
+ `- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output.`,
6115
+ `- Waiting on long-running commands: if a gate finishes in under ~2 minutes, run it in the foreground with a timeout. Anything longer: launch it with run_in_background and STOP \u2014 a completion notification arrives when it finishes. Never busy-wait with sleep/pgrep/tail loops (foreground shell commands are killed at ~2 minutes), and never re-run the suite to escape a wait that looks stalled.`,
6116
+ `- Deferred tools have no schema loaded until fetched \u2014 before first use, load it via ToolSearch (query "select:<ToolName>", e.g. "select:Monitor"); never guess a deferred tool's parameters.`,
6082
6117
  `- The \`gh\` CLI may not be installed \u2014 use the mcp__conveyor__* tools for PR and task state.`,
6083
6118
  `- Read a file before your first Write/Edit to it, and batch multiple changes to the same file into a single call instead of many sequential edits.`,
6084
6119
  `
@@ -6508,7 +6543,7 @@ async function buildInitialPrompt(mode, context, isAuto, agentMode) {
6508
6543
  }
6509
6544
 
6510
6545
  // src/tools/task-context-tools.ts
6511
- import { z as z3 } from "zod";
6546
+ import { z as z4 } from "zod";
6512
6547
 
6513
6548
  // src/tools/helpers.ts
6514
6549
  function textResult(text) {
@@ -6542,8 +6577,8 @@ function buildReadTaskChatTool(connection) {
6542
6577
  "read_task_chat",
6543
6578
  "Read recent human/user chat messages for a task. Omit task_id for the current task; pass a child ID for a child's chat. For agent logs use get_execution_logs.",
6544
6579
  {
6545
- limit: z3.number().optional().describe("Number of recent messages to fetch (default 20)"),
6546
- task_id: z3.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
6580
+ limit: z4.number().optional().describe("Number of recent messages to fetch (default 20)"),
6581
+ task_id: z4.string().optional().describe("Child task ID to read chat from. Omit to read the current task's chat.")
6547
6582
  },
6548
6583
  async ({ limit, task_id }) => {
6549
6584
  try {
@@ -6587,7 +6622,7 @@ function buildGetTaskTool(connection) {
6587
6622
  "get_task",
6588
6623
  "Look up any task by slug or ID. Returns JSON with id, slug, title, description, plan, status, branch, githubPRNumber, githubPRUrl, storyPoints. For children use list_subtasks.",
6589
6624
  {
6590
- slug_or_id: z3.string().describe("The task slug (e.g. 'my-task') or CUID")
6625
+ slug_or_id: z4.string().describe("The task slug (e.g. 'my-task') or CUID")
6591
6626
  },
6592
6627
  async ({ slug_or_id }) => {
6593
6628
  try {
@@ -6610,9 +6645,9 @@ function buildGetExecutionLogsTool(connection) {
6610
6645
  "get_execution_logs",
6611
6646
  "Read CLI execution logs \u2014 agent reasoning, tool calls, and setup/dev-server output. Filter via source='agent' or 'application'. For human chat use read_task_chat.",
6612
6647
  {
6613
- task_id: z3.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
6614
- source: z3.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
6615
- limit: z3.number().optional().describe("Max number of log entries to return (default 50, max 500).")
6648
+ task_id: z4.string().optional().describe("Task ID or slug. Omit to read logs from the current task."),
6649
+ source: z4.enum(["agent", "application"]).optional().describe("Filter by log source. Omit for all logs."),
6650
+ limit: z4.number().optional().describe("Max number of log entries to return (default 50, max 500).")
6616
6651
  },
6617
6652
  async ({ task_id, source, limit }) => {
6618
6653
  try {
@@ -6672,7 +6707,7 @@ function buildGetAttachmentTool(connection) {
6672
6707
  return defineTool(
6673
6708
  "get_attachment",
6674
6709
  "Fetch one task file's content plus metadata by file ID. Call list_task_files first to discover IDs and check sizes \u2014 large binaries may be truncated by the service's size limit.",
6675
- { fileId: z3.string().describe("The file ID to retrieve") },
6710
+ { fileId: z4.string().describe("The file ID to retrieve") },
6676
6711
  async ({ fileId }) => {
6677
6712
  try {
6678
6713
  const file = await connection.call("getTaskFile", {
@@ -6710,7 +6745,7 @@ function buildTaskContextTools(connection) {
6710
6745
  }
6711
6746
 
6712
6747
  // src/tools/dependency-suggestion-tools.ts
6713
- import { z as z4 } from "zod";
6748
+ import { z as z5 } from "zod";
6714
6749
  function buildGetDependenciesTool(connection) {
6715
6750
  return defineTool(
6716
6751
  "get_dependencies",
@@ -6736,10 +6771,10 @@ function buildGetSuggestionsTool(connection) {
6736
6771
  "get_suggestions",
6737
6772
  "List project suggestions sorted by vote score. Filter by status or cap with limit (default 20). Suggestions are project-level ideas, not tasks \u2014 use get_task for tasks.",
6738
6773
  {
6739
- status: z4.string().optional().describe(
6774
+ status: z5.string().optional().describe(
6740
6775
  "Filter by status: Planning, Open, InProgress, ReviewPR, ReviewDev, ReviewLive, Complete, Cancelled"
6741
6776
  ),
6742
- limit: z4.number().int().min(1).max(100).optional().describe("Max results (default 20)")
6777
+ limit: z5.number().int().min(1).max(100).optional().describe("Max results (default 20)")
6743
6778
  },
6744
6779
  async ({ status, limit }) => {
6745
6780
  try {
@@ -6763,14 +6798,14 @@ function buildGetSuggestionsTool(connection) {
6763
6798
  }
6764
6799
 
6765
6800
  // src/tools/mutation-tools.ts
6766
- import { z as z5 } from "zod";
6801
+ import { z as z6 } from "zod";
6767
6802
  function buildPostToChatTool(connection) {
6768
6803
  return defineTool(
6769
6804
  "post_to_chat",
6770
6805
  "Post a message to the task chat for the team to see. Your turn output is NOT shown in chat, so this is the only way the team sees your status, summaries, and questions. Omit task_id to post to the current task's chat; pass a child's ID to message its chat.",
6771
6806
  {
6772
- message: z5.string().describe("The message to post to the team"),
6773
- task_id: z5.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
6807
+ message: z6.string().describe("The message to post to the team"),
6808
+ task_id: z6.string().optional().describe("Child task ID to post to. Omit to post to the current task's chat.")
6774
6809
  },
6775
6810
  async ({ message, task_id }) => {
6776
6811
  try {
@@ -6808,8 +6843,8 @@ function buildForceUpdateTaskStatusTool(connection) {
6808
6843
  "force_update_task_status",
6809
6844
  "EMERGENCY ONLY: force-override a task's Kanban status. Use when an automatic transition failed and the task is wedged. Normal flow transitions status automatically.",
6810
6845
  {
6811
- status: z5.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
6812
- task_id: z5.string().optional().describe("Child task ID to update. Omit to update the current task.")
6846
+ status: z6.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
6847
+ task_id: z6.string().optional().describe("Child task ID to update. Omit to update the current task.")
6813
6848
  },
6814
6849
  async ({ status, task_id }) => {
6815
6850
  try {
@@ -6840,18 +6875,18 @@ function buildCreatePullRequestTool(connection, config) {
6840
6875
  "create_pull_request",
6841
6876
  "Create a GitHub PR for this task. Auto-stages, commits (commitMessage or title default), pushes to origin, then opens the PR. Always use this instead of gh CLI or raw git.",
6842
6877
  {
6843
- title: z5.string().describe("The PR title"),
6844
- body: z5.string().describe("The PR description/body in markdown"),
6845
- branch: z5.string().optional().describe(
6878
+ title: z6.string().describe("The PR title"),
6879
+ body: z6.string().describe("The PR description/body in markdown"),
6880
+ branch: z6.string().optional().describe(
6846
6881
  "The head branch name for the PR. If the task doesn't have a branch set, this will be used. Defaults to the task's existing branch."
6847
6882
  ),
6848
- baseBranch: z5.string().optional().describe(
6883
+ baseBranch: z6.string().optional().describe(
6849
6884
  "The base branch to target for the PR (e.g. 'main', 'develop'). Defaults to the project's configured dev branch."
6850
6885
  ),
6851
- commitMessage: z5.string().optional().describe(
6886
+ commitMessage: z6.string().optional().describe(
6852
6887
  "Commit message for staging uncommitted changes. If not provided, a default message based on the PR title will be used."
6853
6888
  ),
6854
- skipVerify: z5.boolean().optional().describe(
6889
+ skipVerify: z6.boolean().optional().describe(
6855
6890
  "Controls the local pre-push quality gate (lint/typecheck/test). Defaults to true (--no-verify): the push skips the local gate because you should run gates yourself before opening the PR and CI re-runs them on the resulting PR. Running the full gate synchronously during the push would block the agent's event loop long enough to drop the Conveyor socket connection. Pass false to force the local pre-push hook to run."
6856
6891
  )
6857
6892
  },
@@ -6933,7 +6968,7 @@ function buildAddDependencyTool(connection) {
6933
6968
  "add_dependency",
6934
6969
  "Add a blocking dependency \u2014 this task cannot start until the named task is merged to dev. For post-task follow-ups use create_follow_up_task instead.",
6935
6970
  {
6936
- depends_on_slug_or_id: z5.string().describe("Slug or ID of the task this task depends on")
6971
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task this task depends on")
6937
6972
  },
6938
6973
  async ({ depends_on_slug_or_id }) => {
6939
6974
  try {
@@ -6955,7 +6990,7 @@ function buildRemoveDependencyTool(connection) {
6955
6990
  "remove_dependency",
6956
6991
  "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.",
6957
6992
  {
6958
- depends_on_slug_or_id: z5.string().describe("Slug or ID of the task to remove as dependency")
6993
+ depends_on_slug_or_id: z6.string().describe("Slug or ID of the task to remove as dependency")
6959
6994
  },
6960
6995
  async ({ depends_on_slug_or_id }) => {
6961
6996
  try {
@@ -6977,10 +7012,10 @@ function buildCreateFollowUpTaskTool(connection) {
6977
7012
  "create_follow_up_task",
6978
7013
  "Create a follow-up task that depends on the current task. Use for out-of-scope work or cleanup that should land after this task merges. For blockers use add_dependency.",
6979
7014
  {
6980
- title: z5.string().describe("Follow-up task title"),
6981
- description: z5.string().optional().describe("Brief description of the follow-up work"),
6982
- plan: z5.string().optional().describe("Implementation plan if known"),
6983
- story_point_value: z5.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
7015
+ title: z6.string().describe("Follow-up task title"),
7016
+ description: z6.string().optional().describe("Brief description of the follow-up work"),
7017
+ plan: z6.string().optional().describe("Implementation plan if known"),
7018
+ story_point_value: z6.number().optional().describe("Story point estimate (1=Common, 2=Magic, 3=Rare, 5=Unique)")
6984
7019
  },
6985
7020
  async ({ title, description, plan, story_point_value }) => {
6986
7021
  try {
@@ -7007,11 +7042,11 @@ function buildCreateSuggestionTool(connection) {
7007
7042
  "create_suggestion",
7008
7043
  "Suggest a feature, improvement, rule, or idea for the project. Duplicates are deduped and your upvote is recorded. For actionable work on this task open a follow-up task.",
7009
7044
  {
7010
- title: z5.string().describe("Short title for the suggestion"),
7011
- description: z5.string().optional().describe(
7045
+ title: z6.string().describe("Short title for the suggestion"),
7046
+ description: z6.string().optional().describe(
7012
7047
  "1-2 sentence description of what should change and why. Keep concise and project-focused."
7013
7048
  ),
7014
- tag_names: z5.array(z5.string()).optional().describe("Tag names to categorize the suggestion")
7049
+ tag_names: z6.array(z6.string()).optional().describe("Tag names to categorize the suggestion")
7015
7050
  },
7016
7051
  async ({ title, description, tag_names }) => {
7017
7052
  try {
@@ -7040,8 +7075,8 @@ function buildVoteSuggestionTool(connection) {
7040
7075
  "vote_suggestion",
7041
7076
  "Vote +1 or -1 on a project suggestion. Use to express support or disagreement with a specific suggestion returned by get_suggestions.",
7042
7077
  {
7043
- suggestion_id: z5.string().describe("The suggestion ID to vote on"),
7044
- value: z5.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
7078
+ suggestion_id: z6.string().describe("The suggestion ID to vote on"),
7079
+ value: z6.number().refine((v) => v === 1 || v === -1, { message: "Value must be 1 or -1" }).describe("+1 to upvote, -1 to downvote")
7045
7080
  },
7046
7081
  async ({ suggestion_id, value }) => {
7047
7082
  try {
@@ -7074,7 +7109,7 @@ function buildMutationTools(connection, config) {
7074
7109
  // src/tools/attachment-tools.ts
7075
7110
  import { readFile as readFile3, stat as stat5 } from "fs/promises";
7076
7111
  import { basename, extname, isAbsolute, join as join5 } from "path";
7077
- import { z as z6 } from "zod";
7112
+ import { z as z7 } from "zod";
7078
7113
  var IMAGE_MIME_BY_EXT = {
7079
7114
  ".png": "image/png",
7080
7115
  ".jpg": "image/jpeg",
@@ -7087,8 +7122,8 @@ function buildUploadAttachmentTool(connection, config) {
7087
7122
  "upload_attachment",
7088
7123
  "Upload an image file (e.g. a Playwright screenshot) as a task attachment AND post it to the task chat in one step \u2014 no follow-up post_to_chat call needed. Supports png/jpg/gif/webp.",
7089
7124
  {
7090
- path: z6.string().describe("Path to the image file \u2014 absolute, or relative to the workspace root"),
7091
- title: z6.string().optional().describe("Short caption posted with the image (defaults to the file name)")
7125
+ path: z7.string().describe("Path to the image file \u2014 absolute, or relative to the workspace root"),
7126
+ title: z7.string().optional().describe("Short caption posted with the image (defaults to the file name)")
7092
7127
  },
7093
7128
  async ({ path: path4, title }) => {
7094
7129
  try {
@@ -7144,7 +7179,7 @@ function buildUploadAttachmentTool(connection, config) {
7144
7179
  }
7145
7180
 
7146
7181
  // src/tools/checklist-tools.ts
7147
- import { z as z7 } from "zod";
7182
+ import { z as z8 } from "zod";
7148
7183
  function buildListManualTestsTool(connection) {
7149
7184
  return defineTool(
7150
7185
  "list_manual_tests",
@@ -7196,8 +7231,8 @@ function buildQueryManualTestsTool(connection) {
7196
7231
  "query_manual_tests",
7197
7232
  "Query manual tests across many tasks in this project, grouped by task. Filter by card status (ReviewDev, ReviewLive, Complete, ...) and/or test status (open | approved | rejected). Use to answer 'show all OPEN manual tests in ReviewDev' or 'show all REJECTED manual tests with the failing reason'. With no filters it defaults to the needs-attention view: open+rejected tests on ReviewDev/ReviewLive cards.",
7198
7233
  {
7199
- cardStatuses: z7.array(z7.string()).optional().describe('Filter tasks by card status, e.g. ["ReviewDev", "ReviewLive"]'),
7200
- testStatuses: z7.array(z7.enum(["open", "approved", "rejected"])).optional().describe("Filter tests by status: open | approved | rejected")
7234
+ cardStatuses: z8.array(z8.string()).optional().describe('Filter tasks by card status, e.g. ["ReviewDev", "ReviewLive"]'),
7235
+ testStatuses: z8.array(z8.enum(["open", "approved", "rejected"])).optional().describe("Filter tests by status: open | approved | rejected")
7201
7236
  },
7202
7237
  async ({ cardStatuses, testStatuses }) => {
7203
7238
  try {
@@ -7221,7 +7256,7 @@ function buildSetManualTestsTool(connection) {
7221
7256
  "set_manual_tests",
7222
7257
  "Add manual test steps to the task checklist. Existing items with the same title are automatically skipped (deduplication). Use to record specific manual verification steps that reviewers should follow when testing this PR.",
7223
7258
  {
7224
- items: z7.array(z7.object({ title: z7.string().min(1).describe("A concise, actionable test step") })).min(1).describe("List of manual test steps to add")
7259
+ items: z8.array(z8.object({ title: z8.string().min(1).describe("A concise, actionable test step") })).min(1).describe("List of manual test steps to add")
7225
7260
  },
7226
7261
  async ({ items }) => {
7227
7262
  try {
@@ -7244,8 +7279,8 @@ function buildEditManualTestTool(connection) {
7244
7279
  "edit_manual_test",
7245
7280
  "Rename an existing manual test step. Identify the test by its current title (case-insensitive); pass the new title to replace it. Use to correct or refine a recorded manual verification step.",
7246
7281
  {
7247
- title: z7.string().min(1).describe("The current title of the manual test to edit"),
7248
- newTitle: z7.string().min(1).describe("The new title for the manual test")
7282
+ title: z8.string().min(1).describe("The current title of the manual test to edit"),
7283
+ newTitle: z8.string().min(1).describe("The new title for the manual test")
7249
7284
  },
7250
7285
  async ({ title, newTitle }) => {
7251
7286
  try {
@@ -7267,7 +7302,7 @@ function buildRemoveManualTestTool(connection) {
7267
7302
  "remove_manual_test",
7268
7303
  "Remove an existing manual test step from the task checklist. Identify the test by its title (case-insensitive). Use to delete a stale or incorrect manual verification step.",
7269
7304
  {
7270
- title: z7.string().min(1).describe("The title of the manual test to remove")
7305
+ title: z8.string().min(1).describe("The title of the manual test to remove")
7271
7306
  },
7272
7307
  async ({ title }) => {
7273
7308
  try {
@@ -7288,7 +7323,7 @@ function buildApproveManualTestTool(connection) {
7288
7323
  "approve_manual_test",
7289
7324
  "Sign off on (approve) a manual test step on behalf of your authenticated user. Identify the test by its title (case-insensitive). Use after you have verified the step passes.",
7290
7325
  {
7291
- title: z7.string().min(1).describe("The title of the manual test to approve")
7326
+ title: z8.string().min(1).describe("The title of the manual test to approve")
7292
7327
  },
7293
7328
  async ({ title }) => {
7294
7329
  try {
@@ -7309,8 +7344,8 @@ function buildRejectManualTestTool(connection) {
7309
7344
  "reject_manual_test",
7310
7345
  "Flag an issue with (reject) a manual test step on behalf of your authenticated user, recording the reason. Identify the test by its title (case-insensitive). Use when the step fails verification.",
7311
7346
  {
7312
- title: z7.string().min(1).describe("The title of the manual test to reject"),
7313
- reason: z7.string().min(1).max(2e3).describe("Why the test failed \u2014 what went wrong, shown to the team")
7347
+ title: z8.string().min(1).describe("The title of the manual test to reject"),
7348
+ reason: z8.string().min(1).max(2e3).describe("Why the test failed \u2014 what went wrong, shown to the team")
7314
7349
  },
7315
7350
  async ({ title, reason }) => {
7316
7351
  try {
@@ -7347,16 +7382,17 @@ function buildCommonTools(connection, config) {
7347
7382
  }
7348
7383
 
7349
7384
  // src/tools/pm-tools.ts
7350
- import { z as z8 } from "zod";
7385
+ import { z as z9 } from "zod";
7351
7386
  var SP_DESCRIPTION = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
7352
7387
  var FOLLOW_PARENT_STATUS_DESCRIPTION = "Child mirrors the parent task's status automatically \u2014 for subtasks that ship on the parent's branch/PR with no build or PR of their own. Manual status writes on a follower stick only until the parent's next transition.";
7388
+ var DEPENDS_ON_DESCRIPTION = "Sibling subtask ids or slugs this subtask blocks on (it won't start until they merge to dev). Set explicit dependency metadata here instead of describing order in the plan text \u2014 the pack runner schedules children off these edges. Omit / leave empty for independent children so they run in parallel.";
7353
7389
  function buildUpdateTaskTool(connection) {
7354
7390
  return defineTool(
7355
7391
  "update_task_plan",
7356
7392
  "Save the finalized plan and/or description to the current task. Use in Plan mode after alignment. For children use update_subtask; for title/tags/PR use update_task_properties.",
7357
7393
  {
7358
- plan: z8.string().optional().describe("The task plan in markdown"),
7359
- description: z8.string().optional().describe("Updated task description")
7394
+ plan: z9.string().optional().describe("The task plan in markdown"),
7395
+ description: z9.string().optional().describe("Updated task description")
7360
7396
  },
7361
7397
  async ({ plan, description }) => {
7362
7398
  try {
@@ -7377,14 +7413,23 @@ function buildCreateSubtaskTool(connection) {
7377
7413
  "create_subtask",
7378
7414
  "Create a subtask under the current parent task. Use when breaking a complex parent into smaller pieces during planning. For post-task follow-ups use create_follow_up_task.",
7379
7415
  {
7380
- title: z8.string().describe("Subtask title"),
7381
- description: z8.string().optional().describe("Brief description"),
7382
- plan: z8.string().optional().describe("Implementation plan in markdown"),
7383
- ordinal: z8.number().optional().describe("Step/order number (0-based)"),
7384
- storyPointValue: z8.number().optional().describe(SP_DESCRIPTION),
7385
- followParentStatus: z8.boolean().optional().describe(FOLLOW_PARENT_STATUS_DESCRIPTION)
7416
+ title: z9.string().describe("Subtask title"),
7417
+ description: z9.string().optional().describe("Brief description"),
7418
+ plan: z9.string().optional().describe("Implementation plan in markdown"),
7419
+ ordinal: z9.number().optional().describe("Step/order number (0-based)"),
7420
+ storyPointValue: z9.number().optional().describe(SP_DESCRIPTION),
7421
+ followParentStatus: z9.boolean().optional().describe(FOLLOW_PARENT_STATUS_DESCRIPTION),
7422
+ dependsOn: z9.array(z9.string()).optional().describe(DEPENDS_ON_DESCRIPTION)
7386
7423
  },
7387
- async ({ title, description, plan, ordinal, storyPointValue, followParentStatus }) => {
7424
+ async ({
7425
+ title,
7426
+ description,
7427
+ plan,
7428
+ ordinal,
7429
+ storyPointValue,
7430
+ followParentStatus,
7431
+ dependsOn
7432
+ }) => {
7388
7433
  try {
7389
7434
  const result = await connection.call("createSubtask", {
7390
7435
  sessionId: connection.sessionId,
@@ -7393,9 +7438,10 @@ function buildCreateSubtaskTool(connection) {
7393
7438
  ...plan !== void 0 && { plan },
7394
7439
  ...storyPointValue !== void 0 && { storyPointValue },
7395
7440
  ...ordinal !== void 0 && { ordinal },
7396
- ...followParentStatus !== void 0 && { followParentStatus }
7441
+ ...followParentStatus !== void 0 && { followParentStatus },
7442
+ ...dependsOn !== void 0 && { dependsOn }
7397
7443
  });
7398
- return textResult(`Subtask created with ID: ${result.id}`);
7444
+ return textResult(`Subtask created with ID: ${result.id} (slug: ${result.slug})`);
7399
7445
  } catch (error) {
7400
7446
  return textResult(
7401
7447
  `Failed to create subtask: ${error instanceof Error ? error.message : "Unknown error"}`
@@ -7407,17 +7453,28 @@ function buildCreateSubtaskTool(connection) {
7407
7453
  function buildUpdateSubtaskTool(connection) {
7408
7454
  return defineTool(
7409
7455
  "update_subtask",
7410
- "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue). Use when refining a child's plan or reordering. For the current task use update_task_plan.",
7456
+ "Update an existing subtask's fields (title, description, plan, ordinal, storyPointValue, dependsOn). Use when refining a child's plan, reordering, or rewiring dependencies. For the current task use update_task_plan.",
7411
7457
  {
7412
- subtaskId: z8.string().describe("The subtask ID to update"),
7413
- title: z8.string().optional(),
7414
- description: z8.string().optional(),
7415
- plan: z8.string().optional(),
7416
- ordinal: z8.number().optional(),
7417
- storyPointValue: z8.number().optional().describe(SP_DESCRIPTION),
7418
- followParentStatus: z8.boolean().optional().describe(FOLLOW_PARENT_STATUS_DESCRIPTION)
7458
+ subtaskId: z9.string().describe("The subtask ID to update"),
7459
+ title: z9.string().optional(),
7460
+ description: z9.string().optional(),
7461
+ plan: z9.string().optional(),
7462
+ ordinal: z9.number().optional(),
7463
+ storyPointValue: z9.number().optional().describe(SP_DESCRIPTION),
7464
+ followParentStatus: z9.boolean().optional().describe(FOLLOW_PARENT_STATUS_DESCRIPTION),
7465
+ dependsOn: z9.array(z9.string()).optional().describe(
7466
+ `${DEPENDS_ON_DESCRIPTION} Replaces the full dependency set \u2014 pass [] to clear all, omit to leave unchanged.`
7467
+ )
7419
7468
  },
7420
- async ({ subtaskId, title, description, plan, storyPointValue, followParentStatus }) => {
7469
+ async ({
7470
+ subtaskId,
7471
+ title,
7472
+ description,
7473
+ plan,
7474
+ storyPointValue,
7475
+ followParentStatus,
7476
+ dependsOn
7477
+ }) => {
7421
7478
  try {
7422
7479
  await connection.call("updateSubtask", {
7423
7480
  sessionId: connection.sessionId,
@@ -7426,7 +7483,8 @@ function buildUpdateSubtaskTool(connection) {
7426
7483
  ...description !== void 0 && { description },
7427
7484
  ...plan !== void 0 && { plan },
7428
7485
  ...storyPointValue !== void 0 && { storyPointValue },
7429
- ...followParentStatus !== void 0 && { followParentStatus }
7486
+ ...followParentStatus !== void 0 && { followParentStatus },
7487
+ ...dependsOn !== void 0 && { dependsOn }
7430
7488
  });
7431
7489
  return textResult("Subtask updated.");
7432
7490
  } catch (error) {
@@ -7439,7 +7497,7 @@ function buildDeleteSubtaskTool(connection) {
7439
7497
  return defineTool(
7440
7498
  "delete_subtask",
7441
7499
  "Delete a subtask by id. When to use: a subtask was created in error or is no longer needed. Returns: confirmation string.",
7442
- { subtaskId: z8.string().describe("The subtask ID to delete") },
7500
+ { subtaskId: z9.string().describe("The subtask ID to delete") },
7443
7501
  async ({ subtaskId }) => {
7444
7502
  try {
7445
7503
  await connection.call("deleteSubtask", {
@@ -7477,7 +7535,7 @@ function buildPackTools(connection) {
7477
7535
  "start_child_cloud_build",
7478
7536
  "Start a cloud build (codespace) for a child task. Preconditions: child is in Open status, has a story point value, and has an agent assigned.",
7479
7537
  {
7480
- childTaskId: z8.string().describe("The child task ID to start a cloud build for")
7538
+ childTaskId: z9.string().describe("The child task ID to start a cloud build for")
7481
7539
  },
7482
7540
  async ({ childTaskId }) => {
7483
7541
  try {
@@ -7497,7 +7555,7 @@ function buildPackTools(connection) {
7497
7555
  "stop_child_build",
7498
7556
  "Send a graceful stop signal to a running child build's agent. Not a force-kill \u2014 the agent may take a moment to wind down.",
7499
7557
  {
7500
- childTaskId: z8.string().describe("The child task ID whose build should be stopped")
7558
+ childTaskId: z9.string().describe("The child task ID whose build should be stopped")
7501
7559
  },
7502
7560
  async ({ childTaskId }) => {
7503
7561
  try {
@@ -7517,7 +7575,7 @@ function buildPackTools(connection) {
7517
7575
  "approve_and_merge_pr",
7518
7576
  "Approve and merge a child task's PR. Preconditions: child in ReviewPR. Returns { merged }: true = merged (status\u2192ReviewDev); false = automerge queued, wait for ReviewDev.",
7519
7577
  {
7520
- childTaskId: z8.string().describe("The child task ID whose PR should be approved and merged")
7578
+ childTaskId: z9.string().describe("The child task ID whose PR should be approved and merged")
7521
7579
  },
7522
7580
  async ({ childTaskId }) => {
7523
7581
  try {
@@ -7555,22 +7613,29 @@ function buildPmTools(connection, options) {
7555
7613
  }
7556
7614
 
7557
7615
  // src/tools/discovery-tools.ts
7558
- import { z as z9 } from "zod";
7559
- var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique)";
7616
+ import { z as z10 } from "zod";
7617
+ var SP_DESCRIPTION2 = "Story point value (1=Common, 2=Magic, 3=Rare, 5=Unique). The key is 'storyPointValue' \u2014 not 'storyPoints'.";
7618
+ var VALID_PROPERTY_KEYS = "title, storyPointValue, tagNames, githubPRUrl, githubBranch";
7560
7619
  function buildDiscoveryTools(connection) {
7561
7620
  return [
7562
7621
  defineTool(
7563
7622
  "update_task_properties",
7564
- "Set one or more task properties in a single call. All fields are optional \u2014 only include the fields you want to update.",
7623
+ "Set one or more task properties in a single call. Valid keys: title, storyPointValue, tagNames, githubPRUrl, githubBranch. All are optional \u2014 include only the ones you want to update (at least one). Unknown keys are rejected.",
7565
7624
  {
7566
- title: z9.string().optional().describe("The new task title"),
7567
- storyPointValue: z9.number().optional().describe(SP_DESCRIPTION2),
7568
- tagNames: z9.array(z9.string()).optional().describe("Array of tag names to assign"),
7569
- githubPRUrl: z9.string().url().optional().describe("GitHub pull request URL to link to this task"),
7570
- githubBranch: z9.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
7625
+ title: z10.string().optional().describe("The new task title"),
7626
+ storyPointValue: z10.number().optional().describe(SP_DESCRIPTION2),
7627
+ tagNames: z10.array(z10.string()).optional().describe("Array of tag names to assign"),
7628
+ githubPRUrl: z10.string().url().optional().describe("GitHub pull request URL to link to this task"),
7629
+ githubBranch: z10.string().optional().describe("Set the GitHub branch name for this task (e.g. 'conveyor/my-feature-abc123')")
7571
7630
  },
7572
7631
  async ({ title, storyPointValue, tagNames, githubPRUrl, githubBranch }) => {
7573
7632
  try {
7633
+ const nothingToUpdate = title === void 0 && storyPointValue === void 0 && tagNames === void 0 && githubPRUrl === void 0 && githubBranch === void 0;
7634
+ if (nothingToUpdate) {
7635
+ return textResult(
7636
+ `No task properties were updated: none of the recognized keys were provided. Valid keys: ${VALID_PROPERTY_KEYS}. (Story points are set via 'storyPointValue', not 'storyPoints'.)`
7637
+ );
7638
+ }
7574
7639
  await connection.call("updateTaskProperties", {
7575
7640
  sessionId: connection.sessionId,
7576
7641
  title,
@@ -7592,13 +7657,17 @@ function buildDiscoveryTools(connection) {
7592
7657
  `Failed to update task properties: ${error instanceof Error ? error.message : "Unknown error"}`
7593
7658
  );
7594
7659
  }
7595
- }
7660
+ },
7661
+ // Reject unknown keys (e.g. "storyPoints") at validation instead of
7662
+ // stripping them into a silent no-op — every field here is optional, so
7663
+ // a stripped typo would otherwise report success while updating nothing.
7664
+ { strict: true }
7596
7665
  )
7597
7666
  ];
7598
7667
  }
7599
7668
 
7600
7669
  // src/tools/code-review-tools.ts
7601
- import { z as z10 } from "zod";
7670
+ import { z as z11 } from "zod";
7602
7671
  async function endReviewSession(connection, reason) {
7603
7672
  await connection.call("endReviewSession", {
7604
7673
  sessionId: connection.sessionId,
@@ -7611,7 +7680,7 @@ function buildCodeReviewTools(connection) {
7611
7680
  "approve_code_review",
7612
7681
  "Approve the code review and exit. Use when the diff passes all review criteria. Takes only a summary \u2014 for changes, use request_code_changes with a structured issues[] list.",
7613
7682
  {
7614
- summary: z10.string().describe("Brief summary of what was reviewed and why it looks good")
7683
+ summary: z11.string().describe("Brief summary of what was reviewed and why it looks good")
7615
7684
  },
7616
7685
  async ({ summary }) => {
7617
7686
  const content = `**Code Review: Approved** :white_check_mark:
@@ -7635,15 +7704,15 @@ ${summary}`;
7635
7704
  "request_code_changes",
7636
7705
  "Request changes during code review and exit. Use when substantive issues must be fixed before merge. Each issue: { file, line?, severity: critical|major|minor, description }.",
7637
7706
  {
7638
- issues: z10.array(
7639
- z10.object({
7640
- file: z10.string().describe("File path where the issue was found"),
7641
- line: z10.number().optional().describe("Line number (if applicable)"),
7642
- severity: z10.enum(["critical", "major", "minor"]).describe("Issue severity"),
7643
- description: z10.string().describe("What is wrong and how to fix it")
7707
+ issues: z11.array(
7708
+ z11.object({
7709
+ file: z11.string().describe("File path where the issue was found"),
7710
+ line: z11.number().optional().describe("Line number (if applicable)"),
7711
+ severity: z11.enum(["critical", "major", "minor"]).describe("Issue severity"),
7712
+ description: z11.string().describe("What is wrong and how to fix it")
7644
7713
  })
7645
7714
  ).describe("List of issues found during review"),
7646
- summary: z10.string().describe("Brief overall summary of the review findings")
7715
+ summary: z11.string().describe("Brief overall summary of the review findings")
7647
7716
  },
7648
7717
  async ({ issues, summary }) => {
7649
7718
  const issueLines = issues.map((issue) => {
@@ -8020,9 +8089,17 @@ async function handleResultCase(event, host, context, startTime, isTyping, lastA
8020
8089
  function hasAskUserQuestionBlock(event) {
8021
8090
  return event.message.content.some((b) => b.type === "tool_use" && b.name === "AskUserQuestion");
8022
8091
  }
8023
- async function armQuestionPending(host, state) {
8092
+ function questionTextFromEvent(event) {
8093
+ const text = event.questions.map((q) => q.question.trim()).filter((q) => q.length > 0).join("\n");
8094
+ return text.length > 0 ? text : void 0;
8095
+ }
8096
+ async function armQuestionPending(host, state, event) {
8024
8097
  state.questionPending = true;
8025
- await host.connection.emitStatus("waiting_for_input", AGENT_STATUS_REASON_USER_QUESTION);
8098
+ await host.connection.emitStatus(
8099
+ "waiting_for_input",
8100
+ AGENT_STATUS_REASON_USER_QUESTION,
8101
+ questionTextFromEvent(event)
8102
+ );
8026
8103
  await host.callbacks.onStatusChange("waiting_for_input");
8027
8104
  }
8028
8105
  async function clearQuestionPending(host, state, options) {
@@ -8036,7 +8113,7 @@ async function clearQuestionPending(host, state, options) {
8036
8113
  async function applyQuestionTransitions(event, host, state) {
8037
8114
  switch (event.type) {
8038
8115
  case "user_question":
8039
- await armQuestionPending(host, state);
8116
+ await armQuestionPending(host, state, event);
8040
8117
  return;
8041
8118
  case "assistant":
8042
8119
  if (!hasAskUserQuestionBlock(event)) {
@@ -9473,74 +9550,77 @@ function readAgentVersion() {
9473
9550
  return null;
9474
9551
  }
9475
9552
 
9476
- // src/execution/usage-sampler.ts
9477
- var logger4 = createServiceLogger("usage-sampler");
9478
- var OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
9479
- var OAUTH_USAGE_BETA = "oauth-2025-04-20";
9480
- var KNOWN_RATE_LIMIT_TYPES = [
9481
- "five_hour",
9482
- "seven_day",
9483
- "seven_day_opus",
9484
- "seven_day_sonnet"
9485
- ];
9486
- function normalizeUtilization(raw) {
9487
- if (typeof raw !== "number" || !Number.isFinite(raw)) return null;
9488
- const fraction = raw > 1 ? raw / 100 : raw;
9489
- return Math.max(0, Math.min(1, fraction));
9490
- }
9491
- function extractBucket(value) {
9492
- if (typeof value === "number") {
9493
- const utilization = normalizeUtilization(value);
9494
- return utilization === null ? null : { utilization, status: "allowed" };
9495
- }
9496
- if (value && typeof value === "object") {
9497
- const obj = value;
9498
- const utilization = normalizeUtilization(
9499
- obj.utilization ?? obj.used ?? obj.percent ?? obj.percentage
9500
- );
9501
- if (utilization === null) return null;
9502
- const status = typeof obj.status === "string" ? obj.status : "allowed";
9503
- return { utilization, status };
9504
- }
9505
- return null;
9553
+ // src/usage/parse-usage.ts
9554
+ function parseUsageGauges(stdout) {
9555
+ const session = stdout.match(/Current session:\s*(\d+(?:\.\d+)?)%\s*used/i);
9556
+ const weekly = [...stdout.matchAll(/Current week[^:\n]*:\s*(\d+(?:\.\d+)?)%\s*used/gi)];
9557
+ return {
9558
+ sessionUsage: session ? Number(session[1]) / 100 : null,
9559
+ weeklyUsage: weekly.length ? Math.max(...weekly.map((m) => Number(m[1]))) / 100 : null
9560
+ };
9506
9561
  }
9507
- function mapUsageResponse(data) {
9508
- if (!data || typeof data !== "object") return [];
9509
- const root = data;
9510
- const candidates = [root];
9511
- for (const key of ["usage", "rate_limits", "rateLimits"]) {
9512
- const nested = root[key];
9513
- if (nested && typeof nested === "object") {
9514
- candidates.push(nested);
9515
- }
9516
- }
9517
- for (const container of candidates) {
9518
- const samples = [];
9519
- for (const type of KNOWN_RATE_LIMIT_TYPES) {
9520
- const bucket = extractBucket(container[type]);
9521
- if (bucket) samples.push({ rateLimitType: type, ...bucket });
9562
+
9563
+ // src/usage/run-probe.ts
9564
+ import { spawn } from "child_process";
9565
+ var PROBE_TIMEOUT_MS = 15e3;
9566
+ function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"]) {
9567
+ return new Promise((resolve) => {
9568
+ let stdout = "";
9569
+ let settled = false;
9570
+ const finish = (out) => {
9571
+ if (settled) return;
9572
+ settled = true;
9573
+ resolve(out);
9574
+ };
9575
+ let child;
9576
+ try {
9577
+ child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"] });
9578
+ } catch {
9579
+ finish("");
9580
+ return;
9522
9581
  }
9523
- if (samples.length > 0) return samples;
9524
- }
9525
- return [];
9582
+ const timer = setTimeout(() => {
9583
+ try {
9584
+ child.kill("SIGKILL");
9585
+ } catch {
9586
+ }
9587
+ finish("");
9588
+ }, PROBE_TIMEOUT_MS);
9589
+ timer.unref?.();
9590
+ child.stdout?.on("data", (d) => {
9591
+ stdout += d.toString();
9592
+ });
9593
+ child.on("error", () => {
9594
+ clearTimeout(timer);
9595
+ finish("");
9596
+ });
9597
+ child.on("close", (code) => {
9598
+ clearTimeout(timer);
9599
+ finish(code === 0 ? stdout : "");
9600
+ });
9601
+ });
9526
9602
  }
9527
- async function sampleKeyUsage(token) {
9603
+
9604
+ // src/execution/usage-sampler.ts
9605
+ var logger4 = createServiceLogger("usage-sampler");
9606
+ async function sampleKeyUsage(token, probe = runUsageProbe) {
9528
9607
  if (!token) return [];
9529
9608
  try {
9530
- const res = await fetch(OAUTH_USAGE_URL, {
9531
- headers: {
9532
- Authorization: `Bearer ${token}`,
9533
- "anthropic-beta": OAUTH_USAGE_BETA
9534
- }
9535
- });
9536
- if (!res.ok) {
9537
- logger4.info("OAuth usage endpoint returned non-200", { status: res.status });
9538
- return [];
9609
+ const stdout = await probe();
9610
+ const { sessionUsage, weeklyUsage } = parseUsageGauges(stdout);
9611
+ const samples = [];
9612
+ if (sessionUsage !== null) {
9613
+ samples.push({ rateLimitType: "five_hour", utilization: sessionUsage, status: "allowed" });
9614
+ }
9615
+ if (weeklyUsage !== null) {
9616
+ samples.push({ rateLimitType: "seven_day", utilization: weeklyUsage, status: "allowed" });
9539
9617
  }
9540
- const data = await res.json();
9541
- return mapUsageResponse(data);
9618
+ if (samples.length === 0) {
9619
+ logger4.info("usage sample produced no gauges", { stdoutLength: stdout.length });
9620
+ }
9621
+ return samples;
9542
9622
  } catch (error) {
9543
- logger4.info("OAuth usage sample failed", {
9623
+ logger4.info("usage sample failed", {
9544
9624
  error: error instanceof Error ? error.message : String(error)
9545
9625
  });
9546
9626
  return [];
@@ -10060,7 +10140,11 @@ var SessionRunner = class _SessionRunner {
10060
10140
  const staleBatch = [...this.pendingMessages];
10061
10141
  const didExecuteInitialQuery = await this.executeInitialMode();
10062
10142
  if (staleBatch.length > 0 && didExecuteInitialQuery) {
10143
+ const historyContents = new Set(
10144
+ (this.fullContext?.chatHistory ?? []).filter((m) => m.role !== "assistant" && m.content.trim()).map((m) => m.content.trim())
10145
+ );
10063
10146
  for (const stale of staleBatch) {
10147
+ if (stale.content.trim() && !historyContents.has(stale.content.trim())) continue;
10064
10148
  const idx = this.pendingMessages.indexOf(stale);
10065
10149
  if (idx !== -1) this.pendingMessages.splice(idx, 1);
10066
10150
  }
@@ -10417,8 +10501,8 @@ var SessionRunner = class _SessionRunner {
10417
10501
  this.periodicFlushInFlight = false;
10418
10502
  }
10419
10503
  }
10420
- /** Sample the running Claude subscription key's rate-limit utilization from the
10421
- * Anthropic OAuth usage endpoint and report it as `rate_limit_update` events.
10504
+ /** Sample the running Claude subscription key's rate-limit utilization from
10505
+ * the Claude CLI `/usage` command and report it as `rate_limit_update` events.
10422
10506
  * The API (`persistRateLimitSnapshot`) attributes them to the key this session
10423
10507
  * launched under, keeping User Settings + the PtY-tab usage widget fresh and
10424
10508
  * `selectBestKey` rotation honest. Best-effort — never throws, no-op when the
@@ -10913,10 +10997,10 @@ function loadConveyorConfig() {
10913
10997
  }
10914
10998
 
10915
10999
  // src/setup/commands.ts
10916
- import { spawn, execSync } from "child_process";
11000
+ import { spawn as spawn2, execSync } from "child_process";
10917
11001
  function runSetupCommand(cmd, cwd, onOutput) {
10918
11002
  return new Promise((resolve, reject) => {
10919
- const child = spawn("sh", ["-c", cmd], {
11003
+ const child = spawn2("sh", ["-c", cmd], {
10920
11004
  cwd,
10921
11005
  stdio: ["ignore", "pipe", "pipe"],
10922
11006
  env: { ...process.env }
@@ -10955,7 +11039,7 @@ function runAuthTokenCommand(cmd, userEmail, cwd) {
10955
11039
  }
10956
11040
  }
10957
11041
  function runStartCommand(cmd, cwd, onOutput) {
10958
- const child = spawn("sh", ["-c", cmd], {
11042
+ const child = spawn2("sh", ["-c", cmd], {
10959
11043
  cwd,
10960
11044
  stdio: ["ignore", "pipe", "pipe"],
10961
11045
  detached: true,
@@ -11026,4 +11110,4 @@ export {
11026
11110
  runStartCommand,
11027
11111
  unshallowRepo
11028
11112
  };
11029
- //# sourceMappingURL=chunk-HAH2S5AD.js.map
11113
+ //# sourceMappingURL=chunk-VCXKVINO.js.map