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(
378378 [
379379 {
380380 provider: "codex",
381 cwd: "/tmp/project",
381382 resumeThreadId: startedSession.threadId,
382383 },
383384 ],
......@@ -465,6 +466,7 @@ routing.layer("ProviderServiceLive routing", (it) => {
465466 [
466467 {
467468 provider: "codex",
469 cwd: "/tmp/project",
468470 resumeThreadId: initial.threadId,
469471 },
470472 ],
......@@ -475,6 +477,38 @@ routing.layer("ProviderServiceLive routing", (it) => {
475477 }),
476478 );
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
478512 it.effect("stops all sessions and clears adapter runtime", () =>
479513 Effect.gen(function* () {
480514 const provider = yield* ProviderService;
apps/server/src/provider/Layers/ProviderService.ts+15
......@@ -93,6 +93,18 @@ function toRuntimePayloadFromSession(session: ProviderSession): Record<string, u
9393 };
9494}
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
96108const makeProviderService = (options?: ProviderServiceLiveOptions) =>
97109 Effect.gen(function* () {
98110 const canonicalEventLogger =
......@@ -266,8 +278,11 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) =>
266278 );
267279 }
268280
281 const persistedCwd = readPersistedCwd(input.binding.runtimePayload);
282
269283 const resumed = yield* adapter.startSession({
270284 provider: input.binding.provider,
285 ...(persistedCwd ? { cwd: persistedCwd } : {}),
271286 ...(resumeThreadId ? { resumeThreadId } : {}),
272287 ...(hasResumeCursor ? { resumeCursor: input.binding.resumeCursor } : {}),
273288 });