@pennyfarthing/core 11.3.7 → 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 +17 -16
- 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/hooks/cyclist-pretooluse-hook.sh +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/core/agent-session.sh +0 -0
- package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
- package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/core/pf.sh +0 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +0 -0
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/dispatcher-template.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/env.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/run-pf.sh +0 -0
- package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
- package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
- package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
- package/pennyfarthing-dist/workflows/release/steps/step-10-publish.md +41 -9
- 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
- package/scripts/README.md +0 -41
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMarkerActions Hook
|
|
3
|
+
*
|
|
4
|
+
* Detects CYCLIST markers in message content and returns action metadata
|
|
5
|
+
* for rendering QuickActions buttons.
|
|
6
|
+
*
|
|
7
|
+
* Story: MSSCI-12787 - Implement CYCLIST Marker Parsing and Action Buttons
|
|
8
|
+
*
|
|
9
|
+
* @see sprint/context/MSSCI-12787-reference/quick-actions.js.deleted
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useMemo } from 'react';
|
|
13
|
+
import { detectMarkers, stripMarkers, MARKER_TYPES } from '../../shared/browser.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Action types that map to different UI presentations
|
|
17
|
+
*/
|
|
18
|
+
export type ActionType =
|
|
19
|
+
| 'handoff' // Show agent button + "Not yet" option
|
|
20
|
+
| 'yesno' // Show Yes/No buttons
|
|
21
|
+
| 'open' // Show text input
|
|
22
|
+
| 'choices' // Show choice buttons
|
|
23
|
+
| 'continue' // Show Continue button
|
|
24
|
+
| 'invoke' // Auto-execute (no buttons)
|
|
25
|
+
| 'context_clear'; // Clear context and reload
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result from marker detection
|
|
29
|
+
*/
|
|
30
|
+
export interface MarkerAction {
|
|
31
|
+
type: ActionType;
|
|
32
|
+
value?: string;
|
|
33
|
+
responses?: string[];
|
|
34
|
+
choices?: Array<{ number: number; text: string }>;
|
|
35
|
+
autoExecute?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract option text from message content for numbered choices.
|
|
40
|
+
* Looks for patterns like "1. Option text" or "1) Option text".
|
|
41
|
+
*/
|
|
42
|
+
function extractChoiceTexts(
|
|
43
|
+
text: string,
|
|
44
|
+
choiceNumbers: number[]
|
|
45
|
+
): Array<{ number: number; text: string }> {
|
|
46
|
+
if (!text) {
|
|
47
|
+
return choiceNumbers.map(num => ({ number: num, text: `Option ${num}` }));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Remove code blocks
|
|
51
|
+
const withoutCode = text.replace(/```[\s\S]*?```/g, '');
|
|
52
|
+
|
|
53
|
+
// Patterns for numbered lists
|
|
54
|
+
const patterns = [
|
|
55
|
+
/^\s*(\d+)\.\s+(.+)$/gm, // "1. Option text"
|
|
56
|
+
/^\s*(\d+)\)\s+(.+)$/gm, // "1) Option text"
|
|
57
|
+
/\*\*(\d+)[.)]\*\*\s*(.+)/gm, // "**1.** Option text"
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const foundChoices = new Map<number, string>();
|
|
61
|
+
|
|
62
|
+
for (const pattern of patterns) {
|
|
63
|
+
pattern.lastIndex = 0;
|
|
64
|
+
let match;
|
|
65
|
+
while ((match = pattern.exec(withoutCode)) !== null) {
|
|
66
|
+
const num = parseInt(match[1], 10);
|
|
67
|
+
if (choiceNumbers.includes(num) && !foundChoices.has(num)) {
|
|
68
|
+
// Clean up the text - remove trailing markdown/formatting
|
|
69
|
+
let optionText = match[2].trim();
|
|
70
|
+
// Remove trailing description after " - " or " — " for cleaner button labels
|
|
71
|
+
const dashIndex = optionText.search(/\s+[-—]\s+/);
|
|
72
|
+
if (dashIndex > 0 && dashIndex < 30) {
|
|
73
|
+
optionText = optionText.substring(0, dashIndex);
|
|
74
|
+
}
|
|
75
|
+
foundChoices.set(num, optionText);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Return choices in order, falling back to "Option N" if not found
|
|
81
|
+
return choiceNumbers.map(num => ({
|
|
82
|
+
number: num,
|
|
83
|
+
text: foundChoices.get(num) || `Option ${num}`,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Process detected markers into action metadata
|
|
89
|
+
*/
|
|
90
|
+
function processMarkers(
|
|
91
|
+
markers: ReturnType<typeof detectMarkers>,
|
|
92
|
+
fullText: string
|
|
93
|
+
): MarkerAction | null {
|
|
94
|
+
if (!markers || markers.length === 0) return null;
|
|
95
|
+
|
|
96
|
+
const primaryMarker = markers[0];
|
|
97
|
+
|
|
98
|
+
switch (primaryMarker.type) {
|
|
99
|
+
case MARKER_TYPES.HANDOFF:
|
|
100
|
+
return {
|
|
101
|
+
type: 'handoff',
|
|
102
|
+
value: primaryMarker.value,
|
|
103
|
+
responses: [primaryMarker.value, 'Not yet'],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
case MARKER_TYPES.INVOKE:
|
|
107
|
+
return {
|
|
108
|
+
type: 'invoke',
|
|
109
|
+
value: primaryMarker.value,
|
|
110
|
+
autoExecute: true,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
case MARKER_TYPES.QUESTION: {
|
|
114
|
+
if (primaryMarker.value === 'yesno') {
|
|
115
|
+
return {
|
|
116
|
+
type: 'yesno',
|
|
117
|
+
responses: ['Yes', 'No'],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Handle open questions - may have suggested prompt: "open" or "open:suggested text"
|
|
121
|
+
if (primaryMarker.value?.startsWith('open')) {
|
|
122
|
+
// Check for suggested prompt after "open:"
|
|
123
|
+
const colonIndex = primaryMarker.value.indexOf(':');
|
|
124
|
+
if (colonIndex !== -1) {
|
|
125
|
+
const suggestion = primaryMarker.value.substring(colonIndex + 1).trim();
|
|
126
|
+
if (suggestion) {
|
|
127
|
+
return {
|
|
128
|
+
type: 'open',
|
|
129
|
+
responses: [suggestion],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Plain open question without suggestion
|
|
134
|
+
return {
|
|
135
|
+
type: 'open',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Check for accompanying CHOICES marker
|
|
139
|
+
const choicesMarker = markers.find(m => m.type === MARKER_TYPES.CHOICES);
|
|
140
|
+
if (choicesMarker) {
|
|
141
|
+
return processChoicesMarker(choicesMarker.value, fullText);
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case MARKER_TYPES.CHOICES:
|
|
147
|
+
return processChoicesMarker(primaryMarker.value, fullText);
|
|
148
|
+
|
|
149
|
+
case MARKER_TYPES.CONTEXT_CLEAR:
|
|
150
|
+
return {
|
|
151
|
+
type: 'context_clear',
|
|
152
|
+
value: primaryMarker.value,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
case MARKER_TYPES.CONTINUE:
|
|
156
|
+
return {
|
|
157
|
+
type: 'continue',
|
|
158
|
+
responses: ['Continue'],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
default:
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Process a CHOICES marker value into choice buttons
|
|
168
|
+
*/
|
|
169
|
+
function processChoicesMarker(
|
|
170
|
+
value: string,
|
|
171
|
+
fullText: string
|
|
172
|
+
): MarkerAction {
|
|
173
|
+
const choiceValues = value.split(',').map(v => v.trim());
|
|
174
|
+
const firstValue = choiceValues[0];
|
|
175
|
+
const isNumeric = /^\d+$/.test(firstValue);
|
|
176
|
+
|
|
177
|
+
let choices: Array<{ number: number; text: string }>;
|
|
178
|
+
|
|
179
|
+
if (isNumeric) {
|
|
180
|
+
// Legacy numeric format - extract text from message
|
|
181
|
+
const choiceNumbers = choiceValues.map(n => parseInt(n, 10));
|
|
182
|
+
choices = extractChoiceTexts(fullText, choiceNumbers);
|
|
183
|
+
} else {
|
|
184
|
+
// Text label format - use labels directly
|
|
185
|
+
choices = choiceValues.map((text, index) => ({
|
|
186
|
+
number: index + 1,
|
|
187
|
+
text,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
type: 'choices',
|
|
193
|
+
choices,
|
|
194
|
+
responses: choices.map(c => c.text),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Hook to detect CYCLIST markers in content and return action metadata.
|
|
200
|
+
*
|
|
201
|
+
* @param content - Message content to analyze
|
|
202
|
+
* @returns MarkerAction if marker detected, null otherwise
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```tsx
|
|
206
|
+
* const actions = useMarkerActions(message.content);
|
|
207
|
+
* if (actions?.type === 'yesno') {
|
|
208
|
+
* // Render Yes/No buttons
|
|
209
|
+
* }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export function useMarkerActions(content: string | undefined): MarkerAction | null {
|
|
213
|
+
return useMemo(() => {
|
|
214
|
+
if (!content) return null;
|
|
215
|
+
|
|
216
|
+
const markers = detectMarkers(content);
|
|
217
|
+
if (!markers) return null;
|
|
218
|
+
|
|
219
|
+
return processMarkers(markers, content);
|
|
220
|
+
}, [content]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Strip CYCLIST markers from content for display.
|
|
225
|
+
*
|
|
226
|
+
* @param content - Content that may contain markers
|
|
227
|
+
* @returns Content with markers removed
|
|
228
|
+
*/
|
|
229
|
+
export function useStrippedContent(content: string | undefined): string {
|
|
230
|
+
return useMemo(() => {
|
|
231
|
+
if (!content) return '';
|
|
232
|
+
return stripMarkers(content);
|
|
233
|
+
}, [content]);
|
|
234
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMessageQueue Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for message queueing while Claude is processing.
|
|
5
|
+
* Story MSSCI-12717 - React Migration
|
|
6
|
+
*
|
|
7
|
+
* Bell Mode (MSSCI-12275):
|
|
8
|
+
* When bell mode is enabled, queued messages are injected into Claude's context
|
|
9
|
+
* via the PostToolUse hook, rather than waiting for Claude to finish.
|
|
10
|
+
*
|
|
11
|
+
* Turn Complete Logic (MSSCI-12450):
|
|
12
|
+
* - Bell mode OFF: All queued messages sent at once when Claude stops
|
|
13
|
+
* - Bell mode ON: Messages injected by hook; remaining queue sent on stop
|
|
14
|
+
*
|
|
15
|
+
* Bell Injected Display (Audit Fix 2026-02-03):
|
|
16
|
+
* - When hook consumes a message, notify via onBellConsumed callback
|
|
17
|
+
* - MessagePanel displays the injected message with 🔔 indicator
|
|
18
|
+
* - "Send Now" button allows immediate injection (abort + submit)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
22
|
+
|
|
23
|
+
export interface QueuedMessage {
|
|
24
|
+
text: string;
|
|
25
|
+
images: Array<{
|
|
26
|
+
dataUrl: string;
|
|
27
|
+
mimeType: string;
|
|
28
|
+
filename: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Callback when a message is consumed by bell mode hook */
|
|
33
|
+
export type BellConsumedCallback = (message: QueuedMessage) => void;
|
|
34
|
+
|
|
35
|
+
/** Functions needed to inject a message immediately */
|
|
36
|
+
export interface InjectDependencies {
|
|
37
|
+
abort: () => void;
|
|
38
|
+
submit: (text: string, images: QueuedMessage['images']) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface UseMessageQueueResult {
|
|
42
|
+
queue: QueuedMessage[];
|
|
43
|
+
queueCount: number;
|
|
44
|
+
isProcessing: boolean;
|
|
45
|
+
bellMode: boolean;
|
|
46
|
+
queuePaused: boolean;
|
|
47
|
+
queueMessage: (message: QueuedMessage) => boolean;
|
|
48
|
+
dequeueMessage: () => QueuedMessage | null;
|
|
49
|
+
removeFromQueue: (index: number) => void;
|
|
50
|
+
clearQueue: () => void;
|
|
51
|
+
setProcessing: (value: boolean) => void;
|
|
52
|
+
setBellMode: (value: boolean) => void;
|
|
53
|
+
pauseQueue: () => void;
|
|
54
|
+
resumeQueue: () => void;
|
|
55
|
+
handleTurnComplete: (onSubmit: (text: string, images: QueuedMessage['images']) => void) => void;
|
|
56
|
+
/** Register callback for when bell mode hook consumes a message */
|
|
57
|
+
onBellConsumed: (callback: BellConsumedCallback) => () => void;
|
|
58
|
+
/** Immediately inject a queued message (abort current + submit) */
|
|
59
|
+
injectMessage: (index: number, deps: InjectDependencies) => Promise<boolean>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const MAX_QUEUE_SIZE = 10;
|
|
63
|
+
const QUEUE_KEY = 'cyclist-message-queue';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sync queue to bell-queue.json for PostToolUse hook
|
|
67
|
+
* Only writes when bell mode is enabled
|
|
68
|
+
*/
|
|
69
|
+
async function syncQueueToFile(queue: QueuedMessage[]): Promise<void> {
|
|
70
|
+
try {
|
|
71
|
+
await fetch('/api/bell-queue', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify(queue),
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Ignore errors - bell queue sync is best-effort
|
|
78
|
+
console.debug('[MessageQueue] Bell queue sync error:', err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function useMessageQueue(): UseMessageQueueResult {
|
|
83
|
+
const [queue, setQueue] = useState<QueuedMessage[]>(() => {
|
|
84
|
+
try {
|
|
85
|
+
const stored = localStorage.getItem(QUEUE_KEY);
|
|
86
|
+
return stored ? JSON.parse(stored) : [];
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
92
|
+
const [bellMode, setBellModeState] = useState(false);
|
|
93
|
+
const [queuePaused, setQueuePaused] = useState(false);
|
|
94
|
+
const queueRef = useRef(queue);
|
|
95
|
+
const bellModeRef = useRef(bellMode);
|
|
96
|
+
|
|
97
|
+
// Callbacks for bell-consumed events (notifies MessagePanel to display)
|
|
98
|
+
const bellConsumedCallbacksRef = useRef<Set<BellConsumedCallback>>(new Set());
|
|
99
|
+
|
|
100
|
+
// Keep refs in sync
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
queueRef.current = queue;
|
|
103
|
+
}, [queue]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
bellModeRef.current = bellMode;
|
|
107
|
+
}, [bellMode]);
|
|
108
|
+
|
|
109
|
+
// Load bell mode from settings on mount
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
// Load via REST
|
|
112
|
+
fetch('/api/settings')
|
|
113
|
+
.then(res => res.json())
|
|
114
|
+
.then((settings: Record<string, unknown>) => {
|
|
115
|
+
const workflow = settings?.workflow as Record<string, unknown> | undefined;
|
|
116
|
+
if (workflow?.bell_mode) {
|
|
117
|
+
setBellModeState(true);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.catch(err => console.debug('[MessageQueue] Failed to load settings:', err));
|
|
121
|
+
|
|
122
|
+
// Subscribe to settings changes via WebSocket
|
|
123
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
124
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/settings`);
|
|
125
|
+
|
|
126
|
+
ws.onmessage = (event) => {
|
|
127
|
+
try {
|
|
128
|
+
const data = JSON.parse(event.data);
|
|
129
|
+
if (data.type === 'init' || data.type === 'update') {
|
|
130
|
+
const workflow = data.settings?.workflow as Record<string, unknown> | undefined;
|
|
131
|
+
setBellModeState(!!workflow?.bell_mode);
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// Ignore parse errors
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return () => ws.close();
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
// Listen for bell-consumed WebSocket events
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
// Connect to bell WebSocket for consumed events
|
|
144
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
145
|
+
const wsUrl = `${protocol}//${window.location.host}/ws/bell`;
|
|
146
|
+
|
|
147
|
+
let ws: WebSocket | null = null;
|
|
148
|
+
let reconnectTimeout: ReturnType<typeof setTimeout>;
|
|
149
|
+
|
|
150
|
+
const connect = () => {
|
|
151
|
+
try {
|
|
152
|
+
ws = new WebSocket(wsUrl);
|
|
153
|
+
|
|
154
|
+
ws.onmessage = (event) => {
|
|
155
|
+
try {
|
|
156
|
+
const data = JSON.parse(event.data);
|
|
157
|
+
if (data.type === 'bell-consumed') {
|
|
158
|
+
// Get the consumed message BEFORE dequeuing (for display callback)
|
|
159
|
+
const consumedMessage = queueRef.current[0];
|
|
160
|
+
|
|
161
|
+
// Dequeue the first message when hook consumes it
|
|
162
|
+
setQueue(prev => {
|
|
163
|
+
if (prev.length === 0) return prev;
|
|
164
|
+
const newQueue = prev.slice(1);
|
|
165
|
+
try {
|
|
166
|
+
localStorage.setItem(QUEUE_KEY, JSON.stringify(newQueue));
|
|
167
|
+
} catch { /* ignore */ }
|
|
168
|
+
return newQueue;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Notify all registered callbacks so MessagePanel can display
|
|
172
|
+
if (consumedMessage) {
|
|
173
|
+
console.log('[MessageQueue] Bell consumed, notifying callbacks:', consumedMessage.text);
|
|
174
|
+
bellConsumedCallbacksRef.current.forEach(cb => {
|
|
175
|
+
try {
|
|
176
|
+
cb(consumedMessage);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.error('[MessageQueue] Bell consumed callback error:', err);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error('[MessageQueue] Failed to parse bell message:', err);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
ws.onclose = () => {
|
|
189
|
+
console.log('[MessageQueue] Bell WebSocket closed, reconnecting...');
|
|
190
|
+
reconnectTimeout = setTimeout(connect, 2000);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
ws.onerror = () => {
|
|
194
|
+
// Will trigger onclose
|
|
195
|
+
};
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.debug('[MessageQueue] Bell WebSocket init failed:', err);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
connect();
|
|
202
|
+
|
|
203
|
+
return () => {
|
|
204
|
+
clearTimeout(reconnectTimeout);
|
|
205
|
+
ws?.close();
|
|
206
|
+
};
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
const saveQueue = useCallback((newQueue: QueuedMessage[]) => {
|
|
210
|
+
try {
|
|
211
|
+
localStorage.setItem(QUEUE_KEY, JSON.stringify(newQueue));
|
|
212
|
+
} catch {
|
|
213
|
+
// Ignore localStorage errors
|
|
214
|
+
}
|
|
215
|
+
// Always sync to file (API checks bell mode)
|
|
216
|
+
syncQueueToFile(newQueue);
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
const queueMessage = useCallback((message: QueuedMessage): boolean => {
|
|
220
|
+
if (!message.text.trim() && message.images.length === 0) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (queueRef.current.length >= MAX_QUEUE_SIZE) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setQueue(prev => {
|
|
228
|
+
const newQueue = [...prev, message];
|
|
229
|
+
saveQueue(newQueue);
|
|
230
|
+
return newQueue;
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
}, [saveQueue]);
|
|
234
|
+
|
|
235
|
+
const dequeueMessage = useCallback((): QueuedMessage | null => {
|
|
236
|
+
let dequeued: QueuedMessage | null = null;
|
|
237
|
+
setQueue(prev => {
|
|
238
|
+
if (prev.length === 0) return prev;
|
|
239
|
+
dequeued = prev[0];
|
|
240
|
+
const newQueue = prev.slice(1);
|
|
241
|
+
saveQueue(newQueue);
|
|
242
|
+
return newQueue;
|
|
243
|
+
});
|
|
244
|
+
return dequeued;
|
|
245
|
+
}, [saveQueue]);
|
|
246
|
+
|
|
247
|
+
const removeFromQueue = useCallback((index: number) => {
|
|
248
|
+
setQueue(prev => {
|
|
249
|
+
if (index < 0 || index >= prev.length) return prev;
|
|
250
|
+
const newQueue = [...prev.slice(0, index), ...prev.slice(index + 1)];
|
|
251
|
+
saveQueue(newQueue);
|
|
252
|
+
return newQueue;
|
|
253
|
+
});
|
|
254
|
+
}, [saveQueue]);
|
|
255
|
+
|
|
256
|
+
const clearQueue = useCallback(() => {
|
|
257
|
+
setQueue([]);
|
|
258
|
+
saveQueue([]);
|
|
259
|
+
}, [saveQueue]);
|
|
260
|
+
|
|
261
|
+
const setProcessing = useCallback((value: boolean) => {
|
|
262
|
+
setIsProcessing(value);
|
|
263
|
+
}, []);
|
|
264
|
+
|
|
265
|
+
const setBellMode = useCallback((value: boolean) => {
|
|
266
|
+
setBellModeState(value);
|
|
267
|
+
}, []);
|
|
268
|
+
|
|
269
|
+
const pauseQueue = useCallback(() => {
|
|
270
|
+
setQueuePaused(true);
|
|
271
|
+
console.log('[MessageQueue] Queue paused');
|
|
272
|
+
}, []);
|
|
273
|
+
|
|
274
|
+
const resumeQueue = useCallback(() => {
|
|
275
|
+
setQueuePaused(false);
|
|
276
|
+
console.log('[MessageQueue] Queue resumed');
|
|
277
|
+
}, []);
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Handle turn complete - send queued messages based on bell mode
|
|
281
|
+
* Called when Claude's turn completes (via onComplete callback)
|
|
282
|
+
*
|
|
283
|
+
* Bell mode OFF: All queued messages sent at once
|
|
284
|
+
* Bell mode ON: Remaining messages (not consumed by hook) sent
|
|
285
|
+
*/
|
|
286
|
+
const handleTurnComplete = useCallback((onSubmit: (text: string, images: QueuedMessage['images']) => void) => {
|
|
287
|
+
if (queuePaused) {
|
|
288
|
+
console.log('[MessageQueue] Queue paused, skipping turn complete');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const currentQueue = queueRef.current;
|
|
293
|
+
if (currentQueue.length === 0) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log('[MessageQueue] Turn complete, sending', currentQueue.length, 'queued messages');
|
|
298
|
+
|
|
299
|
+
// Send all queued messages
|
|
300
|
+
for (const msg of currentQueue) {
|
|
301
|
+
onSubmit(msg.text, msg.images);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Clear the queue
|
|
305
|
+
setQueue([]);
|
|
306
|
+
saveQueue([]);
|
|
307
|
+
}, [queuePaused, saveQueue]);
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Register a callback for when bell mode hook consumes a message.
|
|
311
|
+
* Returns unsubscribe function.
|
|
312
|
+
*/
|
|
313
|
+
const onBellConsumed = useCallback((callback: BellConsumedCallback): (() => void) => {
|
|
314
|
+
bellConsumedCallbacksRef.current.add(callback);
|
|
315
|
+
return () => {
|
|
316
|
+
bellConsumedCallbacksRef.current.delete(callback);
|
|
317
|
+
};
|
|
318
|
+
}, []);
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Immediately inject a queued message (abort current + submit).
|
|
322
|
+
* Used for "Send Now" button functionality.
|
|
323
|
+
*
|
|
324
|
+
* @param index - Index of message to inject
|
|
325
|
+
* @param deps - abort and submit functions from Claude context
|
|
326
|
+
* @returns true if message was injected
|
|
327
|
+
*/
|
|
328
|
+
const injectMessage = useCallback(async (
|
|
329
|
+
index: number,
|
|
330
|
+
deps: InjectDependencies
|
|
331
|
+
): Promise<boolean> => {
|
|
332
|
+
const currentQueue = queueRef.current;
|
|
333
|
+
if (index < 0 || index >= currentQueue.length) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get the message before removing
|
|
338
|
+
const message = currentQueue[index];
|
|
339
|
+
if (!message) return false;
|
|
340
|
+
|
|
341
|
+
// Remove from queue immediately
|
|
342
|
+
setQueue(prev => {
|
|
343
|
+
const newQueue = [...prev.slice(0, index), ...prev.slice(index + 1)];
|
|
344
|
+
saveQueue(newQueue);
|
|
345
|
+
return newQueue;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Abort Claude if processing
|
|
349
|
+
console.log('[MessageQueue] Injecting message, aborting Claude...');
|
|
350
|
+
deps.abort();
|
|
351
|
+
|
|
352
|
+
// Brief delay to let abort complete
|
|
353
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
354
|
+
|
|
355
|
+
// Submit the message
|
|
356
|
+
console.log('[MessageQueue] Submitting injected message:', message.text);
|
|
357
|
+
deps.submit(message.text, message.images);
|
|
358
|
+
|
|
359
|
+
return true;
|
|
360
|
+
}, [saveQueue]);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
queue,
|
|
364
|
+
queueCount: queue.length,
|
|
365
|
+
isProcessing,
|
|
366
|
+
bellMode,
|
|
367
|
+
queuePaused,
|
|
368
|
+
queueMessage,
|
|
369
|
+
dequeueMessage,
|
|
370
|
+
removeFromQueue,
|
|
371
|
+
clearQueue,
|
|
372
|
+
setProcessing,
|
|
373
|
+
setBellMode,
|
|
374
|
+
pauseQueue,
|
|
375
|
+
resumeQueue,
|
|
376
|
+
handleTurnComplete,
|
|
377
|
+
onBellConsumed,
|
|
378
|
+
injectMessage,
|
|
379
|
+
};
|
|
380
|
+
}
|