@rallycry/conveyor-agent 4.4.0 → 4.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.
@@ -253,6 +253,10 @@ var ConveyorConnection = class _ConveyorConnection {
253
253
  if (!this.socket) return;
254
254
  this.socket.emit("agentRunner:statusUpdate", { status });
255
255
  }
256
+ emitRateLimitPause(resetsAt) {
257
+ if (!this.socket) return;
258
+ this.socket.emit("agentRunner:rateLimitPause", { resetsAt });
259
+ }
256
260
  sendHeartbeat() {
257
261
  if (!this.socket) return;
258
262
  this.socket.emit("agentRunner:heartbeat", {});
@@ -727,16 +731,19 @@ function handleRateLimitEvent(event, host) {
727
731
  const { rate_limit_info } = event;
728
732
  const status = rate_limit_info.status;
729
733
  if (status === "rejected") {
730
- const resetsAt = rate_limit_info.resetsAt ? new Date(rate_limit_info.resetsAt).toISOString() : "unknown";
731
- const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAt})`;
734
+ const resetsAt = rate_limit_info.resetsAt ? new Date(rate_limit_info.resetsAt).toISOString() : void 0;
735
+ const resetsAtDisplay = resetsAt ?? "unknown";
736
+ const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
732
737
  host.connection.sendEvent({ type: "error", message });
733
738
  void host.callbacks.onEvent({ type: "error", message });
739
+ return resetsAt;
734
740
  } else if (status === "allowed_warning") {
735
741
  const utilization = rate_limit_info.utilization ? `${Math.round(rate_limit_info.utilization * 100)}%` : "high";
736
742
  const message = `Rate limit warning: ${utilization} utilization (type: ${rate_limit_info.rateLimitType ?? "unknown"})`;
737
743
  host.connection.sendEvent({ type: "thinking", message });
738
744
  void host.callbacks.onEvent({ type: "thinking", message });
739
745
  }
746
+ return void 0;
740
747
  }
741
748
  async function processEvents(events, context, host) {
742
749
  const startTime = Date.now();
@@ -744,6 +751,7 @@ async function processEvents(events, context, host) {
744
751
  let isTyping = false;
745
752
  let retriable = false;
746
753
  let resultSummary;
754
+ let rateLimitResetsAt;
747
755
  const turnToolCalls = [];
748
756
  for await (const event of events) {
749
757
  if (host.isStopped()) break;
@@ -793,7 +801,8 @@ async function processEvents(events, context, host) {
793
801
  break;
794
802
  }
795
803
  case "rate_limit_event": {
796
- handleRateLimitEvent(event, host);
804
+ const resetsAt = handleRateLimitEvent(event, host);
805
+ if (resetsAt) rateLimitResetsAt = resetsAt;
797
806
  break;
798
807
  }
799
808
  }
@@ -801,7 +810,7 @@ async function processEvents(events, context, host) {
801
810
  if (isTyping) {
802
811
  host.connection.sendTypingStop();
803
812
  }
804
- return { retriable, resultSummary };
813
+ return { retriable, resultSummary, rateLimitResetsAt };
805
814
  }
806
815
 
807
816
  // src/execution/query-executor.ts
@@ -974,16 +983,28 @@ function buildModePrompt(agentMode, context) {
974
983
  ## Mode: Discovery`,
975
984
  `You are in Discovery mode \u2014 helping plan and scope this task.`,
976
985
  `- You have read-only codebase access (can read files, run git commands, search code)`,
977
- `- You have MCP tools: update_task (title, description, plan, tags, SP, icon)`,
986
+ `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
978
987
  `- You can create and manage subtasks`,
979
- `- You cannot write code or edit files (except .claude/plans/)`,
980
988
  `- Goal: collaborate with the user to create a clear plan`,
981
989
  `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
982
990
  ``,
991
+ `### Self-Identification Tools`,
992
+ `Use these MCP tools to set your own task properties:`,
993
+ `- \`update_task\` \u2014 save your plan and description`,
994
+ `- \`set_story_points\` \u2014 assign story points`,
995
+ `- \`set_task_title\` \u2014 set an accurate title`,
996
+ `- \`set_task_tags\` \u2014 categorize the work`,
997
+ `- \`set_task_icon\` / \`generate_task_icon\` \u2014 set a task icon (call \`list_icons\` first)`,
998
+ ``,
983
999
  `### Self-Update vs Subtasks`,
984
1000
  `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
985
1001
  `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
986
- `- When planning for yourself: use update_task to save the plan, then set_story_points, set_task_title, set_task_icon`
1002
+ ``,
1003
+ `### Finishing Planning`,
1004
+ `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1005
+ `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via set_story_points), **title** (via set_task_title)`,
1006
+ `- ExitPlanMode validates these properties and moves the task to Open status`,
1007
+ `- It does NOT start building \u2014 the team controls when to switch to Build mode`
987
1008
  ];
988
1009
  if (context) {
989
1010
  parts.push(...buildPropertyInstructions(context));
@@ -1357,7 +1378,15 @@ ${context.plan}`);
1357
1378
  }
