| author | |
| committer | |
| log | a62e7d670c67bf221a5699b5a988367781fead74 |
| tree | da0473ffac1db68798598cd213874f51b9db9877 |
| parent | 08abda9dcf6fee89561f861004efb48bad665df2 |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 112 insertions(+), 15 deletions(-)
apps/server/scripts/evaluate-thread-titles.ts+7-5| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | // Add --initial to evaluate only the opening request. |
| 7 | 7 | import * as NodeUtil from "node:util"; |
| 8 | 8 | import * as NodeCrypto from "node:crypto"; |
| 9 | import { FetchHttpClient } from "effect/unstable/http"; | |
| 9 | import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"; | |
| 10 | 10 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 11 | 11 | import { CodexSettings, ProviderInstanceId } from "@t3tools/contracts"; |
| 12 | 12 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -15,13 +15,13 @@ import * as Duration from "effect/Duration"; |
| 15 | 15 | import * as FileSystem from "effect/FileSystem"; |
| 16 | 16 | import * as Path from "effect/Path"; |
| 17 | 17 | import * as Schema from "effect/Schema"; |
| 18 | import { makeCodexTextGeneration } from "../src/textGeneration/CodexTextGeneration.ts"; | |
| 18 | import * as CodexTextGeneration from "../src/textGeneration/CodexTextGeneration.ts"; | |
| 19 | 19 | import { threadTitleEvaluationCases } from "./threadTitleEvaluationCases.ts"; |
| 20 | 20 | import { |
| 21 | 21 | formatThreadTitleContext, |
| 22 | 22 | type ThreadTitleMessage, |
| 23 | 23 | } from "../src/textGeneration/ThreadTitleContext.ts"; |
| 24 | import { resolveThreadTitleLinks } from "../src/textGeneration/ThreadTitleLinks.ts"; | |
| 24 | import * as ThreadTitleLinks from "../src/textGeneration/ThreadTitleLinks.ts"; | |
| 25 | 25 | import * as SourceControlProviderRegistry from "../src/sourceControl/SourceControlProviderRegistry.ts"; |
| 26 | 26 | import * as GitHubCli from "../src/sourceControl/GitHubCli.ts"; |
| 27 | 27 | import * as GitLabCli from "../src/sourceControl/GitLabCli.ts"; |
| ... | ... | @@ -66,7 +66,9 @@ await Effect.runPromise( |
| 66 | 66 | const fs = yield* FileSystem.FileSystem; |
| 67 | 67 | const path = yield* Path.Path; |
| 68 | 68 | const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-title-evaluation-" }); |
| 69 | const generation = yield* makeCodexTextGeneration(yield* decodeSettings({})); | |
| 69 | const generation = yield* CodexTextGeneration.makeCodexTextGeneration( | |
| 70 | yield* decodeSettings({}), | |
| 71 | ); | |
| 70 | 72 | const baseline = values.baseline |
| 71 | 73 | ? yield* fs.readFileString(values.baseline).pipe(Effect.flatMap(decodeResults)) |
| 72 | 74 | : []; |
| ... | ... | @@ -84,7 +86,7 @@ await Effect.runPromise( |
| 84 | 86 | const message = values.initial ? firstMessage.text : context.message; |
| 85 | 87 | const attachments = values.initial ? firstMessage.attachments : context.attachments; |
| 86 | 88 | const [elapsed, { generated, linkedContextDigest }] = yield* Effect.gen(function* () { |
| 87 | const linkedContext = yield* resolveThreadTitleLinks({ | |
| 89 | const linkedContext = yield* ThreadTitleLinks.resolveThreadTitleLinks({ | |
| 88 | 90 | cwd, |
| 89 | 91 | message, |
| 90 | 92 | }); |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+4-2| ... | ... | @@ -1857,7 +1857,8 @@ const make = Effect.gen(function* () { |
| 1857 | 1857 | return Effect.interrupt; |
| 1858 | 1858 | } |
| 1859 | 1859 | return Effect.logWarning("provider command reactor failed to find pending thread titles", { |
| 1860 | cause: Cause.pretty(cause), | |
| 1860 | failureKind: Cause.hasDies(cause) ? "defect" : "failure", | |
| 1861 | reasonCount: cause.reasons.length, | |
| 1861 | 1862 | }).pipe(Effect.as({ interruptedRegenerations: [], refinementThreadIds: [] })); |
| 1862 | 1863 | }), |
| 1863 | 1864 | ); |
| ... | ... | @@ -1900,7 +1901,8 @@ const make = Effect.gen(function* () { |
| 1900 | 1901 | return Effect.logWarning( |
| 1901 | 1902 | "provider command reactor failed to recover pending thread titles", |
| 1902 | 1903 | { |
| 1903 | cause: Cause.pretty(cause), | |
| 1904 | failureKind: Cause.hasDies(cause) ? "defect" : "failure", | |
| 1905 | reasonCount: cause.reasons.length, | |
| 1904 | 1906 | }, |
| 1905 | 1907 | ); |
| 1906 | 1908 | }), |
apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts+35| ... | ... | @@ -444,3 +444,38 @@ for (const kind of ["pull", "issues"]) { |
| 444 | 444 | }), |
| 445 | 445 | ); |
| 446 | 446 | } |
| 447 | ||
| 448 | for (const stage of ["read", "decode"] as const) { | |
| 449 | it.effect(`retains the ${stage} failure without exposing its raw contents`, () => | |
| 450 | Effect.gen(function* () { | |
| 451 | const cause = new GitHubCli.GitHubCliCommandError({ | |
| 452 | command: "gh", | |
| 453 | cwd: "/repo", | |
| 454 | cause: new Error("private response text"), | |
| 455 | }); | |
| 456 | const provider = yield* makeProvider({ | |
| 457 | execute: () => | |
| 458 | stage === "read" | |
| 459 | ? Effect.fail(cause) | |
| 460 | : Effect.succeed({ | |
| 461 | exitCode: ChildProcessSpawner.ExitCode(0), | |
| 462 | stdout: "private response text", | |
| 463 | stderr: "", | |
| 464 | stdoutTruncated: false, | |
| 465 | stderrTruncated: false, | |
| 466 | }), | |
| 467 | }); | |
| 468 | const lookup = provider.resolveLink?.({ | |
| 469 | cwd: "/repo", | |
| 470 | url: new URL("https://github.com/owner/repo/issues/42"), | |
| 471 | }); | |
| 472 | assert.ok(lookup); | |
| 473 | const error = yield* Effect.flip(lookup); | |
| 474 | assert.strictEqual(error.operation, stage === "read" ? "resolveLink" : "resolveLink.decode"); | |
| 475 | assert.strictEqual(error.detail, "The linked subject could not be read."); | |
| 476 | assert.notInclude(error.message, "private response text"); | |
| 477 | if (stage === "read") assert.strictEqual(error.cause, cause); | |
| 478 | else assert.propertyVal(error.cause, "_tag", "SchemaError"); | |
| 479 | }), | |
| 480 | ); | |
| 481 | } |
apps/server/src/sourceControl/GitHubSourceControlProvider.ts+14-3| ... | ... | @@ -219,7 +219,7 @@ export const make = Effect.gen(function* () { |
| 219 | 219 | input: { readonly cwd: string; readonly url: URL }, |
| 220 | 220 | endpoint: string, |
| 221 | 221 | ) { |
| 222 | return yield* github | |
| 222 | const result = yield* github | |
| 223 | 223 | .execute({ |
| 224 | 224 | cwd: input.cwd, |
| 225 | 225 | args: ["api", "--hostname", input.url.host, endpoint, "--jq", "{title, body}"], |
| ... | ... | @@ -228,8 +228,6 @@ export const make = Effect.gen(function* () { |
| 228 | 228 | maxOutputBytes: 32_000, |
| 229 | 229 | }) |
| 230 | 230 | .pipe( |
| 231 | Effect.flatMap((result) => decodeLinkSubject(result.stdout)), | |
| 232 | Effect.map((subject) => ({ title: subject.title, body: subject.body })), | |
| 233 | 231 | Effect.mapError( |
| 234 | 232 | (cause) => |
| 235 | 233 | new SourceControlProviderError({ |
| ... | ... | @@ -241,6 +239,19 @@ export const make = Effect.gen(function* () { |
| 241 | 239 | }), |
| 242 | 240 | ), |
| 243 | 241 | ); |
| 242 | const subject = yield* decodeLinkSubject(result.stdout).pipe( | |
| 243 | Effect.mapError( | |
| 244 | (cause) => | |
| 245 | new SourceControlProviderError({ | |
| 246 | provider: "github", | |
| 247 | operation: "resolveLink.decode", | |
| 248 | cwd: input.cwd, | |
| 249 | detail: "The linked subject could not be read.", | |
| 250 | cause, | |
| 251 | }), | |
| 252 | ), | |
| 253 | ); | |
| 254 | return { title: subject.title, body: subject.body }; | |
| 244 | 255 | }); |
| 245 | 256 | |
| 246 | 257 | return SourceControlProvider.SourceControlProvider.of({ |
apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts+36| ... | ... | @@ -271,3 +271,39 @@ for (const kind of ["merge_requests", "issues"]) { |
| 271 | 271 | }), |
| 272 | 272 | ); |
| 273 | 273 | } |
| 274 | ||
| 275 | for (const stage of ["read", "decode"] as const) { | |
| 276 | it.effect(`retains the ${stage} failure without exposing its raw contents`, () => | |
| 277 | Effect.gen(function* () { | |
| 278 | const cause = new GitLabCli.GitLabCliCommandError({ | |
| 279 | command: "glab", | |
| 280 | cwd: "/repo", | |
| 281 | operation: "execute", | |
| 282 | cause: new Error("private response text"), | |
| 283 | }); | |
| 284 | const provider = yield* makeProvider({ | |
| 285 | execute: () => | |
| 286 | stage === "read" | |
| 287 | ? Effect.fail(cause) | |
| 288 | : Effect.succeed({ | |
| 289 | exitCode: ChildProcessSpawner.ExitCode(0), | |
| 290 | stdout: "private response text", | |
| 291 | stderr: "", | |
| 292 | stdoutTruncated: false, | |
| 293 | stderrTruncated: false, | |
| 294 | }), | |
| 295 | }); | |
| 296 | const lookup = provider.resolveLink?.({ | |
| 297 | cwd: "/repo", | |
| 298 | url: new URL("https://gitlab.com/owner/repo/-/issues/42"), | |
| 299 | }); | |
| 300 | assert.ok(lookup); | |
| 301 | const error = yield* Effect.flip(lookup); | |
| 302 | assert.strictEqual(error.operation, stage === "read" ? "resolveLink" : "resolveLink.decode"); | |
| 303 | assert.strictEqual(error.detail, "The linked subject could not be read."); | |
| 304 | assert.notInclude(error.message, "private response text"); | |
| 305 | if (stage === "read") assert.strictEqual(error.cause, cause); | |
| 306 | else assert.propertyVal(error.cause, "_tag", "SchemaError"); | |
| 307 | }), | |
| 308 | ); | |
| 309 | } |
apps/server/src/sourceControl/GitLabSourceControlProvider.ts+14-3| ... | ... | @@ -116,7 +116,7 @@ export const make = Effect.gen(function* () { |
| 116 | 116 | input: { readonly cwd: string; readonly url: URL }, |
| 117 | 117 | endpoint: string, |
| 118 | 118 | ) { |
| 119 | return yield* gitlab | |
| 119 | const result = yield* gitlab | |
| 120 | 120 | .execute({ |
| 121 | 121 | cwd: input.cwd, |
| 122 | 122 | args: ["api", "--hostname", input.url.host, endpoint], |
| ... | ... | @@ -124,8 +124,6 @@ export const make = Effect.gen(function* () { |
| 124 | 124 | maxOutputBytes: 32_000, |
| 125 | 125 | }) |
| 126 | 126 | .pipe( |
| 127 | Effect.flatMap((result) => decodeLinkSubject(result.stdout)), | |
| 128 | Effect.map((subject) => ({ title: subject.title, body: subject.description })), | |
| 129 | 127 | Effect.mapError( |
| 130 | 128 | (cause) => |
| 131 | 129 | new SourceControlProviderError({ |
| ... | ... | @@ -137,6 +135,19 @@ export const make = Effect.gen(function* () { |
| 137 | 135 | }), |
| 138 | 136 | ), |
| 139 | 137 | ); |
| 138 | const subject = yield* decodeLinkSubject(result.stdout).pipe( | |
| 139 | Effect.mapError( | |
| 140 | (cause) => | |
| 141 | new SourceControlProviderError({ | |
| 142 | provider: "gitlab", | |
| 143 | operation: "resolveLink.decode", | |
| 144 | cwd: input.cwd, | |
| 145 | detail: "The linked subject could not be read.", | |
| 146 | cause, | |
| 147 | }), | |
| 148 | ), | |
| 149 | ); | |
| 150 | return { title: subject.title, body: subject.description }; | |
| 140 | 151 | }); |
| 141 | 152 | |
| 142 | 153 | return SourceControlProvider.SourceControlProvider.of({ |
apps/server/src/textGeneration/TextGeneration.ts+2-2| ... | ... | @@ -7,7 +7,7 @@ import { TextGenerationError } from "@t3tools/contracts"; |
| 7 | 7 | import * as ProviderInstanceRegistry from "../provider/Services/ProviderInstanceRegistry.ts"; |
| 8 | 8 | import type { ProviderInstance } from "../provider/ProviderDriver.ts"; |
| 9 | 9 | import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts"; |
| 10 | import { resolveThreadTitleLinks } from "./ThreadTitleLinks.ts"; | |
| 10 | import * as ThreadTitleLinks from "./ThreadTitleLinks.ts"; | |
| 11 | 11 | import type { TextGenerationPolicy } from "./TextGenerationPolicy.ts"; |
| 12 | 12 | |
| 13 | 13 | export type TextGenerationProvider = "codex" | "claudeAgent" | "cursor" | "grok" | "opencode"; |
| ... | ... | @@ -158,7 +158,7 @@ export const make = Effect.gen(function* () { |
| 158 | 158 | Effect.gen(function* () { |
| 159 | 159 | const linkedContext = |
| 160 | 160 | input.linkedContext ?? |
| 161 | (yield* resolveThreadTitleLinks(input).pipe( | |
| 161 | (yield* ThreadTitleLinks.resolveThreadTitleLinks(input).pipe( | |
| 162 | 162 | Effect.provideService( |
| 163 | 163 | SourceControlProviderRegistry.SourceControlProviderRegistry, |
| 164 | 164 | sourceControl, |