| 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 | 378 | [ |
| 379 | 379 | { |
| 380 | 380 | provider: "codex", |
| 381 | cwd: "/tmp/project", | |
| 381 | 382 | resumeThreadId: startedSession.threadId, |
| 382 | 383 | }, |
| 383 | 384 | ], |
| ... | ... | @@ -465,6 +466,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 465 | 466 | [ |
| 466 | 467 | { |
| 467 | 468 | provider: "codex", |
| 469 | cwd: "/tmp/project", | |
| 468 | 470 | resumeThreadId: initial.threadId, |
| 469 | 471 | }, |
| 470 | 472 | ], |
| ... | ... | @@ -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 | 512 | it.effect("stops all sessions and clears adapter runtime", () => |
| 479 | 513 | Effect.gen(function* () { |
| 480 | 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 | 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 | 108 | const makeProviderService = (options?: ProviderServiceLiveOptions) => |
| 97 | 109 | Effect.gen(function* () { |
| 98 | 110 | const canonicalEventLogger = |
| ... | ... | @@ -266,8 +278,11 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => |
| 266 | 278 | ); |
| 267 | 279 | } |
| 268 | 280 | |
| 281 | const persistedCwd = readPersistedCwd(input.binding.runtimePayload); | |
| 282 | ||
| 269 | 283 | const resumed = yield* adapter.startSession({ |
| 270 | 284 | provider: input.binding.provider, |
| 285 | ...(persistedCwd ? { cwd: persistedCwd } : {}), | |
| 271 | 286 | ...(resumeThreadId ? { resumeThreadId } : {}), |
| 272 | 287 | ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}), |
| 273 | 288 | }); |