| ... | @@ -150,28 +150,12 @@ export interface BranchNamePromptInput { | ... | @@ -150,28 +150,12 @@ export interface BranchNamePromptInput { |
| 150 | interface PromptFromMessageInput { | 150 | interface PromptFromMessageInput { |
| 151 | instruction: string; | 151 | instruction: string; |
| 152 | responseShape: string; | 152 | responseShape: string; |
| 153 | guidance?: ReadonlyArray<string> | undefined; | | |
| 154 | rulesLabel?: string | undefined; | | |
| 155 | rules: ReadonlyArray<string>; | 153 | rules: ReadonlyArray<string>; |
| 156 | afterRules?: ReadonlyArray<string> | undefined; | | |
| 157 | message: string; | 154 | message: string; |
| 158 | messageLabel?: string | undefined; | | |
| 159 | preserveMessageEnd?: boolean | undefined; | | |
| 160 | attachments?: ReadonlyArray<ChatAttachment> | undefined; | 155 | attachments?: ReadonlyArray<ChatAttachment> | undefined; |
| 161 | additionalInstructions?: string | undefined; | 156 | additionalInstructions?: string | undefined; |
| 162 | } | 157 | } |
| 163 | | 158 | |
| 164 | function preserveMessageEnd(message: string): string { | | |
| 165 | const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER); | | |
| 166 | const contents = alreadyTruncated | | |
| 167 | ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length) | | |
| 168 | : message; | | |
| 169 | if (!alreadyTruncated && contents.length <= 8_000) { | | |
| 170 | return contents; | | |
| 171 | } | | |
| 172 | return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`; | | |
| 173 | } | | |
| 174 | | | |
| 175 | function buildPromptFromMessage(input: PromptFromMessageInput): string { | 159 | function buildPromptFromMessage(input: PromptFromMessageInput): string { |
| 176 | const attachmentLines = (input.attachments ?? []).map( | 160 | const attachmentLines = (input.attachments ?? []).map( |
| 177 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, | 161 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, |
| ... | @@ -180,15 +164,11 @@ function buildPromptFromMessage(input: PromptFromMessageInput): string { | ... | @@ -180,15 +164,11 @@ function buildPromptFromMessage(input: PromptFromMessageInput): string { |
| 180 | const promptSections = [ | 164 | const promptSections = [ |
| 181 | input.instruction, | 165 | input.instruction, |
| 182 | input.responseShape, | 166 | input.responseShape, |
| 183 | ...(input.guidance ?? []), | 167 | "Rules:", |
| 184 | input.rulesLabel ?? "Rules:", | | |
| 185 | ...input.rules.map((rule) => `- ${rule}`), | 168 | ...input.rules.map((rule) => `- ${rule}`), |
| 186 | ...(input.afterRules ?? []), | | |
| 187 | "", | 169 | "", |
| 188 | `${input.messageLabel ?? "User message"}:`, | 170 | "User message:", |
| 189 | input.preserveMessageEnd | 171 | limitSection(input.message, 8_000), |
| 190 | ? preserveMessageEnd(input.message) | | |
| 191 | : limitSection(input.message, 8_000), | | |
| 192 | ...policyInstruction(input.additionalInstructions), | 172 | ...policyInstruction(input.additionalInstructions), |
| 193 | ]; | 173 | ]; |
| 194 | if (attachmentLines.length > 0) { | 174 | if (attachmentLines.length > 0) { |
| ... | @@ -234,88 +214,102 @@ export interface ThreadTitlePromptInput { | ... | @@ -234,88 +214,102 @@ export interface ThreadTitlePromptInput { |
| 234 | policy?: TextGenerationPolicy | undefined; | 214 | policy?: TextGenerationPolicy | undefined; |
| 235 | } | 215 | } |
| 236 | | 216 | |
| | 217 | // Keep shared editorial rules in these two prompts in sync. Regeneration |
| | 218 | // intentionally adds guidance for thread history and the previous title. |
| | 219 | const INITIAL_THREAD_TITLE_PROMPT = `Generate a title that will help the user recognize this T3 Code thread weeks later. |
| | 220 | Return JSON with exactly one key: title. |
| | 221 | |
| | 222 | Before answering, silently reduce the request to: |
| | 223 | - Subject: What system, feature, or problem is this really about? |
| | 224 | - Outcome: What does the user ultimately want to understand or change? |
| | 225 | - Incidental instructions: What only describes how the agent should do the work? |
| | 226 | |
| | 227 | Title the subject and outcome. Discard incidental instructions. |
| | 228 | |
| | 229 | Editorial rules: |
| | 230 | - 3-8 words, fewer than 40 characters. |
| | 231 | - Use a compact noun phrase or clear action phrase. |
| | 232 | - Capture the umbrella goal when the request lists several symptoms or steps. |
| | 233 | - Name the product change, not the mock, plan, report, branch, or PR used to produce it. |
| | 234 | - Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic. |
| | 235 | - For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject. |
| | 236 | - For research, name the question domain rather than the requested research process. |
| | 237 | - Do not claim the work is complete. |
| | 238 | - Do not copy and truncate the user's message. |
| | 239 | - Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation. |
| | 240 | - Use attached images as primary context for UI issues. |
| | 241 | - When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.`; |
| | 242 | |
| | 243 | function regenerateThreadTitlePrompt(previousTitle: string): string { |
| | 244 | return `Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later. |
| | 245 | The previous title was ${JSON.stringify(previousTitle)}. |
| | 246 | Return JSON with exactly one key: title. |
| | 247 | |
| | 248 | Determine the title in this order: |
| | 249 | 1. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about. |
| | 250 | 2. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal. |
| | 251 | 3. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread. |
| | 252 | 4. Title the durable subject and desired outcome, not the current workflow state. |
| | 253 | |
| | 254 | Editorial rules: |
| | 255 | - 3-8 words, fewer than 40 characters. |
| | 256 | - Use a compact noun phrase or clear action phrase. |
| | 257 | - Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail. |
| | 258 | - A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects. |
| | 259 | - Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic. |
| | 260 | - Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic. |
| | 261 | - Treat final operational follow-ups and assistant completion summaries as weak evidence of subject. |
| | 262 | - For reviews, name the reviewed feature or system and its durable concern, not one finding from the review. |
| | 263 | - For research, name the question domain rather than the research process. |
| | 264 | - Do not claim the work is complete. |
| | 265 | - Do not copy and truncate a thread message. |
| | 266 | - Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation. |
| | 267 | - Use attached images as primary context for UI issues. |
| | 268 | - When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing. |
| | 269 | - Return a meaningfully improved title, not a cosmetic paraphrase of the previous title. |
| | 270 | |
| | 271 | Examples of the distinction: |
| | 272 | - A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review." |
| | 273 | - A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions." |
| | 274 | - A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.`; |
| | 275 | } |
| | 276 | |
| | 277 | function preserveMessageEnd(message: string): string { |
| | 278 | const alreadyTruncated = message.startsWith(EARLIER_CONTENT_TRUNCATION_MARKER); |
| | 279 | const contents = alreadyTruncated |
| | 280 | ? message.slice(EARLIER_CONTENT_TRUNCATION_MARKER.length) |
| | 281 | : message; |
| | 282 | if (!alreadyTruncated && contents.length <= 8_000) { |
| | 283 | return contents; |
| | 284 | } |
| | 285 | return `${EARLIER_CONTENT_TRUNCATION_MARKER}${contents.slice(-8_000)}`; |
| | 286 | } |
| | 287 | |
| | 288 | function threadTitlePromptSuffix(input: ThreadTitlePromptInput): string { |
| | 289 | const additionalInstructions = policyInstruction(input.policy?.threadTitleInstructions); |
| | 290 | const attachmentLines = (input.attachments ?? []).map( |
| | 291 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, |
| | 292 | ); |
| | 293 | |
| | 294 | let suffix = ""; |
| | 295 | if (additionalInstructions.length > 0) { |
| | 296 | suffix = `\n${additionalInstructions.join("\n")}`; |
| | 297 | } |
| | 298 | if (attachmentLines.length > 0) { |
| | 299 | suffix += `\n\nAttachment metadata:\n${limitSection(attachmentLines.join("\n"), 4_000)}`; |
| | 300 | } |
| | 301 | return suffix; |
| | 302 | } |
| | 303 | |
| 237 | export function buildThreadTitlePrompt(input: ThreadTitlePromptInput) { | 304 | export function buildThreadTitlePrompt(input: ThreadTitlePromptInput) { |
| 238 | const isRegeneration = input.previousTitle !== undefined; | 305 | let prompt: string; |
| 239 | const prompt = buildPromptFromMessage({ | 306 | if (input.previousTitle === undefined) { |
| 240 | instruction: isRegeneration | 307 | const message = limitSection(input.message, 8_000); |
| 241 | ? [ | 308 | prompt = `${INITIAL_THREAD_TITLE_PROMPT}\n\nUser message:\n${message}${threadTitlePromptSuffix(input)}`; |
| 242 | "Regenerate the title for an existing T3 Code thread so the user can recognize it weeks later.", | 309 | } else { |
| 243 | `The previous title was ${JSON.stringify(input.previousTitle)}.`, | 310 | const message = preserveMessageEnd(input.message); |
| 244 | ].join("\n") | 311 | prompt = `${regenerateThreadTitlePrompt(input.previousTitle)}\n\nThread contents:\n${message}${threadTitlePromptSuffix(input)}`; |
| 245 | : "Generate a title that will help the user recognize this T3 Code thread weeks later.", | 312 | } |
| 246 | responseShape: "Return JSON with exactly one key: title.", | | |
| 247 | guidance: isRegeneration | | |
| 248 | ? [ | | |
| 249 | "", | | |
| 250 | "Determine the title in this order:", | | |
| 251 | "1. Read the USER messages first. Identify the latest explicit durable goal. The original subject remains the subject until the user clearly changes what the thread is about.", | | |
| 252 | "2. Use ASSISTANT messages to resolve vague links, unnamed code, and discovered product nouns. Do not promote one assistant finding into the thread subject unless the user adopts it as a new goal.", | | |
| 253 | "3. Compare that subject with the previous title. Preserve accurate scope words, especially when earlier content is truncated. Replace the previous title when it is generic, artifact-based, a completion update, or contradicted by the thread.", | | |
| 254 | "4. Title the durable subject and desired outcome, not the current workflow state.", | | |
| 255 | "", | | |
| 256 | ] | | |
| 257 | : [ | | |
| 258 | "", | | |
| 259 | "Before answering, silently reduce the request to:", | | |
| 260 | "- Subject: What system, feature, or problem is this really about?", | | |
| 261 | "- Outcome: What does the user ultimately want to understand or change?", | | |
| 262 | "- Incidental instructions: What only describes how the agent should do the work?", | | |
| 263 | "", | | |
| 264 | "Title the subject and outcome. Discard incidental instructions.", | | |
| 265 | "", | | |
| 266 | ], | | |
| 267 | rulesLabel: "Editorial rules:", | | |
| 268 | rules: isRegeneration | | |
| 269 | ? [ | | |
| 270 | "3-8 words, fewer than 40 characters.", | | |
| 271 | "Use a compact noun phrase or clear action phrase.", | | |
| 272 | "Preserve the umbrella subject when later messages focus on one finding, provider, platform, or implementation detail.", | | |
| 273 | "A thread progressing through research, planning, implementation, review, CI, merge, and monitoring has usually not changed subjects.", | | |
| 274 | "Ignore deliverables and operations such as mocks, plans, HTML, branches, PRs, tests, CI, commits, merging, and monitoring unless they are the actual topic.", | | |
| 275 | "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.", | | |
| 276 | "Treat final operational follow-ups and assistant completion summaries as weak evidence of subject.", | | |
| 277 | "For reviews, name the reviewed feature or system and its durable concern, not one finding from the review.", | | |
| 278 | "For research, name the question domain rather than the research process.", | | |
| 279 | "Do not claim the work is complete.", | | |
| 280 | "Do not copy and truncate a thread message.", | | |
| 281 | "Avoid project names already visible in the UI, PR numbers, quotes, labels, filler, and trailing punctuation.", | | |
| 282 | "Use attached images as primary context for UI issues.", | | |
| 283 | "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.", | | |
| 284 | "Return a meaningfully improved title, not a cosmetic paraphrase of the previous title.", | | |
| 285 | ] | | |
| 286 | : [ | | |
| 287 | "3-8 words, fewer than 40 characters.", | | |
| 288 | "Use a compact noun phrase or clear action phrase.", | | |
| 289 | "Capture the umbrella goal when the request lists several symptoms or steps.", | | |
| 290 | "Name the product change, not the mock, plan, report, branch, or PR used to produce it.", | | |
| 291 | "Models, subagents, tools, output formats, and monitoring instructions do not belong in the title unless they are themselves the topic.", | | |
| 292 | 'For reviews, name what is being reviewed and the relevant concern. Avoid generic titles such as "Review PR 123" when linked or attached context reveals the subject.', | | |
| 293 | "For research, name the question domain rather than the requested research process.", | | |
| 294 | "Do not claim the work is complete.", | | |
| 295 | "Do not copy and truncate the user's message.", | | |
| 296 | "Avoid project names already visible in the UI, quotes, labels, filler, and trailing punctuation.", | | |
| 297 | "Use attached images as primary context for UI issues.", | | |
| 298 | "When a URL or attachment is the only source of the subject, use available tools to inspect it. If it cannot be resolved, remain accurate rather than guessing.", | | |
| 299 | ], | | |
| 300 | afterRules: isRegeneration | | |
| 301 | ? [ | | |
| 302 | "", | | |
| 303 | "Examples of the distinction:", | | |
| 304 | '- A subagent-monitoring review that finds a Codex roster bug remains "Review Subagent Monitoring Risks," not "Codex Roster Bug Review."', | | |
| 305 | '- A vague failing-test request later identified as a lazy thread-feed mismatch becomes "Fix Lazy Thread Feed Test," not "Prevent Mobile Feed Regressions."', | | |
| 306 | "- A QR-sharing overhaul that ends with CI and merge work remains about QR sharing, not the PR lifecycle.", | | |
| 307 | ] | | |
| 308 | : undefined, | | |
| 309 | message: input.message, | | |
| 310 | ...(isRegeneration | | |
| 311 | ? { | | |
| 312 | messageLabel: "Thread contents", | | |
| 313 | preserveMessageEnd: true, | | |
| 314 | } | | |
| 315 | : {}), | | |
| 316 | attachments: input.attachments, | | |
| 317 | additionalInstructions: input.policy?.threadTitleInstructions, | | |
| 318 | }); | | |
| 319 | const outputSchema = Schema.Struct({ | 313 | const outputSchema = Schema.Struct({ |
| 320 | title: Schema.String, | 314 | title: Schema.String, |
| 321 | }); | 315 | }); |