@rallycry/conveyor-agent 6.0.7 → 6.0.8
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.
- package/dist/{chunk-ANYHEBDY.js → chunk-T6IASOS2.js} +347 -87
- package/dist/chunk-T6IASOS2.js.map +1 -0
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ANYHEBDY.js.map +0 -1
|
@@ -487,6 +487,10 @@ var ConveyorConnection = class _ConveyorConnection {
|
|
|
487
487
|
if (!this.socket) return;
|
|
488
488
|
this.socket.emit("agentRunner:debugReproduceRequested", { hypothesis });
|
|
489
489
|
}
|
|
490
|
+
emitCodeReviewResult(content, approved) {
|
|
491
|
+
if (!this.socket) throw new Error("Not connected");
|
|
492
|
+
this.socket.emit("agentRunner:codeReviewResult", { content, approved });
|
|
493
|
+
}
|
|
490
494
|
searchIncidents(status, source) {
|
|
491
495
|
return searchIncidents(this.socket, status, source);
|
|
492
496
|
}
|
|
@@ -1487,7 +1491,13 @@ var API_ERROR_PATTERN2 = /API Error: [45]\d\d/;
|
|
|
1487
1491
|
function stopTypingIfNeeded(host, isTyping) {
|
|
1488
1492
|
if (isTyping) host.connection.sendTypingStop();
|
|
1489
1493
|
}
|
|
1490
|
-
function
|
|
1494
|
+
function shouldBreakEventLoop(host, state) {
|
|
1495
|
+
if (host.isStopped()) return true;
|
|
1496
|
+
if (state.loopDetected) return true;
|
|
1497
|
+
return false;
|
|
1498
|
+
}
|
|
1499
|
+
function flushPendingToolCalls(host, state) {
|
|
1500
|
+
const { turnToolCalls } = state;
|
|
1491
1501
|
if (turnToolCalls.length === 0) return;
|
|
1492
1502
|
for (let i = 0; i < turnToolCalls.length; i++) {
|
|
1493
1503
|
if (i < host.pendingToolOutputs.length) {
|
|
@@ -1495,6 +1505,12 @@ function flushPendingToolCalls(host, turnToolCalls) {
|
|
|
1495
1505
|
}
|
|
1496
1506
|
}
|
|
1497
1507
|
host.connection.sendEvent({ type: "turn_end", toolCalls: [...turnToolCalls] });
|
|
1508
|
+
if (host.loopDetector.recordTurn(turnToolCalls)) {
|
|
1509
|
+
state.loopDetected = true;
|
|
1510
|
+
host.connection.postChatMessage(
|
|
1511
|
+
"Loop detected: the agent has been repeating the same tool pattern. Intervening to change strategy..."
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1498
1514
|
turnToolCalls.length = 0;
|
|
1499
1515
|
host.pendingToolOutputs.length = 0;
|
|
1500
1516
|
}
|
|
@@ -1552,12 +1568,13 @@ async function processEvents(events, context, host) {
|
|
|
1552
1568
|
rateLimitResetsAt: void 0,
|
|
1553
1569
|
staleSession: void 0,
|
|
1554
1570
|
authError: void 0,
|
|
1571
|
+
loopDetected: false,
|
|
1555
1572
|
lastAssistantUsage: void 0,
|
|
1556
1573
|
turnToolCalls: []
|
|
1557
1574
|
};
|
|
1558
1575
|
for await (const event of events) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1576
|
+
flushPendingToolCalls(host, state);
|
|
1577
|
+
if (shouldBreakEventLoop(host, state)) break;
|
|
1561
1578
|
const now = Date.now();
|
|
1562
1579
|
if (now - lastStatusEmit >= STATUS_REEMIT_INTERVAL_MS) {
|
|
1563
1580
|
host.connection.emitStatus("running");
|
|
@@ -1587,14 +1604,15 @@ async function processEvents(events, context, host) {
|
|
|
1587
1604
|
break;
|
|
1588
1605
|
}
|
|
1589
1606
|
}
|
|
1590
|
-
flushPendingToolCalls(host, state
|
|
1607
|
+
flushPendingToolCalls(host, state);
|
|
1591
1608
|
stopTypingIfNeeded(host, state.isTyping);
|
|
1592
1609
|
return {
|
|
1593
1610
|
retriable: state.retriable || state.sawApiError,
|
|
1594
1611
|
resultSummary: state.resultSummary,
|
|
1595
1612
|
rateLimitResetsAt: state.rateLimitResetsAt,
|
|
1596
1613
|
...state.staleSession && { staleSession: state.staleSession },
|
|
1597
|
-
...state.authError && { authError: state.authError }
|
|
1614
|
+
...state.authError && { authError: state.authError },
|
|
1615
|
+
...state.loopDetected && { loopDetected: state.loopDetected }
|
|
1598
1616
|
};
|
|
1599
1617
|
}
|
|
1600
1618
|
|
|
@@ -2035,10 +2053,52 @@ function buildModePrompt(agentMode, context) {
|
|
|
2035
2053
|
].join("\n");
|
|
2036
2054
|
case "auto":
|
|
2037
2055
|
return buildAutoPrompt(context);
|
|
2056
|
+
case "code-review":
|
|
2057
|
+
return buildCodeReviewPrompt();
|
|
2038
2058
|
default:
|
|
2039
2059
|
return null;
|
|
2040
2060
|
}
|
|
2041
2061
|
}
|
|
2062
|
+
function buildCodeReviewPrompt() {
|
|
2063
|
+
return [
|
|
2064
|
+
`
|
|
2065
|
+
## Mode: Code Review`,
|
|
2066
|
+
`You are an automated code reviewer. A PR has passed all CI checks and you are performing a final code quality review before merge.`,
|
|
2067
|
+
``,
|
|
2068
|
+
`## Review Process`,
|
|
2069
|
+
`1. Run \`git diff <devBranch>..HEAD\` to see all changes in this PR`,
|
|
2070
|
+
`2. Read the task plan to understand the intended changes`,
|
|
2071
|
+
`3. Explore the surrounding codebase to verify pattern consistency`,
|
|
2072
|
+
`4. Review against the criteria below`,
|
|
2073
|
+
``,
|
|
2074
|
+
`### Review Criteria`,
|
|
2075
|
+
`- **Correctness**: Does the code do what the plan says? Logic errors, off-by-one, race conditions?`,
|
|
2076
|
+
`- **Pattern Consistency**: Does the code follow existing patterns in the codebase? Check nearby files.`,
|
|
2077
|
+
`- **Security**: No hardcoded secrets, no injection vulnerabilities, proper input validation at boundaries.`,
|
|
2078
|
+
`- **Performance**: No unnecessary loops, no N+1 queries, no blocking in async contexts.`,
|
|
2079
|
+
`- **Error Handling**: Appropriate error handling at system boundaries. No swallowed errors.`,
|
|
2080
|
+
`- **Test Coverage**: Are new code paths tested? Edge cases covered?`,
|
|
2081
|
+
`- **TypeScript Best Practices**: Proper typing (no unnecessary \`any\`), correct React patterns, proper async/await.`,
|
|
2082
|
+
`- **Naming & Readability**: Clear names, no misleading comments, self-documenting code.`,
|
|
2083
|
+
``,
|
|
2084
|
+
`## Output \u2014 You MUST do exactly ONE of:`,
|
|
2085
|
+
``,
|
|
2086
|
+
`### If code passes review:`,
|
|
2087
|
+
`Use the \`approve_code_review\` tool with a brief summary of what looks good.`,
|
|
2088
|
+
``,
|
|
2089
|
+
`### If changes are needed:`,
|
|
2090
|
+
`Use the \`request_code_changes\` tool with specific issues:`,
|
|
2091
|
+
`- Reference specific files and line numbers`,
|
|
2092
|
+
`- Explain what's wrong and suggest fixes`,
|
|
2093
|
+
`- Focus on substantive issues, not style nitpicks (linting handles that)`,
|
|
2094
|
+
``,
|
|
2095
|
+
`## Rules`,
|
|
2096
|
+
`- You are READ-ONLY. Do NOT modify any files.`,
|
|
2097
|
+
`- Do NOT re-review things CI already validates (formatting, lint rules).`,
|
|
2098
|
+
`- Be concise \u2014 the task agent needs actionable feedback, not essays.`,
|
|
2099
|
+
`- Max 5-7 issues per review. Prioritize the most important ones.`
|
|
2100
|
+
].join("\n");
|
|
2101
|
+
}
|
|
2042
2102
|
|
|
2043
2103
|
// src/execution/system-prompt.ts
|
|
2044
2104
|
function formatProjectAgentLine(pa) {
|
|
@@ -2410,6 +2470,18 @@ ${context.plan}`);
|
|
|
2410
2470
|
}
|
|
2411
2471
|
return parts;
|
|
2412
2472
|
}
|
|
2473
|
+
function buildCodeReviewInstructions(context) {
|
|
2474
|
+
const parts = [
|
|
2475
|
+
`You are performing an automated code review for this task.`,
|
|
2476
|
+
`The PR branch is "${context.githubBranch}"${context.baseBranch ? ` based on "${context.baseBranch}"` : ""}.`,
|
|
2477
|
+
`Begin your code review by running \`git diff ${context.baseBranch ?? "dev"}..HEAD\` to see all changes.`,
|
|
2478
|
+
``,
|
|
2479
|
+
`CRITICAL: You are in Code Review mode. You are READ-ONLY \u2014 do NOT modify any files.`,
|
|
2480
|
+
`After reviewing, you MUST call exactly one of: \`approve_code_review\` or \`request_code_changes\`.`,
|
|
2481
|
+
`Do NOT go idle, ask for confirmation, or wait for instructions \u2014 complete the review and exit.`
|
|
2482
|
+
];
|
|
2483
|
+
return parts;
|
|
2484
|
+
}
|
|
2413
2485
|
function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
|
|
2414
2486
|
if (isPm && agentMode === "building") {
|
|
2415
2487
|
return [
|
|
@@ -2507,6 +2579,10 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
2507
2579
|
function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
2508
2580
|
const parts = [`
|
|
2509
2581
|
## Instructions`];
|
|
2582
|
+
if (agentMode === "code-review") {
|
|
2583
|
+
parts.push(...buildCodeReviewInstructions(context));
|
|
2584
|
+
return parts;
|
|
2585
|
+
}
|
|
2510
2586
|
const isPm = mode === "pm";
|
|
2511
2587
|
if (scenario === "fresh") {
|
|
2512
2588
|
parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context, agentMode));
|
|
@@ -2558,7 +2634,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2558
2634
|
}
|
|
2559
2635
|
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
2560
2636
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
2561
|
-
if (!isPackRunner) {
|
|
2637
|
+
if (!isPackRunner && agentMode !== "code-review") {
|
|
2562
2638
|
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode, isAuto);
|
|
2563
2639
|
if (sessionRelaunch) return sessionRelaunch;
|
|
2564
2640
|
}
|
|
@@ -4365,6 +4441,73 @@ function buildDebugTools(manager) {
|
|
|
4365
4441
|
];
|
|
4366
4442
|
}
|
|
4367
4443
|
|
|
4444
|
+
// src/tools/code-review-tools.ts
|
|
4445
|
+
import { tool as tool7 } from "@anthropic-ai/claude-agent-sdk";
|
|
4446
|
+
import { z as z7 } from "zod";
|
|
4447
|
+
function buildCodeReviewTools(connection) {
|
|
4448
|
+
return [
|
|
4449
|
+
tool7(
|
|
4450
|
+
"approve_code_review",
|
|
4451
|
+
"Approve the code review. Use this when the code passes all review criteria and is ready to merge.",
|
|
4452
|
+
{
|
|
4453
|
+
summary: z7.string().describe("Brief summary of what was reviewed and why it looks good")
|
|
4454
|
+
},
|
|
4455
|
+
// eslint-disable-next-line require-await -- SDK tool() API requires async handler
|
|
4456
|
+
async ({ summary }) => {
|
|
4457
|
+
connection.emitCodeReviewResult(
|
|
4458
|
+
`**Code Review: Approved** :white_check_mark:
|
|
4459
|
+
|
|
4460
|
+
${summary}`,
|
|
4461
|
+
true
|
|
4462
|
+
);
|
|
4463
|
+
connection.sendEvent({
|
|
4464
|
+
type: "code_review_complete",
|
|
4465
|
+
result: "approved",
|
|
4466
|
+
summary
|
|
4467
|
+
});
|
|
4468
|
+
return textResult("Code review approved. Exiting.");
|
|
4469
|
+
}
|
|
4470
|
+
),
|
|
4471
|
+
tool7(
|
|
4472
|
+
"request_code_changes",
|
|
4473
|
+
"Request changes during code review. Use this when substantive issues are found that need to be fixed before merge.",
|
|
4474
|
+
{
|
|
4475
|
+
issues: z7.array(
|
|
4476
|
+
z7.object({
|
|
4477
|
+
file: z7.string().describe("File path where the issue was found"),
|
|
4478
|
+
line: z7.number().optional().describe("Line number (if applicable)"),
|
|
4479
|
+
severity: z7.enum(["critical", "major", "minor"]).describe("Issue severity"),
|
|
4480
|
+
description: z7.string().describe("What is wrong and how to fix it")
|
|
4481
|
+
})
|
|
4482
|
+
).describe("List of issues found during review"),
|
|
4483
|
+
summary: z7.string().describe("Brief overall summary of the review findings")
|
|
4484
|
+
},
|
|
4485
|
+
// eslint-disable-next-line require-await -- SDK tool() API requires async handler
|
|
4486
|
+
async ({ issues, summary }) => {
|
|
4487
|
+
const issueLines = issues.map((issue) => {
|
|
4488
|
+
const loc = issue.line ? `:${issue.line}` : "";
|
|
4489
|
+
return `- **[${issue.severity}]** \`${issue.file}${loc}\`: ${issue.description}`;
|
|
4490
|
+
}).join("\n");
|
|
4491
|
+
connection.emitCodeReviewResult(
|
|
4492
|
+
`**Code Review: Changes Requested** :warning:
|
|
4493
|
+
|
|
4494
|
+
${summary}
|
|
4495
|
+
|
|
4496
|
+
${issueLines}`,
|
|
4497
|
+
false
|
|
4498
|
+
);
|
|
4499
|
+
connection.sendEvent({
|
|
4500
|
+
type: "code_review_complete",
|
|
4501
|
+
result: "changes_requested",
|
|
4502
|
+
summary,
|
|
4503
|
+
issues
|
|
4504
|
+
});
|
|
4505
|
+
return textResult("Code review complete \u2014 changes requested. Exiting.");
|
|
4506
|
+
}
|
|
4507
|
+
)
|
|
4508
|
+
];
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4368
4511
|
// src/tools/index.ts
|
|
4369
4512
|
function textResult(text) {
|
|
4370
4513
|
return { content: [{ type: "text", text }] };
|
|
@@ -4395,8 +4538,22 @@ function getModeTools(agentMode, connection, config, context) {
|
|
|
4395
4538
|
}
|
|
4396
4539
|
}
|
|
4397
4540
|
function createConveyorMcpServer(connection, config, context, agentMode, debugManager) {
|
|
4398
|
-
const commonTools = buildCommonTools(connection, config);
|
|
4399
4541
|
const effectiveMode = agentMode ?? context?.agentMode ?? void 0;
|
|
4542
|
+
if (effectiveMode === "code-review") {
|
|
4543
|
+
return createSdkMcpServer({
|
|
4544
|
+
name: "conveyor",
|
|
4545
|
+
tools: [
|
|
4546
|
+
buildReadTaskChatTool(connection),
|
|
4547
|
+
buildGetTaskPlanTool(connection, config),
|
|
4548
|
+
buildGetTaskTool(connection),
|
|
4549
|
+
buildGetTaskCliTool(connection),
|
|
4550
|
+
buildListTaskFilesTool(connection),
|
|
4551
|
+
buildGetTaskFileTool(connection),
|
|
4552
|
+
...buildCodeReviewTools(connection)
|
|
4553
|
+
]
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
const commonTools = buildCommonTools(connection, config);
|
|
4400
4557
|
const modeTools = getModeTools(effectiveMode, connection, config, context);
|
|
4401
4558
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
4402
4559
|
const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
|
|
@@ -4411,6 +4568,7 @@ function createConveyorMcpServer(connection, config, context, agentMode, debugMa
|
|
|
4411
4568
|
import { randomUUID } from "crypto";
|
|
4412
4569
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
4413
4570
|
var DESTRUCTIVE_CMD_PATTERN = /git\s+push\s+--force(?!\s*-with-lease)|git\s+reset\s+--hard|rm\s+-rf\s+\//;
|
|
4571
|
+
var CODE_REVIEW_WRITE_CMD_PATTERN = /git\s+push|git\s+commit|git\s+add|rm\s+|mv\s+|cp\s+|mkdir\s+|touch\s+|chmod\s+|chown\s+/;
|
|
4414
4572
|
function isPlanFile(input) {
|
|
4415
4573
|
const filePath = String(input.file_path ?? input.path ?? "");
|
|
4416
4574
|
return filePath.includes(".claude/plans/");
|
|
@@ -4460,6 +4618,24 @@ function handleReviewToolAccess(toolName, input, isParentTask) {
|
|
|
4460
4618
|
}
|
|
4461
4619
|
return { behavior: "allow", updatedInput: input };
|
|
4462
4620
|
}
|
|
4621
|
+
function handleCodeReviewToolAccess(toolName, input) {
|
|
4622
|
+
if (PM_PLAN_FILE_TOOLS.has(toolName)) {
|
|
4623
|
+
return {
|
|
4624
|
+
behavior: "deny",
|
|
4625
|
+
message: "Code review mode is fully read-only. File writes are not permitted."
|
|
4626
|
+
};
|
|
4627
|
+
}
|
|
4628
|
+
if (toolName === "Bash") {
|
|
4629
|
+
const cmd = String(input.command ?? "");
|
|
4630
|
+
if (DESTRUCTIVE_CMD_PATTERN.test(cmd) || CODE_REVIEW_WRITE_CMD_PATTERN.test(cmd)) {
|
|
4631
|
+
return {
|
|
4632
|
+
behavior: "deny",
|
|
4633
|
+
message: "Code review mode is read-only. Write operations and destructive commands are blocked."
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
return { behavior: "allow", updatedInput: input };
|
|
4638
|
+
}
|
|
4463
4639
|
function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
|
|
4464
4640
|
if (hasExitedPlanMode) {
|
|
4465
4641
|
return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
|
|
@@ -4564,6 +4740,9 @@ function buildCanUseTool(host) {
|
|
|
4564
4740
|
case "auto":
|
|
4565
4741
|
result = handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
4566
4742
|
break;
|
|
4743
|
+
case "code-review":
|
|
4744
|
+
result = handleCodeReviewToolAccess(toolName, input);
|
|
4745
|
+
break;
|
|
4567
4746
|
default:
|
|
4568
4747
|
result = { behavior: "allow", updatedInput: input };
|
|
4569
4748
|
}
|
|
@@ -4611,10 +4790,13 @@ function buildHooks(host) {
|
|
|
4611
4790
|
};
|
|
4612
4791
|
}
|
|
4613
4792
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
4614
|
-
return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
|
|
4793
|
+
return mode === "discovery" || mode === "help" || mode === "code-review" || mode === "auto" && !hasExitedPlanMode;
|
|
4615
4794
|
}
|
|
4616
4795
|
function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
|
|
4617
4796
|
const modeDisallowed = isReadOnlyMode(mode, hasExitedPlanMode) ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
4797
|
+
if (mode === "code-review") {
|
|
4798
|
+
modeDisallowed.push("ExitPlanMode", "EnterPlanMode");
|
|
4799
|
+
}
|
|
4618
4800
|
const configured = settings.disallowedTools ?? [];
|
|
4619
4801
|
const combined = [...configured, ...modeDisallowed];
|
|
4620
4802
|
return combined.length > 0 ? combined : void 0;
|
|
@@ -4647,11 +4829,11 @@ function buildQueryOptions(host, context) {
|
|
|
4647
4829
|
tools: { type: "preset", preset: "claude_code" },
|
|
4648
4830
|
mcpServers: { conveyor: createConveyorMcpServer(host.connection, host.config, context, mode) },
|
|
4649
4831
|
hooks: buildHooks(host),
|
|
4650
|
-
maxTurns: settings.maxTurns,
|
|
4832
|
+
maxTurns: mode === "code-review" ? Math.min(settings.maxTurns ?? 15, 15) : settings.maxTurns,
|
|
4651
4833
|
effort: settings.effort,
|
|
4652
4834
|
thinking: settings.thinking,
|
|
4653
4835
|
betas: settings.betas,
|
|
4654
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 50,
|
|
4836
|
+
maxBudgetUsd: mode === "code-review" ? Math.min(settings.maxBudgetUsd ?? 10, 10) : settings.maxBudgetUsd ?? 50,
|
|
4655
4837
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
4656
4838
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
4657
4839
|
stderr: (data) => {
|
|
@@ -4891,6 +5073,17 @@ function handleProcessResult(result, context, host, options) {
|
|
|
4891
5073
|
if (result.authError) {
|
|
4892
5074
|
return { action: "return_promise", promise: handleAuthError(context, host, options) };
|
|
4893
5075
|
}
|
|
5076
|
+
if (result.loopDetected) {
|
|
5077
|
+
host.loopDetector.reset();
|
|
5078
|
+
return {
|
|
5079
|
+
action: "return_promise",
|
|
5080
|
+
promise: runSdkQuery(
|
|
5081
|
+
host,
|
|
5082
|
+
context,
|
|
5083
|
+
"You've been repeating the same tool pattern for several consecutive turns. Step back, analyze why your current approach is failing, and try a fundamentally different strategy. Do NOT repeat the same sequence of actions."
|
|
5084
|
+
)
|
|
5085
|
+
};
|
|
5086
|
+
}
|
|
4894
5087
|
if (!result.retriable) return { action: "return" };
|
|
4895
5088
|
return {
|
|
4896
5089
|
action: "continue",
|
|
@@ -4960,6 +5153,38 @@ var CostTracker = class {
|
|
|
4960
5153
|
}
|
|
4961
5154
|
};
|
|
4962
5155
|
|
|
5156
|
+
// src/execution/loop-detector.ts
|
|
5157
|
+
var DEFAULT_MAX_CONSECUTIVE_REPEATS = 3;
|
|
5158
|
+
var LoopDetector = class _LoopDetector {
|
|
5159
|
+
fingerprints = [];
|
|
5160
|
+
maxConsecutiveRepeats;
|
|
5161
|
+
constructor(maxConsecutiveRepeats = DEFAULT_MAX_CONSECUTIVE_REPEATS) {
|
|
5162
|
+
this.maxConsecutiveRepeats = maxConsecutiveRepeats;
|
|
5163
|
+
}
|
|
5164
|
+
/** Build a fingerprint from a turn's tool calls: sorted unique tool names joined by comma. */
|
|
5165
|
+
static fingerprint(toolCalls) {
|
|
5166
|
+
const names = [...new Set(toolCalls.map((tc) => tc.tool))].sort();
|
|
5167
|
+
return names.join(",");
|
|
5168
|
+
}
|
|
5169
|
+
/** Record a completed turn and return whether a loop is detected. */
|
|
5170
|
+
recordTurn(toolCalls) {
|
|
5171
|
+
if (toolCalls.length === 0) return false;
|
|
5172
|
+
const fp = _LoopDetector.fingerprint(toolCalls);
|
|
5173
|
+
this.fingerprints.push(fp);
|
|
5174
|
+
if (this.fingerprints.length < this.maxConsecutiveRepeats) return false;
|
|
5175
|
+
const recent = this.fingerprints.slice(-this.maxConsecutiveRepeats);
|
|
5176
|
+
return recent.every((f) => f === fp);
|
|
5177
|
+
}
|
|
5178
|
+
/** Reset state (e.g. after a successful intervention breaks the loop). */
|
|
5179
|
+
reset() {
|
|
5180
|
+
this.fingerprints.length = 0;
|
|
5181
|
+
}
|
|
5182
|
+
/** Number of recorded turns. */
|
|
5183
|
+
get turnCount() {
|
|
5184
|
+
return this.fingerprints.length;
|
|
5185
|
+
}
|
|
5186
|
+
};
|
|
5187
|
+
|
|
4963
5188
|
// src/runner/plan-sync.ts
|
|
4964
5189
|
import { readdirSync, statSync, readFileSync } from "fs";
|
|
4965
5190
|
import { join as join3 } from "path";
|
|
@@ -5311,6 +5536,7 @@ function buildQueryHost(deps) {
|
|
|
5311
5536
|
callbacks: deps.callbacks,
|
|
5312
5537
|
setupLog: deps.setupLog,
|
|
5313
5538
|
costTracker: deps.costTracker,
|
|
5539
|
+
loopDetector: deps.loopDetector,
|
|
5314
5540
|
get agentMode() {
|
|
5315
5541
|
return deps.getEffectiveAgentMode();
|
|
5316
5542
|
},
|
|
@@ -5358,10 +5584,12 @@ var AgentRunner = class {
|
|
|
5358
5584
|
taskContext = null;
|
|
5359
5585
|
planSync;
|
|
5360
5586
|
costTracker = new CostTracker();
|
|
5587
|
+
loopDetector = new LoopDetector();
|
|
5361
5588
|
worktreeActive = false;
|
|
5362
5589
|
agentMode = null;
|
|
5363
5590
|
hasExitedPlanMode = false;
|
|
5364
5591
|
pendingModeRestart = false;
|
|
5592
|
+
pendingModeAutoStart = false;
|
|
5365
5593
|
sessionIds = /* @__PURE__ */ new Map();
|
|
5366
5594
|
lastQueryModeRestart = false;
|
|
5367
5595
|
startCommandStarted = false;
|
|
@@ -5565,6 +5793,12 @@ var AgentRunner = class {
|
|
|
5565
5793
|
async executeInitialMode() {
|
|
5566
5794
|
if (!this.taskContext) return;
|
|
5567
5795
|
const mode = this.effectiveAgentMode;
|
|
5796
|
+
if (mode === "code-review") {
|
|
5797
|
+
await this.setState("running");
|
|
5798
|
+
await this.runQuerySafe(this.taskContext);
|
|
5799
|
+
this.stopped = true;
|
|
5800
|
+
return;
|
|
5801
|
+
}
|
|
5568
5802
|
const shouldRun = mode === "building" || mode === "auto" || mode === "review" && !!this.taskContext.isParentTask;
|
|
5569
5803
|
if (shouldRun) {
|
|
5570
5804
|
await this.setState("running");
|
|
@@ -5621,6 +5855,12 @@ var AgentRunner = class {
|
|
|
5621
5855
|
await this.handleModeRestartCycle();
|
|
5622
5856
|
continue;
|
|
5623
5857
|
}
|
|
5858
|
+
if (this.pendingModeAutoStart) {
|
|
5859
|
+
this.pendingModeAutoStart = false;
|
|
5860
|
+
this.interrupted = false;
|
|
5861
|
+
await this.handleModeRestartCycle();
|
|
5862
|
+
continue;
|
|
5863
|
+
}
|
|
5624
5864
|
if (this._state === "idle") {
|
|
5625
5865
|
const msg = await this.waitForUserContent();
|
|
5626
5866
|
if (!msg) {
|
|
@@ -5748,6 +5988,7 @@ var AgentRunner = class {
|
|
|
5748
5988
|
callbacks: this.callbacks,
|
|
5749
5989
|
setupLog: this.setupLog,
|
|
5750
5990
|
costTracker: this.costTracker,
|
|
5991
|
+
loopDetector: this.loopDetector,
|
|
5751
5992
|
planSync: this.planSync,
|
|
5752
5993
|
sessionIds: this.sessionIds,
|
|
5753
5994
|
getEffectiveAgentMode: () => this.effectiveAgentMode,
|
|
@@ -5772,15 +6013,32 @@ var AgentRunner = class {
|
|
|
5772
6013
|
handleModeChange(newAgentMode) {
|
|
5773
6014
|
if (this.config.mode !== "pm") return;
|
|
5774
6015
|
if (newAgentMode) this.agentMode = newAgentMode;
|
|
6016
|
+
this.updateExitedPlanModeFlag(newAgentMode);
|
|
5775
6017
|
const effectiveMode = this.effectiveAgentMode;
|
|
6018
|
+
const isBuildCapable = effectiveMode === "building" || effectiveMode === "auto" && this.hasExitedPlanMode;
|
|
5776
6019
|
this.connection.emitModeChanged(effectiveMode);
|
|
5777
6020
|
this.connection.postChatMessage(
|
|
5778
6021
|
`Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
|
|
5779
6022
|
);
|
|
5780
|
-
if (
|
|
6023
|
+
if (isBuildCapable && this.taskContext?.status === "Open") {
|
|
5781
6024
|
this.connection.updateStatus("InProgress");
|
|
5782
6025
|
this.taskContext.status = "InProgress";
|
|
5783
6026
|
}
|
|
6027
|
+
if (isBuildCapable) {
|
|
6028
|
+
this.pendingModeAutoStart = true;
|
|
6029
|
+
this.softStop();
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
updateExitedPlanModeFlag(newAgentMode) {
|
|
6033
|
+
if (newAgentMode === "building") {
|
|
6034
|
+
this.hasExitedPlanMode = true;
|
|
6035
|
+
return;
|
|
6036
|
+
}
|
|
6037
|
+
if (newAgentMode !== "auto") return;
|
|
6038
|
+
const pastPlanning = this.taskContext?.status !== "Planning" && this.taskContext?.status !== "Unidentified";
|
|
6039
|
+
if (pastPlanning) {
|
|
6040
|
+
this.hasExitedPlanMode = true;
|
|
6041
|
+
}
|
|
5784
6042
|
}
|
|
5785
6043
|
softStop() {
|
|
5786
6044
|
this.interrupted = true;
|
|
@@ -5926,17 +6184,17 @@ import {
|
|
|
5926
6184
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
5927
6185
|
|
|
5928
6186
|
// src/tools/project-tools.ts
|
|
5929
|
-
import { tool as
|
|
5930
|
-
import { z as
|
|
6187
|
+
import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
6188
|
+
import { z as z8 } from "zod";
|
|
5931
6189
|
function buildReadTools(connection) {
|
|
5932
6190
|
return [
|
|
5933
|
-
|
|
6191
|
+
tool8(
|
|
5934
6192
|
"list_tasks",
|
|
5935
6193
|
"List tasks in the project. Optionally filter by status or assignee.",
|
|
5936
6194
|
{
|
|
5937
|
-
status:
|
|
5938
|
-
assigneeId:
|
|
5939
|
-
limit:
|
|
6195
|
+
status: z8.string().optional().describe("Filter by task status (e.g. Planning, Open, InProgress, ReviewPR, Complete)"),
|
|
6196
|
+
assigneeId: z8.string().optional().describe("Filter by assigned user ID"),
|
|
6197
|
+
limit: z8.number().optional().describe("Max number of tasks to return (default 50)")
|
|
5940
6198
|
},
|
|
5941
6199
|
async (params) => {
|
|
5942
6200
|
try {
|
|
@@ -5950,10 +6208,10 @@ function buildReadTools(connection) {
|
|
|
5950
6208
|
},
|
|
5951
6209
|
{ annotations: { readOnlyHint: true } }
|
|
5952
6210
|
),
|
|
5953
|
-
|
|
6211
|
+
tool8(
|
|
5954
6212
|
"get_task",
|
|
5955
6213
|
"Get detailed information about a task including its chat messages, child tasks, and codespace status.",
|
|
5956
|
-
{ task_id:
|
|
6214
|
+
{ task_id: z8.string().describe("The task ID to look up") },
|
|
5957
6215
|
async ({ task_id }) => {
|
|
5958
6216
|
try {
|
|
5959
6217
|
const task = await connection.requestGetTask(task_id);
|
|
@@ -5966,14 +6224,14 @@ function buildReadTools(connection) {
|
|
|
5966
6224
|
},
|
|
5967
6225
|
{ annotations: { readOnlyHint: true } }
|
|
5968
6226
|
),
|
|
5969
|
-
|
|
6227
|
+
tool8(
|
|
5970
6228
|
"search_tasks",
|
|
5971
6229
|
"Search tasks by tags, text query, or status filters.",
|
|
5972
6230
|
{
|
|
5973
|
-
tagNames:
|
|
5974
|
-
searchQuery:
|
|
5975
|
-
statusFilters:
|
|
5976
|
-
limit:
|
|
6231
|
+
tagNames: z8.array(z8.string()).optional().describe("Filter by tag names"),
|
|
6232
|
+
searchQuery: z8.string().optional().describe("Text search in title/description"),
|
|
6233
|
+
statusFilters: z8.array(z8.string()).optional().describe("Filter by statuses"),
|
|
6234
|
+
limit: z8.number().optional().describe("Max results (default 20)")
|
|
5977
6235
|
},
|
|
5978
6236
|
async (params) => {
|
|
5979
6237
|
try {
|
|
@@ -5987,7 +6245,7 @@ function buildReadTools(connection) {
|
|
|
5987
6245
|
},
|
|
5988
6246
|
{ annotations: { readOnlyHint: true } }
|
|
5989
6247
|
),
|
|
5990
|
-
|
|
6248
|
+
tool8(
|
|
5991
6249
|
"list_tags",
|
|
5992
6250
|
"List all tags available in the project.",
|
|
5993
6251
|
{},
|
|
@@ -6003,7 +6261,7 @@ function buildReadTools(connection) {
|
|
|
6003
6261
|
},
|
|
6004
6262
|
{ annotations: { readOnlyHint: true } }
|
|
6005
6263
|
),
|
|
6006
|
-
|
|
6264
|
+
tool8(
|
|
6007
6265
|
"get_project_summary",
|
|
6008
6266
|
"Get a summary of the project including task counts by status and active builds.",
|
|
6009
6267
|
{},
|
|
@@ -6023,15 +6281,15 @@ function buildReadTools(connection) {
|
|
|
6023
6281
|
}
|
|
6024
6282
|
function buildMutationTools(connection) {
|
|
6025
6283
|
return [
|
|
6026
|
-
|
|
6284
|
+
tool8(
|
|
6027
6285
|
"create_task",
|
|
6028
6286
|
"Create a new task in the project.",
|
|
6029
6287
|
{
|
|
6030
|
-
title:
|
|
6031
|
-
description:
|
|
6032
|
-
plan:
|
|
6033
|
-
status:
|
|
6034
|
-
isBug:
|
|
6288
|
+
title: z8.string().describe("Task title"),
|
|
6289
|
+
description: z8.string().optional().describe("Task description"),
|
|
6290
|
+
plan: z8.string().optional().describe("Implementation plan in markdown"),
|
|
6291
|
+
status: z8.string().optional().describe("Initial status (default: Planning)"),
|
|
6292
|
+
isBug: z8.boolean().optional().describe("Whether this is a bug report")
|
|
6035
6293
|
},
|
|
6036
6294
|
async (params) => {
|
|
6037
6295
|
try {
|
|
@@ -6044,16 +6302,16 @@ function buildMutationTools(connection) {
|
|
|
6044
6302
|
}
|
|
6045
6303
|
}
|
|
6046
6304
|
),
|
|
6047
|
-
|
|
6305
|
+
tool8(
|
|
6048
6306
|
"update_task",
|
|
6049
6307
|
"Update an existing task's title, description, plan, status, or assignee.",
|
|
6050
6308
|
{
|
|
6051
|
-
task_id:
|
|
6052
|
-
title:
|
|
6053
|
-
description:
|
|
6054
|
-
plan:
|
|
6055
|
-
status:
|
|
6056
|
-
assignedUserId:
|
|
6309
|
+
task_id: z8.string().describe("The task ID to update"),
|
|
6310
|
+
title: z8.string().optional().describe("New title"),
|
|
6311
|
+
description: z8.string().optional().describe("New description"),
|
|
6312
|
+
plan: z8.string().optional().describe("New plan in markdown"),
|
|
6313
|
+
status: z8.string().optional().describe("New status"),
|
|
6314
|
+
assignedUserId: z8.string().nullable().optional().describe("Assign to user ID, or null to unassign")
|
|
6057
6315
|
},
|
|
6058
6316
|
async ({ task_id, ...fields }) => {
|
|
6059
6317
|
try {
|
|
@@ -6285,8 +6543,8 @@ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
6285
6543
|
|
|
6286
6544
|
// src/tools/audit-tools.ts
|
|
6287
6545
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6288
|
-
import { tool as
|
|
6289
|
-
import { z as
|
|
6546
|
+
import { tool as tool9, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
|
|
6547
|
+
import { z as z9 } from "zod";
|
|
6290
6548
|
function mapCreateTag(input) {
|
|
6291
6549
|
return {
|
|
6292
6550
|
type: "create_tag",
|
|
@@ -6368,14 +6626,14 @@ function collectRecommendation(toolName, input, collector, onRecommendation) {
|
|
|
6368
6626
|
}
|
|
6369
6627
|
function createAuditMcpServer(collector, onRecommendation) {
|
|
6370
6628
|
const auditTools = [
|
|
6371
|
-
|
|
6629
|
+
tool9(
|
|
6372
6630
|
"recommend_create_tag",
|
|
6373
6631
|
"Recommend creating a new tag for an uncovered subsystem or area",
|
|
6374
6632
|
{
|
|
6375
|
-
name:
|
|
6376
|
-
color:
|
|
6377
|
-
description:
|
|
6378
|
-
reasoning:
|
|
6633
|
+
name: z9.string().describe("Proposed tag name (lowercase, hyphenated)"),
|
|
6634
|
+
color: z9.string().optional().describe("Hex color code"),
|
|
6635
|
+
description: z9.string().describe("What this tag covers"),
|
|
6636
|
+
reasoning: z9.string().describe("Why this tag should be created")
|
|
6379
6637
|
},
|
|
6380
6638
|
async (args) => {
|
|
6381
6639
|
const result = collectRecommendation(
|
|
@@ -6387,14 +6645,14 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6387
6645
|
return { content: [{ type: "text", text: result }] };
|
|
6388
6646
|
}
|
|
6389
6647
|
),
|
|
6390
|
-
|
|
6648
|
+
tool9(
|
|
6391
6649
|
"recommend_update_description",
|
|
6392
6650
|
"Recommend updating a tag's description to better reflect its scope",
|
|
6393
6651
|
{
|
|
6394
|
-
tagId:
|
|
6395
|
-
tagName:
|
|
6396
|
-
description:
|
|
6397
|
-
reasoning:
|
|
6652
|
+
tagId: z9.string(),
|
|
6653
|
+
tagName: z9.string(),
|
|
6654
|
+
description: z9.string().describe("Proposed new description"),
|
|
6655
|
+
reasoning: z9.string()
|
|
6398
6656
|
},
|
|
6399
6657
|
async (args) => {
|
|
6400
6658
|
const result = collectRecommendation(
|
|
@@ -6406,16 +6664,16 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6406
6664
|
return { content: [{ type: "text", text: result }] };
|
|
6407
6665
|
}
|
|
6408
6666
|
),
|
|
6409
|
-
|
|
6667
|
+
tool9(
|
|
6410
6668
|
"recommend_context_link",
|
|
6411
6669
|
"Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
|
|
6412
6670
|
{
|
|
6413
|
-
tagId:
|
|
6414
|
-
tagName:
|
|
6415
|
-
linkType:
|
|
6416
|
-
path:
|
|
6417
|
-
label:
|
|
6418
|
-
reasoning:
|
|
6671
|
+
tagId: z9.string(),
|
|
6672
|
+
tagName: z9.string(),
|
|
6673
|
+
linkType: z9.enum(["rule", "doc", "file", "folder"]),
|
|
6674
|
+
path: z9.string(),
|
|
6675
|
+
label: z9.string().optional(),
|
|
6676
|
+
reasoning: z9.string()
|
|
6419
6677
|
},
|
|
6420
6678
|
async (args) => {
|
|
6421
6679
|
const result = collectRecommendation(
|
|
@@ -6427,16 +6685,16 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6427
6685
|
return { content: [{ type: "text", text: result }] };
|
|
6428
6686
|
}
|
|
6429
6687
|
),
|
|
6430
|
-
|
|
6688
|
+
tool9(
|
|
6431
6689
|
"flag_documentation_gap",
|
|
6432
6690
|
"Flag a file that agents read heavily but has no tag documentation linked",
|
|
6433
6691
|
{
|
|
6434
|
-
tagName:
|
|
6435
|
-
tagId:
|
|
6436
|
-
filePath:
|
|
6437
|
-
readCount:
|
|
6438
|
-
suggestedAction:
|
|
6439
|
-
reasoning:
|
|
6692
|
+
tagName: z9.string().describe("Tag whose agents read this file"),
|
|
6693
|
+
tagId: z9.string().optional(),
|
|
6694
|
+
filePath: z9.string(),
|
|
6695
|
+
readCount: z9.number(),
|
|
6696
|
+
suggestedAction: z9.string().describe("What doc or rule should be created"),
|
|
6697
|
+
reasoning: z9.string()
|
|
6440
6698
|
},
|
|
6441
6699
|
async (args) => {
|
|
6442
6700
|
const result = collectRecommendation(
|
|
@@ -6448,15 +6706,15 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6448
6706
|
return { content: [{ type: "text", text: result }] };
|
|
6449
6707
|
}
|
|
6450
6708
|
),
|
|
6451
|
-
|
|
6709
|
+
tool9(
|
|
6452
6710
|
"recommend_merge_tags",
|
|
6453
6711
|
"Recommend merging one tag into another",
|
|
6454
6712
|
{
|
|
6455
|
-
tagId:
|
|
6456
|
-
tagName:
|
|
6457
|
-
mergeIntoTagId:
|
|
6458
|
-
mergeIntoTagName:
|
|
6459
|
-
reasoning:
|
|
6713
|
+
tagId: z9.string().describe("Tag ID to be merged (removed after merge)"),
|
|
6714
|
+
tagName: z9.string().describe("Name of the tag to be merged"),
|
|
6715
|
+
mergeIntoTagId: z9.string().describe("Tag ID to merge into (kept)"),
|
|
6716
|
+
mergeIntoTagName: z9.string(),
|
|
6717
|
+
reasoning: z9.string()
|
|
6460
6718
|
},
|
|
6461
6719
|
async (args) => {
|
|
6462
6720
|
const result = collectRecommendation(
|
|
@@ -6468,14 +6726,14 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6468
6726
|
return { content: [{ type: "text", text: result }] };
|
|
6469
6727
|
}
|
|
6470
6728
|
),
|
|
6471
|
-
|
|
6729
|
+
tool9(
|
|
6472
6730
|
"recommend_rename_tag",
|
|
6473
6731
|
"Recommend renaming a tag",
|
|
6474
6732
|
{
|
|
6475
|
-
tagId:
|
|
6476
|
-
tagName:
|
|
6477
|
-
newName:
|
|
6478
|
-
reasoning:
|
|
6733
|
+
tagId: z9.string(),
|
|
6734
|
+
tagName: z9.string().describe("Current tag name"),
|
|
6735
|
+
newName: z9.string().describe("Proposed new name"),
|
|
6736
|
+
reasoning: z9.string()
|
|
6479
6737
|
},
|
|
6480
6738
|
async (args) => {
|
|
6481
6739
|
const result = collectRecommendation(
|
|
@@ -6487,10 +6745,10 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6487
6745
|
return { content: [{ type: "text", text: result }] };
|
|
6488
6746
|
}
|
|
6489
6747
|
),
|
|
6490
|
-
|
|
6748
|
+
tool9(
|
|
6491
6749
|
"complete_audit",
|
|
6492
6750
|
"Signal that the audit is complete with a summary of all findings",
|
|
6493
|
-
{ summary:
|
|
6751
|
+
{ summary: z9.string().describe("Brief overview of all findings") },
|
|
6494
6752
|
async (args) => {
|
|
6495
6753
|
collector.complete = true;
|
|
6496
6754
|
collector.summary = args.summary ?? "Audit completed.";
|
|
@@ -6706,21 +6964,22 @@ function setupWorkDir(projectDir, assignment) {
|
|
|
6706
6964
|
return { workDir, usesWorktree: shouldWorktree };
|
|
6707
6965
|
}
|
|
6708
6966
|
function spawnChildAgent(assignment, workDir) {
|
|
6709
|
-
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox } = assignment;
|
|
6967
|
+
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox, agentMode } = assignment;
|
|
6710
6968
|
const cliPath = path.resolve(__dirname, "cli.js");
|
|
6711
6969
|
const childEnv = { ...process.env };
|
|
6712
6970
|
delete childEnv.CONVEYOR_PROJECT_TOKEN;
|
|
6713
6971
|
delete childEnv.CONVEYOR_PROJECT_ID;
|
|
6972
|
+
const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
|
|
6714
6973
|
const child = fork(cliPath, [], {
|
|
6715
6974
|
env: {
|
|
6716
6975
|
...childEnv,
|
|
6717
6976
|
CONVEYOR_API_URL: apiUrl,
|
|
6718
6977
|
CONVEYOR_TASK_TOKEN: taskToken,
|
|
6719
6978
|
CONVEYOR_TASK_ID: taskId,
|
|
6720
|
-
CONVEYOR_MODE: mode,
|
|
6979
|
+
CONVEYOR_MODE: agentMode === "code-review" ? "code-review" : mode,
|
|
6721
6980
|
CONVEYOR_WORKSPACE: workDir,
|
|
6722
6981
|
CONVEYOR_USE_WORKTREE: "false",
|
|
6723
|
-
CONVEYOR_AGENT_MODE:
|
|
6982
|
+
CONVEYOR_AGENT_MODE: effectiveAgentMode,
|
|
6724
6983
|
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
6725
6984
|
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false",
|
|
6726
6985
|
CONVEYOR_FROM_PROJECT_RUNNER: "true"
|
|
@@ -7155,8 +7414,9 @@ var ProjectRunner = class {
|
|
|
7155
7414
|
handleAssignment(assignment) {
|
|
7156
7415
|
const { taskId, mode } = assignment;
|
|
7157
7416
|
const shortId = taskId.slice(0, 8);
|
|
7158
|
-
|
|
7159
|
-
|
|
7417
|
+
const agentKey = assignment.agentMode === "code-review" ? `${taskId}:code-review` : taskId;
|
|
7418
|
+
if (this.activeAgents.has(agentKey)) {
|
|
7419
|
+
logger8.info("Task already running, skipping", { taskId: shortId, agentKey });
|
|
7160
7420
|
return;
|
|
7161
7421
|
}
|
|
7162
7422
|
if (this.activeAgents.size >= MAX_CONCURRENT) {
|
|
@@ -7175,16 +7435,16 @@ var ProjectRunner = class {
|
|
|
7175
7435
|
}
|
|
7176
7436
|
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
7177
7437
|
const child = spawnChildAgent(assignment, workDir);
|
|
7178
|
-
this.activeAgents.set(
|
|
7438
|
+
this.activeAgents.set(agentKey, {
|
|
7179
7439
|
process: child,
|
|
7180
7440
|
worktreePath: workDir,
|
|
7181
7441
|
mode,
|
|
7182
7442
|
usesWorktree
|
|
7183
7443
|
});
|
|
7184
7444
|
this.connection.emitTaskStarted(taskId);
|
|
7185
|
-
logger8.info("Started task", { taskId: shortId, mode, workDir });
|
|
7445
|
+
logger8.info("Started task", { taskId: shortId, mode, agentKey, workDir });
|
|
7186
7446
|
child.on("exit", (code) => {
|
|
7187
|
-
this.activeAgents.delete(
|
|
7447
|
+
this.activeAgents.delete(agentKey);
|
|
7188
7448
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
7189
7449
|
this.connection.emitTaskStopped(taskId, reason);
|
|
7190
7450
|
logger8.info("Task exited", { taskId: shortId, reason });
|
|
@@ -7344,4 +7604,4 @@ export {
|
|
|
7344
7604
|
ProjectRunner,
|
|
7345
7605
|
FileCache
|
|
7346
7606
|
};
|
|
7347
|
-
//# sourceMappingURL=chunk-
|
|
7607
|
+
//# sourceMappingURL=chunk-T6IASOS2.js.map
|