simplify PR by making agent mode only work with workflow dispatch and schedule events

This commit is contained in:
km-anthropic
2025-07-29 10:46:44 -07:00
parent 26d6ecc65d
commit 3402c5355d
3 changed files with 79 additions and 126 deletions

View File

@@ -1,34 +1,31 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import { mkdir, writeFile } from "fs/promises"; import { mkdir, writeFile } from "fs/promises";
import type { Mode, ModeOptions, ModeResult } from "../types"; import type { Mode, ModeOptions, ModeResult } from "../types";
import { prepareMcpConfig } from "../../mcp/install-mcp-server";
import { exportToolEnvironmentVariables } from "../../tools/export";
/** /**
* Agent mode implementation. * Agent mode implementation.
* *
* This mode is designed for automation and workflow_dispatch scenarios. * This mode is specifically designed for automation events (workflow_dispatch and schedule).
* It always triggers (no checking), allows highly flexible configurations, * It bypasses the standard trigger checking and comment tracking used by tag mode,
* and works well with override_prompt for custom workflows. * making it ideal for scheduled tasks and manual workflow runs.
*
* In the future, this mode could restrict certain tools for safety in automation contexts,
* e.g., disallowing WebSearch or limiting file system operations.
*/ */
export const agentMode: Mode = { export const agentMode: Mode = {
name: "agent", name: "agent",
description: "Automation mode that always runs without trigger checking", description: "Automation mode for workflow_dispatch and schedule events",
shouldTrigger() { shouldTrigger(context) {
return true; // Only trigger for automation events
return (
context.eventName === "workflow_dispatch" ||
context.eventName === "schedule"
);
}, },
prepareContext(context, data) { prepareContext(context) {
// Agent mode doesn't use comment tracking or branch management
return { return {
mode: "agent", mode: "agent",
githubContext: context, githubContext: context,
commentId: data?.commentId,
baseBranch: data?.baseBranch,
claudeBranch: data?.claudeBranch,
}; };
}, },
@@ -44,9 +41,8 @@ export const agentMode: Mode = {
return false; return false;
}, },
async prepare({ context, githubToken }: ModeOptions): Promise<ModeResult> { async prepare({ context }: ModeOptions): Promise<ModeResult> {
// Agent mode is designed for automation events (workflow_dispatch, schedule) // Agent mode handles automation events (workflow_dispatch, schedule) only
// and potentially other events where we want full automation without tracking
// Create prompt directory // Create prompt directory
await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, { await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, {
@@ -67,24 +63,48 @@ export const agentMode: Mode = {
promptContent, promptContent,
); );
// Export tool environment variables // Export tool environment variables for agent mode
exportToolEnvironmentVariables(agentMode, context); const baseTools = [
"Edit",
"MultiEdit",
"Glob",
"Grep",
"LS",
"Read",
"Write",
];
// Get MCP configuration // Add user-specified tools
const allowedTools = [...baseTools, ...context.inputs.allowedTools];
const disallowedTools = [
"WebSearch",
"WebFetch",
...context.inputs.disallowedTools,
];
core.exportVariable("ALLOWED_TOOLS", allowedTools.join(","));
core.exportVariable("DISALLOWED_TOOLS", disallowedTools.join(","));
// Agent mode uses a minimal MCP configuration
// We don't need comment servers or PR-specific tools for automation
const mcpConfig: any = {
mcpServers: {},
};
// Add user-provided additional MCP config if any
const additionalMcpConfig = process.env.MCP_CONFIG || ""; const additionalMcpConfig = process.env.MCP_CONFIG || "";
const mcpConfig = await prepareMcpConfig({ if (additionalMcpConfig.trim()) {
githubToken, try {
owner: context.repository.owner, const additional = JSON.parse(additionalMcpConfig);
repo: context.repository.repo, if (additional && typeof additional === "object") {
branch: "", // No specific branch for agent mode Object.assign(mcpConfig, additional);
baseBranch: "", // No base branch needed }
additionalMcpConfig, } catch (error) {
claudeCommentId: "", core.warning(`Failed to parse additional MCP config: ${error}`);
allowedTools: context.inputs.allowedTools, }
context, }
});
core.setOutput("mcp_config", mcpConfig); core.setOutput("mcp_config", JSON.stringify(mcpConfig));
return { return {
commentId: undefined, commentId: undefined,
@@ -93,7 +113,7 @@ export const agentMode: Mode = {
currentBranch: "", currentBranch: "",
claudeBranch: undefined, claudeBranch: undefined,
}, },
mcpConfig, mcpConfig: JSON.stringify(mcpConfig),
}; };
}, },
}; };

View File