1358
1379
  return parts;
1359
1380
  }
1360
- function buildFreshInstructions(isPm, isAutoMode, context) {
1381
+ function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
1382
+ if (isPm && agentMode === "building") {
1383
+ return [
1384
+ `Your plan has been approved. Begin implementing it now.`,
1385
+ `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
1386
+ `Start by reading the relevant source files mentioned in the plan, then write code.`,
1387
+ `When finished, commit and push your changes, then use the create_pull_request tool to open a PR. Do NOT use gh CLI.`
1388
+ ];
1389
+ }
1361
1390
  if (isAutoMode && isPm) {
1362
1391
  return [
1363
1392
  `You are operating autonomously. Begin planning immediately.`,
@@ -1432,11 +1461,22 @@ function buildInstructions(mode, context, scenario, agentMode) {
1432
1461
  ## Instructions`];
1433
1462
  const isPm = mode === "pm";
1434
1463
  if (scenario === "fresh") {
1435
- parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context));
1464
+ parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context, agentMode));
1436
1465
  return parts;
1437
1466
  }
1438
1467
  if (scenario === "idle_relaunch") {
1439
- if (isPm) {
1468
+ if (isPm && (agentMode === "building" || agentMode === "review")) {
1469
+ parts.push(
1470
+ `You were relaunched but no new instructions have been given since your last run.`,
1471
+ `Work on the git branch "${context.githubBranch}". Stay on this branch \u2014 do not checkout or create other branches.`,
1472
+ `Run \`git log --oneline -10\` to review what you already committed, then verify the current state is correct.`,
1473
+ `Reply with a brief status update summarizing where things stand (visible in chat).`,
1474
+ `Then wait for further instructions \u2014 do NOT redo work that was already completed.`
1475
+ );
1476
+ if (context.githubPRUrl) {
1477
+ parts.push(`An existing PR is open at ${context.githubPRUrl}. Do not create a new PR.`);
1478
+ }
1479
+ } else if (isPm) {
1440
1480
  parts.push(
1441
1481
  `You were relaunched but no new instructions have been given since your last run.`,
1442
1482
  `You are the project manager for this task.`,
@@ -2130,6 +2170,12 @@ async function handleExitPlanMode(host, input) {
2130
2170
  }
2131
2171
  await host.connection.triggerIdentification();
2132
2172
  host.hasExitedPlanMode = true;
2173
+ if (host.agentMode === "discovery") {
2174
+ host.connection.postChatMessage(
2175
+ "Task identified and moved to Open. Switch to Build mode when ready to start implementation."
2176
+ );
2177
+ return { behavior: "allow", updatedInput: input };
2178
+ }
2133
2179
  const newMode = host.isParentTask ? "review" : "building";
2134
2180
  host.pendingModeRestart = true;
2135
2181
  if (host.onModeTransition) {
@@ -2169,7 +2215,7 @@ async function handleAskUserQuestion(host, input) {
2169
2215
  }
2170
2216
  function buildCanUseTool(host) {
2171
2217
  return async (toolName, input) => {
2172
- if (toolName === "ExitPlanMode" && host.agentMode === "auto" && !host.hasExitedPlanMode) {
2218
+ if (toolName === "ExitPlanMode" && (host.agentMode === "auto" || host.agentMode === "discovery") && !host.hasExitedPlanMode) {
2173
2219
  return await handleExitPlanMode(host, input);
2174
2220
  }
2175
2221
  if (toolName === "AskUserQuestion") {
@@ -2235,9 +2281,6 @@ function buildSandboxConfig(host) {
2235
2281
  }
2236
2282
  };
2237
2283
  }
2238
- function isActiveBuildMode(mode, hasExitedPlanMode) {
2239
- return mode === "building" || mode === "review" || mode === "auto" && hasExitedPlanMode;
2240
- }
2241
2284
  function isReadOnlyMode(mode, hasExitedPlanMode) {
2242
2285
  return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
2243
2286
  }
@@ -2250,7 +2293,9 @@ function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
2250
2293
  function buildQueryOptions(host, context) {
2251
2294
  const settings = context.agentSettings ?? host.config.agentSettings ?? {};
2252
2295
  const mode = host.agentMode;
2253
- const shouldSandbox = host.config.mode === "pm" && isActiveBuildMode(mode, host.hasExitedPlanMode) && context.useSandbox !== false;
2296
+ const isCloud = host.config.mode === "pm";
2297
+ const isReadOnly = isReadOnlyMode(mode, host.hasExitedPlanMode);
2298
+ const needsCanUseTool = isCloud && isReadOnly;
2254
2299
  const systemPromptText = buildSystemPrompt(
2255
2300
  host.config.mode,
2256
2301
  context,
@@ -2268,8 +2313,8 @@ function buildQueryOptions(host, context) {
2268
2313
  },
2269
2314
  settingSources,
2270
2315
  cwd: host.config.workspaceDir,
2271
- permissionMode: shouldSandbox ? "acceptEdits" : "bypassPermissions",
2272
- allowDangerouslySkipPermissions: !shouldSandbox,
2316
+ permissionMode: needsCanUseTool ? "acceptEdits" : "bypassPermissions",
2317
+ allowDangerouslySkipPermissions: !needsCanUseTool,
2273
2318
  canUseTool: buildCanUseTool(host),
2274
2319
  tools: { type: "preset", preset: "claude_code" },
2275
2320
  mcpServers: { conveyor: createConveyorMcpServer(host.connection, host.config, context) },
@@ -2282,7 +2327,7 @@ function buildQueryOptions(host, context) {
2282
2327
  disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
2283
2328
  enableFileCheckpointing: settings.enableFileCheckpointing
2284
2329
  };
2285
- if (shouldSandbox) {
2330
+ if (needsCanUseTool) {
2286
2331
  baseOptions.sandbox = buildSandboxConfig(host);
2287
2332
  }
2288
2333
  return baseOptions;
@@ -2452,12 +2497,23 @@ async function runWithRetry(initialQuery, context, host, options) {
2452
2497
  if (host.isStopped()) return;
2453
2498
  const agentQuery = attempt === 0 ? initialQuery : buildRetryQuery(host, context, options, lastErrorWasImage);
2454
2499
  try {
2455
- const { retriable, resultSummary, modeRestart } = await processEvents(
2500
+ const { retriable, resultSummary, modeRestart, rateLimitResetsAt } = await processEvents(
2456
2501
  agentQuery,
2457
2502
  context,
2458
2503
  host
2459
2504
  );
2460
- if (modeRestart || !retriable || host.isStopped()) return;
2505
+ if (modeRestart || host.isStopped()) return;
2506
+ if (rateLimitResetsAt) {
2507
+ const resetMs = new Date(rateLimitResetsAt).getTime() - Date.now();
2508
+ if (resetMs > 5 * 6e4) {
2509
+ host.connection.emitRateLimitPause(rateLimitResetsAt);
2510
+ host.connection.postChatMessage(
2511
+ `Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
2512
+ );
2513
+ return;
2514
+ }
2515
+ }
2516
+ if (!retriable) return;
2461
2517
  lastErrorWasImage = IMAGE_ERROR_PATTERN2.test(resultSummary ?? "");
2462
2518
  } catch (error) {
2463
2519
  if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
@@ -2748,13 +2804,8 @@ async function handleAutoModeRestart(state, connection, setState, runQuerySafe,
2748
2804
  const newMode = state.agentMode;
2749
2805
  const isParentTask = !!taskContext.isParentTask;
2750
2806
  const newModel = getModelForMode(taskContext, newMode, isParentTask);
2751
- const resumeSessionId = newModel ? state.sessionIds.get(newModel) : null;
2752
- if (resumeSessionId) {
2753
- taskContext.claudeSessionId = resumeSessionId;
2754
- } else {
2755
- taskContext.claudeSessionId = null;
2756
- connection.storeSessionId("");
2757
- }
2807
+ taskContext.claudeSessionId = null;
2808
+ connection.storeSessionId("");
2758
2809
  if (newModel) taskContext.model = newModel;
2759
2810
  taskContext.agentMode = newMode;
2760
2811
  connection.emitModeTransition({ fromMode: "auto", toMode: newMode ?? "building" });
@@ -3168,6 +3219,10 @@ var AgentRunner = class {
3168
3219
  this.connection.postChatMessage(
3169
3220
  `Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
3170
3221
  );
3222
+ if (effectiveMode === "building" && this.taskContext?.status === "Open") {
3223
+ this.connection.updateStatus("InProgress");
3224
+ this.taskContext.status = "InProgress";
3225
+ }
3171
3226
  }
3172
3227
  stop() {
3173
3228
  this.stopped = true;
@@ -3641,4 +3696,4 @@ export {
3641
3696
  ProjectRunner,
3642
3697
  FileCache
3643
3698
  };
3644
- //# sourceMappingURL=chunk-FS3A4THO.js.map
3699
+ //# sourceMappingURL=chunk-7Y3RP3ZA.js.map