@rallycry/conveyor-agent 7.0.2 → 7.0.4

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.
@@ -141,25 +141,7 @@ var AgentConnection = class {
141
141
  });
142
142
  this.socket.io.on("reconnect", () => {
143
143
  process.stderr.write("[conveyor-agent] Reconnected\n");
144
- this.call("connectAgent", { sessionId: this.config.sessionId }).then(({ pendingMessages }) => {
145
- for (const msg of pendingMessages) {
146
- if (msg.content) {
147
- if (this.messageCallback) {
148
- this.messageCallback({ content: msg.content, userId: msg.userId });
149
- } else {
150
- this.earlyMessages.push({ content: msg.content, userId: msg.userId });
151
- }
152
- }
153
- }
154
- }).catch(() => {
155
- });
156
- if (this.lastEmittedStatus) {
157
- void this.call("reportAgentStatus", {
158
- sessionId: this.config.sessionId,
159
- status: this.lastEmittedStatus
160
- }).catch(() => {
161
- });
162
- }
144
+ void this.reconnectToSession();
163
145
  });
164
146
  this.socket.io.on("reconnect_attempt", () => {
165
147
  });
@@ -174,6 +156,53 @@ var AgentConnection = class {
174
156
  this.socket = null;
175
157
  }
176
158
  }
159
+ // ── Reconnect with retry ────────────────────────────────────────────
160
+ async reconnectToSession() {
161
+ const maxRetries = 5;
162
+ const baseDelayMs = 2e3;
163
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
164
+ try {
165
+ const { pendingMessages } = await this.call("connectAgent", {
166
+ sessionId: this.config.sessionId
167
+ });
168
+ this.drainPendingMessages(pendingMessages);
169
+ process.stderr.write("[conveyor-agent] Reconnected to session successfully\n");
170
+ if (this.lastEmittedStatus) {
171
+ void this.call("reportAgentStatus", {
172
+ sessionId: this.config.sessionId,
173
+ status: this.lastEmittedStatus
174
+ }).catch(() => {
175
+ });
176
+ }
177
+ return;
178
+ } catch (err) {
179
+ const errMsg = err instanceof Error ? err.message : String(err);
180
+ const delayMs = baseDelayMs * 2 ** (attempt - 1);
181
+ process.stderr.write(
182
+ `[conveyor-agent] connectAgent failed (attempt ${attempt}/${maxRetries}): ${errMsg} \u2014 retrying in ${delayMs / 1e3}s
183
+ `
184
+ );
185
+ await new Promise((resolve2) => {
186
+ setTimeout(resolve2, delayMs);
187
+ });
188
+ }
189
+ }
190
+ process.stderr.write(
191
+ `[conveyor-agent] Failed to reconnect to session after ${maxRetries} attempts, exiting
192
+ `
193
+ );
194
+ process.exit(1);
195
+ }
196
+ drainPendingMessages(messages) {
197
+ for (const msg of messages) {
198
+ if (!msg.content) continue;
199
+ if (this.messageCallback) {
200
+ this.messageCallback({ content: msg.content, userId: msg.userId });
201
+ } else {
202
+ this.earlyMessages.push({ content: msg.content, userId: msg.userId });
203
+ }
204
+ }
205
+ }
177
206
  // ── Callback registration with early-buffer draining ───────────────
