| author | |
| committer | |
| log | e3004ae806d4e9a81e03ff919f50d2d34c37ffe7 |
| tree | e8ed9f8c631dadae4942d640067ea0b98c2077d8 |
| parent | 9385314d26aba97d1fed1e05066e4562dbdf56bc |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>16 files changed, 106 insertions(+), 102 deletions(-)
apps/server/scripts/cli.ts+24-2| ... | @@ -14,6 +14,22 @@ import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog | ... | @@ -14,6 +14,22 @@ import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog |
| 14 | import rootPackageJson from "../../../package.json" with { type: "json" }; | 14 | import rootPackageJson from "../../../package.json" with { type: "json" }; |
| 15 | import serverPackageJson from "../package.json" with { type: "json" }; | 15 | import serverPackageJson from "../package.json" with { type: "json" }; |
| 16 | 16 | ||
| 17 | interface PackageJson { | ||
| 18 | name: string; | ||
| 19 | repository: { | ||
| 20 | type: string; | ||
| 21 | url: string; | ||
| 22 | directory: string; | ||
| 23 | }; | ||
| 24 | bin: Record<string, string>; | ||
| 25 | type: string; | ||
| 26 | version: string; | ||
| 27 | engines: Record<string, string>; | ||
| 28 | files: string[]; | ||
| 29 | dependencies: Record<string, string>; | ||
| 30 | overrides: Record<string, string>; | ||
| 31 | } | ||
| 32 | |||
| 17 | class CliError extends Data.TaggedError("CliError")<{ | 33 | class CliError extends Data.TaggedError("CliError")<{ |
| 18 | readonly message: string; | 34 | readonly message: string; |
| 19 | readonly cause?: unknown; | 35 | readonly cause?: unknown; |
| ... | @@ -192,7 +208,7 @@ const publishCmd = Command.make( | ... | @@ -192,7 +208,7 @@ const publishCmd = Command.make( |
| 192 | // Resolve catalog dependencies before any file mutations. If this throws, | 208 | // Resolve catalog dependencies before any file mutations. If this throws, |
| 193 | // acquire fails and no release hook runs, so filesystem must still be untouched. | 209 | // acquire fails and no release hook runs, so filesystem must still be untouched. |
| 194 | const version = Option.getOrElse(config.appVersion, () => serverPackageJson.version); | 210 | const version = Option.getOrElse(config.appVersion, () => serverPackageJson.version); |
| 195 | const pkg = { | 211 | const pkg: PackageJson = { |
| 196 | name: serverPackageJson.name, | 212 | name: serverPackageJson.name, |
| 197 | repository: serverPackageJson.repository, | 213 | repository: serverPackageJson.repository, |
| 198 | bin: serverPackageJson.bin, | 214 | bin: serverPackageJson.bin, |
| ... | @@ -200,7 +216,8 @@ const publishCmd = Command.make( | ... | @@ -200,7 +216,8 @@ const publishCmd = Command.make( |
| 200 | version, | 216 | version, |
| 201 | engines: serverPackageJson.engines, | 217 | engines: serverPackageJson.engines, |
| 202 | files: serverPackageJson.files, | 218 | files: serverPackageJson.files, |
| 203 | dependencies: serverPackageJson.dependencies as Record<string, unknown>, | 219 | dependencies: serverPackageJson.dependencies, |
| 220 | overrides: rootPackageJson.overrides, | ||
| 204 | }; | 221 | }; |
| 205 | 222 | ||
| 206 | pkg.dependencies = resolveCatalogDependencies( | 223 | pkg.dependencies = resolveCatalogDependencies( |
| ... | @@ -208,6 +225,11 @@ const publishCmd = Command.make( | ... | @@ -208,6 +225,11 @@ const publishCmd = Command.make( |
| 208 | rootPackageJson.workspaces.catalog, | 225 | rootPackageJson.workspaces.catalog, |
| 209 | "apps/server dependencies", | 226 | "apps/server dependencies", |
| 210 | ); | 227 | ); |
| 228 | pkg.overrides = resolveCatalogDependencies( | ||
| 229 | pkg.overrides, | ||
| 230 | rootPackageJson.workspaces.catalog, | ||
| 231 | "root overrides", | ||
| 232 | ); | ||
| 211 | 233 | ||
| 212 | const original = yield* fs.readFileString(packageJsonPath); | 234 | const original = yield* fs.readFileString(packageJsonPath); |
| 213 | yield* fs.writeFileString(backupPath, original); | 235 | yield* fs.writeFileString(backupPath, original); |
apps/server/src/auth/Layers/ServerSecretStore.ts+6-9| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import * as Crypto from "node:crypto"; | 1 | import * as Crypto from "node:crypto"; |
| 2 | 2 | ||
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import { Effect, FileSystem, Layer, Path, Predicate } from "effect"; |
| 4 | import * as PlatformError from "effect/PlatformError"; | 4 | import * as PlatformError from "effect/PlatformError"; |
| 5 | 5 | ||
| 6 | import { ServerConfig } from "../../config.ts"; | 6 | import { ServerConfig } from "../../config.ts"; |
| ... | @@ -28,17 +28,14 @@ export const makeServerSecretStore = Effect.gen(function* () { | ... | @@ -28,17 +28,14 @@ export const makeServerSecretStore = Effect.gen(function* () { |
| 28 | 28 | ||
| 29 | const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); | 29 | const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); |
| 30 | 30 | ||
| 31 | const isMissingSecretFileError = (cause: unknown): cause is PlatformError.PlatformError => | 31 | const isPlatformError = (u: unknown): u is PlatformError.PlatformError => |
| 32 | cause instanceof PlatformError.PlatformError && cause.reason._tag === "NotFound"; | 32 | Predicate.isTagged(u, "PlatformError"); |
| 33 | |||
| 34 | const isAlreadyExistsSecretFileError = (cause: unknown): cause is PlatformError.PlatformError => | ||
| 35 | cause instanceof PlatformError.PlatformError && cause.reason._tag === "AlreadyExists"; | ||
| 36 | 33 | ||
| 37 | const get: ServerSecretStoreShape["get"] = (name) => | 34 | const get: ServerSecretStoreShape["get"] = (name) => |
| 38 | fileSystem.readFile(resolveSecretPath(name)).pipe( | 35 | fileSystem.readFile(resolveSecretPath(name)).pipe( |
| 39 | Effect.map((bytes) => Uint8Array.from(bytes)), | 36 | Effect.map((bytes) => Uint8Array.from(bytes)), |
| 40 | Effect.catch((cause) => | 37 | Effect.catch((cause) => |
| 41 | isMissingSecretFileError(cause) | 38 | cause.reason._tag === "NotFound" |
| 42 | ? Effect.succeed(null) | 39 | ? Effect.succeed(null) |
| 43 | : Effect.fail( | 40 | : Effect.fail( |
| 44 | new SecretStoreError({ | 41 | new SecretStoreError({ |
| ... | @@ -108,7 +105,7 @@ export const makeServerSecretStore = Effect.gen(function* () { | ... | @@ -108,7 +105,7 @@ export const makeServerSecretStore = Effect.gen(function* () { |
| 108 | return create(name, generated).pipe( | 105 | return create(name, generated).pipe( |
| 109 | Effect.as(Uint8Array.from(generated)), | 106 | Effect.as(Uint8Array.from(generated)), |
| 110 | Effect.catchTag("SecretStoreError", (error) => | 107 | Effect.catchTag("SecretStoreError", (error) => |
| 111 | isAlreadyExistsSecretFileError(error.cause) | 108 | isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists" |
| 112 | ? get(name).pipe( | 109 | ? get(name).pipe( |
| 113 | Effect.flatMap((created) => | 110 | Effect.flatMap((created) => |
| 114 | created !== null | 111 | created !== null |
| ... | @@ -129,7 +126,7 @@ export const makeServerSecretStore = Effect.gen(function* () { | ... | @@ -129,7 +126,7 @@ export const makeServerSecretStore = Effect.gen(function* () { |
| 129 | const remove: ServerSecretStoreShape["remove"] = (name) => | 126 | const remove: ServerSecretStoreShape["remove"] = (name) => |
| 130 | fileSystem.remove(resolveSecretPath(name)).pipe( | 127 | fileSystem.remove(resolveSecretPath(name)).pipe( |
| 131 | Effect.catch((cause) => | 128 | Effect.catch((cause) => |
| 132 | isMissingSecretFileError(cause) | 129 | cause.reason._tag === "NotFound" |
| 133 | ? Effect.void | 130 | ? Effect.void |
| 134 | : Effect.fail( | 131 | : Effect.fail( |
| 135 | new SecretStoreError({ | 132 | new SecretStoreError({ |
apps/server/src/git/Layers/GitCore.ts+1-1| ... | @@ -2001,7 +2001,7 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: { | ... | @@ -2001,7 +2001,7 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: { |
| 2001 | "GitCore.removeWorktree", | 2001 | "GitCore.removeWorktree", |
| 2002 | input.cwd, | 2002 | input.cwd, |
| 2003 | args, | 2003 | args, |
| 2004 | `${commandLabel(args)} failed (cwd: ${input.cwd}): ${error instanceof Error ? error.message : String(error)}`, | 2004 | `${commandLabel(args)} failed (cwd: ${input.cwd}): ${error.message}`, |
| 2005 | error, | 2005 | error, |
| 2006 | ), | 2006 | ), |
| 2007 | ), | 2007 | ), |
apps/server/src/git/Layers/GitHubCli.ts+2-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Effect, Layer, Schema } from "effect"; | 1 | import { Effect, Layer, Schema, SchemaIssue } from "effect"; |
| 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; | 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; |
| 3 | 3 | ||
| 4 | import { runProcess } from "../../processRunner"; | 4 | import { runProcess } from "../../processRunner"; |
| ... | @@ -154,7 +154,7 @@ function decodeGitHubJson<S extends Schema.Top>( | ... | @@ -154,7 +154,7 @@ function decodeGitHubJson<S extends Schema.Top>( |
| 154 | (error) => | 154 | (error) => |
| 155 | new GitHubCliError({ | 155 | new GitHubCliError({ |
| 156 | operation, | 156 | operation, |
| 157 | detail: error instanceof Error ? `${invalidDetail}: ${error.message}` : invalidDetail, | 157 | detail: `${invalidDetail}: ${SchemaIssue.makeFormatterDefault()(error.issue)}`, |
| 158 | cause: error, | 158 | cause: error, |
| 159 | }), | 159 | }), |
| 160 | ), | 160 | ), |
apps/server/src/provider/Layers/CodexAdapter.test.ts-7| ... | @@ -238,14 +238,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => { | ... | @@ -238,14 +238,7 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => { |
| 238 | .pipe(Effect.result); | 238 | .pipe(Effect.result); |
| 239 | 239 | ||
| 240 | assert.equal(result._tag, "Failure"); | 240 | assert.equal(result._tag, "Failure"); |
| 241 | if (result._tag !== "Failure") { | ||
| 242 | return; | ||
| 243 | } | ||
| 244 | |||
| 245 | assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError"); | 241 | assert.equal(result.failure._tag, "ProviderAdapterSessionNotFoundError"); |
| 246 | if (result.failure._tag !== "ProviderAdapterSessionNotFoundError") { | ||
| 247 | return; | ||
| 248 | } | ||
| 249 | assert.equal(result.failure.provider, "codex"); | 242 | assert.equal(result.failure.provider, "codex"); |
| 250 | assert.equal(result.failure.threadId, "sess-missing"); | 243 | assert.equal(result.failure.threadId, "sess-missing"); |
| 251 | assert.equal(result.failure.cause instanceof Error, true); | 244 | assert.equal(result.failure.cause instanceof Error, true); |
apps/server/src/provider/Layers/CodexAdapter.ts+4-11| ... | @@ -51,18 +51,11 @@ export interface CodexAdapterLiveOptions { | ... | @@ -51,18 +51,11 @@ export interface CodexAdapterLiveOptions { |
| 51 | readonly nativeEventLogger?: EventNdjsonLogger; | 51 | readonly nativeEventLogger?: EventNdjsonLogger; |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | function toMessage(cause: unknown, fallback: string): string { | ||
| 55 | if (cause instanceof Error && cause.message.length > 0) { | ||
| 56 | return cause.message; | ||
| 57 | } | ||
| 58 | return fallback; | ||
| 59 | } | ||
| 60 | |||
| 61 | function toSessionError( | 54 | function toSessionError( |
| 62 | threadId: ThreadId, | 55 | threadId: ThreadId, |
| 63 | cause: unknown, | 56 | cause: unknown, |
| 64 | ): ProviderAdapterSessionNotFoundError | ProviderAdapterSessionClosedError | undefined { | 57 | ): ProviderAdapterSessionNotFoundError | ProviderAdapterSessionClosedError | undefined { |
| 65 | const normalized = toMessage(cause, "").toLowerCase(); | 58 | const normalized = cause instanceof Error ? cause.message.toLowerCase() : ""; |
| 66 | if (normalized.includes("unknown session") || normalized.includes("unknown provider session")) { | 59 | if (normalized.includes("unknown session") || normalized.includes("unknown provider session")) { |
| 67 | return new ProviderAdapterSessionNotFoundError({ | 60 | return new ProviderAdapterSessionNotFoundError({ |
| 68 | provider: PROVIDER, | 61 | provider: PROVIDER, |
| ... | @@ -88,7 +81,7 @@ function toRequestError(threadId: ThreadId, method: string, cause: unknown): Pro | ... | @@ -88,7 +81,7 @@ function toRequestError(threadId: ThreadId, method: string, cause: unknown): Pro |
| 88 | return new ProviderAdapterRequestError({ | 81 | return new ProviderAdapterRequestError({ |
| 89 | provider: PROVIDER, | 82 | provider: PROVIDER, |
| 90 | method, | 83 | method, |
| 91 | detail: toMessage(cause, `${method} failed`), | 84 | detail: cause instanceof Error ? `${method} failed: ${cause.message}` : `${method} failed`, |
| 92 | cause, | 85 | cause, |
| 93 | }); | 86 | }); |
| 94 | } | 87 | } |
| ... | @@ -1427,7 +1420,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( | ... | @@ -1427,7 +1420,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( |
| 1427 | new ProviderAdapterProcessError({ | 1420 | new ProviderAdapterProcessError({ |
| 1428 | provider: PROVIDER, | 1421 | provider: PROVIDER, |
| 1429 | threadId: input.threadId, | 1422 | threadId: input.threadId, |
| 1430 | detail: toMessage(cause, "Failed to start Codex adapter session."), | 1423 | detail: `Failed to start Codex adapter session: ${cause instanceof Error ? cause.message : String(cause)}.`, |
| 1431 | cause, | 1424 | cause, |
| 1432 | }), | 1425 | }), |
| 1433 | }); | 1426 | }); |
| ... | @@ -1455,7 +1448,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( | ... | @@ -1455,7 +1448,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( |
| 1455 | new ProviderAdapterRequestError({ | 1448 | new ProviderAdapterRequestError({ |
| 1456 | provider: PROVIDER, | 1449 | provider: PROVIDER, |
| 1457 | method: "turn/start", | 1450 | method: "turn/start", |
| 1458 | detail: toMessage(cause, "Failed to read attachment file."), | 1451 | detail: `Failed to read attachment file: ${cause.message}.`, |
| 1459 | cause, | 1452 | cause, |
| 1460 | }), | 1453 | }), |
| 1461 | ), | 1454 | ), |
apps/server/src/provider/Layers/CodexProvider.ts+2-5| ... | @@ -389,7 +389,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu | ... | @@ -389,7 +389,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu |
| 389 | auth: { status: "unknown" }, | 389 | auth: { status: "unknown" }, |
| 390 | message: isCommandMissingCause(error) | 390 | message: isCommandMissingCause(error) |
| 391 | ? "Codex CLI (`codex`) is not installed or not on PATH." | 391 | ? "Codex CLI (`codex`) is not installed or not on PATH." |
| 392 | : `Failed to execute Codex CLI health check: ${error instanceof Error ? error.message : String(error)}.`, | 392 | : `Failed to execute Codex CLI health check: ${error.message}.`, |
| 393 | }, | 393 | }, |
| 394 | }); | 394 | }); |
| 395 | } | 395 | } |
| ... | @@ -489,10 +489,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu | ... | @@ -489,10 +489,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu |
| 489 | version: parsedVersion, | 489 | version: parsedVersion, |
| 490 | status: "warning", | 490 | status: "warning", |
| 491 | auth: { status: "unknown" }, | 491 | auth: { status: "unknown" }, |
| 492 | message: | 492 | message: `Could not verify Codex authentication status: ${error.message}.`, |
| 493 | error instanceof Error | ||
| 494 | ? `Could not verify Codex authentication status: ${error.message}.` | ||
| 495 | : "Could not verify Codex authentication status.", | ||
| 496 | }, | 493 | }, |
| 497 | }); | 494 | }); |
| 498 | } | 495 | } |
apps/server/src/provider/providerSnapshot.ts+1-2| ... | @@ -32,8 +32,7 @@ export function nonEmptyTrimmed(value: string | undefined): string | undefined { | ... | @@ -32,8 +32,7 @@ export function nonEmptyTrimmed(value: string | undefined): string | undefined { |
| 32 | return trimmed.length > 0 ? trimmed : undefined; | 32 | return trimmed.length > 0 ? trimmed : undefined; |
| 33 | } | 33 | } |
| 34 | 34 | ||
| 35 | export function isCommandMissingCause(error: unknown): boolean { | 35 | export function isCommandMissingCause(error: Error): boolean { |
| 36 | if (!(error instanceof Error)) return false; | ||
| 37 | const lower = error.message.toLowerCase(); | 36 | const lower = error.message.toLowerCase(); |
| 38 | return lower.includes("enoent") || lower.includes("notfound"); | 37 | return lower.includes("enoent") || lower.includes("notfound"); |
| 39 | } | 38 | } |
apps/server/src/terminal/Layers/Manager.ts+31-26| ... | @@ -8,7 +8,6 @@ import { | ... | @@ -8,7 +8,6 @@ import { |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; | 9 | import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; |
| 10 | import { | 10 | import { |
| 11 | Data, | ||
| 12 | Effect, | 11 | Effect, |
| 13 | Encoding, | 12 | Encoding, |
| 14 | Equal, | 13 | Equal, |
| ... | @@ -17,6 +16,7 @@ import { | ... | @@ -17,6 +16,7 @@ import { |
| 17 | FileSystem, | 16 | FileSystem, |
| 18 | Layer, | 17 | Layer, |
| 19 | Option, | 18 | Option, |
| 19 | Schema, | ||
| 20 | Scope, | 20 | Scope, |
| 21 | Semaphore, | 21 | Semaphore, |
| 22 | SynchronizedRef, | 22 | SynchronizedRef, |
| ... | @@ -54,22 +54,28 @@ const DEFAULT_OPEN_COLS = 120; | ... | @@ -54,22 +54,28 @@ const DEFAULT_OPEN_COLS = 120; |
| 54 | const DEFAULT_OPEN_ROWS = 30; | 54 | const DEFAULT_OPEN_ROWS = 30; |
| 55 | const TERMINAL_ENV_BLOCKLIST = new Set(["PORT", "ELECTRON_RENDERER_PORT", "ELECTRON_RUN_AS_NODE"]); | 55 | const TERMINAL_ENV_BLOCKLIST = new Set(["PORT", "ELECTRON_RENDERER_PORT", "ELECTRON_RUN_AS_NODE"]); |
| 56 | 56 | ||
| 57 | type TerminalSubprocessChecker = ( | 57 | class TerminalSubprocessCheckError extends Schema.TaggedErrorClass<TerminalSubprocessCheckError>()( |
| 58 | terminalPid: number, | 58 | "TerminalSubprocessCheckError", |
| 59 | ) => Effect.Effect<boolean, TerminalSubprocessCheckError>; | 59 | { |
| 60 | 60 | message: Schema.String, | |
| 61 | class TerminalSubprocessCheckError extends Data.TaggedError("TerminalSubprocessCheckError")<{ | 61 | cause: Schema.optional(Schema.Defect), |
| 62 | readonly message: string; | 62 | terminalPid: Schema.Number, |
| 63 | readonly cause?: unknown; | 63 | command: Schema.Literals(["powershell", "pgrep", "ps"]), |
| 64 | readonly terminalPid: number; | 64 | }, |
| 65 | readonly command: "powershell" | "pgrep" | "ps"; | 65 | ) {} |
| 66 | }> {} | 66 | |
| 67 | class TerminalProcessSignalError extends Schema.TaggedErrorClass<TerminalProcessSignalError>()( | ||
| 68 | "TerminalProcessSignalError", | ||
| 69 | { | ||
| 70 | message: Schema.String, | ||
| 71 | cause: Schema.optional(Schema.Defect), | ||
| 72 | signal: Schema.Literals(["SIGTERM", "SIGKILL"]), | ||
| 73 | }, | ||
| 74 | ) {} | ||
| 67 | 75 | ||
| 68 | class TerminalProcessSignalError extends Data.TaggedError("TerminalProcessSignalError")<{ | 76 | interface TerminalSubprocessChecker { |
| 69 | readonly message: string; | 77 | (terminalPid: number): Effect.Effect<boolean, TerminalSubprocessCheckError>; |
| 70 | readonly cause?: unknown; | 78 | } |
| 71 | readonly signal: "SIGTERM" | "SIGKILL"; | ||
| 72 | }> {} | ||
| 73 | 79 | ||
| 74 | interface ShellCandidate { | 80 | interface ShellCandidate { |
| 75 | shell: string; | 81 | shell: string; |
| ... | @@ -271,9 +277,8 @@ function isRetryableShellSpawnError(error: PtySpawnError): boolean { | ... | @@ -271,9 +277,8 @@ function isRetryableShellSpawnError(error: PtySpawnError): boolean { |
| 271 | 277 | ||
| 272 | if (current instanceof Error) { | 278 | if (current instanceof Error) { |
| 273 | messages.push(current.message); | 279 | messages.push(current.message); |
| 274 | const cause = (current as { cause?: unknown }).cause; | 280 | if (current.cause) { |
| 275 | if (cause) { | 281 | queue.push(current.cause); |
| 276 | queue.push(cause); | ||
| 277 | } | 282 | } |
| 278 | continue; | 283 | continue; |
| 279 | } | 284 | } |
| ... | @@ -876,7 +881,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -876,7 +881,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 876 | Effect.logWarning("failed to persist terminal history", { | 881 | Effect.logWarning("failed to persist terminal history", { |
| 877 | threadId, | 882 | threadId, |
| 878 | terminalId, | 883 | terminalId, |
| 879 | error: error instanceof Error ? error.message : String(error), | 884 | error, |
| 880 | }), | 885 | }), |
| 881 | ), | 886 | ), |
| 882 | ); | 887 | ); |
| ... | @@ -959,7 +964,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -959,7 +964,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 959 | Effect.catch((cleanupError) => | 964 | Effect.catch((cleanupError) => |
| 960 | Effect.logWarning("failed to remove legacy terminal history", { | 965 | Effect.logWarning("failed to remove legacy terminal history", { |
| 961 | threadId, | 966 | threadId, |
| 962 | error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), | 967 | error: cleanupError, |
| 963 | }), | 968 | }), |
| 964 | ), | 969 | ), |
| 965 | ); | 970 | ); |
| ... | @@ -975,7 +980,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -975,7 +980,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 975 | Effect.logWarning("failed to delete terminal history", { | 980 | Effect.logWarning("failed to delete terminal history", { |
| 976 | threadId, | 981 | threadId, |
| 977 | terminalId, | 982 | terminalId, |
| 978 | error: error instanceof Error ? error.message : String(error), | 983 | error, |
| 979 | }), | 984 | }), |
| 980 | ), | 985 | ), |
| 981 | ); | 986 | ); |
| ... | @@ -985,7 +990,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -985,7 +990,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 985 | Effect.logWarning("failed to delete terminal history", { | 990 | Effect.logWarning("failed to delete terminal history", { |
| 986 | threadId, | 991 | threadId, |
| 987 | terminalId, | 992 | terminalId, |
| 988 | error: error instanceof Error ? error.message : String(error), | 993 | error, |
| 989 | }), | 994 | }), |
| 990 | ), | 995 | ), |
| 991 | ); | 996 | ); |
| ... | @@ -1011,7 +1016,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1011,7 +1016,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1011 | Effect.catch((error) => | 1016 | Effect.catch((error) => |
| 1012 | Effect.logWarning("failed to delete terminal histories for thread", { | 1017 | Effect.logWarning("failed to delete terminal histories for thread", { |
| 1013 | threadId, | 1018 | threadId, |
| 1014 | error: error instanceof Error ? error.message : String(error), | 1019 | error, |
| 1015 | }), | 1020 | }), |
| 1016 | ), | 1021 | ), |
| 1017 | ), | 1022 | ), |
| ... | @@ -1463,12 +1468,12 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1463,12 +1468,12 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1463 | const terminalPid = session.pid; | 1468 | const terminalPid = session.pid; |
| 1464 | const hasRunningSubprocess = yield* subprocessChecker(terminalPid).pipe( | 1469 | const hasRunningSubprocess = yield* subprocessChecker(terminalPid).pipe( |
| 1465 | Effect.map(Option.some), | 1470 | Effect.map(Option.some), |
| 1466 | Effect.catch((error) => | 1471 | Effect.catch((reason) => |
| 1467 | Effect.logWarning("failed to check terminal subprocess activity", { | 1472 | Effect.logWarning("failed to check terminal subprocess activity", { |
| 1468 | threadId: session.threadId, | 1473 | threadId: session.threadId, |
| 1469 | terminalId: session.terminalId, | 1474 | terminalId: session.terminalId, |
| 1470 | terminalPid, | 1475 | terminalPid, |
| 1471 | error: error instanceof Error ? error.message : String(error), | 1476 | reason, |
| 1472 | }).pipe(Effect.as(Option.none<boolean>())), | 1477 | }).pipe(Effect.as(Option.none<boolean>())), |
| 1473 | ), | 1478 | ), |
| 1474 | ); | 1479 | ); |
apps/server/src/workspace/Layers/WorkspaceEntries.ts+1-4| ... | @@ -214,9 +214,6 @@ function directoryAncestorsOf(relativePath: string): string[] { | ... | @@ -214,9 +214,6 @@ function directoryAncestorsOf(relativePath: string): string[] { |
| 214 | return directories; | 214 | return directories; |
| 215 | } | 215 | } |
| 216 | 216 | ||
| 217 | const processErrorDetail = (cause: unknown): string => | ||
| 218 | cause instanceof Error ? cause.message : String(cause); | ||
| 219 | |||
| 220 | export const makeWorkspaceEntries = Effect.gen(function* () { | 217 | export const makeWorkspaceEntries = Effect.gen(function* () { |
| 221 | const path = yield* Path.Path; | 218 | const path = yield* Path.Path; |
| 222 | const gitOption = yield* Effect.serviceOption(GitCore); | 219 | const gitOption = yield* Effect.serviceOption(GitCore); |
| ... | @@ -319,7 +316,7 @@ export const makeWorkspaceEntries = Effect.gen(function* () { | ... | @@ -319,7 +316,7 @@ export const makeWorkspaceEntries = Effect.gen(function* () { |
| 319 | new WorkspaceEntriesError({ | 316 | new WorkspaceEntriesError({ |
| 320 | cwd, | 317 | cwd, |
| 321 | operation: "workspaceEntries.readDirectoryEntries", | 318 | operation: "workspaceEntries.readDirectoryEntries", |
| 322 | detail: processErrorDetail(cause), | 319 | detail: cause instanceof Error ? cause.message : String(cause), |
| 323 | cause, | 320 | cause, |
| 324 | }), | 321 | }), |
| 325 | }).pipe( | 322 | }).pipe( |
apps/server/src/ws.ts+1-4| ... | @@ -313,10 +313,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => | ... | @@ -313,10 +313,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => |
| 313 | worktreePath: input.worktreePath, | 313 | worktreePath: input.worktreePath, |
| 314 | scriptId: input.scriptId, | 314 | scriptId: input.scriptId, |
| 315 | terminalId: input.terminalId, | 315 | terminalId: input.terminalId, |
| 316 | detail: | 316 | detail: error.message, |
| 317 | error instanceof Error | ||
| 318 | ? error.message | ||
| 319 | : "Unknown setup activity dispatch failure.", | ||
| 320 | }, | 317 | }, |
| 321 | ), | 318 | ), |
| 322 | ), | 319 | ), |
apps/web/src/environments/primary/auth.ts+17-19| ... | @@ -16,6 +16,14 @@ import { | ... | @@ -16,6 +16,14 @@ import { |
| 16 | } from "../../pairingUrl"; | 16 | } from "../../pairingUrl"; |
| 17 | 17 | ||
| 18 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; | 18 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; |
| 19 | import { Data, Predicate } from "effect"; | ||
| 20 | |||
| 21 | export class BootstrapHttpError extends Data.TaggedError("BootstrapHttpError")<{ | ||
| 22 | readonly message: string; | ||
| 23 | readonly status: number; | ||
| 24 | }> {} | ||
| 25 | const isBootstrapHttpError = (u: unknown): u is BootstrapHttpError => | ||
| 26 | Predicate.isTagged(u, "BootstrapHttpError"); | ||
| 19 | 27 | ||
| 20 | export interface ServerPairingLinkRecord { | 28 | export interface ServerPairingLinkRecord { |
| 21 | readonly id: string; | 29 | readonly id: string; |
| ... | @@ -87,10 +95,10 @@ export async function fetchSessionState(): Promise<AuthSessionState> { | ... | @@ -87,10 +95,10 @@ export async function fetchSessionState(): Promise<AuthSessionState> { |
| 87 | credentials: "include", | 95 | credentials: "include", |
| 88 | }); | 96 | }); |
| 89 | if (!response.ok) { | 97 | if (!response.ok) { |
| 90 | throw new BootstrapHttpError( | 98 | throw new BootstrapHttpError({ |
| 91 | `Failed to load server auth session state (${response.status}).`, | 99 | message: `Failed to load server auth session state (${response.status}).`, |
| 92 | response.status, | 100 | status: response.status, |
| 93 | ); | 101 | }); |
| 94 | } | 102 | } |
| 95 | return (await response.json()) as AuthSessionState; | 103 | return (await response.json()) as AuthSessionState; |
| 96 | }); | 104 | }); |
| ... | @@ -115,10 +123,10 @@ async function exchangeBootstrapCredential(credential: string): Promise<AuthBoot | ... | @@ -115,10 +123,10 @@ async function exchangeBootstrapCredential(credential: string): Promise<AuthBoot |
| 115 | 123 | ||
| 116 | if (!response.ok) { | 124 | if (!response.ok) { |
| 117 | const message = await response.text(); | 125 | const message = await response.text(); |
| 118 | throw new BootstrapHttpError( | 126 | throw new BootstrapHttpError({ |
| 119 | message || `Failed to bootstrap auth session (${response.status}).`, | 127 | message: message || `Failed to bootstrap auth session (${response.status}).`, |
| 120 | response.status, | 128 | status: response.status, |
| 121 | ); | 129 | }); |
| 122 | } | 130 | } |
| 123 | 131 | ||
| 124 | return (await response.json()) as AuthBootstrapResult; | 132 | return (await response.json()) as AuthBootstrapResult; |
| ... | @@ -146,16 +154,6 @@ const TRANSIENT_BOOTSTRAP_STATUS_CODES = new Set([502, 503, 504]); | ... | @@ -146,16 +154,6 @@ const TRANSIENT_BOOTSTRAP_STATUS_CODES = new Set([502, 503, 504]); |
| 146 | const BOOTSTRAP_RETRY_TIMEOUT_MS = 15_000; | 154 | const BOOTSTRAP_RETRY_TIMEOUT_MS = 15_000; |
| 147 | const BOOTSTRAP_RETRY_STEP_MS = 500; | 155 | const BOOTSTRAP_RETRY_STEP_MS = 500; |
| 148 | 156 | ||
| 149 | export class BootstrapHttpError extends Error { | ||
| 150 | readonly status: number; | ||
| 151 | |||
| 152 | constructor(message: string, status: number) { | ||
| 153 | super(message); | ||
| 154 | this.name = "BootstrapHttpError"; | ||
| 155 | this.status = status; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | export async function retryTransientBootstrap<T>(operation: () => Promise<T>): Promise<T> { | 157 | export async function retryTransientBootstrap<T>(operation: () => Promise<T>): Promise<T> { |
| 160 | const startedAt = Date.now(); | 158 | const startedAt = Date.now(); |
| 161 | while (true) { | 159 | while (true) { |
| ... | @@ -182,7 +180,7 @@ function waitForBootstrapRetry(delayMs: number): Promise<void> { | ... | @@ -182,7 +180,7 @@ function waitForBootstrapRetry(delayMs: number): Promise<void> { |
| 182 | } | 180 | } |
| 183 | 181 | ||
| 184 | function isTransientBootstrapError(error: unknown): boolean { | 182 | function isTransientBootstrapError(error: unknown): boolean { |
| 185 | if (error instanceof BootstrapHttpError) { | 183 | if (isBootstrapHttpError(error)) { |
| 186 | return TRANSIENT_BOOTSTRAP_STATUS_CODES.has(error.status); | 184 | return TRANSIENT_BOOTSTRAP_STATUS_CODES.has(error.status); |
| 187 | } | 185 | } |
| 188 | 186 |
apps/web/src/environments/primary/context.ts+4-4| ... | @@ -52,10 +52,10 @@ async function fetchPrimaryEnvironmentDescriptor(): Promise<ExecutionEnvironment | ... | @@ -52,10 +52,10 @@ async function fetchPrimaryEnvironmentDescriptor(): Promise<ExecutionEnvironment |
| 52 | resolvePrimaryEnvironmentHttpUrl(SERVER_ENVIRONMENT_DESCRIPTOR_PATH), | 52 | resolvePrimaryEnvironmentHttpUrl(SERVER_ENVIRONMENT_DESCRIPTOR_PATH), |
| 53 | ); | 53 | ); |
| 54 | if (!response.ok) { | 54 | if (!response.ok) { |
| 55 | throw new BootstrapHttpError( | 55 | throw new BootstrapHttpError({ |
| 56 | `Failed to load server environment descriptor (${response.status}).`, | 56 | message: `Failed to load server environment descriptor (${response.status}).`, |
| 57 | response.status, | 57 | status: response.status, |
| 58 | ); | 58 | }); |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | const descriptor = (await response.json()) as ExecutionEnvironmentDescriptor; | 61 | const descriptor = (await response.json()) as ExecutionEnvironmentDescriptor; |
package.json+6| ... | @@ -62,7 +62,13 @@ | ... | @@ -62,7 +62,13 @@ |
| 62 | "vitest": "catalog:" | 62 | "vitest": "catalog:" |
| 63 | }, | 63 | }, |
| 64 | "overrides": { | 64 | "overrides": { |
| 65 | "@effect/atom-react": "catalog:", | ||
| 66 | "@effect/platform-bun": "catalog:", | ||
| 67 | "@effect/platform-node": "catalog:", | ||
| 65 | "@effect/platform-node-shared": "catalog:", | 68 | "@effect/platform-node-shared": "catalog:", |
| 69 | "@effect/sql-sqlite-bun": "catalog:", | ||
| 70 | "@effect/vitest": "catalog:", | ||
| 71 | "effect": "catalog:", | ||
| 66 | "vite": "^8.0.0" | 72 | "vite": "^8.0.0" |
| 67 | }, | 73 | }, |
| 68 | "engines": { | 74 | "engines": { |
scripts/build-desktop-artifact.ts+3-3| ... | @@ -427,9 +427,9 @@ function validateBundledClientAssets(clientDir: string) { | ... | @@ -427,9 +427,9 @@ function validateBundledClientAssets(clientDir: string) { |
| 427 | } | 427 | } |
| 428 | 428 | ||
| 429 | function resolveDesktopRuntimeDependencies( | 429 | function resolveDesktopRuntimeDependencies( |
| 430 | dependencies: Record<string, unknown> | undefined, | 430 | dependencies: Record<string, string> | undefined, |
| 431 | catalog: Record<string, unknown>, | 431 | catalog: Record<string, string>, |
| 432 | ): Record<string, unknown> { | 432 | ): Record<string, string> { |
| 433 | if (!dependencies || Object.keys(dependencies).length === 0) { | 433 | if (!dependencies || Object.keys(dependencies).length === 0) { |
| 434 | return {}; | 434 | return {}; |
| 435 | } | 435 | } |
scripts/lib/resolve-catalog.ts+3-3| ... | @@ -5,10 +5,10 @@ | ... | @@ -5,10 +5,10 @@ |
| 5 | * the concrete version string found in `catalog`. Throws on missing entries. | 5 | * the concrete version string found in `catalog`. Throws on missing entries. |
| 6 | */ | 6 | */ |
| 7 | export function resolveCatalogDependencies( | 7 | export function resolveCatalogDependencies( |
| 8 | dependencies: Record<string, unknown>, | 8 | dependencies: Record<string, string>, |
| 9 | catalog: Record<string, unknown>, | 9 | catalog: Record<string, string>, |
| 10 | label: string, | 10 | label: string, |
| 11 | ): Record<string, unknown> { | 11 | ): Record<string, string> { |
| 12 | return Object.fromEntries( | 12 | return Object.fromEntries( |
| 13 | Object.entries(dependencies).map(([name, spec]) => { | 13 | Object.entries(dependencies).map(([name, spec]) => { |
| 14 | if (typeof spec !== "string" || !spec.startsWith("catalog:")) { | 14 | if (typeof spec !== "string" || !spec.startsWith("catalog:")) { |