@rallycry/conveyor-agent 8.4.1 → 8.5.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.
@@ -5,13 +5,14 @@ import {
5
5
  textResult
6
6
  } from "./chunk-FDWECEDJ.js";
7
7
  import {
8
+ configHomePlansDir,
8
9
  createHarness,
9
10
  createServiceLogger,
10
11
  defineTool,
11
12
  ensureClaudeCredentials,
12
13
  removeConveyorCredentials,
13
14
  sessionTranscriptPath
14
- } from "./chunk-VDH55LTT.js";
15
+ } from "./chunk-63FTZOL5.js";
15
16
 
16
17
  // src/setup/bootstrap.ts
17
18
  var BOOTSTRAP_TIMEOUT_MS = 3e4;
@@ -227,7 +228,8 @@ var AgentConnection = class _AgentConnection {
227
228
  content: msg.content,
228
229
  userId: msg.userId,
229
230
  ...msg.source && { source: msg.source },
230
- ...msg.files && { files: msg.files }
231
+ ...msg.files && { files: msg.files },
232
+ ...msg.delivery === "prefill" && { delivery: msg.delivery }
231
233
  };
232
234
  if (this.messageCallback) this.messageCallback(incoming);
233
235
  else this.earlyMessages.push(incoming);
@@ -1316,7 +1318,7 @@ var PlanSync = class {
1316
1318
  this.workspaceDir = workspaceDir;
1317
1319
  }
1318
1320
  getPlanDirs() {
1319
- return [join(this.workspaceDir, ".claude", "plans")];
1321
+ return [join(this.workspaceDir, ".claude", "plans"), configHomePlansDir()];
1320
1322
  }
