| author | |
| committer | |
| log | 42b510e8c876d97bf505c3e637e521f7c59ef73c |
| tree | f1ea5b8d84977ed2ad671a0b06d9aea5f501c260 |
| parent | ce841a7ef10543478823d44c73da3820b9990829 |
20 files changed, 840 insertions(+), 640 deletions(-)
apps/server/package.json-1| ... | @@ -18,7 +18,6 @@ | ... | @@ -18,7 +18,6 @@ |
| 18 | }, | 18 | }, |
| 19 | "dependencies": { | 19 | "dependencies": { |
| 20 | "@effect/platform-node": "4.0.0-beta.10", | 20 | "@effect/platform-node": "4.0.0-beta.10", |
| 21 | "@effect/sql-sqlite-node": "4.0.0-beta.10", | ||
| 22 | "effect": "4.0.0-beta.10", | 21 | "effect": "4.0.0-beta.10", |
| 23 | "node-pty": "^1.1.0", | 22 | "node-pty": "^1.1.0", |
| 24 | "open": "^10.1.0", | 23 | "open": "^10.1.0", |
apps/server/src/checkpointing/Layers/CheckpointService.test.ts+147-112| ... | @@ -1,7 +1,3 @@ | ... | @@ -1,7 +1,3 @@ |
| 1 | import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; | ||
| 2 | import os from "node:os"; | ||
| 3 | import path from "node:path"; | ||
| 4 | |||
| 5 | import type { | 1 | import type { |
| 6 | ProviderApprovalDecision, | 2 | ProviderApprovalDecision, |
| 7 | ProviderEvent, | 3 | ProviderEvent, |
| ... | @@ -13,7 +9,7 @@ import type { | ... | @@ -13,7 +9,7 @@ import type { |
| 13 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 14 | import { NodeServices } from "@effect/platform-node"; | 10 | import { NodeServices } from "@effect/platform-node"; |
| 15 | import { it, assert } from "@effect/vitest"; | 11 | import { it, assert } from "@effect/vitest"; |
| 16 | import { Effect, Layer } from "effect"; | 12 | import { Effect, FileSystem, Layer, Path } from "effect"; |
| 17 | 13 | ||
| 18 | import { runGit as runGitProcess } from "../../git/Process.ts"; | 14 | import { runGit as runGitProcess } from "../../git/Process.ts"; |
| 19 | import { CheckpointServiceLive } from "./CheckpointService.ts"; | 15 | import { CheckpointServiceLive } from "./CheckpointService.ts"; |
| ... | @@ -34,14 +30,9 @@ import type { | ... | @@ -34,14 +30,9 @@ import type { |
| 34 | } from "../../provider/Services/ProviderAdapter.ts"; | 30 | } from "../../provider/Services/ProviderAdapter.ts"; |
| 35 | import { ProviderSessionDirectory } from "../../provider/Services/ProviderSessionDirectory.ts"; | 31 | import { ProviderSessionDirectory } from "../../provider/Services/ProviderSessionDirectory.ts"; |
| 36 | import { ProviderSessionDirectoryLive } from "../../provider/Layers/ProviderSessionDirectory.ts"; | 32 | import { ProviderSessionDirectoryLive } from "../../provider/Layers/ProviderSessionDirectory.ts"; |
| 37 | import { makeSqliteCheckpointRepositoryLive } from "../../persistence/Layers/Checkpoints.ts"; | 33 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; |
| 38 | import { CheckpointRepository } from "../../persistence/Services/Checkpoints.ts"; | 34 | import { CheckpointRepository } from "../../persistence/Services/Checkpoints.ts"; |
| 39 | 35 | import { CheckpointRepositoryLive } from "../../persistence/Layers/Checkpoints.ts"; | |
| 40 | const makeTemporaryDirectoryScoped = (prefix: string) => | ||
| 41 | Effect.acquireRelease( | ||
| 42 | Effect.promise(() => mkdtemp(path.join(os.tmpdir(), prefix))), | ||
| 43 | (directory) => Effect.promise(() => rm(directory, { recursive: true, force: true })), | ||
| 44 | ); | ||
| 45 | 36 | ||
| 46 | const runGit = (cwd: string, args: readonly string[]) => | 37 | const runGit = (cwd: string, args: readonly string[]) => |
| 47 | runGitProcess({ | 38 | runGitProcess({ |
| ... | @@ -53,23 +44,24 @@ const runGit = (cwd: string, args: readonly string[]) => | ... | @@ -53,23 +44,24 @@ const runGit = (cwd: string, args: readonly string[]) => |
| 53 | Effect.provide(NodeServices.layer), | 44 | Effect.provide(NodeServices.layer), |
| 54 | ); | 45 | ); |
| 55 | 46 | ||
| 56 | function createRepo() { | 47 | const makeGitRepository = Effect.gen(function* () { |
| 57 | return Effect.gen(function* () { | 48 | const fs = yield* FileSystem.FileSystem; |
| 58 | const cwd = yield* makeTemporaryDirectoryScoped("t3-checkpoint-service-repo-"); | 49 | const path = yield* Path.Path; |
| 59 | yield* runGit(cwd, ["init", "--initial-branch=main"]); | 50 | const cwd = yield* fs.makeTempDirectory(); |
| 60 | yield* runGit(cwd, ["config", "user.email", "test@example.com"]); | 51 | yield* runGit(cwd, ["init", "--initial-branch=main"]); |
| 61 | yield* runGit(cwd, ["config", "user.name", "Test User"]); | 52 | yield* runGit(cwd, ["config", "user.email", "test@example.com"]); |
| 62 | yield* Effect.promise(() => writeFile(path.join(cwd, "README.md"), "v1\n")); | 53 | yield* runGit(cwd, ["config", "user.name", "Test User"]); |
| 63 | yield* runGit(cwd, ["add", "."]); | 54 | yield* fs.writeFileString(path.join(cwd, "README.md"), "v1\n"); |
| 64 | yield* runGit(cwd, ["commit", "-m", "Initial"]); | 55 | yield* runGit(cwd, ["add", "."]); |
| 65 | return cwd; | 56 | yield* runGit(cwd, ["commit", "-m", "Initial"]); |
| 66 | }); | 57 | return cwd; |
| 67 | } | 58 | }); |
| 68 | 59 | ||
| 69 | class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterError> { | 60 | class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterError> { |
| 70 | readonly provider = "codex" as const; | 61 | readonly provider = "codex" as const; |
| 71 | 62 | ||
| 72 | private active = true; | 63 | private active = true; |
| 64 | private readThreadFailure: ProviderAdapterError | undefined; | ||
| 73 | private snapshot: ProviderThreadSnapshot; | 65 | private snapshot: ProviderThreadSnapshot; |
| 74 | private readonly session: ProviderSession; | 66 | private readonly session: ProviderSession; |
| 75 | 67 | ||
| ... | @@ -105,6 +97,10 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr | ... | @@ -105,6 +97,10 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr |
| 105 | return this.snapshot.turns; | 97 | return this.snapshot.turns; |
| 106 | } | 98 | } |
| 107 | 99 | ||
| 100 | setReadThreadFailure(error: ProviderAdapterError | undefined): void { | ||
| 101 | this.readThreadFailure = error; | ||
| 102 | } | ||
| 103 | |||
| 108 | readonly startSession = ( | 104 | readonly startSession = ( |
| 109 | _input: ProviderSessionStartInput, | 105 | _input: ProviderSessionStartInput, |
| 110 | ): Effect.Effect<ProviderSession, ProviderAdapterError> => | 106 | ): Effect.Effect<ProviderSession, ProviderAdapterError> => |
| ... | @@ -157,7 +153,9 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr | ... | @@ -157,7 +153,9 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr |
| 157 | this.hasSession(sessionId).pipe( | 153 | this.hasSession(sessionId).pipe( |
| 158 | Effect.flatMap((hasSession) => | 154 | Effect.flatMap((hasSession) => |
| 159 | hasSession | 155 | hasSession |
| 160 | ? Effect.succeed(this.snapshot) | 156 | ? this.readThreadFailure |
| 157 | ? Effect.fail(this.readThreadFailure) | ||
| 158 | : Effect.succeed(this.snapshot) | ||
| 161 | : Effect.fail( | 159 | : Effect.fail( |
| 162 | new ProviderAdapterSessionNotFoundError({ | 160 | new ProviderAdapterSessionNotFoundError({ |
| 163 | provider: this.provider, | 161 | provider: this.provider, |
| ... | @@ -203,9 +201,7 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr | ... | @@ -203,9 +201,7 @@ class InMemoryProviderAdapter implements ProviderAdapterShape<ProviderAdapterErr |
| 203 | 201 | ||
| 204 | function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { | 202 | function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { |
| 205 | return Effect.gen(function* () { | 203 | return Effect.gen(function* () { |
| 206 | const cwd = yield* createRepo(); | 204 | const cwd = yield* makeGitRepository; |
| 207 | const stateDir = yield* makeTemporaryDirectoryScoped("t3-checkpoint-service-state-"); | ||
| 208 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 209 | 205 | ||
| 210 | const sessionId = "sess-1"; | 206 | const sessionId = "sess-1"; |
| 211 | const adapter = new InMemoryProviderAdapter(sessionId, cwd, turns); | 207 | const adapter = new InMemoryProviderAdapter(sessionId, cwd, turns); |
| ... | @@ -220,9 +216,10 @@ function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { | ... | @@ -220,9 +216,10 @@ function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { |
| 220 | 216 | ||
| 221 | const dependencies = Layer.mergeAll( | 217 | const dependencies = Layer.mergeAll( |
| 222 | Layer.provide(CheckpointStoreLive, NodeServices.layer), | 218 | Layer.provide(CheckpointStoreLive, NodeServices.layer), |
| 223 | makeSqliteCheckpointRepositoryLive(dbPath), | 219 | Layer.provide(CheckpointRepositoryLive, SqlitePersistenceMemory), |
| 224 | Layer.succeed(ProviderAdapterRegistry, registry), | 220 | Layer.succeed(ProviderAdapterRegistry, registry), |
| 225 | ProviderSessionDirectoryLive, | 221 | ProviderSessionDirectoryLive, |
| 222 | NodeServices.layer, | ||
| 226 | ); | 223 | ); |
| 227 | 224 | ||
| 228 | const layer = CheckpointServiceLive.pipe(Layer.provideMerge(dependencies)); | 225 | const layer = CheckpointServiceLive.pipe(Layer.provideMerge(dependencies)); |
| ... | @@ -236,87 +233,84 @@ function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { | ... | @@ -236,87 +233,84 @@ function makeFixture(turns: ReadonlyArray<ProviderThreadTurnSnapshot>) { |
| 236 | }); | 233 | }); |
| 237 | } | 234 | } |
| 238 | 235 | ||
| 239 | it.effect( | 236 | it.effect("captures checkpoints, diffs refs, and reverts workspace + turns", () => |
| 240 | "CheckpointServiceLive captures checkpoints, diffs refs, and reverts workspace + turns", | 237 | Effect.gen(function* () { |
| 241 | () => | 238 | const firstTurn: ProviderThreadTurnSnapshot = { |
| 242 | Effect.gen(function* () { | 239 | id: "turn-1", |
| 243 | const firstTurn: ProviderThreadTurnSnapshot = { | 240 | items: [{ type: "userMessage", content: [{ type: "text", text: "first" }] }], |
| 244 | id: "turn-1", | 241 | }; |
| 245 | items: [{ type: "userMessage", content: [{ type: "text", text: "first" }] }], | 242 | const secondTurn: ProviderThreadTurnSnapshot = { |
| 246 | }; | 243 | id: "turn-2", |
| 247 | const secondTurn: ProviderThreadTurnSnapshot = { | 244 | items: [{ type: "agentMessage", text: "second" }], |
| 248 | id: "turn-2", | 245 | }; |
| 249 | items: [{ type: "agentMessage", text: "second" }], | ||
| 250 | }; | ||
| 251 | |||
| 252 | const fixture = yield* makeFixture([]); | ||
| 253 | |||
| 254 | yield* Effect.gen(function* () { | ||
| 255 | const service = yield* CheckpointService; | ||
| 256 | const directory = yield* ProviderSessionDirectory; | ||
| 257 | |||
| 258 | yield* directory.upsert({ | ||
| 259 | sessionId: fixture.sessionId, | ||
| 260 | provider: "codex", | ||
| 261 | threadId: "thread-1", | ||
| 262 | }); | ||
| 263 | |||
| 264 | yield* service.initializeForSession({ | ||
| 265 | providerSessionId: fixture.sessionId, | ||
| 266 | cwd: fixture.cwd, | ||
| 267 | }); | ||
| 268 | |||
| 269 | yield* Effect.promise(() => writeFile(path.join(fixture.cwd, "README.md"), "v2\n")); | ||
| 270 | fixture.adapter.setTurns([firstTurn]); | ||
| 271 | |||
| 272 | yield* service.captureCurrentTurn({ | ||
| 273 | providerSessionId: fixture.sessionId, | ||
| 274 | }); | ||
| 275 | |||
| 276 | const listed = yield* service.listCheckpoints({ sessionId: fixture.sessionId }); | ||
| 277 | assert.equal(listed.checkpoints.length, 2); | ||
| 278 | assert.equal(listed.checkpoints[1]?.id, "turn-1"); | ||
| 279 | assert.equal(listed.checkpoints[1]?.isCurrent, true); | ||
| 280 | |||
| 281 | const diff = yield* service.getCheckpointDiff({ | ||
| 282 | sessionId: fixture.sessionId, | ||
| 283 | fromTurnCount: 0, | ||
| 284 | toTurnCount: 1, | ||
| 285 | }); | ||
| 286 | assert.equal(diff.threadId, "thread-1"); | ||
| 287 | assert.equal(diff.diff.includes("diff --git a/README.md b/README.md"), true); | ||
| 288 | assert.equal(diff.diff.includes("-v1"), true); | ||
| 289 | assert.equal(diff.diff.includes("+v2"), true); | ||
| 290 | |||
| 291 | yield* Effect.promise(() => writeFile(path.join(fixture.cwd, "README.md"), "v3\n")); | ||
| 292 | fixture.adapter.setTurns([firstTurn, secondTurn]); | ||
| 293 | |||
| 294 | yield* service.captureCurrentTurn({ | ||
| 295 | providerSessionId: fixture.sessionId, | ||
| 296 | }); | ||
| 297 | |||
| 298 | const beforeRevert = yield* Effect.promise(() => | ||
| 299 | readFile(path.join(fixture.cwd, "README.md"), "utf8"), | ||
| 300 | ); | ||
| 301 | assert.equal(beforeRevert, "v3\n"); | ||
| 302 | |||
| 303 | const reverted = yield* service.revertToCheckpoint({ | ||
| 304 | sessionId: fixture.sessionId, | ||
| 305 | turnCount: 1, | ||
| 306 | }); | ||
| 307 | 246 | ||
| 308 | const afterRevert = yield* Effect.promise(() => | 247 | const fixture = yield* makeFixture([]); |
| 309 | readFile(path.join(fixture.cwd, "README.md"), "utf8"), | 248 | |
| 310 | ); | 249 | yield* Effect.gen(function* () { |
| 311 | assert.equal(afterRevert, "v2\n"); | 250 | const fs = yield* FileSystem.FileSystem; |
| 312 | assert.equal(reverted.turnCount, 1); | 251 | const path = yield* Path.Path; |
| 313 | assert.equal(reverted.rolledBackTurns, 1); | 252 | |
| 314 | assert.equal(reverted.checkpoints.length, 2); | 253 | const service = yield* CheckpointService; |
| 315 | assert.equal(reverted.checkpoints[1]?.id, "turn-1"); | 254 | const directory = yield* ProviderSessionDirectory; |
| 316 | assert.equal(reverted.checkpoints[1]?.isCurrent, true); | 255 | |
| 317 | assert.equal(fixture.adapter.getTurns().length, 1); | 256 | yield* directory.upsert({ |
| 318 | }).pipe(Effect.provide(fixture.layer)); | 257 | sessionId: fixture.sessionId, |
| 319 | }), | 258 | provider: "codex", |
| 259 | threadId: "thread-1", | ||
| 260 | }); | ||
| 261 | |||
| 262 | yield* service.initializeForSession({ | ||
| 263 | providerSessionId: fixture.sessionId, | ||
| 264 | cwd: fixture.cwd, | ||
| 265 | }); | ||
| 266 | |||
| 267 | yield* fs.writeFileString(path.join(fixture.cwd, "README.md"), "v2\n"); | ||
| 268 | fixture.adapter.setTurns([firstTurn]); | ||
| 269 | |||
| 270 | yield* service.captureCurrentTurn({ | ||
| 271 | providerSessionId: fixture.sessionId, | ||
| 272 | }); | ||
| 273 | |||
| 274 | const listed = yield* service.listCheckpoints({ sessionId: fixture.sessionId }); | ||
| 275 | assert.equal(listed.checkpoints.length, 2); | ||
| 276 | assert.equal(listed.checkpoints[1]?.id, "turn-1"); | ||
| 277 | assert.equal(listed.checkpoints[1]?.isCurrent, true); | ||
| 278 | |||
| 279 | const diff = yield* service.getCheckpointDiff({ | ||
| 280 | sessionId: fixture.sessionId, | ||
| 281 | fromTurnCount: 0, | ||
| 282 | toTurnCount: 1, | ||
| 283 | }); | ||
| 284 | assert.equal(diff.threadId, "thread-1"); | ||
| 285 | assert.equal(diff.diff.includes("diff --git a/README.md b/README.md"), true); | ||
| 286 | assert.equal(diff.diff.includes("-v1"), true); | ||
| 287 | assert.equal(diff.diff.includes("+v2"), true); | ||
| 288 | |||
| 289 | yield* fs.writeFileString(path.join(fixture.cwd, "README.md"), "v3\n"); | ||
| 290 | fixture.adapter.setTurns([firstTurn, secondTurn]); | ||
| 291 | |||
| 292 | yield* service.captureCurrentTurn({ | ||
| 293 | providerSessionId: fixture.sessionId, | ||
| 294 | }); | ||
| 295 | |||
| 296 | const beforeRevert = yield* fs.readFileString(path.join(fixture.cwd, "README.md")); | ||
| 297 | assert.equal(beforeRevert, "v3\n"); | ||
| 298 | |||
| 299 | const reverted = yield* service.revertToCheckpoint({ | ||
| 300 | sessionId: fixture.sessionId, | ||
| 301 | turnCount: 1, | ||
| 302 | }); | ||
| 303 | |||
| 304 | const afterRevert = yield* fs.readFileString(path.join(fixture.cwd, "README.md")); | ||
| 305 | assert.equal(afterRevert, "v2\n"); | ||
| 306 | assert.equal(reverted.turnCount, 1); | ||
| 307 | assert.equal(reverted.rolledBackTurns, 1); | ||
| 308 | assert.equal(reverted.checkpoints.length, 2); | ||
| 309 | assert.equal(reverted.checkpoints[1]?.id, "turn-1"); | ||
| 310 | assert.equal(reverted.checkpoints[1]?.isCurrent, true); | ||
| 311 | assert.equal(fixture.adapter.getTurns().length, 1); | ||
| 312 | }).pipe(Effect.provide(fixture.layer)); | ||
| 313 | }).pipe(Effect.provide(NodeServices.layer)), | ||
| 320 | ); | 314 | ); |
| 321 | 315 | ||
| 322 | it.effect( | 316 | it.effect( |
| ... | @@ -338,7 +332,7 @@ it.effect( | ... | @@ -338,7 +332,7 @@ it.effect( |
| 338 | assert.equal(result.failure._tag, "CheckpointSessionNotFoundError"); | 332 | assert.equal(result.failure._tag, "CheckpointSessionNotFoundError"); |
| 339 | } | 333 | } |
| 340 | }).pipe(Effect.provide(fixture.layer)); | 334 | }).pipe(Effect.provide(fixture.layer)); |
| 341 | }), | 335 | }).pipe(Effect.provide(NodeServices.layer)), |
| 342 | ); | 336 | ); |
| 343 | 337 | ||
| 344 | it.effect( | 338 | it.effect( |
| ... | @@ -353,6 +347,8 @@ it.effect( | ... | @@ -353,6 +347,8 @@ it.effect( |
| 353 | const fixture = yield* makeFixture([]); | 347 | const fixture = yield* makeFixture([]); |
| 354 | 348 | ||
| 355 | yield* Effect.gen(function* () { | 349 | yield* Effect.gen(function* () { |
| 350 | const fs = yield* FileSystem.FileSystem; | ||
| 351 | const path = yield* Path.Path; | ||
| 356 | const service = yield* CheckpointService; | 352 | const service = yield* CheckpointService; |
| 357 | const store = yield* CheckpointStore; | 353 | const store = yield* CheckpointStore; |
| 358 | const checkpointRepository = yield* CheckpointRepository; | 354 | const checkpointRepository = yield* CheckpointRepository; |
| ... | @@ -369,7 +365,7 @@ it.effect( | ... | @@ -369,7 +365,7 @@ it.effect( |
| 369 | cwd: fixture.cwd, | 365 | cwd: fixture.cwd, |
| 370 | }); | 366 | }); |
| 371 | 367 | ||
| 372 | yield* Effect.promise(() => writeFile(path.join(fixture.cwd, "README.md"), "v2\n")); | 368 | yield* fs.writeFileString(path.join(fixture.cwd, "README.md"), "v2\n"); |
| 373 | fixture.adapter.setTurns([firstTurn]); | 369 | fixture.adapter.setTurns([firstTurn]); |
| 374 | yield* service.captureCurrentTurn({ | 370 | yield* service.captureCurrentTurn({ |
| 375 | providerSessionId: fixture.sessionId, | 371 | providerSessionId: fixture.sessionId, |
| ... | @@ -400,5 +396,44 @@ it.effect( | ... | @@ -400,5 +396,44 @@ it.effect( |
| 400 | assert.equal(result.failure._tag, "CheckpointUnavailableError"); | 396 | assert.equal(result.failure._tag, "CheckpointUnavailableError"); |
| 401 | } | 397 | } |
| 402 | }).pipe(Effect.provide(fixture.layer)); | 398 | }).pipe(Effect.provide(fixture.layer)); |
| 403 | }), | 399 | }).pipe(Effect.provide(NodeServices.layer)), |
| 400 | ); | ||
| 401 | |||
| 402 | it.effect("CheckpointServiceLive initializes root checkpoints without requiring thread/read", () => | ||
| 403 | Effect.gen(function* () { | ||
| 404 | const fixture = yield* makeFixture([]); | ||
| 405 | |||
| 406 | yield* Effect.gen(function* () { | ||
| 407 | const service = yield* CheckpointService; | ||
| 408 | const checkpointRepository = yield* CheckpointRepository; | ||
| 409 | const directory = yield* ProviderSessionDirectory; | ||
| 410 | |||
| 411 | yield* directory.upsert({ | ||
| 412 | sessionId: fixture.sessionId, | ||
| 413 | provider: "codex", | ||
| 414 | threadId: "thread-1", | ||
| 415 | }); | ||
| 416 | fixture.adapter.setReadThreadFailure( | ||
| 417 | new ProviderAdapterValidationError({ | ||
| 418 | provider: "codex", | ||
| 419 | operation: "readThread", | ||
| 420 | issue: "thread/read is temporarily unavailable", | ||
| 421 | }), | ||
| 422 | ); | ||
| 423 | |||
| 424 | yield* service.initializeForSession({ | ||
| 425 | providerSessionId: fixture.sessionId, | ||
| 426 | cwd: fixture.cwd, | ||
| 427 | }); | ||
| 428 | |||
| 429 | const root = yield* checkpointRepository.getCheckpoint({ | ||
| 430 | providerSessionId: fixture.sessionId, | ||
| 431 | turnCount: 0, | ||
| 432 | }); | ||
| 433 | assert.equal(root._tag, "Some"); | ||
| 434 | if (root._tag === "Some") { | ||
| 435 | assert.equal(root.value.threadId, "thread-1"); | ||
| 436 | } | ||
| 437 | }).pipe(Effect.provide(fixture.layer)); | ||
| 438 | }).pipe(Effect.provide(NodeServices.layer)), | ||
| 404 | ); | 439 | ); |
apps/server/src/checkpointing/Layers/CheckpointService.ts+40-12| ... | @@ -441,6 +441,43 @@ const makeCheckpointService = Effect.gen(function* () { | ... | @@ -441,6 +441,43 @@ const makeCheckpointService = Effect.gen(function* () { |
| 441 | ), | 441 | ), |
| 442 | ); | 442 | ); |
| 443 | 443 | ||
| 444 | const resolveThreadIdForSession = ( | ||
| 445 | sessionId: string, | ||
| 446 | operation: string, | ||
| 447 | ): Effect.Effect<string, CheckpointServiceError> => | ||
| 448 | directory.getThreadId(sessionId).pipe( | ||
| 449 | Effect.mapError( | ||
| 450 | (cause) => | ||
| 451 | new CheckpointSessionNotFoundError({ | ||
| 452 | sessionId, | ||
| 453 | cause, | ||
| 454 | }), | ||
| 455 | ), | ||
| 456 | Effect.flatMap((threadId) => | ||
| 457 | Option.match(threadId, { | ||
| 458 | onSome: (value) => { | ||
| 459 | const normalizedThreadId = value.trim(); | ||
| 460 | if (normalizedThreadId.length > 0) { | ||
| 461 | return Effect.succeed(normalizedThreadId); | ||
| 462 | } | ||
| 463 | return Effect.fail( | ||
| 464 | invariantError( | ||
| 465 | operation, | ||
| 466 | `Resolved empty thread id for session ${sessionId}.`, | ||
| 467 | ), | ||
| 468 | ); | ||
| 469 | }, | ||
| 470 | onNone: () => | ||
| 471 | Effect.fail( | ||
| 472 | invariantError( | ||
| 473 | operation, | ||
| 474 | `Missing thread id in session directory for session ${sessionId}.`, | ||
| 475 | ), | ||
| 476 | ), | ||
| 477 | }), | ||
| 478 | ), | ||
| 479 | ); | ||
| 480 | |||
| 444 | const rollbackThreadSnapshot = ( | 481 | const rollbackThreadSnapshot = ( |
| 445 | sessionId: string, | 482 | sessionId: string, |
| 446 | adapter: ProviderAdapterShape<ProviderAdapterError>, | 483 | adapter: ProviderAdapterShape<ProviderAdapterError>, |
| ... | @@ -633,19 +670,10 @@ const makeCheckpointService = Effect.gen(function* () { | ... | @@ -633,19 +670,10 @@ const makeCheckpointService = Effect.gen(function* () { |
| 633 | Effect.gen(function* () { | 670 | Effect.gen(function* () { |
| 634 | const adapter = yield* resolveAdapterForSession(input.providerSessionId); | 671 | const adapter = yield* resolveAdapterForSession(input.providerSessionId); |
| 635 | yield* ensureSessionIsActive(input.providerSessionId, adapter); | 672 | yield* ensureSessionIsActive(input.providerSessionId, adapter); |
| 636 | const snapshot = yield* readThreadSnapshot( | 673 | const threadId = yield* resolveThreadIdForSession( |
| 637 | input.providerSessionId, | 674 | input.providerSessionId, |
| 638 | adapter, | 675 | "CheckpointService.initializeForSession", |
| 639 | "CheckpointService.initializeForSession:readThread", | ||
| 640 | ); | 676 | ); |
| 641 | if (snapshot.turns.length !== 0) { | ||
| 642 | return yield* Effect.fail( | ||
| 643 | invariantError( | ||
| 644 | "CheckpointService.initializeForSession", | ||
| 645 | `Expected empty thread snapshot during initialization, received ${snapshot.turns.length} turn(s).`, | ||
| 646 | ), | ||
| 647 | ); | ||
| 648 | } | ||
| 649 | 677 | ||
| 650 | const supportsGit = yield* store.isGitRepository(input.cwd); | 678 | const supportsGit = yield* store.isGitRepository(input.cwd); |
| 651 | if (!supportsGit) { | 679 | if (!supportsGit) { |
| ... | @@ -686,7 +714,7 @@ const makeCheckpointService = Effect.gen(function* () { | ... | @@ -686,7 +714,7 @@ const makeCheckpointService = Effect.gen(function* () { |
| 686 | yield* captureAndPersistCheckpoint({ | 714 | yield* captureAndPersistCheckpoint({ |
| 687 | providerSessionId: input.providerSessionId, | 715 | providerSessionId: input.providerSessionId, |
| 688 | cwd: input.cwd, | 716 | cwd: input.cwd, |
| 689 | threadId: snapshot.threadId, | 717 | threadId, |
| 690 | checkpoint: rootCheckpoint(), | 718 | checkpoint: rootCheckpoint(), |
| 691 | }); | 719 | }); |
| 692 | yield* setTrackedCheckpointCwd(input.providerSessionId, input.cwd); | 720 | yield* setTrackedCheckpointCwd(input.providerSessionId, input.cwd); |
apps/server/src/orchestration/Layer.test.ts+34-90| ... | @@ -1,40 +1,21 @@ | ... | @@ -1,40 +1,21 @@ |
| 1 | import fs from "node:fs"; | ||
| 2 | import os from "node:os"; | ||
| 3 | import path from "node:path"; | ||
| 4 | |||
| 5 | import type { OrchestrationEvent } from "@t3tools/contracts"; | 1 | import type { OrchestrationEvent } from "@t3tools/contracts"; |
| 6 | import { Effect, Layer, ManagedRuntime } from "effect"; | 2 | import { Effect, Layer, ManagedRuntime } from "effect"; |
| 7 | import { afterEach, describe, expect, it } from "vitest"; | 3 | import { describe, expect, it } from "vitest"; |
| 8 | 4 | ||
| 9 | import { | 5 | import { |
| 10 | OrchestrationEventRepository, | 6 | OrchestrationEventRepository, |
| 11 | type OrchestrationEventRepositoryShape, | 7 | type OrchestrationEventRepositoryShape, |
| 12 | } from "../persistence/Services/OrchestrationEvents.ts"; | 8 | } from "../persistence/Services/OrchestrationEvents.ts"; |
| 13 | import { PersistenceSqlError } from "../persistence/Errors.ts"; | 9 | import { PersistenceSqlError } from "../persistence/Errors.ts"; |
| 14 | import { makeSqlitePersistenceLive } from "../persistence/Layers/Sqlite.ts"; | 10 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; |
| 15 | import { OrchestrationEngineLive } from "./Layer.ts"; | 11 | import { OrchestrationEngineLive } from "./Layer.ts"; |
| 16 | import { OrchestrationEngineService } from "./Service.ts"; | 12 | import { OrchestrationEngineService } from "./Service.ts"; |
| 17 | import { OrchestrationEventRepositoryLive } from "../persistence/Layers/OrchestrationEvents.ts"; | 13 | import { OrchestrationEventRepositoryLive } from "../persistence/Layers/OrchestrationEvents.ts"; |
| 18 | 14 | ||
| 19 | const tempDirs: string[] = []; | 15 | export async function createOrchestrationSystem() { |
| 20 | |||
| 21 | function makeTempDir(prefix: string): string { | ||
| 22 | const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); | ||
| 23 | tempDirs.push(dir); | ||
| 24 | return dir; | ||
| 25 | } | ||
| 26 | |||
| 27 | afterEach(() => { | ||
| 28 | for (const dir of tempDirs.splice(0, tempDirs.length)) { | ||
| 29 | fs.rmSync(dir, { recursive: true, force: true }); | ||
| 30 | } | ||
| 31 | }); | ||
| 32 | |||
| 33 | export async function createOrchestrationSystem(stateDir: string) { | ||
| 34 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 35 | const orchestrationLayer = OrchestrationEngineLive.pipe( | 16 | const orchestrationLayer = OrchestrationEngineLive.pipe( |
| 36 | Layer.provide(OrchestrationEventRepositoryLive), | 17 | Layer.provide(OrchestrationEventRepositoryLive), |
| 37 | Layer.provide(makeSqlitePersistenceLive(dbPath)), | 18 | Layer.provide(SqlitePersistenceMemory), |
| 38 | ); | 19 | ); |
| 39 | const runtime = ManagedRuntime.make(orchestrationLayer); | 20 | const runtime = ManagedRuntime.make(orchestrationLayer); |
| 40 | const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService)); | 21 | const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService)); |
| ... | @@ -46,16 +27,15 @@ export async function createOrchestrationSystem(stateDir: string) { | ... | @@ -46,16 +27,15 @@ export async function createOrchestrationSystem(stateDir: string) { |
| 46 | } | 27 | } |
| 47 | 28 | ||
| 48 | describe("OrchestrationEngine", () => { | 29 | describe("OrchestrationEngine", () => { |
| 49 | it("replays to the same deterministic snapshot", async () => { | 30 | it("returns deterministic snapshots for repeated reads", async () => { |
| 50 | const stateDir = makeTempDir("t3code-orchestration-"); | ||
| 51 | const createdAt = new Date().toISOString(); | 31 | const createdAt = new Date().toISOString(); |
| 52 | const projectId = "project-1"; | 32 | const projectId = "project-1"; |
| 53 | const threadId = "thread-1"; | 33 | const threadId = "thread-1"; |
| 54 | 34 | ||
| 55 | const firstSystem = await createOrchestrationSystem(stateDir); | 35 | const system = await createOrchestrationSystem(); |
| 56 | const engineA = firstSystem.engine; | 36 | const engine = system.engine; |
| 57 | await firstSystem.run( | 37 | await system.run( |
| 58 | engineA.dispatch({ | 38 | engine.dispatch({ |
| 59 | type: "thread.create", | 39 | type: "thread.create", |
| 60 | commandId: "cmd-1", | 40 | commandId: "cmd-1", |
| 61 | threadId, | 41 | threadId, |
| ... | @@ -67,8 +47,8 @@ describe("OrchestrationEngine", () => { | ... | @@ -67,8 +47,8 @@ describe("OrchestrationEngine", () => { |
| 67 | createdAt, | 47 | createdAt, |
| 68 | }), | 48 | }), |
| 69 | ); | 49 | ); |
| 70 | await firstSystem.run( | 50 | await system.run( |
| 71 | engineA.dispatch({ | 51 | engine.dispatch({ |
| 72 | type: "message.send", | 52 | type: "message.send", |
| 73 | commandId: "cmd-3", | 53 | commandId: "cmd-3", |
| 74 | threadId, | 54 | threadId, |
| ... | @@ -79,19 +59,14 @@ describe("OrchestrationEngine", () => { | ... | @@ -79,19 +59,14 @@ describe("OrchestrationEngine", () => { |
| 79 | createdAt, | 59 | createdAt, |
| 80 | }), | 60 | }), |
| 81 | ); | 61 | ); |
| 82 | const snapshotA = await firstSystem.run(engineA.getSnapshot()); | 62 | const snapshotA = await system.run(engine.getSnapshot()); |
| 83 | await firstSystem.dispose(); | 63 | const snapshotB = await system.run(engine.getSnapshot()); |
| 84 | |||
| 85 | const secondSystem = await createOrchestrationSystem(stateDir); | ||
| 86 | const engineB = secondSystem.engine; | ||
| 87 | const snapshotB = await secondSystem.run(engineB.getSnapshot()); | ||
| 88 | expect(snapshotB).toEqual(snapshotA); | 64 | expect(snapshotB).toEqual(snapshotA); |
| 89 | await secondSystem.dispose(); | 65 | await system.dispose(); |
| 90 | }); | 66 | }); |
| 91 | 67 | ||
| 92 | it("fans out read-model updates to subscribers", async () => { | 68 | it("fans out read-model updates to subscribers", async () => { |
| 93 | const stateDir = makeTempDir("t3code-orchestration-fanout-"); | 69 | const system = await createOrchestrationSystem(); |
| 94 | const system = await createOrchestrationSystem(stateDir); | ||
| 95 | const engine = system.engine; | 70 | const engine = system.engine; |
| 96 | const updates: number[] = []; | 71 | const updates: number[] = []; |
| 97 | const unsubscribe = await system.run( | 72 | const unsubscribe = await system.run( |
| ... | @@ -118,8 +93,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -118,8 +93,7 @@ describe("OrchestrationEngine", () => { |
| 118 | }); | 93 | }); |
| 119 | 94 | ||
| 120 | it("replays append-only events from sequence", async () => { | 95 | it("replays append-only events from sequence", async () => { |
| 121 | const stateDir = makeTempDir("t3code-orchestration-replay-"); | 96 | const system = await createOrchestrationSystem(); |
| 122 | const system = await createOrchestrationSystem(stateDir); | ||
| 123 | const engine = system.engine; | 97 | const engine = system.engine; |
| 124 | await system.run( | 98 | await system.run( |
| 125 | engine.dispatch({ | 99 | engine.dispatch({ |
| ... | @@ -150,11 +124,10 @@ describe("OrchestrationEngine", () => { | ... | @@ -150,11 +124,10 @@ describe("OrchestrationEngine", () => { |
| 150 | }); | 124 | }); |
| 151 | 125 | ||
| 152 | it("stores completed turn summaries even when no files changed", async () => { | 126 | it("stores completed turn summaries even when no files changed", async () => { |
| 153 | const stateDir = makeTempDir("t3code-orchestration-turn-diff-"); | 127 | const system = await createOrchestrationSystem(); |
| 154 | const firstSystem = await createOrchestrationSystem(stateDir); | 128 | const engine = system.engine; |
| 155 | const engine = firstSystem.engine; | ||
| 156 | const completedAt = new Date().toISOString(); | 129 | const completedAt = new Date().toISOString(); |
| 157 | await firstSystem.run( | 130 | await system.run( |
| 158 | engine.dispatch({ | 131 | engine.dispatch({ |
| 159 | type: "thread.create", | 132 | type: "thread.create", |
| 160 | commandId: "cmd-thread-turn-diff", | 133 | commandId: "cmd-thread-turn-diff", |
| ... | @@ -167,7 +140,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -167,7 +140,7 @@ describe("OrchestrationEngine", () => { |
| 167 | createdAt: completedAt, | 140 | createdAt: completedAt, |
| 168 | }), | 141 | }), |
| 169 | ); | 142 | ); |
| 170 | await firstSystem.run( | 143 | await system.run( |
| 171 | engine.dispatch({ | 144 | engine.dispatch({ |
| 172 | type: "thread.turnDiff.complete", | 145 | type: "thread.turnDiff.complete", |
| 173 | commandId: "cmd-turn-diff-complete", | 146 | commandId: "cmd-turn-diff-complete", |
| ... | @@ -181,7 +154,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -181,7 +154,7 @@ describe("OrchestrationEngine", () => { |
| 181 | }), | 154 | }), |
| 182 | ); | 155 | ); |
| 183 | 156 | ||
| 184 | const firstThread = (await firstSystem.run(engine.getSnapshot())).threads.find( | 157 | const firstThread = (await system.run(engine.getSnapshot())).threads.find( |
| 185 | (thread) => thread.id === "thread-turn-diff", | 158 | (thread) => thread.id === "thread-turn-diff", |
| 186 | ); | 159 | ); |
| 187 | expect(firstThread?.turnDiffSummaries).toEqual([ | 160 | expect(firstThread?.turnDiffSummaries).toEqual([ |
| ... | @@ -193,31 +166,14 @@ describe("OrchestrationEngine", () => { | ... | @@ -193,31 +166,14 @@ describe("OrchestrationEngine", () => { |
| 193 | checkpointTurnCount: 1, | 166 | checkpointTurnCount: 1, |
| 194 | }, | 167 | }, |
| 195 | ]); | 168 | ]); |
| 196 | await firstSystem.dispose(); | 169 | await system.dispose(); |
| 197 | |||
| 198 | const secondSystem = await createOrchestrationSystem(stateDir); | ||
| 199 | const restartedEngine = secondSystem.engine; | ||
| 200 | const restartedThread = (await secondSystem.run(restartedEngine.getSnapshot())).threads.find( | ||
| 201 | (thread) => thread.id === "thread-turn-diff", | ||
| 202 | ); | ||
| 203 | expect(restartedThread?.turnDiffSummaries).toEqual([ | ||
| 204 | { | ||
| 205 | turnId: "turn-1", | ||
| 206 | completedAt, | ||
| 207 | status: "completed", | ||
| 208 | files: [], | ||
| 209 | checkpointTurnCount: 1, | ||
| 210 | }, | ||
| 211 | ]); | ||
| 212 | await secondSystem.dispose(); | ||
| 213 | }); | 170 | }); |
| 214 | 171 | ||
| 215 | it("reverts thread messages and turn summaries to a checkpoint", async () => { | 172 | it("reverts thread messages and turn summaries to a checkpoint", async () => { |
| 216 | const stateDir = makeTempDir("t3code-orchestration-revert-"); | 173 | const system = await createOrchestrationSystem(); |
| 217 | const firstSystem = await createOrchestrationSystem(stateDir); | 174 | const engine = system.engine; |
| 218 | const engine = firstSystem.engine; | ||
| 219 | const createdAt = new Date().toISOString(); | 175 | const createdAt = new Date().toISOString(); |
| 220 | await firstSystem.run( | 176 | await system.run( |
| 221 | engine.dispatch({ | 177 | engine.dispatch({ |
| 222 | type: "thread.create", | 178 | type: "thread.create", |
| 223 | commandId: "cmd-thread-revert", | 179 | commandId: "cmd-thread-revert", |
| ... | @@ -230,7 +186,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -230,7 +186,7 @@ describe("OrchestrationEngine", () => { |
| 230 | createdAt, | 186 | createdAt, |
| 231 | }), | 187 | }), |
| 232 | ); | 188 | ); |
| 233 | await firstSystem.run( | 189 | await system.run( |
| 234 | engine.dispatch({ | 190 | engine.dispatch({ |
| 235 | type: "message.send", | 191 | type: "message.send", |
| 236 | commandId: "cmd-msg-1-user", | 192 | commandId: "cmd-msg-1-user", |
| ... | @@ -242,7 +198,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -242,7 +198,7 @@ describe("OrchestrationEngine", () => { |
| 242 | createdAt, | 198 | createdAt, |
| 243 | }), | 199 | }), |
| 244 | ); | 200 | ); |
| 245 | await firstSystem.run( | 201 | await system.run( |
| 246 | engine.dispatch({ | 202 | engine.dispatch({ |
| 247 | type: "message.send", | 203 | type: "message.send", |
| 248 | commandId: "cmd-msg-1-assistant", | 204 | commandId: "cmd-msg-1-assistant", |
| ... | @@ -254,7 +210,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -254,7 +210,7 @@ describe("OrchestrationEngine", () => { |
| 254 | createdAt, | 210 | createdAt, |
| 255 | }), | 211 | }), |
| 256 | ); | 212 | ); |
| 257 | await firstSystem.run( | 213 | await system.run( |
| 258 | engine.dispatch({ | 214 | engine.dispatch({ |
| 259 | type: "thread.turnDiff.complete", | 215 | type: "thread.turnDiff.complete", |
| 260 | commandId: "cmd-turn-1-complete", | 216 | commandId: "cmd-turn-1-complete", |
| ... | @@ -270,7 +226,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -270,7 +226,7 @@ describe("OrchestrationEngine", () => { |
| 270 | ); | 226 | ); |
| 271 | 227 | ||
| 272 | const createdAtSecond = new Date(Date.now() + 1_000).toISOString(); | 228 | const createdAtSecond = new Date(Date.now() + 1_000).toISOString(); |
| 273 | await firstSystem.run( | 229 | await system.run( |
| 274 | engine.dispatch({ | 230 | engine.dispatch({ |
| 275 | type: "message.send", | 231 | type: "message.send", |
| 276 | commandId: "cmd-msg-2-user", | 232 | commandId: "cmd-msg-2-user", |
| ... | @@ -282,7 +238,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -282,7 +238,7 @@ describe("OrchestrationEngine", () => { |
| 282 | createdAt: createdAtSecond, | 238 | createdAt: createdAtSecond, |
| 283 | }), | 239 | }), |
| 284 | ); | 240 | ); |
| 285 | await firstSystem.run( | 241 | await system.run( |
| 286 | engine.dispatch({ | 242 | engine.dispatch({ |
| 287 | type: "message.send", | 243 | type: "message.send", |
| 288 | commandId: "cmd-msg-2-assistant", | 244 | commandId: "cmd-msg-2-assistant", |
| ... | @@ -294,7 +250,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -294,7 +250,7 @@ describe("OrchestrationEngine", () => { |
| 294 | createdAt: createdAtSecond, | 250 | createdAt: createdAtSecond, |
| 295 | }), | 251 | }), |
| 296 | ); | 252 | ); |
| 297 | await firstSystem.run( | 253 | await system.run( |
| 298 | engine.dispatch({ | 254 | engine.dispatch({ |
| 299 | type: "thread.turnDiff.complete", | 255 | type: "thread.turnDiff.complete", |
| 300 | commandId: "cmd-turn-2-complete", | 256 | commandId: "cmd-turn-2-complete", |
| ... | @@ -309,7 +265,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -309,7 +265,7 @@ describe("OrchestrationEngine", () => { |
| 309 | }), | 265 | }), |
| 310 | ); | 266 | ); |
| 311 | 267 | ||
| 312 | await firstSystem.run( | 268 | await system.run( |
| 313 | engine.dispatch({ | 269 | engine.dispatch({ |
| 314 | type: "thread.revert", | 270 | type: "thread.revert", |
| 315 | commandId: "cmd-thread-revert-apply", | 271 | commandId: "cmd-thread-revert-apply", |
| ... | @@ -320,25 +276,13 @@ describe("OrchestrationEngine", () => { | ... | @@ -320,25 +276,13 @@ describe("OrchestrationEngine", () => { |
| 320 | }), | 276 | }), |
| 321 | ); | 277 | ); |
| 322 | 278 | ||
| 323 | const thread = (await firstSystem.run(engine.getSnapshot())).threads.find( | 279 | const thread = (await system.run(engine.getSnapshot())).threads.find( |
| 324 | (entry) => entry.id === "thread-revert", | 280 | (entry) => entry.id === "thread-revert", |
| 325 | ); | 281 | ); |
| 326 | expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]); | 282 | expect(thread?.messages.map((message) => message.id)).toEqual(["user-1", "assistant:turn-1"]); |
| 327 | expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]); | 283 | expect(thread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]); |
| 328 | expect(thread?.latestTurnId).toBe("turn-1"); | 284 | expect(thread?.latestTurnId).toBe("turn-1"); |
| 329 | await firstSystem.dispose(); | 285 | await system.dispose(); |
| 330 | |||
| 331 | const secondSystem = await createOrchestrationSystem(stateDir); | ||
| 332 | const restarted = secondSystem.engine; | ||
| 333 | const restartedThread = (await secondSystem.run(restarted.getSnapshot())).threads.find( | ||
| 334 | (entry) => entry.id === "thread-revert", | ||
| 335 | ); | ||
| 336 | expect(restartedThread?.messages.map((message) => message.id)).toEqual([ | ||
| 337 | "user-1", | ||
| 338 | "assistant:turn-1", | ||
| 339 | ]); | ||
| 340 | expect(restartedThread?.turnDiffSummaries.map((summary) => summary.turnId)).toEqual(["turn-1"]); | ||
| 341 | await secondSystem.dispose(); | ||
| 342 | }); | 286 | }); |
| 343 | 287 | ||
| 344 | it("allows stop to be called multiple times", async () => { | 288 | it("allows stop to be called multiple times", async () => { |
apps/server/src/persistence/Layers/Checkpoints.test.ts+4-17| ... | @@ -1,29 +1,16 @@ | ... | @@ -1,29 +1,16 @@ |
| 1 | import { mkdtemp, rm } from "node:fs/promises"; | ||
| 2 | import os from "node:os"; | ||
| 3 | import path from "node:path"; | ||
| 4 | |||
| 5 | import { it, assert } from "@effect/vitest"; | 1 | import { it, assert } from "@effect/vitest"; |
| 6 | import { Effect, Layer, Option } from "effect"; | 2 | import { Effect, Layer, Option } from "effect"; |
| 7 | 3 | ||
| 8 | import { CheckpointRepository } from "../Services/Checkpoints.ts"; | 4 | import { CheckpointRepository } from "../Services/Checkpoints.ts"; |
| 9 | import { CheckpointRepositoryLive } from "./Checkpoints.ts"; | 5 | import { CheckpointRepositoryLive } from "./Checkpoints.ts"; |
| 10 | import { makeSqlitePersistenceLive } from "./Sqlite.ts"; | 6 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; |
| 11 | |||
| 12 | const makeTemporaryDirectoryScoped = (prefix: string) => | ||
| 13 | Effect.acquireRelease( | ||
| 14 | Effect.promise(() => mkdtemp(path.join(os.tmpdir(), prefix))), | ||
| 15 | (directory) => Effect.promise(() => rm(directory, { recursive: true, force: true })), | ||
| 16 | ); | ||
| 17 | 7 | ||
| 18 | function makeCheckpointRepositoryLayer(dbPath: string) { | 8 | function makeCheckpointRepositoryLayer() { |
| 19 | return CheckpointRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath))); | 9 | return CheckpointRepositoryLive.pipe(Layer.provide(SqlitePersistenceMemory)); |
| 20 | } | 10 | } |
| 21 | 11 | ||
| 22 | it.effect("CheckpointRepositoryLive upserts, lists, reads, and prunes checkpoint metadata", () => | 12 | it.effect("CheckpointRepositoryLive upserts, lists, reads, and prunes checkpoint metadata", () => |
| 23 | Effect.gen(function* () { | 13 | Effect.gen(function* () { |
| 24 | const stateDir = yield* makeTemporaryDirectoryScoped("t3code-checkpoint-repository-"); | ||
| 25 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 26 | |||
| 27 | yield* Effect.gen(function* () { | 14 | yield* Effect.gen(function* () { |
| 28 | const repository = yield* CheckpointRepository; | 15 | const repository = yield* CheckpointRepository; |
| 29 | 16 | ||
| ... | @@ -90,6 +77,6 @@ it.effect("CheckpointRepositoryLive upserts, lists, reads, and prunes checkpoint | ... | @@ -90,6 +77,6 @@ it.effect("CheckpointRepositoryLive upserts, lists, reads, and prunes checkpoint |
| 90 | yield* repository.deleteAllForSession({ providerSessionId: "sess-1" }); | 77 | yield* repository.deleteAllForSession({ providerSessionId: "sess-1" }); |
| 91 | const empty = yield* repository.listCheckpoints({ providerSessionId: "sess-1" }); | 78 | const empty = yield* repository.listCheckpoints({ providerSessionId: "sess-1" }); |
| 92 | assert.equal(empty.length, 0); | 79 | assert.equal(empty.length, 0); |
| 93 | }).pipe(Effect.provide(makeCheckpointRepositoryLayer(dbPath))); | 80 | }).pipe(Effect.provide(makeCheckpointRepositoryLayer())); |
| 94 | }), | 81 | }), |
| 95 | ); | 82 | ); |
apps/server/src/persistence/Layers/Checkpoints.ts-5| ... | @@ -20,7 +20,6 @@ import { | ... | @@ -20,7 +20,6 @@ import { |
| 20 | CheckpointRepositoryValidationError, | 20 | CheckpointRepositoryValidationError, |
| 21 | type CheckpointRepositoryError, | 21 | type CheckpointRepositoryError, |
| 22 | } from "../Errors.ts"; | 22 | } from "../Errors.ts"; |
| 23 | import { makeSqlitePersistenceLive } from "./Sqlite.ts"; | ||
| 24 | import { | 23 | import { |
| 25 | CheckpointRepository, | 24 | CheckpointRepository, |
| 26 | type CheckpointRepositoryEntry, | 25 | type CheckpointRepositoryEntry, |
| ... | @@ -357,7 +356,3 @@ export const CheckpointRepositoryLive = Layer.effect( | ... | @@ -357,7 +356,3 @@ export const CheckpointRepositoryLive = Layer.effect( |
| 357 | CheckpointRepository, | 356 | CheckpointRepository, |
| 358 | makeCheckpointRepository, | 357 | makeCheckpointRepository, |
| 359 | ); | 358 | ); |
| 360 | |||
| 361 | export function makeSqliteCheckpointRepositoryLive(dbPath: string) { | ||
| 362 | return CheckpointRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath))); | ||
| 363 | } |
apps/server/src/persistence/Layers/OrchestrationEvents.test.ts+26-86| ... | @@ -1,39 +1,25 @@ | ... | @@ -1,39 +1,25 @@ |
| 1 | import fs from "node:fs"; | 1 | import { Effect, Layer } from "effect"; |
| 2 | import os from "node:os"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import path from "node:path"; | ||
| 4 | import { DatabaseSync } from "node:sqlite"; | ||
| 5 | |||
| 6 | import { ManagedRuntime, Effect } from "effect"; | ||
| 7 | import { afterEach, describe, expect, it } from "vitest"; | ||
| 8 | 3 | ||
| 9 | import { OrchestrationEventRepository } from "../Services/OrchestrationEvents.ts"; | 4 | import { OrchestrationEventRepository } from "../Services/OrchestrationEvents.ts"; |
| 10 | import { makeSqliteOrchestrationEventRepositoryLive } from "./OrchestrationEvents.ts"; | 5 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; |
| 11 | 6 | import { OrchestrationEventRepositoryLive } from "./OrchestrationEvents.ts"; | |
| 12 | const tempDirs: string[] = []; | 7 | import { NodeServices } from "@effect/platform-node"; |
| 13 | 8 | ||
| 14 | function makeTempDir(prefix: string): string { | 9 | const layer = it.layer( |
| 15 | const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); | 10 | Layer.mergeAll( |
| 16 | tempDirs.push(dir); | 11 | Layer.provide(OrchestrationEventRepositoryLive, SqlitePersistenceMemory), |
| 17 | return dir; | 12 | NodeServices.layer, |
| 18 | } | 13 | ), |
| 19 | 14 | ); | |
| 20 | afterEach(() => { | 15 | |
| 21 | for (const dir of tempDirs.splice(0, tempDirs.length)) { | 16 | layer("OrchestrationEventRepository", (it) => { |
| 22 | fs.rmSync(dir, { recursive: true, force: true }); | 17 | it.effect("stores and replays events", () => |
| 23 | } | 18 | Effect.gen(function* () { |
| 24 | }); | 19 | const createdAt = new Date().toISOString(); |
| 25 | 20 | ||
| 26 | describe("OrchestrationEventRepository", () => { | 21 | const repository = yield* OrchestrationEventRepository; |
| 27 | it("persists and replays events across restarts", async () => { | 22 | const saved = yield* repository.append({ |
| 28 | const stateDir = makeTempDir("t3code-event-store-"); | ||
| 29 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 30 | |||
| 31 | const createdAt = new Date().toISOString(); | ||
| 32 | |||
| 33 | const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | ||
| 34 | const first = await firstRuntime.runPromise(Effect.service(OrchestrationEventRepository)); | ||
| 35 | const saved = await firstRuntime.runPromise( | ||
| 36 | first.append({ | ||
| 37 | eventId: "event-1", | 23 | eventId: "event-1", |
| 38 | type: "thread.created", | 24 | type: "thread.created", |
| 39 | aggregateType: "thread", | 25 | aggregateType: "thread", |
| ... | @@ -41,57 +27,11 @@ describe("OrchestrationEventRepository", () => { | ... | @@ -41,57 +27,11 @@ describe("OrchestrationEventRepository", () => { |
| 41 | occurredAt: createdAt, | 27 | occurredAt: createdAt, |
| 42 | commandId: "cmd-1", | 28 | commandId: "cmd-1", |
| 43 | payload: { id: "thread-1", projectId: "project-1", title: "demo" }, | 29 | payload: { id: "thread-1", projectId: "project-1", title: "demo" }, |
| 44 | }), | 30 | }); |
| 45 | ); | 31 | assert.equal(saved.sequence, 1); |
| 46 | expect(saved.sequence).toBe(1); | ||
| 47 | await firstRuntime.dispose(); | ||
| 48 | |||
| 49 | const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | ||
| 50 | const second = await secondRuntime.runPromise(Effect.service(OrchestrationEventRepository)); | ||
| 51 | const replayed = await secondRuntime.runPromise(second.readFromSequence(0)); | ||
| 52 | expect(replayed).toEqual([saved]); | ||
| 53 | await secondRuntime.dispose(); | ||
| 54 | }); | ||
| 55 | |||
| 56 | it("creates and reuses the migrator tracking table", async () => { | ||
| 57 | const stateDir = makeTempDir("t3code-event-store-migrations-"); | ||
| 58 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 59 | |||
| 60 | const firstRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | ||
| 61 | const first = await firstRuntime.runPromise(Effect.service(OrchestrationEventRepository)); | ||
| 62 | await firstRuntime.runPromise(first.readAll()); | ||
| 63 | await firstRuntime.dispose(); | ||
| 64 | |||
| 65 | const secondRuntime = ManagedRuntime.make(makeSqliteOrchestrationEventRepositoryLive(dbPath)); | ||
| 66 | const second = await secondRuntime.runPromise(Effect.service(OrchestrationEventRepository)); | ||
| 67 | await secondRuntime.runPromise(second.readAll()); | ||
| 68 | await secondRuntime.dispose(); | ||
| 69 | |||
| 70 | const db = new DatabaseSync(dbPath, { readOnly: true }); | ||
| 71 | const tableRow = db | ||
| 72 | .prepare( | ||
| 73 | ` | ||
| 74 | SELECT name | ||
| 75 | FROM sqlite_master | ||
| 76 | WHERE type = 'table' AND name IN ('effect_sql_migrations', 'orchestration_sql_migrations') | ||
| 77 | ORDER BY name ASC | ||
| 78 | `, | ||
| 79 | ) | ||
| 80 | .get() as { name: string } | undefined; | ||
| 81 | expect(tableRow?.name).toBeDefined(); | ||
| 82 | expect(tableRow).toBeDefined(); | ||
| 83 | if (!tableRow) return; | ||
| 84 | 32 | ||
| 85 | const migrationRows = db | 33 | const replayed = yield* repository.readFromSequence(0); |
| 86 | .prepare( | 34 | assert.deepEqual(replayed, [saved]); |
| 87 | ` | 35 | }), |
| 88 | SELECT * | 36 | ); |
| 89 | FROM ${tableRow.name} | ||
| 90 | `, | ||
| 91 | ) | ||
| 92 | .all() as Array<Record<string, unknown>>; | ||
| 93 | expect(migrationRows.length).toBeGreaterThanOrEqual(1); | ||
| 94 | expect(Object.values(migrationRows[0] ?? {}).length).toBeGreaterThan(0); | ||
| 95 | db.close(); | ||
| 96 | }); | ||
| 97 | }); | 37 | }); |
apps/server/src/persistence/Layers/OrchestrationEvents.ts-5| ... | @@ -11,7 +11,6 @@ import { | ... | @@ -11,7 +11,6 @@ import { |
| 11 | toPersistenceSqlError, | 11 | toPersistenceSqlError, |
| 12 | type OrchestrationEventRepositoryError, | 12 | type OrchestrationEventRepositoryError, |
| 13 | } from "../Errors.ts"; | 13 | } from "../Errors.ts"; |
| 14 | import { makeSqlitePersistenceLive } from "./Sqlite.ts"; | ||
| 15 | import { | 14 | import { |
| 16 | OrchestrationEventRepository, | 15 | OrchestrationEventRepository, |
| 17 | type OrchestrationEventRepositoryShape, | 16 | type OrchestrationEventRepositoryShape, |
| ... | @@ -177,7 +176,3 @@ export const OrchestrationEventRepositoryLive = Layer.effect( | ... | @@ -177,7 +176,3 @@ export const OrchestrationEventRepositoryLive = Layer.effect( |
| 177 | OrchestrationEventRepository, | 176 | OrchestrationEventRepository, |
| 178 | makeRepository, | 177 | makeRepository, |
| 179 | ); | 178 | ); |
| 180 | |||
| 181 | export function makeSqliteOrchestrationEventRepositoryLive(dbPath: string) { | ||
| 182 | return OrchestrationEventRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath))); | ||
| 183 | } |
apps/server/src/persistence/Layers/Projects.test.ts+40-67| ... | @@ -1,83 +1,56 @@ | ... | @@ -1,83 +1,56 @@ |
| 1 | import fs from "node:fs"; | 1 | import { Effect, Layer, FileSystem } from "effect"; |
| 2 | import os from "node:os"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import path from "node:path"; | ||
| 4 | |||
| 5 | import { Effect, Layer, ManagedRuntime } from "effect"; | ||
| 6 | import { afterEach, describe, expect, it } from "vitest"; | ||
| 7 | 3 | ||
| 8 | import { ProjectRepository } from "../Services/Projects.ts"; | 4 | import { ProjectRepository } from "../Services/Projects.ts"; |
| 9 | import { ProjectRepositoryLive } from "./Projects.ts"; | 5 | import { ProjectRepositoryLive } from "./Projects.ts"; |
| 10 | import { makeSqlitePersistenceLive } from "./Sqlite.ts"; | 6 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; |
| 11 | 7 | import { NodeServices } from "@effect/platform-node"; | |
| 12 | function makeProjectRepositoryTest(dbPath: string) { | ||
| 13 | return ProjectRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath))); | ||
| 14 | } | ||
| 15 | |||
| 16 | const tempDirs: string[] = []; | ||
| 17 | |||
| 18 | function makeTempDir(prefix: string): string { | ||
| 19 | const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); | ||
| 20 | tempDirs.push(dir); | ||
| 21 | return dir; | ||
| 22 | } | ||
| 23 | |||
| 24 | afterEach(() => { | ||
| 25 | for (const dir of tempDirs.splice(0, tempDirs.length)) { | ||
| 26 | fs.rmSync(dir, { recursive: true, force: true }); | ||
| 27 | } | ||
| 28 | }); | ||
| 29 | |||
| 30 | describe("ProjectRepository", () => { | ||
| 31 | it("persists projects and deduplicates adds by cwd", async () => { | ||
| 32 | const stateDir = makeTempDir("t3code-project-repo-state-"); | ||
| 33 | const projectDir = makeTempDir("t3code-project-repo-project-"); | ||
| 34 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 35 | |||
| 36 | const firstRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); | ||
| 37 | const first = await firstRuntime.runPromise(Effect.service(ProjectRepository)); | ||
| 38 | |||
| 39 | const created = await firstRuntime.runPromise(first.add({ cwd: projectDir })); | ||
| 40 | expect(created.created).toBe(true); | ||
| 41 | |||
| 42 | const duplicate = await firstRuntime.runPromise(first.add({ cwd: projectDir })); | ||
| 43 | expect(duplicate.created).toBe(false); | ||
| 44 | expect(duplicate.project.id).toBe(created.project.id); | ||
| 45 | 8 | ||
| 46 | await firstRuntime.dispose(); | 9 | const layer = it.layer( |
| 10 | Layer.mergeAll(Layer.provide(ProjectRepositoryLive, SqlitePersistenceMemory), NodeServices.layer), | ||
| 11 | ); | ||
| 47 | 12 | ||
| 48 | const secondRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); | 13 | layer("ProjectRepository", (it) => { |
| 49 | const second = await secondRuntime.runPromise(Effect.service(ProjectRepository)); | 14 | it.effect("stores projects and deduplicates adds by cwd", () => |
| 50 | const listed = await secondRuntime.runPromise(second.list()); | 15 | Effect.gen(function* () { |
| 16 | const fs = yield* FileSystem.FileSystem; | ||
| 17 | const projectDir = yield* fs.makeTempDirectoryScoped(); | ||
| 51 | 18 | ||
| 52 | expect(listed).toHaveLength(1); | 19 | const repository = yield* ProjectRepository; |
| 53 | expect(listed[0]?.cwd).toBe(projectDir); | ||
| 54 | 20 | ||
| 55 | await secondRuntime.dispose(); | 21 | const created = yield* repository.add({ cwd: projectDir }); |
| 56 | }); | 22 | assert.equal(created.created, true); |
| 57 | 23 | ||
| 58 | it("prunes missing project paths", async () => { | 24 | const duplicate = yield* repository.add({ cwd: projectDir }); |
| 59 | const stateDir = makeTempDir("t3code-project-repo-prune-state-"); | 25 | assert.equal(duplicate.created, false); |
| 60 | const existing = makeTempDir("t3code-project-repo-prune-existing-"); | 26 | assert.equal(duplicate.project.id, created.project.id); |
| 61 | const missing = makeTempDir("t3code-project-repo-prune-missing-"); | ||
| 62 | const dbPath = path.join(stateDir, "orchestration.sqlite"); | ||
| 63 | 27 | ||
| 64 | const runtime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); | 28 | const listed = yield* repository.list(); |
| 65 | const repository = await runtime.runPromise(Effect.service(ProjectRepository)); | 29 | assert.equal(listed.length, 1); |
| 30 | assert.equal(listed[0]?.cwd, projectDir); | ||
| 31 | }), | ||
| 32 | ); | ||
| 66 | 33 | ||
| 67 | const existingProject = await runtime.runPromise(repository.add({ cwd: existing })); | 34 | it.effect("prunes missing project paths", () => |
| 68 | const missingProject = await runtime.runPromise(repository.add({ cwd: missing })); | 35 | Effect.gen(function* () { |
| 36 | const fs = yield* FileSystem.FileSystem; | ||
| 37 | const existing = yield* fs.makeTempDirectoryScoped(); | ||
| 38 | const missing = yield* fs.makeTempDirectory(); | ||
| 69 | 39 | ||
| 70 | expect(existingProject.created).toBe(true); | 40 | const repository = yield* ProjectRepository; |
| 71 | expect(missingProject.created).toBe(true); | ||
| 72 | 41 | ||
| 73 | fs.rmSync(missing, { recursive: true, force: true }); | 42 | const existingProject = yield* repository.add({ cwd: existing }); |
| 43 | const missingProject = yield* repository.add({ cwd: missing }); | ||
| 74 | 44 | ||
| 75 | await runtime.runPromise(repository.pruneMissing()); | 45 | assert.isTrue(existingProject.created); |
| 76 | const listed = await runtime.runPromise(repository.list()); | 46 | assert.isTrue(missingProject.created); |
| 77 | 47 | ||
| 78 | expect(listed).toHaveLength(1); | 48 | yield* fs.remove(missing, { recursive: true, force: true }); |
| 79 | expect(listed[0]?.cwd).toBe(existing); | ||
| 80 | 49 | ||
| 81 | await runtime.dispose(); | 50 | yield* repository.pruneMissing(); |
| 82 | }); | 51 | const listed = yield* repository.list(); |
| 52 | assert.equal(listed.length, 1); | ||
| 53 | assert.equal(listed[0]?.cwd, existing); | ||
| 54 | }), | ||
| 55 | ); | ||
| 83 | }); | 56 | }); |
apps/server/src/persistence/Layers/Sqlite.ts+18-35| ... | @@ -1,41 +1,24 @@ | ... | @@ -1,41 +1,24 @@ |
| 1 | import fs from "node:fs"; | 1 | import { Effect, Layer, FileSystem, Path } from "effect"; |
| 2 | import path from "node:path"; | ||
| 3 | |||
| 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 5 | import * as SqliteClient from "@effect/sql-sqlite-node/SqliteClient"; | ||
| 6 | import { Effect, Layer, ServiceMap } from "effect"; | ||
| 7 | 2 | ||
| 8 | import { runMigrations } from "../Migrations.ts"; | 3 | import { runMigrations } from "../Migrations.ts"; |
| 4 | import * as SqliteClient from "../SqliteClient.ts"; | ||
| 9 | 5 | ||
| 10 | export interface PersistenceConfigShape { | 6 | const setup = Layer.effectDiscard( |
| 11 | readonly dbPath: string; | 7 | Effect.gen(function* () { |
| 12 | } | 8 | const sql = yield* SqliteClient.SqliteClient; |
| 13 | 9 | yield* sql`PRAGMA journal_mode = WAL;`; | |
| 14 | export class PersistenceConfig extends ServiceMap.Service< | 10 | yield* sql`PRAGMA foreign_keys = ON;`; |
| 15 | PersistenceConfig, | 11 | yield* runMigrations; |
| 16 | PersistenceConfigShape | 12 | }), |
| 17 | >()("persistence/Config") {} | 13 | ); |
| 18 | 14 | ||
| 19 | export function makeSqlitePersistenceLive(dbPath: string) { | 15 | export const makeSqlitePersistenceLive = (dbPath: string) => |
| 20 | const SqliteClientLive = Layer.unwrap( | 16 | Effect.gen(function* () { |
| 21 | Effect.sync(() => { | 17 | const fs = yield* FileSystem.FileSystem; |
| 22 | fs.mkdirSync(path.dirname(dbPath), { recursive: true }); | 18 | const path = yield* Path.Path; |
| 23 | return SqliteClient.layer({ filename: dbPath }); | 19 | yield* fs.makeDirectory(path.dirname(dbPath), { recursive: true }); |
| 24 | }), | ||
| 25 | ); | ||
| 26 | 20 | ||
| 27 | return Layer.effect( | 21 | return Layer.provideMerge(setup, SqliteClient.layer({ filename: dbPath })); |
| 28 | SqlClient.SqlClient, | 22 | }).pipe(Layer.unwrap); |
| 29 | Effect.gen(function* () { | ||
| 30 | const sql = yield* SqliteClient.SqliteClient; | ||
| 31 | yield* sql`PRAGMA journal_mode = WAL;`; | ||
| 32 | yield* sql`PRAGMA foreign_keys = ON;`; | ||
| 33 | yield* runMigrations; | ||
| 34 | return sql; | ||
| 35 | }), | ||
| 36 | ).pipe(Layer.provide(SqliteClientLive)); | ||
| 37 | } | ||
| 38 | 23 | ||
| 39 | export const SqlitePersistenceLive = Layer.unwrap( | 24 | export const SqlitePersistenceMemory = Layer.provideMerge(setup, SqliteClient.layerMemory()); |
| 40 | Effect.map(Effect.service(PersistenceConfig), ({ dbPath }) => makeSqlitePersistenceLive(dbPath)), | ||
| 41 | ); |
apps/server/src/persistence/Migrations.ts+1-1| ... | @@ -59,7 +59,7 @@ export const runMigrations = run({ loader }); | ... | @@ -59,7 +59,7 @@ export const runMigrations = run({ loader }); |
| 59 | * @example | 59 | * @example |
| 60 | * ```typescript | 60 | * ```typescript |
| 61 | * import { MigrationsLive } from "@acme/db/Migrations" | 61 | * import { MigrationsLive } from "@acme/db/Migrations" |
| 62 | * import { SqliteClient } from "@effect/sql-sqlite-node" | 62 | * import * as SqliteClient from "@acme/db/SqliteClient" |
| 63 | * | 63 | * |
| 64 | * // Migrations run automatically when SqliteClient is provided | 64 | * // Migrations run automatically when SqliteClient is provided |
| 65 | * const AppLayer = MigrationsLive.pipe( | 65 | * const AppLayer = MigrationsLive.pipe( |
apps/server/src/persistence/SqliteClient.test.ts created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | import { assert, it } from "@effect/vitest"; | ||
| 2 | import { Effect } from "effect"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 4 | |||
| 5 | import * as SqliteClient from "./SqliteClient.ts"; | ||
| 6 | |||
| 7 | it.effect("runs prepared queries and returns positional values", () => | ||
| 8 | Effect.gen(function* () { | ||
| 9 | const sql = yield* SqlClient.SqlClient; | ||
| 10 | |||
| 11 | yield* sql`CREATE TABLE entries(id INTEGER PRIMARY KEY, name TEXT NOT NULL)`; | ||
| 12 | yield* sql`INSERT INTO entries(name) VALUES (${"alpha"}), (${"beta"})`; | ||
| 13 | |||
| 14 | const rows = yield* sql<{ readonly id: number; readonly name: string }>` | ||
| 15 | SELECT id, name FROM entries ORDER BY id | ||
| 16 | `; | ||
| 17 | assert.equal(rows.length, 2); | ||
| 18 | assert.equal(rows[0]?.name, "alpha"); | ||
| 19 | assert.equal(rows[1]?.name, "beta"); | ||
| 20 | |||
| 21 | const values = yield* sql`SELECT id, name FROM entries ORDER BY id`.values; | ||
| 22 | assert.equal(values.length, 2); | ||
| 23 | assert.equal(values[0]?.[1], "alpha"); | ||
| 24 | assert.equal(values[1]?.[1], "beta"); | ||
| 25 | }).pipe(Effect.provide(SqliteClient.layerMemory())), | ||
| 26 | ); | ||
apps/server/src/persistence/SqliteClient.ts created+242| ... | @@ -0,0 +1,242 @@ | ||
| 1 | /** | ||
| 2 | * Port of `@effect/sql-sqlite-node` that uses the native `node:sqlite` | ||
| 3 | * bindings instead of `better-sqlite3`. | ||
| 4 | */ | ||
| 5 | import { DatabaseSync, type StatementSync } from "node:sqlite"; | ||
| 6 | |||
| 7 | import * as Cache from "effect/Cache"; | ||
| 8 | import * as Config from "effect/Config"; | ||
| 9 | import * as Duration from "effect/Duration"; | ||
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Fiber from "effect/Fiber"; | ||
| 12 | import { identity } from "effect/Function"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Scope from "effect/Scope"; | ||
| 15 | import * as Semaphore from "effect/Semaphore"; | ||
| 16 | import * as ServiceMap from "effect/ServiceMap"; | ||
| 17 | import * as Stream from "effect/Stream"; | ||
| 18 | import * as Reactivity from "effect/unstable/reactivity/Reactivity"; | ||
| 19 | import * as Client from "effect/unstable/sql/SqlClient"; | ||
| 20 | import type { Connection } from "effect/unstable/sql/SqlConnection"; | ||
| 21 | import { SqlError } from "effect/unstable/sql/SqlError"; | ||
| 22 | import * as Statement from "effect/unstable/sql/Statement"; | ||
| 23 | |||
| 24 | const ATTR_DB_SYSTEM_NAME = "db.system.name"; | ||
| 25 | |||
| 26 | export const TypeId: TypeId = "~local/sqlite-node/SqliteClient"; | ||
| 27 | |||
| 28 | export type TypeId = "~local/sqlite-node/SqliteClient"; | ||
| 29 | |||
| 30 | export const SqliteClient = ServiceMap.Service<Client.SqlClient>("@local/sqlite-node/SqliteClient"); | ||
| 31 | |||
| 32 | export interface SqliteClientConfig { | ||
| 33 | readonly filename: string; | ||
| 34 | readonly readonly?: boolean | undefined; | ||
| 35 | readonly allowExtension?: boolean | undefined; | ||
| 36 | readonly prepareCacheSize?: number | undefined; | ||
| 37 | readonly prepareCacheTTL?: Duration.Input | undefined; | ||
| 38 | readonly disableWAL?: boolean | undefined; | ||
| 39 | readonly spanAttributes?: Record<string, unknown> | undefined; | ||
| 40 | readonly transformResultNames?: ((str: string) => string) | undefined; | ||
| 41 | readonly transformQueryNames?: ((str: string) => string) | undefined; | ||
| 42 | } | ||
| 43 | |||
| 44 | export interface SqliteMemoryClientConfig extends Omit< | ||
| 45 | SqliteClientConfig, | ||
| 46 | "filename" | "readonly" | ||
| 47 | > {} | ||
| 48 | |||
| 49 | const makeWithDatabase = ( | ||
| 50 | options: SqliteClientConfig, | ||
| 51 | openDatabase: () => DatabaseSync, | ||
| 52 | ): Effect.Effect<Client.SqlClient, never, Scope.Scope | Reactivity.Reactivity> => | ||
| 53 | Effect.gen(function* () { | ||
| 54 | const compiler = Statement.makeCompilerSqlite(options.transformQueryNames); | ||
| 55 | const transformRows = options.transformResultNames | ||
| 56 | ? Statement.defaultTransforms(options.transformResultNames).array | ||
| 57 | : undefined; | ||
| 58 | |||
| 59 | const makeConnection = Effect.gen(function* () { | ||
| 60 | const scope = yield* Effect.scope; | ||
| 61 | const db = openDatabase(); | ||
| 62 | yield* Scope.addFinalizer( | ||
| 63 | scope, | ||
| 64 | Effect.sync(() => db.close()), | ||
| 65 | ); | ||
| 66 | |||
| 67 | if (options.disableWAL !== true) { | ||
| 68 | db.exec("PRAGMA journal_mode = WAL"); | ||
| 69 | } | ||
| 70 | |||
| 71 | const statementReaderCache = new WeakMap<StatementSync, boolean>(); | ||
| 72 | const hasRows = (statement: StatementSync): boolean => { | ||
| 73 | const cached = statementReaderCache.get(statement); | ||
| 74 | if (cached !== undefined) { | ||
| 75 | return cached; | ||
| 76 | } | ||
| 77 | const value = statement.columns().length > 0; | ||
| 78 | statementReaderCache.set(statement, value); | ||
| 79 | return value; | ||
| 80 | }; | ||
| 81 | |||
| 82 | const prepareCache = yield* Cache.make({ | ||
| 83 | capacity: options.prepareCacheSize ?? 200, | ||
| 84 | timeToLive: options.prepareCacheTTL ?? Duration.minutes(10), | ||
| 85 | lookup: (sql: string) => | ||
| 86 | Effect.try({ | ||
| 87 | try: () => db.prepare(sql), | ||
| 88 | catch: (cause) => new SqlError({ cause, message: "Failed to prepare statement" }), | ||
| 89 | }), | ||
| 90 | }); | ||
| 91 | |||
| 92 | const runStatement = ( | ||
| 93 | statement: StatementSync, | ||
| 94 | params: ReadonlyArray<unknown>, | ||
| 95 | raw: boolean, | ||
| 96 | ) => | ||
| 97 | Effect.withFiber<ReadonlyArray<any>, SqlError>((fiber) => { | ||
| 98 | statement.setReadBigInts(Boolean(ServiceMap.get(fiber.services, Client.SafeIntegers))); | ||
| 99 | try { | ||
| 100 | if (hasRows(statement)) { | ||
| 101 | return Effect.succeed(statement.all(...(params as any))); | ||
| 102 | } | ||
| 103 | const result = statement.run(...(params as any)); | ||
| 104 | return Effect.succeed(raw ? (result as unknown as ReadonlyArray<any>) : []); | ||
| 105 | } catch (cause) { | ||
| 106 | return Effect.fail(new SqlError({ cause, message: "Failed to execute statement" })); | ||
| 107 | } | ||
| 108 | }); | ||
| 109 | |||
| 110 | const run = (sql: string, params: ReadonlyArray<unknown>, raw = false) => | ||
| 111 | Effect.flatMap(Cache.get(prepareCache, sql), (s) => runStatement(s, params, raw)); | ||
| 112 | |||
| 113 | const runValues = (sql: string, params: ReadonlyArray<unknown>) => | ||
| 114 | Effect.acquireUseRelease( | ||
| 115 | Cache.get(prepareCache, sql), | ||
| 116 | (statement) => | ||
| 117 | Effect.try({ | ||
| 118 | try: () => { | ||
| 119 | if (hasRows(statement)) { | ||
| 120 | statement.setReturnArrays(true); | ||
| 121 | // Safe to cast to array after we've setReturnArrays(true) | ||
| 122 | return statement.all(...(params as any)) as unknown as ReadonlyArray< | ||
| 123 | ReadonlyArray<unknown> | ||
| 124 | >; | ||
| 125 | } | ||
| 126 | statement.run(...(params as any)); | ||
| 127 | return []; | ||
| 128 | }, | ||
| 129 | catch: (cause) => new SqlError({ cause, message: "Failed to execute statement" }), | ||
| 130 | }), | ||
| 131 | (statement) => | ||
| 132 | Effect.sync(() => { | ||
| 133 | if (hasRows(statement)) { | ||
| 134 | statement.setReturnArrays(false); | ||
| 135 | } | ||
| 136 | }), | ||
| 137 | ); | ||
| 138 | |||
| 139 | return identity<Connection>({ | ||
| 140 | execute(sql, params, rowTransform) { | ||
| 141 | return rowTransform ? Effect.map(run(sql, params), rowTransform) : run(sql, params); | ||
| 142 | }, | ||
| 143 | executeRaw(sql, params) { | ||
| 144 | return run(sql, params, true); | ||
| 145 | }, | ||
| 146 | executeValues(sql, params) { | ||
| 147 | return runValues(sql, params); | ||
| 148 | }, | ||
| 149 | executeUnprepared(sql, params, rowTransform) { | ||
| 150 | const effect = runStatement(db.prepare(sql), params ?? [], false); | ||
| 151 | return rowTransform ? Effect.map(effect, rowTransform) : effect; | ||
| 152 | }, | ||
| 153 | executeStream(_sql, _params) { | ||
| 154 | return Stream.die("executeStream not implemented"); | ||
| 155 | }, | ||
| 156 | }); | ||
| 157 | }); | ||
| 158 | |||
| 159 | const semaphore = yield* Semaphore.make(1); | ||
| 160 | const connection = yield* makeConnection; | ||
| 161 | |||
| 162 | const acquirer = semaphore.withPermits(1)(Effect.succeed(connection)); | ||
| 163 | const transactionAcquirer = Effect.uninterruptibleMask((restore) => { | ||
| 164 | const fiber = Fiber.getCurrent()!; | ||
| 165 | const scope = ServiceMap.getUnsafe(fiber.services, Scope.Scope); | ||
| 166 | return Effect.as( | ||
| 167 | Effect.tap(restore(semaphore.take(1)), () => | ||
| 168 | Scope.addFinalizer(scope, semaphore.release(1)), | ||
| 169 | ), | ||
| 170 | connection, | ||
| 171 | ); | ||
| 172 | }); | ||
| 173 | |||
| 174 | return yield* Client.make({ | ||
| 175 | acquirer, | ||
| 176 | compiler, | ||
| 177 | transactionAcquirer, | ||
| 178 | spanAttributes: [ | ||
| 179 | ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []), | ||
| 180 | [ATTR_DB_SYSTEM_NAME, "sqlite"], | ||
| 181 | ], | ||
| 182 | transformRows, | ||
| 183 | }); | ||
| 184 | }); | ||
| 185 | |||
| 186 | const make = ( | ||
| 187 | options: SqliteClientConfig, | ||
| 188 | ): Effect.Effect<Client.SqlClient, never, Scope.Scope | Reactivity.Reactivity> => | ||
| 189 | makeWithDatabase( | ||
| 190 | options, | ||
| 191 | () => | ||
| 192 | new DatabaseSync(options.filename, { | ||
| 193 | readOnly: options.readonly ?? false, | ||
| 194 | allowExtension: options.allowExtension ?? false, | ||
| 195 | }), | ||
| 196 | ); | ||
| 197 | |||
| 198 | const makeMemory = ( | ||
| 199 | config: SqliteMemoryClientConfig = {}, | ||
| 200 | ): Effect.Effect<Client.SqlClient, never, Scope.Scope | Reactivity.Reactivity> => | ||
| 201 | makeWithDatabase( | ||
| 202 | { | ||
| 203 | ...config, | ||
| 204 | filename: ":memory:", | ||
| 205 | readonly: false, | ||
| 206 | disableWAL: config.disableWAL ?? true, | ||
| 207 | }, | ||
| 208 | () => { | ||
| 209 | const database = new DatabaseSync(":memory:", { | ||
| 210 | allowExtension: config.allowExtension ?? false, | ||
| 211 | }); | ||
| 212 | return database; | ||
| 213 | }, | ||
| 214 | ); | ||
| 215 | |||
| 216 | export const layerConfig = ( | ||
| 217 | config: Config.Wrap<SqliteClientConfig>, | ||
| 218 | ): Layer.Layer<Client.SqlClient, Config.ConfigError> => | ||
| 219 | Layer.effectServices( | ||
| 220 | Config.unwrap(config) | ||
| 221 | .asEffect() | ||
| 222 | .pipe( | ||
| 223 | Effect.flatMap(make), | ||
| 224 | Effect.map((client) => | ||
| 225 | ServiceMap.make(SqliteClient, client).pipe(ServiceMap.add(Client.SqlClient, client)), | ||
| 226 | ), | ||
| 227 | ), | ||
| 228 | ).pipe(Layer.provide(Reactivity.layer)); | ||
| 229 | |||
| 230 | export const layer = (config: SqliteClientConfig): Layer.Layer<Client.SqlClient> => | ||
| 231 | Layer.effectServices( | ||
| 232 | Effect.map(make(config), (client) => | ||
| 233 | ServiceMap.make(SqliteClient, client).pipe(ServiceMap.add(Client.SqlClient, client)), | ||
| 234 | ), | ||
| 235 | ).pipe(Layer.provide(Reactivity.layer)); | ||
| 236 | |||
| 237 | export const layerMemory = (config: SqliteMemoryClientConfig = {}): Layer.Layer<Client.SqlClient> => | ||
| 238 | Layer.effectServices( | ||
| 239 | Effect.map(makeMemory(config), (client) => | ||
| 240 | ServiceMap.make(SqliteClient, client).pipe(ServiceMap.add(Client.SqlClient, client)), | ||
| 241 | ), | ||
| 242 | ).pipe(Layer.provide(Reactivity.layer)); | ||
apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+11-7| ... | @@ -6,8 +6,9 @@ import { Effect, Layer } from "effect"; | ... | @@ -6,8 +6,9 @@ import { Effect, Layer } from "effect"; |
| 6 | 6 | ||
| 7 | import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts"; | 7 | import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts"; |
| 8 | import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts"; | 8 | import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts"; |
| 9 | import { makeProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts"; | 9 | import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts"; |
| 10 | import { ProviderUnsupportedError } from "../Errors.ts"; | 10 | import { ProviderUnsupportedError } from "../Errors.ts"; |
| 11 | import { NodeServices } from "@effect/platform-node"; | ||
| 11 | 12 | ||
| 12 | const fakeCodexAdapter: CodexAdapterShape = { | 13 | const fakeCodexAdapter: CodexAdapterShape = { |
| 13 | provider: "codex", | 14 | provider: "codex", |
| ... | @@ -25,13 +26,14 @@ const fakeCodexAdapter: CodexAdapterShape = { | ... | @@ -25,13 +26,14 @@ const fakeCodexAdapter: CodexAdapterShape = { |
| 25 | }; | 26 | }; |
| 26 | 27 | ||
| 27 | const layer = it.layer( | 28 | const layer = it.layer( |
| 28 | makeProviderAdapterRegistryLive().pipe( | 29 | Layer.mergeAll( |
| 29 | Layer.provide(Layer.succeed(CodexAdapter, fakeCodexAdapter)), | 30 | Layer.provide(ProviderAdapterRegistryLive, Layer.succeed(CodexAdapter, fakeCodexAdapter)), |
| 31 | NodeServices.layer, | ||
| 30 | ), | 32 | ), |
| 31 | ); | 33 | ); |
| 32 | 34 | ||
| 33 | layer("ProviderAdapterRegistryLive", (it) => { | 35 | layer("ProviderAdapterRegistryLive", (it) => { |
| 34 | it("resolves a registered provider adapter", () => | 36 | it.effect("resolves a registered provider adapter", () => |
| 35 | Effect.gen(function* () { | 37 | Effect.gen(function* () { |
| 36 | const registry = yield* ProviderAdapterRegistry; | 38 | const registry = yield* ProviderAdapterRegistry; |
| 37 | const adapter = yield* registry.getByProvider("codex"); | 39 | const adapter = yield* registry.getByProvider("codex"); |
| ... | @@ -39,12 +41,14 @@ layer("ProviderAdapterRegistryLive", (it) => { | ... | @@ -39,12 +41,14 @@ layer("ProviderAdapterRegistryLive", (it) => { |
| 39 | 41 | ||
| 40 | const providers = yield* registry.listProviders(); | 42 | const providers = yield* registry.listProviders(); |
| 41 | assert.deepEqual(providers, ["codex"]); | 43 | assert.deepEqual(providers, ["codex"]); |
| 42 | })); | 44 | }), |
| 45 | ); | ||
| 43 | 46 | ||
| 44 | it("fails with ProviderUnsupportedError for unknown providers", () => | 47 | it.effect("fails with ProviderUnsupportedError for unknown providers", () => |
| 45 | Effect.gen(function* () { | 48 | Effect.gen(function* () { |
| 46 | const registry = yield* ProviderAdapterRegistry; | 49 | const registry = yield* ProviderAdapterRegistry; |
| 47 | const adapter = yield* registry.getByProvider("unknown" as ProviderKind).pipe(Effect.result); | 50 | const adapter = yield* registry.getByProvider("unknown" as ProviderKind).pipe(Effect.result); |
| 48 | assertFailure(adapter, new ProviderUnsupportedError({ provider: "unknown" })); | 51 | assertFailure(adapter, new ProviderUnsupportedError({ provider: "unknown" })); |
| 49 | })); | 52 | }), |
| 53 | ); | ||
| 50 | }); | 54 | }); |
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts-4| ... | @@ -51,7 +51,3 @@ export const ProviderAdapterRegistryLive = Layer.effect( | ... | @@ -51,7 +51,3 @@ export const ProviderAdapterRegistryLive = Layer.effect( |
| 51 | ProviderAdapterRegistry, | 51 | ProviderAdapterRegistry, |
| 52 | makeProviderAdapterRegistry(), | 52 | makeProviderAdapterRegistry(), |
| 53 | ); | 53 | ); |
| 54 | |||
| 55 | export function makeProviderAdapterRegistryLive(options?: ProviderAdapterRegistryLiveOptions) { | ||
| 56 | return Layer.effect(ProviderAdapterRegistry, makeProviderAdapterRegistry(options)); | ||
| 57 | } |
apps/server/src/provider/Layers/ProviderService.test.ts+46-7| ... | @@ -31,6 +31,7 @@ import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts" | ... | @@ -31,6 +31,7 @@ import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts" |
| 31 | import { ProviderService } from "../Services/ProviderService.ts"; | 31 | import { ProviderService } from "../Services/ProviderService.ts"; |
| 32 | import { ProviderServiceLive } from "./ProviderService.ts"; | 32 | import { ProviderServiceLive } from "./ProviderService.ts"; |
| 33 | import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts"; | 33 | import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts"; |
| 34 | import { NodeServices } from "@effect/platform-node"; | ||
| 34 | 35 | ||
| 35 | function makeFakeCodexAdapter() { | 36 | function makeFakeCodexAdapter() { |
| 36 | const sessions = new Map<string, ProviderSession>(); | 37 | const sessions = new Map<string, ProviderSession>(); |
| ... | @@ -253,15 +254,17 @@ function makeProviderServiceLayer() { | ... | @@ -253,15 +254,17 @@ function makeProviderServiceLayer() { |
| 253 | listProviders: () => Effect.succeed(["codex"]), | 254 | listProviders: () => Effect.succeed(["codex"]), |
| 254 | }; | 255 | }; |
| 255 | 256 | ||
| 257 | const checkpointLayer = Layer.succeed(CheckpointService, checkpoint.service); | ||
| 258 | const providerAdapterLayer = Layer.succeed(ProviderAdapterRegistry, registry); | ||
| 259 | |||
| 256 | const layer = it.layer( | 260 | const layer = it.layer( |
| 257 | ProviderServiceLive.pipe( | 261 | Layer.mergeAll( |
| 258 | Layer.provide( | 262 | ProviderServiceLive.pipe( |
| 259 | Layer.mergeAll( | 263 | Layer.provide(providerAdapterLayer), |
| 260 | Layer.succeed(ProviderAdapterRegistry, registry), | 264 | Layer.provide(ProviderSessionDirectoryLive), |
| 261 | ProviderSessionDirectoryLive, | 265 | Layer.provide(checkpointLayer), |
| 262 | Layer.succeed(CheckpointService, checkpoint.service), | ||
| 263 | ), | ||
| 264 | ), | 266 | ), |
| 267 | NodeServices.layer, | ||
| 265 | ), | 268 | ), |
| 266 | ); | 269 | ); |
| 267 | 270 | ||
| ... | @@ -424,4 +427,40 @@ validation.layer("ProviderServiceLive validation", (it) => { | ... | @@ -424,4 +427,40 @@ validation.layer("ProviderServiceLive validation", (it) => { |
| 424 | ); | 427 | ); |
| 425 | }), | 428 | }), |
| 426 | ); | 429 | ); |
| 430 | |||
| 431 | it.effect("fails startSession when adapter returns no threadId", () => | ||
| 432 | Effect.gen(function* () { | ||
| 433 | const provider = yield* ProviderService; | ||
| 434 | |||
| 435 | validation.codex.startSession.mockImplementationOnce((input: ProviderSessionStartInput) => | ||
| 436 | Effect.sync(() => { | ||
| 437 | const now = new Date().toISOString(); | ||
| 438 | return { | ||
| 439 | sessionId: "sess-missing-thread", | ||
| 440 | provider: "codex", | ||
| 441 | status: "ready", | ||
| 442 | cwd: input.cwd ?? process.cwd(), | ||
| 443 | createdAt: now, | ||
| 444 | updatedAt: now, | ||
| 445 | } satisfies ProviderSession; | ||
| 446 | }), | ||
| 447 | ); | ||
| 448 | |||
| 449 | const failure = yield* Effect.result( | ||
| 450 | provider.startSession({ | ||
| 451 | provider: "codex", | ||
| 452 | cwd: "/tmp/project", | ||
| 453 | }), | ||
| 454 | ); | ||
| 455 | |||
| 456 | assertFailure( | ||
| 457 | failure, | ||
| 458 | new ProviderValidationError({ | ||
| 459 | operation: "ProviderService.startSession", | ||
| 460 | issue: | ||
| 461 | "Provider 'codex' returned a session without threadId. threadId is required for checkpoint initialization.", | ||
| 462 | }), | ||
| 463 | ); | ||
| 464 | }), | ||
| 465 | ); | ||
| 427 | }); | 466 | }); |
apps/server/src/provider/Layers/ProviderService.ts+11-1| ... | @@ -161,10 +161,20 @@ const makeProviderService = Effect.gen(function* () { | ... | @@ -161,10 +161,20 @@ const makeProviderService = Effect.gen(function* () { |
| 161 | ); | 161 | ); |
| 162 | } | 162 | } |
| 163 | 163 | ||
| 164 | const threadId = session.threadId?.trim(); | ||
| 165 | if (!threadId) { | ||
| 166 | return yield* Effect.fail( | ||
| 167 | toValidationError( | ||
| 168 | "ProviderService.startSession", | ||
| 169 | `Provider '${adapter.provider}' returned a session without threadId. threadId is required for checkpoint initialization.`, | ||
| 170 | ), | ||
| 171 | ); | ||
| 172 | } | ||
| 173 | |||
| 164 | yield* directory.upsert({ | 174 | yield* directory.upsert({ |
| 165 | sessionId: session.sessionId, | 175 | sessionId: session.sessionId, |
| 166 | provider: session.provider, | 176 | provider: session.provider, |
| 167 | ...(session.threadId !== undefined ? { threadId: session.threadId } : {}), | 177 | threadId, |
| 168 | }); | 178 | }); |
| 169 | 179 | ||
| 170 | const checkpointCwd = session.cwd ?? input.cwd ?? process.cwd(); | 180 | const checkpointCwd = session.cwd ?? input.cwd ?? process.cwd(); |
apps/server/src/wsServer.test.ts+13-5| ... | @@ -30,7 +30,9 @@ import type { | ... | @@ -30,7 +30,9 @@ import type { |
| 30 | } from "@t3tools/contracts"; | 30 | } from "@t3tools/contracts"; |
| 31 | import type { TerminalManager } from "./terminalManager"; | 31 | import type { TerminalManager } from "./terminalManager"; |
| 32 | import { ProjectRepositoryLive } from "./persistence/Layers/Projects"; | 32 | import { ProjectRepositoryLive } from "./persistence/Layers/Projects"; |
| 33 | import { makeSqlitePersistenceLive } from "./persistence/Layers/Sqlite"; | 33 | import { makeSqlitePersistenceLive, SqlitePersistenceMemory } from "./persistence/Layers/Sqlite"; |
| 34 | import { NodeServices } from "@effect/platform-node"; | ||
| 35 | import { SqlClient } from "effect/unstable/sql"; | ||
| 34 | 36 | ||
| 35 | interface PendingMessages { | 37 | interface PendingMessages { |
| 36 | queue: unknown[]; | 38 | queue: unknown[]; |
| ... | @@ -237,6 +239,7 @@ describe("WebSocket Server", () => { | ... | @@ -237,6 +239,7 @@ describe("WebSocket Server", () => { |
| 237 | 239 | ||
| 238 | function createTestServer( | 240 | function createTestServer( |
| 239 | options: { | 241 | options: { |
| 242 | persistenceLayer?: Layer.Layer<SqlClient.SqlClient, any>; | ||
| 240 | cwd?: string; | 243 | cwd?: string; |
| 241 | devUrl?: string; | 244 | devUrl?: string; |
| 242 | authToken?: string; | 245 | authToken?: string; |
| ... | @@ -253,6 +256,7 @@ describe("WebSocket Server", () => { | ... | @@ -253,6 +256,7 @@ describe("WebSocket Server", () => { |
| 253 | port: 0, | 256 | port: 0, |
| 254 | cwd: options.cwd ?? "/test/project", | 257 | cwd: options.cwd ?? "/test/project", |
| 255 | stateDir, | 258 | stateDir, |
| 259 | persistenceLayer: options.persistenceLayer ?? SqlitePersistenceMemory, | ||
| 256 | ...(options.devUrl ? { devUrl: options.devUrl } : {}), | 260 | ...(options.devUrl ? { devUrl: options.devUrl } : {}), |
| 257 | ...(options.authToken ? { authToken: options.authToken } : {}), | 261 | ...(options.authToken ? { authToken: options.authToken } : {}), |
| 258 | ...(options.gitManager ? { gitManager: options.gitManager as never } : {}), | 262 | ...(options.gitManager ? { gitManager: options.gitManager as never } : {}), |
| ... | @@ -1033,11 +1037,11 @@ describe("WebSocket Server", () => { | ... | @@ -1033,11 +1037,11 @@ describe("WebSocket Server", () => { |
| 1033 | const stateDir = makeTempDir("t3code-ws-prune-state-"); | 1037 | const stateDir = makeTempDir("t3code-ws-prune-state-"); |
| 1034 | const existing = makeTempDir("t3code-ws-existing-project-"); | 1038 | const existing = makeTempDir("t3code-ws-existing-project-"); |
| 1035 | const missing = makeTempDir("t3code-ws-missing-project-"); | 1039 | const missing = makeTempDir("t3code-ws-missing-project-"); |
| 1040 | const dbPath = path.join(stateDir, "test-prune.sqlite"); | ||
| 1041 | const persistenceLayer = Layer.provide(makeSqlitePersistenceLive(dbPath), NodeServices.layer); | ||
| 1036 | 1042 | ||
| 1037 | const projectRuntime = ManagedRuntime.make( | 1043 | const projectRuntime = ManagedRuntime.make( |
| 1038 | ProjectRepositoryLive.pipe( | 1044 | ProjectRepositoryLive.pipe(Layer.provide(persistenceLayer)), |
| 1039 | Layer.provide(makeSqlitePersistenceLive(path.join(stateDir, "orchestration.sqlite"))), | ||
| 1040 | ), | ||
| 1041 | ); | 1045 | ); |
| 1042 | const projectRepository = await projectRuntime.runPromise(Effect.service(ProjectRepository)); | 1046 | const projectRepository = await projectRuntime.runPromise(Effect.service(ProjectRepository)); |
| 1043 | await projectRuntime.runPromise(projectRepository.add({ cwd: existing })); | 1047 | await projectRuntime.runPromise(projectRepository.add({ cwd: existing })); |
| ... | @@ -1045,7 +1049,11 @@ describe("WebSocket Server", () => { | ... | @@ -1045,7 +1049,11 @@ describe("WebSocket Server", () => { |
| 1045 | await projectRuntime.dispose(); | 1049 | await projectRuntime.dispose(); |
| 1046 | fs.rmSync(missing, { recursive: true, force: true }); | 1050 | fs.rmSync(missing, { recursive: true, force: true }); |
| 1047 | 1051 | ||
| 1048 | server = createTestServer({ stateDir, cwd: "/test" }); | 1052 | server = createTestServer({ |
| 1053 | stateDir, | ||
| 1054 | cwd: "/test", | ||
| 1055 | persistenceLayer, | ||
| 1056 | }); | ||
| 1049 | await server.start(); | 1057 | await server.start(); |
| 1050 | const addr = server.httpServer.address(); | 1058 | const addr = server.httpServer.address(); |
| 1051 | const port = typeof addr === "object" && addr !== null ? addr.port : 0; | 1059 | const port = typeof addr === "object" && addr !== null ? addr.port : 0; |
apps/server/src/wsServer.ts+181-124| ... | @@ -18,11 +18,11 @@ import { | ... | @@ -18,11 +18,11 @@ import { |
| 18 | type WsResponse, | 18 | type WsResponse, |
| 19 | wsRequestSchema, | 19 | wsRequestSchema, |
| 20 | } from "@t3tools/contracts"; | 20 | } from "@t3tools/contracts"; |
| 21 | import { NodeServices } from "@effect/platform-node"; | ||
| 21 | import { Effect, Layer, ManagedRuntime } from "effect"; | 22 | import { Effect, Layer, ManagedRuntime } from "effect"; |
| 22 | import { WebSocketServer, type WebSocket } from "ws"; | 23 | import { WebSocketServer, type WebSocket } from "ws"; |
| 23 | 24 | ||
| 24 | import { createLogger } from "./logger"; | 25 | import { createLogger } from "./logger"; |
| 25 | import { ProviderManager } from "./providerManager"; | ||
| 26 | import { GitManager } from "./gitManager"; | 26 | import { GitManager } from "./gitManager"; |
| 27 | import { | 27 | import { |
| 28 | checkoutGitBranch, | 28 | checkoutGitBranch, |
| ... | @@ -43,6 +43,17 @@ import { makeSqlitePersistenceLive } from "./persistence/Layers/Sqlite"; | ... | @@ -43,6 +43,17 @@ import { makeSqlitePersistenceLive } from "./persistence/Layers/Sqlite"; |
| 43 | import { ProjectRepository, type ProjectRepositoryShape } from "./persistence/Services/Projects"; | 43 | import { ProjectRepository, type ProjectRepositoryShape } from "./persistence/Services/Projects"; |
| 44 | import assert from "node:assert"; | 44 | import assert from "node:assert"; |
| 45 | import { OrchestrationEventRepositoryLive } from "./persistence/Layers/OrchestrationEvents"; | 45 | import { OrchestrationEventRepositoryLive } from "./persistence/Layers/OrchestrationEvents"; |
| 46 | import { CodexAdapterLive } from "./provider/Layers/CodexAdapter"; | ||
| 47 | import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry"; | ||
| 48 | import { ProviderServiceLive } from "./provider/Layers/ProviderService"; | ||
| 49 | import { ProviderSessionDirectoryLive } from "./provider/Layers/ProviderSessionDirectory"; | ||
| 50 | import { ProviderService, type ProviderServiceShape } from "./provider/Services/ProviderService"; | ||
| 51 | import { CheckpointStoreLive } from "./checkpointing/Layers/CheckpointStore"; | ||
| 52 | import { CheckpointServiceLive } from "./checkpointing/Layers/CheckpointService"; | ||
| 53 | import { CheckpointRepositoryLive } from "./persistence/Layers/Checkpoints"; | ||
| 54 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | ||
| 55 | import * as SqlError from "effect/unstable/sql/SqlError"; | ||
| 56 | import * as Migrator from "effect/unstable/sql/Migrator"; | ||
| 46 | 57 | ||
| 47 | const MIME_TYPES: Record<string, string> = { | 58 | const MIME_TYPES: Record<string, string> = { |
| 48 | ".html": "text/html; charset=utf-8", | 59 | ".html": "text/html; charset=utf-8", |
| ... | @@ -64,6 +75,9 @@ export interface ServerOptions { | ... | @@ -64,6 +75,9 @@ export interface ServerOptions { |
| 64 | host?: string | undefined; | 75 | host?: string | undefined; |
| 65 | cwd: string; | 76 | cwd: string; |
| 66 | stateDir?: string | undefined; | 77 | stateDir?: string | undefined; |
| 78 | persistenceLayer?: | ||
| 79 | | Layer.Layer<SqlClient.SqlClient, SqlError.SqlError | Migrator.MigrationError> | ||
| 80 | | undefined; | ||
| 67 | staticDir?: string | undefined; | 81 | staticDir?: string | undefined; |
| 68 | devUrl?: string | undefined; | 82 | devUrl?: string | undefined; |
| 69 | logWebSocketEvents?: boolean | undefined; | 83 | logWebSocketEvents?: boolean | undefined; |
| ... | @@ -121,6 +135,7 @@ export function createServer(options: ServerOptions) { | ... | @@ -121,6 +135,7 @@ export function createServer(options: ServerOptions) { |
| 121 | host, | 135 | host, |
| 122 | cwd, | 136 | cwd, |
| 123 | stateDir, | 137 | stateDir, |
| 138 | persistenceLayer: customPersistenceLayer, | ||
| 124 | staticDir, | 139 | staticDir, |
| 125 | devUrl, | 140 | devUrl, |
| 126 | logWebSocketEvents: explicitLogWsEvents, | 141 | logWebSocketEvents: explicitLogWsEvents, |
| ... | @@ -128,15 +143,15 @@ export function createServer(options: ServerOptions) { | ... | @@ -128,15 +143,15 @@ export function createServer(options: ServerOptions) { |
| 128 | terminalManager = new TerminalManager(), | 143 | terminalManager = new TerminalManager(), |
| 129 | authToken, | 144 | authToken, |
| 130 | } = options; | 145 | } = options; |
| 131 | const providerManager = new ProviderManager(); | ||
| 132 | 146 | ||
| 133 | const resolvedStateDir = stateDir ?? path.join(os.homedir(), ".t3", "userdata"); | 147 | const resolvedStateDir = stateDir ?? path.join(os.homedir(), ".t3", "userdata"); |
| 134 | let effectRuntime: ManagedRuntime.ManagedRuntime< | 148 | let effectRuntime: ManagedRuntime.ManagedRuntime< |
| 135 | ProjectRepository | OrchestrationEngineService, | 149 | ProjectRepository | OrchestrationEngineService | ProviderService, |
| 136 | unknown | 150 | unknown |
| 137 | > | null = null; | 151 | > | null = null; |
| 138 | let projectRepository: ProjectRepositoryShape | null = null; | 152 | let projectRepository: ProjectRepositoryShape | null = null; |
| 139 | let orchestrationEngine: OrchestrationEngineShape | null = null; | 153 | let orchestrationEngine: OrchestrationEngineShape | null = null; |
| 154 | let providerService: ProviderServiceShape | null = null; | ||
| 140 | const clients = new Set<WebSocket>(); | 155 | const clients = new Set<WebSocket>(); |
| 141 | const logger = createLogger("ws"); | 156 | const logger = createLogger("ws"); |
| 142 | const logWebSocketEvents = | 157 | const logWebSocketEvents = |
| ... | @@ -144,6 +159,7 @@ export function createServer(options: ServerOptions) { | ... | @@ -144,6 +159,7 @@ export function createServer(options: ServerOptions) { |
| 144 | let keybindingsConfig = loadResolvedKeybindingsConfig(logger); | 159 | let keybindingsConfig = loadResolvedKeybindingsConfig(logger); |
| 145 | let unsubscribeReadModel = noop; | 160 | let unsubscribeReadModel = noop; |
| 146 | let unsubscribeDomainEvents = noop; | 161 | let unsubscribeDomainEvents = noop; |
| 162 | let unsubscribeProviderEvents = noop; | ||
| 147 | 163 | ||
| 148 | function logOutgoingPush(push: WsPush, recipients: number) { | 164 | function logOutgoingPush(push: WsPush, recipients: number) { |
| 149 | if (!logWebSocketEvents) return; | 165 | if (!logWebSocketEvents) return; |
| ... | @@ -169,8 +185,16 @@ export function createServer(options: ServerOptions) { | ... | @@ -169,8 +185,16 @@ export function createServer(options: ServerOptions) { |
| 169 | return effectRuntime; | 185 | return effectRuntime; |
| 170 | }; | 186 | }; |
| 171 | 187 | ||
| 188 | const getProviderService = () => { | ||
| 189 | assert(providerService, "Provider service is not started"); | ||
| 190 | return providerService; | ||
| 191 | }; | ||
| 192 | |||
| 172 | async function attachOrchestrationSubscriptions( | 193 | async function attachOrchestrationSubscriptions( |
| 173 | runtime: ManagedRuntime.ManagedRuntime<ProjectRepository | OrchestrationEngineService, unknown>, | 194 | runtime: ManagedRuntime.ManagedRuntime< |
| 195 | ProjectRepository | OrchestrationEngineService | ProviderService, | ||
| 196 | unknown | ||
| 197 | >, | ||
| 174 | orchestrationEngine: OrchestrationEngineShape, | 198 | orchestrationEngine: OrchestrationEngineShape, |
| 175 | ) { | 199 | ) { |
| 176 | unsubscribeReadModel = await runtime.runPromise( | 200 | unsubscribeReadModel = await runtime.runPromise( |
| ... | @@ -215,114 +239,121 @@ export function createServer(options: ServerOptions) { | ... | @@ -215,114 +239,121 @@ export function createServer(options: ServerOptions) { |
| 215 | ); | 239 | ); |
| 216 | } | 240 | } |
| 217 | 241 | ||
| 218 | // Forward provider events to all connected WebSocket clients | 242 | async function attachProviderSubscriptions( |
| 219 | providerManager.on("event", (event) => { | 243 | runtime: ManagedRuntime.ManagedRuntime< |
| 220 | void (async () => { | 244 | ProjectRepository | OrchestrationEngineService | ProviderService, |
| 221 | const liveOrchestrationEngine = orchestrationEngine; | 245 | unknown |
| 222 | const runtime = effectRuntime; | 246 | >, |
| 223 | if (!liveOrchestrationEngine || !runtime) { | 247 | providerService: ProviderServiceShape, |
| 224 | return; | 248 | orchestrationEngine: OrchestrationEngineShape, |
| 225 | } | 249 | ) { |
| 226 | const snapshot = await runtime.runPromise(liveOrchestrationEngine.getSnapshot()); | 250 | unsubscribeProviderEvents = await runtime.runPromise( |
| 227 | const thread = snapshot.threads.find((entry) => entry.session?.sessionId === event.sessionId); | 251 | providerService.subscribeToEvents((event) => { |
| 228 | if (!thread) return; | 252 | void (async () => { |
| 229 | const now = event.createdAt; | 253 | const snapshot = await runtime.runPromise(orchestrationEngine.getSnapshot()); |
| 230 | if (event.method === "turn/started" || event.method === "turn/completed") { | 254 | const thread = snapshot.threads.find( |
| 231 | await runtime.runPromise( | 255 | (entry) => entry.session?.sessionId === event.sessionId, |
| 232 | liveOrchestrationEngine.dispatch({ | ||
| 233 | type: "thread.session", | ||
| 234 | commandId: crypto.randomUUID(), | ||
| 235 | threadId: thread.id, | ||
| 236 | session: { | ||
| 237 | sessionId: event.sessionId, | ||
| 238 | provider: event.provider, | ||
| 239 | status: event.method === "turn/started" ? "running" : "ready", | ||
| 240 | threadId: thread.id, | ||
| 241 | activeTurnId: event.method === "turn/started" ? (event.turnId ?? null) : null, | ||
| 242 | createdAt: thread.session?.createdAt ?? now, | ||
| 243 | updatedAt: now, | ||
| 244 | lastError: null, | ||
| 245 | }, | ||
| 246 | createdAt: now, | ||
| 247 | }), | ||
| 248 | ); | ||
| 249 | } | ||
| 250 | if (event.textDelta && event.textDelta.length > 0) { | ||
| 251 | const assistantMessageId = `assistant:${event.turnId ?? event.itemId ?? event.sessionId}`; | ||
| 252 | await runtime.runPromise( | ||
| 253 | liveOrchestrationEngine.dispatch({ | ||
| 254 | type: "message.send", | ||
| 255 | commandId: crypto.randomUUID(), | ||
| 256 | threadId: thread.id, | ||
| 257 | messageId: assistantMessageId, | ||
| 258 | role: "assistant", | ||
| 259 | text: event.textDelta, | ||
| 260 | streaming: true, | ||
| 261 | createdAt: now, | ||
| 262 | }), | ||
| 263 | ); | ||
| 264 | } | ||
| 265 | if (event.method === "turn/completed") { | ||
| 266 | const assistantMessageId = `assistant:${event.turnId ?? event.itemId ?? event.sessionId}`; | ||
| 267 | await runtime.runPromise( | ||
| 268 | liveOrchestrationEngine.dispatch({ | ||
| 269 | type: "message.send", | ||
| 270 | commandId: crypto.randomUUID(), | ||
| 271 | threadId: thread.id, | ||
| 272 | messageId: assistantMessageId, | ||
| 273 | role: "assistant", | ||
| 274 | text: "", | ||
| 275 | streaming: false, | ||
| 276 | createdAt: now, | ||
| 277 | }), | ||
| 278 | ); | ||
| 279 | } | ||
| 280 | if (event.method === "checkpoint/captured") { | ||
| 281 | const payload = asObject(event.payload); | ||
| 282 | const turnId = event.turnId ?? asString(payload?.turnId); | ||
| 283 | if (turnId) { | ||
| 284 | await runtime.runPromise( | ||
| 285 | liveOrchestrationEngine.dispatch({ | ||
| 286 | type: "thread.turnDiff.complete", | ||
| 287 | commandId: crypto.randomUUID(), | ||
| 288 | threadId: thread.id, | ||
| 289 | turnId, | ||
| 290 | completedAt: now, | ||
| 291 | ...(asString(payload?.status) !== null | ||
| 292 | ? { status: asString(payload?.status) ?? undefined } | ||
| 293 | : {}), | ||
| 294 | files: [], | ||
| 295 | assistantMessageId: `assistant:${turnId}`, | ||
| 296 | ...(asNumber(payload?.turnCount) !== null | ||
| 297 | ? { checkpointTurnCount: asNumber(payload?.turnCount) ?? undefined } | ||
| 298 | : {}), | ||
| 299 | createdAt: now, | ||
| 300 | }), | ||
| 301 | ); | 256 | ); |
| 302 | } | 257 | if (!thread) return; |
| 303 | } | 258 | const now = event.createdAt; |
| 304 | if (event.kind === "error" && event.message) { | 259 | if (event.method === "turn/started" || event.method === "turn/completed") { |
| 305 | await runtime.runPromise( | 260 | await runtime.runPromise( |
| 306 | liveOrchestrationEngine.dispatch({ | 261 | orchestrationEngine.dispatch({ |
| 307 | type: "thread.session", | 262 | type: "thread.session", |
| 308 | commandId: crypto.randomUUID(), | 263 | commandId: crypto.randomUUID(), |
| 309 | threadId: thread.id, | 264 | threadId: thread.id, |
| 310 | session: { | 265 | session: { |
| 311 | sessionId: event.sessionId, | 266 | sessionId: event.sessionId, |
| 312 | provider: event.provider, | 267 | provider: event.provider, |
| 313 | status: "error", | 268 | status: event.method === "turn/started" ? "running" : "ready", |
| 314 | threadId: thread.id, | 269 | threadId: thread.id, |
| 315 | activeTurnId: event.turnId ?? null, | 270 | activeTurnId: event.method === "turn/started" ? (event.turnId ?? null) : null, |
| 316 | createdAt: thread.session?.createdAt ?? now, | 271 | createdAt: thread.session?.createdAt ?? now, |
| 317 | updatedAt: now, | 272 | updatedAt: now, |
| 318 | lastError: event.message, | 273 | lastError: null, |
| 319 | }, | 274 | }, |
| 320 | createdAt: now, | 275 | createdAt: now, |
| 321 | }), | 276 | }), |
| 322 | ); | 277 | ); |
| 323 | } | 278 | } |
| 324 | })().catch(() => undefined); | 279 | if (event.textDelta && event.textDelta.length > 0) { |
| 325 | }); | 280 | const assistantMessageId = `assistant:${event.turnId ?? event.itemId ?? event.sessionId}`; |
| 281 | await runtime.runPromise( | ||
| 282 | orchestrationEngine.dispatch({ | ||
| 283 | type: "message.send", | ||
| 284 | commandId: crypto.randomUUID(), | ||
| 285 | threadId: thread.id, | ||
| 286 | messageId: assistantMessageId, | ||
| 287 | role: "assistant", | ||
| 288 | text: event.textDelta, | ||
| 289 | streaming: true, | ||
| 290 | createdAt: now, | ||
| 291 | }), | ||
| 292 | ); | ||
| 293 | } | ||
| 294 | if (event.method === "turn/completed") { | ||
| 295 | const assistantMessageId = `assistant:${event.turnId ?? event.itemId ?? event.sessionId}`; | ||
| 296 | await runtime.runPromise( | ||
| 297 | orchestrationEngine.dispatch({ | ||
| 298 | type: "message.send", | ||
| 299 | commandId: crypto.randomUUID(), | ||
| 300 | threadId: thread.id, | ||
| 301 | messageId: assistantMessageId, | ||
| 302 | role: "assistant", | ||
| 303 | text: "", | ||
| 304 | streaming: false, | ||
| 305 | createdAt: now, | ||
| 306 | }), | ||
| 307 | ); | ||
| 308 | } | ||
| 309 | if (event.method === "checkpoint/captured") { | ||
| 310 | const payload = asObject(event.payload); | ||
| 311 | const turnId = event.turnId ?? asString(payload?.turnId); | ||
| 312 | if (turnId) { | ||
| 313 | await runtime.runPromise( | ||
| 314 | orchestrationEngine.dispatch({ | ||
| 315 | type: "thread.turnDiff.complete", | ||
| 316 | commandId: crypto.randomUUID(), | ||
| 317 | threadId: thread.id, | ||
| 318 | turnId, | ||
| 319 | completedAt: now, | ||
| 320 | ...(asString(payload?.status) !== null | ||
| 321 | ? { status: asString(payload?.status) ?? undefined } | ||
| 322 | : {}), | ||
| 323 | files: [], | ||
| 324 | assistantMessageId: `assistant:${turnId}`, | ||
| 325 | ...(asNumber(payload?.turnCount) !== null | ||
| 326 | ? { checkpointTurnCount: asNumber(payload?.turnCount) ?? undefined } | ||
| 327 | : {}), | ||
| 328 | createdAt: now, | ||
| 329 | }), | ||
| 330 | ); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | if (event.kind === "error" && event.message) { | ||
| 334 | await runtime.runPromise( | ||
| 335 | orchestrationEngine.dispatch({ | ||
| 336 | type: "thread.session", | ||
| 337 | commandId: crypto.randomUUID(), | ||
| 338 | threadId: thread.id, | ||
| 339 | session: { | ||
| 340 | sessionId: event.sessionId, | ||
| 341 | provider: event.provider, | ||
| 342 | status: "error", | ||
| 343 | threadId: thread.id, | ||
| 344 | activeTurnId: event.turnId ?? null, | ||
| 345 | createdAt: thread.session?.createdAt ?? now, | ||
| 346 | updatedAt: now, | ||
| 347 | lastError: event.message, | ||
| 348 | }, | ||
| 349 | createdAt: now, | ||
| 350 | }), | ||
| 351 | ); | ||
| 352 | } | ||
| 353 | })().catch(() => undefined); | ||
| 354 | }), | ||
| 355 | ); | ||
| 356 | } | ||
| 326 | 357 | ||
| 327 | const onTerminalEvent = (event: TerminalEvent) => { | 358 | const onTerminalEvent = (event: TerminalEvent) => { |
| 328 | const push: WsPush = { | 359 | const push: WsPush = { |
| ... | @@ -504,33 +535,36 @@ export function createServer(options: ServerOptions) { | ... | @@ -504,33 +535,36 @@ export function createServer(options: ServerOptions) { |
| 504 | const orchestrationEngine = getOrchestrationEngine(); | 535 | const orchestrationEngine = getOrchestrationEngine(); |
| 505 | const projectRepository = getProjectRepository(); | 536 | const projectRepository = getProjectRepository(); |
| 506 | const effectRuntime = getEffectRuntime(); | 537 | const effectRuntime = getEffectRuntime(); |
| 538 | const providerService = getProviderService(); | ||
| 507 | 539 | ||
| 508 | switch (request.method) { | 540 | switch (request.method) { |
| 509 | case WS_METHODS.providersStartSession: | 541 | case WS_METHODS.providersStartSession: |
| 510 | return providerManager.startSession(request.params as never); | 542 | return effectRuntime.runPromise(providerService.startSession(request.params as never)); |
| 511 | 543 | ||
| 512 | case WS_METHODS.providersSendTurn: | 544 | case WS_METHODS.providersSendTurn: |
| 513 | return providerManager.sendTurn(request.params as never); | 545 | return effectRuntime.runPromise(providerService.sendTurn(request.params as never)); |
| 514 | 546 | ||
| 515 | case WS_METHODS.providersInterruptTurn: | 547 | case WS_METHODS.providersInterruptTurn: |
| 516 | return providerManager.interruptTurn(request.params as never); | 548 | return effectRuntime.runPromise(providerService.interruptTurn(request.params as never)); |
| 517 | 549 | ||
| 518 | case WS_METHODS.providersRespondToRequest: | 550 | case WS_METHODS.providersRespondToRequest: |
| 519 | return providerManager.respondToRequest(request.params as never); | 551 | return effectRuntime.runPromise(providerService.respondToRequest(request.params as never)); |
| 520 | 552 | ||
| 521 | case WS_METHODS.providersStopSession: { | 553 | case WS_METHODS.providersStopSession: { |
| 522 | providerManager.stopSession(request.params as never); | 554 | await effectRuntime.runPromise(providerService.stopSession(request.params as never)); |
| 523 | return undefined; | 555 | return undefined; |
| 524 | } | 556 | } |
| 525 | 557 | ||
| 526 | case WS_METHODS.providersListCheckpoints: | 558 | case WS_METHODS.providersListCheckpoints: |
| 527 | return providerManager.listCheckpoints(request.params as never); | 559 | return effectRuntime.runPromise(providerService.listCheckpoints(request.params as never)); |
| 528 | 560 | ||
| 529 | case WS_METHODS.providersGetCheckpointDiff: | 561 | case WS_METHODS.providersGetCheckpointDiff: |
| 530 | return providerManager.getCheckpointDiff(request.params as never); | 562 | return effectRuntime.runPromise(providerService.getCheckpointDiff(request.params as never)); |
| 531 | 563 | ||
| 532 | case WS_METHODS.providersRevertToCheckpoint: { | 564 | case WS_METHODS.providersRevertToCheckpoint: { |
| 533 | const result = await providerManager.revertToCheckpoint(request.params as never); | 565 | const result = await effectRuntime.runPromise( |
| 566 | providerService.revertToCheckpoint(request.params as never), | ||
| 567 | ); | ||
| 534 | const params = request.params as { sessionId?: string }; | 568 | const params = request.params as { sessionId?: string }; |
| 535 | const sessionId = params.sessionId; | 569 | const sessionId = params.sessionId; |
| 536 | if (typeof sessionId === "string") { | 570 | if (typeof sessionId === "string") { |
| ... | @@ -735,25 +769,42 @@ export function createServer(options: ServerOptions) { | ... | @@ -735,25 +769,42 @@ export function createServer(options: ServerOptions) { |
| 735 | 769 | ||
| 736 | async function createEffectRuntime() { | 770 | async function createEffectRuntime() { |
| 737 | const dbPath = path.join(resolvedStateDir, "orchestration.sqlite"); | 771 | const dbPath = path.join(resolvedStateDir, "orchestration.sqlite"); |
| 738 | const persistenceLayer = makeSqlitePersistenceLive(dbPath); | 772 | const persistenceLayer = customPersistenceLayer ?? makeSqlitePersistenceLive(dbPath); |
| 739 | const orchestrationLayer = Layer.provide( | 773 | const orchestrationLayer = Layer.provide( |
| 740 | OrchestrationEngineLive, | 774 | OrchestrationEngineLive, |
| 741 | OrchestrationEventRepositoryLive, | 775 | OrchestrationEventRepositoryLive, |
| 742 | ); | 776 | ); |
| 743 | const layer = Layer.mergeAll(orchestrationLayer, ProjectRepositoryLive).pipe( | 777 | const adapterRegistryLayer = ProviderAdapterRegistryLive.pipe(Layer.provide(CodexAdapterLive)); |
| 778 | const checkpointStoreLayer = CheckpointStoreLive.pipe(Layer.provide(NodeServices.layer)); | ||
| 779 | const checkpointDependenciesLayer = Layer.mergeAll( | ||
| 780 | checkpointStoreLayer, | ||
| 781 | CheckpointRepositoryLive, | ||
| 782 | adapterRegistryLayer, | ||
| 783 | ProviderSessionDirectoryLive, | ||
| 784 | ); | ||
| 785 | const checkpointServiceLayer = CheckpointServiceLive.pipe( | ||
| 786 | Layer.provideMerge(checkpointDependenciesLayer), | ||
| 787 | ); | ||
| 788 | const providerLayer = ProviderServiceLive.pipe(Layer.provideMerge(checkpointServiceLayer)); | ||
| 789 | |||
| 790 | const layer = Layer.mergeAll(orchestrationLayer, ProjectRepositoryLive, providerLayer).pipe( | ||
| 744 | Layer.provide(persistenceLayer), | 791 | Layer.provide(persistenceLayer), |
| 792 | Layer.provideMerge(NodeServices.layer), | ||
| 745 | ); | 793 | ); |
| 746 | const runtime = ManagedRuntime.make(layer); | 794 | const runtime = ManagedRuntime.make(layer); |
| 747 | 795 | ||
| 748 | try { | 796 | try { |
| 749 | const [nextOrchestrationEngine, repository] = await Promise.all([ | 797 | const [nextOrchestrationEngine, repository, nextProviderService] = await Promise.all([ |
| 750 | runtime.runPromise(Effect.service(OrchestrationEngineService)), | 798 | runtime.runPromise(Effect.service(OrchestrationEngineService)), |
| 751 | runtime.runPromise(Effect.service(ProjectRepository)), | 799 | runtime.runPromise(Effect.service(ProjectRepository)), |
| 800 | runtime.runPromise(Effect.service(ProviderService)), | ||
| 752 | ]); | 801 | ]); |
| 753 | orchestrationEngine = nextOrchestrationEngine; | 802 | orchestrationEngine = nextOrchestrationEngine; |
| 754 | projectRepository = repository; | 803 | projectRepository = repository; |
| 804 | providerService = nextProviderService; | ||
| 755 | await runtime.runPromise(repository.pruneMissing()); | 805 | await runtime.runPromise(repository.pruneMissing()); |
| 756 | await attachOrchestrationSubscriptions(runtime, nextOrchestrationEngine); | 806 | await attachOrchestrationSubscriptions(runtime, nextOrchestrationEngine); |
| 807 | await attachProviderSubscriptions(runtime, nextProviderService, nextOrchestrationEngine); | ||
| 757 | } catch (error) { | 808 | } catch (error) { |
| 758 | await runtime.dispose().catch(() => undefined); | 809 | await runtime.dispose().catch(() => undefined); |
| 759 | throw error; | 810 | throw error; |
| ... | @@ -765,18 +816,26 @@ export function createServer(options: ServerOptions) { | ... | @@ -765,18 +816,26 @@ export function createServer(options: ServerOptions) { |
| 765 | async function disposeEffectRuntime() { | 816 | async function disposeEffectRuntime() { |
| 766 | unsubscribeReadModel(); | 817 | unsubscribeReadModel(); |
| 767 | unsubscribeDomainEvents(); | 818 | unsubscribeDomainEvents(); |
| 819 | unsubscribeProviderEvents(); | ||
| 768 | unsubscribeReadModel = noop; | 820 | unsubscribeReadModel = noop; |
| 769 | unsubscribeDomainEvents = noop; | 821 | unsubscribeDomainEvents = noop; |
| 822 | unsubscribeProviderEvents = noop; | ||
| 770 | 823 | ||
| 771 | const runtime = effectRuntime; | 824 | const runtime = effectRuntime; |
| 825 | const liveProviderService = providerService; | ||
| 772 | effectRuntime = null; | 826 | effectRuntime = null; |
| 773 | orchestrationEngine = null; | 827 | orchestrationEngine = null; |
| 774 | projectRepository = null; | 828 | projectRepository = null; |
| 829 | providerService = null; | ||
| 775 | 830 | ||
| 776 | if (!runtime) { | 831 | if (!runtime) { |
| 777 | return; | 832 | return; |
| 778 | } | 833 | } |
| 779 | 834 | ||
| 835 | if (liveProviderService) { | ||
| 836 | await runtime.runPromise(liveProviderService.stopAll()).catch(() => undefined); | ||
| 837 | } | ||
| 838 | |||
| 780 | await runtime.dispose(); | 839 | await runtime.dispose(); |
| 781 | } | 840 | } |
| 782 | 841 | ||
| ... | @@ -804,8 +863,6 @@ export function createServer(options: ServerOptions) { | ... | @@ -804,8 +863,6 @@ export function createServer(options: ServerOptions) { |
| 804 | async function stop(): Promise<void> { | 863 | async function stop(): Promise<void> { |
| 805 | await disposeEffectRuntime(); | 864 | await disposeEffectRuntime(); |
| 806 | terminalManager.off("event", onTerminalEvent); | 865 | terminalManager.off("event", onTerminalEvent); |
| 807 | providerManager.stopAll(); | ||
| 808 | providerManager.dispose(); | ||
| 809 | terminalManager.dispose(); | 866 | terminalManager.dispose(); |
| 810 | 867 | ||
| 811 | for (const client of clients) { | 868 | for (const client of clients) { |
bun.lock-61| ... | @@ -35,7 +35,6 @@ | ... | @@ -35,7 +35,6 @@ |
| 35 | }, | 35 | }, |
| 36 | "dependencies": { | 36 | "dependencies": { |
| 37 | "@effect/platform-node": "4.0.0-beta.10", | 37 | "@effect/platform-node": "4.0.0-beta.10", |
| 38 | "@effect/sql-sqlite-node": "4.0.0-beta.10", | ||
| 39 | "effect": "4.0.0-beta.10", | 38 | "effect": "4.0.0-beta.10", |
| 40 | "node-pty": "^1.1.0", | 39 | "node-pty": "^1.1.0", |
| 41 | "open": "^10.1.0", | 40 | "open": "^10.1.0", |
| ... | @@ -154,8 +153,6 @@ | ... | @@ -154,8 +153,6 @@ |
| 154 | 153 | ||
| 155 | "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.10", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-gBSeOG2bfchXFdgdi5oipHKynOzSd7BlzTy5QcjFOoCUTtLP9igqohPB9hTkV3a6seWE2JJqsgsQ79VuLb7fAg=="], | 154 | "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.10", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-gBSeOG2bfchXFdgdi5oipHKynOzSd7BlzTy5QcjFOoCUTtLP9igqohPB9hTkV3a6seWE2JJqsgsQ79VuLb7fAg=="], |
| 156 | 155 | ||
| 157 | "@effect/sql-sqlite-node": ["@effect/sql-sqlite-node@4.0.0-beta.10", "", { "dependencies": { "better-sqlite3": "^12.6.2" }, "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-oQl+gF31HaVM9R8YEpf96CIJypP1Roa5z1VPj8s7ZP+3aCtRVor1K+eMxYNAb8ph7R68Tz0LpmVkefq0mEI3CA=="], | ||
| 158 | |||
| 159 | "@effect/vitest": ["@effect/vitest@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10", "vitest": "^3.0.0" } }, "sha512-WmE8vCNw+eDoAq9CwwymrAdIBw1f+8hDP3AUF7jyC7/Cj/kyAYOFwkbtKmDXIFaTlf7G/2fmulEXeCO5W8j0kQ=="], | 156 | "@effect/vitest": ["@effect/vitest@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10", "vitest": "^3.0.0" } }, "sha512-WmE8vCNw+eDoAq9CwwymrAdIBw1f+8hDP3AUF7jyC7/Cj/kyAYOFwkbtKmDXIFaTlf7G/2fmulEXeCO5W8j0kQ=="], |
| 160 | 157 | ||
| 161 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], | 158 | "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], |
| ... | @@ -610,28 +607,18 @@ | ... | @@ -610,28 +607,18 @@ |
| 610 | 607 | ||
| 611 | "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], | 608 | "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], |
| 612 | 609 | ||
| 613 | "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], | ||
| 614 | |||
| 615 | "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], | 610 | "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], |
| 616 | 611 | ||
| 617 | "better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="], | ||
| 618 | |||
| 619 | "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], | 612 | "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], |
| 620 | 613 | ||
| 621 | "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], | ||
| 622 | |||
| 623 | "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], | 614 | "birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], |
| 624 | 615 | ||
| 625 | "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], | ||
| 626 | |||
| 627 | "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], | 616 | "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], |
| 628 | 617 | ||
| 629 | "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], | 618 | "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], |
| 630 | 619 | ||
| 631 | "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], | 620 | "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], |
| 632 | 621 | ||
| 633 | "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], | ||
| 634 | |||
| 635 | "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], | 622 | "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], |
| 636 | 623 | ||
| 637 | "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], | 624 | "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], |
| ... | @@ -662,8 +649,6 @@ | ... | @@ -662,8 +649,6 @@ |
| 662 | 649 | ||
| 663 | "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], | 650 | "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], |
| 664 | 651 | ||
| 665 | "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], | ||
| 666 | |||
| 667 | "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], | 652 | "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], |
| 668 | 653 | ||
| 669 | "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], | 654 | "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], |
| ... | @@ -698,8 +683,6 @@ | ... | @@ -698,8 +683,6 @@ |
| 698 | 683 | ||
| 699 | "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], | 684 | "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], |
| 700 | 685 | ||
| 701 | "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], | ||
| 702 | |||
| 703 | "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], | 686 | "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], |
| 704 | 687 | ||
| 705 | "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], | 688 | "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], |
| ... | @@ -774,8 +757,6 @@ | ... | @@ -774,8 +757,6 @@ |
| 774 | 757 | ||
| 775 | "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], | 758 | "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], |
| 776 | 759 | ||
| 777 | "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], | ||
| 778 | |||
| 779 | "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], | 760 | "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], |
| 780 | 761 | ||
| 781 | "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], | 762 | "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], |
| ... | @@ -788,8 +769,6 @@ | ... | @@ -788,8 +769,6 @@ |
| 788 | 769 | ||
| 789 | "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], | 770 | "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], |
| 790 | 771 | ||
| 791 | "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], | ||
| 792 | |||
| 793 | "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], | 772 | "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], |
| 794 | 773 | ||
| 795 | "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], | 774 | "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], |
| ... | @@ -798,8 +777,6 @@ | ... | @@ -798,8 +777,6 @@ |
| 798 | 777 | ||
| 799 | "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], | 778 | "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], |
| 800 | 779 | ||
| 801 | "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], | ||
| 802 | |||
| 803 | "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], | 780 | "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], |
| 804 | 781 | ||
| 805 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], | 782 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], |
| ... | @@ -818,8 +795,6 @@ | ... | @@ -818,8 +795,6 @@ |
| 818 | 795 | ||
| 819 | "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], | 796 | "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], |
| 820 | 797 | ||
| 821 | "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], | ||
| 822 | |||
| 823 | "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], | 798 | "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], |
| 824 | 799 | ||
| 825 | "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], | 800 | "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], |
| ... | @@ -868,14 +843,10 @@ | ... | @@ -868,14 +843,10 @@ |
| 868 | 843 | ||
| 869 | "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], | 844 | "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], |
| 870 | 845 | ||
| 871 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], | ||
| 872 | |||
| 873 | "import-from": ["import-from@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ=="], | 846 | "import-from": ["import-from@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ=="], |
| 874 | 847 | ||
| 875 | "import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], | 848 | "import-without-cache": ["import-without-cache@0.2.5", "", {}, "sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A=="], |
| 876 | 849 | ||
| 877 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], | ||
| 878 | |||
| 879 | "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], | 850 | "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], |
| 880 | 851 | ||
| 881 | "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], | 852 | "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], |
| ... | @@ -1082,8 +1053,6 @@ | ... | @@ -1082,8 +1053,6 @@ |
| 1082 | 1053 | ||
| 1083 | "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], | 1054 | "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], |
| 1084 | 1055 | ||
| 1085 | "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], | ||
| 1086 | |||
| 1087 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], | 1056 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], |
| 1088 | 1057 | ||
| 1089 | "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], | 1058 | "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], |
| ... | @@ -1098,10 +1067,6 @@ | ... | @@ -1098,10 +1067,6 @@ |
| 1098 | 1067 | ||
| 1099 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], | 1068 | "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], |
| 1100 | 1069 | ||
| 1101 | "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], | ||
| 1102 | |||
| 1103 | "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], | ||
| 1104 | |||
| 1105 | "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], | 1070 | "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], |
| 1106 | 1071 | ||
| 1107 | "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], | 1072 | "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], |
| ... | @@ -1150,8 +1115,6 @@ | ... | @@ -1150,8 +1115,6 @@ |
| 1150 | 1115 | ||
| 1151 | "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], | 1116 | "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], |
| 1152 | 1117 | ||
| 1153 | "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], | ||
| 1154 | |||
| 1155 | "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], | 1118 | "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], |
| 1156 | 1119 | ||
| 1157 | "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], | 1120 | "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], |
| ... | @@ -1168,8 +1131,6 @@ | ... | @@ -1168,8 +1131,6 @@ |
| 1168 | 1131 | ||
| 1169 | "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], | 1132 | "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], |
| 1170 | 1133 | ||
| 1171 | "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], | ||
| 1172 | |||
| 1173 | "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], | 1134 | "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], |
| 1174 | 1135 | ||
| 1175 | "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], | 1136 | "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], |
| ... | @@ -1178,8 +1139,6 @@ | ... | @@ -1178,8 +1139,6 @@ |
| 1178 | 1139 | ||
| 1179 | "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], | 1140 | "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], |
| 1180 | 1141 | ||
| 1181 | "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], | ||
| 1182 | |||
| 1183 | "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], | 1142 | "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], |
| 1184 | 1143 | ||
| 1185 | "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], | 1144 | "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], |
| ... | @@ -1234,8 +1193,6 @@ | ... | @@ -1234,8 +1193,6 @@ |
| 1234 | 1193 | ||
| 1235 | "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], | 1194 | "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], |
| 1236 | 1195 | ||
| 1237 | "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], | ||
| 1238 | |||
| 1239 | "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], | 1196 | "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], |
| 1240 | 1197 | ||
| 1241 | "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], | 1198 | "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], |
| ... | @@ -1254,10 +1211,6 @@ | ... | @@ -1254,10 +1211,6 @@ |
| 1254 | 1211 | ||
| 1255 | "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], | 1212 | "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], |
| 1256 | 1213 | ||
| 1257 | "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], | ||
| 1258 | |||
| 1259 | "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], | ||
| 1260 | |||
| 1261 | "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], | 1214 | "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], |
| 1262 | 1215 | ||
| 1263 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], | 1216 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], |
| ... | @@ -1278,14 +1231,10 @@ | ... | @@ -1278,14 +1231,10 @@ |
| 1278 | 1231 | ||
| 1279 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | 1232 | "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], |
| 1280 | 1233 | ||
| 1281 | "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], | ||
| 1282 | |||
| 1283 | "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], | 1234 | "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], |
| 1284 | 1235 | ||
| 1285 | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | 1236 | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], |
| 1286 | 1237 | ||
| 1287 | "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], | ||
| 1288 | |||
| 1289 | "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], | 1238 | "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], |
| 1290 | 1239 | ||
| 1291 | "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], | 1240 | "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], |
| ... | @@ -1306,10 +1255,6 @@ | ... | @@ -1306,10 +1255,6 @@ |
| 1306 | 1255 | ||
| 1307 | "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], | 1256 | "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], |
| 1308 | 1257 | ||
| 1309 | "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], | ||
| 1310 | |||
| 1311 | "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], | ||
| 1312 | |||
| 1313 | "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], | 1258 | "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], |
| 1314 | 1259 | ||
| 1315 | "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], | 1260 | "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], |
| ... | @@ -1346,8 +1291,6 @@ | ... | @@ -1346,8 +1291,6 @@ |
| 1346 | 1291 | ||
| 1347 | "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], | 1292 | "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], |
| 1348 | 1293 | ||
| 1349 | "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], | ||
| 1350 | |||
| 1351 | "turbo": ["turbo@2.8.7", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.7", "turbo-darwin-arm64": "2.8.7", "turbo-linux-64": "2.8.7", "turbo-linux-arm64": "2.8.7", "turbo-windows-64": "2.8.7", "turbo-windows-arm64": "2.8.7" }, "bin": { "turbo": "bin/turbo" } }, "sha512-RBLh5caMAu1kFdTK1jgH2gH/z+jFsvX5rGbhgJ9nlIAWXSvxlzwId05uDlBA1+pBd3wO/UaKYzaQZQBXDd7kcA=="], | 1294 | "turbo": ["turbo@2.8.7", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.7", "turbo-darwin-arm64": "2.8.7", "turbo-linux-64": "2.8.7", "turbo-linux-arm64": "2.8.7", "turbo-windows-64": "2.8.7", "turbo-windows-arm64": "2.8.7" }, "bin": { "turbo": "bin/turbo" } }, "sha512-RBLh5caMAu1kFdTK1jgH2gH/z+jFsvX5rGbhgJ9nlIAWXSvxlzwId05uDlBA1+pBd3wO/UaKYzaQZQBXDd7kcA=="], |
| 1352 | 1295 | ||
| 1353 | "turbo-darwin-64": ["turbo-darwin-64@2.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xr4TO/oDDwoozbDtBvunb66g//WK8uHRygl72vUthuwzmiw48pil4IuoG/QbMHd9RE8aBnVmzC0WZEWk/WWt3A=="], | 1296 | "turbo-darwin-64": ["turbo-darwin-64@2.8.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xr4TO/oDDwoozbDtBvunb66g//WK8uHRygl72vUthuwzmiw48pil4IuoG/QbMHd9RE8aBnVmzC0WZEWk/WWt3A=="], |
| ... | @@ -1400,8 +1343,6 @@ | ... | @@ -1400,8 +1343,6 @@ |
| 1400 | 1343 | ||
| 1401 | "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], | 1344 | "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], |
| 1402 | 1345 | ||
| 1403 | "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], | ||
| 1404 | |||
| 1405 | "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], | 1346 | "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], |
| 1406 | 1347 | ||
| 1407 | "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], | 1348 | "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], |
| ... | @@ -1514,8 +1455,6 @@ | ... | @@ -1514,8 +1455,6 @@ |
| 1514 | 1455 | ||
| 1515 | "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], | 1456 | "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], |
| 1516 | 1457 | ||
| 1517 | "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], | ||
| 1518 | |||
| 1519 | "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], | 1458 | "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], |
| 1520 | 1459 | ||
| 1521 | "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], | 1460 | "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], |