Compare commits

...
1 Commits
Author SHA1 Message Date
NamitandJames Long e2979039f1 fix(core): bump session time_updated on step lifecycle events (#49105)
Co-authored-by: James Long <longster@gmail.com>
2026-10-07 11:40:47 -04:00
2 changed files with 73 additions and 5 deletions

No files matched your search

+25 -5
View File
@@ -55,7 +55,7 @@ const forkTitle = (value?: string) => {
return `${value} (fork #1)`
}
function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage) {
function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usage, timeUpdated?: number) {
return db
.update(SessionTable)
.set({
@@ -65,13 +65,22 @@ function applyUsage(db: DatabaseService, sessionID: SessionSchema.ID, value: Usa
tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning}`,
tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read}`,
tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write}`,
time_updated: sql`${SessionTable.time_updated}`,
time_updated: timeUpdated ?? sql`${SessionTable.time_updated}`,
})
.where(eq(SessionTable.id, sessionID))
.run()
.pipe(Effect.orDie)
}
function touch(db: DatabaseService, event: MessageEvent) {
return db
.update(SessionTable)
.set({ time_updated: event.created })
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
}
const publishSessionUsage = Effect.fn("SessionProjector.publishUsage")(function* (
db: DatabaseService,
bus: Bus.Interface,
@@ -681,19 +690,30 @@ const layer = Layer.effectDiscard(
yield* bus.project(SessionEvent.Skill.Activated, (event) => run(db, event))
yield* bus.project(SessionEvent.Shell.Started, (event) => run(db, event))
yield* bus.project(SessionEvent.Shell.Ended, (event) => run(db, event))
yield* bus.project(SessionEvent.Step.Started, (event) => run(db, event))
yield* bus.project(SessionEvent.Step.Started, (event) =>
Effect.gen(function* () {
yield* run(db, event)
yield* touch(db, event)
}),
)
yield* bus.project(SessionEvent.Step.Streamed, (event) => run(db, event))
yield* bus.project(SessionEvent.Step.Ended, (event) =>
Effect.gen(function* () {
yield* run(db, event)
yield* applyUsage(db, event.data.sessionID, event.data)
yield* applyUsage(db, event.data.sessionID, event.data, event.created)
}),
)
yield* bus.project(SessionEvent.Step.Failed, (event) =>
Effect.gen(function* () {
yield* run(db, event)
if (event.data.cost !== undefined && event.data.tokens !== undefined)
yield* applyUsage(db, event.data.sessionID, { cost: event.data.cost, tokens: event.data.tokens })
yield* applyUsage(
db,
event.data.sessionID,
{ cost: event.data.cost, tokens: event.data.tokens },
event.created,
)
else yield* touch(db, event)
}),
)
yield* bus.project(SessionEvent.Text.Started, (event) => run(db, event))
@@ -1,5 +1,6 @@
import { describe, expect } from "bun:test"
import { DateTime, Effect, Fiber, Option, Schema, Stream } from "effect"
import { TestClock } from "effect/testing"
import { asc, eq, sql } from "drizzle-orm"
import { Database } from "@opencode/core/database/database"
import { Agent } from "@opencode/core/agent"
@@ -760,4 +761,51 @@ describe("SessionProjector", () => {
])
}),
)
it.effect("bumps session time_updated on step lifecycle events", () =>
Effect.gen(function* () {
const db = yield* seedSession({ time_created: 0, time_updated: 0 })
const bus = yield* Bus.Service
const updated = () =>
db.select({ time_updated: SessionTable.time_updated }).from(SessionTable).get().pipe(Effect.orDie)
const first = SessionMessage.ID.make("msg_touch_first")
const second = SessionMessage.ID.make("msg_touch_second")
yield* TestClock.setTime(5)
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: first,
agent: build,
model,
started: 5,
})
expect(yield* updated()).toEqual({ time_updated: 5 })
yield* TestClock.setTime(9)
yield* bus.publish(SessionEvent.Step.Ended, {
sessionID,
assistantMessageID: first,
finish: "stop",
cost: Money.USD.make(0),
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
})
expect(yield* updated()).toEqual({ time_updated: 9 })
yield* TestClock.setTime(12)
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: second,
agent: build,
model,
started: 12,
})
yield* TestClock.setTime(15)
yield* bus.publish(SessionEvent.Step.Failed, {
sessionID,
assistantMessageID: second,
error: { type: "provider.invalid-request", message: "Failed" },
})
expect(yield* updated()).toEqual({ time_updated: 15 })
}),
)
})