@pennyfarthing/core 11.3.8 → 11.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +4 -1
- package/packages/core/dist/public/css/react.css +1 -1
- package/packages/core/dist/public/js/react/react.js +24 -24
- package/packages/core/src/public/App.tsx +356 -0
- package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
- package/packages/core/src/public/components/AgentPopup.tsx +308 -0
- package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
- package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
- package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
- package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
- package/packages/core/src/public/components/CommandPalette.tsx +554 -0
- package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
- package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
- package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
- package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
- package/packages/core/src/public/components/ControlBar.tsx +636 -0
- package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
- package/packages/core/src/public/components/DiffViewer.tsx +585 -0
- package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
- package/packages/core/src/public/components/Editor.tsx +630 -0
- package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
- package/packages/core/src/public/components/FileTree.tsx +379 -0
- package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
- package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
- package/packages/core/src/public/components/FullFileTree.tsx +237 -0
- package/packages/core/src/public/components/HealthGauge.tsx +181 -0
- package/packages/core/src/public/components/Message.tsx +225 -0
- package/packages/core/src/public/components/MessageList.tsx +98 -0
- package/packages/core/src/public/components/MessageView.tsx +400 -0
- package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
- package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
- package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
- package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
- package/packages/core/src/public/components/QuickActions.tsx +267 -0
- package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
- package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
- package/packages/core/src/public/components/StatsStrip.tsx +162 -0
- package/packages/core/src/public/components/StreamingContent.tsx +77 -0
- package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
- package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
- package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
- package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
- package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
- package/packages/core/src/public/components/ToolStack.tsx +209 -0
- package/packages/core/src/public/components/ToolStatus.tsx +57 -0
- package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
- package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
- package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
- package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
- package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
- package/packages/core/src/public/components/panel-registry.ts +13 -0
- package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
- package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
- package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
- package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
- package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
- package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
- package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
- package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
- package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
- package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
- package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
- package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
- package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
- package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
- package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
- package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
- package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
- package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
- package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
- package/packages/core/src/public/components/panels/index.ts +24 -0
- package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
- package/packages/core/src/public/components/ui/badge.tsx +36 -0
- package/packages/core/src/public/components/ui/button.tsx +57 -0
- package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
- package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
- package/packages/core/src/public/components/ui/command.tsx +151 -0
- package/packages/core/src/public/components/ui/dialog.tsx +120 -0
- package/packages/core/src/public/components/ui/popover.tsx +31 -0
- package/packages/core/src/public/components/ui/progress.tsx +28 -0
- package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
- package/packages/core/src/public/components/ui/select.tsx +157 -0
- package/packages/core/src/public/components/ui/separator.tsx +29 -0
- package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
- package/packages/core/src/public/components/ui/switch.tsx +27 -0
- package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
- package/packages/core/src/public/components/ui/toggle.tsx +43 -0
- package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
- package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
- package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
- package/packages/core/src/public/css/theme-browser.css +550 -0
- package/packages/core/src/public/css/theme-system.css +630 -0
- package/packages/core/src/public/hooks/index.ts +49 -0
- package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
- package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
- package/packages/core/src/public/hooks/useClaude.ts +234 -0
- package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
- package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
- package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
- package/packages/core/src/public/hooks/useComplexity.ts +80 -0
- package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
- package/packages/core/src/public/hooks/useDependencies.ts +82 -0
- package/packages/core/src/public/hooks/useDiffs.ts +143 -0
- package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
- package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
- package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
- package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
- package/packages/core/src/public/hooks/useHotspots.ts +123 -0
- package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
- package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
- package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
- package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
- package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
- package/packages/core/src/public/hooks/usePersona.ts +112 -0
- package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
- package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
- package/packages/core/src/public/hooks/useSprint.ts +157 -0
- package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
- package/packages/core/src/public/hooks/useStory.ts +135 -0
- package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
- package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
- package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
- package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
- package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
- package/packages/core/src/public/hooks/useTodos.ts +93 -0
- package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
- package/packages/core/src/public/images/cyclist-dark.png +0 -0
- package/packages/core/src/public/images/cyclist-light.png +0 -0
- package/packages/core/src/public/index.html +14 -0
- package/packages/core/src/public/index.tsx +10 -0
- package/packages/core/src/public/lib/utils.ts +6 -0
- package/packages/core/src/public/styles/dockview-theme.css +376 -0
- package/packages/core/src/public/styles/tailwind.css +4454 -0
- package/packages/core/src/public/types/message.ts +51 -0
- package/packages/core/src/public/utils/avatar-service.ts +73 -0
- package/packages/core/src/public/utils/color-presets.ts +940 -0
- package/packages/core/src/public/utils/font-presets.ts +362 -0
- package/packages/core/src/public/utils/formatDuration.ts +14 -0
- package/packages/core/src/public/utils/markdown.ts +249 -0
- package/packages/core/src/public/utils/messageFilters.ts +128 -0
- package/packages/core/src/public/utils/slash-commands.ts +341 -0
- package/packages/core/src/public/utils/subagent-display.ts +146 -0
- package/packages/core/src/public/utils/syntax.ts +219 -0
- package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
- package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
- package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
- package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/cli.py +0 -1
- package/pennyfarthing-dist/pf/bc/focus.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
- package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
- package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
- package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
- package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
- package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
- package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/settings.py +44 -8
- package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
- package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
- package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
- package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
- package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
- package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ControlBar Component
|
|
3
|
+
*
|
|
4
|
+
* Provides Stop, Reset, Bell Mode, Relay Mode, and Agent Quick Picker controls.
|
|
5
|
+
* Story MSSCI-12729 - Stop/Reset Controls and Escape Key
|
|
6
|
+
* Story MSSCI-12275 - Bell Mode toggle
|
|
7
|
+
* Story MSSCI-12395 - Relay Mode toggle
|
|
8
|
+
* Story MSSCI-14762 - Quick agent picker in control bar
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Stop button visible only when Claude is running
|
|
12
|
+
* - Reset button always visible
|
|
13
|
+
* - Agent quick picker (lightweight dropdown for rapid agent switching)
|
|
14
|
+
* - Bell mode toggle (inject queued messages via PostToolUse hook)
|
|
15
|
+
* - Relay mode toggle (auto-handoff to next agent)
|
|
16
|
+
* - Escape key handler for stopping (single press = interrupt, double = force kill)
|
|
17
|
+
* - Visual feedback for "Stopping..." state
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useEffect, useRef, useCallback, useState, FocusEvent } from 'react';
|
|
21
|
+
import { BellRing, Zap, RotateCcw, UserCog } from 'lucide-react';
|
|
22
|
+
import { Button } from '@/components/ui/button';
|
|
23
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
24
|
+
import { useClaudeContext } from '../contexts/ClaudeContext';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Focus Tracking Hook
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
function useFocusTracking() {
|
|
31
|
+
const [focusedId, setFocusedId] = useState<string | null>(null);
|
|
32
|
+
|
|
33
|
+
const handleFocus = useCallback((id: string) => (e: FocusEvent<HTMLButtonElement>) => {
|
|
34
|
+
setFocusedId(id);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const handleBlur = useCallback(() => {
|
|
38
|
+
setFocusedId(null);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const isFocused = useCallback((id: string) => focusedId === id, [focusedId]);
|
|
42
|
+
|
|
43
|
+
return { handleFocus, handleBlur, isFocused };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Agent Quick Picker
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
interface ThemeAgent {
|
|
51
|
+
role: string;
|
|
52
|
+
character: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ThemeData {
|
|
57
|
+
agents: ThemeAgent[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function AgentQuickPicker({ currentAgent, onAgentSwitch }: { currentAgent: string | null; onAgentSwitch?: (role: string) => void }): React.ReactElement {
|
|
61
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
62
|
+
const [agents, setAgents] = useState<ThemeAgent[]>([]);
|
|
63
|
+
const pickerRef = useRef<HTMLDivElement>(null);
|
|
64
|
+
|
|
65
|
+
// Fetch agent list on mount
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
fetch('/api/theme-agents/full')
|
|
68
|
+
.then(res => res.ok ? res.json() : null)
|
|
69
|
+
.then((data: ThemeData | null) => {
|
|
70
|
+
if (data?.agents) {
|
|
71
|
+
setAgents(data.agents);
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.catch(() => {});
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Close on outside click
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!isOpen) return;
|
|
80
|
+
|
|
81
|
+
const handleMouseDown = (e: MouseEvent) => {
|
|
82
|
+
if (pickerRef.current && !pickerRef.current.contains(e.target as Node)) {
|
|
83
|
+
setIsOpen(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
document.addEventListener('mousedown', handleMouseDown);
|
|
88
|
+
return () => document.removeEventListener('mousedown', handleMouseDown);
|
|
89
|
+
}, [isOpen]);
|
|
90
|
+
|
|
91
|
+
// Close on Escape
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!isOpen) return;
|
|
94
|
+
|
|
95
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
96
|
+
if (e.key === 'Escape') {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
setIsOpen(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
103
|
+
return () => document.removeEventListener('keydown', handleKeyDown, true);
|
|
104
|
+
}, [isOpen]);
|
|
105
|
+
|
|
106
|
+
const handleAgentClick = useCallback((agent: ThemeAgent) => {
|
|
107
|
+
if (agent.role === currentAgent) return;
|
|
108
|
+
onAgentSwitch?.(agent.role);
|
|
109
|
+
setIsOpen(false);
|
|
110
|
+
}, [currentAgent, onAgentSwitch]);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="agent-quick-picker-wrapper" ref={pickerRef}>
|
|
114
|
+
<Tooltip>
|
|
115
|
+
<TooltipTrigger asChild>
|
|
116
|
+
<Button
|
|
117
|
+
variant="ghost"
|
|
118
|
+
size="icon"
|
|
119
|
+
type="button"
|
|
120
|
+
className={`btn-toggle agent-picker-toggle ${isOpen ? 'active' : ''}`}
|
|
121
|
+
data-testid="agent-quick-picker"
|
|
122
|
+
onClick={() => setIsOpen(prev => !prev)}
|
|
123
|
+
aria-label="Switch agent"
|
|
124
|
+
aria-expanded={isOpen}
|
|
125
|
+
aria-haspopup="listbox"
|
|
126
|
+
>
|
|
127
|
+
<UserCog className="h-4 w-4" />
|
|
128
|
+
</Button>
|
|
129
|
+
</TooltipTrigger>
|
|
130
|
+
<TooltipContent>Switch Agent</TooltipContent>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
|
|
133
|
+
{isOpen && (
|
|
134
|
+
<div
|
|
135
|
+
className="agent-quick-picker-dropdown"
|
|
136
|
+
data-testid="agent-quick-picker-dropdown"
|
|
137
|
+
role="listbox"
|
|
138
|
+
aria-label="Available agents"
|
|
139
|
+
>
|
|
140
|
+
{agents.map(agent => {
|
|
141
|
+
const isCurrent = agent.role === currentAgent;
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
key={agent.role}
|
|
145
|
+
className={`agent-quick-picker-option ${isCurrent ? 'current' : ''}`}
|
|
146
|
+
data-testid={`agent-option-${agent.role}`}
|
|
147
|
+
role="option"
|
|
148
|
+
aria-selected={isCurrent}
|
|
149
|
+
aria-label={`${agent.role} (${agent.character})`}
|
|
150
|
+
title={agent.character}
|
|
151
|
+
onClick={() => handleAgentClick(agent)}
|
|
152
|
+
>
|
|
153
|
+
<span className="agent-option-role">{agent.role}</span>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// Types
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
export interface ControlBarProps {
|
|
168
|
+
/** Whether Claude is currently running */
|
|
169
|
+
isRunning: boolean;
|
|
170
|
+
/** Whether stop is in progress */
|
|
171
|
+
isStopping?: boolean;
|
|
172
|
+
/** Called when stop button clicked or Escape pressed */
|
|
173
|
+
onStop: () => void;
|
|
174
|
+
/** Called on double Escape for force kill */
|
|
175
|
+
onForceStop?: () => void;
|
|
176
|
+
/** Called when reset button clicked */
|
|
177
|
+
onReset: () => void;
|
|
178
|
+
/** Bell mode state (inject queued messages via hook) */
|
|
179
|
+
bellMode?: boolean;
|
|
180
|
+
/** Relay mode state (auto-handoff to next agent) */
|
|
181
|
+
relayMode?: boolean;
|
|
182
|
+
/** Called when bell mode toggle clicked */
|
|
183
|
+
onBellModeChange?: (enabled: boolean) => void;
|
|
184
|
+
/** Called when relay mode toggle clicked */
|
|
185
|
+
onRelayModeChange?: (enabled: boolean) => void;
|
|
186
|
+
/** Context percentage for TirePump visibility */
|
|
187
|
+
contextPercent?: number;
|
|
188
|
+
/** Current agent slug for TirePump reload */
|
|
189
|
+
currentAgent?: string | null;
|
|
190
|
+
/** Called when TirePump button clicked */
|
|
191
|
+
onTirePump?: () => void;
|
|
192
|
+
/** Called when agent is selected from quick picker */
|
|
193
|
+
onAgentSwitch?: (role: string) => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// ControlBar Component
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
export function ControlBar({
|
|
201
|
+
isRunning,
|
|
202
|
+
isStopping = false,
|
|
203
|
+
onStop,
|
|
204
|
+
onForceStop,
|
|
205
|
+
onReset,
|
|
206
|
+
bellMode = false,
|
|
207
|
+
relayMode = false,
|
|
208
|
+
onBellModeChange,
|
|
209
|
+
onRelayModeChange,
|
|
210
|
+
contextPercent = 0,
|
|
211
|
+
currentAgent = null,
|
|
212
|
+
onTirePump,
|
|
213
|
+
onAgentSwitch,
|
|
214
|
+
}: ControlBarProps): React.ReactElement {
|
|
215
|
+
const lastEscapeTime = useRef<number>(0);
|
|
216
|
+
const DOUBLE_PRESS_THRESHOLD = 500; // ms
|
|
217
|
+
const { handleFocus, handleBlur, isFocused } = useFocusTracking();
|
|
218
|
+
|
|
219
|
+
// Global Escape key handler
|
|
220
|
+
const handleKeyDown = useCallback(
|
|
221
|
+
(event: KeyboardEvent) => {
|
|
222
|
+
// Only handle Escape
|
|
223
|
+
if (event.key !== 'Escape' && event.keyCode !== 27) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Don't handle if not running
|
|
228
|
+
if (!isRunning) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check for double press
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
const timeSinceLastEscape = now - lastEscapeTime.current;
|
|
235
|
+
|
|
236
|
+
if (timeSinceLastEscape < DOUBLE_PRESS_THRESHOLD && onForceStop) {
|
|
237
|
+
// Double Escape - force kill
|
|
238
|
+
onForceStop();
|
|
239
|
+
} else {
|
|
240
|
+
// Single Escape - normal stop
|
|
241
|
+
onStop();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
lastEscapeTime.current = now;
|
|
245
|
+
},
|
|
246
|
+
[isRunning, onStop, onForceStop]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Register global keydown listener
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
252
|
+
return () => {
|
|
253
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
254
|
+
};
|
|
255
|
+
}, [handleKeyDown]);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<TooltipProvider delayDuration={300}>
|
|
259
|
+
<div className="control-bar" data-testid="control-bar">
|
|
260
|
+
{/* Mode toggles - Agent Picker, Bell, and Relay */}
|
|
261
|
+
<div className="control-bar-toggles">
|
|
262
|
+
{/* Agent Quick Picker */}
|
|
263
|
+
<AgentQuickPicker currentAgent={currentAgent} onAgentSwitch={onAgentSwitch} />
|
|
264
|
+
|
|
265
|
+
{/* Bell Mode Toggle */}
|
|
266
|
+
<Tooltip>
|
|
267
|
+
<TooltipTrigger asChild>
|
|
268
|
+
<Button
|
|
269
|
+
variant="ghost"
|
|
270
|
+
size="icon"
|
|
271
|
+
type="button"
|
|
272
|
+
className={`btn-toggle bell-toggle ${bellMode ? 'active' : ''}`}
|
|
273
|
+
data-testid="bell-mode-toggle"
|
|
274
|
+
onClick={() => onBellModeChange?.(!bellMode)}
|
|
275
|
+
aria-pressed={bellMode}
|
|
276
|
+
aria-label="Bell mode - inject queued messages via hook"
|
|
277
|
+
>
|
|
278
|
+
<BellRing className="h-4 w-4" />
|
|
279
|
+
</Button>
|
|
280
|
+
</TooltipTrigger>
|
|
281
|
+
<TooltipContent>Bell Mode: Inject queued messages during tool use (Cmd+B)</TooltipContent>
|
|
282
|
+
</Tooltip>
|
|
283
|
+
|
|
284
|
+
{/* Relay Mode Toggle */}
|
|
285
|
+
<Tooltip>
|
|
286
|
+
<TooltipTrigger asChild>
|
|
287
|
+
<Button
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="icon"
|
|
290
|
+
type="button"
|
|
291
|
+
className={`btn-toggle relay-toggle ${relayMode ? 'active' : ''}`}
|
|
292
|
+
data-testid="relay-toggle"
|
|
293
|
+
onClick={() => onRelayModeChange?.(!relayMode)}
|
|
294
|
+
aria-pressed={relayMode}
|
|
295
|
+
aria-label="Relay mode - auto-handoff to next agent"
|
|
296
|
+
>
|
|
297
|
+
<Zap className="h-4 w-4" />
|
|
298
|
+
</Button>
|
|
299
|
+
</TooltipTrigger>
|
|
300
|
+
<TooltipContent>Relay Mode: Auto-handoff to next agent (Cmd+4)</TooltipContent>
|
|
301
|
+
</Tooltip>
|
|
302
|
+
|
|
303
|
+
{/* TirePump Button - always visible, warning style at 70%+ */}
|
|
304
|
+
<Tooltip>
|
|
305
|
+
<TooltipTrigger asChild>
|
|
306
|
+
<Button
|
|
307
|
+
variant="ghost"
|
|
308
|
+
size="icon"
|
|
309
|
+
type="button"
|
|
310
|
+
className={`btn-toggle pump-toggle ${contextPercent >= 70 ? 'warning' : ''}`}
|
|
311
|
+
data-testid="pump-toggle"
|
|
312
|
+
onClick={onTirePump}
|
|
313
|
+
disabled={!currentAgent}
|
|
314
|
+
aria-label="TirePump: Clear context and reload agent"
|
|
315
|
+
>
|
|
316
|
+
<RotateCcw className="h-4 w-4" />
|
|
317
|
+
</Button>
|
|
318
|
+
</TooltipTrigger>
|
|
319
|
+
<TooltipContent>{currentAgent ? `TirePump: Clear context (${contextPercent}%) and reload ${currentAgent}` : 'TirePump: No agent loaded'}</TooltipContent>
|
|
320
|
+
</Tooltip>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Stop button - always visible, disabled when not running */}
|
|
324
|
+
<Button
|
|
325
|
+
variant="destructive"
|
|
326
|
+
type="button"
|
|
327
|
+
className={`btn-stop danger ${isStopping ? 'stopping' : ''} ${isRunning && !isStopping ? 'throbbing' : ''} ${isFocused('stop') ? 'focused focus-visible' : ''}`}
|
|
328
|
+
data-testid="stop-button"
|
|
329
|
+
onClick={onStop}
|
|
330
|
+
disabled={!isRunning || isStopping}
|
|
331
|
+
aria-busy={isStopping}
|
|
332
|
+
aria-label="Stop Claude"
|
|
333
|
+
onFocus={handleFocus('stop')}
|
|
334
|
+
onBlur={handleBlur}
|
|
335
|
+
>
|
|
336
|
+
<span data-icon="stop" className="icon" />
|
|
337
|
+
{isStopping ? (
|
|
338
|
+
<>
|
|
339
|
+
<span className="spinner" data-loading />
|
|
340
|
+
Stopping...
|
|
341
|
+
</>
|
|
342
|
+
) : (
|
|
343
|
+
'Stop'
|
|
344
|
+
)}
|
|
345
|
+
</Button>
|
|
346
|
+
|
|
347
|
+
{/* Reset button - always visible */}
|
|
348
|
+
<Button
|
|
349
|
+
variant="outline"
|
|
350
|
+
type="button"
|
|
351
|
+
className={`btn-reset ${isFocused('reset') ? 'focused focus-visible' : ''}`}
|
|
352
|
+
data-testid="reset-button"
|
|
353
|
+
onClick={onReset}
|
|
354
|
+
aria-label="Reset session"
|
|
355
|
+
onFocus={handleFocus('reset')}
|
|
356
|
+
onBlur={handleBlur}
|
|
357
|
+
>
|
|
358
|
+
Reset
|
|
359
|
+
</Button>
|
|
360
|
+
</div>
|
|
361
|
+
</TooltipProvider>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// =============================================================================
|
|
366
|
+
// useControlBar Hook
|
|
367
|
+
// =============================================================================
|
|
368
|
+
|
|
369
|
+
interface UseControlBarResult {
|
|
370
|
+
/** Whether Claude is currently running */
|
|
371
|
+
isRunning: boolean;
|
|
372
|
+
/** Whether stop is in progress */
|
|
373
|
+
isStopping: boolean;
|
|
374
|
+
/** Bell mode state */
|
|
375
|
+
bellMode: boolean;
|
|
376
|
+
/** Relay mode state */
|
|
377
|
+
relayMode: boolean;
|
|
378
|
+
/** Context percentage for TirePump */
|
|
379
|
+
contextPercent: number;
|
|
380
|
+
/** Current agent slug for TirePump */
|
|
381
|
+
currentAgent: string | null;
|
|
382
|
+
/** Handle stop action */
|
|
383
|
+
handleStop: () => void;
|
|
384
|
+
/** Handle force stop action (SIGKILL) */
|
|
385
|
+
handleForceStop: () => void;
|
|
386
|
+
/** Handle reset action */
|
|
387
|
+
handleReset: () => void;
|
|
388
|
+
/** Handle bell mode toggle */
|
|
389
|
+
handleBellModeChange: (enabled: boolean) => void;
|
|
390
|
+
/** Handle relay mode toggle */
|
|
391
|
+
handleRelayModeChange: (enabled: boolean) => void;
|
|
392
|
+
/** Handle TirePump action */
|
|
393
|
+
handleTirePump: () => void;
|
|
394
|
+
/** Handle agent switch from quick picker */
|
|
395
|
+
handleAgentSwitch: (role: string) => void;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function useControlBar(): UseControlBarResult {
|
|
399
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
400
|
+
const [isStopping, setIsStopping] = useState(false);
|
|
401
|
+
const [bellMode, setBellMode] = useState(false);
|
|
402
|
+
const [relayMode, setRelayMode] = useState(false);
|
|
403
|
+
const [contextPercent, setContextPercent] = useState(0);
|
|
404
|
+
const [currentAgent, setCurrentAgent] = useState<string | null>(null);
|
|
405
|
+
|
|
406
|
+
// Claude context for WebSocket communication
|
|
407
|
+
const { abort, clear, clearAndReload, send, onMessage, onComplete, onError, isConnected } = useClaudeContext();
|
|
408
|
+
|
|
409
|
+
// Load initial settings and listen for changes (using REST/WebSocket, not IPC)
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
// Handle settings update from any source
|
|
412
|
+
function handleSettingsUpdate(settings: Record<string, unknown>) {
|
|
413
|
+
const workflow = settings?.workflow as Record<string, unknown> | undefined;
|
|
414
|
+
const newBellMode = !!workflow?.bell_mode;
|
|
415
|
+
const newRelayMode = !!workflow?.relay_mode;
|
|
416
|
+
console.log('[ControlBar] Settings updated:', { bellMode: newBellMode, relayMode: newRelayMode });
|
|
417
|
+
setBellMode(newBellMode);
|
|
418
|
+
setRelayMode(newRelayMode);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Load initial settings via REST
|
|
422
|
+
async function loadSettings() {
|
|
423
|
+
try {
|
|
424
|
+
console.log('[ControlBar] Loading settings via REST');
|
|
425
|
+
const response = await fetch('/api/settings');
|
|
426
|
+
if (response.ok) {
|
|
427
|
+
const settings = await response.json();
|
|
428
|
+
handleSettingsUpdate(settings);
|
|
429
|
+
}
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.error('[ControlBar] Failed to load settings:', err);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log('[ControlBar] useEffect mount - loading settings');
|
|
436
|
+
loadSettings();
|
|
437
|
+
|
|
438
|
+
// WebSocket subscription for real-time sync
|
|
439
|
+
console.log('[ControlBar] Connecting to /ws/settings for real-time sync');
|
|
440
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
441
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/settings`);
|
|
442
|
+
|
|
443
|
+
ws.onmessage = (event) => {
|
|
444
|
+
try {
|
|
445
|
+
const data = JSON.parse(event.data);
|
|
446
|
+
if (data.type === 'init' || data.type === 'update') {
|
|
447
|
+
handleSettingsUpdate(data.settings);
|
|
448
|
+
}
|
|
449
|
+
} catch (err) {
|
|
450
|
+
console.error('[ControlBar] Failed to parse WebSocket message:', err);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
ws.onerror = (err) => {
|
|
455
|
+
console.error('[ControlBar] WebSocket error:', err);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
return () => {
|
|
459
|
+
ws.close();
|
|
460
|
+
};
|
|
461
|
+
}, []);
|
|
462
|
+
|
|
463
|
+
// Subscribe to context WebSocket for TirePump visibility
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
466
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/context`);
|
|
467
|
+
|
|
468
|
+
ws.onmessage = (event) => {
|
|
469
|
+
try {
|
|
470
|
+
const data = JSON.parse(event.data);
|
|
471
|
+
if (data.type === 'init' || data.type === 'update') {
|
|
472
|
+
const percent = data.context?.percent ?? 0;
|
|
473
|
+
setContextPercent(percent);
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.error('[ControlBar] Failed to parse context message:', err);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
return () => {
|
|
481
|
+
ws.close();
|
|
482
|
+
};
|
|
483
|
+
}, []);
|
|
484
|
+
|
|
485
|
+
// Subscribe to persona WebSocket for current agent
|
|
486
|
+
useEffect(() => {
|
|
487
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
488
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/persona`);
|
|
489
|
+
|
|
490
|
+
ws.onmessage = (event) => {
|
|
491
|
+
try {
|
|
492
|
+
const persona = JSON.parse(event.data);
|
|
493
|
+
// slug is the agent role (dev, sm, tea, reviewer, etc.)
|
|
494
|
+
setCurrentAgent(persona?.slug ?? null);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.error('[ControlBar] Failed to parse persona message:', err);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
return () => {
|
|
501
|
+
ws.close();
|
|
502
|
+
};
|
|
503
|
+
}, []);
|
|
504
|
+
|
|
505
|
+
// Listen for Claude running state changes via WebSocket context
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
if (!isConnected) return;
|
|
508
|
+
|
|
509
|
+
// Track when Claude starts/completes
|
|
510
|
+
const handleMessage = () => {
|
|
511
|
+
setIsRunning(true);
|
|
512
|
+
setIsStopping(false);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const handleComplete = () => {
|
|
516
|
+
setIsRunning(false);
|
|
517
|
+
setIsStopping(false);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const handleError = () => {
|
|
521
|
+
setIsRunning(false);
|
|
522
|
+
setIsStopping(false);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// Subscribe to events via context
|
|
526
|
+
const cleanupMessage = onMessage(handleMessage);
|
|
527
|
+
const cleanupComplete = onComplete(handleComplete);
|
|
528
|
+
const cleanupError = onError(handleError);
|
|
529
|
+
|
|
530
|
+
return () => {
|
|
531
|
+
cleanupMessage();
|
|
532
|
+
cleanupComplete();
|
|
533
|
+
cleanupError();
|
|
534
|
+
};
|
|
535
|
+
}, [isConnected, onMessage, onComplete, onError]);
|
|
536
|
+
|
|
537
|
+
const handleStop = useCallback(() => {
|
|
538
|
+
setIsStopping(true);
|
|
539
|
+
try {
|
|
540
|
+
// Use abort() via WebSocket
|
|
541
|
+
abort();
|
|
542
|
+
} catch (err) {
|
|
543
|
+
console.error('[ControlBar] Stop failed:', err);
|
|
544
|
+
}
|
|
545
|
+
}, [abort]);
|
|
546
|
+
|
|
547
|
+
const handleForceStop = useCallback(() => {
|
|
548
|
+
setIsStopping(true);
|
|
549
|
+
try {
|
|
550
|
+
abort();
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.error('[ControlBar] Force stop failed:', err);
|
|
553
|
+
}
|
|
554
|
+
}, [abort]);
|
|
555
|
+
|
|
556
|
+
const handleReset = useCallback(() => {
|
|
557
|
+
try {
|
|
558
|
+
clear();
|
|
559
|
+
setIsRunning(false);
|
|
560
|
+
setIsStopping(false);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
console.error('[ControlBar] Reset failed:', err);
|
|
563
|
+
}
|
|
564
|
+
}, [clear]);
|
|
565
|
+
|
|
566
|
+
const handleBellModeChange = useCallback(async (enabled: boolean) => {
|
|
567
|
+
try {
|
|
568
|
+
// Use REST API for settings
|
|
569
|
+
await fetch('/api/settings', {
|
|
570
|
+
method: 'PATCH',
|
|
571
|
+
headers: { 'Content-Type': 'application/json' },
|
|
572
|
+
body: JSON.stringify({ workflow: { bell_mode: enabled } }),
|
|
573
|
+
});
|
|
574
|
+
setBellMode(enabled);
|
|
575
|
+
console.log('[ControlBar] Bell mode set to:', enabled);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
console.error('[ControlBar] Failed to toggle bell mode:', err);
|
|
578
|
+
}
|
|
579
|
+
}, []);
|
|
580
|
+
|
|
581
|
+
const handleRelayModeChange = useCallback(async (enabled: boolean) => {
|
|
582
|
+
try {
|
|
583
|
+
// Use REST API for settings
|
|
584
|
+
await fetch('/api/settings', {
|
|
585
|
+
method: 'PATCH',
|
|
586
|
+
headers: { 'Content-Type': 'application/json' },
|
|
587
|
+
body: JSON.stringify({ workflow: { relay_mode: enabled } }),
|
|
588
|
+
});
|
|
589
|
+
setRelayMode(enabled);
|
|
590
|
+
console.log('[ControlBar] Relay mode set to:', enabled);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
console.error('[ControlBar] Failed to toggle relay mode:', err);
|
|
593
|
+
}
|
|
594
|
+
}, []);
|
|
595
|
+
|
|
596
|
+
// TirePump: Clear context and reload current agent
|
|
597
|
+
const handleTirePump = useCallback(() => {
|
|
598
|
+
if (!currentAgent) {
|
|
599
|
+
console.warn('[ControlBar] Cannot TirePump: no current agent');
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
console.log('[ControlBar] TirePump: clearing context and reloading agent:', currentAgent);
|
|
604
|
+
clearAndReload(currentAgent);
|
|
605
|
+
// Reset local state since session is being cleared
|
|
606
|
+
setIsRunning(false);
|
|
607
|
+
setIsStopping(false);
|
|
608
|
+
setContextPercent(0);
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.error('[ControlBar] TirePump failed:', err);
|
|
611
|
+
}
|
|
612
|
+
}, [currentAgent, clearAndReload]);
|
|
613
|
+
|
|
614
|
+
// Agent quick picker: send /{role} command
|
|
615
|
+
const handleAgentSwitch = useCallback((role: string) => {
|
|
616
|
+
send(`/${role}`);
|
|
617
|
+
}, [send]);
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
isRunning,
|
|
621
|
+
isStopping,
|
|
622
|
+
bellMode,
|
|
623
|
+
relayMode,
|
|
624
|
+
contextPercent,
|
|
625
|
+
currentAgent,
|
|
626
|
+
handleStop,
|
|
627
|
+
handleForceStop,
|
|
628
|
+
handleReset,
|
|
629
|
+
handleBellModeChange,
|
|
630
|
+
handleRelayModeChange,
|
|
631
|
+
handleTirePump,
|
|
632
|
+
handleAgentSwitch,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export default ControlBar;
|