History

Move worktree branch auto-rename into provider turn reactor

- 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

Julius Marminge committed 8 months ago

Commit 5e55aa31 parent a21095b12 files · +405 −264Browse files

Showing the changes to apps/server/src/git (2 of 12 files). Show all

Unified · Split

2 files
  1. apps/server/src/git/Layers/CodexTextGeneration.test.ts +66 −1
  2. apps/server/src/git/Layers/CodexTextGeneration.ts +10 −7
apps/server/src/git/Layers/CodexTextGeneration.test.ts+66−1
@@ -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;
apps/server/src/git/Layers/CodexTextGeneration.ts+10−7
@@ -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",