@rallycry/conveyor-agent 8.8.0 → 8.9.0

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.
@@ -12,7 +12,7 @@ import {
12
12
  ensureClaudeCredentials,
13
13
  removeConveyorCredentials,
14
14
  sessionTranscriptPath
15
- } from "./chunk-6B545CHM.js";
15
+ } from "./chunk-TN2YYT2V.js";
16
16
 
17
17
  // src/setup/bootstrap.ts
18
18
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -1424,14 +1424,11 @@ var PlanSync = class {
1424
1424
  }
1425
1425
  };
1426
1426
 
1427
- // src/execution/query-executor.ts
1428
- import { createHash } from "crypto";
1429
- import { existsSync, readFileSync as readFileSync2, truncateSync } from "fs";
1430
-
1431
1427
  // ../shared/dist/index.js
1432
1428
  import { z } from "zod";
1433
1429
  import { z as z2 } from "zod";
1434
1430
  import { z as z3 } from "zod";
1431
+ var DEFAULT_SONNET_MODEL = "claude-sonnet-4-6";
1435
1432
  var MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024;
1436
1433
  var EMBED_THRESHOLD_IMAGES = 5 * 1024 * 1024;
1437
1434
  var EMBED_THRESHOLD_TEXT = 2 * 1024 * 1024;
@@ -2220,6 +2217,10 @@ var TASK_CHAT_HISTORY_LIMIT = 20;
2220
2217
  var PM_CHAT_HISTORY_LIMIT = 40;
2221
2218
  var AGENT_CHAT_HISTORY_FETCH_LIMIT = Math.max(TASK_CHAT_HISTORY_LIMIT, PM_CHAT_HISTORY_LIMIT) + 10;
2222
2219
 
2220
+ // src/execution/query-executor.ts
2221
+ import { createHash } from "crypto";
2222
+ import { existsSync, readFileSync as readFileSync2, truncateSync } from "fs";
2223
+
2223
2224
  // src/execution/pack-runner-prompt.ts
