@rallycry/conveyor-agent 8.8.1 → 8.10.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;
@@ -1878,6 +1878,10 @@ var SearchProjectTasksRequestSchema = z2.object({
1878
1878
  tagNames: z2.array(z2.string()).optional(),
1879
1879
  searchQuery: z2.string().optional(),
1880
1880
  statusFilters: z2.array(z2.string()).optional(),
1881
+ // Card types to include. Omitted/empty → defaults to ["task"] in the handler so
1882
+ // search doesn't surface incidents/suggestions unless asked. Enum validation lives
1883
+ // at the MCP tool layer (mirrors statusFilters).
1884
+ typeFilters: z2.array(z2.string()).optional(),
1881
1885
  assigneeId: z2.string().optional(),
1882
1886
  unassigned: z2.boolean().optional(),
1883
1887
  limit: z2.number().int().positive().optional().default(20)
@@ -7402,7 +7406,7 @@ var ExplorationTracker = class {
7402
7406
  // src/runner/query-bridge.ts
7403
7407
  var logger3 = createServiceLogger("QueryBridge");
7404
7408
  function resolveHarnessKind() {
7405
- return process.env.CONVEYOR_HARNESS === "sdk" ? "sdk" : "pty";
7409
+ return process.env.CONVEYOR_FORCE_SDK_CARDS === "1" ? "sdk" : "pty";
7406
7410
  }
7407
7411
  function buildPtyBridge(connection) {
7408
7412
  return {
@@ -7431,7 +7435,8 @@ var QueryBridge = class {
7431
7435
  runnerConfig;
7432
7436
  callbacks;
7433
7437
  harness;
7434
- /** Which harness drives this bridge ("pty" task chat vs "sdk" rollback). */
7438
+ /** Which harness drives this bridge ("pty" task chat; "sdk" only under the
7439
+ * CONVEYOR_FORCE_SDK_CARDS maintainer override). */
7435
7440
  harnessKind;
7436
7441
  costTracker;
7437
7442
  planSync;
@@ -7687,6 +7692,9 @@ function handlePullBranch(workDir, branch) {
7687
7692
  }
7688
7693
 
7689
7694
  // src/runner/session-runner.ts
7695
+ function isPostToChatTool(name) {
7696
+ return typeof name === "string" && (name === "post_to_chat" || name.endsWith("__post_to_chat"));
7697
+ }
7690
7698
  var SessionRunner = class _SessionRunner {
7691
7699
  connection;
7692
7700
  mode;
@@ -7712,6 +7720,12 @@ var SessionRunner = class _SessionRunner {
7712
7720
  inputResolver = null;
7713
7721
  pendingMessages = [];
7714
7722
  prNudgeCount = 0;
7723
+ /** Set when the agent posts a substantive message to the task chat (the
7724
+ * `post_to_chat` tool) during the current turn; reset at the start of every
7725
+ * query. Read by the stuck-detection: an auto agent that finished without a
7726
+ * PR but DID explain itself / ask for help in chat is waiting on a human,
7727
+ * not silently stuck, so it is left attached rather than nudged. */
7728
+ agentSpokeThisTurn = false;
7715
7729
  /** Guards overlapping runs of the periodic git flush. */
7716
7730
  periodicFlushInFlight = false;
7717
7731
  constructor(config, callbacks) {
@@ -7852,7 +7866,7 @@ var SessionRunner = class _SessionRunner {
7852
7866
  this.queryBridge.isDiscoveryCompleted = false;
7853
7867
  }
7854
7868
  if (!this.stopped && this.pendingMessages.length === 0) {
7855
- await this.maybeSendPRNudge();
7869
+ await this.maybeHandleStuckAuto();
7856
7870
  }
7857
7871
  if (!this.stopped) {
7858
7872
  process.stderr.write(
@@ -7925,7 +7939,7 @@ var SessionRunner = class _SessionRunner {
7925
7939
  continue;
7926
7940
  }
7927
7941
  if (!this.stopped && this.pendingMessages.length === 0) {
7928
- await this.maybeSendPRNudge();
7942
+ await this.maybeHandleStuckAuto();
7929
7943
  }
7930
7944
  if (!this.stopped) await this.setState("idle");
7931
7945
  } else if (this._state === "error") {
@@ -8116,6 +8130,7 @@ var SessionRunner = class _SessionRunner {
8116
8130
  /** Run queryBridge.execute, swallowing abort errors from stop/softStop. */
8117
8131
  async executeQuery(followUpContent, promptDelivery) {
8118
8132
  if (!this.fullContext || !this.queryBridge) return;
8133
+ this.agentSpokeThisTurn = false;
8119
8134
  try {
8120
8135
  await this.queryBridge.execute(this.fullContext, followUpContent, promptDelivery);
8121
8136
  } catch (err) {
@@ -8209,15 +8224,38 @@ var SessionRunner = class _SessionRunner {
8209
8224
  resolver(null);
8210
8225
  }
8211
8226
  }
8212
- // ── Auto-mode PR nudge ──────────────────────────────────────────────
8213
- static MAX_PR_NUDGES = 3;
8214
- needsPRNudge() {
8227
+ // ── Auto-mode stuck detection & nudge ───────────────────────────────
8228
+ static MAX_STUCK_NUDGES = 3;
8229
+ /** The prompt delivered into the live TUI when an auto agent looks stuck. It
8230
+ * must offer BOTH escape routes so the agent never just goes idle. */
8231
+ 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.";
8232
+ /**
8233
+ * An auto task is "stuck" when its last turn ended (the call sites run
8234
+ * post-turn, so the TUI is no longer actively working), it has no open PR,
8235
+ * AND the agent did not communicate in chat this turn. If the agent posted a
8236
+ * message (explained itself / asked for help) it is waiting on a human, not
8237
+ * silently stuck, so we leave it attached instead of nudging.
8238
+ */
8239
+ isAutoStuck() {
8215
8240
  if (!this.mode.isAuto || this.stopped) return false;
8216
8241
  if (this.queryBridge?.wasRateLimited) return false;
8217
- if (this.prNudgeCount > _SessionRunner.MAX_PR_NUDGES) return false;
8242
+ if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) return false;
8243
+ if (this.agentSpokeThisTurn) return false;
8218
8244
  if (!this.taskContext) return false;
8219
8245
  return this.taskContext.status === "InProgress" && !this.taskContext.githubPRUrl;
8220
8246
  }
8247
+ /**
8248
+ * Stop driving the agent and route the core loop into dormant idle —
8249
+ * connected and attachable, but NOT running (so no tokens are spent while it
8250
+ * waits). A human reply (chat or TUI keystroke after the next wake) resumes
8251
+ * it. This replaces the old hard `stop()`: the auto agent must never kill its
8252
+ * own session, so a person can always take over.
8253
+ */
8254
+ enterDormantForHumanTakeover(message) {
8255
+ this.connection.postChatMessage(message);
8256
+ this.completedThisTurn = true;
8257
+ void this.connection.sendHeartbeat();
8258
+ }
8221
8259
  async refreshTaskContext() {
8222
8260
  try {
8223
8261
  const ctx = await this.connection.call("getTaskContext", {
@@ -8240,28 +8278,32 @@ var SessionRunner = class _SessionRunner {
8240
8278
  } catch {
8241
8279
  }
8242
8280
  }
8243
- async maybeSendPRNudge() {
8281
+ async maybeHandleStuckAuto() {
8244
8282
  await this.refreshTaskContext();
8245
- if (!this.needsPRNudge()) return;
8246
- while (!this.stopped && !this.interrupted && this.needsPRNudge()) {
8283
+ if (!this.isAutoStuck()) return;
8284
+ while (!this.stopped && !this.interrupted && this.isAutoStuck()) {
8247
8285
  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.`
8286
+ if (this.prNudgeCount > _SessionRunner.MAX_STUCK_NUDGES) {
8287
+ this.enterDormantForHumanTakeover(
8288
+ `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
8289
  );
8252
- this.stop();
8253
8290
  return;
8254
8291
  }
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.`;
8292
+ const attempt = this.prNudgeCount;
8293
+ 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
8294
  this.connection.postChatMessage(chatMsg);
8259
8295
  await this.setState("running");
8260
- await this.callbacks.onEvent({ type: "pr_nudge", prompt });
8296
+ await this.callbacks.onEvent({ type: "pr_nudge", prompt: _SessionRunner.STUCK_PROMPT });
8261
8297
  if (this.interrupted || this.stopped) return;
8262
- await this.executeQuery(prompt);
8298
+ await this.executeQuery(_SessionRunner.STUCK_PROMPT);
8263
8299
  if (this.interrupted || this.stopped) return;
8264
8300
  await this.refreshTaskContext();
8301
+ if (this.agentSpokeThisTurn && !this.taskContext?.githubPRUrl) {
8302
+ this.enterDormantForHumanTakeover(
8303
+ "Auto-mode: the agent posted an update and is waiting for input. Staying connected \u2014 reply here or in the terminal to continue."
8304
+ );
8305
+ return;
8306
+ }
8265
8307
  }
8266
8308
  }
8267
8309
  // ── Context & bridge construction ────────────────────────────────
@@ -8324,6 +8366,10 @@ var SessionRunner = class _SessionRunner {
8324
8366
  return this.callbacks.onStatusChange(status);
8325
8367
  },
8326
8368
  onEvent: (event) => {
8369
+ const evt = event;
8370
+ if (evt.type === "tool_use" && isPostToChatTool(evt.tool)) {
8371
+ this.agentSpokeThisTurn = true;
8372
+ }
8327
8373
  if (event.type === "completed") {
8328
8374
  this.completedThisTurn = true;
8329
8375
  void this.connection.sendHeartbeat();
@@ -9394,6 +9440,7 @@ function errorMessage(error) {
9394
9440
  return error instanceof Error ? error.message : "Unknown error";
9395
9441
  }
9396
9442
  var DESCRIPTION_PREVIEW_CHARS = 300;
9443
+ var CARD_TYPE_ENUM = ["task", "incident", "suggestion"];
9397
9444
  function summarizeTask(task) {
9398
9445
  if (typeof task !== "object" || task === null) return task;
9399
9446
  const { plan, description, ...rest } = task;
@@ -9448,11 +9495,12 @@ function buildSearchTasksTool(connection) {
9448
9495
  const projectId = connection.projectId;
9449
9496
  return defineTool(
9450
9497
  "search_tasks",
9451
- "Search tasks by tags, text query, status filters, or assignment. Returns summaries \u2014 plan omitted, description truncated; use get_project_task for full details.",
9498
+ "Search cards by tags, text query, status, type, or assignment. Defaults to type=task \u2014 pass typeFilters to include incidents/suggestions. Returns summaries \u2014 plan omitted, description truncated; use get_project_task for full details.",
9452
9499
  {
9453
9500
  tagNames: z17.array(z17.string()).optional().describe("Filter by tag names"),
9454
9501
  searchQuery: z17.string().optional().describe("Text search in title/description"),
9455
9502
  statusFilters: z17.array(z17.string()).optional().describe("Filter by statuses"),
9503
+ typeFilters: z17.array(z17.enum(CARD_TYPE_ENUM)).optional().describe('Card types to include (default ["task"]). Pass e.g. ["incident"] or several.'),
9456
9504
  assigneeId: z17.string().optional().describe("Filter by assigned user ID"),
9457
9505
  unassigned: z17.boolean().optional().describe("Only return tasks with no assignee (mutually exclusive with assigneeId)"),
9458
9506
  limit: z17.number().optional().describe("Max results (default 20)")
@@ -10582,7 +10630,7 @@ var ProjectRunner = class {
10582
10630
  async handleAuditTags(request) {
10583
10631
  this.connection.emitStatus("busy");
10584
10632
  try {
10585
- const { handleTagAudit } = await import("./tag-audit-handler-S4VT47XG.js");
10633
+ const { handleTagAudit } = await import("./tag-audit-handler-X3LEX63H.js");
10586
10634
  await handleTagAudit(request, this.connection, this.projectDir);
10587
10635
  } catch (error) {
10588
10636
  const msg = parseErrorMessage(error);
@@ -10605,7 +10653,7 @@ var ProjectRunner = class {
10605
10653
  async handleAuditTasks(request) {
10606
10654
  this.connection.emitStatus("busy");
10607
10655
  try {
10608
- const { handleTaskAudit } = await import("./task-audit-handler-QK7S4LWO.js");
10656
+ const { handleTaskAudit } = await import("./task-audit-handler-BRCXB5V6.js");
10609
10657
  await handleTaskAudit(request, this.connection, this.projectDir);
10610
10658
  } catch (error) {
10611
10659
  const msg = parseErrorMessage(error);
@@ -10746,4 +10794,4 @@ export {
10746
10794
  loadConveyorConfig,
10747
10795
  unshallowRepo
10748
10796
  };
10749
- //# sourceMappingURL=chunk-4ZZXIHJV.js.map
10797
+ //# sourceMappingURL=chunk-D7JSB2IF.js.map