| author | |
| committer | |
| log | 188df6da074bf60af765881bdfd6e886ef83e6ee |
| tree | f44f72b0eefbb70bec3c5829909daa923376fc01 |
| parent | 0d55a42840231122f8d7414b35f155343031f82d |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>6 files changed, 327 insertions(+), 4 deletions(-)
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+76| ... | ... | @@ -138,6 +138,12 @@ describe("ProviderCommandReactor", () => { |
| 138 | 138 | (input.runtimeMode === "approval-required" || input.runtimeMode === "full-access") |
| 139 | 139 | ? input.runtimeMode |
| 140 | 140 | : "full-access", |
| 141 | ...(typeof input === "object" && | |
| 142 | input !== null && | |
| 143 | "cwd" in input && | |
| 144 | typeof input.cwd === "string" | |
| 145 | ? { cwd: input.cwd } | |
| 146 | : {}), | |
| 141 | 147 | ...(modelSelection.model !== undefined ? { model: modelSelection.model } : {}), |
| 142 | 148 | threadId, |
| 143 | 149 | resumeCursor: resumeCursor ?? { opaque: `resume-${sessionIndex}` }, |
| ... | ... | @@ -864,6 +870,76 @@ describe("ProviderCommandReactor", () => { |
| 864 | 870 | expect(harness.stopSession.mock.calls.length).toBe(0); |
| 865 | 871 | }); |
| 866 | 872 | |
| 873 | it("restarts the provider session when the thread workspace changes", async () => { | |
| 874 | const harness = await createHarness({ | |
| 875 | threadModelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, | |
| 876 | }); | |
| 877 | const now = new Date().toISOString(); | |
| 878 | ||
| 879 | await Effect.runPromise( | |
| 880 | harness.engine.dispatch({ | |
| 881 | type: "thread.turn.start", | |
| 882 | commandId: CommandId.make("cmd-turn-start-workspace-1"), | |
| 883 | threadId: ThreadId.make("thread-1"), | |
| 884 | message: { | |
| 885 | messageId: asMessageId("user-message-workspace-1"), | |
| 886 | role: "user", | |
| 887 | text: "first in project root", | |
| 888 | attachments: [], | |
| 889 | }, | |
| 890 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | |
| 891 | runtimeMode: "approval-required", | |
| 892 | createdAt: now, | |
| 893 | }), | |
| 894 | ); | |
| 895 | ||
| 896 | await waitFor(() => harness.startSession.mock.calls.length === 1); | |
| 897 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | |
| 898 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ | |
| 899 | cwd: "/tmp/provider-project", | |
| 900 | }); | |
| 901 | ||
| 902 | await Effect.runPromise( | |
| 903 | harness.engine.dispatch({ | |
| 904 | type: "thread.meta.update", | |
| 905 | commandId: CommandId.make("cmd-thread-worktree-change"), | |
| 906 | threadId: ThreadId.make("thread-1"), | |
| 907 | worktreePath: "/tmp/provider-project-worktree", | |
| 908 | }), | |
| 909 | ); | |
| 910 | ||
| 911 | await Effect.runPromise( | |
| 912 | harness.engine.dispatch({ | |
| 913 | type: "thread.turn.start", | |
| 914 | commandId: CommandId.make("cmd-turn-start-workspace-2"), | |
| 915 | threadId: ThreadId.make("thread-1"), | |
| 916 | message: { | |
| 917 | messageId: asMessageId("user-message-workspace-2"), | |
| 918 | role: "user", | |
| 919 | text: "second in worktree", | |
| 920 | attachments: [], | |
| 921 | }, | |
| 922 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | |
| 923 | runtimeMode: "approval-required", | |
| 924 | createdAt: now, | |
| 925 | }), | |
| 926 | ); | |
| 927 | ||
| 928 | await waitFor(() => harness.startSession.mock.calls.length === 2); | |
| 929 | await waitFor(() => harness.sendTurn.mock.calls.length === 2); | |
| 930 | expect(harness.stopSession.mock.calls.length).toBe(0); | |
| 931 | expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ | |
| 932 | threadId: ThreadId.make("thread-1"), | |
| 933 | cwd: "/tmp/provider-project-worktree", | |
| 934 | resumeCursor: { opaque: "resume-1" }, | |
| 935 | modelSelection: { | |
| 936 | provider: "claudeAgent", | |
| 937 | model: "claude-sonnet-4-6", | |
| 938 | }, | |
| 939 | runtimeMode: "approval-required", | |
| 940 | }); | |
| 941 | }); | |
| 942 | ||
| 867 | 943 | it("restarts claude sessions when claude effort changes", async () => { |
| 868 | 944 | const harness = await createHarness({ |
| 869 | 945 | threadModelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+6| ... | ... | @@ -334,6 +334,7 @@ const make = Effect.gen(function* () { |
| 334 | 334 | thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null; |
| 335 | 335 | if (existingSessionThreadId) { |
| 336 | 336 | const runtimeModeChanged = thread.runtimeMode !== thread.session?.runtimeMode; |
| 337 | const cwdChanged = effectiveCwd !== activeSession?.cwd; | |
| 337 | 338 | const sessionModelSwitch = |
| 338 | 339 | currentProvider === undefined |
| 339 | 340 | ? "in-session" |
| ... | ... | @@ -350,6 +351,7 @@ const make = Effect.gen(function* () { |
| 350 | 351 | |
| 351 | 352 | if ( |
| 352 | 353 | !runtimeModeChanged && |
| 354 | !cwdChanged && | |
| 353 | 355 | !shouldRestartForModelChange && |
| 354 | 356 | !shouldRestartForModelSelectionChange |
| 355 | 357 | ) { |
| ... | ... | @@ -367,6 +369,9 @@ const make = Effect.gen(function* () { |
| 367 | 369 | currentRuntimeMode: thread.session?.runtimeMode, |
| 368 | 370 | desiredRuntimeMode: thread.runtimeMode, |
| 369 | 371 | runtimeModeChanged, |
| 372 | previousCwd: activeSession?.cwd, | |
| 373 | desiredCwd: effectiveCwd, | |
| 374 | cwdChanged, | |
| 370 | 375 | modelChanged, |
| 371 | 376 | shouldRestartForModelChange, |
| 372 | 377 | shouldRestartForModelSelectionChange, |
| ... | ... | @@ -381,6 +386,7 @@ const make = Effect.gen(function* () { |
| 381 | 386 | restartedSessionThreadId: restartedSession.threadId, |
| 382 | 387 | provider: restartedSession.provider, |
| 383 | 388 | runtimeMode: restartedSession.runtimeMode, |
| 389 | cwd: restartedSession.cwd, | |
| 384 | 390 | }); |
| 385 | 391 | yield* bindSessionToThread(restartedSession); |
| 386 | 392 | return restartedSession.threadId; |
apps/server/src/provider/Layers/ClaudeAdapter.test.ts+90| ... | ... | @@ -2561,6 +2561,96 @@ describe("ClaudeAdapterLive", () => { |
| 2561 | 2561 | ); |
| 2562 | 2562 | }); |
| 2563 | 2563 | |
| 2564 | it.effect("preserves durable resume ids across Claude resume hooks", () => { | |
| 2565 | const harness = makeHarness(); | |
| 2566 | return Effect.gen(function* () { | |
| 2567 | const adapter = yield* ClaudeAdapter; | |
| 2568 | const durableSessionId = "550e8400-e29b-41d4-a716-446655440000"; | |
| 2569 | const transientHookSessionId = "7368d0c7-40a3-4d8a-bcc1-ac80c49f2719"; | |
| 2570 | ||
| 2571 | const runtimeEventsFiber = yield* Stream.take(adapter.streamEvents, 7).pipe( | |
| 2572 | Stream.runCollect, | |
| 2573 | Effect.forkChild, | |
| 2574 | ); | |
| 2575 | ||
| 2576 | yield* adapter.startSession({ | |
| 2577 | threadId: RESUME_THREAD_ID, | |
| 2578 | provider: "claudeAgent", | |
| 2579 | resumeCursor: { | |
| 2580 | threadId: RESUME_THREAD_ID, | |
| 2581 | resume: durableSessionId, | |
| 2582 | resumeSessionAt: "assistant-99", | |
| 2583 | turnCount: 3, | |
| 2584 | }, | |
| 2585 | runtimeMode: "full-access", | |
| 2586 | }); | |
| 2587 | ||
| 2588 | harness.query.emit({ | |
| 2589 | type: "system", | |
| 2590 | subtype: "hook_started", | |
| 2591 | hook_id: "resume-hook-1", | |
| 2592 | hook_name: "SessionStart:resume", | |
| 2593 | hook_event: "SessionStart", | |
| 2594 | session_id: transientHookSessionId, | |
| 2595 | uuid: "resume-hook-started", | |
| 2596 | } as unknown as SDKMessage); | |
| 2597 | ||
| 2598 | harness.query.emit({ | |
| 2599 | type: "system", | |
| 2600 | subtype: "hook_response", | |
| 2601 | hook_id: "resume-hook-1", | |
| 2602 | hook_name: "SessionStart:resume", | |
| 2603 | hook_event: "SessionStart", | |
| 2604 | output: "", | |
| 2605 | stdout: "", | |
| 2606 | stderr: "", | |
| 2607 | outcome: "success", | |
| 2608 | session_id: transientHookSessionId, | |
| 2609 | uuid: "resume-hook-response", | |
| 2610 | } as unknown as SDKMessage); | |
| 2611 | ||
| 2612 | harness.query.emit({ | |
| 2613 | type: "system", | |
| 2614 | subtype: "init", | |
| 2615 | apiKeySource: "none", | |
| 2616 | claude_code_version: "test", | |
| 2617 | cwd: "/tmp/claude-adapter-test", | |
| 2618 | tools: [], | |
| 2619 | mcp_servers: [], | |
| 2620 | model: "claude-sonnet-4-5", | |
| 2621 | permissionMode: "bypassPermissions", | |
| 2622 | slash_commands: [], | |
| 2623 | output_style: "default", | |
| 2624 | skills: [], | |
| 2625 | plugins: [], | |
| 2626 | session_id: durableSessionId, | |
| 2627 | uuid: "resume-init", | |
| 2628 | } as unknown as SDKMessage); | |
| 2629 | ||
| 2630 | const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber)); | |
| 2631 | const threadStartedEvents = runtimeEvents.filter((event) => event.type === "thread.started"); | |
| 2632 | assert.equal(threadStartedEvents.length, 1); | |
| 2633 | const threadStarted = threadStartedEvents[0]; | |
| 2634 | assert.equal(threadStarted?.type, "thread.started"); | |
| 2635 | if (threadStarted?.type === "thread.started") { | |
| 2636 | assert.deepEqual(threadStarted.payload, { | |
| 2637 | providerThreadId: durableSessionId, | |
| 2638 | }); | |
| 2639 | } | |
| 2640 | ||
| 2641 | const activeSessions = yield* adapter.listSessions(); | |
| 2642 | const resumeCursor = activeSessions[0]?.resumeCursor as | |
| 2643 | | { | |
| 2644 | readonly resume?: string; | |
| 2645 | } | |
| 2646 | | undefined; | |
| 2647 | assert.equal(resumeCursor?.resume, durableSessionId); | |
| 2648 | }).pipe( | |
| 2649 | Effect.provideService(Random.Random, makeDeterministicRandomService()), | |
| 2650 | Effect.provide(harness.layer), | |
| 2651 | ); | |
| 2652 | }); | |
| 2653 | ||
| 2564 | 2654 | it.effect("uses an app-generated Claude session id for fresh sessions", () => { |
| 2565 | 2655 | const harness = makeHarness(); |
| 2566 | 2656 | return Effect.gen(function* () { |
apps/server/src/provider/Layers/ClaudeAdapter.ts+40| ... | ... | @@ -198,6 +198,18 @@ function isSyntheticClaudeThreadId(value: string): boolean { |
| 198 | 198 | return value.startsWith("claude-thread-"); |
| 199 | 199 | } |
| 200 | 200 | |
| 201 | function hasDurableClaudeSessionId(message: SDKMessage): boolean { | |
| 202 | if (message.type !== "system") { | |
| 203 | return true; | |
| 204 | } | |
| 205 | ||
| 206 | return ( | |
| 207 | message.subtype !== "hook_started" && | |
| 208 | message.subtype !== "hook_progress" && | |
| 209 | message.subtype !== "hook_response" | |
| 210 | ); | |
| 211 | } | |
| 212 | ||
| 201 | 213 | function toMessage(cause: unknown, fallback: string): string { |
| 202 | 214 | if (cause instanceof Error && cause.message.length > 0) { |
| 203 | 215 | return cause.message; |
| ... | ... | @@ -1249,6 +1261,9 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 1249 | 1261 | if (typeof message.session_id !== "string" || message.session_id.length === 0) { |
| 1250 | 1262 | return; |
| 1251 | 1263 | } |
| 1264 | if (!hasDurableClaudeSessionId(message)) { | |
| 1265 | return; | |
| 1266 | } | |
| 1252 | 1267 | const nextThreadId = message.session_id; |
| 1253 | 1268 | context.resumeSessionId = message.session_id; |
| 1254 | 1269 | yield* updateResumeCursor(context); |
| ... | ... | @@ -2875,6 +2890,31 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2875 | 2890 | ...(Object.keys(extraArgs).length > 0 ? { extraArgs } : {}), |
| 2876 | 2891 | }; |
| 2877 | 2892 | |
| 2893 | yield* Effect.annotateCurrentSpan({ | |
| 2894 | "provider.kind": PROVIDER, | |
| 2895 | "provider.thread_id": threadId, | |
| 2896 | "provider.runtime_mode": input.runtimeMode, | |
| 2897 | "claude.resume.source": | |
| 2898 | existingResumeSessionId !== undefined ? "resume-session" : "generated-session", | |
| 2899 | "claude.resume.thread_id": resumeState?.threadId ?? "", | |
| 2900 | "claude.resume.session_id": existingResumeSessionId ?? "", | |
| 2901 | "claude.resume.session_at": resumeState?.resumeSessionAt ?? "", | |
| 2902 | "claude.resume.turn_count": resumeState?.turnCount ?? -1, | |
| 2903 | "claude.query.cwd": input.cwd ?? "", | |
| 2904 | "claude.query.model": apiModelId ?? "", | |
| 2905 | "claude.query.effort": effectiveEffort ?? "", | |
| 2906 | "claude.query.permission_mode": permissionMode ?? "", | |
| 2907 | "claude.query.allow_dangerously_skip_permissions": permissionMode === "bypassPermissions", | |
| 2908 | "claude.query.resume": existingResumeSessionId ?? "", | |
| 2909 | "claude.query.session_id": newSessionId ?? "", | |
| 2910 | "claude.query.include_partial_messages": true, | |
| 2911 | "claude.query.additional_directories": input.cwd ? [input.cwd] : [], | |
| 2912 | "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], | |
| 2913 | "claude.query.settings_json": JSON.stringify(settings), | |
| 2914 | "claude.query.extra_args_json": JSON.stringify(extraArgs), | |
| 2915 | "claude.query.path_to_executable": claudeBinaryPath, | |
| 2916 | }); | |
| 2917 | ||
| 2878 | 2918 | const queryRuntime = yield* Effect.try({ |
| 2879 | 2919 | try: () => |
| 2880 | 2920 | createQuery({ |
apps/server/src/provider/Layers/ProviderService.test.ts+92-3| ... | ... | @@ -929,9 +929,10 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 929 | 929 | const provider = yield* ProviderService; |
| 930 | 930 | const runtimeRepository = yield* ProviderSessionRuntimeRepository; |
| 931 | 931 | |
| 932 | const session = yield* provider.startSession(asThreadId("thread-1"), { | |
| 932 | const threadId = asThreadId("thread-runtime-status"); | |
| 933 | const session = yield* provider.startSession(threadId, { | |
| 933 | 934 | provider: "codex", |
| 934 | threadId: asThreadId("thread-1"), | |
| 935 | threadId, | |
| 935 | 936 | runtimeMode: "full-access", |
| 936 | 937 | }); |
| 937 | 938 | yield* provider.sendTurn({ |
| ... | ... | @@ -957,7 +958,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 957 | 958 | lastError: string | null; |
| 958 | 959 | lastRuntimeEvent: string | null; |
| 959 | 960 | }; |
| 960 | assert.equal(runtimePayload.cwd, process.cwd()); | |
| 961 | assert.equal(runtimePayload.cwd, session.cwd); | |
| 961 | 962 | assert.equal(runtimePayload.model, null); |
| 962 | 963 | assert.equal(runtimePayload.activeTurnId, `turn-${String(session.threadId)}`); |
| 963 | 964 | assert.equal(runtimePayload.lastError, null); |
| ... | ... | @@ -1058,6 +1059,94 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 1058 | 1059 | fs.rmSync(tempDir, { recursive: true, force: true }); |
| 1059 | 1060 | }).pipe(Effect.provide(NodeServices.layer)), |
| 1060 | 1061 | ); |
| 1062 | ||
| 1063 | it.effect( | |
| 1064 | "reuses persisted cwd when startSession resumes a claude session without cwd input", | |
| 1065 | () => | |
| 1066 | Effect.gen(function* () { | |
| 1067 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-cwd-")); | |
| 1068 | const dbPath = path.join(tempDir, "orchestration.sqlite"); | |
| 1069 | const persistenceLayer = makeSqlitePersistenceLive(dbPath); | |
| 1070 | const runtimeRepositoryLayer = ProviderSessionRuntimeRepositoryLive.pipe( | |
| 1071 | Layer.provide(persistenceLayer), | |
| 1072 | ); | |
| 1073 | ||
| 1074 | const firstClaude = makeFakeCodexAdapter("claudeAgent"); | |
| 1075 | const firstRegistry: typeof ProviderAdapterRegistry.Service = { | |
| 1076 | getByProvider: (provider) => | |
| 1077 | provider === "claudeAgent" | |
| 1078 | ? Effect.succeed(firstClaude.adapter) | |
| 1079 | : Effect.fail(new ProviderUnsupportedError({ provider })), | |
| 1080 | listProviders: () => Effect.succeed(["claudeAgent"]), | |
| 1081 | }; | |
| 1082 | const firstDirectoryLayer = ProviderSessionDirectoryLive.pipe( | |
| 1083 | Layer.provide(runtimeRepositoryLayer), | |
| 1084 | ); | |
| 1085 | const firstProviderLayer = makeProviderServiceLive().pipe( | |
| 1086 | Layer.provide(Layer.succeed(ProviderAdapterRegistry, firstRegistry)), | |
| 1087 | Layer.provide(firstDirectoryLayer), | |
| 1088 | Layer.provide(defaultServerSettingsLayer), | |
| 1089 | Layer.provide(AnalyticsService.layerTest), | |
| 1090 | ); | |
| 1091 | ||
| 1092 | const initial = yield* Effect.gen(function* () { | |
| 1093 | const provider = yield* ProviderService; | |
| 1094 | return yield* provider.startSession(asThreadId("thread-claude-cwd"), { | |
| 1095 | provider: "claudeAgent", | |
| 1096 | threadId: asThreadId("thread-claude-cwd"), | |
| 1097 | cwd: "/tmp/project-claude-cwd", | |
| 1098 | runtimeMode: "full-access", | |
| 1099 | }); | |
| 1100 | }).pipe(Effect.provide(firstProviderLayer)); | |
| 1101 | ||
| 1102 | const secondClaude = makeFakeCodexAdapter("claudeAgent"); | |
| 1103 | const secondRegistry: typeof ProviderAdapterRegistry.Service = { | |
| 1104 | getByProvider: (provider) => | |
| 1105 | provider === "claudeAgent" | |
| 1106 | ? Effect.succeed(secondClaude.adapter) | |
| 1107 | : Effect.fail(new ProviderUnsupportedError({ provider })), | |
| 1108 | listProviders: () => Effect.succeed(["claudeAgent"]), | |
| 1109 | }; | |
| 1110 | const secondDirectoryLayer = ProviderSessionDirectoryLive.pipe( | |
| 1111 | Layer.provide(runtimeRepositoryLayer), | |
| 1112 | ); | |
| 1113 | const secondProviderLayer = makeProviderServiceLive().pipe( | |
| 1114 | Layer.provide(Layer.succeed(ProviderAdapterRegistry, secondRegistry)), | |
| 1115 | Layer.provide(secondDirectoryLayer), | |
| 1116 | Layer.provide(defaultServerSettingsLayer), | |
| 1117 | Layer.provide(AnalyticsService.layerTest), | |
| 1118 | ); | |
| 1119 | ||
| 1120 | secondClaude.startSession.mockClear(); | |
| 1121 | ||
| 1122 | yield* Effect.gen(function* () { | |
| 1123 | const provider = yield* ProviderService; | |
| 1124 | yield* provider.startSession(initial.threadId, { | |
| 1125 | provider: "claudeAgent", | |
| 1126 | threadId: initial.threadId, | |
| 1127 | runtimeMode: "full-access", | |
| 1128 | }); | |
| 1129 | }).pipe(Effect.provide(secondProviderLayer)); | |
| 1130 | ||
| 1131 | assert.equal(secondClaude.startSession.mock.calls.length, 1); | |
| 1132 | const resumedStartInput = secondClaude.startSession.mock.calls[0]?.[0]; | |
| 1133 | assert.equal(typeof resumedStartInput === "object" && resumedStartInput !== null, true); | |
| 1134 | if (resumedStartInput && typeof resumedStartInput === "object") { | |
| 1135 | const startPayload = resumedStartInput as { | |
| 1136 | provider?: string; | |
| 1137 | cwd?: string; | |
| 1138 | resumeCursor?: unknown; | |
| 1139 | threadId?: string; | |
| 1140 | }; | |
| 1141 | assert.equal(startPayload.provider, "claudeAgent"); | |
| 1142 | assert.equal(startPayload.cwd, "/tmp/project-claude-cwd"); | |
| 1143 | assert.deepEqual(startPayload.resumeCursor, initial.resumeCursor); | |
| 1144 | assert.equal(startPayload.threadId, initial.threadId); | |
| 1145 | } | |
| 1146 | ||
| 1147 | fs.rmSync(tempDir, { recursive: true, force: true }); | |
| 1148 | }).pipe(Effect.provide(NodeServices.layer)), | |
| 1149 | ); | |
| 1061 | 1150 | }); |
| 1062 | 1151 | |
| 1063 | 1152 | const fanout = makeProviderServiceLayer(); |
apps/server/src/provider/Layers/ProviderService.ts+23-1| ... | ... | @@ -374,9 +374,31 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 374 | 374 | (persistedBinding?.provider === input.provider |
| 375 | 375 | ? persistedBinding.resumeCursor |
| 376 | 376 | : undefined); |
| 377 | const effectiveCwd = | |
| 378 | input.cwd ?? | |
| 379 | (persistedBinding?.provider === input.provider | |
| 380 | ? readPersistedCwd(persistedBinding.runtimePayload) | |
| 381 | : undefined); | |
| 382 | yield* Effect.annotateCurrentSpan({ | |
| 383 | "provider.resume_cursor.source": | |
| 384 | input.resumeCursor !== undefined | |
| 385 | ? "request" | |
| 386 | : effectiveResumeCursor !== undefined && persistedBinding?.provider === input.provider | |
| 387 | ? "persisted" | |
| 388 | : "none", | |
| 389 | "provider.resume_cursor.present": effectiveResumeCursor !== undefined, | |
| 390 | "provider.cwd.source": | |
| 391 | input.cwd !== undefined | |
| 392 | ? "request" | |
| 393 | : effectiveCwd !== undefined && persistedBinding?.provider === input.provider | |
| 394 | ? "persisted" | |
| 395 | : "none", | |
| 396 | "provider.cwd.effective": effectiveCwd ?? "", | |
| 397 | }); | |
| 377 | 398 | const adapter = yield* registry.getByProvider(input.provider); |
| 378 | 399 | const session = yield* adapter.startSession({ |
| 379 | 400 | ...input, |
| 401 | ...(effectiveCwd !== undefined ? { cwd: effectiveCwd } : {}), | |
| 380 | 402 | ...(effectiveResumeCursor !== undefined ? { resumeCursor: effectiveResumeCursor } : {}), |
| 381 | 403 | }); |
| 382 | 404 | |
| ... | ... | @@ -398,7 +420,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 398 | 420 | provider: session.provider, |
| 399 | 421 | runtimeMode: input.runtimeMode, |
| 400 | 422 | hasResumeCursor: session.resumeCursor !== undefined, |
| 401 | hasCwd: typeof input.cwd === "string" && input.cwd.trim().length > 0, | |
| 423 | hasCwd: typeof effectiveCwd === "string" && effectiveCwd.trim().length > 0, | |
| 402 | 424 | hasModel: |
| 403 | 425 | typeof input.modelSelection?.model === "string" && |
| 404 | 426 | input.modelSelection.model.trim().length > 0, |