178
207
  onMessage(callback) {
179
208
  this.messageCallback = callback;
@@ -234,6 +263,7 @@ var AgentConnection = class {
234
263
  };
235
264
  const heartbeatStatus = statusMap[this.lastEmittedStatus ?? "idle"] ?? "active";
236
265
  void this.call("heartbeat", {
266
+ sessionId: this.config.sessionId,
237
267
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
238
268
  status: heartbeatStatus
239
269
  }).catch(() => {
@@ -1143,6 +1173,14 @@ function buildDiscoveryPrompt(context) {
1143
1173
  `- If the work fits in a single task (1-3 SP), update YOUR OWN plan and properties \u2014 do not create subtasks`,
1144
1174
  `- Only create subtasks when the work genuinely requires multiple independent pieces (e.g., Pack-tier work, 8+ SP)`,
1145
1175
  ``,
1176
+ `### Subtask Plan Requirements`,
1177
+ `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1178
+ `- Plans should be multi-step implementation guides, not vague descriptions`,
1179
+ `- Include specific file paths, function names, and code patterns to modify`,
1180
+ `- Reference existing implementations when relevant (e.g., "follow the pattern in src/services/foo.ts")`,
1181
+ `- Include testing requirements and acceptance criteria`,
1182
+ `- Set \`storyPointValue\` based on estimated complexity`,
1183
+ ``,
1146
1184
  `### Finishing Planning`,
1147
1185
  `Once your plan is complete and all required properties are set, call the **ExitPlanMode** tool.`,
1148
1186
  `- Required before ExitPlanMode will succeed: **plan** (via update_task), **story points** (via update_task_properties), **title** (via update_task_properties)`,
@@ -1174,6 +1212,14 @@ function buildAutoPrompt(context) {
1174
1212
  `- Once ExitPlanMode succeeds, the system will automatically restart your session in Building mode with the appropriate model`,
1175
1213
  `- You do NOT need to do anything after calling ExitPlanMode \u2014 the transition is handled for you`,
1176
1214
  ``,
1215
+ `### Subtask Plan Requirements`,
1216
+ `When creating subtasks, each MUST include a detailed \`plan\` field:`,
1217
+ `- Plans should be multi-step implementation guides, not vague descriptions`,
1218
+ `- Include specific file paths, function names, and code patterns to modify`,
1219
+ `- Reference existing implementations when relevant`,
1220
+ `- Include testing requirements and acceptance criteria`,
1221
+ `- Set \`storyPointValue\` based on estimated complexity`,
1222
+ ``,
1177
1223
  `### Autonomous Guidelines:`,
1178
1224
  `- Make decisions independently \u2014 do not ask the team for approval at each step`,
1179
1225
  `- Only escalate when genuinely blocked (ambiguous requirements, missing access, conflicting instructions)`,
@@ -1200,13 +1246,12 @@ function buildModePrompt(agentMode, context) {
1200
1246
  parts.push(
1201
1247
  ``,
1202
1248
  `### Resource Management`,
1203
- `Your pod starts with minimal resources (0.25 CPU / 1 Gi). You MUST call \`scale_up_resources\``,
1204
- `BEFORE running any of these operations \u2014 they WILL fail or OOM at baseline resources:`,
1205
- `- **light** (1 CPU / 4 Gi) \u2014 bun/npm/yarn install, pip install, basic dev servers, light builds`,
1206
- `- **standard** (2 CPU / 8 Gi) \u2014 full dev servers, test suites, typecheck, lint`,
1207
- `- **heavy** (4 CPU / 16 Gi) \u2014 E2E/browser automation, large parallel builds`,
1208
- `Scaling is one-way (up only) and capped by project limits.`,
1209
- `CRITICAL: Always scale to at least "light" before running any package install command.`
1249
+ `Your pod starts with minimal resources. You MUST call \`scale_up_resources\``,
1250
+ `BEFORE running any resource-intensive operations \u2014 they WILL fail or OOM at baseline resources:`,
1251
+ `- **setup** \u2014 package installs, basic dev servers, light builds`,
1252
+ `- **build** \u2014 full dev servers, test suites, typecheck, lint, E2E tests`,
1253
+ `Actual CPU/memory values are configured per-project. Scaling is one-way (up only).`,
1254
+ `CRITICAL: Always scale to at least "setup" before running any package install command.`
1210
1255
  );
1211
1256
  }
1212
1257
  return parts.join("\n");
@@ -2305,6 +2350,7 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
2305
2350
  }
2306
2351
  }
2307
2352
  const result = await connection.call("createPullRequest", {
2353
+ sessionId: connection.sessionId,
2308
2354
  title,
2309
2355
  body,
2310
2356
  head: branch,
@@ -2318,7 +2364,15 @@ Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>`;
2318
2364
  return textResult(`Pull request #${result.prNumber} created: ${result.prUrl}`);
2319
2365
  } catch (error) {
2320
2366
  const msg = error instanceof Error ? error.message : "Unknown error";
2321
- return textResult(`Failed to create pull request: ${msg}`);
2367
+ return textResult(
2368
+ `Failed to create pull request: ${msg}
2369
+
2370
+ Troubleshooting:
2371
+ - Ensure all changes are committed and pushed to the remote branch
2372
+ - Check that the branch exists on the remote (run: git push -u origin HEAD)
2373
+ - Verify there isn't already an open PR for this branch
2374
+ - If git auth fails, the token may have expired \u2014 retry the operation`
2375
+ );
2322
2376
  }
2323
2377
  }
2324
2378
  );
@@ -2455,9 +2509,9 @@ function buildVoteSuggestionTool(connection) {
2455
2509
  function buildScaleUpResourcesTool(connection) {
2456
2510
  return defineTool(
2457
2511
  "scale_up_resources",
2458
- "Scale up the pod's CPU and memory resources. Use before running dev servers, tests, builds, or other resource-intensive operations. Tiers: 'light' (1 CPU / 4 Gi \u2014 installs, basic dev servers), 'standard' (2 CPU / 8 Gi \u2014 full dev servers, test suites, typecheck), 'heavy' (4 CPU / 16 Gi \u2014 E2E tests, large parallel builds). Scaling is one-way (up only) and capped by project limits.",
2512
+ "Scale up the pod's CPU and memory resources. Use before running dev servers, tests, builds, or other resource-intensive operations. Phases: 'setup' (installs, basic dev servers), 'build' (full dev servers, test suites, typecheck, builds). Actual CPU/memory values are configured per-project. Scaling is one-way (up only).",
2459
2513
  {
2460
- tier: z.enum(["initial", "light", "standard", "heavy"]).describe("The resource tier to scale up to"),
2514
+ tier: z.enum(["initial", "setup", "build"]).describe("The resource phase to scale up to"),
2461
2515
  reason: z.string().optional().describe("Brief reason for scaling (e.g., 'running test suite')")
2462
2516
  },
2463
2517
  async ({ tier, reason }) => {
@@ -2468,13 +2522,8 @@ function buildScaleUpResourcesTool(connection) {
2468
2522
  reason
2469
2523
  });
2470
2524
  if (result.success) {
2471
- if (result.currentTier === result.previousTier) {
2472
- return textResult(
2473
- `Already at ${result.currentTier} tier (${result.cpu} CPU / ${result.memory} Gi). No scaling needed.`
2474
- );
2475
- }
2476
2525
  return textResult(
2477
- `Scaled to ${result.cpu} CPU / ${result.memory} Gi (${result.currentTier} tier, was ${result.previousTier}).`
2526
+ `Scaled to ${result.currentTier} phase (${result.cpu}). Was ${result.previousTier}.`
2478
2527
  );
2479
2528
  }
2480
2529
  return textResult(
@@ -2551,14 +2600,15 @@ function buildCreateSubtaskTool(connection) {
2551
2600
  ordinal: z2.number().optional().describe("Step/order number (0-based)"),
2552
2601
  storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2553
2602
  },
2554
- async ({ title, description, plan: _plan, ordinal, storyPointValue }) => {
2603
+ async ({ title, description, plan, ordinal, storyPointValue }) => {
2555
2604
  try {
2556
2605
  const result = await connection.call("createSubtask", {
2557
2606
  sessionId: connection.sessionId,
2558
2607
  title,
2559
- description,
2560
- storyPoints: storyPointValue,
2561
- ordinal
2608
+ ...description !== void 0 && { description },
2609
+ ...plan !== void 0 && { plan },
2610
+ ...storyPointValue !== void 0 && { storyPointValue },
2611
+ ...ordinal !== void 0 && { ordinal }
2562
2612
  });
2563
2613
  return textResult(`Subtask created with ID: ${result.id}`);
2564
2614
  } catch (error) {
@@ -2581,20 +2631,15 @@ function buildUpdateSubtaskTool(connection) {
2581
2631
  ordinal: z2.number().optional(),
2582
2632
  storyPointValue: z2.number().optional().describe(SP_DESCRIPTION)
2583
2633
  },
2584
- async ({
2585
- subtaskId,
2586
- title,
2587
- description,
2588
- plan: _plan,
2589
- ordinal: _ordinal,
2590
- storyPointValue: _sp
2591
- }) => {
2634
+ async ({ subtaskId, title, description, plan, storyPointValue }) => {
2592
2635
  try {
2593
2636
  await connection.call("updateSubtask", {
2594
2637
  sessionId: connection.sessionId,
2595
2638
  subtaskId,
2596
- title,
2597
- description
2639
+ ...title !== void 0 && { title },
2640
+ ...description !== void 0 && { description },
2641
+ ...plan !== void 0 && { plan },
2642
+ ...storyPointValue !== void 0 && { storyPointValue }
2598
2643
  });
2599
2644
  return textResult("Subtask updated.");
2600
2645
  } catch (error) {
@@ -4656,15 +4701,39 @@ function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask)
4656
4701
  }
4657
4702
  return { behavior: "allow", updatedInput: input };
4658
4703
  }
4704
+ function collectMissingProps(taskProps) {
4705
+ const missing = [];
4706
+ if (!taskProps.plan?.trim()) missing.push("plan (save via update_task)");
4707
+ if (!taskProps.storyPointId) missing.push("story points (use update_task_properties)");
4708
+ if (!taskProps.title || taskProps.title === "Untitled")
4709
+ missing.push("title (use update_task_properties)");
4710
+ return missing;
4711
+ }
4659
4712
  async function handleExitPlanMode(host, input) {
4713
+ if (host.hasExitedPlanMode) {
4714
+ return { behavior: "allow", updatedInput: input };
4715
+ }
4660
4716
  try {
4661
4717
  host.syncPlanFile();
4662
4718
  const taskProps = await host.connection.getTaskProperties();
4663
- const missingProps = [];
4664
- if (!taskProps.plan?.trim()) missingProps.push("plan (save via update_task)");
4665
- if (!taskProps.storyPointId) missingProps.push("story points (use update_task_properties)");
4666
- if (!taskProps.title || taskProps.title === "Untitled")
4667
- missingProps.push("title (use update_task_properties)");
4719
+ const missingProps = collectMissingProps(taskProps);
4720
+ if (host.isParentTask) {
4721
+ try {
4722
+ const subtasks = await host.connection.call("listSubtasks", {
4723
+ sessionId: host.connection.sessionId
4724
+ });
4725
+ const subtasksWithoutPlans = subtasks.filter(
4726
+ (s) => !s.plan?.trim()
4727
+ );
4728
+ if (subtasksWithoutPlans.length > 0) {
4729
+ const names = subtasksWithoutPlans.map((s) => s.title).join(", ");
4730
+ missingProps.push(
4731
+ `subtask plans \u2014 these subtasks are missing plans: ${names} (use update_subtask with plan field)`
4732
+ );
4733
+ }
4734
+ } catch {
4735
+ }
4736
+ }
4668
4737
  if (missingProps.length > 0) {
4669
4738
  return {
4670
4739
  behavior: "deny",
@@ -5223,6 +5292,8 @@ var QueryBridge = class {
5223
5292
  const msg = err instanceof Error ? err.message : String(err);
5224
5293
  logger2.error("Query execution failed", { error: msg });
5225
5294
  this.connection.sendEvent({ type: "error", message: msg });
5295
+ } finally {
5296
+ this.mode.pendingModeRestart = false;
5226
5297
  }
5227
5298
  }
5228
5299
  // ── QueryHost construction ──────────────────────────────────────────
@@ -5431,6 +5502,7 @@ var SessionRunner = class _SessionRunner {
5431
5502
  if (!this.stopped && this.pendingMessages.length === 0) {
5432
5503
  await this.maybeSendPRNudge();
5433
5504
  }
5505
+ this.hasCompleted = true;
5434
5506
  if (!this.stopped) await this.setState("idle");
5435
5507
  } else if (this._state === "error") {
5436
5508
  await this.setState("idle");
@@ -6624,7 +6696,7 @@ var ProjectRunner = class {
6624
6696
  }
6625
6697
  // ── Event wiring ───────────────────────────────────────────────────────
6626
6698
  wireEventHandlers() {
6627
- this.connection.onTaskAssignment((assignment) => this.handleAssignment(assignment));
6699
+ this.connection.onTaskAssignment((assignment) => void this.handleAssignment(assignment));
6628
6700
  this.connection.onStopTask((data) => this.handleStopTask(data.taskId));
6629
6701
  this.connection.onShutdown(() => void this.stop());
6630
6702
  this.connection.onChatMessage((msg) => {
@@ -6682,13 +6754,42 @@ var ProjectRunner = class {
6682
6754
  }
6683
6755
  }
6684
6756
  // ── Task management ────────────────────────────────────────────────────
6685
- handleAssignment(assignment) {
6757
+ async killAgent(agent, taskId) {
6758
+ const shortId = taskId.slice(0, 8);
6759
+ if (agent.process.exitCode !== null) {
6760
+ logger6.info("Agent process already exited", { taskId: shortId });
6761
+ return;
6762
+ }
6763
+ logger6.info("Killing agent process", { taskId: shortId });
6764
+ agent.process.kill("SIGTERM");
6765
+ await new Promise((resolve2) => {
6766
+ const timer = setTimeout(() => {
6767
+ if (agent.process.exitCode === null) {
6768
+ logger6.warn("Agent did not exit after SIGTERM, sending SIGKILL", { taskId: shortId });
6769
+ agent.process.kill("SIGKILL");
6770
+ }
6771
+ resolve2();
6772
+ }, STOP_TIMEOUT_MS);
6773
+ agent.process.on("exit", () => {
6774
+ clearTimeout(timer);
6775
+ resolve2();
6776
+ });
6777
+ });
6778
+ }
6779
+ // oxlint-disable-next-line max-lines-per-function -- re-assignment logic requires sequential checks
6780
+ async handleAssignment(assignment) {
6686
6781
  const { taskId, mode } = assignment;
6687
6782
  const shortId = taskId.slice(0, 8);
6688
6783
  const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
6689
- if (this.activeAgents.has(agentKey)) {
6690
- logger6.info("Task already running, skipping", { taskId: shortId });
6691
- return;
6784
+ const existing = this.activeAgents.get(agentKey);
6785
+ if (existing) {
6786
+ if (existing.process.exitCode === null) {
6787
+ logger6.info("Re-assignment received, killing existing agent", { taskId: shortId });
6788
+ await this.killAgent(existing, taskId);
6789
+ } else {
6790
+ logger6.info("Stale agent entry (process already exited), cleaning up", { taskId: shortId });
6791
+ }
6792
+ this.activeAgents.delete(agentKey);
6692
6793
  }
6693
6794
  if (this.activeAgents.size >= MAX_CONCURRENT) {
6694
6795
  logger6.warn("Max concurrent agents reached", { maxConcurrent: MAX_CONCURRENT });
@@ -6730,16 +6831,11 @@ var ProjectRunner = class {
6730
6831
  }
6731
6832
  }
6732
6833
  handleStopTask(taskId) {
6733
- let agent = this.activeAgents.get(taskId);
6734
- if (!agent) agent = this.activeAgents.get(`${taskId}:code-review`);
6834
+ const agentKey = this.activeAgents.has(taskId) ? taskId : `${taskId}:code-review`;
6835
+ const agent = this.activeAgents.get(agentKey);
6735
6836
  if (!agent) return;
6736
6837
  logger6.info("Stopping task", { taskId: taskId.slice(0, 8) });
6737
- agent.process.kill("SIGTERM");
6738
- const timer = setTimeout(() => {
6739
- if (this.activeAgents.has(taskId)) agent.process.kill("SIGKILL");
6740
- }, STOP_TIMEOUT_MS);
6741
- agent.process.on("exit", () => {
6742
- clearTimeout(timer);
6838
+ void this.killAgent(agent, taskId).then(() => {
6743
6839
  if (agent.usesWorktree) {
6744
6840
  try {
6745
6841
  removeWorktree(this.projectDir, taskId);
@@ -7027,4 +7123,4 @@ export {
7027
7123
  removeWorktree,
7028
7124
  ProjectRunner
7029
7125
  };
7030
- //# sourceMappingURL=chunk-NZ3IPMLO.js.map
7126
+ //# sourceMappingURL=chunk-K2TJHPMA.js.map