fix(server): bind project profile on chat-session start
Chat sessions never emitted ProjectProfileBoundEvent — only the workflow path did — so router triage always saw a null profile and could not cite conventions or commands. Extract the binding from launchSessionRun into ServerModule.bindProjectProfile and call it from handleStartChatSession.
This commit is contained in:
@@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.filter
|
|||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(ServerModule::class.java)
|
private val log = LoggerFactory.getLogger(ServerModule::class.java)
|
||||||
@@ -220,35 +221,7 @@ class ServerModule(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Bind the per-repo project profile (curated .correx/project.toml) as an event so
|
bindProjectProfile(sessionId)
|
||||||
// stages and replay read the recorded snapshot, never the live file (invariants #8/#9).
|
|
||||||
val workspaceRoot = runCatching {
|
|
||||||
sessionRepository.getSession(sessionId).state.boundWorkspace?.workspaceRoot
|
|
||||||
}.getOrNull() ?: projectMemory?.repoRoot()
|
|
||||||
workspaceRoot?.let { root ->
|
|
||||||
val projectProfile = ProjectProfileLoader.load(root)
|
|
||||||
if (!projectProfile.isEmpty()) {
|
|
||||||
eventStore.append(
|
|
||||||
NewEvent(
|
|
||||||
metadata = EventMetadata(
|
|
||||||
eventId = EventId(java.util.UUID.randomUUID().toString()),
|
|
||||||
sessionId = sessionId,
|
|
||||||
timestamp = Clock.System.now(),
|
|
||||||
schemaVersion = 1,
|
|
||||||
causationId = null,
|
|
||||||
correlationId = null,
|
|
||||||
),
|
|
||||||
payload = ProjectProfileBoundEvent(
|
|
||||||
sessionId = sessionId,
|
|
||||||
workspaceRoot = root,
|
|
||||||
about = projectProfile.about,
|
|
||||||
conventions = projectProfile.conventions,
|
|
||||||
commands = projectProfile.commands,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runCatching {
|
runCatching {
|
||||||
orchestrator.run(sessionId, graph, sessionConfig)
|
orchestrator.run(sessionId, graph, sessionConfig)
|
||||||
// After a successful freestyle planning run, lock the plan and execute phase 2.
|
// After a successful freestyle planning run, lock the plan and execute phase 2.
|
||||||
@@ -290,6 +263,38 @@ class ServerModule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind the per-repo project profile (curated .correx/project.toml) as an event so stages,
|
||||||
|
* router chat triage, and replay read the recorded snapshot, never the live file
|
||||||
|
* (invariants #8/#9). Shared by the workflow path and the chat-session path.
|
||||||
|
*/
|
||||||
|
suspend fun bindProjectProfile(sessionId: SessionId) {
|
||||||
|
val workspaceRoot = runCatching {
|
||||||
|
sessionRepository.getSession(sessionId).state.boundWorkspace?.workspaceRoot
|
||||||
|
}.getOrNull() ?: projectMemory?.repoRoot() ?: return
|
||||||
|
val projectProfile = withContext(Dispatchers.IO) { ProjectProfileLoader.load(workspaceRoot) }
|
||||||
|
if (projectProfile.isEmpty()) return
|
||||||
|
eventStore.append(
|
||||||
|
NewEvent(
|
||||||
|
metadata = EventMetadata(
|
||||||
|
eventId = EventId(java.util.UUID.randomUUID().toString()),
|
||||||
|
sessionId = sessionId,
|
||||||
|
timestamp = Clock.System.now(),
|
||||||
|
schemaVersion = 1,
|
||||||
|
causationId = null,
|
||||||
|
correlationId = null,
|
||||||
|
),
|
||||||
|
payload = ProjectProfileBoundEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
workspaceRoot = workspaceRoot,
|
||||||
|
about = projectProfile.about,
|
||||||
|
conventions = projectProfile.conventions,
|
||||||
|
commands = projectProfile.commands,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun launchSessionResume(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
private fun launchSessionResume(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) {
|
||||||
activeSessionJobs.computeIfAbsent(sessionId) {
|
activeSessionJobs.computeIfAbsent(sessionId) {
|
||||||
moduleScope.launch {
|
moduleScope.launch {
|
||||||
|
|||||||
@@ -379,6 +379,11 @@ class GlobalStreamHandler(private val module: ServerModule) {
|
|||||||
payload = ChatSessionStartedEvent(sessionId = sessionId),
|
payload = ChatSessionStartedEvent(sessionId = sessionId),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
// Bind the per-repo project profile so router triage can cite conventions/commands.
|
||||||
|
// Parity with launchSessionRun — chat sessions previously never bound a profile.
|
||||||
|
runCatching { module.bindProjectProfile(sessionId) }
|
||||||
|
.onFailure { log.warn("project profile binding failed for chat session={}: {}", sessionId.value, it.message) }
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
module.routerFacade.onUserInput(sessionId, msg.text)
|
module.routerFacade.onUserInput(sessionId, msg.text)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|||||||
Reference in New Issue
Block a user