1321
1323
  snapshotPlanFiles() {
1322
1324
  this.planFileSnapshot.clear();
@@ -6259,7 +6261,11 @@ async function handleExitPlanMode(host, input) {
6259
6261
  await host.connection.postChatMessageAwait(
6260
6262
  "Planning complete \u2014 awaiting team approval. Icon and agent assignment will be set automatically."
6261
6263
  );
6262
- host.requestStop();
6264
+ if (host.harnessKind === "pty") {
6265
+ setTimeout(() => host.requestStop(), 250);
6266
+ } else {
6267
+ host.requestStop();
6268
+ }
6263
6269
  return { behavior: "allow", updatedInput: input };
6264
6270
  }
6265
6271
  try {
@@ -6533,9 +6539,10 @@ function repairTornSessionFile(path2) {
6533
6539
  }
6534
6540
  function resolvePromptDelivery(inputs) {
6535
6541
  if (inputs.harnessKind !== "pty") return "submit";
6536
- if ((inputs.runnerMode ?? "task") !== "task") return "submit";
6542
+ if (inputs.runnerMode === "code-review") return "submit";
6537
6543
  if (inputs.isFollowUp || inputs.hasExistingSession) return "submit";
6538
6544
  if (inputs.isAuto || inputs.agentMode === "auto") return "submit";
6545
+ if (inputs.agentMode !== "discovery" && inputs.agentMode !== "help") return "submit";
6539
6546
  return "prefill";
6540
6547
  }
6541
6548
  function isReadOnlyMode(mode, hasExitedPlanMode) {
@@ -6572,6 +6579,14 @@ function buildQueryOptions(host, context) {
6572
6579
  permissionMode: needsCanUseTool ? "plan" : "bypassPermissions",
6573
6580
  allowDangerouslySkipPermissions: !needsCanUseTool,
6574
6581
  canUseTool: buildCanUseTool(host),
6582
+ // The spawned CLI never sees `systemPrompt` (an SDK-only option) — deliver
6583
+ // the same text via `--append-system-prompt`. PTY-only: the SDK harness
6584
+ // already receives it through systemPrompt.append.
6585
+ ...host.harnessKind === "pty" && systemPromptText ? { appendSystemPrompt: systemPromptText } : {},
6586
+ // Auto mode pre-exit: after the ExitPlanMode hook allows the call, the
6587
+ // CLI's plan dialog still renders — press Enter so the autonomous agent
6588
+ // continues building in the same session (Conveyor validated in the hook).
6589
+ planDialogAutoAccept: mode === "auto" && !host.hasExitedPlanMode,
6575
6590
  tools: { type: "preset", preset: "claude_code" },
6576
6591
  mcpServers: {
6577
6592
  conveyor: createConveyorMcpServer(host.harness, host.connection, host.config, context, mode)
@@ -6712,13 +6727,7 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
6712
6727
  };
6713
6728
  const resume = hasExistingSession ? sessionUuid : void 0;
6714
6729
  if (followUpContent) {
6715
- const prompt = await buildFollowUpPrompt(host, context, followUpContent);
6716
- const agentQuery = host.harness.executeQuery({
6717
- prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
6718
- options: { ...options },
6719
- resume
6720
- });
6721
- await trackAndRun(host, context, options, agentQuery);
6730
+ await runFollowUpQuery(host, context, options, resume, followUpContent);
6722
6731
  } else if (isDiscoveryLike && promptDelivery !== "prefill") {
6723
6732
  return;
6724
6733
  } else {
@@ -6728,6 +6737,42 @@ async function runSdkQuery(host, context, followUpContent, promptDeliveryOverrid
6728
6737
  await host.syncPlanFile();
6729
6738
  }
6730
6739
  }
6740
+ async function runFollowUpQuery(host, context, options, resume, followUpContent) {
6741
+ if (options.promptDelivery === "prefill") {
6742
+ await runPrefilledFollowUp(host, context, options, resume, followUpContent);
6743
+ return;
6744
+ }
6745
+ const prompt = await buildFollowUpPrompt(host, context, followUpContent);
6746
+ const agentQuery = host.harness.executeQuery({
6747
+ prompt: typeof prompt === "string" ? prompt : host.createInputStream(prompt),
6748
+ options: { ...options },
6749
+ resume
6750
+ });
6751
+ await trackAndRun(host, context, options, agentQuery);
6752
+ }
6753
+ async function runPrefilledFollowUp(host, context, options, resume, followUpContent) {
6754
+ const followUpText = typeof followUpContent === "string" ? followUpContent : followUpContent.filter((b) => b.type === "text").map((b) => b.text).join("\n");
6755
+ const queryOptions = { ...options };
6756
+ if (host.config.mode === "pm" && !resume) {
6757
+ const initialPrompt = await buildInitialPrompt(
6758
+ host.config.mode,
6759
+ context,
6760
+ host.isAuto,
6761
+ host.agentMode
6762
+ );
6763
+ queryOptions.appendSystemPrompt = [queryOptions.appendSystemPrompt, initialPrompt].filter(Boolean).join("\n\n").slice(0, APPEND_SYSTEM_PROMPT_MAX_CHARS);
6764
+ }
6765
+ let agentQuery = host.harness.executeQuery({
6766
+ prompt: followUpText,
6767
+ options: queryOptions,
6768
+ resume
6769
+ });
6770
+ agentQuery = notifyOnFirstEvent(agentQuery, async () => {
6771
+ host.connection.emitStatus("running");
6772
+ await host.callbacks.onStatusChange("running");
6773
+ });
6774
+ await trackAndRun(host, context, queryOptions, agentQuery);
6775
+ }
6731
6776
  async function trackAndRun(host, context, options, agentQuery) {
6732
6777
  if (host.harnessKind === "pty" && options.promptDelivery !== "prefill") {
6733
6778
  agentQuery = watchForParkedTui(agentQuery, host);
@@ -6739,6 +6784,14 @@ async function trackAndRun(host, context, options, agentQuery) {
6739
6784
  host.activeQuery = null;
6740
6785
  }
6741
6786
  }
6787
+ var APPEND_SYSTEM_PROMPT_MAX_CHARS = 96e3;
6788
+ function latestUserMessageText(context) {
6789
+ for (let i = context.chatHistory.length - 1; i >= 0; i--) {
6790
+ const msg = context.chatHistory[i];
6791
+ if (msg.role === "user" && msg.content.trim()) return msg.content.trim();
6792
+ }
6793
+ return null;
6794
+ }
6742
6795
  async function runInitialQuery(host, context, options, resume, promptDelivery) {
6743
6796
  const initialPrompt = await buildInitialPrompt(
6744
6797
  host.config.mode,
@@ -6746,10 +6799,18 @@ async function runInitialQuery(host, context, options, resume, promptDelivery) {
6746
6799
  host.isAuto,
6747
6800
  host.agentMode
6748
6801
  );
6749
- const prompt = buildMultimodalPrompt(initialPrompt, context);
6802
+ let prompt;
6803
+ const queryOptions = { ...options };
6804
+ const prefillMessage = promptDelivery === "prefill" ? latestUserMessageText(context) : null;
6805
+ if (prefillMessage) {
6806
+ prompt = prefillMessage;
6807
+ queryOptions.appendSystemPrompt = [queryOptions.appendSystemPrompt, initialPrompt].filter(Boolean).join("\n\n").slice(0, APPEND_SYSTEM_PROMPT_MAX_CHARS);
6808
+ } else {
6809
+ prompt = buildMultimodalPrompt(initialPrompt, context);
6810
+ }
6750
6811
  let agentQuery = host.harness.executeQuery({
6751
6812
  prompt: host.createInputStream(prompt),
6752
- options: { ...options },
6813
+ options: queryOptions,
6753
6814
  resume
6754
6815
  });
6755
6816
  if (promptDelivery === "prefill") {
@@ -6758,7 +6819,7 @@ async function runInitialQuery(host, context, options, resume, promptDelivery) {
6758
6819
  await host.callbacks.onStatusChange("running");
6759
6820
  });
6760
6821
  }
6761
- await trackAndRun(host, context, options, agentQuery);
6822
+ await trackAndRun(host, context, queryOptions, agentQuery);
6762
6823
  }
6763
6824
  async function buildRetryQuery(host, context, options, lastErrorWasImage) {
6764
6825
  if (lastErrorWasImage) {
@@ -7111,6 +7172,7 @@ var QueryBridge = class {
7111
7172
  runnerConfig;
7112
7173
  callbacks;
7113
7174
  harness;
7175
+ /** Which harness drives this bridge ("pty" task chat vs "sdk" rollback). */
7114
7176
  harnessKind;
7115
7177
  costTracker;
7116
7178
  planSync;
@@ -7516,10 +7578,13 @@ var SessionRunner = class _SessionRunner {
7516
7578
  this.queryBridge = this.createQueryBridge();
7517
7579
  await this.seedCostTrackerFromServer();
7518
7580
  this.logInitialization();
7519
- const staleMessageCount = this.pendingMessages.length;
7581
+ const staleBatch = [...this.pendingMessages];
7520
7582
  const didExecuteInitialQuery = await this.executeInitialMode();
7521
- if (staleMessageCount > 0 && didExecuteInitialQuery) {
7522
- this.pendingMessages.splice(0, staleMessageCount);
7583
+ if (staleBatch.length > 0 && didExecuteInitialQuery) {
7584
+ for (const stale of staleBatch) {
7585
+ const idx = this.pendingMessages.indexOf(stale);
7586
+ if (idx !== -1) this.pendingMessages.splice(idx, 1);
7587
+ }
7523
7588
  }
7524
7589
  if (this.queryBridge?.isDiscoveryCompleted) {
7525
7590
  process.stderr.write(
@@ -7567,14 +7632,18 @@ var SessionRunner = class _SessionRunner {
7567
7632
  }
7568
7633
  break;
7569
7634
  }
7570
- await this.setState("running");
7571
7635
  this.interrupted = false;
7572
7636
  await this.callbacks.onEvent({
7573
7637
  type: "user_message",
7574
7638
  content: msg.content,
7575
7639
  userId: msg.userId
7576
7640
  });
7577
- await this.executeQuery(msg.content);
7641
+ if (this.prefillEligible(msg)) {
7642
+ await this.runPrefilledMessage(msg, this.mode.effectiveMode);
7643
+ } else {
7644
+ await this.setState("running");
7645
+ await this.executeQuery(msg.content);
7646
+ }
7578
7647
  if (this.queryBridge?.isDiscoveryCompleted) {
7579
7648
  process.stderr.write(
7580
7649
  "[conveyor-agent] Discovery completed \u2014 entering dormant idle (staying connected)\n"
@@ -7647,7 +7716,13 @@ var SessionRunner = class _SessionRunner {
7647
7716
  async executeInitialMode() {
7648
7717
  if (!this.taskContext || !this.fullContext) return false;
7649
7718
  const effectiveMode = this.mode.effectiveMode;
7650
- const delivery = this.pendingMessages.length > 0 ? "submit" : this.queryBridge?.initialPromptDelivery(this.fullContext) ?? "submit";
7719
+ const intrinsicDelivery = this.queryBridge?.initialPromptDelivery(this.fullContext) ?? "submit";
7720
+ const hinted = this.prepareInitialPendingMessages(intrinsicDelivery);
7721
+ if (hinted) {
7722
+ await this.runPrefilledMessage(hinted, effectiveMode);
7723
+ return true;
7724
+ }
7725
+ const delivery = this.pendingMessages.length > 0 ? "submit" : intrinsicDelivery;
7651
7726
  const shouldRun = effectiveMode === "building" || effectiveMode === "auto" || effectiveMode === "review" || delivery === "prefill";
7652
7727
  if (!shouldRun) {
7653
7728
  await this.setState("idle");
@@ -7674,6 +7749,88 @@ var SessionRunner = class _SessionRunner {
7674
7749
  if (!this.stopped) await this.setState("idle");
7675
7750
  return true;
7676
7751
  }
7752
+ // ── Prefill-delivery helpers ───────────────────────────────────────
7753
+ /**
7754
+ * Startup-queue preamble: the card's initial message reaches a fresh pod
7755
+ * twice — inside the task context (chat history) AND as a queued pending
7756
+ * message. When the intrinsic delivery is prefill (manual-mode fresh TUI)
7757
+ * or a hinted message is queued (Refine), drop those context-duplicates so
7758
+ * they don't force submit semantics — a genuinely-new live message still
7759
+ * does. Returns the lone hinted message to park, if any.
7760
+ */
7761
+ prepareInitialPendingMessages(intrinsicDelivery) {
7762
+ const hasHintedPending = this.pendingMessages.some((m) => m.delivery === "prefill");
7763
+ if (this.pendingMessages.length > 0 && (intrinsicDelivery === "prefill" || hasHintedPending)) {
7764
+ this.foldContextDuplicatePendingMessages();
7765
+ }
7766
+ return this.takePrefillHintedMessage();
7767
+ }
7768
+ /**
7769
+ * Drop pending messages whose content is already present as a user message
7770
+ * in the task-context chat history (the card's initial message is delivered
7771
+ * both ways). Hinted (prefill) messages are kept — they are consumed by the
7772
+ * dedicated prefill path.
7773
+ */
7774
+ foldContextDuplicatePendingMessages() {
7775
+ const userContents = new Set(
7776
+ (this.fullContext?.chatHistory ?? []).filter((m) => m.role === "user" && m.content.trim()).map((m) => m.content.trim())
7777
+ );
7778
+ const kept = this.pendingMessages.filter(
7779
+ (m) => m.delivery === "prefill" || !m.content.trim() || !userContents.has(m.content.trim())
7780
+ );
7781
+ const dropped = this.pendingMessages.length - kept.length;
7782
+ if (dropped > 0) {
7783
+ this.pendingMessages.length = 0;
7784
+ this.pendingMessages.push(...kept);
7785
+ process.stderr.write(
7786
+ `[conveyor-agent] Folded ${dropped} pending message(s) already present in chat context
7787
+ `
7788
+ );
7789
+ }
7790
+ }
7791
+ /**
7792
+ * Honor a prefill hint only when it is unambiguous: PTY harness, a manual
7793
+ * (human-facing) agent mode, no other message queued behind it, and not an
7794
+ * automated-critical source. Everything else keeps submit semantics.
7795
+ */
7796
+ prefillEligible(msg) {
7797
+ if (msg.delivery !== "prefill") return false;
7798
+ if (this.pendingMessages.length > 0) return false;
7799
+ if (this.queryBridge?.harnessKind !== "pty") return false;
7800
+ if (msg.source && msg.source !== "mode_change" && msg.source !== "user") return false;
7801
+ const m = this.mode.effectiveMode;
7802
+ return m === "discovery" || m === "review" || m === "help";
7803
+ }
7804
+ /** Consume the sole pending message when it should park as a prefill. */
7805
+ takePrefillHintedMessage() {
7806
+ if (this.pendingMessages.length !== 1) return null;
7807
+ const [first] = this.pendingMessages;
7808
+ if (!first || first.delivery !== "prefill") return null;
7809
+ this.pendingMessages.length = 0;
7810
+ if (this.prefillEligible(first)) return first;
7811
+ this.pendingMessages.push(first);
7812
+ return null;
7813
+ }
7814
+ /**
7815
+ * Park a message in the Connected-TUI input (prefill) and wait for the human
7816
+ * to submit. Empty content prefills the mode's initial prompt instead (the
7817
+ * latest user chat message — e.g. a Refine wake on a dormant agent). The
7818
+ * caller restores the idle state afterwards.
7819
+ */
7820
+ async runPrefilledMessage(msg, effectiveMode) {
7821
+ await this.setState("waiting_for_input");
7822
+ await this.callbacks.onEvent({
7823
+ type: "execute_mode",
7824
+ mode: effectiveMode,
7825
+ delivery: "prefill"
7826
+ });
7827
+ this.lifecycle.startIdleTimer();
7828
+ try {
7829
+ await this.executeQuery(msg.content.trim() ? msg.content : void 0, "prefill");
7830
+ } finally {
7831
+ this.lifecycle.cancelIdleTimer();
7832
+ }
7833
+ }
7677
7834
  // ── Message waiting ────────────────────────────────────────────────
7678
7835
  waitForMessage() {
7679
7836
  if (this.pendingMessages.length > 0) {
@@ -10111,7 +10268,7 @@ var ProjectRunner = class {
10111
10268
  async handleAuditTags(request) {
10112
10269
  this.connection.emitStatus("busy");
10113
10270
  try {
10114
- const { handleTagAudit } = await import("./tag-audit-handler-SWVMCAJH.js");
10271
+ const { handleTagAudit } = await import("./tag-audit-handler-PKYLDJHH.js");
10115
10272
  await handleTagAudit(request, this.connection, this.projectDir);
10116
10273
  } catch (error) {
10117
10274
  const msg = parseErrorMessage(error);
@@ -10134,7 +10291,7 @@ var ProjectRunner = class {
10134
10291
  async handleAuditTasks(request) {
10135
10292
  this.connection.emitStatus("busy");
10136
10293
  try {
10137
- const { handleTaskAudit } = await import("./task-audit-handler-CZ2WWJFO.js");
10294
+ const { handleTaskAudit } = await import("./task-audit-handler-WINYLZMU.js");
10138
10295
  await handleTaskAudit(request, this.connection, this.projectDir);
10139
10296
  } catch (error) {
10140
10297
  const msg = parseErrorMessage(error);
@@ -10275,4 +10432,4 @@ export {
10275
10432
  loadConveyorConfig,
10276
10433
  unshallowRepo
10277
10434
  };
10278
- //# sourceMappingURL=chunk-B4QHEMMV.js.map
10435
+ //# sourceMappingURL=chunk-3T4SJEH6.js.map