@rallycry/conveyor-agent 6.0.6 → 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-RHRQJO5E.js → chunk-T6IASOS2.js} +357 -90
- 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-RHRQJO5E.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
|
|
|
@@ -1638,6 +1656,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
1638
1656
|
`- "Open" \u2014 Ready to execute. Use start_child_cloud_build to fire it.`,
|
|
1639
1657
|
`- "InProgress" \u2014 Currently being worked on by a Task Runner. Wait \u2014 it will move to ReviewPR when done.`,
|
|
1640
1658
|
`- "ReviewPR" \u2014 Task Runner finished and opened a PR. Review and merge it.`,
|
|
1659
|
+
`- "Hold" \u2014 PR exists but is on hold for team review. Do not merge \u2014 skip and move on.`,
|
|
1641
1660
|
`- "ReviewDev" \u2014 PR was merged to dev. This child is complete. Move on.`,
|
|
1642
1661
|
`- "Complete" \u2014 Fully done. Move on.`,
|
|
1643
1662
|
``,
|
|
@@ -1654,6 +1673,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
1654
1673
|
` - "InProgress": A Task Runner is actively working on this child. Do nothing \u2014 wait for it to finish.`,
|
|
1655
1674
|
` - "Open": This is the next child to execute. Fire it with start_child_cloud_build.`,
|
|
1656
1675
|
` - If it fails because the child is missing story points or an agent: notify the team in chat and go idle.`,
|
|
1676
|
+
` - "Hold": On hold \u2014 team must review before merge. Skip.`,
|
|
1657
1677
|
` - "ReviewDev" / "Complete": Already done. Skip.`,
|
|
1658
1678
|
` - "Planning": Not ready. If this is blocking progress, notify the team.`,
|
|
1659
1679
|
``,
|
|
@@ -1661,7 +1681,7 @@ function buildPackRunnerSystemPrompt(context, config, setupLog) {
|
|
|
1661
1681
|
``,
|
|
1662
1682
|
`4. After firing a child build: report which task you fired to chat, then explicitly state you are going idle. The system will relaunch you when the child completes or changes status.`,
|
|
1663
1683
|
``,
|
|
1664
|
-
`5. When ALL children are in "ReviewDev" or "
|
|
1684
|
+
`5. When ALL children are in "ReviewDev", "Complete", or "Hold" (no "Open", "InProgress", or "ReviewPR" remaining): do a final review, summarize results in chat, and mark this parent task complete with force_update_task_status("Complete").`,
|
|
1665
1685
|
``,
|
|
1666
1686
|
`## Important Rules`,
|
|
1667
1687
|
`- Process children ONE at a time, in ordinal order.`,
|
|
@@ -2033,10 +2053,52 @@ function buildModePrompt(agentMode, context) {
|
|
|
2033
2053
|
].join("\n");
|
|
2034
2054
|
case "auto":
|
|
2035
2055
|
return buildAutoPrompt(context);
|
|
2056
|
+
case "code-review":
|
|
2057
|
+
return buildCodeReviewPrompt();
|
|
2036
2058
|
default:
|
|
2037
2059
|
return null;
|
|
2038
2060
|
}
|
|
2039
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
|
+
}
|
|
2040
2102
|
|
|
2041
2103
|
// src/execution/system-prompt.ts
|
|
2042
2104
|
function formatProjectAgentLine(pa) {
|
|
@@ -2408,6 +2470,18 @@ ${context.plan}`);
|
|
|
2408
2470
|
}
|
|
2409
2471
|
return parts;
|
|
2410
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
|
+
}
|
|
2411
2485
|
function buildFreshInstructions(isPm, isAutoMode, context, agentMode) {
|
|
2412
2486
|
if (isPm && agentMode === "building") {
|
|
2413
2487
|
return [
|
|
@@ -2505,6 +2579,10 @@ Address the requested changes directly. Do NOT re-investigate the codebase from
|
|
|
2505
2579
|
function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
2506
2580
|
const parts = [`
|
|
2507
2581
|
## Instructions`];
|
|
2582
|
+
if (agentMode === "code-review") {
|
|
2583
|
+
parts.push(...buildCodeReviewInstructions(context));
|
|
2584
|
+
return parts;
|
|
2585
|
+
}
|
|
2508
2586
|
const isPm = mode === "pm";
|
|
2509
2587
|
if (scenario === "fresh") {
|
|
2510
2588
|
parts.push(...buildFreshInstructions(isPm, agentMode === "auto", context, agentMode));
|
|
@@ -2556,7 +2634,7 @@ function buildInstructions(mode, context, scenario, agentMode, isAuto) {
|
|
|
2556
2634
|
}
|
|
2557
2635
|
async function buildInitialPrompt(mode, context, isAuto, agentMode) {
|
|
2558
2636
|
const isPackRunner = mode === "pm" && !!isAuto && !!context.isParentTask;
|
|
2559
|
-
if (!isPackRunner) {
|
|
2637
|
+
if (!isPackRunner && agentMode !== "code-review") {
|
|
2560
2638
|
const sessionRelaunch = buildRelaunchWithSession(mode, context, agentMode, isAuto);
|
|
2561
2639
|
if (sessionRelaunch) return sessionRelaunch;
|
|
2562
2640
|
}
|
|
@@ -2643,7 +2721,7 @@ function buildForceUpdateTaskStatusTool(connection) {
|
|
|
2643
2721
|
"force_update_task_status",
|
|
2644
2722
|
"EMERGENCY ONLY: Force-override a task's Kanban status. Status transitions happen automatically (building sets InProgress, PR creation sets ReviewPR, merge sets ReviewDev). Only use this if an automatic transition failed or a task is stuck in the wrong status. Omit task_id to update the current task, or provide a child task ID.",
|
|
2645
2723
|
{
|
|
2646
|
-
status: z.enum(["InProgress", "ReviewPR", "ReviewDev", "Complete"]).describe("The new status for the task"),
|
|
2724
|
+
status: z.enum(["InProgress", "ReviewPR", "Hold", "ReviewDev", "Complete"]).describe("The new status for the task"),
|
|
2647
2725
|
task_id: z.string().optional().describe("Child task ID to update. Omit to update the current task.")
|
|
2648
2726
|
},
|
|
2649
2727
|
async ({ status, task_id }) => {
|
|
@@ -3427,7 +3505,8 @@ async function injectTelemetry(cdpClient) {
|
|
|
3427
3505
|
return { success: false, error: result.value };
|
|
3428
3506
|
}
|
|
3429
3507
|
try {
|
|
3430
|
-
|
|
3508
|
+
let parsed = JSON.parse(result.value);
|
|
3509
|
+
if (typeof parsed === "string") parsed = JSON.parse(parsed);
|
|
3431
3510
|
return {
|
|
3432
3511
|
success: parsed.success === true,
|
|
3433
3512
|
patches: parsed.patches
|
|
@@ -4362,6 +4441,73 @@ function buildDebugTools(manager) {
|
|
|
4362
4441
|
];
|
|
4363
4442
|
}
|
|
4364
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
|
+
|
|
4365
4511
|
// src/tools/index.ts
|
|
4366
4512
|
function textResult(text) {
|
|
4367
4513
|
return { content: [{ type: "text", text }] };
|
|
@@ -4392,8 +4538,22 @@ function getModeTools(agentMode, connection, config, context) {
|
|
|
4392
4538
|
}
|
|
4393
4539
|
}
|
|
4394
4540
|
function createConveyorMcpServer(connection, config, context, agentMode, debugManager) {
|
|
4395
|
-
const commonTools = buildCommonTools(connection, config);
|
|
4396
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);
|
|
4397
4557
|
const modeTools = getModeTools(effectiveMode, connection, config, context);
|
|
4398
4558
|
const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" ? buildDiscoveryTools(connection, context) : [];
|
|
4399
4559
|
const debugTools = debugManager && effectiveMode === "building" ? buildDebugTools(debugManager) : [];
|
|
@@ -4408,6 +4568,7 @@ function createConveyorMcpServer(connection, config, context, agentMode, debugMa
|
|
|
4408
4568
|
import { randomUUID } from "crypto";
|
|
4409
4569
|
var PM_PLAN_FILE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
|
|
4410
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+/;
|
|
4411
4572
|
function isPlanFile(input) {
|
|
4412
4573
|
const filePath = String(input.file_path ?? input.path ?? "");
|
|
4413
4574
|
return filePath.includes(".claude/plans/");
|
|
@@ -4457,6 +4618,24 @@ function handleReviewToolAccess(toolName, input, isParentTask) {
|
|
|
4457
4618
|
}
|
|
4458
4619
|
return { behavior: "allow", updatedInput: input };
|
|
4459
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
|
+
}
|
|
4460
4639
|
function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
|
|
4461
4640
|
if (hasExitedPlanMode) {
|
|
4462
4641
|
return isParentTask ? handleReviewToolAccess(toolName, input, true) : handleBuildingToolAccess(toolName, input);
|
|
@@ -4561,6 +4740,9 @@ function buildCanUseTool(host) {
|
|
|
4561
4740
|
case "auto":
|
|
4562
4741
|
result = handleAutoToolAccess(toolName, input, host.hasExitedPlanMode, host.isParentTask);
|
|
4563
4742
|
break;
|
|
4743
|
+
case "code-review":
|
|
4744
|
+
result = handleCodeReviewToolAccess(toolName, input);
|
|
4745
|
+
break;
|
|
4564
4746
|
default:
|
|
4565
4747
|
result = { behavior: "allow", updatedInput: input };
|
|
4566
4748
|
}
|
|
@@ -4608,10 +4790,13 @@ function buildHooks(host) {
|
|
|
4608
4790
|
};
|
|
4609
4791
|
}
|
|
4610
4792
|
function isReadOnlyMode(mode, hasExitedPlanMode) {
|
|
4611
|
-
return mode === "discovery" || mode === "help" || mode === "auto" && !hasExitedPlanMode;
|
|
4793
|
+
return mode === "discovery" || mode === "help" || mode === "code-review" || mode === "auto" && !hasExitedPlanMode;
|
|
4612
4794
|
}
|
|
4613
4795
|
function buildDisallowedTools(settings, mode, hasExitedPlanMode) {
|
|
4614
4796
|
const modeDisallowed = isReadOnlyMode(mode, hasExitedPlanMode) ? ["TodoWrite", "TodoRead", "NotebookEdit"] : [];
|
|
4797
|
+
if (mode === "code-review") {
|
|
4798
|
+
modeDisallowed.push("ExitPlanMode", "EnterPlanMode");
|
|
4799
|
+
}
|
|
4615
4800
|
const configured = settings.disallowedTools ?? [];
|
|
4616
4801
|
const combined = [...configured, ...modeDisallowed];
|
|
4617
4802
|
return combined.length > 0 ? combined : void 0;
|
|
@@ -4644,11 +4829,11 @@ function buildQueryOptions(host, context) {
|
|
|
4644
4829
|
tools: { type: "preset", preset: "claude_code" },
|
|
4645
4830
|
mcpServers: { conveyor: createConveyorMcpServer(host.connection, host.config, context, mode) },
|
|
4646
4831
|
hooks: buildHooks(host),
|
|
4647
|
-
maxTurns: settings.maxTurns,
|
|
4832
|
+
maxTurns: mode === "code-review" ? Math.min(settings.maxTurns ?? 15, 15) : settings.maxTurns,
|
|
4648
4833
|
effort: settings.effort,
|
|
4649
4834
|
thinking: settings.thinking,
|
|
4650
4835
|
betas: settings.betas,
|
|
4651
|
-
maxBudgetUsd: settings.maxBudgetUsd ?? 50,
|
|
4836
|
+
maxBudgetUsd: mode === "code-review" ? Math.min(settings.maxBudgetUsd ?? 10, 10) : settings.maxBudgetUsd ?? 50,
|
|
4652
4837
|
disallowedTools: buildDisallowedTools(settings, mode, host.hasExitedPlanMode),
|
|
4653
4838
|
enableFileCheckpointing: settings.enableFileCheckpointing,
|
|
4654
4839
|
stderr: (data) => {
|
|
@@ -4888,6 +5073,17 @@ function handleProcessResult(result, context, host, options) {
|
|
|
4888
5073
|
if (result.authError) {
|
|
4889
5074
|
return { action: "return_promise", promise: handleAuthError(context, host, options) };
|
|
4890
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
|
+
}
|
|
4891
5087
|
if (!result.retriable) return { action: "return" };
|
|
4892
5088
|
return {
|
|
4893
5089
|
action: "continue",
|
|
@@ -4957,6 +5153,38 @@ var CostTracker = class {
|
|
|
4957
5153
|
}
|
|
4958
5154
|
};
|
|
4959
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
|
+
|
|
4960
5188
|
// src/runner/plan-sync.ts
|
|
4961
5189
|
import { readdirSync, statSync, readFileSync } from "fs";
|
|
4962
5190
|
import { join as join3 } from "path";
|
|
@@ -5308,6 +5536,7 @@ function buildQueryHost(deps) {
|
|
|
5308
5536
|
callbacks: deps.callbacks,
|
|
5309
5537
|
setupLog: deps.setupLog,
|
|
5310
5538
|
costTracker: deps.costTracker,
|
|
5539
|
+
loopDetector: deps.loopDetector,
|
|
5311
5540
|
get agentMode() {
|
|
5312
5541
|
return deps.getEffectiveAgentMode();
|
|
5313
5542
|
},
|
|
@@ -5355,10 +5584,12 @@ var AgentRunner = class {
|
|
|
5355
5584
|
taskContext = null;
|
|
5356
5585
|
planSync;
|
|
5357
5586
|
costTracker = new CostTracker();
|
|
5587
|
+
loopDetector = new LoopDetector();
|
|
5358
5588
|
worktreeActive = false;
|
|
5359
5589
|
agentMode = null;
|
|
5360
5590
|
hasExitedPlanMode = false;
|
|
5361
5591
|
pendingModeRestart = false;
|
|
5592
|
+
pendingModeAutoStart = false;
|
|
5362
5593
|
sessionIds = /* @__PURE__ */ new Map();
|
|
5363
5594
|
lastQueryModeRestart = false;
|
|
5364
5595
|
startCommandStarted = false;
|
|
@@ -5472,6 +5703,10 @@ var AgentRunner = class {
|
|
|
5472
5703
|
}
|
|
5473
5704
|
this.taskContext._runnerSessionId = randomUUID2();
|
|
5474
5705
|
if (this.taskContext.agentMode) this.agentMode = this.taskContext.agentMode;
|
|
5706
|
+
const pastPlanning = this.taskContext.status !== "Planning" && this.taskContext.status !== "Unidentified";
|
|
5707
|
+
if (this.agentMode === "auto" && pastPlanning) {
|
|
5708
|
+
this.hasExitedPlanMode = true;
|
|
5709
|
+
}
|
|
5475
5710
|
this.logEffectiveSettings();
|
|
5476
5711
|
if (process.env.CODESPACES === "true") unshallowRepo(this.config.workspaceDir);
|
|
5477
5712
|
return true;
|
|
@@ -5558,6 +5793,12 @@ var AgentRunner = class {
|
|
|
5558
5793
|
async executeInitialMode() {
|
|
5559
5794
|
if (!this.taskContext) return;
|
|
5560
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
|
+
}
|
|
5561
5802
|
const shouldRun = mode === "building" || mode === "auto" || mode === "review" && !!this.taskContext.isParentTask;
|
|
5562
5803
|
if (shouldRun) {
|
|
5563
5804
|
await this.setState("running");
|
|
@@ -5614,6 +5855,12 @@ var AgentRunner = class {
|
|
|
5614
5855
|
await this.handleModeRestartCycle();
|
|
5615
5856
|
continue;
|
|
5616
5857
|
}
|
|
5858
|
+
if (this.pendingModeAutoStart) {
|
|
5859
|
+
this.pendingModeAutoStart = false;
|
|
5860
|
+
this.interrupted = false;
|
|
5861
|
+
await this.handleModeRestartCycle();
|
|
5862
|
+
continue;
|
|
5863
|
+
}
|
|
5617
5864
|
if (this._state === "idle") {
|
|
5618
5865
|
const msg = await this.waitForUserContent();
|
|
5619
5866
|
if (!msg) {
|
|
@@ -5741,6 +5988,7 @@ var AgentRunner = class {
|
|
|
5741
5988
|
callbacks: this.callbacks,
|
|
5742
5989
|
setupLog: this.setupLog,
|
|
5743
5990
|
costTracker: this.costTracker,
|
|
5991
|
+
loopDetector: this.loopDetector,
|
|
5744
5992
|
planSync: this.planSync,
|
|
5745
5993
|
sessionIds: this.sessionIds,
|
|
5746
5994
|
getEffectiveAgentMode: () => this.effectiveAgentMode,
|
|
@@ -5765,15 +6013,32 @@ var AgentRunner = class {
|
|
|
5765
6013
|
handleModeChange(newAgentMode) {
|
|
5766
6014
|
if (this.config.mode !== "pm") return;
|
|
5767
6015
|
if (newAgentMode) this.agentMode = newAgentMode;
|
|
6016
|
+
this.updateExitedPlanModeFlag(newAgentMode);
|
|
5768
6017
|
const effectiveMode = this.effectiveAgentMode;
|
|
6018
|
+
const isBuildCapable = effectiveMode === "building" || effectiveMode === "auto" && this.hasExitedPlanMode;
|
|
5769
6019
|
this.connection.emitModeChanged(effectiveMode);
|
|
5770
6020
|
this.connection.postChatMessage(
|
|
5771
6021
|
`Mode switched to **${effectiveMode}**${effectiveMode === "building" ? " \u2014 I now have direct coding access." : ""}`
|
|
5772
6022
|
);
|
|
5773
|
-
if (
|
|
6023
|
+
if (isBuildCapable && this.taskContext?.status === "Open") {
|
|
5774
6024
|
this.connection.updateStatus("InProgress");
|
|
5775
6025
|
this.taskContext.status = "InProgress";
|
|
5776
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
|
+
}
|
|
5777
6042
|
}
|
|
5778
6043
|
softStop() {
|
|
5779
6044
|
this.interrupted = true;
|
|
@@ -5919,17 +6184,17 @@ import {
|
|
|
5919
6184
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
5920
6185
|
|
|
5921
6186
|
// src/tools/project-tools.ts
|
|
5922
|
-
import { tool as
|
|
5923
|
-
import { z as
|
|
6187
|
+
import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
6188
|
+
import { z as z8 } from "zod";
|
|
5924
6189
|
function buildReadTools(connection) {
|
|
5925
6190
|
return [
|
|
5926
|
-
|
|
6191
|
+
tool8(
|
|
5927
6192
|
"list_tasks",
|
|
5928
6193
|
"List tasks in the project. Optionally filter by status or assignee.",
|
|
5929
6194
|
{
|
|
5930
|
-
status:
|
|
5931
|
-
assigneeId:
|
|
5932
|
-
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)")
|
|
5933
6198
|
},
|
|
5934
6199
|
async (params) => {
|
|
5935
6200
|
try {
|
|
@@ -5943,10 +6208,10 @@ function buildReadTools(connection) {
|
|
|
5943
6208
|
},
|
|
5944
6209
|
{ annotations: { readOnlyHint: true } }
|
|
5945
6210
|
),
|
|
5946
|
-
|
|
6211
|
+
tool8(
|
|
5947
6212
|
"get_task",
|
|
5948
6213
|
"Get detailed information about a task including its chat messages, child tasks, and codespace status.",
|
|
5949
|
-
{ task_id:
|
|
6214
|
+
{ task_id: z8.string().describe("The task ID to look up") },
|
|
5950
6215
|
async ({ task_id }) => {
|
|
5951
6216
|
try {
|
|
5952
6217
|
const task = await connection.requestGetTask(task_id);
|
|
@@ -5959,14 +6224,14 @@ function buildReadTools(connection) {
|
|
|
5959
6224
|
},
|
|
5960
6225
|
{ annotations: { readOnlyHint: true } }
|
|
5961
6226
|
),
|
|
5962
|
-
|
|
6227
|
+
tool8(
|
|
5963
6228
|
"search_tasks",
|
|
5964
6229
|
"Search tasks by tags, text query, or status filters.",
|
|
5965
6230
|
{
|
|
5966
|
-
tagNames:
|
|
5967
|
-
searchQuery:
|
|
5968
|
-
statusFilters:
|
|
5969
|
-
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)")
|
|
5970
6235
|
},
|
|
5971
6236
|
async (params) => {
|
|
5972
6237
|
try {
|
|
@@ -5980,7 +6245,7 @@ function buildReadTools(connection) {
|
|
|
5980
6245
|
},
|
|
5981
6246
|
{ annotations: { readOnlyHint: true } }
|
|
5982
6247
|
),
|
|
5983
|
-
|
|
6248
|
+
tool8(
|
|
5984
6249
|
"list_tags",
|
|
5985
6250
|
"List all tags available in the project.",
|
|
5986
6251
|
{},
|
|
@@ -5996,7 +6261,7 @@ function buildReadTools(connection) {
|
|
|
5996
6261
|
},
|
|
5997
6262
|
{ annotations: { readOnlyHint: true } }
|
|
5998
6263
|
),
|
|
5999
|
-
|
|
6264
|
+
tool8(
|
|
6000
6265
|
"get_project_summary",
|
|
6001
6266
|
"Get a summary of the project including task counts by status and active builds.",
|
|
6002
6267
|
{},
|
|
@@ -6016,15 +6281,15 @@ function buildReadTools(connection) {
|
|
|
6016
6281
|
}
|
|
6017
6282
|
function buildMutationTools(connection) {
|
|
6018
6283
|
return [
|
|
6019
|
-
|
|
6284
|
+
tool8(
|
|
6020
6285
|
"create_task",
|
|
6021
6286
|
"Create a new task in the project.",
|
|
6022
6287
|
{
|
|
6023
|
-
title:
|
|
6024
|
-
description:
|
|
6025
|
-
plan:
|
|
6026
|
-
status:
|
|
6027
|
-
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")
|
|
6028
6293
|
},
|
|
6029
6294
|
async (params) => {
|
|
6030
6295
|
try {
|
|
@@ -6037,16 +6302,16 @@ function buildMutationTools(connection) {
|
|
|
6037
6302
|
}
|
|
6038
6303
|
}
|
|
6039
6304
|
),
|
|
6040
|
-
|
|
6305
|
+
tool8(
|
|
6041
6306
|
"update_task",
|
|
6042
6307
|
"Update an existing task's title, description, plan, status, or assignee.",
|
|
6043
6308
|
{
|
|
6044
|
-
task_id:
|
|
6045
|
-
title:
|
|
6046
|
-
description:
|
|
6047
|
-
plan:
|
|
6048
|
-
status:
|
|
6049
|
-
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")
|
|
6050
6315
|
},
|
|
6051
6316
|
async ({ task_id, ...fields }) => {
|
|
6052
6317
|
try {
|
|
@@ -6278,8 +6543,8 @@ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
6278
6543
|
|
|
6279
6544
|
// src/tools/audit-tools.ts
|
|
6280
6545
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6281
|
-
import { tool as
|
|
6282
|
-
import { z as
|
|
6546
|
+
import { tool as tool9, createSdkMcpServer as createSdkMcpServer3 } from "@anthropic-ai/claude-agent-sdk";
|
|
6547
|
+
import { z as z9 } from "zod";
|
|
6283
6548
|
function mapCreateTag(input) {
|
|
6284
6549
|
return {
|
|
6285
6550
|
type: "create_tag",
|
|
@@ -6361,14 +6626,14 @@ function collectRecommendation(toolName, input, collector, onRecommendation) {
|
|
|
6361
6626
|
}
|
|
6362
6627
|
function createAuditMcpServer(collector, onRecommendation) {
|
|
6363
6628
|
const auditTools = [
|
|
6364
|
-
|
|
6629
|
+
tool9(
|
|
6365
6630
|
"recommend_create_tag",
|
|
6366
6631
|
"Recommend creating a new tag for an uncovered subsystem or area",
|
|
6367
6632
|
{
|
|
6368
|
-
name:
|
|
6369
|
-
color:
|
|
6370
|
-
description:
|
|
6371
|
-
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")
|
|
6372
6637
|
},
|
|
6373
6638
|
async (args) => {
|
|
6374
6639
|
const result = collectRecommendation(
|
|
@@ -6380,14 +6645,14 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6380
6645
|
return { content: [{ type: "text", text: result }] };
|
|
6381
6646
|
}
|
|
6382
6647
|
),
|
|
6383
|
-
|
|
6648
|
+
tool9(
|
|
6384
6649
|
"recommend_update_description",
|
|
6385
6650
|
"Recommend updating a tag's description to better reflect its scope",
|
|
6386
6651
|
{
|
|
6387
|
-
tagId:
|
|
6388
|
-
tagName:
|
|
6389
|
-
description:
|
|
6390
|
-
reasoning:
|
|
6652
|
+
tagId: z9.string(),
|
|
6653
|
+
tagName: z9.string(),
|
|
6654
|
+
description: z9.string().describe("Proposed new description"),
|
|
6655
|
+
reasoning: z9.string()
|
|
6391
6656
|
},
|
|
6392
6657
|
async (args) => {
|
|
6393
6658
|
const result = collectRecommendation(
|
|
@@ -6399,16 +6664,16 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6399
6664
|
return { content: [{ type: "text", text: result }] };
|
|
6400
6665
|
}
|
|
6401
6666
|
),
|
|
6402
|
-
|
|
6667
|
+
tool9(
|
|
6403
6668
|
"recommend_context_link",
|
|
6404
6669
|
"Recommend linking a doc, rule, file, or folder to a tag's contextPaths",
|
|
6405
6670
|
{
|
|
6406
|
-
tagId:
|
|
6407
|
-
tagName:
|
|
6408
|
-
linkType:
|
|
6409
|
-
path:
|
|
6410
|
-
label:
|
|
6411
|
-
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()
|
|
6412
6677
|
},
|
|
6413
6678
|
async (args) => {
|
|
6414
6679
|
const result = collectRecommendation(
|
|
@@ -6420,16 +6685,16 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6420
6685
|
return { content: [{ type: "text", text: result }] };
|
|
6421
6686
|
}
|
|
6422
6687
|
),
|
|
6423
|
-
|
|
6688
|
+
tool9(
|
|
6424
6689
|
"flag_documentation_gap",
|
|
6425
6690
|
"Flag a file that agents read heavily but has no tag documentation linked",
|
|
6426
6691
|
{
|
|
6427
|
-
tagName:
|
|
6428
|
-
tagId:
|
|
6429
|
-
filePath:
|
|
6430
|
-
readCount:
|
|
6431
|
-
suggestedAction:
|
|
6432
|
-
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()
|
|
6433
6698
|
},
|
|
6434
6699
|
async (args) => {
|
|
6435
6700
|
const result = collectRecommendation(
|
|
@@ -6441,15 +6706,15 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6441
6706
|
return { content: [{ type: "text", text: result }] };
|
|
6442
6707
|
}
|
|
6443
6708
|
),
|
|
6444
|
-
|
|
6709
|
+
tool9(
|
|
6445
6710
|
"recommend_merge_tags",
|
|
6446
6711
|
"Recommend merging one tag into another",
|
|
6447
6712
|
{
|
|
6448
|
-
tagId:
|
|
6449
|
-
tagName:
|
|
6450
|
-
mergeIntoTagId:
|
|
6451
|
-
mergeIntoTagName:
|
|
6452
|
-
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()
|
|
6453
6718
|
},
|
|
6454
6719
|
async (args) => {
|
|
6455
6720
|
const result = collectRecommendation(
|
|
@@ -6461,14 +6726,14 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6461
6726
|
return { content: [{ type: "text", text: result }] };
|
|
6462
6727
|
}
|
|
6463
6728
|
),
|
|
6464
|
-
|
|
6729
|
+
tool9(
|
|
6465
6730
|
"recommend_rename_tag",
|
|
6466
6731
|
"Recommend renaming a tag",
|
|
6467
6732
|
{
|
|
6468
|
-
tagId:
|
|
6469
|
-
tagName:
|
|
6470
|
-
newName:
|
|
6471
|
-
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()
|
|
6472
6737
|
},
|
|
6473
6738
|
async (args) => {
|
|
6474
6739
|
const result = collectRecommendation(
|
|
@@ -6480,10 +6745,10 @@ function createAuditMcpServer(collector, onRecommendation) {
|
|
|
6480
6745
|
return { content: [{ type: "text", text: result }] };
|
|
6481
6746
|
}
|
|
6482
6747
|
),
|
|
6483
|
-
|
|
6748
|
+
tool9(
|
|
6484
6749
|
"complete_audit",
|
|
6485
6750
|
"Signal that the audit is complete with a summary of all findings",
|
|
6486
|
-
{ summary:
|
|
6751
|
+
{ summary: z9.string().describe("Brief overview of all findings") },
|
|
6487
6752
|
async (args) => {
|
|
6488
6753
|
collector.complete = true;
|
|
6489
6754
|
collector.summary = args.summary ?? "Audit completed.";
|
|
@@ -6699,21 +6964,22 @@ function setupWorkDir(projectDir, assignment) {
|
|
|
6699
6964
|
return { workDir, usesWorktree: shouldWorktree };
|
|
6700
6965
|
}
|
|
6701
6966
|
function spawnChildAgent(assignment, workDir) {
|
|
6702
|
-
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox } = assignment;
|
|
6967
|
+
const { taskToken, apiUrl, taskId, mode, isAuto, useSandbox, agentMode } = assignment;
|
|
6703
6968
|
const cliPath = path.resolve(__dirname, "cli.js");
|
|
6704
6969
|
const childEnv = { ...process.env };
|
|
6705
6970
|
delete childEnv.CONVEYOR_PROJECT_TOKEN;
|
|
6706
6971
|
delete childEnv.CONVEYOR_PROJECT_ID;
|
|
6972
|
+
const effectiveAgentMode = agentMode ?? (isAuto ? "auto" : "");
|
|
6707
6973
|
const child = fork(cliPath, [], {
|
|
6708
6974
|
env: {
|
|
6709
6975
|
...childEnv,
|
|
6710
6976
|
CONVEYOR_API_URL: apiUrl,
|
|
6711
6977
|
CONVEYOR_TASK_TOKEN: taskToken,
|
|
6712
6978
|
CONVEYOR_TASK_ID: taskId,
|
|
6713
|
-
CONVEYOR_MODE: mode,
|
|
6979
|
+
CONVEYOR_MODE: agentMode === "code-review" ? "code-review" : mode,
|
|
6714
6980
|
CONVEYOR_WORKSPACE: workDir,
|
|
6715
6981
|
CONVEYOR_USE_WORKTREE: "false",
|
|
6716
|
-
CONVEYOR_AGENT_MODE:
|
|
6982
|
+
CONVEYOR_AGENT_MODE: effectiveAgentMode,
|
|
6717
6983
|
CONVEYOR_IS_AUTO: isAuto ? "true" : "false",
|
|
6718
6984
|
CONVEYOR_USE_SANDBOX: useSandbox === true ? "true" : "false",
|
|
6719
6985
|
CONVEYOR_FROM_PROJECT_RUNNER: "true"
|
|
@@ -7148,8 +7414,9 @@ var ProjectRunner = class {
|
|
|
7148
7414
|
handleAssignment(assignment) {
|
|
7149
7415
|
const { taskId, mode } = assignment;
|
|
7150
7416
|
const shortId = taskId.slice(0, 8);
|
|
7151
|
-
|
|
7152
|
-
|
|
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 });
|
|
7153
7420
|
return;
|
|
7154
7421
|
}
|
|
7155
7422
|
if (this.activeAgents.size >= MAX_CONCURRENT) {
|
|
@@ -7168,16 +7435,16 @@ var ProjectRunner = class {
|
|
|
7168
7435
|
}
|
|
7169
7436
|
const { workDir, usesWorktree } = setupWorkDir(this.projectDir, assignment);
|
|
7170
7437
|
const child = spawnChildAgent(assignment, workDir);
|
|
7171
|
-
this.activeAgents.set(
|
|
7438
|
+
this.activeAgents.set(agentKey, {
|
|
7172
7439
|
process: child,
|
|
7173
7440
|
worktreePath: workDir,
|
|
7174
7441
|
mode,
|
|
7175
7442
|
usesWorktree
|
|
7176
7443
|
});
|
|
7177
7444
|
this.connection.emitTaskStarted(taskId);
|
|
7178
|
-
logger8.info("Started task", { taskId: shortId, mode, workDir });
|
|
7445
|
+
logger8.info("Started task", { taskId: shortId, mode, agentKey, workDir });
|
|
7179
7446
|
child.on("exit", (code) => {
|
|
7180
|
-
this.activeAgents.delete(
|
|
7447
|
+
this.activeAgents.delete(agentKey);
|
|
7181
7448
|
const reason = code === 0 ? "completed" : `exited with code ${code}`;
|
|
7182
7449
|
this.connection.emitTaskStopped(taskId, reason);
|
|
7183
7450
|
logger8.info("Task exited", { taskId: shortId, reason });
|
|
@@ -7337,4 +7604,4 @@ export {
|
|
|
7337
7604
|
ProjectRunner,
|
|
7338
7605
|
FileCache
|
|
7339
7606
|
};
|
|
7340
|
-
//# sourceMappingURL=chunk-
|
|
7607
|
+
//# sourceMappingURL=chunk-T6IASOS2.js.map
|