Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/issue-monster.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion .github/workflows/workflow-generator.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 49 additions & 8 deletions actions/setup/js/assign_agent_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,12 @@ async function getPullRequestDetails(owner, repo, pullNumber) {
* @param {string} agentName - Agent name for error messages
* @param {string[]|null} allowedAgents - Optional list of allowed agent names. If provided, filters out non-allowed agents from current assignees.
* @param {string|null} pullRequestRepoId - Optional pull request repository ID for specifying where the PR should be created (GitHub agentAssignment.targetRepositoryId)
* @param {string|null} model - Optional AI model to use (e.g., "claude-opus-4.6", "auto")
* @param {string|null} customAgent - Optional custom agent ID for custom agents
* @param {string|null} customInstructions - Optional custom instructions for the agent
* @returns {Promise<boolean>} True if successful
*/
async function assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents = null, pullRequestRepoId = null) {
async function assignAgentToIssue(assignableId, agentId, currentAssignees, agentName, allowedAgents = null, pullRequestRepoId = null, model = null, customAgent = null, customInstructions = null) {
// Filter current assignees based on allowed list (if configured)
let filteredAssignees = currentAssignees;
if (allowedAgents && allowedAgents.length > 0) {
Expand All @@ -272,29 +275,60 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent
// Build actor IDs array - include new agent and preserve filtered assignees
const actorIds = [agentId, ...filteredAssignees.map(a => a.id).filter(id => id !== agentId)];

// Build the mutation - conditionally include agentAssignment if pullRequestRepoId is provided
// Build the agentAssignment object if any agent-specific parameters are provided
const hasAgentAssignment = pullRequestRepoId || model || customAgent || customInstructions;

// Build the mutation - conditionally include agentAssignment if any parameters are provided
let mutation;
let variables;

if (pullRequestRepoId) {
// Include agentAssignment with targetRepositoryId for cross-repo PR creation
if (hasAgentAssignment) {
// Build agentAssignment object with only the fields that are provided
const agentAssignmentFields = [];
const agentAssignmentParams = [];

if (pullRequestRepoId) {
agentAssignmentFields.push("targetRepositoryId: $targetRepoId");
agentAssignmentParams.push("$targetRepoId: ID!");
}
if (model) {
agentAssignmentFields.push("model: $model");
agentAssignmentParams.push("$model: String!");
}
if (customAgent) {
agentAssignmentFields.push("customAgent: $customAgent");
agentAssignmentParams.push("$customAgent: String!");
}
if (customInstructions) {
agentAssignmentFields.push("customInstructions: $customInstructions");
agentAssignmentParams.push("$customInstructions: String!");
}

// Build the mutation with agentAssignment
const allParams = ["$assignableId: ID!", "$actorIds: [ID!]!", ...agentAssignmentParams].join(", ");
const assignmentFields = agentAssignmentFields.join("\n ");

mutation = `
mutation($assignableId: ID!, $actorIds: [ID!]!, $targetRepoId: ID!) {
mutation(${allParams}) {
replaceActorsForAssignable(input: {
assignableId: $assignableId,
actorIds: $actorIds,
agentAssignment: {
targetRepositoryId: $targetRepoId
${assignmentFields}
}
}) {
__typename
}
}
`;

variables = {
assignableId: assignableId,
actorIds,
targetRepoId: pullRequestRepoId,
...(pullRequestRepoId && { targetRepoId: pullRequestRepoId }),
...(model && { model }),
...(customAgent && { customAgent }),
...(customInstructions && { customInstructions }),
};
} else {
// Standard mutation without agentAssignment
Expand All @@ -317,7 +351,14 @@ async function assignAgentToIssue(assignableId, agentId, currentAssignees, agent
try {
core.info("Using built-in github object for mutation");

core.debug(`GraphQL mutation with variables: assignableId=${assignableId}, actorIds=${JSON.stringify(actorIds)}${pullRequestRepoId ? `, targetRepoId=${pullRequestRepoId}` : ""}`);
// Build debug log message with all parameters
let debugMsg = `GraphQL mutation with variables: assignableId=${assignableId}, actorIds=${JSON.stringify(actorIds)}`;
if (pullRequestRepoId) debugMsg += `, targetRepoId=${pullRequestRepoId}`;
if (model) debugMsg += `, model=${model}`;
if (customAgent) debugMsg += `, customAgent=${customAgent}`;
if (customInstructions) debugMsg += `, customInstructions=${customInstructions.substring(0, 50)}...`;
core.debug(debugMsg);

const response = await github.graphql(mutation, {
...variables,
headers: {
Expand Down
118 changes: 118 additions & 0 deletions actions/setup/js/assign_agent_helpers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,124 @@ describe("assign_agent_helpers.cjs", () => {
const agentMatches = calledArgs.actorIds.filter(id => id === "AGENT_456");
expect(agentMatches.length).toBe(1);
});

it("should include model in agentAssignment when provided", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, "claude-opus-4.6");

const calledArgs = mockGithub.graphql.mock.calls[0];
const mutation = calledArgs[0];
const variables = calledArgs[1];

// Mutation should include agentAssignment with model
expect(mutation).toContain("agentAssignment");
expect(mutation).toContain("model: $model");
expect(variables.model).toBe("claude-opus-4.6");
});

it("should include customAgent in agentAssignment when provided", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, "custom-agent-123");

const calledArgs = mockGithub.graphql.mock.calls[0];
const mutation = calledArgs[0];
const variables = calledArgs[1];

// Mutation should include agentAssignment with customAgent
expect(mutation).toContain("agentAssignment");
expect(mutation).toContain("customAgent: $customAgent");
expect(variables.customAgent).toBe("custom-agent-123");
});

it("should include customInstructions in agentAssignment when provided", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, null, "Focus on performance optimization");

const calledArgs = mockGithub.graphql.mock.calls[0];
const mutation = calledArgs[0];
const variables = calledArgs[1];

// Mutation should include agentAssignment with customInstructions
expect(mutation).toContain("agentAssignment");
expect(mutation).toContain("customInstructions: $customInstructions");
expect(variables.customInstructions).toBe("Focus on performance optimization");
});

it("should include multiple agentAssignment parameters when provided", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, "REPO_ID_789", "claude-opus-4.6", "custom-agent-123", "Focus on performance");

const calledArgs = mockGithub.graphql.mock.calls[0];
const mutation = calledArgs[0];
const variables = calledArgs[1];

// Mutation should include agentAssignment with all parameters
expect(mutation).toContain("agentAssignment");
expect(mutation).toContain("targetRepositoryId: $targetRepoId");
expect(mutation).toContain("model: $model");
expect(mutation).toContain("customAgent: $customAgent");
expect(mutation).toContain("customInstructions: $customInstructions");

expect(variables.targetRepoId).toBe("REPO_ID_789");
expect(variables.model).toBe("claude-opus-4.6");
expect(variables.customAgent).toBe("custom-agent-123");
expect(variables.customInstructions).toBe("Focus on performance");
});

it("should omit agentAssignment when no agent-specific parameters provided", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, null, null, null);

const calledArgs = mockGithub.graphql.mock.calls[0];
const mutation = calledArgs[0];

// Mutation should NOT include agentAssignment when no parameters provided
expect(mutation).not.toContain("agentAssignment");
});

it("should include only provided agentAssignment fields", async () => {
mockGithub.graphql.mockResolvedValueOnce({
replaceActorsForAssignable: {
__typename: "ReplaceActorsForAssignablePayload",
},
});

// Only provide model, not customAgent or customInstructions
await assignAgentToIssue("ISSUE_123", "AGENT_456", [{ id: "USER_1", login: "user1" }], "copilot", null, null, "claude-opus-4.6", null, null);

const calledArgs = mockGithub.graphql.mock.calls[0];
const variables = calledArgs[1];

// Only model should be in variables
expect(variables.model).toBe("claude-opus-4.6");
expect(variables.customAgent).toBeUndefined();
expect(variables.customInstructions).toBeUndefined();
});
});

describe("generatePermissionErrorSummary", () => {
Expand Down
Loading
Loading