Unified · Split
| @@ -150,28 +150,12 @@ export interface BranchNamePromptInput { · 149 unmodified lines | |||
| 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 { · 2 unmodified lines | |||
| 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 { · 39 unmodified lines | |||
| 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 | }); |