diff --git a/src/modes/agent/index.ts b/src/modes/agent/index.ts index caa9435..a32260a 100644 --- a/src/modes/agent/index.ts +++ b/src/modes/agent/index.ts @@ -1,34 +1,31 @@ import * as core from "@actions/core"; import { mkdir, writeFile } from "fs/promises"; import type { Mode, ModeOptions, ModeResult } from "../types"; -import { prepareMcpConfig } from "../../mcp/install-mcp-server"; -import { exportToolEnvironmentVariables } from "../../tools/export"; /** * Agent mode implementation. * - * This mode is designed for automation and workflow_dispatch scenarios. - * It always triggers (no checking), allows highly flexible configurations, - * and works well with override_prompt for custom workflows. - * - * In the future, this mode could restrict certain tools for safety in automation contexts, - * e.g., disallowing WebSearch or limiting file system operations. + * This mode is specifically designed for automation events (workflow_dispatch and schedule). + * It bypasses the standard trigger checking and comment tracking used by tag mode, + * making it ideal for scheduled tasks and manual workflow runs. */ export const agentMode: Mode = { name: "agent", - description: "Automation mode that always runs without trigger checking", + description: "Automation mode for workflow_dispatch and schedule events", - shouldTrigger() { - return true; + shouldTrigger(context) { + // 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 { mode: "agent", githubContext: context, - commentId: data?.commentId, - baseBranch: data?.baseBranch, - claudeBranch: data?.claudeBranch, }; }, @@ -44,9 +41,8 @@ export const agentMode: Mode = { return false; }, - async prepare({ context, githubToken }: ModeOptions): Promise { - // Agent mode is designed for automation events (workflow_dispatch, schedule) - // and potentially other events where we want full automation without tracking + async prepare({ context }: ModeOptions): Promise { + // Agent mode handles automation events (workflow_dispatch, schedule) only // Create prompt directory await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, { @@ -67,24 +63,48 @@ export const agentMode: Mode = { promptContent, ); - // Export tool environment variables - exportToolEnvironmentVariables(agentMode, context); + // Export tool environment variables for agent mode + 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 mcpConfig = await prepareMcpConfig({ - githubToken, - owner: context.repository.owner, - repo: context.repository.repo, - branch: "", // No specific branch for agent mode - baseBranch: "", // No base branch needed - additionalMcpConfig, - claudeCommentId: "", - allowedTools: context.inputs.allowedTools, - context, - }); + if (additionalMcpConfig.trim()) { + try { + const additional = JSON.parse(additionalMcpConfig); + if (additional && typeof additional === "object") { + Object.assign(mcpConfig, additional); + } + } catch (error) { + core.warning(`Failed to parse additional MCP config: ${error}`); + } + } - core.setOutput("mcp_config", mcpConfig); + core.setOutput("mcp_config", JSON.stringify(mcpConfig)); return { commentId: undefined, @@ -93,7 +113,7 @@ export const agentMode: Mode = { currentBranch: "", claudeBranch: undefined, }, - mcpConfig, + mcpConfig: JSON.stringify(mcpConfig), }; }, }; diff --git a/src/tools/export.ts b/src/tools/export.ts deleted file mode 100644 index 5d37a2e..0000000 --- a/src/tools/export.ts +++ /dev/null @@ -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); -} diff --git a/test/modes/agent.test.ts b/test/modes/agent.test.ts index d6583c8..9302790 100644 --- a/test/modes/agent.test.ts +++ b/test/modes/agent.test.ts @@ -13,70 +13,52 @@ describe("Agent Mode", () => { }); }); - test("agent mode has correct properties and behavior", () => { - // Basic properties + test("agent mode has correct properties", () => { expect(agentMode.name).toBe("agent"); 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); - - // Tool methods return empty arrays expect(agentMode.getAllowedTools()).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", () => { - 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", () => { + test("prepareContext returns minimal data", () => { const context = agentMode.prepareContext(mockContext); expect(context.mode).toBe("agent"); expect(context.githubContext).toBe(mockContext); - expect(context.commentId).toBeUndefined(); - expect(context.baseBranch).toBeUndefined(); - expect(context.claudeBranch).toBeUndefined(); + // Agent mode doesn't use comment tracking or branch management + expect(Object.keys(context)).toEqual(["mode", "githubContext"]); }); - test("agent mode triggers for all event types", () => { - const events = [ + test("agent mode only triggers for workflow_dispatch and schedule 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", - "schedule", - "workflow_dispatch", "repository_dispatch", "issue_comment", "pull_request", + "pull_request_review", + "issues", ]; - events.forEach((eventName) => { + otherEvents.forEach((eventName) => { const context = createMockContext({ eventName, isPR: false }); - expect(agentMode.shouldTrigger(context)).toBe(true); + expect(agentMode.shouldTrigger(context)).toBe(false); }); }); });