package focus import ( "context" "encoding/json" "errors" "fmt" "time" "keel/internal/evidence" "keel/internal/mode" "keel/internal/mode/focus/domain" ) // Compile-time assertions: focus.Mode must satisfy the harness contracts. var _ mode.Mode = (*Mode)(nil) var _ mode.EvidenceConsumer = (*Mode)(nil) var _ mode.Expirer = (*Mode)(nil) // Kind identifies this mode to the harness. func (c *Mode) Kind() string { return "focus" } var errBadPayload = errors.New("focus: invalid command payload") type commitmentPayload struct { NextAction string `json:"next_action"` SuccessCondition string `json:"success_condition"` TimeboxSecs int64 `json:"timebox_secs"` AllowedWindowClasses []string `json:"allowed_window_classes"` Enforce bool `json:"enforce"` } // Command maps surface command names onto the existing focus transitions. func (c *Mode) Command(ctx context.Context, name string, payload json.RawMessage) error { switch name { case "planning": return c.EnterPlanning() case "coach": var r struct { Intent string `json:"intent"` } if err := json.Unmarshal(payload, &r); err != nil { return errBadPayload } return c.RequestCoach(r.Intent) case "commitment": var r commitmentPayload if err := json.Unmarshal(payload, &r); err != nil { return errBadPayload } level := domain.EnforcementWarn if r.Enforce { level = domain.EnforcementBlock } return c.StartManualCommitment(r.NextAction, r.SuccessCondition, time.Duration(r.TimeboxSecs)*time.Second, r.AllowedWindowClasses, level) case "complete": return c.Complete() case "end": return c.End() case "refocus": return c.Refocus() case "ontask": return c.OnTask() default: return fmt.Errorf("focus: unknown command %q", name) } } // View returns the focus state projection (the existing State shape). func (c *Mode) View() any { return c.State() } // Active is true from Planning through Review. End() drives the runtime back to // Locked, which the harness reads as "done" and releases. func (c *Mode) Active() bool { c.mu.Lock() defer c.mu.Unlock() return c.runtimeState != domain.RuntimeLocked } // OnWindow forwards the sensor stream to the existing evidence recorder. func (c *Mode) OnWindow(w evidence.WindowSnapshot) { c.RecordWindow(w) }