@rallycry/conveyor-agent 4.9.3 → 4.10.1

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.
@@ -638,6 +638,12 @@ import { randomUUID as randomUUID2 } from "crypto";
638
638
  import { execSync as execSync3 } from "child_process";
639
639
 
640
640
  // src/execution/event-processor.ts
641
+ function epochSecondsToISO(value) {
642
+ if (typeof value === "string") return value;
643
+ if (typeof value !== "number" || value <= 0) return void 0;
644
+ const ms = value < 1e12 ? value * 1e3 : value;
645
+ return new Date(ms).toISOString();
646
+ }
641
647
  async function processAssistantEvent(event, host, turnToolCalls) {
642
648
  const { content } = event.message;
643
649
  const turnTextParts = [];
@@ -675,50 +681,46 @@ function isRetriableMessage(msg) {
675
681
  if (API_ERROR_PATTERN.test(msg)) return true;
676
682
  return false;
677
683
  }
678
- function handleSuccessResult(event, host, context, startTime) {
679
- const durationMs = Date.now() - startTime;
680
- const summary = event.result || "Task completed.";
681
- const retriable = isRetriableMessage(summary);
682
- const cumulativeTotal = host.costTracker.addQueryCost(event.total_cost_usd);
683
- const { modelUsage } = event;
684
- if (modelUsage && typeof modelUsage === "object") {
685
- host.costTracker.addModelUsage(modelUsage);
684
+ function aggregateModelUsage(modelUsage) {
685
+ let queryInputTokens = 0;
686
+ let contextWindow = 0;
687
+ let totalInputTokens = 0;
688
+ let totalCacheRead = 0;
689
+ let totalCacheCreation = 0;
690
+ for (const data of Object.values(modelUsage)) {
691
+ const d = data;
692
+ const input = d.inputTokens ?? 0;
693
+ const cacheRead = d.cacheReadInputTokens ?? 0;
694
+ const cacheCreation = d.cacheCreationInputTokens ?? 0;
695
+ totalInputTokens += input;
696
+ totalCacheRead += cacheRead;
697
+ totalCacheCreation += cacheCreation;
698
+ queryInputTokens += input + cacheRead + cacheCreation;
699
+ const cw = data.contextWindow ?? 0;
700
+ if (cw > contextWindow) contextWindow = cw;
701
+ }
702
+ return { queryInputTokens, contextWindow, totalInputTokens, totalCacheRead, totalCacheCreation };
703
+ }
704
+ function emitContextUpdate(modelUsage, host, context) {
705
+ const usage = aggregateModelUsage(modelUsage);
706
+ let { contextWindow } = usage;
707
+ const settings = context.agentSettings ?? host.config.agentSettings ?? {};
708
+ const has1mBeta = settings.betas?.includes("context-1m-2025-08-07");
709
+ if (has1mBeta && contextWindow > 0 && contextWindow <= 2e5) {
710
+ contextWindow = 1e6;
686
711
  }
687
- host.connection.sendEvent({ type: "completed", summary, costUsd: cumulativeTotal, durationMs });
688
- if (modelUsage && typeof modelUsage === "object") {
689
- let queryInputTokens = 0;
690
- let contextWindow = 0;
691
- let totalInputTokens = 0;
692
- let totalCacheRead = 0;
693
- let totalCacheCreation = 0;
694
- for (const data of Object.values(modelUsage)) {
695
- const d = data;
696
- const input = d.inputTokens ?? 0;
697
- const cacheRead = d.cacheReadInputTokens ?? 0;
698
- const cacheCreation = d.cacheCreationInputTokens ?? 0;
699
- totalInputTokens += input;
700
- totalCacheRead += cacheRead;
701
- totalCacheCreation += cacheCreation;
702
- queryInputTokens += input + cacheRead + cacheCreation;
703
- const cw = data.contextWindow ?? 0;
704
- if (cw > contextWindow) contextWindow = cw;
705
- }
706
- const settings = context.agentSettings ?? host.config.agentSettings ?? {};
707
- const has1mBeta = settings.betas?.includes("context-1m-2025-08-07");
708
- if (has1mBeta && contextWindow > 0 && contextWindow <= 2e5) {
709
- contextWindow = 1e6;
710
- }
711
- if (contextWindow > 0) {
712
- host.connection.sendEvent({
713
- type: "context_update",
714
- contextTokens: queryInputTokens,
715
- contextWindow,
716
- inputTokens: totalInputTokens,
717
- cacheReadInputTokens: totalCacheRead,
718
- cacheCreationInputTokens: totalCacheCreation
719
- });
720
- }
712
+ if (contextWindow > 0) {
713
+ host.connection.sendEvent({
714
+ type: "context_update",
715
+ contextTokens: usage.queryInputTokens,
716
+ contextWindow,
717
+ inputTokens: usage.totalInputTokens,
718
+ cacheReadInputTokens: usage.totalCacheRead,
719
+ cacheCreationInputTokens: usage.totalCacheCreation
720
+ });
721
721
  }
722
+ }
723
+ function trackCostSpending(host, context, cumulativeTotal) {
722
724
  if (cumulativeTotal > 0 && context.agentId && context._runnerSessionId) {
723
725
  const breakdown = host.costTracker.modelBreakdown;
724
726
  host.connection.trackSpending({
@@ -729,6 +731,21 @@ function handleSuccessResult(event, host, context, startTime) {
729
731
  modelUsage: breakdown.length > 0 ? breakdown : void 0
730
732
  });
731
733
  }
734
+ }
735
+ function handleSuccessResult(event, host, context, startTime) {
736
+ const durationMs = Date.now() - startTime;
737
+ const summary = event.result || "Task completed.";
738
+ const retriable = isRetriableMessage(summary);
739
+ const cumulativeTotal = host.costTracker.addQueryCost(event.total_cost_usd);
740
+ const { modelUsage } = event;
741
+ if (modelUsage && typeof modelUsage === "object") {
742
+ host.costTracker.addModelUsage(modelUsage);
743
+ }
744
+ host.connection.sendEvent({ type: "completed", summary, costUsd: cumulativeTotal, durationMs });
745
+ if (modelUsage && typeof modelUsage === "object") {
746
+ emitContextUpdate(modelUsage, host, context);
747
+ }
748
+ trackCostSpending(host, context, cumulativeTotal);
732
749
  return { totalCostUsd: cumulativeTotal, retriable };
733
750
  }
734
751
  function handleErrorResult(event, host) {
@@ -768,7 +785,7 @@ async function emitResultEvent(event, host, context, startTime) {
768
785
  function handleRateLimitEvent(event, host) {
769
786
  const { rate_limit_info } = event;
770
787
  const status = rate_limit_info.status;
771
- if (rate_limit_info.utilization != null && rate_limit_info.rateLimitType) {
788
+ if (rate_limit_info.utilization !== void 0 && rate_limit_info.rateLimitType) {
772
789
  host.connection.sendEvent({
773
790
  type: "rate_limit_update",
774
791
  rateLimitType: rate_limit_info.rateLimitType,
@@ -777,7 +794,7 @@ function handleRateLimitEvent(event, host) {
777
794
  });
778
795
  }
779
796
  if (status === "rejected") {
780
- const resetsAt = rate_limit_info.resetsAt ? new Date(rate_limit_info.resetsAt).toISOString() : void 0;
797
+ const resetsAt = epochSecondsToISO(rate_limit_info.resetsAt);
781
798
  const resetsAtDisplay = resetsAt ?? "unknown";
782
799
  const message = `Rate limit rejected (type: ${rate_limit_info.rateLimitType ?? "unknown"}, resets at: ${resetsAtDisplay})`;
783
800
  host.connection.sendEvent({ type: "error", message });
@@ -791,6 +808,18 @@ function handleRateLimitEvent(event, host) {
791
808
  }
792
809
  return void 0;
793
810
  }
811
+ async function handleSystemEvent(event, host, context, sessionIdStored) {
812
+ if (event.subtype !== "init") return false;
813
+ if (event.session_id && !sessionIdStored) {
814
+ host.connection.storeSessionId(event.session_id);
815
+ context.claudeSessionId = event.session_id;
816
+ }
817
+ await host.callbacks.onEvent({
818
+ type: "thinking",
819
+ message: `Agent initialized (model: ${event.model})`
820
+ });
821
+ return !!(event.session_id && !sessionIdStored);
822
+ }
794
823
  async function processEvents(events, context, host) {
795
824
  const startTime = Date.now();
796
825
  let sessionIdStored = false;
@@ -810,17 +839,8 @@ async function processEvents(events, context, host) {
810
839
  switch (event.type) {
811
840
  case "system": {
812
841
  const systemEvent = event;
813
- if (systemEvent.subtype === "init") {
814
- if (systemEvent.session_id && !sessionIdStored) {
815
- sessionIdStored = true;
816
- host.connection.storeSessionId(systemEvent.session_id);
817
- context.claudeSessionId = systemEvent.session_id;
818
- }
819
- await host.callbacks.onEvent({
820
- type: "thinking",
821
- message: `Agent initialized (model: ${systemEvent.model})`
822
- });
823
- }
842
+ const stored = await handleSystemEvent(systemEvent, host, context, sessionIdStored);
843
+ if (stored) sessionIdStored = true;
824
844
  break;
825
845
  }
826
846
  case "assistant": {
@@ -1021,42 +1041,73 @@ function buildPropertyInstructions(context) {
1021
1041
  }
1022
1042
  return parts;
1023
1043
  }
1044
+ function buildDiscoveryPrompt(context) {
1045
+ const parts = [
1046
+ `
1047
+ ## Mode: Discovery`,
1048
+ `You are in Discovery mode \u2014 helping plan and scope this task.`,
1049
+ `- You have read-only codebase access (can read files, run git commands, search code)`,
1050
+ `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1051
+ `- You can create and manage subtasks`,
1052
+ `- Goal: collaborate with the user to create a clear plan`,
1053
+ `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
1054
+ ``,
1055
+ `### Self-Identification Tools`,
1056
+ `Use these MCP tools to set your own task properties:`,
1057
+ `- \`update_task\` \u2014 save your plan and description`,
1058
+ `- \`set_story_points\` \u2014 assign story points`,
1059
+ `- \`set_task_title\` \u2014 set an accurate title`,
1060
+ `- \`set_task_tags\` \u2014 categorize the work`,
1061
+ `- \`set_task_icon\` / \`generate_task_icon\` \u2014 set a task icon (call \`list_icons\` first)`,
1062
+ ``,
1063
+ `### Self-Update vs Subtasks`,
1064
+ `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1065
+ `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
1066
+ ``,
1067
+ `### Finishing Planning`,
1068
+ `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1069
+ `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via set_story_points), **title** (via set_task_title)`,
1070
+ `- ExitPlanMode validates these properties and moves the task to Open status`,
1071
+ `- It does NOT start building \u2014 the team controls when to switch to Build mode`
1072
+ ];
1073
+ if (context) parts.push(...buildPropertyInstructions(context));
1074
+ return parts.join("\n");
1075
+ }
1076
+ function buildAutoPrompt(context) {
1077
+ const parts = [
1078
+ `
1079
+ ## Mode: Auto`,
1080
+ `You are in Auto mode \u2014 operating autonomously through planning \u2192 building \u2192 PR.`,
1081
+ ``,
1082
+ `### Phase 1: Discovery & Planning (current)`,
1083
+ `- You have read-only codebase access (can read files, run git commands, search code)`,
1084
+ `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1085
+ `- You have MCP tools for task properties: update_task, set_story_points, set_task_tags, set_task_icon, set_task_title`,
1086
+ ``,
1087
+ `### Required before transitioning:`,
1088
+ `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1089
+ `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
1090
+ `2. **Story Points** \u2014 Assign via set_story_points`,
1091
+ `3. **Title** \u2014 Set an accurate title via set_task_title (if the current one is vague or "Untitled")`,
1092
+ ``,
1093
+ `### Transitioning to Building:`,
1094
+ `When your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1095
+ `- If any required properties are missing, ExitPlanMode will be denied with details on what's missing`,
1096
+ `- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
1097
+ `- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
1098
+ ``,
1099
+ `### Autonomous Guidelines:`,
1100
+ `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1101
+ `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
1102
+ `- Be thorough in discovery: read relevant files, understand the codebase architecture, then plan`
1103
+ ];
1104
+ if (context) parts.push(...buildPropertyInstructions(context));
1105
+ return parts.join("\n");
1106
+ }
1024
1107
  function buildModePrompt(agentMode, context) {
1025
1108
  switch (agentMode) {
1026
- case "discovery": {
1027
- const parts = [
1028
- `
1029
- ## Mode: Discovery`,
1030
- `You are in Discovery mode \u2014 helping plan and scope this task.`,
1031
- `- You have read-only codebase access (can read files, run git commands, search code)`,
1032
- `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1033
- `- You can create and manage subtasks`,
1034
- `- Goal: collaborate with the user to create a clear plan`,
1035
- `- Proactively fill task properties (SP, tags, icon) as the plan takes shape`,
1036
- ``,
1037
- `### Self-Identification Tools`,
1038
- `Use these MCP tools to set your own task properties:`,
1039
- `- \`update_task\` \u2014 save your plan and description`,
1040
- `- \`set_story_points\` \u2014 assign story points`,
1041
- `- \`set_task_title\` \u2014 set an accurate title`,
1042
- `- \`set_task_tags\` \u2014 categorize the work`,
1043
- `- \`set_task_icon\` / \`generate_task_icon\` \u2014 set a task icon (call \`list_icons\` first)`,
1044
- ``,
1045
- `### Self-Update vs Subtasks`,
1046
- `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1047
- `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
1048
- ``,
1049
- `### Finishing Planning`,
1050
- `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1051
- `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via set_story_points), **title** (via set_task_title)`,
1052
- `- ExitPlanMode validates these properties and moves the task to Open status`,
1053
- `- It does NOT start building \u2014 the team controls when to switch to Build mode`
1054
- ];
1055
- if (context) {
1056
- parts.push(...buildPropertyInstructions(context));
1057
- }
1058
- return parts.join("\n");
1059
- }
1109
+ case "discovery":
1110
+ return buildDiscoveryPrompt(context);
1060
1111
  case "building":
1061
1112
  return [
1062
1113
  `
@@ -1077,39 +1128,8 @@ function buildModePrompt(agentMode, context) {
1077
1128
  `- You have Pack Runner coordination tools (list_subtasks, fire builds, approve PRs)`,
1078
1129
  `- Goal: ensure quality, provide feedback, coordinate progression`
1079
1130
  ].join("\n");
1080
- case "auto": {
1081
- const parts = [
1082
- `
1083
- ## Mode: Auto`,
1084
- `You are in Auto mode \u2014 operating autonomously through planning \u2192 building \u2192 PR.`,
1085
- ``,
1086
- `### Phase 1: Discovery & Planning (current)`,
1087
- `- You have read-only codebase access (can read files, run git commands, search code)`,
1088
- `- You can write plan files in .claude/plans/ only \u2014 no other file writes`,
1089
- `- You have MCP tools for task properties: update_task, set_story_points, set_task_tags, set_task_icon, set_task_title`,
1090
- ``,
1091
- `### Required before transitioning:`,
1092
- `Before calling ExitPlanMode, you MUST fill in ALL of these:`,
1093
- `1. **Plan** \u2014 Save a clear implementation plan using update_task`,
1094
- `2. **Story Points** \u2014 Assign via set_story_points`,
1095
- `3. **Title** \u2014 Set an accurate title via set_task_title (if the current one is vague or "Untitled")`,
1096
- ``,
1097
- `### Transitioning to Building:`,
1098
- `When your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1099
- `- If any required properties are missing, ExitPlanMode will be denied with details on what's missing`,
1100
- `- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
1101
- `- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
1102
- ``,
1103
- `### Autonomous Guidelines:`,
1104
- `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1105
- `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
1106
- `- Be thorough in discovery: read relevant files, understand the codebase architecture, then plan`
1107
- ];
1108
- if (context) {
1109
- parts.push(...buildPropertyInstructions(context));
1110
- }
1111
- return parts.join("\n");
1112
- }
1131
+ case "auto":
1132
+ return buildAutoPrompt(context);
1113
1133
  default:
1114
1134
  return null;
1115
1135
  }
@@ -2594,6 +2614,19 @@ async function emitRetryStatus(host, attempt, delayMs) {
2594
2614
  host.connection.emitStatus("running");
2595
2615
  await host.callbacks.onStatusChange("running");
2596
2616
  }
2617
+ function handleRateLimitPause(host, rateLimitResetsAt) {
2618
+ host.connection.emitRateLimitPause(rateLimitResetsAt);
2619
+ host.connection.postChatMessage(
2620
+ `Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
2621
+ );
2622
+ }
2623
+ function handleRetryError(error, context, host, options, prevImageError) {
2624
+ if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
2625
+ return handleStaleSession(context, host, options);
2626
+ }
2627
+ if (!isRetriableError(error)) throw error;
2628
+ return { action: "continue", lastErrorWasImage: classifyImageError(error) || prevImageError };
2629
+ }
2597
2630
  async function runWithRetry(initialQuery, context, host, options) {
2598
2631
  let lastErrorWasImage = false;
2599
2632
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
@@ -2607,23 +2640,16 @@ async function runWithRetry(initialQuery, context, host, options) {
2607
2640
  );
2608
2641
  if (modeRestart || host.isStopped()) return;
2609
2642
  if (rateLimitResetsAt) {
2610
- const resetMs = new Date(rateLimitResetsAt).getTime() - Date.now();
2611
- if (resetMs > 5 * 6e4) {
2612
- host.connection.emitRateLimitPause(rateLimitResetsAt);
2613
- host.connection.postChatMessage(
2614
- `Rate limited. The task will be automatically re-queued and resume after ${new Date(rateLimitResetsAt).toLocaleString()}.`
2615
- );
2616
- return;
2617
- }
2643
+ handleRateLimitPause(host, rateLimitResetsAt);
2644
+ return;
2618
2645
  }
2619
2646
  if (!retriable) return;
2620
2647
  lastErrorWasImage = IMAGE_ERROR_PATTERN2.test(resultSummary ?? "");
2621
2648
  } catch (error) {
2622
- if (isStaleOrExitedSession(error, context) && context.claudeSessionId) {
2623
- return handleStaleSession(context, host, options);
2624
- }
2625
- if (classifyImageError(error)) lastErrorWasImage = true;
2626
- if (!isRetriableError(error)) throw error;
2649
+ const outcome = handleRetryError(error, context, host, options, lastErrorWasImage);
2650
+ if (outcome instanceof Promise) return outcome;
2651
+ if (outcome.action === "return") return;
2652
+ lastErrorWasImage = outcome.lastErrorWasImage;
2627
2653
  }
2628
2654
  if (attempt >= RETRY_DELAYS_MS.length) {
2629
2655
  host.connection.postChatMessage(
@@ -3212,29 +3238,33 @@ var AgentRunner = class {
3212
3238
  await this.setState("error");
3213
3239
  }
3214
3240
  }
3241
+ async handleModeRestartCycle() {
3242
+ if (!this.taskContext) return;
3243
+ await handleAutoModeRestart(
3244
+ { agentMode: this.agentMode, sessionIds: this.sessionIds, taskContext: this.taskContext },
3245
+ this.connection,
3246
+ (s) => this.setState(s),
3247
+ (ctx) => this.runQuerySafe(ctx),
3248
+ async () => {
3249
+ try {
3250
+ return await this.connection.fetchTaskContext();
3251
+ } catch {
3252
+ return null;
3253
+ }
3254
+ }
3255
+ );
3256
+ this.taskContext = await this.connection.fetchTaskContext().catch(() => null) ?? this.taskContext;
3257
+ if (!this.stopped && this._state !== "error") {
3258
+ const nudged = await this.maybeSendPRNudge();
3259
+ if (!nudged) await this.setState("idle");
3260
+ }
3261
+ }
3215
3262
  async runCoreLoop() {
3216
3263
  if (!this.taskContext) return;
3217
3264
  while (!this.stopped) {
3218
3265
  if (this.lastQueryModeRestart) {
3219
3266
  this.lastQueryModeRestart = false;
3220
- await handleAutoModeRestart(
3221
- { agentMode: this.agentMode, sessionIds: this.sessionIds, taskContext: this.taskContext },
3222
- this.connection,
3223
- (s) => this.setState(s),
3224
- (ctx) => this.runQuerySafe(ctx),
3225
- async () => {
3226
- try {
3227
- return await this.connection.fetchTaskContext();
3228
- } catch {
3229
- return null;
3230
- }
3231
- }
3232
- );
3233
- this.taskContext = await this.connection.fetchTaskContext().catch(() => null) ?? this.taskContext;
3234
- if (!this.stopped && this._state !== "error") {
3235
- const nudged = await this.maybeSendPRNudge();
3236
- if (!nudged) await this.setState("idle");
3237
- }
3267
+ await this.handleModeRestartCycle();
3238
3268
  continue;
3239
3269
  }
3240
3270
  if (this._state === "idle") {
@@ -3888,4 +3918,4 @@ export {
3888
3918
  ProjectRunner,
3889
3919
  FileCache
3890
3920
  };
3891
- //# sourceMappingURL=chunk-ACPWJ2SL.js.map
3921
+ //# sourceMappingURL=chunk-XRB3VPXH.js.map