diff --git a/src/review/unified-comment.ts b/src/review/unified-comment.ts index e080ddb5cf..a0f036e3ec 100644 --- a/src/review/unified-comment.ts +++ b/src/review/unified-comment.ts @@ -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 ยท diff --git a/test/unit/unified-comment.test.ts b/test/unit/unified-comment.test.ts index cce6b82520..dca73347c5 100644 --- a/test/unit/unified-comment.test.ts +++ b/test/unit/unified-comment.test.ts @@ -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)", () => { @@ -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"); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 34e9be5122..148c43e0e6 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -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; @@ -621,7 +621,7 @@ declare abstract class DurableObjectNamespace; jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; } -type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; +type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high" | "us"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } @@ -12316,6 +12316,13 @@ interface ForwardableEmailMessage extends EmailMessage { * @returns A promise that resolves when the email message is replied. */ reply(message: EmailMessage): Promise; + /** + * 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; } /** A file attachment for an email message */ type EmailAttachment = { @@ -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; + 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; - 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; - text?: string; - html?: string; - attachments?: EmailAttachment[]; - }): Promise; + send(builder: EmailMessageBuilder): Promise; } declare abstract class EmailEvent extends ExtendableEvent { readonly message: ForwardableEmailMessage; @@ -13174,6 +13204,11 @@ 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; + error: Error; + }; + export type WorkflowDelayFunction = (input: WorkflowDynamicDelayContext) => WorkflowDelayDuration | Promise; export type WorkflowTimeoutDuration = WorkflowSleepDuration; export type WorkflowRetentionDuration = WorkflowSleepDuration; export type WorkflowBackoff = 'constant' | 'linear' | 'exponential'; @@ -13181,7 +13216,7 @@ declare namespace CloudflareWorkersModule { export type WorkflowStepConfig = { retries?: { limit: number; - delay: WorkflowDelayDuration | number; + delay: WorkflowDelayDuration | number | WorkflowDelayFunction; backoff?: WorkflowBackoff; }; timeout?: WorkflowTimeoutDuration | number; @@ -13207,13 +13242,22 @@ declare namespace CloudflareWorkersModule { type: string; sensitive?: WorkflowStepSensitivity; }; - export type WorkflowStepContext = { + export type WorkflowStepContext = { 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 = { ctx: WorkflowStepContext; @@ -13229,7 +13273,9 @@ declare namespace CloudflareWorkersModule { }; export abstract class WorkflowStep { do>(name: string, callback: (ctx: WorkflowStepContext) => Promise, rollbackOptions?: WorkflowStepRollbackOptions): Promise; - do>(name: string, config: WorkflowStepConfig, callback: (ctx: WorkflowStepContext) => Promise, rollbackOptions?: WorkflowStepRollbackOptions): Promise; + do, const C extends WorkflowStepConfig>(name: string, config: C, callback: (ctx: WorkflowStepContext) => Promise, rollbackOptions?: WorkflowStepRollbackOptions): Promise; sleep: (name: string, duration: WorkflowSleepDuration) => Promise; sleepUntil: (name: string, timestamp: Date | number) => Promise; waitForEvent>(name: string, options: { @@ -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; @@ -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. @@ -14726,8 +14780,9 @@ declare abstract class WorkflowInstance { public resume(): Promise; /** * 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; + public terminate(options?: WorkflowInstanceTerminateOptions): Promise; /** * Restart the instance. Optionally restart from a specific step, preserving * cached results for all steps before it.