feat(session): add durable session archival

This commit is contained in:
Brendan Allan 2026-07-21 19:02:27 +08:00
commit 08e616bcad
No known key found for this signature in database
GPG key ID: 41E835AEA046A32E
9 changed files with 75 additions and 1 deletions

View file

@ -226,6 +226,7 @@ export interface Interface {
model: ModelV2.Ref
}) => Effect.Effect<void, NotFoundError>
readonly rename: (input: { sessionID: SessionSchema.ID; title: string }) => Effect.Effect<void, NotFoundError>
readonly archive: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
readonly move: (input: {
sessionID: SessionSchema.ID
directory: AbsolutePath
@ -705,6 +706,11 @@ const layer = Layer.effect(
title: input.title,
})
}),
archive: Effect.fn("V2Session.archive")(function* (sessionID) {
const session = yield* result.get(sessionID)
if (session.time.archived) return
yield* events.publish(SessionEvent.Archived, { sessionID })
}),
move: Effect.fn("V2Session.move")(function* (input) {
const current = yield* result.get(input.sessionID)
const value = input.directory.trim()

View file

@ -53,7 +53,7 @@ export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.In
time: {
created: DateTime.makeUnsafe(row.time_created),
updated: DateTime.makeUnsafe(row.time_updated),
archived: row.time_archived ? DateTime.makeUnsafe(row.time_archived) : undefined,
archived: row.time_archived !== null ? DateTime.makeUnsafe(row.time_archived) : undefined,
},
})
}

View file

@ -171,6 +171,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
},
"session.moved": () => Effect.void,
"session.renamed": () => Effect.void,
"session.archived": () => Effect.void,
"session.deleted": () => Effect.void,
"session.forked": () => Effect.void,
"session.input.promoted": () => Effect.void,

View file

@ -609,6 +609,17 @@ const layer = Layer.effectDiscard(
.run()
.pipe(Effect.orDie),
)
yield* events.project(SessionEvent.Archived, (event) =>
db
.update(SessionTable)
.set({
time_archived: DateTime.toEpochMillis(event.created),
time_updated: DateTime.toEpochMillis(event.created),
})
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie),
)
yield* events.project(SessionEvent.UsageRecorded, (event) => applyUsage(db, event.data.sessionID, event.data))
yield* events.project(SessionEvent.Forked, (event) => projectFork(db, event))
yield* events.project(SessionEvent.InputPromoted, (event) =>