| author | |
| committer | |
| log | cebbe6ff193c9598bf77c4f673a933db34ed7bcb |
| tree | a892f6b3c787bd17d3a47c3f32ed177043bc5a37 |
| parent | e4a84a348e59d59e03bca15baa627aed99aec790 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>6 files changed, 113 insertions(+), 4 deletions(-)
packages/effect-acp/src/_internal/stdio.test.ts created+45| ... | @@ -0,0 +1,45 @@ | ||
| 1 | import { assert, describe, it } from "@effect/vitest"; | ||
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as PlatformError from "effect/PlatformError"; | ||
| 4 | import { ChildProcessSpawner } from "effect/unstable/process"; | ||
| 5 | |||
| 6 | import * as AcpError from "../errors.ts"; | ||
| 7 | import { makeTerminationError } from "./stdio.ts"; | ||
| 8 | |||
| 9 | describe("ACP child process termination", () => { | ||
| 10 | it.effect("retains the process identifier with the exit code", () => | ||
| 11 | Effect.gen(function* () { | ||
| 12 | const error = yield* makeTerminationError({ | ||
| 13 | pid: ChildProcessSpawner.ProcessId(41), | ||
| 14 | exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(7)), | ||
| 15 | }); | ||
| 16 | |||
| 17 | assert.instanceOf(error, AcpError.AcpProcessExitedError); | ||
| 18 | assert.equal(error.pid, 41); | ||
| 19 | assert.equal(error.code, 7); | ||
| 20 | assert.equal(error.message, "ACP process exited with code 7"); | ||
| 21 | }), | ||
| 22 | ); | ||
| 23 | |||
| 24 | it.effect("retains the process identifier and exact exit-status cause", () => | ||
| 25 | Effect.gen(function* () { | ||
| 26 | const rootCause = new Error("private process diagnostics"); | ||
| 27 | const cause = PlatformError.systemError({ | ||
| 28 | _tag: "Unknown", | ||
| 29 | module: "ChildProcess", | ||
| 30 | method: "exitCode", | ||
| 31 | cause: rootCause, | ||
| 32 | }); | ||
| 33 | const error = yield* makeTerminationError({ | ||
| 34 | pid: ChildProcessSpawner.ProcessId(42), | ||
| 35 | exitCode: Effect.fail(cause), | ||
| 36 | }); | ||
| 37 | |||
| 38 | assert.instanceOf(error, AcpError.AcpTransportError); | ||
| 39 | assert.equal(error.pid, 42); | ||
| 40 | assert.strictEqual(error.cause, cause); | ||
| 41 | assert.equal(error.message, "ACP transport operation read-process-exit-status failed."); | ||
| 42 | assert.notInclude(error.message, rootCause.message); | ||
| 43 | }), | ||
| 44 | ); | ||
| 45 | }); | ||
packages/effect-acp/src/_internal/stdio.ts+8-2| ... | @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { | ... | @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { |
| 44 | }; | 44 | }; |
| 45 | }); | 45 | }); |
| 46 | 46 | ||
| 47 | type ChildProcessTerminationHandle = Pick< | ||
| 48 | ChildProcessSpawner.ChildProcessHandle, | ||
| 49 | "exitCode" | "pid" | ||
| 50 | >; | ||
| 51 | |||
| 47 | export const makeTerminationError = ( | 52 | export const makeTerminationError = ( |
| 48 | handle: ChildProcessSpawner.ChildProcessHandle, | 53 | handle: ChildProcessTerminationHandle, |
| 49 | ): Effect.Effect<AcpError.AcpError> => | 54 | ): Effect.Effect<AcpError.AcpError> => |
| 50 | Effect.match(handle.exitCode, { | 55 | Effect.match(handle.exitCode, { |
| 51 | onFailure: (cause) => | 56 | onFailure: (cause) => |
| 52 | new AcpError.AcpTransportError({ | 57 | new AcpError.AcpTransportError({ |
| 53 | operation: "read-process-exit-status", | 58 | operation: "read-process-exit-status", |
| 59 | pid: handle.pid, | ||
| 54 | cause, | 60 | cause, |
| 55 | }), | 61 | }), |
| 56 | onSuccess: (code) => new AcpError.AcpProcessExitedError({ code }), | 62 | onSuccess: (code) => new AcpError.AcpProcessExitedError({ code, pid: handle.pid }), |
| 57 | }); | 63 | }); |
packages/effect-acp/src/errors.ts+2| ... | @@ -91,6 +91,7 @@ export class AcpProcessExitedError extends Schema.TaggedErrorClass<AcpProcessExi | ... | @@ -91,6 +91,7 @@ export class AcpProcessExitedError extends Schema.TaggedErrorClass<AcpProcessExi |
| 91 | "AcpProcessExitedError", | 91 | "AcpProcessExitedError", |
| 92 | { | 92 | { |
| 93 | code: Schema.optional(Schema.Number), | 93 | code: Schema.optional(Schema.Number), |
| 94 | pid: Schema.optionalKey(Schema.Int), | ||
| 94 | cause: Schema.optional(Schema.Defect()), | 95 | cause: Schema.optional(Schema.Defect()), |
| 95 | }, | 96 | }, |
| 96 | ) { | 97 | ) { |
| ... | @@ -160,6 +161,7 @@ export class AcpTransportError extends Schema.TaggedErrorClass<AcpTransportError | ... | @@ -160,6 +161,7 @@ export class AcpTransportError extends Schema.TaggedErrorClass<AcpTransportError |
| 160 | ), | 161 | ), |
| 161 | method: Schema.optional(Schema.String), | 162 | method: Schema.optional(Schema.String), |
| 162 | detail: Schema.optional(Schema.String), | 163 | detail: Schema.optional(Schema.String), |
| 164 | pid: Schema.optionalKey(Schema.Int), | ||
| 163 | cause: Schema.Defect(), | 165 | cause: Schema.Defect(), |
| 164 | }, | 166 | }, |
| 165 | ) { | 167 | ) { |
packages/effect-codex-app-server/src/_internal/stdio.test.ts created+48| ... | @@ -0,0 +1,48 @@ | ||
| 1 | import { assert, describe, it } from "@effect/vitest"; | ||
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as PlatformError from "effect/PlatformError"; | ||
| 4 | import { ChildProcessSpawner } from "effect/unstable/process"; | ||
| 5 | |||
| 6 | import * as CodexError from "../errors.ts"; | ||
| 7 | import { makeTerminationError } from "./stdio.ts"; | ||
| 8 | |||
| 9 | describe("Codex App Server child process termination", () => { | ||
| 10 | it.effect("retains the process identifier with the exit code", () => | ||
| 11 | Effect.gen(function* () { | ||
| 12 | const error = yield* makeTerminationError({ | ||
| 13 | pid: ChildProcessSpawner.ProcessId(51), | ||
| 14 | exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(9)), | ||
| 15 | }); | ||
| 16 | |||
| 17 | assert.instanceOf(error, CodexError.CodexAppServerProcessExitedError); | ||
| 18 | assert.equal(error.pid, 51); | ||
| 19 | assert.equal(error.code, 9); | ||
| 20 | assert.equal(error.message, "Codex App Server process exited with code 9"); | ||
| 21 | }), | ||
| 22 | ); | ||
| 23 | |||
| 24 | it.effect("retains the process identifier and exact exit-status cause", () => | ||
| 25 | Effect.gen(function* () { | ||
| 26 | const rootCause = new Error("private process diagnostics"); | ||
| 27 | const cause = PlatformError.systemError({ | ||
| 28 | _tag: "Unknown", | ||
| 29 | module: "ChildProcess", | ||
| 30 | method: "exitCode", | ||
| 31 | cause: rootCause, | ||
| 32 | }); | ||
| 33 | const error = yield* makeTerminationError({ | ||
| 34 | pid: ChildProcessSpawner.ProcessId(52), | ||
| 35 | exitCode: Effect.fail(cause), | ||
| 36 | }); | ||
| 37 | |||
| 38 | assert.instanceOf(error, CodexError.CodexAppServerTransportError); | ||
| 39 | assert.equal(error.pid, 52); | ||
| 40 | assert.strictEqual(error.cause, cause); | ||
| 41 | assert.equal( | ||
| 42 | error.message, | ||
| 43 | "Codex App Server transport operation 'read-process-exit-status' failed.", | ||
| 44 | ); | ||
| 45 | assert.notInclude(error.message, rootCause.message); | ||
| 46 | }), | ||
| 47 | ); | ||
| 48 | }); | ||
packages/effect-codex-app-server/src/_internal/stdio.ts+8-2| ... | @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { | ... | @@ -44,14 +44,20 @@ export const makeInMemoryStdio = Effect.fn("makeInMemoryStdio")(function* () { |
| 44 | }; | 44 | }; |
| 45 | }); | 45 | }); |
| 46 | 46 | ||
| 47 | type ChildProcessTerminationHandle = Pick< | ||
| 48 | ChildProcessSpawner.ChildProcessHandle, | ||
| 49 | "exitCode" | "pid" | ||
| 50 | >; | ||
| 51 | |||
| 47 | export const makeTerminationError = ( | 52 | export const makeTerminationError = ( |
| 48 | handle: ChildProcessSpawner.ChildProcessHandle, | 53 | handle: ChildProcessTerminationHandle, |
| 49 | ): Effect.Effect<CodexError.CodexAppServerError> => | 54 | ): Effect.Effect<CodexError.CodexAppServerError> => |
| 50 | Effect.match(handle.exitCode, { | 55 | Effect.match(handle.exitCode, { |
| 51 | onFailure: (cause) => | 56 | onFailure: (cause) => |
| 52 | new CodexError.CodexAppServerTransportError({ | 57 | new CodexError.CodexAppServerTransportError({ |
| 53 | operation: "read-process-exit-status", | 58 | operation: "read-process-exit-status", |
| 59 | pid: handle.pid, | ||
| 54 | cause, | 60 | cause, |
| 55 | }), | 61 | }), |
| 56 | onSuccess: (code) => new CodexError.CodexAppServerProcessExitedError({ code }), | 62 | onSuccess: (code) => new CodexError.CodexAppServerProcessExitedError({ code, pid: handle.pid }), |
| 57 | }); | 63 | }); |
packages/effect-codex-app-server/src/errors.ts+2| ... | @@ -146,6 +146,7 @@ export class CodexAppServerProcessExitedError extends Schema.TaggedErrorClass<Co | ... | @@ -146,6 +146,7 @@ export class CodexAppServerProcessExitedError extends Schema.TaggedErrorClass<Co |
| 146 | "CodexAppServerProcessExitedError", | 146 | "CodexAppServerProcessExitedError", |
| 147 | { | 147 | { |
| 148 | code: Schema.optional(Schema.Number), | 148 | code: Schema.optional(Schema.Number), |
| 149 | pid: Schema.optionalKey(Schema.Int), | ||
| 149 | cause: Schema.optional(Schema.Defect()), | 150 | cause: Schema.optional(Schema.Defect()), |
| 150 | }, | 151 | }, |
| 151 | ) { | 152 | ) { |
| ... | @@ -233,6 +234,7 @@ export class CodexAppServerTransportError extends Schema.TaggedErrorClass<CodexA | ... | @@ -233,6 +234,7 @@ export class CodexAppServerTransportError extends Schema.TaggedErrorClass<CodexA |
| 233 | "CodexAppServerTransportError", | 234 | "CodexAppServerTransportError", |
| 234 | { | 235 | { |
| 235 | operation: CodexAppServerTransportOperation, | 236 | operation: CodexAppServerTransportOperation, |
| 237 | pid: Schema.optionalKey(Schema.Int), | ||
| 236 | cause: Schema.Defect(), | 238 | cause: Schema.Defect(), |
| 237 | }, | 239 | }, |
| 238 | ) { | 240 | ) { |