- 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 | ' printf "%s\\n" "missing --image input" >&2', | 38 | ' printf "%s\\n" "missing --image input" >&2', |
| 39 | " exit 2", | 39 | " exit 2", |
| 40 | "fi", | 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 | 'if [ -n "$T3_FAKE_CODEX_STDERR" ]; then', | 54 | 'if [ -n "$T3_FAKE_CODEX_STDERR" ]; then', |
| 43 | ' printf "%s\\n" "$T3_FAKE_CODEX_STDERR" >&2', | 55 | ' printf "%s\\n" "$T3_FAKE_CODEX_STDERR" >&2', |
| 44 | "fi", | 56 | "fi", |
| @@ -60,6 +72,8 @@ function withFakeCodexEnv<A, E, R>( · 15 unmodified lines | |||
| 60 | exitCode?: number; | 72 | exitCode?: number; |
| 61 | stderr?: string; | 73 | stderr?: string; |
| 62 | requireImage?: boolean; | 74 | requireImage?: boolean; |
| 75 | stdinMustContain?: string; | ||
| 76 | stdinMustNotContain?: string; | ||
| 63 | }, | 77 | }, |
| 64 | effect: Effect.Effect<A, E, R>, | 78 | effect: Effect.Effect<A, E, R>, |
| 65 | ) { | 79 | ) { |
| @@ -73,6 +87,8 @@ function withFakeCodexEnv<A, E, R>( · 7 unmodified lines | |||
| 73 | const previousExitCode = process.env.T3_FAKE_CODEX_EXIT_CODE; | 87 | const previousExitCode = process.env.T3_FAKE_CODEX_EXIT_CODE; |
| 74 | const previousStderr = process.env.T3_FAKE_CODEX_STDERR; | 88 | const previousStderr = process.env.T3_FAKE_CODEX_STDERR; |
| 75 | const previousRequireImage = process.env.T3_FAKE_CODEX_REQUIRE_IMAGE; | 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 | yield* Effect.sync(() => { | 93 | yield* Effect.sync(() => { |
| 78 | process.env.PATH = `${binDir}:${previousPath ?? ""}`; | 94 | process.env.PATH = `${binDir}:${previousPath ?? ""}`; |
| @@ -95,6 +111,18 @@ function withFakeCodexEnv<A, E, R>( · 16 unmodified lines | |||
| 95 | } else { | 111 | } else { |
| 96 | delete process.env.T3_FAKE_CODEX_REQUIRE_IMAGE; | 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 | return { | 128 | return { |
| @@ -103,6 +131,8 @@ function withFakeCodexEnv<A, E, R>( · 2 unmodified lines | |||
| 103 | previousExitCode, | 131 | previousExitCode, |
| 104 | previousStderr, | 132 | previousStderr, |
| 105 | previousRequireImage, | 133 | previousRequireImage, |
| 134 | previousStdinMustContain, | ||
| 135 | previousStdinMustNotContain, | ||
| 106 | }; | 136 | }; |
| 107 | }), | 137 | }), |
| 108 | () => effect, | 138 | () => effect, |
| @@ -133,6 +163,18 @@ function withFakeCodexEnv<A, E, R>( · 24 unmodified lines | |||
| 133 | } else { | 163 | } else { |
| 134 | process.env.T3_FAKE_CODEX_REQUIRE_IMAGE = previous.previousRequireImage; | 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 | output: JSON.stringify({ | 244 | output: JSON.stringify({ |
| 203 | branch: " Feat/Session ", | 245 | branch: " Feat/Session ", |
| 204 | }), | 246 | }), |
| 247 | stdinMustNotContain: "Image attachments supplied to the model", | ||
| 205 | }, | 248 | }, |
| 206 | Effect.gen(function* () { | 249 | Effect.gen(function* () { |
| 207 | const textGeneration = yield* TextGeneration; | 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 | it.effect("passes image attachments through as codex image inputs", () => | 283 | it.effect("passes image attachments through as codex image inputs", () => |
| 220 | withFakeCodexEnv( | 284 | withFakeCodexEnv( |
| 221 | { | 285 | { |
| @@ -223,6 +287,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGenerationLive", (it) => { · 1 unmodified line | |||
| 223 | branch: "fix/ui-regression", | 287 | branch: "fix/ui-regression", |
| 224 | }), | 288 | }), |
| 225 | requireImage: true, | 289 | requireImage: true, |
| 290 | stdinMustContain: "Attachment metadata:", | ||
| 226 | }, | 291 | }, |
| 227 | Effect.gen(function* () { | 292 | Effect.gen(function* () { |
| 228 | const textGeneration = yield* TextGeneration; | 293 | const textGeneration = yield* TextGeneration; |
| @@ -503,7 +503,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { · 502 unmodified lines | |||
| 503 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, | 503 | (attachment) => `- ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes)`, |
| 504 | ); | 504 | ); |
| 505 | 505 | ||
| 506 | const prompt = [ | 506 | const promptSections = [ |
| 507 | "You generate concise git branch names.", | 507 | "You generate concise git branch names.", |
| 508 | "Return a JSON object with key: branch.", | 508 | "Return a JSON object with key: branch.", |
| 509 | "Rules:", | 509 | "Rules:", |
| @@ -512,14 +512,17 @@ const makeCodexTextGeneration = Effect.gen(function* () { · 2 unmodified lines | |||
| 512 | "- Use plain words only, no issue prefixes and no punctuation-heavy text.", | 512 | "- Use plain words only, no issue prefixes and no punctuation-heavy text.", |
| 513 | "- If images are attached, use them as primary context for visual/UI issues.", | 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 | "User message:", | 515 | "User message:", |
| 518 | limitSection(input.message, 8_000), | 516 | limitSection(input.message, 8_000), |
| 519 | "", | 517 | ]; |
| 520 | "Attachment metadata:", | 518 | if (attachmentLines.length > 0) { |
| 521 | attachmentLines.length > 0 ? limitSection(attachmentLines.join("\n"), 4_000) : "- none", | 519 | promptSections.push( |
| 522 | ].join("\n"); | 520 | "", |
| 521 | "Attachment metadata:", | ||
| 522 | limitSection(attachmentLines.join("\n"), 4_000), | ||
| 523 | ); | ||
| 524 | } | ||
| 525 | const prompt = promptSections.join("\n"); | ||
| 523 | 526 | ||
| 524 | const generated = yield* runCodexJson({ | 527 | const generated = yield* runCodexJson({ |
| 525 | operation: "generateBranchName", | 528 | operation: "generateBranchName", |