@@ -1,49 +0,0 @@
/**
* Handles exporting tool-related environment variables
*/
import * as core from "@actions/core";
import type { Mode } from "../modes/types";
import type { ParsedGitHubContext } from "../github/context";
import {
buildAllowedToolsString,
buildDisallowedToolsString,
} from "../create-prompt/index";
export function exportToolEnvironmentVariables(
mode: Mode,
context: ParsedGitHubContext,
): void {
const hasActionsReadPermission =
context.inputs.additionalPermissions.get("actions") === "read" &&
context.isPR;
const modeAllowedTools = mode.getAllowedTools();
const modeDisallowedTools = mode.getDisallowedTools();
// Combine with existing allowed tools
const combinedAllowedTools = [
...context.inputs.allowedTools,
...modeAllowedTools,
];
const combinedDisallowedTools = [
...context.inputs.disallowedTools,
...modeDisallowedTools,
];
const allAllowedTools = buildAllowedToolsString(
combinedAllowedTools,
hasActionsReadPermission,
context.inputs.useCommitSigning,
);
const allDisallowedTools = buildDisallowedToolsString(
combinedDisallowedTools,
combinedAllowedTools,
);
console.log(`Allowed tools: ${allAllowedTools}`);
console.log(`Disallowed tools: ${allDisallowedTools}`);
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
core.exportVariable("DISALLOWED_TOOLS", allDisallowedTools);
}

View File

@@ -13,70 +13,52 @@ describe("Agent Mode", () => {
}); });
}); });
test("agent mode has correct properties and behavior", () => { test("agent mode has correct properties", () => {
// Basic properties
expect(agentMode.name).toBe("agent"); expect(agentMode.name).toBe("agent");
expect(agentMode.description).toBe( expect(agentMode.description).toBe(
"Automation mode that always runs without trigger checking", "Automation mode for workflow_dispatch and schedule events",
); );
expect(agentMode.shouldCreateTrackingComment()).toBe(false); expect(agentMode.shouldCreateTrackingComment()).toBe(false);
// Tool methods return empty arrays
expect(agentMode.getAllowedTools()).toEqual([]); expect(agentMode.getAllowedTools()).toEqual([]);
expect(agentMode.getDisallowedTools()).toEqual([]); expect(agentMode.getDisallowedTools()).toEqual([]);
// Always triggers regardless of context
const contextWithoutTrigger = createMockContext({
eventName: "workflow_dispatch",
isPR: false,
inputs: {
...createMockContext().inputs,
triggerPhrase: "@claude",
},
payload: {} as any,
});
expect(agentMode.shouldTrigger(contextWithoutTrigger)).toBe(true);
}); });
test("prepareContext includes all required data", () => { test("prepareContext returns minimal data", () => {
const data = {
commentId: 789,
baseBranch: "develop",
claudeBranch: "claude/automated-task",
};
const context = agentMode.prepareContext(mockContext, data);
expect(context.mode).toBe("agent");
expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBe(789);
expect(context.baseBranch).toBe("develop");
expect(context.claudeBranch).toBe("claude/automated-task");
});
test("prepareContext works without data", () => {
const context = agentMode.prepareContext(mockContext); const context = agentMode.prepareContext(mockContext);
expect(context.mode).toBe("agent"); expect(context.mode).toBe("agent");
expect(context.githubContext).toBe(mockContext); expect(context.githubContext).toBe(mockContext);
expect(context.commentId).toBeUndefined(); // Agent mode doesn't use comment tracking or branch management
expect(context.baseBranch).toBeUndefined(); expect(Object.keys(context)).toEqual(["mode", "githubContext"]);
expect(context.claudeBranch).toBeUndefined();
}); });
test("agent mode triggers for all event types", () => { test("agent mode only triggers for workflow_dispatch and schedule events", () => {
const events = [ // Should trigger for automation events
const workflowDispatchContext = createMockContext({
eventName: "workflow_dispatch",
isPR: false,
});
expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(true);
const scheduleContext = createMockContext({
eventName: "schedule",
isPR: false,
});
expect(agentMode.shouldTrigger(scheduleContext)).toBe(true);
// Should NOT trigger for other events
const otherEvents = [
"push", "push",
"schedule",
"workflow_dispatch",
"repository_dispatch", "repository_dispatch",
"issue_comment", "issue_comment",
"pull_request", "pull_request",
"pull_request_review",
"issues",
]; ];
events.forEach((eventName) => { otherEvents.forEach((eventName) => {
const context = createMockContext({ eventName, isPR: false }); const context = createMockContext({ eventName, isPR: false });
expect(agentMode.shouldTrigger(context)).toBe(true); expect(agentMode.shouldTrigger(context)).toBe(false);
}); });
}); });
}); });