Skip to content

Commit a5bebc7

Browse files
bug: better session id picker
1 parent 0bff589 commit a5bebc7

File tree

5 files changed

+149
-14
lines changed

5 files changed

+149
-14
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "oh-my-opencode-dashboard",
3-
"version": "0.3.1",
3+
"version": "0.3.2-beta.0",
44
"description": "Local-only, read-only dashboard for viewing OhMyOpenCode agent progress",
55
"license": "SUL-1.0",
66
"repository": {

src/ingest/session.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function mkStorageRoot(): string {
1919
}
2020

2121
describe("pickActiveSessionId", () => {
22-
it("prefers last boulder session_id that exists", () => {
22+
it("uses a boulder session_id that exists when no session metadata is available", () => {
2323
const storageRoot = mkStorageRoot()
2424
const storage = getStorageRoots(storageRoot)
2525
const projectRoot = "/tmp/project"
@@ -34,6 +34,45 @@ describe("pickActiveSessionId", () => {
3434
expect(picked).toBe("ses_ok")
3535
})
3636

37+
it("prefers newest main session metadata over a stale boulder session_id", () => {
38+
const storageRoot = mkStorageRoot()
39+
const storage = getStorageRoots(storageRoot)
40+
const projectRoot = "/tmp/project/"
41+
const projectID = "proj_1"
42+
fs.mkdirSync(path.join(storage.session, projectID), { recursive: true })
43+
44+
fs.writeFileSync(
45+
path.join(storage.session, projectID, "ses_old.json"),
46+
JSON.stringify({
47+
id: "ses_old",
48+
projectID,
49+
directory: "/tmp/project",
50+
time: { created: 1, updated: 10 },
51+
}),
52+
"utf8"
53+
)
54+
fs.writeFileSync(
55+
path.join(storage.session, projectID, "ses_new.json"),
56+
JSON.stringify({
57+
id: "ses_new",
58+
projectID,
59+
directory: "/tmp/project",
60+
time: { created: 2, updated: 20 },
61+
}),
62+
"utf8"
63+
)
64+
65+
fs.mkdirSync(path.join(storage.message, "ses_old"), { recursive: true })
66+
fs.mkdirSync(path.join(storage.message, "ses_new"), { recursive: true })
67+
68+
const picked = pickActiveSessionId({
69+
projectRoot,
70+
storage,
71+
boulderSessionIds: ["ses_old"],
72+
})
73+
expect(picked).toBe("ses_new")
74+
})
75+
3776
it("falls back to newest main session metadata for directory", () => {
3877
const storageRoot = mkStorageRoot()
3978
const storage = getStorageRoots(storageRoot)

src/ingest/session.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,53 @@ export function pickActiveSessionId(opts: {
129129
storage: OpenCodeStorageRoots
130130
boulderSessionIds?: string[]
131131
}): string | null {
132+
const metas = readMainSessionMetas(opts.storage.session, opts.projectRoot)
133+
const metaById = new Map(metas.map((m) => [m.id, m] as const))
134+
135+
let bestId: string | null = metas[0]?.id ?? null
136+
let bestUpdated = bestId ? (metaById.get(bestId)?.time.updated ?? -Infinity) : -Infinity
137+
let bestIsBoulder = false
138+
139+
const consider = (candidateId: string, updatedAt: number, isBoulder: boolean): void => {
140+
if (!bestId) {
141+
bestId = candidateId
142+
bestUpdated = updatedAt
143+
bestIsBoulder = isBoulder
144+
return
145+
}
146+
if (updatedAt > bestUpdated) {
147+
bestId = candidateId
148+
bestUpdated = updatedAt
149+
bestIsBoulder = isBoulder
150+
return
151+
}
152+
if (updatedAt === bestUpdated && isBoulder && !bestIsBoulder) {
153+
bestId = candidateId
154+
bestUpdated = updatedAt
155+
bestIsBoulder = true
156+
}
157+
}
158+
132159
const ids = opts.boulderSessionIds ?? []
133160
for (let i = ids.length - 1; i >= 0; i--) {
134161
const id = ids[i]
135-
if (sessionExists(opts.storage.message, id)) return id
162+
if (!sessionExists(opts.storage.message, id)) continue
163+
164+
const meta = metaById.get(id)
165+
if (meta) {
166+
consider(id, meta.time.updated ?? meta.time.created ?? 0, true)
167+
continue
168+
}
169+
170+
if (metas.length === 0) {
171+
const messageDir = getMessageDir(opts.storage.message, id)
172+
const recent = readMostRecentMessageMeta(messageDir, 200)
173+
const created = typeof recent?.time?.created === "number" ? recent.time.created : 0
174+
consider(id, created, true)
175+
}
136176
}
137177

138-
const metas = readMainSessionMetas(opts.storage.session, opts.projectRoot)
139-
return metas[0]?.id ?? null
178+
return bestId
140179
}
141180

142181
function readMostRecentMessageMeta(messageDir: string, maxMessages: number): StoredMessageMeta | null {

src/ingest/sqlite-derive.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,12 @@ describe("sqlite derive helpers", () => {
330330
},
331331
)
332332

333-
it("pickActiveSessionIdSqlite prefers boulder session ids when they have messages", () => {
333+
it("pickActiveSessionIdSqlite prefers newest main session metadata over a stale boulder session_id", () => {
334334
const sqlitePath = mkSqliteDb()
335335
const db = new BunDatabase(sqlitePath)
336336
insertSession(db, { id: "ses_project_latest", directory: "/repo", created: 10, updated: 5000 })
337337
insertSession(db, { id: "ses_boulder", directory: "/repo", created: 20, updated: 100 })
338+
insertMessage(db, { id: "msg_latest", sessionId: "ses_project_latest", created: 5000 })
338339
insertMessage(db, { id: "msg_boulder", sessionId: "ses_boulder", created: 100 })
339340
db.close()
340341

@@ -345,6 +346,25 @@ describe("sqlite derive helpers", () => {
345346
})
346347
expect(result.ok).toBe(true)
347348
if (!result.ok) return
349+
expect(result.value).toBe("ses_project_latest")
350+
})
351+
352+
it("pickActiveSessionIdSqlite prefers a boulder session_id when it is most recent", () => {
353+
const sqlitePath = mkSqliteDb()
354+
const db = new BunDatabase(sqlitePath)
355+
insertSession(db, { id: "ses_project_latest", directory: "/repo", created: 10, updated: 5000 })
356+
insertSession(db, { id: "ses_boulder", directory: "/repo", created: 20, updated: 6000 })
357+
insertMessage(db, { id: "msg_latest", sessionId: "ses_project_latest", created: 5000 })
358+
insertMessage(db, { id: "msg_boulder", sessionId: "ses_boulder", created: 6000 })
359+
db.close()
360+
361+
const result = pickActiveSessionIdSqlite({
362+
sqlitePath,
363+
projectRoot: "/repo",
364+
boulderSessionIds: ["ses_boulder"],
365+
})
366+
expect(result.ok).toBe(true)
367+
if (!result.ok) return
348368
expect(result.value).toBe("ses_boulder")
349369
})
350370
})

src/ingest/sqlite-derive.ts

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,22 +249,59 @@ export function pickActiveSessionIdSqlite(opts: {
249249
projectRoot: string
250250
boulderSessionIds?: string[]
251251
}): SqliteDeriveResult<string | null> {
252+
const metasResult = readMainSessionMetasSqlite({
253+
sqlitePath: opts.sqlitePath,
254+
directoryFilter: opts.projectRoot,
255+
})
256+
if (!metasResult.ok) return metasResult
257+
258+
const metas = metasResult.rows
259+
const metaById = new Map(metas.map((m) => [m.id, m] as const))
260+
261+
let bestId: string | null = metas[0]?.id ?? null
262+
let bestUpdated = bestId ? (metaById.get(bestId)?.time.updated ?? -Infinity) : -Infinity
263+
let bestIsBoulder = false
264+
265+
const consider = (candidateId: string, updatedAt: number, isBoulder: boolean): void => {
266+
if (!bestId) {
267+
bestId = candidateId
268+
bestUpdated = updatedAt
269+
bestIsBoulder = isBoulder
270+
return
271+
}
272+
if (updatedAt > bestUpdated) {
273+
bestId = candidateId
274+
bestUpdated = updatedAt
275+
bestIsBoulder = isBoulder
276+
return
277+
}
278+
if (updatedAt === bestUpdated && isBoulder && !bestIsBoulder) {
279+
bestId = candidateId
280+
bestUpdated = updatedAt
281+
bestIsBoulder = true
282+
}
283+
}
284+
252285
const ids = opts.boulderSessionIds ?? []
253286
for (let i = ids.length - 1; i >= 0; i--) {
254287
const id = ids[i]
255288
const messages = readRecentMessageMetasSqlite({ sqlitePath: opts.sqlitePath, sessionId: id, limit: 1 })
256289
if (!messages.ok) return messages
257-
if (messages.rows.length > 0) {
258-
return { ok: true, value: id }
290+
if (messages.rows.length === 0) continue
291+
292+
const meta = metaById.get(id)
293+
if (meta) {
294+
consider(id, meta.time.updated ?? meta.time.created ?? 0, true)
295+
continue
296+
}
297+
298+
if (metas.length === 0) {
299+
const created = typeof messages.rows[0]?.time?.created === "number" ? messages.rows[0].time.created : 0
300+
consider(id, created, true)
259301
}
260302
}
261303

262-
const metas = readMainSessionMetasSqlite({
263-
sqlitePath: opts.sqlitePath,
264-
directoryFilter: opts.projectRoot,
265-
})
266-
if (!metas.ok) return metas
267-
return { ok: true, value: metas.rows[0]?.id ?? null }
304+
return { ok: true, value: bestId }
268305
}
269306

270307
export function getMainSessionViewSqlite(opts: {

0 commit comments

Comments
 (0)