- Generate and rename temporary `t3code/*` worktree branches on first user turn in `ProviderCommandReactor` - Remove websocket/client branch-generation RPCs and related contract/API surface - Update Codex text-generation prompt to include attachment metadata only when attachments exist, with expanded tests
Showing the changes to apps/server/src/git (2 of 12 files). Show all
Unified · Split
| @@ -38,7 +38,19 @@ function makeFakeCodexBinary(dir: string) { · 37 unmodified lines | ||
| 38 | 38 | ' printf "%s\\n" "missing --image input" >&2', |
| 39 | 39 | " exit 2", |
| 40 | 40 | "fi", |
| 41 | − "cat >/dev/null", | |
| 41 | + 'stdin_content="$(cat)"', | |
| 42 | + 'if [ -n "$T3_FAKE_CODEX_STDIN_MUST_CONTAIN" ]; then', | |
| 43 | + ' printf "%s" "$stdin_content" | grep -F -- "$T3_FAKE_CODEX_STDIN_MUST_CONTAIN" >/dev/null || {', | |
| 44 | + ' printf "%s\\n" "stdin missing expected content" >&2', | |
| 45 | + " exit 3", | |
| 46 | + " }", | |
| 47 | + "fi", | |
| 48 | + 'if [ -n "$T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN" ]; then', | |
| 49 | + ' if printf "%s" "$stdin_content" | grep -F -- "$T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN" >/dev/null; then', | |
| 50 | + ' printf "%s\\n" "stdin contained forbidden content" >&2', | |
| 51 | + " exit 4", | |
| 52 | + " fi", | |
| 53 | + "fi", | |
| 42 | 54 | 'if [ -n "$T3_FAKE_CODEX_STDERR" ]; then', |
| 43 | 55 | ' printf "%s\\n" "$T3_FAKE_CODEX_STDERR" >&2', |
| 44 | 56 | "fi", |
| @@ -60,6 +72,8 @@ function withFakeCodexEnv<A, E, R>( · 15 unmodified lines | ||
| 60 | 72 | exitCode?: number; |
| 61 | 73 | stderr?: string; |
| 62 | 74 | requireImage?: boolean; |
| 75 | + stdinMustContain?: string; | |
| 76 | + stdinMustNotContain?: string; | |
| 63 | 77 | }, |
| 64 | 78 | effect: Effect.Effect<A, E, R>, |
| 65 | 79 | ) { |
| @@ -73,6 +87,8 @@ function withFakeCodexEnv<A, E, R>( · 7 unmodified lines | ||
| 73 | 87 | const previousExitCode = process.env.T3_FAKE_CODEX_EXIT_CODE; |
| 74 | 88 | const previousStderr = process.env.T3_FAKE_CODEX_STDERR; |
| 75 | 89 | const previousRequireImage = process.env.T3_FAKE_CODEX_REQUIRE_IMAGE; |
| 90 | + const previousStdinMustContain = process.env.T3_FAKE_CODEX_STDIN_MUST_CONTAIN; | |
| 91 | + const previousStdinMustNotContain = process.env.T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN; | |
| 76 | 92 | |
| 77 | 93 | yield* Effect.sync(() => { |
| 78 | 94 | process.env.PATH = `${binDir}:${previousPath ?? ""}`; |
| @@ -95,6 +111,18 @@ function withFakeCodexEnv<A, E, R>( · 16 unmodified lines | ||
| 95 | 111 | } else { |
| 96 | 112 | delete process.env.T3_FAKE_CODEX_REQUIRE_IMAGE; |
| 97 | 113 | } |
| 114 | + | |
| 115 | + if (input.stdinMustContain !== undefined) { | |
| 116 | + process.env.T3_FAKE_CODEX_STDIN_MUST_CONTAIN = input.stdinMustContain; | |
| 117 | + } else { | |
| 118 | + delete process.env.T3_FAKE_CODEX_STDIN_MUST_CONTAIN; | |
| 119 | + } | |
| 120 | + | |
| 121 | + if (input.stdinMustNotContain !== undefined) { | |
| 122 | + process.env.T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN = input.stdinMustNotContain; | |
| 123 | + } else { | |
| 124 | + delete process.env.T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN; | |
| 125 | + } | |
| 98 | 126 | }); |
| 99 | 127 | |
| 100 | 128 | return { |
| @@ -103,6 +131,8 @@ function withFakeCodexEnv<A, E, R>( · 2 unmodified lines | ||
| 103 | 131 | previousExitCode, |
| 104 | 132 | previousStderr, |
| 105 | 133 | previousRequireImage, |
| 134 | + previousStdinMustContain, | |
| 135 | + previousStdinMustNotContain, | |
| 106 | 136 | }; |
| 107 | 137 | }), |
| 108 | 138 | () => effect, |
| @@ -133,6 +163,18 @@ function withFakeCodexEnv<A, E, R>( · 24 unmodified lines | ||
| 133 | 163 | } else { |
| 134 | 164 | process.env.T3_FAKE_CODEX_REQUIRE_IMAGE = previous.previousRequireImage; |
| 135 | 165 | } |
| 166 | + | |
| 167 | + if (previous.previousStdinMustContain === undefined) { | |
| 168 | + delete process.env.T3_FAKE_CODEX_STDIN_MUST_CONTAIN; | |
| 169 | + } else { | |
| 170 | + process.env.T3_FAKE_CODEX_STDIN_MUST_CONTAIN = previous.previousStdinMustContain; | |
| 171 | + } | |
| 172 | + | |
| 173 | + if (previous.previousStdinMustNotContain === undefined) { | |
| 174 | + delete process.env.T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN; | |
| 175 | + } else { | |
| 176 | + process.env.T3_FAKE_CODEX_STDIN_MUST_NOT_CONTAIN = previous.previousStdinMustNotContain; | |
| 177 | + } | |
| 136 | 178 | }), |
| 137 | 179 | ); |
| 138 | 180 | } |
| @@ -202,6 +244,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGenerationLive", (it) => { · 63 unmodified lines | ||
| 202 | 244 | output: JSON.stringify({ |
| 203 | 245 | branch: " Feat/Session ", |
| 204 | 246 | }), |
| 247 | + stdinMustNotContain: "Image attachments supplied to the model", | |
| 205 | 248 | }, |
| 206 | 249 | Effect.gen(function* () { |
| 207 | 250 | const textGeneration = yield* TextGeneration; |
| @@ -216,6 +259,27 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGenerationLive", (it) => { · 8 unmodified lines | ||
| 216 | 259 | ), |
| 217 | 260 | ); |
| 218 | 261 | |
| 262 | + it.effect("omits attachment metadata section when no attachments are provided", () => | |
| 263 | + withFakeCodexEnv( | |
| 264 | + { | |
| 265 | + output: JSON.stringify({ | |
| 266 | + branch: "fix/session-timeout", | |
| 267 | + }), | |
| 268 | + stdinMustNotContain: "Attachment metadata:", | |
| 269 | + }, | |
| 270 | + Effect.gen(function* () { | |
| 271 | + const textGeneration = yield* TextGeneration; | |
| 272 | + | |
| 273 | + const generated = yield* textGeneration.generateBranchName({ | |
| 274 | + cwd: process.cwd(), | |
| 275 | + message: "Fix timeout behavior.", | |
| 276 | + }); | |
| 277 | + | |
| 278 | + expect(generated.branch).toBe("fix/session-timeout"); | |
| 279 | + }), | |
| 280 | + ), | |
| 281 | + ); | |
| 282 | + | |
| 219 | 283 | it.effect("passes image attachments through as codex image inputs", () => |
| 220 | 284 | withFakeCodexEnv( |
| 221 | 285 | { |
| @@ -223,6 +287,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGenerationLive", (it) => { · 1 unmodified line | ||
| 223 | 287 | branch: "fix/ui-regression", |
| 224 | 288 | }), |
| 225 | 289 | requireImage: true, |
| 290 | + stdinMustContain: "Attachment metadata:", | |
| 226 | 291 | }, |
| 227 | 292 | Effect.gen(function* () { |
| 228 | 293 | const textGeneration = yield* TextGeneration; |
| @@ -503,7 +503,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { · 502 unmodified lines | ||
| 503 | 503 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, |
| 504 | 504 | ); |
| 505 | 505 | |
| 506 | − const prompt = [ | |
| 506 | + const promptSections = [ | |
| 507 | 507 | "You generate concise git branch names.", |
| 508 | 508 | "Return a JSON object with key: branch.", |
| 509 | 509 | "Rules:", |
| @@ -512,14 +512,17 @@ const makeCodexTextGeneration = Effect.gen(function* () { · 2 unmodified lines | ||
| 512 | 512 | "- Use plain words only, no issue prefixes and no punctuation-heavy text.", |
| 513 | 513 | "- If images are attached, use them as primary context for visual/UI issues.", |
| 514 | 514 | "", |
| 515 | − `Image attachments supplied to the model: ${imagePaths.length}`, | |
| 516 | − "", | |
| 517 | 515 | "User message:", |
| 518 | 516 | limitSection(input.message, 8_000), |
| 519 | − "", | |
| 520 | − "Attachment metadata:", | |
| 521 | − attachmentLines.length > 0 ? limitSection(attachmentLines.join("\n"), 4_000) : "- none", | |
| 522 | − ].join("\n"); | |
| 517 | + ]; | |
| 518 | + if (attachmentLines.length > 0) { | |
| 519 | + promptSections.push( | |
| 520 | + "", | |
| 521 | + "Attachment metadata:", | |
| 522 | + limitSection(attachmentLines.join("\n"), 4_000), | |
| 523 | + ); | |
| 524 | + } | |
| 525 | + const prompt = promptSections.join("\n"); | |
| 523 | 526 | |
| 524 | 527 | const generated = yield* runCodexJson({ |
| 525 | 528 | operation: "generateBranchName", |