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
17 changes: 12 additions & 5 deletions src/services/agent-orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export async function explainBlockersWithAgent(env: Env, input: AgentPlanRequest
const run = buildRunRecord({
objective: `Explain scoreability and review blockers${repoFullName ? ` for ${repoFullName}` : ""}.`,
actorLogin: login,
surface: "api",
surface: isLocalBranch ? "api" : ((input as AgentPlanRequest).surface ?? "api"),
status: "running",
payload: isLocalBranch
? { kind: "explain_branch_blockers", input: input as unknown as Record<string, JsonValue> }
Expand Down Expand Up @@ -221,10 +221,12 @@ async function executeDecisionPackRun(env: Env, run: AgentRunRecord, kind: strin
const pack = serving.pack;
const isStale = pack.freshness !== "fresh";
const decisions = repoFullName ? pack.repoDecisions.filter((decision) => sameRepo(decision.repoFullName, repoFullName)) : pack.repoDecisions;
const allowCrossRepoFallback = !repoFullName || run.surface !== "github_comment";
const scopedDecisionActions = decisions.length > 0 ? decisions : allowCrossRepoFallback ? pack.repoDecisions : [];
const actions =
kind === "explain_blockers"
? buildBlockerActions(run, pack, decisions)
: buildDecisionActions(run, pack, decisions.length > 0 ? decisions : pack.repoDecisions);
? buildBlockerActions(run, pack, decisions, { allowFallback: allowCrossRepoFallback })
: buildDecisionActions(run, pack, scopedDecisionActions);
const contexts = [contextSnapshotFromPack(run.id, pack, decisions)];
await replaceAgentActions(env, run.id, actions);
await persistAgentContextSnapshot(env, contexts[0]!);
Expand Down Expand Up @@ -328,8 +330,13 @@ function buildDecisionActions(run: AgentRunRecord, pack: ContributorDecisionPack
return decisions.slice(0, 5).map((decision, index) => actionFromRepoDecision(run, decision, index));
}

function buildBlockerActions(run: AgentRunRecord, pack: ContributorDecisionPack, decisions: RepoDecision[]): AgentActionRecord[] {
const selected = decisions.length > 0 ? decisions : pack.repoDecisions.filter((decision) => decision.scoreBlockers.length > 0).slice(0, 6);
function buildBlockerActions(
run: AgentRunRecord,
pack: ContributorDecisionPack,
decisions: RepoDecision[],
options: { allowFallback?: boolean } = {},
): AgentActionRecord[] {
const selected = decisions.length > 0 ? decisions : options.allowFallback === false ? [] : pack.repoDecisions.filter((decision) => decision.scoreBlockers.length > 0).slice(0, 6);
return selected.slice(0, 8).map((decision, index) =>
actionRecord({
run,
Expand Down
50 changes: 50 additions & 0 deletions test/unit/agent-orchestrator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type AgentRunBundle,
} from "../../src/services/agent-orchestrator";
import { CONTRIBUTOR_DECISION_PACK_SIGNAL, type ContributorDecisionPack } from "../../src/services/decision-pack";
import { buildPublicAgentCommandComment, parseGittensoryMentionCommand } from "../../src/github/commands";
import { normalizeRegistryPayload } from "../../src/registry/normalize";
import { persistRegistrySnapshot } from "../../src/registry/sync";
import type { AgentRunRecord, JsonValue } from "../../src/types";
Expand Down Expand Up @@ -126,6 +127,55 @@ describe("agent orchestrator", () => {
});
});

it("does not fall back to cross-repo private rankings for public GitHub comments", async () => {
const env = createTestEnv();
const secretDecision = repoDecision({
repoFullName: "private-org/secret-alpha",
priorityScore: 99,
nextActions: ["Privately prioritize the secret-alpha patch before opening more public work."],
publicNextActions: [],
});
await persistDecisionPack(
env,
decisionPackFixture({
repoDecisions: [secretDecision],
topActions: [
{
...action("open_new_direct_pr", "private-org/secret-alpha", "pursue", 99),
nextActions: ["Privately prioritize the secret-alpha patch before opening more public work."],
publicNextActions: [],
},
],
}),
);

const publicPlan = await planNextWork(env, {
login: "oktofeesh1",
repoFullName: "public-org/installed-repo",
surface: "github_comment",
objective: "Respond to @gittensory next-action for public-org/installed-repo#101.",
});
const publicBlockers = await explainBlockersWithAgent(env, {
login: "oktofeesh1",
repoFullName: "public-org/installed-repo",
surface: "github_comment",
});
const comment = buildPublicAgentCommandComment({
command: parseGittensoryMentionCommand("@gittensory next-action")!,
repo: null,
issue: { number: 101, title: "Public PR", state: "open", pull_request: {} },
pullRequest: null,
actorKind: "maintainer",
bundle: publicPlan,
});

expect(publicPlan.actions).toHaveLength(0);
expect(publicBlockers.actions).toHaveLength(0);
expect(publicPlan.contextSnapshots[0]?.payload).toMatchObject({ selectedRepos: [] });
expect(comment).toContain("No public-safe context is available");
expect(comment).not.toMatch(/private-org\/secret-alpha|secret-alpha patch|Privately prioritize/i);
});

it("serves a stale decision pack as a completed run with degraded data quality and a freshness warning", async () => {
const sent: unknown[] = [];
const env = createTestEnv({
Expand Down