diff --git a/src/services/agent-orchestrator.ts b/src/services/agent-orchestrator.ts index 3f3ca2fb24..7c3deaf62d 100644 --- a/src/services/agent-orchestrator.ts +++ b/src/services/agent-orchestrator.ts @@ -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 } @@ -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]!); @@ -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, diff --git a/test/unit/agent-orchestrator.test.ts b/test/unit/agent-orchestrator.test.ts index 252df115ef..1fca5e4502 100644 --- a/test/unit/agent-orchestrator.test.ts +++ b/test/unit/agent-orchestrator.test.ts @@ -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"; @@ -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({