@rallycry/conveyor-agent 8.8.1 → 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-EOGZOS2H.js";
15
+ } from "./chunk-TN2YYT2V.js";
16
16
 
17
17
  // src/setup/bootstrap.ts
18
18
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -7402,7 +7402,7 @@ var ExplorationTracker = class {
7402
7402
  // src/runner/query-bridge.ts
7403
7403
  var logger3 = createServiceLogger("QueryBridge");
7404
7404
  function resolveHarnessKind() {
7405
- return process.env.CONVEYOR_HARNESS === "sdk" ? "sdk" : "pty";
7405
+ return process.env.CONVEYOR_FORCE_SDK_CARDS === "1" ? "sdk" : "pty";
7406
7406
  }
7407
7407
  function buildPtyBridge(connection) {
7408
7408
  return {
@@ -7431,7 +7431,8 @@ var QueryBridge = class {
7431
7431
  runnerConfig;
7432
7432
  callbacks;
7433
7433
  harness;
7434
- /** 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). */
7435
7436
  harnessKind;
7436
7437
  costTracker;
7437
7438
  planSync;
@@ -7687,6 +7688,9 @@ function handlePullBranch(workDir, branch) {
7687
7688
  }
7688
7689
 
7689
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
+ }
7690
7694
  var SessionRunner = class _SessionRunner {
7691
7695
  connection;
7692
7696
  mode;
@@ -7712,6 +7716,12 @@ var SessionRunner = class _SessionRunner {
7712
7716
  inputResolver = null;
7713
7717
  pendingMessages = [];
7714
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;
7715
7725
  /** Guards overlapping runs of the periodic git flush. */
7716
7726
  periodicFlushInFlight = false;
7717
7727
  constructor(config, callbacks) {
@@ -7852,7 +7862,7 @@ var SessionRunner = class _SessionRunner {
7852
7862
  this.queryBridge.isDiscoveryCompleted = false;
7853
7863
  }
7854
7864
  if (!this.stopped && this.pendingMessages.length === 0) {
7855
- await this.maybeSendPRNudge();
7865
+ await this.maybeHandleStuckAuto();
7856
7866
  }
7857
7867
  if (!this.stopped) {
7858
7868
  process.stderr.write(
@@ -7925,7 +7935,7 @@ var SessionRunner = class _SessionRunner {
7925
7935
  continue;
7926
7936
  }
7927
7937
  if (!this.stopped && this.pendingMessages.length === 0) {
7928
- await this.maybeSendPRNudge();
7938
+ await this.maybeHandleStuckAuto();
7929
7939
  }
7930
7940
  if (!this.stopped) await this.setState("idle");
7931
7941
  } else if (this._state === "error") {
@@ -8116,6 +8126,7 @@ var SessionRunner = class _SessionRunner {
8116
8126
  /** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
8117
8127
  async executeQuery(followUpContent, promptDelivery) {
8118
8128
  if (!this.fullContext || !this.queryBridge) return;
8129
+ this.agentSpokeThisTurn = false;
8119
8130
  try {
8120
8131
  await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
8121
8132
  } catch (err) {
@@ -8209,15 +8220,38 @@ var SessionRunner = class _SessionRunner {
8209
8220
  resolver(null);
8210
8221
  }
8211
8222
  }
8212
- // ── Auto-mode PR nudge ──────────────────────────────────────────────
8213
- static MAX_PR_NUDGES = 3;
8214
- 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() {
8215
8236
  if (!this.mode.isAuto || this.stopped) return false;
8216
8237
  if (this.queryBridge?.wasRateLimited) return false;
8217
- 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;
8218
8240
  if (!this.taskContext) return false;
8219
8241
  return this.taskContext.status === "InProgress" && !this.taskContext.githubPRUrl;
8220
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
+ }
8221
8255
  async refreshTaskContext() {
8222
8256
  try {
8223
8257
  const ctx = await this.connection.call("getTaskContext", {
@@ -8240,28 +8274,32 @@ var SessionRunner = class _SessionRunner {
8240
8274
  } catch {
8241
8275
  }
8242
8276
  }
8243
- async maybeSendPRNudge() {
8277
+ async maybeHandleStuckAuto() {
8244
8278
  await this.refreshTaskContext();
8245
- if (!this.needsPRNudge()) return;
8246
- while (!this.stopped && !this.interrupted && this.needsPRNudge()) {
8279
+ if (!this.isAutoStuck()) return;
8280
+ while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
8247
8281
  this.prNudgeCount++;
8248
- if (this.prNudgeCount > _SessionRunner.MAX_PR_NUDGES) {
8249
- this.connection.postChatMessage(
8250
- `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.`
8251
8285
  );
8252
- this.stop();
8253
8286
  return;
8254
8287
  }
8255
- const isFirst = this.prNudgeCount === 1;
8256
- 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...`;
8257
- 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...`;
8258
8290
  this.connection.postChatMessage(chatMsg);
8259
8291
  await this.setState("running");
8260
- await this.callbacks.onEvent({ type: "pr_nudge", prompt });
8292
+ await this.callbacks.onEvent({ type: "pr_nudge", prompt: _SessionRunner.STUCK_PROMPT });
8261
8293
  if (this.interrupted || this.stopped) return;
8262
- await this.executeQuery(prompt);
8294
+ await this.executeQuery(_SessionRunner.STUCK_PROMPT);
8263
8295
  if (this.interrupted || this.stopped) return;
8264
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
+ }
8265
8303
  }
8266
8304
  }
8267
8305
  // ── Context & bridge construction ────────────────────────────────
@@ -8324,6 +8362,10 @@ var SessionRunner = class _SessionRunner {
8324
8362
  return this.callbacks.onStatusChange(status);
8325
8363
  },
8326
8364
  onEvent: (event) => {
8365
+ const evt = event;
8366
+ if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
8367
+ this.agentSpokeThisTurn = true;
8368
+ }
8327
8369
  if (event.type === "completed") {
8328
8370
  this.completedThisTurn = true;
8329
8371
  void this.connection.sendHeartbeat();
@@ -10582,7 +10624,7 @@ var ProjectRunner = class {
10582
10624
  async handleAuditTags(request) {
10583
10625
  this.connection.emitStatus("busy");
10584
10626
  try {
10585
- const { handleTagAudit } = await import("./tag-audit-handler-S4VT47XG.js");
10627
+ const { handleTagAudit } = await import("./tag-audit-handler-X3LEX63H.js");
10586
10628
  await handleTagAudit(request, this.connection, this.projectDir);
10587
10629
  } catch (error) {
10588
10630
  const msg = parseErrorMessage(error);
@@ -10605,7 +10647,7 @@ var ProjectRunner = class {
10605
10647
  async handleAuditTasks(request) {
10606
10648
  this.connection.emitStatus("busy");
10607
10649
  try {
10608
- const { handleTaskAudit } = await import("./task-audit-handler-QK7S4LWO.js");
10650
+ const { handleTaskAudit } = await import("./task-audit-handler-BRCXB5V6.js");
10609
10651
  await handleTaskAudit(request, this.connection, this.projectDir);
10610
10652
  } catch (error) {
10611
10653
  const msg = parseErrorMessage(error);
@@ -10746,4 +10788,4 @@ export {
10746
10788
  loadConveyorConfig,
10747
10789
  unshallowRepo
10748
10790
  };
10749
- //# sourceMappingURL=chunk-4ZZXIHJV.js.map
10791
+ //# sourceMappingURL=chunk-6462GDLZ.js.map