| author | |
| committer | |
| log | 2aad749e2474f79a532764c9f6f7ce4abc3a68f0 |
| tree | 9547f9e8102d4376755c90e1aa5a1d629fe85a42 |
| parent | aa267ebb0597ed8e3f367410ba4636465c1643f6 |
- Read and validate `cwd` from persisted runtime payload when restarting sessions
- Apply persisted `cwd` during stale-session recovery paths, including `sendTurn`
- Add tests to verify resumed sessions preserve the original working directory2 files changed, 49 insertions(+), 0 deletions(-)
apps/server/src/provider/Layers/ProviderService.test.ts+34| ... | @@ -378,6 +378,7 @@ it.effect( | ... | @@ -378,6 +378,7 @@ it.effect( |
| 378 | [ | 378 | [ |
| 379 | { | 379 | { |
| 380 | provider: "codex", | 380 | provider: "codex", |
| 381 | cwd: "/tmp/project", | ||
| 381 | resumeThreadId: startedSession.threadId, | 382 | resumeThreadId: startedSession.threadId, |
| 382 | }, | 383 | }, |
| 383 | ], | 384 | ], |
| ... | @@ -465,6 +466,7 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -465,6 +466,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 465 | [ | 466 | [ |
| 466 | { | 467 | { |
| 467 | provider: "codex", | 468 | provider: "codex", |
| 469 | cwd: "/tmp/project", | ||
| 468 | resumeThreadId: initial.threadId, | 470 | resumeThreadId: initial.threadId, |
| 469 | }, | 471 | }, |
| 470 | ], | 472 | ], |
| ... | @@ -475,6 +477,38 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -475,6 +477,38 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 475 | }), | 477 | }), |
| 476 | ); | 478 | ); |
| 477 | 479 | ||
| 480 | it.effect("recovers stale sessions for sendTurn using persisted cwd", () => | ||
| 481 | Effect.gen(function* () { | ||
| 482 | const provider = yield* ProviderService; | ||
| 483 | |||
| 484 | const initial = yield* provider.startSession(asThreadId("thread-1"), { | ||
| 485 | provider: "codex", | ||
| 486 | cwd: "/tmp/project-send-turn", | ||
| 487 | }); | ||
| 488 | |||
| 489 | yield* provider.stopAll(); | ||
| 490 | routing.codex.startSession.mockClear(); | ||
| 491 | routing.codex.sendTurn.mockClear(); | ||
| 492 | |||
| 493 | yield* provider.sendTurn({ | ||
| 494 | sessionId: initial.sessionId, | ||
| 495 | input: "resume", | ||
| 496 | attachments: [], | ||
| 497 | }); | ||
| 498 | |||
| 499 | assert.deepEqual(routing.codex.startSession.mock.calls, [ | ||
| 500 | [ | ||
| 501 | { | ||
| 502 | provider: "codex", | ||
| 503 | cwd: "/tmp/project-send-turn", | ||
| 504 | resumeThreadId: initial.threadId, | ||
| 505 | }, | ||
| 506 | ], | ||
| 507 | ]); | ||
| 508 | assert.equal(routing.codex.sendTurn.mock.calls.length, 1); | ||
| 509 | }), | ||
| 510 | ); | ||
| 511 | |||
| 478 | it.effect("stops all sessions and clears adapter runtime", () => | 512 | it.effect("stops all sessions and clears adapter runtime", () => |
| 479 | Effect.gen(function* () { | 513 | Effect.gen(function* () { |
| 480 | const provider = yield* ProviderService; | 514 | const provider = yield* ProviderService; |
apps/server/src/provider/Layers/ProviderService.ts+15| ... | @@ -93,6 +93,18 @@ function toRuntimePayloadFromSession(session: ProviderSession): Record<string, u | ... | @@ -93,6 +93,18 @@ function toRuntimePayloadFromSession(session: ProviderSession): Record<string, u |
| 93 | }; | 93 | }; |
| 94 | } | 94 | } |
| 95 | 95 | ||
| 96 | function readPersistedCwd(runtimePayload: ProviderSessionBinding["runtimePayload"]): string | undefined { | ||
| 97 | if (!runtimePayload || typeof runtimePayload !== "object" || Array.isArray(runtimePayload)) { | ||
| 98 | return undefined; | ||
| 99 | } | ||
| 100 | const rawCwd = runtimePayload.cwd; | ||
| 101 | if (typeof rawCwd !== "string") { | ||
| 102 | return undefined; | ||
| 103 | } | ||
| 104 | const trimmed = rawCwd.trim(); | ||
| 105 | return trimmed.length > 0 ? trimmed : undefined; | ||
| 106 | } | ||
| 107 | |||
| 96 | const makeProviderService = (options?: ProviderServiceLiveOptions) => | 108 | const makeProviderService = (options?: ProviderServiceLiveOptions) => |
| 97 | Effect.gen(function* () { | 109 | Effect.gen(function* () { |
| 98 | const canonicalEventLogger = | 110 | const canonicalEventLogger = |
| ... | @@ -266,8 +278,11 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => | ... | @@ -266,8 +278,11 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => |
| 266 | ); | 278 | ); |
| 267 | } | 279 | } |
| 268 | 280 | ||
| 281 | const persistedCwd = readPersistedCwd(input.binding.runtimePayload); | ||
| 282 | |||
| 269 | const resumed = yield* adapter.startSession({ | 283 | const resumed = yield* adapter.startSession({ |
| 270 | provider: input.binding.provider, | 284 | provider: input.binding.provider, |
| 285 | ...(persistedCwd ? { cwd: persistedCwd } : {}), | ||
| 271 | ...(resumeThreadId ? { resumeThreadId } : {}), | 286 | ...(resumeThreadId ? { resumeThreadId } : {}), |
| 272 | ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}), | 287 | ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}), |
| 273 | }); | 288 | }); |