Skip to content
Closed
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
7 changes: 3 additions & 4 deletions src/review/unified-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,10 +673,9 @@ export function renderUnifiedReviewComment(input: UnifiedReviewInput, ctx: Unifi
? appendMoreFooter(bullets(blockersTrunc.shown), blockersTrunc.hiddenCount)
: `_+${blockersTrunc.hiddenCount} more_`;
blocks.push(`**${heading}**\n${blockersBody}`);
// The FULL (pre-display-truncation) blocker set, not blockersTrunc.shown -- an AI agent benefits from
// every blocker, not just the human-scannable capped subset shown above. Never gated by verbosity: this
// is an extension of the blockers themselves (never gated), not decorative detail like Nits.
blocks.push(buildAiContextBlock(blockersAll, collapsiblesOpen));
// Keep the copyable AI prompt inside the same public display cap as the human blocker list: hidden
// blockers must remain represented only by the shared "+N more" footer, not re-expanded in a collapsible.
if (blockersTrunc.shown.length) blocks.push(buildAiContextBlock(blockersTrunc.shown, collapsiblesOpen));
}

// Category breakdown (#2150): a compact, deterministic one-liner of the finding mix (e.g. "2 correctness 路
Expand Down
21 changes: 16 additions & 5 deletions test/unit/unified-comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,18 +553,27 @@ describe("'Copy for AI agents' block", () => {
expect(md).toContain("> 2. Second issue.");
});