2224
2225
  function findLastAgentMessageIndex(history) {
2225
2226
  for (let i = history.length - 1; i >= 0; i--) {
@@ -7401,7 +7402,7 @@ var ExplorationTracker = class {
7401
7402
  // src/runner/query-bridge.ts
7402
7403
  var logger3 = createServiceLogger("QueryBridge");
7403
7404
  function resolveHarnessKind() {
7404
- return process.env.CONVEYOR_HARNESS === "sdk" ? "sdk" : "pty";
7405
+ return process.env.CONVEYOR_FORCE_SDK_CARDS === "1" ? "sdk" : "pty";
7405
7406
  }
7406
7407
  function buildPtyBridge(connection) {
7407
7408
  return {
@@ -7430,7 +7431,8 @@ var QueryBridge = class {
7430
7431
  runnerConfig;
7431
7432
  callbacks;
7432
7433
  harness;
7433
- /** Which harness drives this bridge ("pty" task chat vs "sdk" rollback). */
7434
+ /** Which harness drives this bridge ("pty" task chat; "sdk" only under the
7435
+ * CONVEYOR_FORCE_SDK_CARDS maintainer override). */
7434
7436
  harnessKind;
7435
7437
  costTracker;
7436
7438
  planSync;
@@ -7686,6 +7688,9 @@ function handlePullBranch(workDir, branch) {
7686
7688
  }
7687
7689
 
7688
7690
  // src/runner/session-runner.ts
7691
+ function isPostToChatTool(name) {
7692
+ return typeof name === "string" && (name === "post_to_chat" || name.endsWith("__post_to_chat"));
7693
+ }
7689
7694
  var SessionRunner = class _SessionRunner {
7690
7695
  connection;
7691
7696
  mode;
@@ -7711,6 +7716,12 @@ var SessionRunner = class _SessionRunner {
7711
7716
  inputResolver = null;
7712
7717
  pendingMessages = [];
7713
7718
  prNudgeCount = 0;
7719
+ /** Set when the agent posts a substantive message to the task chat (the
7720
+ * `post_to_chat` tool) during the current turn; reset at the start of every
7721
+ * query. Read by the stuck-detection: an auto agent that finished without a
7722
+ * PR but DID explain itself / ask for help in chat is waiting on a human,
7723
+ * not silently stuck, so it is left attached rather than nudged. */
7724
+ agentSpokeThisTurn = false;
7714
7725
  /** Guards overlapping runs of the periodic git flush. */
7715
7726
  periodicFlushInFlight = false;
7716
7727
  constructor(config, callbacks) {
@@ -7851,7 +7862,7 @@ var SessionRunner = class _SessionRunner {
7851
7862
  this.queryBridge.isDiscoveryCompleted = false;
7852
7863
  }
7853
7864
  if (!this.stopped && this.pendingMessages.length === 0) {
7854
- await this.maybeSendPRNudge();
7865
+ await this.maybeHandleStuckAuto();
7855
7866
  }
7856
7867
  if (!this.stopped) {
7857
7868
  process.stderr.write(
@@ -7924,7 +7935,7 @@ var SessionRunner = class _SessionRunner {
7924
7935
  continue;
7925
7936
  }
7926
7937
  if (!this.stopped && this.pendingMessages.length === 0) {
7927
- await this.maybeSendPRNudge();
7938
+ await this.maybeHandleStuckAuto();
7928
7939
  }
7929
7940
  if (!this.stopped) await this.setState("idle");
7930
7941
  } else if (this._state === "error") {
@@ -8115,6 +8126,7 @@ var SessionRunner = class _SessionRunner {
8115
8126
  /** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
8116
8127
  async executeQuery(followUpContent, promptDelivery) {
8117
8128
  if (!this.fullContext || !this.queryBridge) return;
8129
+ this.agentSpokeThisTurn = false;
8118
8130
  try {
8119
8131
  await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
8120
8132
  } catch (err) {
@@ -8208,15 +8220,38 @@ var SessionRunner = class _SessionRunner {
8208
8220
  resolver(null);
8209
8221
  }
8210
8222
  }
8211
- // ── Auto-mode PR nudge ──────────────────────────────────────────────
8212
- static MAX_PR_NUDGES = 3;
8213
- needsPRNudge() {
8223
+ // ── Auto-mode stuck detection & nudge ───────────────────────────────
8224
+ static MAX_STUCK_NUDGES = 3;
8225
+ /** The prompt delivered into the live TUI when an auto agent looks stuck. It
8226
+ * must offer BOTH escape routes so the agent never just goes idle. */
8227
+ 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.";
8228
+ /**
8229
+ * An auto task is "stuck" when its last turn ended (the call sites run
8230
+ * post-turn, so the TUI is no longer actively working), it has no open PR,
8231
+ * AND the agent did not communicate in chat this turn. If the agent posted a
8232
+ * message (explained itself / asked for help) it is waiting on a human, not
8233
+ * silently stuck, so we leave it attached instead of nudging.
8234
+ */
8235
+ isAutoStuck() {
8214
8236
  if (!this.mode.isAuto || this.stopped) return false;
8215
8237
  if (this.queryBridge?.wasRateLimited) return false;
8216
- if (this.prNudgeCount > _SessionRunner.MAX_PR_NUDGES) return false;
8238
+ if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) return false;
8239
+ if (this.agentSpokeThisTurn) return false;
8217
8240
  if (!this.taskContext) return false;
8218
8241
  return this.taskContext.status === "InProgress" && !this.taskContext.githubPRUrl;
8219
8242
  }
8243
+ /**
8244
+ * Stop driving the agent and route the core loop into dormant idle —
8245
+ * connected and attachable, but NOT running (so no tokens are spent while it
8246
+ * waits). A human reply (chat or TUI keystroke after the next wake) resumes
8247
+ * it. This replaces the old hard `stop()`: the auto agent must never kill its
8248
+ * own session, so a person can always take over.
8249
+ */
8250
+ enterDormantForHumanTakeover(message) {
8251
+ this.connection.postChatMessage(message);
8252
+ this.completedThisTurn = true;
8253
+ void this.connection.sendHeartbeat();
8254
+ }
8220
8255
  async refreshTaskContext() {
8221
8256
  try {
8222
8257
  const ctx = await this.connection.call("getTaskContext", {
@@ -8239,28 +8274,32 @@ var SessionRunner = class _SessionRunner {
8239
8274
  } catch {
8240
8275
  }
8241
8276
  }
8242
- async maybeSendPRNudge() {
8277
+ async maybeHandleStuckAuto() {
8243
8278
  await this.refreshTaskContext();
8244
- if (!this.needsPRNudge()) return;
8245
- while (!this.stopped && !this.interrupted && this.needsPRNudge()) {
8279
+ if (!this.isAutoStuck()) return;
8280
+ while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
8246
8281
  this.prNudgeCount++;
8247
- if (this.prNudgeCount > _SessionRunner.MAX_PR_NUDGES) {
8248
- this.connection.postChatMessage(
8249
- `Auto-mode agent failed to open a PR after ${_SessionRunner.MAX_PR_NUDGES} nudges. Shutting down.`
8282
+ if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) {
8283
+ this.enterDormantForHumanTakeover(
8284
+ `Auto-mode: I couldn't open a PR or get unstuck after ${_SessionRunner.MAX_STUCK_NUDGES} attempts. Staying connected \u2014 reply here or in the terminal and I'll keep working.`
8250
8285
  );
8251
- this.stop();
8252
8286
  return;
8253
8287
  }
8254
- const isFirst = this.prNudgeCount === 1;
8255
- const chatMsg = isFirst ? "Auto-nudge: Task is still In Progress with no PR. Prompting agent to continue..." : `Auto-nudge (attempt ${this.prNudgeCount}/${_SessionRunner.MAX_PR_NUDGES}): Task still has no PR. Prompting agent again...`;
8256
- const prompt = isFirst ? "You are in Auto mode and your task is still In Progress with no pull request. You MUST create a pull request before finishing. Use the create_pull_request tool now to open a PR for your changes. If you are blocked, explain what is preventing you from creating a PR." : `This is reminder ${this.prNudgeCount} of ${_SessionRunner.MAX_PR_NUDGES}. You MUST open a pull request using create_pull_request NOW or explain what is blocking you. Do NOT go idle \u2014 either create the PR or describe the specific blocker.`;
8288
+ const attempt = this.prNudgeCount;
8289
+ const chatMsg = attempt === 1 ? "Auto-nudge: still In Progress with no PR \u2014 prompting the agent to finish or explain where it's stuck..." : `Auto-nudge (attempt ${attempt}/${_SessionRunner.MAX_STUCK_NUDGES}): still no PR \u2014 prompting the agent again...`;
8257
8290
  this.connection.postChatMessage(chatMsg);
8258
8291
  await this.setState("running");
8259
- await this.callbacks.onEvent({ type: "pr_nudge", prompt });
8292
+ await this.callbacks.onEvent({ type: "pr_nudge", prompt: _SessionRunner.STUCK_PROMPT });
8260
8293
  if (this.interrupted || this.stopped) return;
8261
- await this.executeQuery(prompt);
8294
+ await this.executeQuery(_SessionRunner.STUCK_PROMPT);
8262
8295
  if (this.interrupted || this.stopped) return;
8263
8296
  await this.refreshTaskContext();
8297
+ if (this.agentSpokeThisTurn && !this.taskContext?.githubPRUrl) {
8298
+ this.enterDormantForHumanTakeover(
8299
+ "Auto-mode: the agent posted an update and is waiting for input. Staying connected \u2014 reply here or in the terminal to continue."
8300
+ );
8301
+ return;
8302
+ }
8264
8303
  }
8265
8304
  }
8266
8305
  // ── Context & bridge construction ────────────────────────────────
@@ -8304,7 +8343,7 @@ var SessionRunner = class _SessionRunner {
8304
8343
  conveyorApiUrl: this.config.connection.apiUrl,
8305
8344
  taskToken: this.config.connection.taskToken,
8306
8345
  taskId: this.fullContext?.taskId ?? "",
8307
- model: this.fullContext?.model ?? "claude-sonnet-4-20250514",
8346
+ model: this.fullContext?.model ?? DEFAULT_SONNET_MODEL,
8308
8347
  instructions: this.fullContext?.agentInstructions ?? "",
8309
8348
  workspaceDir: this.config.workspaceDir,
8310
8349
  mode: this.config.runnerMode,
@@ -8323,6 +8362,10 @@ var SessionRunner = class _SessionRunner {
8323
8362
  return this.callbacks.onStatusChange(status);
8324
8363
  },
8325
8364
  onEvent: (event) => {
8365
+ const evt = event;
8366
+ if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
8367
+ this.agentSpokeThisTurn = true;
8368
+ }
8326
8369
  if (event.type === "completed") {
8327
8370
  this.completedThisTurn = true;
8328
8371
  void this.connection.sendHeartbeat();
@@ -9661,7 +9704,7 @@ function buildProjectTools(connection, getRequestingUserId = () => void 0) {
9661
9704
 
9662
9705
  // src/runner/project-chat-handler.ts
9663
9706
  var logger6 = createServiceLogger("ProjectChat");
9664
- var FALLBACK_MODEL = "claude-sonnet-4-20250514";
9707
+ var FALLBACK_MODEL = DEFAULT_SONNET_MODEL;
9665
9708
  function buildSystemPrompt2(projectDir, agentCtx) {
9666
9709
  const parts = [];
9667
9710
  if (agentCtx?.agentInstructions) {
@@ -10581,7 +10624,7 @@ var ProjectRunner = class {
10581
10624
  async handleAuditTags(request) {
10582
10625
  this.connection.emitStatus("busy");
10583
10626
  try {
10584
- const { handleTagAudit } = await import("./tag-audit-handler-3IFB7YDV.js");
10627
+ const { handleTagAudit } = await import("./tag-audit-handler-X3LEX63H.js");
10585
10628
  await handleTagAudit(request, this.connection, this.projectDir);
10586
10629
  } catch (error) {
10587
10630
  const msg = parseErrorMessage(error);
@@ -10604,7 +10647,7 @@ var ProjectRunner = class {
10604
10647
  async handleAuditTasks(request) {
10605
10648
  this.connection.emitStatus("busy");
10606
10649
  try {
10607
- const { handleTaskAudit } = await import("./task-audit-handler-U5Q52YT2.js");
10650
+ const { handleTaskAudit } = await import("./task-audit-handler-BRCXB5V6.js");
10608
10651
  await handleTaskAudit(request, this.connection, this.projectDir);
10609
10652
  } catch (error) {
10610
10653
  const msg = parseErrorMessage(error);
@@ -10745,4 +10788,4 @@ export {
10745
10788
  loadConveyorConfig,
10746
10789
  unshallowRepo
10747
10790
  };
10748
- //# sourceMappingURL=chunk-KEBJAORX.js.map
10791
+ //# sourceMappingURL=chunk-6462GDLZ.js.map