feat(tui,server,config): live config editor + artifact viewer
Two operator features over the existing WebSocket protocol, plus a tui-go nav fix. Config editor (g / palette "config"): - core:config gains a thin registry stack: OrchestrationKnobs ([orchestration] block), CorrexConfigWriter (round-trip TOML serializer; parseToml(write(c))==c), ConfigHolder (AtomicReference live config), and EditableConfig (allowlist of editable fields + patch applier; security fields are absent so they can never be patched). - ConfigService validates -> persists (atomic temp+move) -> swaps the holder -> rebuilds config-derived services. Live-apply is scoped to safe seams: stage timeout, compaction threshold (now a supplier), router knobs (routerFacade rebuild), and personalization/project toggles all take effect on the next session/turn; in-flight sessions keep the config they started with. server.host/port are persisted + badged "restart required", not hot-swapped. - Protocol: GetConfig / UpdateConfig / ConfigSnapshot; TUI overlay with type-aware editing (bool toggle, enum cycle, inline int/string edit) and save. Artifact viewer (v / palette "artifacts"): - Server assembles a session's artifacts from the event log and resolves bytes from the CAS store (StreamQueries.listArtifacts) — a replay-neutral snapshot read. TUI overlay lists artifacts and shows scrollable content. tui-go fix: workflow failure no longer clears selectedID (which yanked the user to the list); listNav lands on the first session when nothing is selected instead of wrapping to a random one. Config is not event-sourced (it lives in TOML); editing stays outside the log.
This commit is contained in:
+8
-2
@@ -10,15 +10,21 @@ import com.correx.core.journal.model.Salience
|
||||
class JournalCompactionService(
|
||||
private val artifactStore: ArtifactStore,
|
||||
private val summarize: suspend (String) -> String,
|
||||
val tokenThreshold: Int = 2000,
|
||||
// Supplier (not a constant) so the threshold can track live config edits without rebuilding
|
||||
// the orchestrator: it is read fresh on each compaction check.
|
||||
private val tokenThreshold: () -> Int = { DEFAULT_TOKEN_THRESHOLD },
|
||||
) {
|
||||
companion object {
|
||||
private const val DEFAULT_TOKEN_THRESHOLD = 2000
|
||||
}
|
||||
|
||||
suspend fun compactIfNeeded(
|
||||
sessionId: SessionId,
|
||||
state: DecisionJournalState,
|
||||
renderedTokenEstimate: Int,
|
||||
emit: suspend (EventPayload) -> Unit,
|
||||
): Boolean {
|
||||
if (renderedTokenEstimate < tokenThreshold || state.records.isEmpty()) return false
|
||||
if (renderedTokenEstimate < tokenThreshold() || state.records.isEmpty()) return false
|
||||
|
||||
val throughSequence = state.records.maxOf { it.sequence }
|
||||
val highRecords = state.records.filter { it.kind.salience() == Salience.HIGH }
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ class JournalCompactionServiceTest {
|
||||
|
||||
@Test
|
||||
fun `returns false when token estimate is below threshold`(): Unit = runBlocking {
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { it }, tokenThreshold = 2000)
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { it }, tokenThreshold = { 2000 })
|
||||
val state = stateWithRecords(makeRecord(1, DecisionKind.INTENT))
|
||||
val emitted = mutableListOf<EventPayload>()
|
||||
val result = svc.compactIfNeeded(SessionId("s1"), state, renderedTokenEstimate = 500) { emitted += it }
|
||||
@@ -42,7 +42,7 @@ class JournalCompactionServiceTest {
|
||||
|
||||
@Test
|
||||
fun `returns false when records is empty even if estimate is high`(): Unit = runBlocking {
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { it }, tokenThreshold = 2000)
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { it }, tokenThreshold = { 2000 })
|
||||
val state = DecisionJournalState(records = emptyList())
|
||||
val emitted = mutableListOf<EventPayload>()
|
||||
val result = svc.compactIfNeeded(SessionId("s1"), state, renderedTokenEstimate = 9999) { emitted += it }
|
||||
@@ -52,7 +52,7 @@ class JournalCompactionServiceTest {
|
||||
|
||||
@Test
|
||||
fun `emits JournalCompactedEvent with correct coversThroughSequence and lowSalienceOmittedCount`(): Unit = runBlocking {
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { "summary" }, tokenThreshold = 100)
|
||||
val svc = JournalCompactionService(fakeArtifactStore(), { "summary" }, tokenThreshold = { 100 })
|
||||
val state = stateWithRecords(
|
||||
makeRecord(1, DecisionKind.INTENT), // HIGH
|
||||
makeRecord(2, DecisionKind.TRANSITION), // LOW
|
||||
@@ -75,7 +75,7 @@ class JournalCompactionServiceTest {
|
||||
val svc = JournalCompactionService(
|
||||
artifactStore = fakeArtifactStore(),
|
||||
summarize = { prompt -> capturedPrompts += prompt; "ok" },
|
||||
tokenThreshold = 100,
|
||||
tokenThreshold = { 100 },
|
||||
)
|
||||
val state = stateWithRecords(
|
||||
makeRecord(1, DecisionKind.INTENT), // HIGH
|
||||
|
||||
Reference in New Issue
Block a user