@rallycry/conveyor-agent 6.0.5 → 6.0.7

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.
@@ -960,6 +960,7 @@ var RECONNECT_BASE_MS = 1e3;
960
960
  var RECONNECT_MAX_MS = 3e4;
961
961
  var TunnelClient = class _TunnelClient {
962
962
  static STABLE_MS = 3e4;
963
+ static MAX_RECONNECT_ATTEMPTS = 10;
963
964
  apiUrl;
964
965
  token;
965
966
  localPort;
@@ -968,6 +969,7 @@ var TunnelClient = class _TunnelClient {
968
969
  reconnectAttempts = 0;
969
970
  reconnectTimer = null;
970
971
  connectedAt = 0;
972
+ loggedNoWebSocket = false;
971
973
  constructor(apiUrl, token, localPort) {
972
974
  this.apiUrl = apiUrl;
973
975
  this.token = token;
@@ -975,6 +977,13 @@ var TunnelClient = class _TunnelClient {
975
977
  }
976
978
  connect() {
977
979
  if (this.stopped) return;
980
+ if (typeof WebSocket === "undefined") {
981
+ if (!this.loggedNoWebSocket) {
982
+ logger2.warn("WebSocket not available in this environment, tunnel disabled");
983
+ this.loggedNoWebSocket = true;
984
+ }
985
+ return;
986
+ }
978
987
  const wsUrl = this.apiUrl.replace(/^http/, "ws").replace(/\/$/, "");
979
988
  const url = `${wsUrl}/api/tunnel?token=${encodeURIComponent(this.token)}`;
980
989
  try {
@@ -990,7 +999,8 @@ var TunnelClient = class _TunnelClient {
990
999
  logger2.info("Tunnel connected", { port: this.localPort });
991
1000
  });
992
1001
  this.ws.addEventListener("close", (event) => {
993
- logger2.info("Tunnel disconnected", { code: event.code, reason: event.reason });
1002
+ const closeEvent = event;
1003
+ logger2.info("Tunnel disconnected", { code: closeEvent.code, reason: closeEvent.reason });
994
1004
  this.scheduleReconnect();
995
1005
  });
996
1006
  this.ws.addEventListener("error", (event) => {
@@ -998,7 +1008,8 @@ var TunnelClient = class _TunnelClient {
998
1008
  logger2.warn("Tunnel error", { error: msg });
999
1009
  });
1000
1010
  this.ws.addEventListener("message", (event) => {
1001
- this.handleMessage(event.data);
1011
+ const messageEvent = event;
1012
+ this.handleMessage(messageEvent.data);
1002
1013
  });
1003
1014
  }
1004
1015
  disconnect() {
@@ -1014,12 +1025,18 @@ var TunnelClient = class _TunnelClient {
1014
1025
  }
1015
1026
  scheduleReconnect() {
1016
1027
  if (this.stopped) return;
1017
- if (Date.now() - this.connectedAt >= _TunnelClient.STABLE_MS) {
1028
+ if (this.connectedAt > 0 && Date.now() - this.connectedAt >= _TunnelClient.STABLE_MS) {
1018
1029
  this.reconnectAttempts = 0;
1019
1030
  }
1031
+ if (this.reconnectAttempts >= _TunnelClient.MAX_RECONNECT_ATTEMPTS) {
1032
+ logger2.warn("Tunnel gave up after max reconnect attempts", {
1033
+ attempts: this.reconnectAttempts
1034
+ });
1035
+ return;
1036
+ }
1020
1037
  const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_MS);
1021
1038
  this.reconnectAttempts++;
1022
- logger2.info("Tunnel reconnecting", { delay, attempt: this.reconnectAttempts });
1039
+ logger2.debug("Tunnel reconnecting", { delay, attempt: this.reconnectAttempts });
1023
1040
  this.reconnectTimer = setTimeout(() => this.connect(), delay);
1024
1041
  }
1025
1042
  // ---------------------------------------------------------------------------
@@ -1141,7 +1158,8 @@ var TunnelClient = class _TunnelClient {
1141
1158
  this.send(JSON.stringify({ type: "ws-open", id }));
1142
1159
  });
1143
1160
  localWs.addEventListener("message", (event) => {
1144
- const data = typeof event.data === "string" ? event.data : String(event.data);
1161
+ const messageEvent = event;
1162
+ const data = typeof messageEvent.data === "string" ? messageEvent.data : String(messageEvent.data);
1145
1163
  this.send(JSON.stringify({ type: "ws-data", id, data }));
1146
1164
  });
1147
1165
  localWs.addEventListener("close", () => {
@@ -1228,10 +1246,6 @@ async function processAssistantEvent(event, host, turnToolCalls) {
1228
1246
  if (turnTextParts.length > 0) {
1229
1247
  host.connection.postChatMessage(turnTextParts.join("\n\n"));
1230
1248
  }
1231
- if (turnToolCalls.length > 0) {
1232
- host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
1233
- turnToolCalls.length = 0;
1234
- }
1235
1249
  }
1236
1250
  var API_ERROR_PATTERN = /API Error: [45]\d\d/;
1237
1251
  var IMAGE_ERROR_PATTERN = /Could not process image/i;
@@ -1473,6 +1487,17 @@ var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
1473
1487
  function stopTypingIfNeeded(host, isTyping) {
1474
1488
  if (isTyping) host.connection.sendTypingStop();
1475
1489
  }
1490
+ function flushPendingToolCalls(host, turnToolCalls) {
1491
+ if (turnToolCalls.length === 0) return;
1492
+ for (let i = 0; i < turnToolCalls.length; i++) {
1493
+ if (i < host.pendingToolOutputs.length) {
1494
+ turnToolCalls[i].output = host.pendingToolOutputs[i];
1495
+ }
1496
+ }
1497
+ host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
1498
+ turnToolCalls.length = 0;
1499
+ host.pendingToolOutputs.length = 0;
1500
+ }
1476
1501
  async function processSystemCase(event, host, context, state) {
1477
1502
  if (event.subtype === "init") {
1478
1503
  const stored = await handleSystemEvent(event, host, context, state.sessionIdStored);
@@ -1509,6 +1534,7 @@ async function processResultCase(event, host, context, startTime, state) {
1509
1534
  );
1510
1535
  if (info.stoppedTyping) state.isTyping = false;
1511
1536
  state.retriable = info.retriable;
1537
+ if (!info.retriable) state.sawApiError = false;
1512
1538
  state.resultSummary = info.resultSummary;
1513
1539
  if (info.staleSession) state.staleSession = true;
1514
1540
  if (info.authError) state.authError = true;
@@ -1531,6 +1557,7 @@ async function processEvents(events, context, host) {
1531
1557
  };
1532
1558
  for await (const event of events) {
1533
1559
  if (host.isStopped()) break;
1560
+ flushPendingToolCalls(host, state.turnToolCalls);
1534
1561
  const now = Date.now();
1535
1562
  if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS) {
1536
1563
  host.connection.emitStatus("running");
@@ -1560,6 +1587,7 @@ async function processEvents(events, context, host) {
1560
1587
  break;
1561
1588
  }
1562
1589
  }
1590
+ flushPendingToolCalls(host, state.turnToolCalls);
1563
1591
  stopTypingIfNeeded(host, state.isTyping);
1564
1592
  return {
1565
1593
  retriable: state.retriable || state.sawApiError,
@@ -1610,6 +1638,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1610
1638
  `- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
1611
1639
  `- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
1612
1640
  `- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
1641
+ `- "Hold" \u2014 PR exists but is on hold for team review. Do not merge \u2014 skip and move on.`,
1613
1642
  `- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
1614
1643
  `- "Complete" \u2014 Fully done. Move on.`,
1615
1644
  ``,
@@ -1626,6 +1655,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1626
1655
  ` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
1627
1656
  ` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
1628
1657
  ` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
1658
+ ` - "Hold": On hold \u2014 team must review before merge. Skip.`,
1629
1659
  ` - "ReviewDev" / "Complete": Already done. Skip.`,
1630
1660
  ` - "Planning": Not ready. If this is blocking progress, notify the team.`,
1631
1661
  ``,
@@ -1633,7 +1663,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1633
1663
  ``,
1634
1664
  `4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
1635
1665
  ``,
1636
- `5. When ALL children are in "ReviewDev" or "Complete" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
1666
+ `5. When ALL children are in "ReviewDev", "Complete", or "Hold" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
1637
1667
  ``,
1638
1668
  `## Important Rules`,
1639
1669
  `- Process children ONE at a time, in ordinal order.`,
@@ -2615,7 +2645,7 @@ function buildForceUpdateTaskStatusTool(connection) {
2615
2645
  "force_update_task_status",
2616
2646
  "EMERGENCY ONLY: Force-override a task's Kanban status. Status transitions happen automatically (building sets InProgress, PR creation sets ReviewPR, merge sets ReviewDev). Only use this if an automatic transition failed or a task is stuck in the wrong status. Omit task_id to update the current task, or provide a child task ID.",
2617
2647
  {
2618
- status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2648
+ status: z.enum(["InProgress", "ReviewPR", "Hold", "ReviewDev", "Complete"]).describe("The new status for the task"),
2619
2649
  task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
2620
2650
  },
2621
2651
  async ({ status, task_id }) => {
@@ -3399,7 +3429,8 @@ async function injectTelemetry(cdpClient) {
3399
3429
  return { success: false, error: result.value };
3400
3430
  }
3401
3431
  try {
3402
- const parsed = JSON.parse(result.value);
3432
+ let parsed = JSON.parse(result.value);
3433
+ if (typeof parsed === "string") parsed = JSON.parse(parsed);
3403
3434
  return {
3404
3435
  success: parsed.success === true,
3405
3436
  patches: parsed.patches
@@ -4569,6 +4600,7 @@ function buildHooks(host) {
4569
4600
  output,
4570
4601
  isError: false
4571
4602
  });
4603
+ host.pendingToolOutputs.push(output);
4572
4604
  }
4573
4605
  return await Promise.resolve({ continue: true });
4574
4606
  }
@@ -4930,7 +4962,6 @@ var CostTracker = class {
4930
4962
 
4931
4963
  // src/runner/plan-sync.ts
4932
4964
  import { readdirSync, statSync, readFileSync } from "fs";
4933
- import { homedir } from "os";
4934
4965
  import { join as join3 } from "path";
4935
4966
  var PlanSync = class {
4936
4967
  planFileSnapshot = /* @__PURE__ */ new Map();
@@ -4945,7 +4976,7 @@ var PlanSync = class {
4945
4976
  this.workspaceDir = workspaceDir;
4946
4977
  }
4947
4978
  getPlanDirs() {
4948
- return [join3(homedir(), ".claude", "plans"), join3(this.workspaceDir, ".claude", "plans")];
4979
+ return [join3(this.workspaceDir, ".claude", "plans")];
4949
4980
  }
4950
4981
  snapshotPlanFiles() {
4951
4982
  this.planFileSnapshot.clear();
@@ -5300,6 +5331,7 @@ function buildQueryHost(deps) {
5300
5331
  },
5301
5332
  sessionIds: deps.sessionIds,
5302
5333
  activeQuery: null,
5334
+ pendingToolOutputs: [],
5303
5335
  isStopped: deps.isStopped,
5304
5336
  createInputStream: deps.createInputStream,
5305
5337
  snapshotPlanFiles: () => deps.planSync.snapshotPlanFiles(),
@@ -5443,6 +5475,10 @@ var AgentRunner = class {
5443
5475
  }
5444
5476
  this.taskContext._runnerSessionId = randomUUID2();
5445
5477
  if (this.taskContext.agentMode) this.agentMode = this.taskContext.agentMode;
5478
+ const pastPlanning = this.taskContext.status !== "Planning" && this.taskContext.status !== "Unidentified";
5479
+ if (this.agentMode === "auto" && pastPlanning) {
5480
+ this.hasExitedPlanMode = true;
5481
+ }
5446
5482
  this.logEffectiveSettings();
5447
5483
  if (process.env.CODESPACES === "true") unshallowRepo(this.config.workspaceDir);
5448
5484
  return true;
@@ -7308,4 +7344,4 @@ export {
7308
7344
  ProjectRunner,
7309
7345
  FileCache
7310
7346
  };
7311
- //# sourceMappingURL=chunk-PLSKXQTB.js.map
7347
+ //# sourceMappingURL=chunk-ANYHEBDY.js.map