Simplify agent mode and re-add additional_permissions input

- Agent mode now only triggers when explicit prompt is provided
- Removed automatic triggering for workflow_dispatch/schedule without prompt
- Re-added additional_permissions input for requesting GitHub permissions
- Fixed TypeScript types for mock context helpers to properly handle partial inputs
- Updated documentation to reflect simplified mode behavior
This commit is contained in:
km-anthropic
2025-08-08 14:00:31 -07:00
parent e2aee89b4a
commit 1b4fc382c8
8 changed files with 75 additions and 41 deletions

View File

@@ -53,7 +53,7 @@ Execution steps:
#### Mode System (`src/modes/`) #### Mode System (`src/modes/`)
- **Tag Mode** (`tag/`): Responds to `@claude` mentions and issue assignments - **Tag Mode** (`tag/`): Responds to `@claude` mentions and issue assignments
- **Agent Mode** (`agent/`): Automated execution for workflow_dispatch and schedule events only - **Agent Mode** (`agent/`): Direct execution when explicit prompt is provided
- Extensible registry pattern in `modes/registry.ts` - Extensible registry pattern in `modes/registry.ts`
#### GitHub Integration (`src/github/`) #### GitHub Integration (`src/github/`)
@@ -118,7 +118,7 @@ src/
- Modes implement `Mode` interface with `shouldTrigger()` and `prepare()` methods - Modes implement `Mode` interface with `shouldTrigger()` and `prepare()` methods
- Registry validates mode compatibility with GitHub event types - Registry validates mode compatibility with GitHub event types
- Agent mode only works with workflow_dispatch and schedule events - Agent mode triggers when explicit prompt is provided
### Comment Threading ### Comment Threading

View File

@@ -65,6 +65,10 @@ inputs:
description: "Additional arguments to pass directly to Claude CLI" description: "Additional arguments to pass directly to Claude CLI"
required: false required: false
default: "" default: ""
additional_permissions:
description: "Additional GitHub permissions to request (e.g., 'actions: read')"
required: false
default: ""
use_sticky_comment: use_sticky_comment:
description: "Use just one comment to deliver issue/PR comments" description: "Use just one comment to deliver issue/PR comments"
required: false required: false
@@ -119,6 +123,7 @@ runs:
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }} USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }} DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }} USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
ADDITIONAL_PERMISSIONS: ${{ inputs.additional_permissions }}
- name: Install Base Action Dependencies - name: Install Base Action Dependencies
if: steps.prepare.outputs.contains_trigger == 'true' if: steps.prepare.outputs.contains_trigger == 'true'

View File

@@ -25,19 +25,19 @@ The traditional implementation mode that responds to @claude mentions, issue ass
**Note: Agent mode is currently in active development and may undergo breaking changes.** **Note: Agent mode is currently in active development and may undergo breaking changes.**
For automation with workflow_dispatch and scheduled events only. For direct automation when an explicit prompt is provided.
- **Triggers**: Only works with `workflow_dispatch` and `schedule` events - does NOT work with PR/issue events - **Triggers**: Works with any event when `prompt` input is provided
- **Features**: Perfect for scheduled tasks, works with `override_prompt` - **Features**: Direct execution without @claude mentions, no tracking comments
- **Use case**: Maintenance tasks, automated reporting, scheduled checks - **Use case**: Automated PR reviews, scheduled tasks, workflow automation
```yaml ```yaml
- uses: anthropics/claude-code-action@beta - uses: anthropics/claude-code-action@beta
with: with:
mode: agent
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
override_prompt: | prompt: |
Check for outdated dependencies and create an issue if any are found. Check for outdated dependencies and create an issue if any are found.
# Mode is auto-detected when prompt is provided
``` ```
### Experimental Review Mode ### Experimental Review Mode

View File

