@rallycry/conveyor-agent 10.6.1 → 10.7.1

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.
@@ -2502,6 +2502,9 @@ var ListMyLiveSessionsRequestSchema = z2.object({
2502
2502
  /** Admin-only: list another member's sessions instead of the caller's. */
2503
2503
  targetUserId: z2.string().optional()
2504
2504
  });
2505
+ var ListActiveSessionUsersRequestSchema = z2.object({
2506
+ projectId: z2.string()
2507
+ });
2505
2508
  var GetProjectAvailableTuisRequestSchema = z2.object({
2506
2509
  projectId: z2.string()
2507
2510
  });
@@ -3027,13 +3030,12 @@ var ClaudeCodeHarness = class {
3027
3030
  }
3028
3031
  createMcpServer(config) {
3029
3032
  const sdkTools = config.tools.map(
3030
- (t) => tool(
3031
- t.name,
3032
- t.description,
3033
- t.schema,
3034
- t.handler,
3035
- t.annotations ? { annotations: t.annotations } : void 0
3036
- )
3033
+ (t) => tool(t.name, t.description, t.schema, t.handler, {
3034
+ ...t.annotations ? { annotations: t.annotations } : {},
3035
+ // Preloads the tool into the prompt (skips ToolSearch deferral) via
3036
+ // `_meta['anthropic/alwaysLoad']` on the SDK MCP transport.
3037
+ ...t.alwaysLoad ? { alwaysLoad: true } : {}
3038
+ })
3037
3039
  );
3038
3040
  return createSdkMcpServer({ name: config.name, tools: sdkTools });
3039
3041
  }
@@ -3861,7 +3863,16 @@ var PtyToolServer = class {
3861
3863
  const inputSchema = tool2.strict ? z3.strictObject(tool2.schema) : tool2.schema;
3862
3864
  register(
3863
3865
  tool2.name,
3864
- { description: tool2.description, inputSchema },
3866
+ {
3867
+ description: tool2.description,
3868
+ inputSchema,
3869
+ // The spawned CLI reads `_meta['anthropic/alwaysLoad']` from the
3870
+ // `tools/list` response and preloads the tool into the prompt
3871
+ // instead of deferring it behind ToolSearch. Per-tool (vs per-server
3872
+ // `alwaysLoad` in the mcp-config) so only the hot protocol tools pay
3873
+ // the prompt cost while the long tail stays deferred.
3874
+ ...tool2.alwaysLoad ? { _meta: { "anthropic/alwaysLoad": true } } : {}
3875
+ },
3865
3876
  (args) => tool2.handler(args)
3866
3877
  );
3867
3878
  }
@@ -4119,6 +4130,25 @@ async function readRaw(path4) {
4119
4130
  return null;
4120
4131
  }
4121
4132
  }
4133
+ var READ_BACK_DELAYS_MS = [250, 500, 1e3, 2e3];
4134
+ var defaultSleep = (ms) => new Promise((resolve) => {
4135
+ setTimeout(resolve, ms);
4136
+ });
4137
+ async function writeWithReadBackRetry(io2, contents, delaysMs = READ_BACK_DELAYS_MS) {
4138
+ const sleep4 = io2.sleep ?? defaultSleep;
4139
+ for (let attempt = 0; ; attempt++) {
4140
+ await io2.write(contents);
4141
+ if (await io2.read() === contents) return true;
4142
+ if (attempt >= delaysMs.length) return false;
4143
+ await sleep4(delaysMs[attempt]);
4144
+ }
4145
+ }
4146
+ function fsWriteIo(path4, mode) {
4147
+ return {
4148
+ write: (contents) => writeFile4(path4, contents, mode === void 0 ? "utf8" : { encoding: "utf8", mode }),
4149
+ read: () => readRaw(path4)
4150
+ };
4151
+ }
4122
4152
  async function ensureClaudeCredentials(env = process.env) {
4123
4153
  const isCloud = isConveyorCloudEnv(env);
4124
4154
  const token = env.CLAUDE_CODE_OAUTH_TOKEN;
@@ -4135,7 +4165,13 @@ async function ensureClaudeCredentials(env = process.env) {
4135
4165
  });
4136
4166
  if (plan.action === "skip") return;
4137
4167
  await mkdir2(claudeConfigHome(), { recursive: true });
