Skip to content

Commit 6afdeb1

Browse files
committed
feat: add TTL-based cleanup worker
Phase 3: Background cleanup worker implementation - Add module-level cleanupInterval variable for worker management - Implement purgeExpiredMessages() with TTL-based logic: * Only purges messages beyond message_threshold * Checks lastViewed timestamp against min_ttl * Purges oldest->newest with configurable delay * Stops at first non-expired message (can't skip middle) - Add startCleanupWorker() to begin periodic cleanup * Runs every cleanup_interval seconds * Only starts when config.enabled is true - Add stopCleanupWorker() to halt cleanup and cleanup resources - Worker removes message, parts, and view data for expired messages
1 parent 425458d commit 6afdeb1

File tree

1 file changed

+72
-0
lines changed
  • packages/opencode/src/cli/cmd/tui/context

1 file changed

+72
-0
lines changed

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { batch, onMount } from "solid-js"
2929
import { Log } from "@/util/log"
3030
import type { Path } from "@opencode-ai/sdk"
3131

32+
// Module-level cleanup worker state
33+
let cleanupInterval: Timer | null = null
34+
3235
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
3336
name: "Sync",
3437
init: () => {
@@ -387,6 +390,55 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
387390
bootstrap()
388391
})
389392

393+
// Cleanup worker functions
394+
async function purgeExpiredMessages(
395+
sessionID: string,
396+
config: {
397+
message_threshold?: number
398+
min_ttl?: number
399+
purge_delay?: number
400+
},
401+
) {
402+
const messages = store.message[sessionID]
403+
const threshold = config.message_threshold ?? 100
404+
405+
if (!messages || messages.length <= threshold) return
406+
407+
const viewData = store.message_view[sessionID] || {}
408+
const now = Date.now()
409+
const minTTL = (config.min_ttl ?? 120) * 1000
410+
const purgeDelay = (config.purge_delay ?? 0.1) * 1000
411+
412+
const candidatesForPurge = messages.slice(0, messages.length - threshold)
413+
414+
for (let i = 0; i < candidatesForPurge.length; i++) {
415+
const msg = candidatesForPurge[i]
416+
const viewInfo = viewData[msg.id]
417+
418+
const lastViewed = viewInfo?.lastViewed || msg.time.created
419+
const timeSinceViewed = now - lastViewed
420+
421+
if (timeSinceViewed >= minTTL) {
422+
setStore(
423+
produce((draft) => {
424+
const idx = draft.message[sessionID].findIndex((m) => m.id === msg.id)
425+
if (idx !== -1) {
426+
draft.message[sessionID].splice(idx, 1)
427+
delete draft.part[msg.id]
428+
if (draft.message_view[sessionID]) {
429+
delete draft.message_view[sessionID][msg.id]
430+
}
431+
}
432+
}),
433+
)
434+
435+
await new Promise((resolve) => setTimeout(resolve, purgeDelay))
436+
} else {
437+
break
438+
}
439+
}
440+
}
441+
390442
const fullSyncedSessions = new Set<string>()
391443
const result = {
392444
data: store,
@@ -501,6 +553,26 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
501553
}),
502554
)
503555
},
556+
startCleanupWorker() {
557+
if (cleanupInterval) return
558+
559+
const historyConfig = store.config.tui?.history_auto_scroll
560+
if (!historyConfig?.enabled) return
561+
562+
const interval = (historyConfig.cleanup_interval ?? 30) * 1000
563+
564+
cleanupInterval = setInterval(() => {
565+
for (const sessionID of Object.keys(store.message)) {
566+
purgeExpiredMessages(sessionID, historyConfig)
567+
}
568+
}, interval)
569+
},
570+
stopCleanupWorker() {
571+
if (cleanupInterval) {
572+
clearInterval(cleanupInterval)
573+
cleanupInterval = null
574+
}
575+
},
504576
bootstrap,
505577
}
506578
return result

0 commit comments

Comments
 (0)