@rallycry/conveyor-agent 5.11.1 → 5.13.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.
@@ -230,6 +230,18 @@ var ConveyorConnection = class _ConveyorConnection {
230
230
  resolve2();
231
231
  }
232
232
  });
233
+ this.socket.on("disconnect", (reason) => {
234
+ process.stderr.write(`[conveyor] Socket disconnected: ${reason}
235
+ `);
236
+ });
237
+ this.socket.io.on("reconnect", (attempt) => {
238
+ process.stderr.write(`[conveyor] Reconnected after ${attempt} attempt(s)
239
+ `);
240
+ });
241
+ this.socket.io.on("reconnect_error", (err) => {
242
+ process.stderr.write(`[conveyor] Reconnection error: ${err.message}
243
+ `);
244
+ });
233
245
  this.socket.io.on("reconnect_attempt", () => {
234
246
  attempts++;
235
247
  if (!settled && attempts >= maxInitialAttempts) {
@@ -557,6 +569,18 @@ var ProjectConnection = class {
557
569
  resolve2();
558
570
  }
559
571
  });
572
+ this.socket.on("disconnect", (reason) => {
573
+ process.stderr.write(`[conveyor] Project socket disconnected: ${reason}
574
+ `);
575
+ });
576
+ this.socket.io.on("reconnect", (attempt) => {
577
+ process.stderr.write(`[conveyor] Project socket reconnected after ${attempt} attempt(s)
578
+ `);
579
+ });
580
+ this.socket.io.on("reconnect_error", (err) => {
581
+ process.stderr.write(`[conveyor] Project socket reconnection error: ${err.message}
582
+ `);
583
+ });
560
584
  this.socket.io.on("reconnect_attempt", () => {
561
585
  attempts++;
562
586
  if (!settled && attempts >= maxInitialAttempts) {
@@ -640,13 +664,13 @@ var ProjectConnection = class {
640
664
  );
641
665
  });
642
666
  }
643
- fetchChatHistory(limit) {
667
+ fetchChatHistory(limit, chatId) {
644
668
  const socket = this.socket;
645
669
  if (!socket) return Promise.reject(new Error("Not connected"));
646
670
  return new Promise((resolve2, reject) => {
647
671
  socket.emit(
648
672
  "projectRunner:getChatHistory",
649
- { limit },
673
+ { limit, chatId },
650
674
  (response) => {
651
675
  if (response.success && response.data) resolve2(response.data);
652
676
  else reject(new Error(response.error ?? "Failed to fetch chat history"));
@@ -654,6 +678,38 @@ var ProjectConnection = class {
654
678
  );
655
679
  });
656
680
  }
681
+ // ── Project MCP tool request methods ──
682
+ requestListTasks(params) {
683
+ return this.requestWithCallback("projectRunner:listTasks", params);
684
+ }
685
+ requestGetTask(taskId) {
686
+ return this.requestWithCallback("projectRunner:getTask", { taskId });
687
+ }
688
+ requestCreateTask(params) {
689
+ return this.requestWithCallback("projectRunner:createTask", params);
690
+ }
691
+ requestUpdateTask(params) {
692
+ return this.requestWithCallback("projectRunner:updateTask", params);
693
+ }
694
+ requestSearchTasks(params) {
695
+ return this.requestWithCallback("projectRunner:searchTasks", params);
696
+ }
697
+ requestListTags() {
698
+ return this.requestWithCallback("projectRunner:listTags", {});
699
+ }
700
+ requestGetProjectSummary() {
701
+ return this.requestWithCallback("projectRunner:getProjectSummary", {});
702
+ }
703
+ requestWithCallback(event, data) {
704
+ const socket = this.socket;
705
+ if (!socket) return Promise.reject(new Error("Not connected"));
706
+ return new Promise((resolve2, reject) => {
707
+ socket.emit(event, data, (response) => {
708
+ if (response.success) resolve2(response.data);
709
+ else reject(new Error(response.error ?? `${event} failed`));
710
+ });
711
+ });
712
+ }
657
713
  emitNewCommitsDetected(data) {
658
714
  if (!this.socket) return;
659
715
  this.socket.emit("projectRunner:newCommitsDetected", data);
@@ -701,9 +757,32 @@ var ProjectConnection = class {
701
757
  };
702
758
 
703
759
  // src/runner/worktree.ts
704
- import { execSync as execSync2 } from "child_process";
760
+ import { execSync as execSync3 } from "child_process";
705
761
  import { existsSync } from "fs";
706
762
  import { join } from "path";
763
+
764
+ // src/runner/git-utils.ts
765
+ import { execSync as execSync2 } from "child_process";
766
+ function hasUncommittedChanges(cwd) {
767
+ const status = execSync2("git status --porcelain", {
768
+ cwd,
769
+ stdio: ["ignore", "pipe", "ignore"]
770
+ }).toString().trim();
771
+ return status.length > 0;
772
+ }
773
+ function getCurrentBranch(cwd) {
774
+ try {
775
+ const branch = execSync2("git branch --show-current", {
776
+ cwd,
777
+ stdio: ["ignore", "pipe", "ignore"]
778
+ }).toString().trim();
779
+ return branch || null;
780
+ } catch {
781
+ return null;
782
+ }
783
+ }
784
+
785
+ // src/runner/worktree.ts
707
786
  var WORKTREE_DIR = ".worktrees";
708
787
  function ensureWorktree(projectDir, taskId, branch) {
709
788
  if (projectDir.includes(`/${WORKTREE_DIR}/`)) {
@@ -712,8 +791,11 @@ function ensureWorktree(projectDir, taskId, branch) {
712
791
  const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
713
792
  if (existsSync(worktreePath)) {
714
793
  if (branch) {
794
+ if (hasUncommittedChanges(worktreePath)) {
795
+ return worktreePath;
796
+ }
715
797
  try {
716
- execSync2(`git checkout --detach origin/${branch}`, {
798
+ execSync3(`git checkout --detach origin/${branch}`, {
717
799
  cwd: worktreePath,
718
800
  stdio: "ignore"
719
801
  });
@@ -723,17 +805,39 @@ function ensureWorktree(projectDir, taskId, branch) {
723
805
  return worktreePath;
724
806
  }
725
807
  const ref = branch ? `origin/${branch}` : "HEAD";
726
- execSync2(`git worktree add --detach "${worktreePath}" ${ref}`, {
808
+ execSync3(`git worktree add --detach "${worktreePath}" ${ref}`, {
727
809
  cwd: projectDir,
728
810
  stdio: "ignore"
729
811
  });
730
812
  return worktreePath;
731
813
  }
814
+ function detachWorktreeBranch(projectDir, branch) {
815
+ try {
816
+ const output = execSync3("git worktree list --porcelain", {
817
+ cwd: projectDir,
818
+ encoding: "utf-8"
819
+ });
820
+ const entries = output.split("\n\n");
821
+ for (const entry of entries) {
822
+ const lines = entry.trim().split("\n");
823
+ const worktreeLine = lines.find((l) => l.startsWith("worktree "));
824
+ const branchLine = lines.find((l) => l.startsWith("branch "));
825
+ if (!worktreeLine || branchLine !== `branch refs/heads/${branch}`) continue;
826
+ const worktreePath = worktreeLine.replace("worktree ", "");
827
+ if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
828
+ try {
829
+ execSync3("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
830
+ } catch {
831
+ }
832
+ }
833
+ } catch {
834
+ }
835
+ }
732
836
  function removeWorktree(projectDir, taskId) {
733
837
  const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
734
838
  if (!existsSync(worktreePath)) return;
735
839
  try {
736
- execSync2(`git worktree remove "${worktreePath}" --force`, {
840
+ execSync3(`git worktree remove "${worktreePath}" --force`, {
737
841
  cwd: projectDir,
738
842
  stdio: "ignore"
739
843
  });
@@ -769,10 +873,10 @@ function loadConveyorConfig(_workspaceDir) {
769
873
  }
770
874
 
771
875
  // src/setup/codespace.ts
772
- import { execSync as execSync3 } from "child_process";
876
+ import { execSync as execSync4 } from "child_process";
773
877
  function unshallowRepo(workspaceDir) {
774
878
  try {
775
- execSync3("git fetch --unshallow", {
879
+ execSync4("git fetch --unshallow", {
776
880
  cwd: workspaceDir,
777
881
  stdio: "ignore",
778
882
  timeout: 6e4
@@ -810,7 +914,7 @@ function errorMeta(error) {
810
914
 
811
915
  // src/runner/agent-runner.ts
812
916
  import { randomUUID as randomUUID2 } from "crypto";
813
- import { execSync as execSync4 } from "child_process";
917
+ import { execSync as execSync5 } from "child_process";
814
918
 
815
919
  // src/execution/event-handlers.ts
816
920
  function safeVoid(promise, context) {
@@ -3444,6 +3548,47 @@ async function executeSetupConfig(config, runnerConfig, connection, setupLog) {
3444
3548
  async function checkoutTaskBranch(runnerConfig, connection, callbacks, setupLog) {
3445
3549
  const taskBranch = process.env.CONVEYOR_TASK_BRANCH;
3446
3550
  if (!taskBranch) return true;
3551
+ const currentBranch = getCurrentBranch(runnerConfig.workspaceDir);
3552
+ if (currentBranch === taskBranch) {
3553
+ pushSetupLog(setupLog, `[conveyor] Already on ${taskBranch}, skipping checkout`);
3554
+ connection.sendEvent({
3555
+ type: "setup_output",
3556
+ stream: "stdout",
3557
+ data: `Already on branch ${taskBranch}, skipping checkout
3558
+ `
3559
+ });
3560
+ try {
3561
+ await runSetupCommand(
3562
+ `git fetch origin ${taskBranch}`,
3563
+ runnerConfig.workspaceDir,
3564
+ (stream, data) => {
3565
+ connection.sendEvent({ type: "setup_output", stream, data });
3566
+ }
3567
+ );
3568
+ } catch {
3569
+ }
3570
+ return true;
3571
+ }
3572
+ let didStash = false;
3573
+ if (hasUncommittedChanges(runnerConfig.workspaceDir)) {
3574
+ pushSetupLog(setupLog, `[conveyor] Uncommitted changes detected, stashing before checkout`);
3575
+ connection.sendEvent({
3576
+ type: "setup_output",
3577
+ stream: "stdout",
3578
+ data: "Uncommitted changes detected \u2014 stashing before branch switch\n"
3579
+ });
3580
+ try {
3581
+ await runSetupCommand(
3582
+ `git stash push -m "conveyor-auto-stash"`,
3583
+ runnerConfig.workspaceDir,
3584
+ (stream, data) => {
3585
+ connection.sendEvent({ type: "setup_output", stream, data });
3586
+ }
3587
+ );
3588
+ didStash = true;
3589
+ } catch {
3590
+ }
3591
+ }
3447
3592
  pushSetupLog(setupLog, `[conveyor] Switching to task branch ${taskBranch}...`);
3448
3593
  connection.sendEvent({
3449
3594
  type: "setup_output",
@@ -3463,6 +3608,19 @@ async function checkoutTaskBranch(runnerConfig, connection, callbacks, setupLog)
3463
3608
  }
3464
3609
  );
3465
3610
  pushSetupLog(setupLog, `[conveyor] Switched to ${taskBranch}`);
3611
+ if (didStash) {
3612
+ try {
3613
+ await runSetupCommand("git stash pop", runnerConfig.workspaceDir, (stream, data) => {
3614
+ connection.sendEvent({ type: "setup_output", stream, data });
3615
+ });
3616
+ pushSetupLog(setupLog, `[conveyor] Restored stashed changes`);
3617
+ } catch {
3618
+ pushSetupLog(
3619
+ setupLog,
3620
+ `[conveyor] Warning: stash pop had conflicts \u2014 agent may need to resolve`
3621
+ );
3622
+ }
3623
+ }
3466
3624
  return true;
3467
3625
  } catch (error) {
3468
3626
  const message = `Failed to checkout ${taskBranch}: ${error instanceof Error ? error.message : "unknown error"}`;
@@ -3807,12 +3965,22 @@ var AgentRunner = class {
3807
3965
  }
3808
3966
  checkoutWorktreeBranch() {
3809
3967
  if (!this.worktreeActive || !this.taskContext?.githubBranch) return;
3968
+ const branch = this.taskContext.githubBranch;
3969
+ const cwd = this.config.workspaceDir;
3970
+ if (getCurrentBranch(cwd) === branch) return;
3810
3971
  try {
3811
- const branch = this.taskContext.githubBranch;
3812
- execSync4(`git fetch origin ${branch} && git checkout ${branch}`, {
3813
- cwd: this.config.workspaceDir,
3814
- stdio: "ignore"
3815
- });
3972
+ let didStash = false;
3973
+ if (hasUncommittedChanges(cwd)) {
3974
+ execSync5(`git stash push -m "conveyor-auto-stash"`, { cwd, stdio: "ignore" });
3975
+ didStash = true;
3976
+ }
3977
+ execSync5(`git fetch origin ${branch} && git checkout ${branch}`, { cwd, stdio: "ignore" });
3978
+ if (didStash) {
3979
+ try {
3980
+ execSync5("git stash pop", { cwd, stdio: "ignore" });
3981
+ } catch {
3982
+ }
3983
+ }
3816
3984
  } catch {
3817
3985
  }
3818
3986
  }
@@ -4100,12 +4268,12 @@ var AgentRunner = class {
4100
4268
 
4101
4269
  // src/runner/project-runner.ts
4102
4270
  import { fork } from "child_process";
4103
- import { execSync as execSync6 } from "child_process";
4271
+ import { execSync as execSync7 } from "child_process";
4104
4272
  import * as path from "path";
4105
4273
  import { fileURLToPath } from "url";
4106
4274
 
4107
4275
  // src/runner/commit-watcher.ts
4108
- import { execSync as execSync5 } from "child_process";
4276
+ import { execSync as execSync6 } from "child_process";
4109
4277
  var logger3 = createServiceLogger("CommitWatcher");
4110
4278
  var CommitWatcher = class {
4111
4279
  constructor(config, callbacks) {
@@ -4137,7 +4305,7 @@ var CommitWatcher = class {
4137
4305
  this.isSyncing = false;
4138
4306
  }
4139
4307
  getLocalHeadSha() {
4140
- return execSync5("git rev-parse HEAD", {
4308
+ return execSync6("git rev-parse HEAD", {
4141
4309
  cwd: this.config.projectDir,
4142
4310
  stdio: ["ignore", "pipe", "ignore"]
4143
4311
  }).toString().trim();
@@ -4145,12 +4313,12 @@ var CommitWatcher = class {
4145
4313
  poll() {
4146
4314
  if (!this.branch || this.isSyncing) return;
4147
4315
  try {
4148
- execSync5(`git fetch origin ${this.branch} --quiet`, {
4316
+ execSync6(`git fetch origin ${this.branch} --quiet`, {
4149
4317
  cwd: this.config.projectDir,
4150
4318
  stdio: "ignore",
4151
4319
  timeout: 3e4
4152
4320
  });
4153
- const remoteSha = execSync5(`git rev-parse origin/${this.branch}`, {
4321
+ const remoteSha = execSync6(`git rev-parse origin/${this.branch}`, {
4154
4322
  cwd: this.config.projectDir,
4155
4323
  stdio: ["ignore", "pipe", "ignore"]
4156
4324
  }).toString().trim();
@@ -4171,12 +4339,12 @@ var CommitWatcher = class {
4171
4339
  let latestMessage = "";
4172
4340
  let latestAuthor = "";
4173
4341
  try {
4174
- const countOutput = execSync5(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
4342
+ const countOutput = execSync6(`git rev-list --count ${previousSha}..origin/${this.branch}`, {
4175
4343
  cwd: this.config.projectDir,
4176
4344
  stdio: ["ignore", "pipe", "ignore"]
4177
4345
  }).toString().trim();
4178
4346
  commitCount = parseInt(countOutput, 10) || 1;
4179
- const logOutput = execSync5(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
4347
+ const logOutput = execSync6(`git log -1 --format="%s|||%an" origin/${this.branch}`, {
4180
4348
  cwd: this.config.projectDir,
4181
4349
  stdio: ["ignore", "pipe", "ignore"]
4182
4350
  }).toString().trim();
@@ -4210,7 +4378,159 @@ var CommitWatcher = class {
4210
4378
  };
4211
4379
 
4212
4380
  // src/runner/project-chat-handler.ts
4213
- import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
4381
+ import {
4382
+ query as query2,
4383
+ createSdkMcpServer as createSdkMcpServer2
4384
+ } from "@anthropic-ai/claude-agent-sdk";
4385
+
4386
+ // src/tools/project-tools.ts
4387
+ import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
4388
+ import { z as z4 } from "zod";
4389
+ function buildReadTools(connection) {
4390
+ return [
4391
+ tool4(
4392
+ "list_tasks",
4393
+ "List tasks in the project. Optionally filter by status or assignee.",
4394
+ {
4395
+ status: z4.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
4396
+ assigneeId: z4.string().optional().describe("Filter by assigned user ID"),
4397
+ limit: z4.number().optional().describe("Max number of tasks to return (default 50)")
4398
+ },
4399
+ async (params) => {
4400
+ try {
4401
+ const tasks = await connection.requestListTasks(params);
4402
+ return textResult(JSON.stringify(tasks, null, 2));
4403
+ } catch (error) {
4404
+ return textResult(
4405
+ `Failed to list tasks: ${error instanceof Error ? error.message : "Unknown error"}`
4406
+ );
4407
+ }
4408
+ },
4409
+ { annotations: { readOnlyHint: true } }
4410
+ ),
4411
+ tool4(
4412
+ "get_task",
4413
+ "Get detailed information about a task including its chat messages, child tasks, and codespace status.",
4414
+ { task_id: z4.string().describe("The task ID to look up") },
4415
+ async ({ task_id }) => {
4416
+ try {
4417
+ const task = await connection.requestGetTask(task_id);
4418
+ return textResult(JSON.stringify(task, null, 2));
4419
+ } catch (error) {
4420
+ return textResult(
4421
+ `Failed to get task: ${error instanceof Error ? error.message : "Unknown error"}`
4422
+ );
4423
+ }
4424
+ },
4425
+ { annotations: { readOnlyHint: true } }
4426
+ ),
4427
+ tool4(
4428
+ "search_tasks",
4429
+ "Search tasks by tags, text query, or status filters.",
4430
+ {
4431
+ tagNames: z4.array(z4.string()).optional().describe("Filter by tag names"),
4432
+ searchQuery: z4.string().optional().describe("Text search in title/description"),
4433
+ statusFilters: z4.array(z4.string()).optional().describe("Filter by statuses"),
4434
+ limit: z4.number().optional().describe("Max results (default 20)")
4435
+ },
4436
+ async (params) => {
4437
+ try {
4438
+ const tasks = await connection.requestSearchTasks(params);
4439
+ return textResult(JSON.stringify(tasks, null, 2));
4440
+ } catch (error) {
4441
+ return textResult(
4442
+ `Failed to search tasks: ${error instanceof Error ? error.message : "Unknown error"}`
4443
+ );
4444
+ }
4445
+ },
4446
+ { annotations: { readOnlyHint: true } }
4447
+ ),
4448
+ tool4(
4449
+ "list_tags",
4450
+ "List all tags available in the project.",
4451
+ {},
4452
+ async () => {
4453
+ try {
4454
+ const tags = await connection.requestListTags();
4455
+ return textResult(JSON.stringify(tags, null, 2));
4456
+ } catch (error) {
4457
+ return textResult(
4458
+ `Failed to list tags: ${error instanceof Error ? error.message : "Unknown error"}`
4459
+ );
4460
+ }
4461
+ },
4462
+ { annotations: { readOnlyHint: true } }
4463
+ ),
4464
+ tool4(
4465
+ "get_project_summary",
4466
+ "Get a summary of the project including task counts by status and active builds.",
4467
+ {},
4468
+ async () => {
4469
+ try {
4470
+ const summary = await connection.requestGetProjectSummary();
4471
+ return textResult(JSON.stringify(summary, null, 2));
4472
+ } catch (error) {
4473
+ return textResult(
4474
+ `Failed to get project summary: ${error instanceof Error ? error.message : "Unknown error"}`
4475
+ );
4476
+ }
4477
+ },
4478
+ { annotations: { readOnlyHint: true } }
4479
+ )
4480
+ ];
4481
+ }
4482
+ function buildMutationTools(connection) {
4483
+ return [
4484
+ tool4(
4485
+ "create_task",
4486
+ "Create a new task in the project.",
4487
+ {
4488
+ title: z4.string().describe("Task title"),
4489
+ description: z4.string().optional().describe("Task description"),
4490
+ plan: z4.string().optional().describe("Implementation plan in markdown"),
4491
+ status: z4.string().optional().describe("Initial status (default: Planning)"),
4492
+ isBug: z4.boolean().optional().describe("Whether this is a bug report")
4493
+ },
4494
+ async (params) => {
4495
+ try {
4496
+ const result = await connection.requestCreateTask(params);
4497
+ return textResult(`Task created: ${result.slug} (ID: ${result.id})`);
4498
+ } catch (error) {
4499
+ return textResult(
4500
+ `Failed to create task: ${error instanceof Error ? error.message : "Unknown error"}`
4501
+ );
4502
+ }
4503
+ }
4504
+ ),
4505
+ tool4(
4506
+ "update_task",
4507
+ "Update an existing task's title, description, plan, status, or assignee.",
4508
+ {
4509
+ task_id: z4.string().describe("The task ID to update"),
4510
+ title: z4.string().optional().describe("New title"),
4511
+ description: z4.string().optional().describe("New description"),
4512
+ plan: z4.string().optional().describe("New plan in markdown"),
4513
+ status: z4.string().optional().describe("New status"),
4514
+ assignedUserId: z4.string().nullable().optional().describe("Assign to user ID, or null to unassign")
4515
+ },
4516
+ async ({ task_id, ...fields }) => {
4517
+ try {
4518
+ await connection.requestUpdateTask({ taskId: task_id, ...fields });
4519
+ return textResult("Task updated successfully.");
4520
+ } catch (error) {
4521
+ return textResult(
4522
+ `Failed to update task: ${error instanceof Error ? error.message : "Unknown error"}`
4523
+ );
4524
+ }
4525
+ }
4526
+ )
4527
+ ];
4528
+ }
4529
+ function buildProjectTools(connection) {
4530
+ return [...buildReadTools(connection), ...buildMutationTools(connection)];
4531
+ }
4532
+
4533
+ // src/runner/project-chat-handler.ts
4214
4534
  var logger4 = createServiceLogger("ProjectChat");
4215
4535
  var FALLBACK_MODEL = "claude-sonnet-4-20250514";
4216
4536
  function buildSystemPrompt2(projectDir, agentCtx) {
@@ -4267,7 +4587,7 @@ function processContentBlock(block, responseParts, turnToolCalls) {
4267
4587
  logger4.debug("Tool use", { tool: block.name });
4268
4588
  }
4269
4589
  }
4270
- async function fetchContext(connection) {
4590
+ async function fetchContext(connection, chatId) {
4271
4591
  let agentCtx = null;
4272
4592
  try {
4273
4593
  agentCtx = await connection.fetchAgentContext();
@@ -4276,15 +4596,19 @@ async function fetchContext(connection) {
4276
4596
  }
4277
4597
  let chatHistory = [];
4278
4598
  try {
4279
- chatHistory = await connection.fetchChatHistory(30);
4599
+ chatHistory = await connection.fetchChatHistory(30, chatId);
4280
4600
  } catch {
4281
4601
  logger4.warn("Could not fetch chat history, proceeding without it");
4282
4602
  }
4283
4603
  return { agentCtx, chatHistory };
4284
4604
  }
4285
- function buildChatQueryOptions(agentCtx, projectDir) {
4605
+ function buildChatQueryOptions(agentCtx, projectDir, connection) {
4286
4606
  const model = agentCtx?.model || FALLBACK_MODEL;
4287
4607
  const settings = agentCtx?.agentSettings ?? {};
4608
+ const mcpServer = createSdkMcpServer2({
4609
+ name: "conveyor",
4610
+ tools: buildProjectTools(connection)
4611
+ });
4288
4612
  return {
4289
4613
  model,
4290
4614
  systemPrompt: {
@@ -4296,8 +4620,9 @@ function buildChatQueryOptions(agentCtx, projectDir) {
4296
4620
  permissionMode: "bypassPermissions",
4297
4621
  allowDangerouslySkipPermissions: true,
4298
4622
  tools: { type: "preset", preset: "claude_code" },
4299
- maxTurns: settings.maxTurns ?? 15,
4300
- maxBudgetUsd: settings.maxBudgetUsd ?? 5,
4623
+ mcpServers: { conveyor: mcpServer },
4624
+ maxTurns: settings.maxTurns ?? 30,
4625
+ maxBudgetUsd: settings.maxBudgetUsd ?? 50,
4301
4626
  effort: settings.effort,
4302
4627
  thinking: settings.thinking
4303
4628
  };
@@ -4363,16 +4688,25 @@ function processEventStream(event, connection, responseParts, turnToolCalls, isT
4363
4688
  }
4364
4689
  return false;
4365
4690
  }
4366
- async function runChatQuery(message, connection, projectDir) {
4367
- const { agentCtx, chatHistory } = await fetchContext(connection);
4368
- const options = buildChatQueryOptions(agentCtx, projectDir);
4691
+ async function runChatQuery(message, connection, projectDir, sessionId) {
4692
+ const { agentCtx, chatHistory } = await fetchContext(connection, message.chatId);
4693
+ const options = buildChatQueryOptions(agentCtx, projectDir, connection);
4369
4694
  const prompt = buildPrompt(message, chatHistory);
4370
4695
  connection.emitAgentStatus("running");
4371
- const events = query2({ prompt, options });
4696
+ const events = query2({
4697
+ prompt,
4698
+ options,
4699
+ ...sessionId ? { resume: sessionId } : {}
4700
+ });
4372
4701
  const responseParts = [];
4373
4702
  const turnToolCalls = [];
4374
4703
  const isTyping = { value: false };
4704
+ let resultSessionId;
4375
4705
  for await (const event of events) {
4706
+ if (event.type === "result") {
4707
+ const resultEvent = event;
4708
+ resultSessionId = resultEvent.sessionId;
4709
+ }
4376
4710
  const done = processEventStream(event, connection, responseParts, turnToolCalls, isTyping);
4377
4711
  if (done) break;
4378
4712
  }
@@ -4383,11 +4717,12 @@ async function runChatQuery(message, connection, projectDir) {
4383
4717
  if (responseText) {
4384
4718
  await connection.emitChatMessage(responseText);
4385
4719
  }
4720
+ return resultSessionId;
4386
4721
  }
4387
- async function handleProjectChatMessage(message, connection, projectDir) {
4722
+ async function handleProjectChatMessage(message, connection, projectDir, sessionId) {
4388
4723
  connection.emitAgentStatus("fetching_context");
4389
4724
  try {
4390
- await runChatQuery(message, connection, projectDir);
4725
+ return await runChatQuery(message, connection, projectDir, sessionId);
4391
4726
  } catch (error) {
4392
4727
  logger4.error("Failed to handle message", errorMeta(error));
4393
4728
  connection.emitAgentStatus("error");
@@ -4397,6 +4732,7 @@ async function handleProjectChatMessage(message, connection, projectDir) {
4397
4732
  );
4398
4733
  } catch {
4399
4734
  }
4735
+ return void 0;
4400
4736
  } finally {
4401
4737
  connection.emitAgentStatus("idle");
4402
4738
  }
@@ -4407,8 +4743,8 @@ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
4407
4743
 
4408
4744
  // src/tools/audit-tools.ts
4409
4745
  import { randomUUID as randomUUID3 } from "crypto";
4410
- import { tool as tool4, createSdkMcpServer as createSdkMcpServer2 } from "@anthropic-ai/claude-agent-sdk";
4411
- import { z as z4 } from "zod";
4746
+ import { tool as tool5, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
4747
+ import { z as z5 } from "zod";
4412
4748
  function mapCreateTag(input) {
4413
4749
  return {
4414
4750
  type: "create_tag",
@@ -4490,14 +4826,14 @@ function collectRecommendation(toolName, input, collector, onRecommendation) {
4490
4826
  }
4491
4827
  function createAuditMcpServer(collector, onRecommendation) {
4492
4828
  const auditTools = [
4493
- tool4(
4829
+ tool5(
4494
4830
  "recommend_create_tag",
4495
4831
  "Recommend creating a new tag for an uncovered subsystem or area",
4496
4832
  {
4497
- name: z4.string().describe("Proposed tag name (lowercase, hyphenated)"),
4498
- color: z4.string().optional().describe("Hex color code"),
4499
- description: z4.string().describe("What this tag covers"),
4500
- reasoning: z4.string().describe("Why this tag should be created")
4833
+ name: z5.string().describe("Proposed tag name (lowercase, hyphenated)"),
4834
+ color: z5.string().optional().describe("Hex color code"),
4835
+ description: z5.string().describe("What this tag covers"),
4836
+ reasoning: z5.string().describe("Why this tag should be created")
4501
4837
  },
4502
4838
  async (args) => {
4503
4839
  const result = collectRecommendation(
@@ -4509,14 +4845,14 @@ function createAuditMcpServer(collector, onRecommendation) {
4509
4845
  return { content: [{ type: "text", text: result }] };
4510
4846
  }
4511
4847
  ),
4512
- tool4(
4848
+ tool5(
4513
4849
  "recommend_update_description",
4514
4850
  "Recommend updating a tag's description to better reflect its scope",
4515
4851
  {
4516
- tagId: z4.string(),
4517
- tagName: z4.string(),
4518
- description: z4.string().describe("Proposed new description"),
4519
- reasoning: z4.string()
4852
+ tagId: z5.string(),
4853
+ tagName: z5.string(),
4854
+ description: z5.string().describe("Proposed new description"),
4855
+ reasoning: z5.string()
4520
4856
  },
4521
4857
  async (args) => {
4522
4858
  const result = collectRecommendation(
@@ -4528,16 +4864,16 @@ function createAuditMcpServer(collector, onRecommendation) {
4528
4864
  return { content: [{ type: "text", text: result }] };
4529
4865
  }
4530
4866
  ),
4531
- tool4(
4867
+ tool5(
4532
4868
  "recommend_context_link",
4533
4869
  "Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
4534
4870
  {
4535
- tagId: z4.string(),
4536
- tagName: z4.string(),
4537
- linkType: z4.enum(["rule", "doc", "file", "folder"]),
4538
- path: z4.string(),
4539
- label: z4.string().optional(),
4540
- reasoning: z4.string()
4871
+ tagId: z5.string(),
4872
+ tagName: z5.string(),
4873
+ linkType: z5.enum(["rule", "doc", "file", "folder"]),
4874
+ path: z5.string(),
4875
+ label: z5.string().optional(),
4876
+ reasoning: z5.string()
4541
4877
  },
4542
4878
  async (args) => {
4543
4879
  const result = collectRecommendation(
@@ -4549,16 +4885,16 @@ function createAuditMcpServer(collector, onRecommendation) {
4549
4885
  return { content: [{ type: "text", text: result }] };
4550
4886
  }
4551
4887
  ),
4552
- tool4(
4888
+ tool5(
4553
4889
  "flag_documentation_gap",
4554
4890
  "Flag a file that agents read heavily but has no tag documentation linked",
4555
4891
  {
4556
- tagName: z4.string().describe("Tag whose agents read this file"),
4557
- tagId: z4.string().optional(),
4558
- filePath: z4.string(),
4559
- readCount: z4.number(),
4560
- suggestedAction: z4.string().describe("What doc or rule should be created"),
4561
- reasoning: z4.string()
4892
+ tagName: z5.string().describe("Tag whose agents read this file"),
4893
+ tagId: z5.string().optional(),
4894
+ filePath: z5.string(),
4895
+ readCount: z5.number(),
4896
+ suggestedAction: z5.string().describe("What doc or rule should be created"),
4897
+ reasoning: z5.string()
4562
4898
  },
4563
4899
  async (args) => {
4564
4900
  const result = collectRecommendation(
@@ -4570,15 +4906,15 @@ function createAuditMcpServer(collector, onRecommendation) {
4570
4906
  return { content: [{ type: "text", text: result }] };
4571
4907
  }
4572
4908
  ),
4573
- tool4(
4909
+ tool5(
4574
4910
  "recommend_merge_tags",
4575
4911
  "Recommend merging one tag into another",
4576
4912
  {
4577
- tagId: z4.string().describe("Tag ID to be merged (removed after merge)"),
4578
- tagName: z4.string().describe("Name of the tag to be merged"),
4579
- mergeIntoTagId: z4.string().describe("Tag ID to merge into (kept)"),
4580
- mergeIntoTagName: z4.string(),
4581
- reasoning: z4.string()
4913
+ tagId: z5.string().describe("Tag ID to be merged (removed after merge)"),
4914
+ tagName: z5.string().describe("Name of the tag to be merged"),
4915
+ mergeIntoTagId: z5.string().describe("Tag ID to merge into (kept)"),
4916
+ mergeIntoTagName: z5.string(),
4917
+ reasoning: z5.string()
4582
4918
  },
4583
4919
  async (args) => {
4584
4920
  const result = collectRecommendation(
@@ -4590,14 +4926,14 @@ function createAuditMcpServer(collector, onRecommendation) {
4590
4926
  return { content: [{ type: "text", text: result }] };
4591
4927
  }
4592
4928
  ),
4593
- tool4(
4929
+ tool5(
4594
4930
  "recommend_rename_tag",
4595
4931
  "Recommend renaming a tag",
4596
4932
  {
4597
- tagId: z4.string(),
4598
- tagName: z4.string().describe("Current tag name"),
4599
- newName: z4.string().describe("Proposed new name"),
4600
- reasoning: z4.string()
4933
+ tagId: z5.string(),
4934
+ tagName: z5.string().describe("Current tag name"),
4935
+ newName: z5.string().describe("Proposed new name"),
4936
+ reasoning: z5.string()
4601
4937
  },
4602
4938
  async (args) => {
4603
4939
  const result = collectRecommendation(
@@ -4609,10 +4945,10 @@ function createAuditMcpServer(collector, onRecommendation) {
4609
4945
  return { content: [{ type: "text", text: result }] };
4610
4946
  }
4611
4947
  ),
4612
- tool4(
4948
+ tool5(
4613
4949
  "complete_audit",
4614
4950
  "Signal that the audit is complete with a summary of all findings",
4615
- { summary: z4.string().describe("Brief overview of all findings") },
4951
+ { summary: z5.string().describe("Brief overview of all findings") },
4616
4952
  async (args) => {
4617
4953
  collector.complete = true;
4618
4954
  collector.summary = args.summary ?? "Audit completed.";
@@ -4620,7 +4956,7 @@ function createAuditMcpServer(collector, onRecommendation) {
4620
4956
  }
4621
4957
  )
4622
4958
  ];
4623
- return createSdkMcpServer2({
4959
+ return createSdkMcpServer3({
4624
4960
  name: "tag-audit",
4625
4961
  tools: auditTools
4626
4962
  });
@@ -4808,13 +5144,20 @@ function setupWorkDir(projectDir, assignment) {
4808
5144
  workDir = projectDir;
4809
5145
  }
4810
5146
  if (branch && branch !== devBranch) {
4811
- try {
4812
- execSync6(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
4813
- } catch {
5147
+ if (hasUncommittedChanges(workDir)) {
5148
+ logger6.warn("Uncommitted changes in work dir, skipping checkout", {
5149
+ taskId: shortId,
5150
+ branch
5151
+ });
5152
+ } else {
4814
5153
  try {
4815
- execSync6(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
5154
+ execSync7(`git checkout ${branch}`, { cwd: workDir, stdio: "ignore" });
4816
5155
  } catch {
4817
- logger6.warn("Could not checkout branch", { taskId: shortId, branch });
5156
+ try {
5157
+ execSync7(`git checkout -b ${branch}`, { cwd: workDir, stdio: "ignore" });
5158
+ } catch {
5159
+ logger6.warn("Could not checkout branch", { taskId: shortId, branch });
5160
+ }
4818
5161
  }
4819
5162
  }
4820
5163
  }
@@ -4871,6 +5214,7 @@ var ProjectRunner = class {
4871
5214
  heartbeatTimer = null;
4872
5215
  stopping = false;
4873
5216
  resolveLifecycle = null;
5217
+ chatSessionIds = /* @__PURE__ */ new Map();
4874
5218
  // Start command process management
4875
5219
  startCommandChild = null;
4876
5220
  startCommandRunning = false;
@@ -4918,9 +5262,20 @@ var ProjectRunner = class {
4918
5262
  checkoutWorkspaceBranch() {
4919
5263
  const workspaceBranch = process.env.CONVEYOR_WORKSPACE_BRANCH;
4920
5264
  if (!workspaceBranch) return;
5265
+ const currentBranch = this.getCurrentBranch();
5266
+ if (currentBranch === workspaceBranch) {
5267
+ logger6.info("Already on workspace branch", { workspaceBranch });
5268
+ return;
5269
+ }
5270
+ if (hasUncommittedChanges(this.projectDir)) {
5271
+ logger6.warn("Uncommitted changes detected, skipping workspace branch checkout", {
5272
+ workspaceBranch
5273
+ });
5274
+ return;
5275
+ }
4921
5276
  try {
4922
- execSync6(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
4923
- execSync6(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5277
+ execSync7(`git fetch origin ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
5278
+ execSync7(`git checkout ${workspaceBranch}`, { cwd: this.projectDir, stdio: "pipe" });
4924
5279
  logger6.info("Checked out workspace branch", { workspaceBranch });
4925
5280
  } catch (err) {
4926
5281
  logger6.warn("Failed to checkout workspace branch, continuing on current branch", {
@@ -5008,39 +5363,20 @@ var ProjectRunner = class {
5008
5363
  this.executeStartCommand();
5009
5364
  }
5010
5365
  getEnvironmentStatus() {
5011
- let currentBranch = "unknown";
5012
- try {
5013
- currentBranch = execSync6("git branch --show-current", {
5014
- cwd: this.projectDir,
5015
- stdio: ["ignore", "pipe", "ignore"]
5016
- }).toString().trim();
5017
- } catch {
5018
- }
5019
5366
  return {
5020
5367
  setupComplete: this.setupComplete,
5021
5368
  startCommandRunning: this.startCommandRunning,
5022
- currentBranch,
5369
+ currentBranch: this.getCurrentBranch() ?? "unknown",
5023
5370
  previewPort: Number(process.env.CONVEYOR_PREVIEW_PORT) || null
5024
5371
  };
5025
5372
  }
5026
5373
  getCurrentBranch() {
5027
- try {
5028
- return execSync6("git branch --show-current", {
5029
- cwd: this.projectDir,
5030
- stdio: ["ignore", "pipe", "ignore"]
5031
- }).toString().trim() || null;
5032
- } catch {
5033
- return null;
5034
- }
5374
+ return getCurrentBranch(this.projectDir);
5035
5375
  }
5036
5376
  // oxlint-disable-next-line max-lines-per-function, complexity -- sequential sync steps with per-step error handling
5037
5377
  async smartSync(previousSha, newSha, branch) {
5038
5378
  const stepsRun = [];
5039
- const status = execSync6("git status --porcelain", {
5040
- cwd: this.projectDir,
5041
- stdio: ["ignore", "pipe", "ignore"]
5042
- }).toString().trim();
5043
- if (status) {
5379
+ if (hasUncommittedChanges(this.projectDir)) {
5044
5380
  this.connection.emitEvent({
5045
5381
  type: "commit_watch_warning",
5046
5382
  message: "Working tree has uncommitted changes. Auto-pull skipped."
@@ -5050,7 +5386,7 @@ var ProjectRunner = class {
5050
5386
  await this.killStartCommand();
5051
5387
  this.connection.emitEnvSwitchProgress({ step: "pull", status: "running" });
5052
5388
  try {
5053
- execSync6(`git pull origin ${branch}`, {
5389
+ execSync7(`git pull origin ${branch}`, {
5054
5390
  cwd: this.projectDir,
5055
5391
  stdio: "pipe",
5056
5392
  timeout: 6e4
@@ -5066,7 +5402,7 @@ var ProjectRunner = class {
5066
5402
  }
5067
5403
  let changedFiles = [];
5068
5404
  try {
5069
- changedFiles = execSync6(`git diff --name-only ${previousSha}..${newSha}`, {
5405
+ changedFiles = execSync7(`git diff --name-only ${previousSha}..${newSha}`, {
5070
5406
  cwd: this.projectDir,
5071
5407
  stdio: ["ignore", "pipe", "ignore"]
5072
5408
  }).toString().trim().split("\n").filter(Boolean);
@@ -5096,7 +5432,7 @@ var ProjectRunner = class {
5096
5432
  if (needsInstall) {
5097
5433
  this.connection.emitEnvSwitchProgress({ step: "install", status: "running" });
5098
5434
  try {
5099
- execSync6("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
5435
+ execSync7("bun install", { cwd: this.projectDir, timeout: 12e4, stdio: "pipe" });
5100
5436
  stepsRun.push("install");
5101
5437
  this.connection.emitEnvSwitchProgress({ step: "install", status: "success" });
5102
5438
  } catch (err) {
@@ -5108,12 +5444,12 @@ var ProjectRunner = class {
5108
5444
  if (needsPrisma) {
5109
5445
  this.connection.emitEnvSwitchProgress({ step: "prisma", status: "running" });
5110
5446
  try {
5111
- execSync6("bunx prisma generate", {
5447
+ execSync7("bunx prisma generate", {
5112
5448
  cwd: this.projectDir,
5113
5449
  timeout: 6e4,
5114
5450
  stdio: "pipe"
5115
5451
  });
5116
- execSync6("bunx prisma db push --accept-data-loss", {
5452
+ execSync7("bunx prisma db push --accept-data-loss", {
5117
5453
  cwd: this.projectDir,
5118
5454
  timeout: 6e4,
5119
5455
  stdio: "pipe"
@@ -5136,14 +5472,15 @@ var ProjectRunner = class {
5136
5472
  try {
5137
5473
  this.connection.emitEnvSwitchProgress({ step: "fetch", status: "running" });
5138
5474
  try {
5139
- execSync6("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
5475
+ execSync7("git fetch origin", { cwd: this.projectDir, stdio: "pipe" });
5140
5476
  } catch {
5141
5477
  logger6.warn("Git fetch failed during branch switch");
5142
5478
  }
5143
5479
  this.connection.emitEnvSwitchProgress({ step: "fetch", status: "success" });
5480
+ detachWorktreeBranch(this.projectDir, branch);
5144
5481
  this.connection.emitEnvSwitchProgress({ step: "checkout", status: "running" });
5145
5482
  try {
5146
- execSync6(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5483
+ execSync7(`git checkout ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5147
5484
  } catch (err) {
5148
5485
  const message = err instanceof Error ? err.message : "Checkout failed";
5149
5486
  this.connection.emitEnvSwitchProgress({ step: "checkout", status: "error", message });
@@ -5151,7 +5488,7 @@ var ProjectRunner = class {
5151
5488
  return;
5152
5489
  }
5153
5490
  try {
5154
- execSync6(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5491
+ execSync7(`git pull origin ${branch}`, { cwd: this.projectDir, stdio: "pipe" });
5155
5492
  } catch {
5156
5493
  logger6.warn("Git pull failed during branch switch", { branch });
5157
5494
  }
@@ -5225,7 +5562,15 @@ var ProjectRunner = class {
5225
5562
  });
5226
5563
  this.connection.onChatMessage((msg) => {
5227
5564
  logger6.debug("Received project chat message");
5228
- void handleProjectChatMessage(msg, this.connection, this.projectDir);
5565
+ const chatId = msg.chatId ?? "default";
5566
+ const existingSessionId = this.chatSessionIds.get(chatId);
5567
+ void handleProjectChatMessage(msg, this.connection, this.projectDir, existingSessionId).then(
5568
+ (newSessionId) => {
5569
+ if (newSessionId) {
5570
+ this.chatSessionIds.set(chatId, newSessionId);
5571
+ }
5572
+ }
5573
+ );
5229
5574
  });
5230
5575
  this.connection.onAuditRequest((request) => {
5231
5576
  logger6.debug("Received tag audit request", { requestId: request.requestId });
@@ -5282,7 +5627,7 @@ var ProjectRunner = class {
5282
5627
  }
5283
5628
  try {
5284
5629
  try {
5285
- execSync6("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
5630
+ execSync7("git fetch origin", { cwd: this.projectDir, stdio: "ignore" });
5286
5631
  } catch {
5287
5632
  logger6.warn("Git fetch failed", { taskId: shortId });
5288
5633
  }
@@ -5456,4 +5801,4 @@ export {
5456
5801
  ProjectRunner,
5457
5802
  FileCache
5458
5803
  };
5459
- //# sourceMappingURL=chunk-U3YWTVH3.js.map
5804
+ //# sourceMappingURL=chunk-4RFYCO2T.js.map