authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 20:58:21-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 20:58:21-08:00
log2aad749e2474f79a532764c9f6f7ce4abc3a68f0
tree9547f9e8102d4376755c90e1aa5a1d629fe85a42
parentaa267ebb0597ed8e3f367410ba4636465c1643f6

Reuse persisted cwd when recovering stale provider sessions

- 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 directory

2 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 );
477479
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}
9595
96function 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
96const makeProviderService = (options?: ProviderServiceLiveOptions) =>108const 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 }
268280
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 });