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:
@@ -64,6 +64,8 @@ const (
|
||||
OverlayGrants
|
||||
OverlayGrantScope
|
||||
OverlayHelp
|
||||
OverlayProjectProfile
|
||||
OverlayOperatorProfile
|
||||
)
|
||||
|
||||
// RouterEntry is one line in a session's conversation transcript.
|
||||
@@ -109,6 +111,13 @@ type ManifestTool struct {
|
||||
Tier int
|
||||
}
|
||||
|
||||
// ProfileField is one editable row in the project/operator profile editors — a flat
|
||||
// key/value text field, mirroring the config editor's STRING-field editing model.
|
||||
type ProfileField struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Approval is a pending approval gate for a session.
|
||||
type Approval struct {
|
||||
RequestID string
|
||||
@@ -179,18 +188,20 @@ type Proposal struct {
|
||||
|
||||
// Session is the UI's view of one server session.
|
||||
type Session struct {
|
||||
ID string
|
||||
Status string
|
||||
WorkflowID string
|
||||
Name string
|
||||
LastEventAt int64
|
||||
CurrentStage string
|
||||
WorkspaceRoot string // bound cwd, from session.workspace_bound
|
||||
LastOutput string
|
||||
LastResponse string
|
||||
Tools []ToolRecord
|
||||
ToolsByStage map[string][]ManifestTool
|
||||
Events []EventEntry
|
||||
ID string
|
||||
Status string
|
||||
WorkflowID string
|
||||
Name string
|
||||
LastEventAt int64
|
||||
CurrentStage string
|
||||
WorkspaceRoot string // bound cwd, from session.workspace_bound
|
||||
LastOutput string
|
||||
LastResponse string
|
||||
Tools []ToolRecord
|
||||
ToolsByStage map[string][]ManifestTool
|
||||
TokenBudgetByStage map[string]int // stage -> ceiling, from stage.tool_manifest
|
||||
StageTokensUsed int // tokens used by the most recent inference in CurrentStage
|
||||
Events []EventEntry
|
||||
// Pending is the *current* approval gate shown in the band. It always mirrors
|
||||
// PendingQueue[PendingIdx] (kept in sync by syncPending) so the render code can
|
||||
// keep reading a single pointer while multiple gates queue up behind it.
|
||||
@@ -301,6 +312,29 @@ type Model struct {
|
||||
configRestart []string // keys from the last save that need a restart
|
||||
configLoading bool
|
||||
|
||||
// project profile editor (OverlayProjectProfile) — populated by project_profile.snapshot;
|
||||
// mirrors the config editor's field-list/staged-edit pattern. fields[0] is "about",
|
||||
// fields[1] is "conventions" (semicolon-joined), the rest are one row per command.
|
||||
projectProfileFields []ProfileField
|
||||
projectProfileIndex int
|
||||
projectProfileStaged map[string]string
|
||||
projectProfileEditing bool
|
||||
projectProfileEditBuf string
|
||||
projectProfileError string
|
||||
projectProfileLoading bool
|
||||
|
||||
// operator profile editor (OverlayOperatorProfile) — populated by operator_profile.snapshot.
|
||||
// fields are "about", "approvalMode", "preferredModels" (comma-joined), "conventions"
|
||||
// (semicolon-joined). proposedAdaptation is read-only (from ProfileAdaptationService).
|
||||
operatorProfileFields []ProfileField
|
||||
operatorProfileIndex int
|
||||
operatorProfileStaged map[string]string
|
||||
operatorProfileEditing bool
|
||||
operatorProfileEditBuf string
|
||||
operatorProfileError string
|
||||
operatorProfileLoading bool
|
||||
operatorProfileProposed string
|
||||
|
||||
// session stats (OverlayStats) — populated by the session.stats reply
|
||||
stats *protocol.StatsDto
|
||||
statsFor string // sessionId the current stats belong to
|
||||
@@ -413,20 +447,22 @@ type Model struct {
|
||||
// NewModel builds the initial idle state.
|
||||
func NewModel(client *ws.Client) Model {
|
||||
return Model{
|
||||
client: client,
|
||||
theme: NewTheme(SoftBlue),
|
||||
wfIndex: -1,
|
||||
inputMode: ModeRouter,
|
||||
historyIndex: -1,
|
||||
routerMessages: map[string][]RouterEntry{},
|
||||
configStaged: map[string]string{},
|
||||
chatMode: ChatModeChat,
|
||||
providerType: "LOCAL",
|
||||
snapshotPhase: true,
|
||||
eventStripShown: true,
|
||||
transcriptSel: -1,
|
||||
sbHidden: loadStatusbarHidden(),
|
||||
actionsHidden: loadPrefs().InlineActionsHidden,
|
||||
client: client,
|
||||
theme: NewTheme(SoftBlue),
|
||||
wfIndex: -1,
|
||||
inputMode: ModeRouter,
|
||||
historyIndex: -1,
|
||||
routerMessages: map[string][]RouterEntry{},
|
||||
configStaged: map[string]string{},
|
||||
projectProfileStaged: map[string]string{},
|
||||
operatorProfileStaged: map[string]string{},
|
||||
chatMode: ChatModeChat,
|
||||
providerType: "LOCAL",
|
||||
snapshotPhase: true,
|
||||
eventStripShown: true,
|
||||
transcriptSel: -1,
|
||||
sbHidden: loadStatusbarHidden(),
|
||||
actionsHidden: loadPrefs().InlineActionsHidden,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user