4138
- await writeFile4(path4, plan.contents, { encoding: "utf8", mode: 384 });
4168
+ const verified = await writeWithReadBackRetry(fsWriteIo(path4, 384), plan.contents);
4169
+ if (!verified) {
4170
+ process.stderr.write(
4171
+ `[conveyor-agent] claude credentials read-back still stale after retries at ${path4} \u2014 TUI may land on the login picker
4172
+ `
4173
+ );
4174
+ }
4139
4175
  await chmod2(path4, 384).catch(() => {
4140
4176
  });
4141
4177
  } catch (err) {
@@ -4168,9 +4204,10 @@ async function sanitizeApprovedApiKeys(oauthToken) {
4168
4204
  const path4 = claudeJsonPath();
4169
4205
  const cleaned = planApprovedApiKeyCleanup(await readRaw(path4), oauthToken);
4170
4206
  if (cleaned === null) return;
4171
- await writeFile4(path4, cleaned, "utf8");
4207
+ const verified = await writeWithReadBackRetry(fsWriteIo(path4), cleaned);
4172
4208
  process.stderr.write(
4173
- "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n"
4209
+ verified ? "[conveyor-agent] removed poisoned customApiKeyResponses.approved entry from .claude.json\n" : `[conveyor-agent] approved-key sanitize read-back still stale after retries at ${path4} \u2014 CLI may still see the poisoned entry
4210
+ `
4174
4211
  );
4175
4212
  } catch (err) {
4176
4213
  const message = err instanceof Error ? err.message : String(err);
@@ -4229,16 +4266,16 @@ async function ensureClaudeOnboarding(env = process.env, trustCwd) {
4229
4266
  const path4 = claudeJsonPath();
4230
4267
  const contents = planClaudeJsonSeed(await readRaw(path4), trustCwd);
4231
4268
  if (contents === null) return;
4232
- await writeFile4(path4, contents, "utf8");
4233
- const verify = await readRaw(path4);
4234
- if (verify === contents) {
4269
+ const verified = await writeWithReadBackRetry(fsWriteIo(path4), contents);
4270
+ if (verified) {
4235
4271
  process.stderr.write(
4236
4272
  `[conveyor-agent] claude onboarding seeded${trustCwd ? ` (trust: ${trustCwd})` : ""}
4237
4273
  `
4238
4274
  );
4239
4275
  } else {
4276
+ const verify = await readRaw(path4);
4240
4277
  process.stderr.write(
4241
- `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4}: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4278
+ `[conveyor-agent] claude onboarding seed read-back MISMATCH at ${path4} after retries: wrote ${contents.length}B, read ${verify?.length ?? 0}B \u2014 CLI may see stale config and park at a startup dialog
4242
4279
  `
4243
4280
  );
4244
4281
  }
@@ -6184,7 +6221,7 @@ Environment (fully ready \u2014 do NOT verify or set up):`,
6184
6221
  `- 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\`.`,
6185
6222
  `- 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.`,
6186
6223
  `- 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.`,
6187
- `- 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.`,
6224
+ `- The core mcp__conveyor__* tools are preloaded \u2014 call them directly; do NOT spend a ToolSearch call on them. For any tool that IS still deferred (schema not loaded), load ALL the schemas you expect to need in ONE ToolSearch call using fully-qualified names (query "select:Monitor,mcp__conveyor__<name>" \u2014 bare MCP tool names without the mcp__<server>__ prefix do not match); never guess a deferred tool's parameters.`,
6188
6225
  `- The \`gh\` CLI may not be installed \u2014 use the mcp__conveyor__* tools for PR and task state.`,
6189
6226
  `- 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.`,
6190
6227
  `
@@ -6437,9 +6474,10 @@ CRITICAL: You are in Auto mode. Do NOT report status, ask for confirmation, or g
6437
6474
  }
6438
6475
  if (isPm) {
6439
6476
  return [
6440
- `You are the project manager for this task.`,
6477
+ `You are the project manager for this task \u2014 a thoughtful, collaborative planner, not the implementer.`,
6478
+ `Your job right now is to help the team turn this request into a clear, well-scoped plan. Be a helpful planning partner: think out loud, surface trade-offs, and keep the human in the loop.`,
6441
6479
  `Read the task description and chat history carefully \u2014 the team has provided the initial context below. Acknowledge what they've asked for and respond in chat before taking silent tool actions.`,
6442
- `Start planning now \u2014 explore the codebase, ask clarifying questions if needed, and draft a plan. Do not wait for additional team input before engaging; the initial message IS the team engaging.`,
6480
+ `Start planning now \u2014 explore the codebase, ask clarifying questions if anything is ambiguous, and draft a plan. Do not wait for additional team input before engaging; the initial message IS the team engaging.`,
6443
6481
  `When you finish planning, save the plan with update_task_plan, post a summary of the plan for the team with post_to_chat (your turn output is NOT shown in chat), then end your turn. A separate task agent will execute the plan after review.`
6444
6482
  ];
6445
6483
  }
@@ -7852,6 +7890,28 @@ function getModeTools(agentMode, connection, config, context) {
7852
7890
  return config.mode === "pm" ? buildPmTools(connection, { includePackTools: false }) : [];
7853
7891
  }
7854
7892
  }
7893
+ var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
7894
+ // Every mode/session
7895
+ "post_to_chat",
7896
+ "read_task_chat",
7897
+ "get_task",
7898
+ "get_current_plan",
7899
+ "set_manual_tests",
7900
+ "create_pull_request",
7901
+ // Planning/auto/building
7902
+ "update_task_plan",
7903
+ "update_task_properties",
7904
+ // Review mode
7905
+ "approve_code_review",
7906
+ "request_code_changes",
7907
+ // Pack/parent orchestration
7908
+ "list_subtasks"
7909
+ ]);
7910
+ function withAlwaysLoad(tools) {
7911
+ return tools.map(
7912
+ (tool2) => ALWAYS_LOADED_TOOLS.has(tool2.name) ? { ...tool2, alwaysLoad: true } : tool2
7913
+ );
7914
+ }
7855
7915
  function buildConveyorTools(connection, config, context, agentMode) {
7856
7916
  const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
7857
7917
  const commonTools = buildCommonTools(connection, config);
@@ -7859,7 +7919,13 @@ function buildConveyorTools(connection, config, context, agentMode) {
7859
7919
  const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" ? buildDiscoveryTools(connection) : [];
7860
7920
  const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
7861
7921
  const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
7862
- return [...commonTools, ...modeTools, ...discoveryTools, ...codeReviewTools, ...emergencyTools];
7922
+ return withAlwaysLoad([
7923
+ ...commonTools,
7924
+ ...modeTools,
7925
+ ...discoveryTools,
7926
+ ...codeReviewTools,
7927
+ ...emergencyTools
7928
+ ]);
7863
7929
  }
7864
7930
  function createConveyorMcpServer(harness, connection, config, context, agentMode) {
7865
7931
  return harness.createMcpServer({
@@ -8465,7 +8531,6 @@ async function handleExitPlanMode(host, input) {
8465
8531
  if (gate) return gate;
8466
8532
  if (host.agentMode === "discovery") {
8467
8533
  host.hasExitedPlanMode = true;
8468
- host.discoveryCompleted = true;
8469
8534
  try {
8470
8535
  await host.connection.triggerIdentification();
8471
8536
  } catch (triggerErr) {
@@ -8474,13 +8539,8 @@ async function handleExitPlanMode(host, input) {
8474
8539
  );
8475
8540
  }
8476
8541
  await host.connection.postChatMessageAwait(
8477
- "Planning complete \u2014 awaiting team approval. Icon and agent assignment will be set automatically."
8542
+ "Plan posted \u2014 switching to auto mode. I'll start building now; send a message any time to steer or add follow-ups."
8478
8543
  );
8479
- if (host.harnessKind === "pty") {
8480
- setTimeout(() => host.requestStop(), 250);
8481
- } else {
8482
- host.requestStop();
8483
- }
8484
8544
  return { behavior: "allow", updatedInput: input };
8485
8545
  }
8486
8546
  try {
@@ -8525,19 +8585,6 @@ async function handleAskUserQuestion(host, input) {
8525
8585
  }
8526
8586
  return { behavior: "allow", updatedInput: { questions: input.questions, answers } };
8527
8587
  }
8528
- var EXPLORATION_TOOLS = /* @__PURE__ */ new Set(["Read", "Grep", "Glob"]);
8529
- function trackExploration(tracker, toolName, input) {
8530
- if (!EXPLORATION_TOOLS.has(toolName)) return null;
8531
- if (toolName === "Read") {
8532
- const filePath = String(input.file_path ?? "");
8533
- if (filePath) return tracker.recordFileRead(filePath);
8534
- }
8535
- if (toolName === "Grep" || toolName === "Glob") {
8536
- const pattern = String(input.pattern ?? "");
8537
- if (pattern) return tracker.recordSearch(pattern);
8538
- }
8539
- return null;
8540
- }
8541
8588
  var DENIAL_WARNING_THRESHOLD = 3;
8542
8589
  var DENIAL_FORCE_STOP_THRESHOLD = 8;
8543
8590
  function handleDenialEscalation(host, consecutiveDenials) {
@@ -8583,12 +8630,6 @@ function buildCanUseTool(host) {
8583
8630
  return await handleAskUserQuestion(host, input);
8584
8631
  }
8585
8632
  const result = resolveToolAccess(host, toolName, input);
8586
- if (result.behavior === "allow" && host.explorationTracker && !host.hasExitedPlanMode) {
8587
- const signal = trackExploration(host.explorationTracker, toolName, input);
8588
- if (signal) {
8589
- host.connection.postChatMessage(signal.message);
8590
- }
8591
- }
8592
8633
  if (result.behavior === "deny") {
8593
8634
  consecutiveDenials++;
8594
8635
  handleDenialEscalation(host, consecutiveDenials);
@@ -8765,7 +8806,7 @@ function resolvePromptDelivery(inputs) {
8765
8806
  if (inputs.runnerMode === "code-review") return "submit";
8766
8807
  if (inputs.isFollowUp || inputs.hasExistingSession) return "submit";
8767
8808
  if (inputs.isAuto || inputs.agentMode === "auto") return "submit";
8768
- if (inputs.agentMode !== "discovery" && inputs.agentMode !== "help") return "submit";
8809
+ if (inputs.agentMode !== "help") return "submit";
8769
8810
  return "prefill";
8770
8811
  }
8771
8812
  function isReadOnlyMode(mode, hasExitedPlanMode) {
@@ -9243,108 +9284,6 @@ async function runWithRetry(initialQuery, context, host, options) {
9243
9284
  }
9244
9285
  }
9245
9286
 
9246
- // src/execution/exploration-tracker.ts
9247
- var FILE_REREAD_NUDGE_L1 = 3;
9248
- var FILE_REREAD_NUDGE_L2 = 5;
9249
- var SEARCH_REPEAT_NUDGE_L1 = 3;
9250
- var SEARCH_REPEAT_NUDGE_L2 = 5;
9251
- var RATIO_MIN_READS_L1 = 10;
9252
- var RATIO_THRESHOLD_L1 = 0.4;
9253
- var RATIO_MIN_READS_L2 = 15;
9254
- var RATIO_THRESHOLD_L2 = 0.3;
9255
- function normalizePath(filePath) {
9256
- let normalized = filePath.replace(/^\.\//, "");
9257
- normalized = normalized.replace(/\/+/g, "/");
9258
- return normalized;
9259
- }
9260
- var ExplorationTracker = class {
9261
- fileReadCounts = /* @__PURE__ */ new Map();
9262
- searchCounts = /* @__PURE__ */ new Map();
9263
- totalReads = 0;
9264
- highestNudgeEmitted = 0;
9265
- complexityMultiplier;
9266
- constructor(isParentTask) {
9267
- this.complexityMultiplier = isParentTask ? 1.5 : 1;
9268
- }
9269
- /** Record a file read and return a staleness signal if thresholds are crossed. */
9270
- recordFileRead(filePath) {
9271
- const normalized = normalizePath(filePath);
9272
- const count = (this.fileReadCounts.get(normalized) ?? 0) + 1;
9273
- this.fileReadCounts.set(normalized, count);
9274
- this.totalReads++;
9275
- return this.checkFileRereadSignal(normalized, count) ?? this.checkExplorationRatio();
9276
- }
9277
- /** Record a search pattern and return a staleness signal if thresholds are crossed. */
9278
- recordSearch(pattern) {
9279
- const count = (this.searchCounts.get(pattern) ?? 0) + 1;
9280
- this.searchCounts.set(pattern, count);
9281
- return this.checkSearchRepeatSignal(pattern, count);
9282
- }
9283
- scaledThreshold(base) {
9284
- return Math.ceil(base * this.complexityMultiplier);
9285
- }
9286
- checkFileRereadSignal(filePath, count) {
9287
- const l2 = this.scaledThreshold(FILE_REREAD_NUDGE_L2);
9288
- const l1 = this.scaledThreshold(FILE_REREAD_NUDGE_L1);
9289
- if (count >= l2 && this.highestNudgeEmitted < 2) {
9290
- this.highestNudgeEmitted = 2;
9291
- return {
9292
- level: 2,
9293
- message: `\u26A0\uFE0F Exploration warning: You've read "${filePath}" ${count} times, suggesting analysis paralysis. Write your plan with the context you have and call ExitPlanMode. If something specific is blocking you, describe the blocker in your plan.`
9294
- };
9295
- }
9296
- if (count >= l1 && this.highestNudgeEmitted < 1) {
9297
- this.highestNudgeEmitted = 1;
9298
- return {
9299
- level: 1,
9300
- message: `Exploration note: You've read "${filePath}" ${count} times. Consider whether you have enough context to start writing your plan. If you're still exploring, try looking at different files.`
9301
- };
9302
- }
9303
- return null;
9304
- }
9305
- checkSearchRepeatSignal(pattern, count) {
9306
- const l2 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L2);
9307
- const l1 = this.scaledThreshold(SEARCH_REPEAT_NUDGE_L1);
9308
- const displayPattern = pattern.length > 60 ? pattern.slice(0, 57) + "..." : pattern;
9309
- if (count >= l2 && this.highestNudgeEmitted < 2) {
9310
- this.highestNudgeEmitted = 2;
9311
- return {
9312
- level: 2,
9313
- message: `\u26A0\uFE0F Exploration warning: You've searched for "${displayPattern}" ${count} times. Repeated searches for the same pattern suggest you may be stuck. Proceed with the information you have.`
9314
- };
9315
- }
9316
- if (count >= l1 && this.highestNudgeEmitted < 1) {
9317
- this.highestNudgeEmitted = 1;
9318
- return {
9319
- level: 1,
9320
- message: `Exploration note: You've searched for "${displayPattern}" ${count} times. If you're not finding what you need, try a different approach or broader pattern.`
9321
- };
9322
- }
9323
- return null;
9324
- }
9325
- checkExplorationRatio() {
9326
- const uniqueReads = this.fileReadCounts.size;
9327
- const ratio = this.totalReads > 0 ? uniqueReads / this.totalReads : 1;
9328
- const minReadsL2 = this.scaledThreshold(RATIO_MIN_READS_L2);
9329
- if (this.totalReads >= minReadsL2 && ratio < RATIO_THRESHOLD_L2 && this.highestNudgeEmitted < 2) {
9330
- this.highestNudgeEmitted = 2;
9331
- return {
9332
- level: 2,
9333
- message: `\u26A0\uFE0F Exploration warning: Only ${Math.round(ratio * 100)}% of your ${this.totalReads} file reads are unique. This indicates significant re-reading. Write your plan with the context you have.`
9334
- };
9335
- }
9336
- const minReadsL1 = this.scaledThreshold(RATIO_MIN_READS_L1);
9337
- if (this.totalReads >= minReadsL1 && ratio < RATIO_THRESHOLD_L1 && this.highestNudgeEmitted < 1) {
9338
- this.highestNudgeEmitted = 1;
9339
- return {
9340
- level: 1,
9341
- message: `Exploration note: ${Math.round(ratio * 100)}% of your ${this.totalReads} file reads are unique. You're re-reading files more than exploring new ones. Consider starting your plan.`
9342
- };
9343
- }
9344
- return null;
9345
- }
9346
- };
9347
-
9348
9287
  // src/runner/query-bridge.ts
9349
9288
  var logger3 = createServiceLogger("QueryBridge");
9350
9289
  function resolveHarnessKind() {
@@ -9528,7 +9467,6 @@ var QueryBridge = class {
9528
9467
  harness: this.harness,
9529
9468
  harnessKind: this.harnessKind,
9530
9469
  setupLog: [],
9531
- explorationTracker: bridge.mode.effectiveMode === "discovery" || bridge.mode.effectiveMode === "auto" && !bridge.mode.hasExitedPlanMode ? new ExplorationTracker(bridge._isParentTask) : null,
9532
9470
  sessionIds: this.sessionIds,
9533
9471
  pendingToolOutputs: this.pendingToolOutputs,
9534
9472
  // Live getters/setters delegating to ModeController + bridge state
@@ -9648,6 +9586,10 @@ function parseUsageGauges(stdout) {
9648
9586
  // src/usage/run-probe.ts
9649
9587
  import { spawn } from "child_process";
9650
9588
  var PROBE_TIMEOUT_MS = 15e3;
9589
+ function buildProbeEnv(env = process.env) {
9590
+ const { CLAUDE_CODE_OAUTH_TOKEN: _oauth, ANTHROPIC_API_KEY: _apiKey, ...rest } = env;
9591
+ return rest;
9592
+ }
9651
9593
  function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"]) {
9652
9594
  return new Promise((resolve) => {
9653
9595
  let stdout = "";
@@ -9659,7 +9601,7 @@ function runUsageProbe(binary = resolveClaudeBinary(), args = ["-p", "/usage"])
9659
9601
  };
9660
9602
  let child;
9661
9603
  try {
9662
- child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"] });
9604
+ child = spawn(binary, args, { stdio: ["ignore", "pipe", "ignore"], env: buildProbeEnv() });
9663
9605
  } catch {
9664
9606
  finish("");
9665
9607
  return;
@@ -9701,7 +9643,10 @@ async function sampleKeyUsage(token, probe = runUsageProbe) {
9701
9643
  samples.push({ rateLimitType: "seven_day", utilization: weeklyUsage, status: "allowed" });
9702
9644
  }
9703
9645
  if (samples.length === 0) {
9704
- logger4.info("usage sample produced no gauges", { stdoutLength: stdout.length });
9646
+ logger4.info("usage sample produced no gauges", {
9647
+ stdoutLength: stdout.length,
9648
+ stdoutHead: stdout.slice(0, 200).replaceAll("\n", " ")
9649
+ });
9705
9650
  }
9706
9651
  return samples;
9707
9652
  } catch (error) {
@@ -10017,9 +9962,7 @@ function isHeavyGateActive() {
10017
9962
  }
10018
9963
 
10019
9964
  // src/runner/session-runner.ts
10020
- function isPostToChatTool(name) {
10021
- return typeof name === "string" && (name === "post_to_chat" || name.endsWith("__post_to_chat"));
10022
- }
9965
+ var AUTO_RUN_MODES = /* @__PURE__ */ new Set(["building", "auto", "review", "discovery"]);
10023
9966
  var SessionRunner = class _SessionRunner {
10024
9967
  connection;
10025
9968
  mode;
@@ -10044,13 +9987,6 @@ var SessionRunner = class _SessionRunner {
10044
9987
  queryBridge = null;
10045
9988
  inputResolver = null;
10046
9989
  pendingMessages = [];
10047
- prNudgeCount = 0;
10048
- /** Set when the agent posts a substantive message to the task chat (the
10049
- * `post_to_chat` tool) during the current turn; reset at the start of every
10050
- * query. Read by the stuck-detection: an auto agent that finished without a
10051
- * PR but DID explain itself / ask for help in chat is waiting on a human,
10052
- * not silently stuck, so it is left attached rather than nudged. */
10053
- agentSpokeThisTurn = false;
10054
9990
  /** Guards overlapping runs of the periodic git flush. */
10055
9991
  periodicFlushInFlight = false;
10056
9992
  /** Consecutive flush ticks skipped because a heavy gate was running. */
@@ -10240,9 +10176,6 @@ var SessionRunner = class _SessionRunner {
10240
10176
  );
10241
10177
  this.queryBridge.isDiscoveryCompleted = false;
10242
10178
  }
10243
- if (!this.stopped && this.pendingMessages.length === 0) {
10244
- await this.maybeHandleStuckAuto();
10245
- }
10246
10179
  if (!this.stopped) {
10247
10180
  process.stderr.write(
10248
10181
  `[conveyor-agent] Listening for messages (mode: ${this.mode.effectiveMode})
@@ -10318,9 +10251,6 @@ var SessionRunner = class _SessionRunner {
10318
10251
  this.interrupted = false;
10319
10252
  continue;
10320
10253
  }
10321
- if (!this.stopped && this.pendingMessages.length === 0) {
10322
- await this.maybeHandleStuckAuto();
10323
- }
10324
10254
  if (!this.stopped) await this.setState("idle");
10325
10255
  } else if (this._state === "error") {
10326
10256
  await this.setState("idle");
@@ -10376,7 +10306,7 @@ var SessionRunner = class _SessionRunner {
10376
10306
  return true;
10377
10307
  }
10378
10308
  const delivery = this.pendingMessages.length > 0 ? "submit" : intrinsicDelivery;
10379
- const shouldRun = effectiveMode === "building" || effectiveMode === "auto" || effectiveMode === "review" || delivery === "prefill";
10309
+ const shouldRun = AUTO_RUN_MODES.has(effectiveMode) || delivery === "prefill";
10380
10310
  if (!shouldRun) {
10381
10311
  await this.setState("idle");
10382
10312
  return false;
@@ -10511,7 +10441,6 @@ var SessionRunner = class _SessionRunner {
10511
10441
  /** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
10512
10442
  async executeQuery(followUpContent, promptDelivery) {
10513
10443
  if (!this.fullContext || !this.queryBridge) return;
10514
- this.agentSpokeThisTurn = false;
10515
10444
  try {
10516
10445
  await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
10517
10446
  } catch (err) {
@@ -10533,7 +10462,6 @@ var SessionRunner = class _SessionRunner {
10533
10462
  /** Run queryBridge.executePassive, swallowing abort errors from stop/softStop. */
10534
10463
  async executePassive() {
10535
10464
  if (!this.fullContext || !this.queryBridge) return;
10536
- this.agentSpokeThisTurn = false;
10537
10465
  try {
10538
10466
  await this.queryBridge.executePassive(this.fullContext);
10539
10467
  } catch (err) {
@@ -10675,119 +10603,6 @@ var SessionRunner = class _SessionRunner {
10675
10603
  resolver(null);
10676
10604
  }
10677
10605
  }
10678
- // ── Auto-mode stuck detection & nudge ───────────────────────────────
10679
- static MAX_STUCK_NUDGES = 3;
10680
- /** The build-phase nudge (In Progress, no PR). Offers BOTH escape routes so
10681
- * the agent never just goes idle. */
10682
- static STUCK_PROMPT = "You are in Auto mode, your task is still In Progress, and there is no open pull request. Do ONE of these right now: (1) open a pull request with the create_pull_request tool, OR (2) call post_to_chat to explain exactly where you are stuck and what you need from a human. Do not finish or go idle silently.";
10683
- /** The pre-build nudge (still Planning/Open, identification/plan unfinished).
10684
- * Same two-escape-route shape, aimed at landing the plan + continuing to build. */
10685
- static STUCK_PROMPT_PLANNING = "You are in Auto mode and the card is still in a pre-build status \u2014 identification and/or the plan are not finished. Do ONE of these right now: (1) set the story points, save the plan with the update_task_plan tool, and continue implementing, OR (2) call post_to_chat to explain exactly where you are stuck and what you need from a human. Do not finish or go idle silently.";
10686
- /**
10687
- * Classify how an auto task is "stuck" after its turn ended (call sites run
10688
- * post-turn, so the TUI is no longer working), or null if it isn't. Shared
10689
- * guards: only auto, not rate-limited, under the nudge cap, and the agent
10690
- * stayed silent this turn — if it posted to chat (explained itself / asked for
10691
- * help) it's waiting on a human, not silently stuck.
10692
- *
10693
- * - "pr": In Progress with no open PR (build finished without shipping).
10694
- * - "planning": still in a pre-build status (Planning/Open) without
10695
- * (identified + saved plan) — e.g. the server-side InProgress bump failed
10696
- * and the agent idled before documenting its plan. An agent that idles
10697
- * here would otherwise clean-exit → its session Ends → the workspace is
10698
- * reaped "agent_gone" before a plan ever landed. Nudging (and, on
10699
- * exhaustion, going dormant) keeps the session Active so the pod is never
10700
- * prematurely slept.
10701
- */
10702
- autoStuckKind() {
10703
- if (!this.mode.isAuto || this.stopped) return null;
10704
- if (this.queryBridge?.wasRateLimited) return null;
10705
- if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) return null;
10706
- if (this.agentSpokeThisTurn) return null;
10707
- if (!this.taskContext) return null;
10708
- const { status, storyPointId, plan, githubPRUrl } = this.taskContext;
10709
- if (status === "InProgress" && !githubPRUrl) return "pr";
10710
- if (PRE_BUILD_TASK_STATUSES.has(status) && !(storyPointId !== null && !!plan?.trim())) {
10711
- return "planning";
10712
- }
10713
- return null;
10714
- }
10715
- isAutoStuck() {
10716
- return this.autoStuckKind() !== null;
10717
- }
10718
- /**
10719
- * Stop driving the agent and route the core loop into dormant idle —
10720
- * connected and attachable, but NOT running (so no tokens are spent while it
10721
- * waits). A human reply (chat or TUI keystroke after the next wake) resumes
10722
- * it. This replaces the old hard `stop()`: the auto agent must never kill its
10723
- * own session, so a person can always take over.
10724
- */
10725
- enterDormantForHumanTakeover(message) {
10726
- this.connection.postChatMessage(message);
10727
- this.completedThisTurn = true;
10728
- void this.connection.sendHeartbeat();
10729
- }
10730
- async refreshTaskContext() {
10731
- try {
10732
- const ctx = await this.connection.call("getTaskContext", {
10733
- sessionId: this.sessionId,
10734
- includeHistory: false
10735
- });
10736
- this.taskContext = {
10737
- status: ctx.status,
10738
- plan: ctx.plan,
10739
- storyPointId: ctx.storyPoints === null || ctx.storyPoints === void 0 ? null : String(ctx.storyPoints),
10740
- model: ctx.model,
10741
- githubPRUrl: ctx.githubPRUrl
10742
- };
10743
- if (this.fullContext) {
10744
- this.fullContext.status = ctx.status;
10745
- this.fullContext.plan = ctx.plan;
10746
- this.fullContext.githubPRUrl = ctx.githubPRUrl;
10747
- this.fullContext.model = ctx.model;
10748
- }
10749
- } catch {
10750
- }
10751
- }
10752
- /** Prompt + chat-message label for a stuck-nudge of the given kind. */
10753
- stuckNudgeContent(kind) {
10754
- if (kind === "planning") {
10755
- return {
10756
- prompt: _SessionRunner.STUCK_PROMPT_PLANNING,
10757
- situation: "still Planning with no identified plan"
10758
- };
10759
- }
10760
- return { prompt: _SessionRunner.STUCK_PROMPT, situation: "still no PR" };
10761
- }
10762
- async maybeHandleStuckAuto() {
10763
- await this.refreshTaskContext();
10764
- if (!this.isAutoStuck()) return;
10765
- while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
10766
- this.prNudgeCount++;
10767
- if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) {
10768
- this.enterDormantForHumanTakeover(
10769
- `Auto-mode: I couldn't finish or get unstuck after ${_SessionRunner.MAX_STUCK_NUDGES} attempts. Staying connected \u2014 reply here or in the terminal and I'll keep working.`
10770
- );
10771
- return;
10772
- }
10773
- const { prompt, situation } = this.stuckNudgeContent(this.autoStuckKind());
10774
- const attempt = this.prNudgeCount;
10775
- const chatMsg = attempt === 1 ? `Auto-nudge: ${situation} \u2014 prompting the agent to finish or explain where it's stuck...` : `Auto-nudge (attempt ${attempt}/${_SessionRunner.MAX_STUCK_NUDGES}): ${situation} \u2014 prompting the agent again...`;
10776
- this.connection.postChatMessage(chatMsg);
10777
- await this.setState("running");
10778
- await this.callbacks.onEvent({ type: "pr_nudge", prompt });
10779
- if (this.interrupted || this.stopped) return;
10780
- await this.executeQuery(prompt);
10781
- if (this.interrupted || this.stopped) return;
10782
- await this.refreshTaskContext();
10783
- if (this.agentSpokeThisTurn && !this.taskContext?.githubPRUrl) {
10784
- this.enterDormantForHumanTakeover(
10785
- "Auto-mode: the agent posted an update and is waiting for input. Staying connected \u2014 reply here or in the terminal to continue."
10786
- );
10787
- return;
10788
- }
10789
- }
10790
- }
10791
10606
  // ── Context & bridge construction ────────────────────────────────
10792
10607
  // oxlint-disable-next-line complexity
10793
10608
  buildFullContext(ctx) {
@@ -10844,10 +10659,6 @@ var SessionRunner = class _SessionRunner {
10844
10659
  return this.callbacks.onStatusChange(status);
10845
10660
  },
10846
10661
  onEvent: (event) => {
10847
- const evt = event;
10848
- if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
10849
- this.agentSpokeThisTurn = true;
10850
- }
10851
10662
  if (event.type === "completed") {
10852
10663
  this.completedThisTurn = true;
10853
10664
  void this.connection.sendHeartbeat();
@@ -11197,4 +11008,4 @@ export {
11197
11008
  runStartCommand,
11198
11009
  unshallowRepo
11199
11010
  };
11200
- //# sourceMappingURL=chunk-5ELLQUP4.js.map
11011
+ //# sourceMappingURL=chunk-EBZRRNO5.js.map