@rallycry/conveyor-agent 6.0.4 → 6.0.6

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,
@@ -1633,7 +1661,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
1633
1661
  ``,
1634
1662
  `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
1663
  ``,
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 update_task_status("Complete").`,
1664
+ `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").`,
1637
1665
  ``,
1638
1666
  `## Important Rules`,
1639
1667
  `- Process children ONE at a time, in ordinal order.`,
@@ -2610,10 +2638,10 @@ function buildPostToChatTool(connection) {
2610
2638
  }
2611
2639
  );
2612
2640
  }
2613
- function buildUpdateTaskStatusTool(connection) {
2641
+ function buildForceUpdateTaskStatusTool(connection) {
2614
2642
  return tool(
2615
- "update_task_status",
2616
- "Update a task's status on the Kanban board. Omit task_id to update the current task, or provide a child task ID to update a child's status.",
2643
+ "force_update_task_status",
2644
+ "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
2645
  {
2618
2646
  status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
2619
2647
  task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
@@ -2822,7 +2850,6 @@ function buildCommonTools(connection, config) {
2822
2850
  return [
2823
2851
  buildReadTaskChatTool(connection),
2824
2852
  buildPostToChatTool(connection),
2825
- buildUpdateTaskStatusTool(connection),
2826
2853
  buildGetTaskPlanTool(connection, config),
2827
2854
  buildGetTaskTool(connection),
2828
2855
  buildGetTaskCliTool(connection),
@@ -4370,9 +4397,10 @@ function createConveyorMcpServer(connection, config, context, agentMode, debugMa
4370
4397
  const modeTools = getModeTools(effectiveMode, connection, config, context);
4371
4398
  const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
4372
4399
  const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
4400
+ const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
4373
4401
  return createSdkMcpServer({
4374
4402
  name: "conveyor",
4375
- tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools]
4403
+ tools: [...commonTools, ...modeTools, ...discoveryTools, ...debugTools, ...emergencyTools]
4376
4404
  });
4377
4405
  }
4378
4406
 
@@ -4569,6 +4597,7 @@ function buildHooks(host) {
4569
4597
  output,
4570
4598
  isError: false
4571
4599
  });
4600
+ host.pendingToolOutputs.push(output);
4572
4601
  }
4573
4602
  return await Promise.resolve({ continue: true });
4574
4603
  }
@@ -4578,24 +4607,6 @@ function buildHooks(host) {
4578
4607
  ]
4579
4608
  };
4580
4609
  }
4581
- function buildSandboxConfig(host) {
4582
- const apiHostname = new URL(host.config.conveyorApiUrl).hostname;
4583
- return {
4584
- enabled: true,
4585
- autoAllowBashIfSandboxed: true,
4586
- allowUnsandboxedCommands: false,
4587
- filesystem: {
4588
- allowWrite: [`${host.config.workspaceDir}/**`],
4589
- denyRead: ["/etc/shadow", "/etc/passwd", "**/.env", "**/.env.*"],
4590
- denyWrite: ["**/.env", "**/.env.*", "**/node_modules/**"]
4591
- },
4592
- network: {
4593
- allowedDomains: [apiHostname, "api.anthropic.com"],
4594
- allowManagedDomainsOnly: true,
4595
- allowLocalBinding: true
4596
- }
4597
- };
4598
- }
4599
4610
  function isReadOnlyMode(mode, hasExitedPlanMode) {
4600
4611
  return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
4601
4612
  }
@@ -4608,7 +4619,6 @@ function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
4608
4619
  function buildQueryOptions(host, context) {
4609
4620
  const settings = context.agentSettings ?? host.config.agentSettings ?? {};
4610
4621
  const mode = host.agentMode;
4611
- const isCloud = host.config.mode === "pm";
4612
4622
  const isReadOnly = isReadOnlyMode(mode, host.hasExitedPlanMode);
4613
4623
  const needsCanUseTool = isReadOnly;
4614
4624
  const systemPromptText = buildSystemPrompt(
@@ -4645,9 +4655,6 @@ function buildQueryOptions(host, context) {
4645
4655
  logger3.warn("Claude Code stderr", { data: data.trimEnd() });
4646
4656
  }
4647
4657
  };
4648
- if (isCloud && isReadOnly) {
4649
- baseOptions.sandbox = buildSandboxConfig(host);
4650
- }
4651
4658
  return baseOptions;
4652
4659
  }
4653
4660
  function buildMultimodalPrompt(textPrompt, context, skipImages = false) {
@@ -4952,7 +4959,6 @@ var CostTracker = class {
4952
4959
 
4953
4960
  // src/runner/plan-sync.ts
4954
4961
  import { readdirSync, statSync, readFileSync } from "fs";
4955
- import { homedir } from "os";
4956
4962
  import { join as join3 } from "path";
4957
4963
  var PlanSync = class {
4958
4964
  planFileSnapshot = /* @__PURE__ */ new Map();
@@ -4967,7 +4973,7 @@ var PlanSync = class {
4967
4973
  this.workspaceDir = workspaceDir;
4968
4974
  }
4969
4975
  getPlanDirs() {
4970
- return [join3(homedir(), ".claude", "plans"), join3(this.workspaceDir, ".claude", "plans")];
4976
+ return [join3(this.workspaceDir, ".claude", "plans")];
4971
4977
  }
4972
4978
  snapshotPlanFiles() {
4973
4979
  this.planFileSnapshot.clear();
@@ -5322,6 +5328,7 @@ function buildQueryHost(deps) {
5322
5328
  },
5323
5329
  sessionIds: deps.sessionIds,
5324
5330
  activeQuery: null,
5331
+ pendingToolOutputs: [],
5325
5332
  isStopped: deps.isStopped,
5326
5333
  createInputStream: deps.createInputStream,
5327
5334
  snapshotPlanFiles: () => deps.planSync.snapshotPlanFiles(),
@@ -7330,4 +7337,4 @@ export {
7330
7337
  ProjectRunner,
7331
7338
  FileCache
7332
7339
  };
7333
- //# sourceMappingURL=chunk-NKZSUGND.js.map
7340
+ //# sourceMappingURL=chunk-RHRQJO5E.js.map