feat(guardrails): steering channel + shell-in-file rule + capability-gap detector
Bundles three operator-reliability guardrails (Vikunja #28/#29/#30) plus the in-flight branch WIP they were built on top of (reasoning_content capture, operator/project profile editor, write-jail workspaceRoot fix) — the tree is interdependent (SessionOrchestrator references reasoningArtifactId from the WIP) and does not compile as separable subsets, so it lands as one commit. Guardrails: - #28 mid-stage steering: ClientMessage.SteerSession -> GlobalStreamHandler -> orchestrator.submitSteering, reusing SteeringNoteAddedEvent + existing context fold (advisory, non-authoritative; invariants #3/#7). Closes the gap where steering typed off an approval gate was silently dropped. - #29 shell-in-file guardrail: ShellInFileContentRule (core:toolintent) blocks a file_write whose content is a bare shell command (e.g. "mkdir -p ..."); FileWriteTool description now advertises auto-mkdir of parent dirs. Basename-allowlist so the extensionless case is caught; scripts/Makefiles/multiline exempt. - #30 pt1 capability-gap detector: deterministic CapabilityGapDetector maps stage intent -> implied ToolCapability, compares to granted tools, emits advisory CapabilityGapDetectedEvent in FreestyleDriver.lockAndRun. Recorded, never fails the gate and never auto-grants (invariants #3/#4/#5). Reflection rung is pt2. Verified: ./gradlew check green (whole tree).
This commit is contained in:
@@ -324,6 +324,10 @@ func (m Model) handleNormalKey(k keyMsg) (tea.Model, tea.Cmd) {
|
||||
m.openGrants()
|
||||
case "g":
|
||||
m.openConfig()
|
||||
case "P":
|
||||
m.openProjectProfile()
|
||||
case "O":
|
||||
m.openOperatorProfile()
|
||||
case "m":
|
||||
m.openModelsOverlay()
|
||||
case "w":
|
||||
@@ -658,6 +662,13 @@ func (m Model) handleOverlayKey(k keyMsg) (tea.Model, tea.Cmd) {
|
||||
if m.overlay == OverlayConfig {
|
||||
return m.handleConfigKey(k)
|
||||
}
|
||||
// Project/operator profile overlays own all their keys, same reason as config.
|
||||
if m.overlay == OverlayProjectProfile {
|
||||
return m.handleProjectProfileKey(k)
|
||||
}
|
||||
if m.overlay == OverlayOperatorProfile {
|
||||
return m.handleOperatorProfileKey(k)
|
||||
}
|
||||
// Sessions overlay owns all its keys: enter resumes (emits a cmd), so it can't
|
||||
// share the generic no-cmd esc path below.
|
||||
if m.overlay == OverlaySessions {
|
||||
|
||||
Reference in New Issue
Block a user