it("uses the FULL blocker set, not the display-truncated one, when maxFindingsCaps.blockers is set", () => {
it("keeps the AI copy block within maxFindingsCaps.blockers", () => {
const md = renderUnifiedReviewComment(
{ ...base, decision: "close", blockers: ["Alpha", "Beta", "Gamma"], maxFindingsCaps: { blockers: 1, nits: null } },
{},
);
// Human-facing bullets are capped at 1 (plus a "+N more" footer)...
expect(md).toMatch(/Alpha[\s\S]*_\+2 more_/);
// ...but the AI-context block still gets every blocker, since an agent benefits from full context.
const aiSection = md.split("馃搵 Copy for AI agents")[1]!;
expect(aiSection).toContain("Alpha");
expect(aiSection).toContain("Beta");
expect(aiSection).toContain("Gamma");
expect(aiSection).not.toContain("Beta");
expect(aiSection).not.toContain("Gamma");
});

it("omits the AI copy block when maxFindingsCaps.blockers is zero", () => {
const md = renderUnifiedReviewComment(
{ ...base, decision: "close", blockers: ["Alpha", "Beta"], maxFindingsCaps: { blockers: 0, nits: null } },
{},
);
expect(md).toContain("_+2 more_");
expect(md).not.toContain("Copy for AI agents");
expect(md).not.toContain("Alpha");
expect(md).not.toContain("Beta");
});

it("is NOT dropped by comment_verbosity: quiet (it extends the never-gated blockers, not decorative detail)", () => {
Expand Down Expand Up @@ -745,6 +754,8 @@ describe("review.max_findings display caps (#2049)", () => {
});
expect(capped).toContain("- alpha blocker");
expect(capped).not.toContain("- beta blocker");
expect(capped).not.toContain("2. beta blocker");
expect(capped).not.toContain("gamma blocker");
expect(capped).toContain("+2 more");
expect(capped).toContain("`3 blockers`");
expect(capped).toContain("+1 more");
Expand Down
93 changes: 74 additions & 19 deletions worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: d5851e3e7ea2bc8cd084f39b4328cb86)
// Runtime types generated with workerd@1.20260701.1 2026-05-28 nodejs_compat
// Runtime types generated with workerd@1.20260708.1 2026-05-28 nodejs_compat
interface __BaseEnv_Env {
DB: D1Database;
JOBS: Queue;
Expand Down Expand Up @@ -621,7 +621,7 @@ declare abstract class DurableObjectNamespace<T extends Rpc.DurableObjectBranded
getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;
}
type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high";
type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high" | "us";
interface DurableObjectNamespaceNewUniqueIdOptions {
jurisdiction?: DurableObjectJurisdiction;
}
Expand Down Expand Up @@ -12316,6 +12316,13 @@ interface ForwardableEmailMessage extends EmailMessage {
* @returns A promise that resolves when the email message is replied.
*/
reply(message: EmailMessage): Promise<EmailSendResult>;
/**
* Reply to the sender of this email message with a message built from the given
* fields. Threading headers (In-Reply-To/References) are set automatically.
* @param builder The reply message contents.
* @returns A promise that resolves when the email message is replied.
*/
reply(builder: EmailReplyMessageBuilder): Promise<EmailSendResult>;
}
/** A file attachment for an email message */
type EmailAttachment = {
Expand All @@ -12336,23 +12343,46 @@ interface EmailAddress {
name: string;
email: string;
}
/**
* Recipient fields for `SendEmail.send()`. At least one of `to`, `cc`, or
* `bcc` must be provided.
*/
type EmailDestinations = {
to?: string | EmailAddress | (string | EmailAddress)[];
cc?: string | EmailAddress | (string | EmailAddress)[];
bcc?: string | EmailAddress | (string | EmailAddress)[];
} & ({
to: string | EmailAddress | (string | EmailAddress)[];
} | {
cc: string | EmailAddress | (string | EmailAddress)[];
} | {
bcc: string | EmailAddress | (string | EmailAddress)[];
});
/**
* Fields shared by all composed emails (no recipients). Used directly by
* `ForwardableEmailMessage.reply()`, which always replies to the original
* sender, and extended by `EmailMessageBuilder` for `SendEmail.send()`.
*/
interface EmailReplyMessageBuilder {
from: string | EmailAddress;
subject: string;
replyTo?: string | EmailAddress;
headers?: Record<string, string>;
text?: string;
html?: string;
attachments?: EmailAttachment[];
}
/**
* Fields for composing an email without constructing raw MIME, for
* `SendEmail.send()`. Requires at least one of `to`, `cc`, or `bcc`.
*/
type EmailMessageBuilder = EmailReplyMessageBuilder & EmailDestinations;
/**
* A binding that allows a Worker to send email messages.
*/
interface SendEmail {
send(message: EmailMessage): Promise<EmailSendResult>;
send(builder: {
from: string | EmailAddress;
to: string | EmailAddress | (string | EmailAddress)[];
subject: string;
replyTo?: string | EmailAddress;
cc?: string | EmailAddress | (string | EmailAddress)[];
bcc?: string | EmailAddress | (string | EmailAddress)[];
headers?: Record<string, string>;
text?: string;
html?: string;
attachments?: EmailAttachment[];
}): Promise<EmailSendResult>;
send(builder: EmailMessageBuilder): Promise<EmailSendResult>;
}
declare abstract class EmailEvent extends ExtendableEvent {
readonly message: ForwardableEmailMessage;
Expand Down Expand Up @@ -13174,14 +13204,19 @@ declare namespace CloudflareWorkersModule {
export type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year';
export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number;
export type WorkflowDelayDuration = WorkflowSleepDuration;
export type WorkflowDynamicDelayContext = {
ctx: WorkflowStepContext<WorkflowDelayFunction>;
error: Error;
};
export type WorkflowDelayFunction = (input: WorkflowDynamicDelayContext) => WorkflowDelayDuration | Promise<WorkflowDelayDuration>;
export type WorkflowTimeoutDuration = WorkflowSleepDuration;
export type WorkflowRetentionDuration = WorkflowSleepDuration;
export type WorkflowBackoff = 'constant' | 'linear' | 'exponential';
export type WorkflowStepSensitivity = 'output';
export type WorkflowStepConfig = {
retries?: {
limit: number;
delay: WorkflowDelayDuration | number;
delay: WorkflowDelayDuration | number | WorkflowDelayFunction;
backoff?: WorkflowBackoff;
};
timeout?: WorkflowTimeoutDuration | number;
Expand All @@ -13207,13 +13242,22 @@ declare namespace CloudflareWorkersModule {
type: string;
sensitive?: WorkflowStepSensitivity;
};
export type WorkflowStepContext = {
export type WorkflowStepContext<Delay = WorkflowDelayDuration | number> = {
step: {
name: string;
count: number;
};
attempt: number;
config: WorkflowStepConfig;
config: {
retries?: {
limit: number;
backoff?: WorkflowBackoff;
} & (Delay extends WorkflowDelayFunction ? {} : {
delay: WorkflowDelayDuration | number;
});
timeout?: WorkflowTimeoutDuration | number;
sensitive?: WorkflowStepSensitivity;
};
};
export type WorkflowRollbackContext<T = unknown> = {
ctx: WorkflowStepContext;
Expand All @@ -13229,7 +13273,9 @@ declare namespace CloudflareWorkersModule {
};
export abstract class WorkflowStep {
do<T extends Rpc.Serializable<T>>(name: string, callback: (ctx: WorkflowStepContext) => Promise<T>, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>;
do<T extends Rpc.Serializable<T>>(name: string, config: WorkflowStepConfig, callback: (ctx: WorkflowStepContext) => Promise<T>, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>;
do<T extends Rpc.Serializable<T>, const C extends WorkflowStepConfig>(name: string, config: C, callback: (ctx: WorkflowStepContext<C['retries'] extends {
delay: infer D;
} ? D : WorkflowDelayDuration | number>) => Promise<T>, rollbackOptions?: WorkflowStepRollbackOptions<T>): Promise<T>;
sleep: (name: string, duration: WorkflowSleepDuration) => Promise<void>;
sleepUntil: (name: string, timestamp: Date | number) => Promise<void>;
waitForEvent<T extends Rpc.Serializable<T>>(name: string, options: {
Expand Down Expand Up @@ -14139,6 +14185,7 @@ declare namespace TailStream {
readonly dispatchNamespace?: string;
readonly entrypoint?: string;
readonly executionModel: string;
readonly durableObjectId?: string;
readonly scriptName?: string;
readonly scriptTags?: string[];
readonly scriptVersion?: ScriptVersion;
Expand Down Expand Up @@ -14693,6 +14740,13 @@ interface WorkflowError {
code?: number;
message: string;
}
interface WorkflowInstanceTerminateOptions {
/**
* If true, run registered rollback handlers before terminating the instance.
* Only steps that registered rollback handlers are rolled back.
*/
rollback?: boolean;
}
interface WorkflowInstanceRestartOptions {
/**
* Restart from a specific step. If omitted, the instance restarts from the beginning.
Expand Down Expand Up @@ -14726,8 +14780,9 @@ declare abstract class WorkflowInstance {
public resume(): Promise<void>;
/**
* Terminate the instance. If it is errored, terminated or complete, an error will be thrown.
* @param options Options for termination, including whether registered rollback handlers should run.
*/
public terminate(): Promise<void>;
public terminate(options?: WorkflowInstanceTerminateOptions): Promise<void>;
/**
* Restart the instance. Optionally restart from a specific step, preserving
* cached results for all steps before it.
Expand Down
Loading