@@ -1,23 +1,22 @@
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 { isAutomationContext } from "../../github/context";
import type { PreparedContext } from "../../create-prompt/types"; import type { PreparedContext } from "../../create-prompt/types";
/** /**
* Agent mode implementation. * Agent mode implementation.
* *
* This mode is specifically designed for automation events (workflow_dispatch and schedule). * This mode runs whenever an explicit prompt is provided in the workflow configuration.
* It bypasses the standard trigger checking and comment tracking used by tag mode, * It bypasses the standard @claude mention checking and comment tracking used by tag mode,
* making it ideal for scheduled tasks and manual workflow runs. * providing direct access to Claude Code for automation workflows.
*/ */
export const agentMode: Mode = { export const agentMode: Mode = {
name: "agent", name: "agent",
description: "Automation mode for workflow_dispatch and schedule events", description: "Direct automation mode for explicit prompts",
shouldTrigger(context) { shouldTrigger(context) {
// Only trigger for automation events // Only trigger when an explicit prompt is provided
return isAutomationContext(context); return !!context.inputs?.prompt;
}, },
prepareContext(context) { prepareContext(context) {
@@ -41,7 +40,7 @@ export const agentMode: Mode = {
}, },
async prepare({ context }: ModeOptions): Promise<ModeResult> { async prepare({ context }: ModeOptions): Promise<ModeResult> {
// Agent mode handles automation events (workflow_dispatch, schedule) only // Agent mode handles automation events and any event with explicit prompts
// TODO: handle by createPrompt (similar to tag and review modes) // TODO: handle by createPrompt (similar to tag and review modes)
// Create prompt directory // Create prompt directory

View File

@@ -9,13 +9,7 @@ import { checkContainsTrigger } from "../github/validation/trigger";
export type AutoDetectedMode = "tag" | "agent"; export type AutoDetectedMode = "tag" | "agent";
export function detectMode(context: GitHubContext): AutoDetectedMode { export function detectMode(context: GitHubContext): AutoDetectedMode {
// If prompt is provided, always use agent mode // If prompt is provided, use agent mode for direct execution
// Reasoning: When users provide explicit instructions via the prompt parameter,
// they want Claude to execute those instructions immediately without waiting for
// @claude mentions or other triggers. This aligns with the v1.0 philosophy where
// Claude Code handles everything - the GitHub Action is just a thin wrapper that
// passes through prompts directly to Claude Code for native handling (including
// slash commands). This provides the most direct and flexible interaction model.
if (context.inputs?.prompt) { if (context.inputs?.prompt) {
return "agent"; return "agent";
} }
@@ -38,7 +32,7 @@ export function detectMode(context: GitHubContext): AutoDetectedMode {
} }
} }
// Default to agent mode for everything else // Default to agent mode (which won't trigger without a prompt)
return "agent"; return "agent";
} }
@@ -47,7 +41,7 @@ export function getModeDescription(mode: AutoDetectedMode): string {
case "tag": case "tag":
return "Interactive mode triggered by @claude mentions"; return "Interactive mode triggered by @claude mentions";
case "agent": case "agent":
return "General automation mode for all events"; return "Direct automation mode for explicit prompts";
default: default:
return "Unknown mode"; return "Unknown mode";
} }

View File

@@ -26,7 +26,7 @@ export type ModeData = {
* *
* Current modes include: * Current modes include:
* - 'tag': Interactive mode triggered by @claude mentions * - 'tag': Interactive mode triggered by @claude mentions
* - 'agent': General automation mode for all events (default) * - 'agent': Direct automation mode triggered by explicit prompts
*/ */
export type Mode = { export type Mode = {
name: ModeName; name: ModeName;

View File

@@ -27,8 +27,12 @@ const defaultRepository = {
full_name: "test-owner/test-repo", full_name: "test-owner/test-repo",
}; };
type MockContextOverrides = Omit<Partial<ParsedGitHubContext>, 'inputs'> & {
inputs?: Partial<ParsedGitHubContext['inputs']>;
};
export const createMockContext = ( export const createMockContext = (
overrides: Partial<ParsedGitHubContext> = {}, overrides: MockContextOverrides = {},
): ParsedGitHubContext => { ): ParsedGitHubContext => {
const baseContext: ParsedGitHubContext = { const baseContext: ParsedGitHubContext = {
runId: "1234567890", runId: "1234567890",
@@ -42,15 +46,19 @@ export const createMockContext = (
inputs: defaultInputs, inputs: defaultInputs,
}; };
if (overrides.inputs) { const mergedInputs = overrides.inputs
overrides.inputs = { ...defaultInputs, ...overrides.inputs }; ? { ...defaultInputs, ...overrides.inputs }
} : defaultInputs;
return { ...baseContext, ...overrides }; return { ...baseContext, ...overrides, inputs: mergedInputs };
};
type MockAutomationOverrides = Omit<Partial<AutomationContext>, 'inputs'> & {
inputs?: Partial<AutomationContext['inputs']>;
}; };
export const createMockAutomationContext = ( export const createMockAutomationContext = (
overrides: Partial<AutomationContext> = {}, overrides: MockAutomationOverrides = {},
): AutomationContext => { ): AutomationContext => {
const baseContext: AutomationContext = { const baseContext: AutomationContext = {
runId: "1234567890", runId: "1234567890",
@@ -62,7 +70,11 @@ export const createMockAutomationContext = (
inputs: defaultInputs, inputs: defaultInputs,
}; };
return { ...baseContext, ...overrides }; const mergedInputs = overrides.inputs
? { ...defaultInputs, ...overrides.inputs }
: defaultInputs;
return { ...baseContext, ...overrides, inputs: mergedInputs };
}; };
export const mockIssueOpenedContext: ParsedGitHubContext = { export const mockIssueOpenedContext: ParsedGitHubContext = {

View File

@@ -15,7 +15,7 @@ describe("Agent Mode", () => {
test("agent mode has correct properties", () => { test("agent mode has correct properties", () => {
expect(agentMode.name).toBe("agent"); expect(agentMode.name).toBe("agent");
expect(agentMode.description).toBe( expect(agentMode.description).toBe(
"Automation mode for workflow_dispatch and schedule events", "Direct automation mode for explicit prompts",
); );
expect(agentMode.shouldCreateTrackingComment()).toBe(false); expect(agentMode.shouldCreateTrackingComment()).toBe(false);
expect(agentMode.getAllowedTools()).toEqual([]); expect(agentMode.getAllowedTools()).toEqual([]);
@@ -31,19 +31,19 @@ describe("Agent Mode", () => {
expect(Object.keys(context)).toEqual(["mode", "githubContext"]); expect(Object.keys(context)).toEqual(["mode", "githubContext"]);
}); });
test("agent mode only triggers for workflow_dispatch and schedule events", () => { test("agent mode only triggers when prompt is provided", () => {
// Should trigger for automation events // Should NOT trigger for automation events without prompt
const workflowDispatchContext = createMockAutomationContext({ const workflowDispatchContext = createMockAutomationContext({
eventName: "workflow_dispatch", eventName: "workflow_dispatch",
}); });
expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(true); expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(false);
const scheduleContext = createMockAutomationContext({ const scheduleContext = createMockAutomationContext({
eventName: "schedule", eventName: "schedule",
}); });
expect(agentMode.shouldTrigger(scheduleContext)).toBe(true); expect(agentMode.shouldTrigger(scheduleContext)).toBe(false);
// Should NOT trigger for entity events // Should NOT trigger for entity events without prompt
const entityEvents = [ const entityEvents = [
"issue_comment", "issue_comment",
"pull_request", "pull_request",
@@ -52,8 +52,32 @@ describe("Agent Mode", () => {
] as const; ] as const;
entityEvents.forEach((eventName) => { entityEvents.forEach((eventName) => {
const context = createMockContext({ eventName }); const contextNoPrompt = createMockContext({ eventName });
expect(agentMode.shouldTrigger(context)).toBe(false); expect(agentMode.shouldTrigger(contextNoPrompt)).toBe(false);
});
// Should trigger for ANY event when prompt is provided
const allEvents = [
"workflow_dispatch",
"schedule",
"issue_comment",
"pull_request",
"pull_request_review",
"issues",
] as const;
allEvents.forEach((eventName) => {
const contextWithPrompt =
eventName === "workflow_dispatch" || eventName === "schedule"
? createMockAutomationContext({
eventName,
inputs: { prompt: "Do something" },
})
: createMockContext({
eventName,
inputs: { prompt: "Do something" },
});
expect(agentMode.shouldTrigger(contextWithPrompt)).toBe(true);
}); });
}); });
}); });