| author | |
| committer | |
| log | 24f9c2a08e7b72a8f02b92aba229c34d35f72e30 |
| tree | 0ef67f68c6f51e55a9a766f52fb4d8e22a756fb2 |
| parent | 31ca9e553112ba336bfb3f6c04951f1086f37b0c |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 65 insertions(+), 10 deletions(-)
apps/server/src/provider/Drivers/CodexHomeLayout.test.ts+36| ... | ... | @@ -114,6 +114,9 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => { |
| 114 | 114 | |
| 115 | 115 | const sessionsTarget = yield* fileSystem.readLink(path.join(shadowHome, "sessions")); |
| 116 | 116 | const configTarget = yield* fileSystem.readLink(path.join(shadowHome, "config.toml")); |
| 117 | const mcpOauthLocksTarget = yield* fileSystem.readLink( | |
| 118 | path.join(shadowHome, "mcp-oauth-locks"), | |
| 119 | ); | |
| 117 | 120 | const modelsCacheExists = yield* fileSystem.exists( |
| 118 | 121 | path.join(shadowHome, "models_cache.json"), |
| 119 | 122 | ); |
| ... | ... | @@ -124,12 +127,45 @@ it.layer(NodeServices.layer)("CodexHomeLayout", (it) => { |
| 124 | 127 | |
| 125 | 128 | expect(sessionsTarget).toBe(path.join(sharedHome, "sessions")); |
| 126 | 129 | expect(configTarget).toBe(path.join(sharedHome, "config.toml")); |
| 130 | expect(mcpOauthLocksTarget).toBe(path.join(sharedHome, "mcp-oauth-locks")); | |
| 127 | 131 | expect(modelsCacheExists).toBe(false); |
| 128 | 132 | expect(authLinkResult._tag).toBe("Failure"); |
| 129 | 133 | expect(authContents).toContain("shadow"); |
| 130 | 134 | }), |
| 131 | 135 | ); |
| 132 | 136 | |
| 137 | it.effect("replaces Codex-created local MCP OAuth locks with the shared lock directory", () => | |
| 138 | Effect.gen(function* () { | |
| 139 | const fileSystem = yield* FileSystem.FileSystem; | |
| 140 | const path = yield* Path.Path; | |
| 141 | const sharedHome = yield* makeTempDir("t3code-codex-shared-"); | |
| 142 | const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-"); | |
| 143 | const shadowHome = path.join(shadowRoot, "shadow"); | |
| 144 | const sharedLocks = path.join(sharedHome, "mcp-oauth-locks"); | |
| 145 | const shadowLocks = path.join(shadowHome, "mcp-oauth-locks"); | |
| 146 | ||
| 147 | yield* writeTextFile(path.join(sharedLocks, "file-store.lock"), ""); | |
| 148 | yield* writeTextFile(path.join(shadowLocks, "file-store.lock"), ""); | |
| 149 | ||
| 150 | const layout = yield* resolveCodexHomeLayout( | |
| 151 | decodeCodexSettings({ | |
| 152 | homePath: sharedHome, | |
| 153 | shadowHomePath: shadowHome, | |
| 154 | }), | |
| 155 | ); | |
| 156 | ||
| 157 | yield* materializeCodexShadowHome(layout); | |
| 158 | ||
| 159 | const locksTarget = yield* fileSystem.readLink(shadowLocks); | |
| 160 | const sharedLockExists = yield* fileSystem.exists( | |
| 161 | path.join(sharedLocks, "file-store.lock"), | |
| 162 | ); | |
| 163 | ||
| 164 | expect(locksTarget).toBe(sharedLocks); | |
| 165 | expect(sharedLockExists).toBe(true); | |
| 166 | }), | |
| 167 | ); | |
| 168 | ||
| 133 | 169 | it.effect("accepts Codex-created shadow-local runtime directories", () => |
| 134 | 170 | Effect.gen(function* () { |
| 135 | 171 | const fileSystem = yield* FileSystem.FileSystem; |
apps/server/src/provider/Drivers/CodexHomeLayout.ts+29-10| ... | ... | @@ -26,10 +26,12 @@ const KNOWN_SHARED_DIRECTORIES = [ |
| 26 | 26 | "plugins", |
| 27 | 27 | "cache", |
| 28 | 28 | "logs", |
| 29 | "mcp-oauth-locks", | |
| 29 | 30 | ] as const; |
| 30 | 31 | |
| 31 | 32 | const PRIVATE_ENTRY_NAMES = new Set(["auth.json", "models_cache.json"]); |
| 32 | 33 | const SHADOW_LOCAL_ENTRY_NAMES = new Set(["log", "memories", "tmp"]); |
| 34 | const REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = new Set(["mcp-oauth-locks"]); | |
| 33 | 35 | |
| 34 | 36 | function resolveHomePath(path: Path.Path, value: string | undefined): string { |
| 35 | 37 | const expanded = |
| ... | ... | @@ -225,16 +227,6 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu |
| 225 | 227 | linkPath: link, |
| 226 | 228 | }); |
| 227 | 229 | |
| 228 | if (state._tag === "NotSymlink") { | |
| 229 | return yield* new CodexShadowHomeEntryConflictError({ | |
| 230 | sharedHomePath: input.sharedHomePath, | |
| 231 | effectiveHomePath: input.effectiveHomePath, | |
| 232 | entryName: input.entryName, | |
| 233 | linkPath: link, | |
| 234 | targetPath: target, | |
| 235 | }); | |
| 236 | } | |
| 237 | ||
| 238 | 230 | const createLink = input.fileSystem.symlink(target, link).pipe( |
| 239 | 231 | Effect.catchTags({ |
| 240 | 232 | PlatformError: (cause) => |
| ... | ... | @@ -250,6 +242,33 @@ const ensureSymlink = Effect.fn("CodexHomeLayout.ensureSymlink")(function* (inpu |
| 250 | 242 | }), |
| 251 | 243 | ); |
| 252 | 244 | |
| 245 | if (state._tag === "NotSymlink") { | |
| 246 | if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) { | |
| 247 | return yield* new CodexShadowHomeEntryConflictError({ | |
| 248 | sharedHomePath: input.sharedHomePath, | |
| 249 | effectiveHomePath: input.effectiveHomePath, | |
| 250 | entryName: input.entryName, | |
| 251 | linkPath: link, | |
| 252 | targetPath: target, | |
| 253 | }); | |
| 254 | } | |
| 255 | ||
| 256 | yield* input.fileSystem.remove(link, { recursive: true }).pipe( | |
| 257 | Effect.catchTags({ | |
| 258 | PlatformError: (cause) => | |
| 259 | new CodexShadowHomeFileSystemError({ | |
| 260 | sharedHomePath: input.sharedHomePath, | |
| 261 | effectiveHomePath: input.effectiveHomePath, | |
| 262 | operation: "remove", | |
| 263 | path: link, | |
| 264 | entryName: input.entryName, | |
| 265 | cause, | |
| 266 | }), | |
| 267 | }), | |
| 268 | ); | |
| 269 | return yield* createLink; | |
| 270 | } | |
| 271 | ||
| 253 | 272 | if (state._tag === "Missing") { |
| 254 | 273 | return yield* createLink; |
| 255 | 274 | } |