@pennyfarthing/cyclist 10.4.0 → 11.0.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/dist/api/agent-load.d.ts +1 -2
- package/dist/api/agent-load.d.ts.map +1 -1
- package/dist/api/agent-load.js +2 -123
- package/dist/api/agent-load.js.map +1 -1
- package/dist/api/audit-log.d.ts +1 -17
- package/dist/api/audit-log.d.ts.map +1 -1
- package/dist/api/audit-log.js +2 -162
- package/dist/api/audit-log.js.map +1 -1
- package/dist/api/background-tasks.d.ts +1 -26
- package/dist/api/background-tasks.d.ts.map +1 -1
- package/dist/api/background-tasks.js +2 -55
- package/dist/api/background-tasks.js.map +1 -1
- package/dist/api/bell.d.ts +1 -18
- package/dist/api/bell.d.ts.map +1 -1
- package/dist/api/bell.js +2 -33
- package/dist/api/bell.js.map +1 -1
- package/dist/api/code-markers.d.ts +1 -8
- package/dist/api/code-markers.d.ts.map +1 -1
- package/dist/api/code-markers.js +2 -61
- package/dist/api/code-markers.js.map +1 -1
- package/dist/api/complexity.d.ts +1 -2
- package/dist/api/complexity.d.ts.map +1 -1
- package/dist/api/complexity.js +2 -46
- package/dist/api/complexity.js.map +1 -1
- package/dist/api/context.d.ts +1 -37
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -143
- package/dist/api/context.js.map +1 -1
- package/dist/api/dead-code.d.ts +1 -2
- package/dist/api/dead-code.d.ts.map +1 -1
- package/dist/api/dead-code.js +2 -69
- package/dist/api/dead-code.js.map +1 -1
- package/dist/api/dependencies.d.ts +1 -2
- package/dist/api/dependencies.d.ts.map +1 -1
- package/dist/api/dependencies.js +2 -42
- package/dist/api/dependencies.js.map +1 -1
- package/dist/api/evaluation.d.ts +1 -19
- package/dist/api/evaluation.d.ts.map +1 -1
- package/dist/api/evaluation.js +2 -127
- package/dist/api/evaluation.js.map +1 -1
- package/dist/api/file-browser.d.ts +1 -8
- package/dist/api/file-browser.d.ts.map +1 -1
- package/dist/api/file-browser.js +2 -114
- package/dist/api/file-browser.js.map +1 -1
- package/dist/api/git.d.ts +1 -46
- package/dist/api/git.d.ts.map +1 -1
- package/dist/api/git.js +2 -354
- package/dist/api/git.js.map +1 -1
- package/dist/api/health-score.d.ts +1 -2
- package/dist/api/health-score.d.ts.map +1 -1
- package/dist/api/health-score.js +2 -46
- package/dist/api/health-score.js.map +1 -1
- package/dist/api/hook-request.d.ts +1 -40
- package/dist/api/hook-request.d.ts.map +1 -1
- package/dist/api/hook-request.js +2 -277
- package/dist/api/hook-request.js.map +1 -1
- package/dist/api/hotspots.d.ts +1 -2
- package/dist/api/hotspots.d.ts.map +1 -1
- package/dist/api/hotspots.js +2 -61
- package/dist/api/hotspots.js.map +1 -1
- package/dist/api/identity.d.ts +1 -16
- package/dist/api/identity.d.ts.map +1 -1
- package/dist/api/identity.js +2 -78
- package/dist/api/identity.js.map +1 -1
- package/dist/api/index.d.ts +1 -34
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -44
- package/dist/api/index.js.map +1 -1
- package/dist/api/mode.d.ts +1 -22
- package/dist/api/mode.d.ts.map +1 -1
- package/dist/api/mode.js +2 -37
- package/dist/api/mode.js.map +1 -1
- package/dist/api/otlp.d.ts +1 -2
- package/dist/api/otlp.d.ts.map +1 -1
- package/dist/api/otlp.js +2 -46
- package/dist/api/otlp.js.map +1 -1
- package/dist/api/permissions.d.ts +1 -15
- package/dist/api/permissions.d.ts.map +1 -1
- package/dist/api/permissions.js +2 -66
- package/dist/api/permissions.js.map +1 -1
- package/dist/api/persona.d.ts +1 -8
- package/dist/api/persona.d.ts.map +1 -1
- package/dist/api/persona.js +2 -67
- package/dist/api/persona.js.map +1 -1
- package/dist/api/portrait.d.ts +1 -5
- package/dist/api/portrait.d.ts.map +1 -1
- package/dist/api/portrait.js +2 -27
- package/dist/api/portrait.js.map +1 -1
- package/dist/api/settings.d.ts +1 -53
- package/dist/api/settings.d.ts.map +1 -1
- package/dist/api/settings.js +2 -464
- package/dist/api/settings.js.map +1 -1
- package/dist/api/spans.d.ts +1 -16
- package/dist/api/spans.d.ts.map +1 -1
- package/dist/api/spans.js +2 -244
- package/dist/api/spans.js.map +1 -1
- package/dist/api/stats.d.ts +1 -12
- package/dist/api/stats.d.ts.map +1 -1
- package/dist/api/stats.js +2 -84
- package/dist/api/stats.js.map +1 -1
- package/dist/api/story.d.ts +1 -2
- package/dist/api/story.d.ts.map +1 -1
- package/dist/api/story.js +2 -14
- package/dist/api/story.js.map +1 -1
- package/dist/api/telemetry.d.ts +1 -18
- package/dist/api/telemetry.d.ts.map +1 -1
- package/dist/api/telemetry.js +2 -164
- package/dist/api/telemetry.js.map +1 -1
- package/dist/api/theme-agents.d.ts +1 -60
- package/dist/api/theme-agents.d.ts.map +1 -1
- package/dist/api/theme-agents.js +2 -213
- package/dist/api/theme-agents.js.map +1 -1
- package/dist/api/todos.d.ts +1 -32
- package/dist/api/todos.d.ts.map +1 -1
- package/dist/api/todos.js +2 -43
- package/dist/api/todos.js.map +1 -1
- package/dist/api/token-stats.d.ts +1 -7
- package/dist/api/token-stats.d.ts.map +1 -1
- package/dist/api/token-stats.js +2 -35
- package/dist/api/token-stats.js.map +1 -1
- package/dist/api/welcome.d.ts +1 -21
- package/dist/api/welcome.d.ts.map +1 -1
- package/dist/api/welcome.js +2 -34
- package/dist/api/welcome.js.map +1 -1
- package/dist/bikerack.js +2 -2
- package/dist/bikerack.js.map +1 -1
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/focus.d.ts +53 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +122 -0
- package/dist/focus.js.map +1 -0
- package/dist/git-cache.d.ts +1 -0
- package/dist/git-cache.d.ts.map +1 -1
- package/dist/git-cache.js +3 -1
- package/dist/git-cache.js.map +1 -1
- package/dist/menu-builder.d.ts.map +1 -1
- package/dist/menu-builder.js +0 -1
- package/dist/menu-builder.js.map +1 -1
- package/dist/prime.d.ts +3 -3
- package/dist/prime.d.ts.map +1 -1
- package/dist/prime.js +38 -14
- package/dist/prime.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +53 -61
- package/dist/server.d.ts +18 -85
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +105 -405
- package/dist/server.js.map +1 -1
- package/dist/sprint-data.d.ts +1 -1
- package/dist/sprint-data.d.ts.map +1 -1
- package/dist/sprint-data.js +2 -2
- package/dist/sprint-data.js.map +1 -1
- package/dist/theme-metadata.d.ts +3 -3
- package/dist/theme-metadata.d.ts.map +1 -1
- package/dist/theme-metadata.js +4 -4
- package/dist/theme-metadata.js.map +1 -1
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +53 -75
- package/dist/websocket.js.map +1 -1
- package/package.json +2 -6
- package/portraits/hogans-heroes/large/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/large/carter-34352.png +0 -0
- package/portraits/hogans-heroes/large/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/large/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/large/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/large/klink-23434.png +0 -0
- package/portraits/hogans-heroes/large/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/large/marya-53543.png +0 -0
- package/portraits/hogans-heroes/large/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/large/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/large/underground-55131.png +0 -0
- package/portraits/hogans-heroes/medium/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/medium/carter-34352.png +0 -0
- package/portraits/hogans-heroes/medium/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/medium/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/medium/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/medium/klink-23434.png +0 -0
- package/portraits/hogans-heroes/medium/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/medium/marya-53543.png +0 -0
- package/portraits/hogans-heroes/medium/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/medium/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/medium/underground-55131.png +0 -0
- package/portraits/monty-python/large/announcer-44441.png +0 -0
- package/portraits/monty-python/large/arguer-35412.png +0 -0
- package/portraits/monty-python/large/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/large/colonel-35423.png +0 -0
- package/portraits/monty-python/large/counsellor-45341.png +0 -0
- package/portraits/monty-python/large/gumbys-23524.png +0 -0
- package/portraits/monty-python/large/nudge-43533.png +0 -0
- package/portraits/monty-python/large/praline-45413.png +0 -0
- package/portraits/monty-python/large/silly-walks-55322.png +0 -0
- package/portraits/monty-python/large/wensleydale-54451.png +0 -0
- package/portraits/monty-python/large/xim-nez-43534.png +0 -0
- package/portraits/monty-python/medium/announcer-44441.png +0 -0
- package/portraits/monty-python/medium/arguer-35412.png +0 -0
- package/portraits/monty-python/medium/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/medium/colonel-35423.png +0 -0
- package/portraits/monty-python/medium/counsellor-45341.png +0 -0
- package/portraits/monty-python/medium/gumbys-23524.png +0 -0
- package/portraits/monty-python/medium/nudge-43533.png +0 -0
- package/portraits/monty-python/medium/praline-45413.png +0 -0
- package/portraits/monty-python/medium/silly-walks-55322.png +0 -0
- package/portraits/monty-python/medium/wensleydale-54451.png +0 -0
- package/portraits/monty-python/medium/xim-nez-43534.png +0 -0
- package/portraits/stephen-king/large/andy-55231.png +0 -0
- package/portraits/stephen-king/large/christine-25112.png +0 -0
- package/portraits/stephen-king/large/danny-53243.png +0 -0
- package/portraits/stephen-king/large/flagg-55311.png +0 -0
- package/portraits/stephen-king/large/gaunt-54421.png +0 -0
- package/portraits/stephen-king/large/jack-44224.png +0 -0
- package/portraits/stephen-king/large/johnny-44353.png +0 -0
- package/portraits/stephen-king/large/margaret-15415.png +0 -0
- package/portraits/stephen-king/large/paul-45233.png +0 -0
- package/portraits/stephen-king/large/pennywise-54411.png +0 -0
- package/portraits/stephen-king/large/roland-35121.png +0 -0
- package/portraits/stephen-king/medium/andy-55231.png +0 -0
- package/portraits/stephen-king/medium/christine-25112.png +0 -0
- package/portraits/stephen-king/medium/danny-53243.png +0 -0
- package/portraits/stephen-king/medium/flagg-55311.png +0 -0
- package/portraits/stephen-king/medium/gaunt-54421.png +0 -0
- package/portraits/stephen-king/medium/jack-44224.png +0 -0
- package/portraits/stephen-king/medium/johnny-44353.png +0 -0
- package/portraits/stephen-king/medium/margaret-15415.png +0 -0
- package/portraits/stephen-king/medium/paul-45233.png +0 -0
- package/portraits/stephen-king/medium/pennywise-54411.png +0 -0
- package/portraits/stephen-king/medium/roland-35121.png +0 -0
- package/portraits/star-trek-tng/large/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/large/data-55241.png +0 -0
- package/portraits/star-trek-tng/large/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/large/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/large/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/large/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/large/miles-35342.png +0 -0
- package/portraits/star-trek-tng/large/q-53521.png +0 -0
- package/portraits/star-trek-tng/large/spock-45231.png +0 -0
- package/portraits/star-trek-tng/large/troi-44352.png +0 -0
- package/portraits/star-trek-tng/medium/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/medium/data-55241.png +0 -0
- package/portraits/star-trek-tng/medium/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/medium/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/medium/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/medium/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/medium/miles-35342.png +0 -0
- package/portraits/star-trek-tng/medium/q-53521.png +0 -0
- package/portraits/star-trek-tng/medium/spock-45231.png +0 -0
- package/portraits/star-trek-tng/medium/troi-44352.png +0 -0
- package/src/public/App.tsx +0 -340
- package/src/public/components/AgentLoadDialog.tsx +0 -202
- package/src/public/components/AgentPopup.tsx +0 -308
- package/src/public/components/ApprovalModal/ApprovalModal.css +0 -35
- package/src/public/components/ApprovalModal/index.tsx +0 -632
- package/src/public/components/BikeRackIndex.tsx +0 -54
- package/src/public/components/BikeRackWorkspace.tsx +0 -142
- package/src/public/components/CommandPalette.tsx +0 -555
- package/src/public/components/ConfirmDialog.tsx +0 -168
- package/src/public/components/ContextIndicator/ContextIndicator.css +0 -85
- package/src/public/components/ContextIndicator/index.tsx +0 -330
- package/src/public/components/ContextSparkline.tsx +0 -56
- package/src/public/components/ControlBar.tsx +0 -636
- package/src/public/components/DeadCodeDialog.tsx +0 -169
- package/src/public/components/DiffViewer.tsx +0 -585
- package/src/public/components/DockviewWorkspace.tsx +0 -737
- package/src/public/components/Editor.tsx +0 -630
- package/src/public/components/ErrorBoundary.tsx +0 -67
- package/src/public/components/FileTree.tsx +0 -379
- package/src/public/components/FontPicker/FontPicker.css +0 -276
- package/src/public/components/FontPicker/index.tsx +0 -430
- package/src/public/components/FullFileTree.tsx +0 -237
- package/src/public/components/HealthGauge.tsx +0 -181
- package/src/public/components/Message.tsx +0 -225
- package/src/public/components/MessageList.tsx +0 -98
- package/src/public/components/MessageView.tsx +0 -400
- package/src/public/components/ModeSwitch/ModeSwitch.css +0 -165
- package/src/public/components/ModeSwitch/index.tsx +0 -372
- package/src/public/components/PersonaHeader.tsx +0 -240
- package/src/public/components/QuickActions.tsx +0 -267
- package/src/public/components/SpanTimeline.tsx +0 -352
- package/src/public/components/StandalonePanel.tsx +0 -84
- package/src/public/components/StatsStrip.tsx +0 -162
- package/src/public/components/StreamingContent.tsx +0 -77
- package/src/public/components/SubagentSpan.tsx +0 -180
- package/src/public/components/TandemPortrait.tsx +0 -72
- package/src/public/components/ThemePalette/ThemePalette.css +0 -179
- package/src/public/components/ThemePalette/index.tsx +0 -326
- package/src/public/components/ToolCallBlock.tsx +0 -252
- package/src/public/components/ToolStack.tsx +0 -209
- package/src/public/components/ToolStatus.tsx +0 -57
- package/src/public/components/dialogs/CodeMarkersDialog.tsx +0 -169
- package/src/public/components/dialogs/ComplexityDialog.tsx +0 -163
- package/src/public/components/dialogs/DependenciesDialog.tsx +0 -120
- package/src/public/components/dialogs/HotspotsDialog.tsx +0 -451
- package/src/public/components/dialogs/ToolDialog.tsx +0 -43
- package/src/public/components/panel-registry.ts +0 -11
- package/src/public/components/panels/ACPanel.tsx +0 -93
- package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +0 -104
- package/src/public/components/panels/AuditLogPanel.tsx +0 -465
- package/src/public/components/panels/BackgroundPanel.tsx +0 -115
- package/src/public/components/panels/BikeLanePanel.tsx +0 -214
- package/src/public/components/panels/ChangedPanel.tsx +0 -65
- package/src/public/components/panels/DebugPanel.tsx +0 -344
- package/src/public/components/panels/DiffsPanel.tsx +0 -155
- package/src/public/components/panels/GitPanel.tsx +0 -216
- package/src/public/components/panels/HotspotsPanel.tsx +0 -365
- package/src/public/components/panels/MessagePanel.tsx +0 -497
- package/src/public/components/panels/SettingsPanel.tsx +0 -453
- package/src/public/components/panels/SprintPanel.tsx +0 -670
- package/src/public/components/panels/TTYPanel.tsx +0 -299
- package/src/public/components/panels/TodoPanel.tsx +0 -142
- package/src/public/components/panels/WorkflowPanel.tsx +0 -224
- package/src/public/components/panels/index.ts +0 -24
- package/src/public/components/ui/alert-dialog.tsx +0 -139
- package/src/public/components/ui/badge.tsx +0 -36
- package/src/public/components/ui/button.tsx +0 -57
- package/src/public/components/ui/checkbox.tsx +0 -28
- package/src/public/components/ui/collapsible.tsx +0 -9
- package/src/public/components/ui/command.tsx +0 -151
- package/src/public/components/ui/dialog.tsx +0 -120
- package/src/public/components/ui/popover.tsx +0 -31
- package/src/public/components/ui/progress.tsx +0 -28
- package/src/public/components/ui/scroll-area.tsx +0 -46
- package/src/public/components/ui/select.tsx +0 -157
- package/src/public/components/ui/separator.tsx +0 -29
- package/src/public/components/ui/skeleton.tsx +0 -15
- package/src/public/components/ui/switch.tsx +0 -27
- package/src/public/components/ui/toggle-group.tsx +0 -59
- package/src/public/components/ui/toggle.tsx +0 -43
- package/src/public/components/ui/tooltip.tsx +0 -30
- package/src/public/contexts/ClaudeContext.tsx +0 -311
- package/src/public/contexts/MessageQueueContext.tsx +0 -143
- package/src/public/css/theme-browser.css +0 -550
- package/src/public/css/theme-system.css +0 -630
- package/src/public/hooks/index.ts +0 -49
- package/src/public/hooks/useAgentLoad.ts +0 -105
- package/src/public/hooks/useBackgroundTasks.ts +0 -131
- package/src/public/hooks/useClaude.ts +0 -234
- package/src/public/hooks/useCodeMarkers.ts +0 -101
- package/src/public/hooks/useColorScheme.ts +0 -42
- package/src/public/hooks/useCommandHistory.ts +0 -99
- package/src/public/hooks/useComplexity.ts +0 -80
- package/src/public/hooks/useDeadCode.ts +0 -99
- package/src/public/hooks/useDependencies.ts +0 -82
- package/src/public/hooks/useDiffs.ts +0 -143
- package/src/public/hooks/useFileBrowser.ts +0 -71
- package/src/public/hooks/useGitStatus.ts +0 -233
- package/src/public/hooks/useHealthScore.ts +0 -69
- package/src/public/hooks/useHotspots.ts +0 -123
- package/src/public/hooks/useLayoutPersistence.ts +0 -138
- package/src/public/hooks/useMarkdownParser.ts +0 -36
- package/src/public/hooks/useMarkerActions.ts +0 -234
- package/src/public/hooks/useMessageQueue.ts +0 -380
- package/src/public/hooks/useMessageStream.ts +0 -131
- package/src/public/hooks/usePersona.ts +0 -112
- package/src/public/hooks/usePlanModeExit.ts +0 -105
- package/src/public/hooks/useResponsiveLayout.ts +0 -173
- package/src/public/hooks/useSprint.ts +0 -147
- package/src/public/hooks/useStatsStrip.ts +0 -204
- package/src/public/hooks/useStory.ts +0 -135
- package/src/public/hooks/useSubagentHelper.ts +0 -64
- package/src/public/hooks/useSyntaxHighlighter.ts +0 -52
- package/src/public/hooks/useTabCompletion.ts +0 -124
- package/src/public/hooks/useTodos.ts +0 -93
- package/src/public/hooks/useUserAvatar.ts +0 -54
- package/src/public/index.tsx +0 -10
- package/src/public/lib/utils.ts +0 -6
- package/src/public/styles/dockview-theme.css +0 -459
- package/src/public/styles/tailwind.css +0 -4396
- package/src/public/types/electron.d.ts +0 -18
- package/src/public/types/message.ts +0 -51
- package/src/public/utils/avatar-service.ts +0 -73
- package/src/public/utils/color-presets.ts +0 -940
- package/src/public/utils/font-presets.ts +0 -362
- package/src/public/utils/formatDuration.ts +0 -14
- package/src/public/utils/markdown.ts +0 -249
- package/src/public/utils/messageFilters.ts +0 -128
- package/src/public/utils/slash-commands.ts +0 -353
- package/src/public/utils/subagent-display.ts +0 -146
- package/src/public/utils/syntax.ts +0 -219
- package/src/public/utils/toolIntentSummarizer.ts +0 -199
- package/src/public/utils/toolStackGrouper.ts +0 -106
- package/src/public/utils/toolTypeColors.ts +0 -45
|
@@ -1,380 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useMessageStream Hook
|
|
3
|
-
*
|
|
4
|
-
* React hook for subscribing to Claude message stream via WebSocket.
|
|
5
|
-
* Migrated from IPC to WebSocket for unified communication.
|
|
6
|
-
*
|
|
7
|
-
* Story MSSCI-12698 - MessageView Component with Streaming
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
11
|
-
import type { MessageData } from '../types/message';
|
|
12
|
-
|
|
13
|
-
interface UseMessageStreamResult {
|
|
14
|
-
messages: MessageData[];
|
|
15
|
-
isStreaming: boolean;
|
|
16
|
-
error: Error | null;
|
|
17
|
-
isConnected: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface WebSocketMessage {
|
|
21
|
-
type: 'message' | 'complete' | 'error' | 'init' | 'mode';
|
|
22
|
-
message?: {
|
|
23
|
-
type: string;
|
|
24
|
-
content?: string | Array<{ type: string; text?: string }>;
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
};
|
|
27
|
-
error?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useMessageStream(): UseMessageStreamResult {
|
|
31
|
-
const [messages, setMessages] = useState<MessageData[]>([]);
|
|
32
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
33
|
-
const [error, setError] = useState<Error | null>(null);
|
|
34
|
-
const [isConnected, setIsConnected] = useState(false);
|
|
35
|
-
const wsRef = useRef<WebSocket | null>(null);
|
|
36
|
-
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
37
|
-
|
|
38
|
-
const handleWebSocketMessage = useCallback((data: WebSocketMessage) => {
|
|
39
|
-
if (data.type === 'message' && data.message) {
|
|
40
|
-
const msg = data.message;
|
|
41
|
-
|
|
42
|
-
// Extract content string
|
|
43
|
-
let contentStr = '';
|
|
44
|
-
if (typeof msg.content === 'string') {
|
|
45
|
-
contentStr = msg.content;
|
|
46
|
-
} else if (Array.isArray(msg.content)) {
|
|
47
|
-
contentStr = msg.content
|
|
48
|
-
.filter((c) => c.type === 'text' && c.text)
|
|
49
|
-
.map((c) => c.text)
|
|
50
|
-
.join('');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Map SDK 'assistant' type to our internal 'agent' type
|
|
54
|
-
const messageType = msg.type === 'assistant' ? 'agent' : msg.type as MessageData['type'];
|
|
55
|
-
|
|
56
|
-
const message: MessageData = {
|
|
57
|
-
type: messageType,
|
|
58
|
-
content: contentStr,
|
|
59
|
-
timestamp: Date.now(),
|
|
60
|
-
isStreaming: true,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
setMessages((prev) => [...prev, message]);
|
|
64
|
-
|
|
65
|
-
// Update streaming state for agent messages
|
|
66
|
-
if (msg.type === 'assistant') {
|
|
67
|
-
setIsStreaming(true);
|
|
68
|
-
}
|
|
69
|
-
} else if (data.type === 'complete') {
|
|
70
|
-
setIsStreaming(false);
|
|
71
|
-
} else if (data.type === 'error' && data.error) {
|
|
72
|
-
setError(new Error(data.error));
|
|
73
|
-
setIsStreaming(false);
|
|
74
|
-
}
|
|
75
|
-
}, []);
|
|
76
|
-
|
|
77
|
-
const connect = useCallback(() => {
|
|
78
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
79
|
-
const wsUrl = `${protocol}//${window.location.host}/ws/claude`;
|
|
80
|
-
|
|
81
|
-
const ws = new WebSocket(wsUrl);
|
|
82
|
-
wsRef.current = ws;
|
|
83
|
-
|
|
84
|
-
ws.onopen = () => {
|
|
85
|
-
console.log('[useMessageStream] Connected');
|
|
86
|
-
setIsConnected(true);
|
|
87
|
-
setError(null);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
ws.onmessage = (event) => {
|
|
91
|
-
try {
|
|
92
|
-
const data = JSON.parse(event.data) as WebSocketMessage;
|
|
93
|
-
handleWebSocketMessage(data);
|
|
94
|
-
} catch (err) {
|
|
95
|
-
console.error('[useMessageStream] Failed to parse message:', err);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
ws.onclose = () => {
|
|
100
|
-
console.log('[useMessageStream] Disconnected');
|
|
101
|
-
setIsConnected(false);
|
|
102
|
-
wsRef.current = null;
|
|
103
|
-
|
|
104
|
-
// Attempt reconnection after delay
|
|
105
|
-
reconnectTimeoutRef.current = setTimeout(() => {
|
|
106
|
-
console.log('[useMessageStream] Attempting reconnect...');
|
|
107
|
-
connect();
|
|
108
|
-
}, 2000);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
ws.onerror = () => {
|
|
112
|
-
setError(new Error('WebSocket connection failed'));
|
|
113
|
-
};
|
|
114
|
-
}, [handleWebSocketMessage]);
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
connect();
|
|
118
|
-
|
|
119
|
-
return () => {
|
|
120
|
-
if (reconnectTimeoutRef.current) {
|
|
121
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
122
|
-
}
|
|
123
|
-
if (wsRef.current) {
|
|
124
|
-
wsRef.current.close();
|
|
125
|
-
wsRef.current = null;
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
}, [connect]);
|
|
129
|
-
|
|
130
|
-
return { messages, isStreaming, error, isConnected };
|
|
131
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePersona Hook
|
|
3
|
-
*
|
|
4
|
-
* React hook for fetching and subscribing to persona data.
|
|
5
|
-
* Story MSSCI-12700 - PersonaHeader Component
|
|
6
|
-
* Story MSSCI-12860 - IPC to WebSocket Migration (Phase 1)
|
|
7
|
-
*
|
|
8
|
-
* Provides:
|
|
9
|
-
* - character: Current agent character name
|
|
10
|
-
* - theme: Current theme name
|
|
11
|
-
* - role: Agent role/title
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { useState, useEffect, useRef } from 'react';
|
|
15
|
-
|
|
16
|
-
export interface TandemAgentData {
|
|
17
|
-
character: string;
|
|
18
|
-
role: string;
|
|
19
|
-
slug: string;
|
|
20
|
-
theme: string;
|
|
21
|
-
isThinking: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface PersonaData {
|
|
25
|
-
character: string | null;
|
|
26
|
-
theme: string | null;
|
|
27
|
-
role: string | null;
|
|
28
|
-
slug: string | null;
|
|
29
|
-
quote: string | null; // Random catchphrase from theme
|
|
30
|
-
tandemAgent?: TandemAgentData | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface UsePersonaResult {
|
|
34
|
-
persona: PersonaData | null;
|
|
35
|
-
isStreaming: boolean;
|
|
36
|
-
isLoading: boolean;
|
|
37
|
-
error: Error | null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function usePersona(): UsePersonaResult {
|
|
41
|
-
const [persona, setPersona] = useState<PersonaData | null>(null);
|
|
42
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
43
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
44
|
-
const [error, setError] = useState<Error | null>(null);
|
|
45
|
-
const wsRef = useRef<WebSocket | null>(null);
|
|
46
|
-
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
50
|
-
const wsUrl = `${protocol}//${window.location.host}/ws/persona`;
|
|
51
|
-
|
|
52
|
-
const connect = () => {
|
|
53
|
-
try {
|
|
54
|
-
wsRef.current = new WebSocket(wsUrl);
|
|
55
|
-
|
|
56
|
-
wsRef.current.onopen = () => {
|
|
57
|
-
console.debug('[usePersona] WebSocket connected');
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
wsRef.current.onmessage = (event) => {
|
|
61
|
-
try {
|
|
62
|
-
const data = JSON.parse(event.data);
|
|
63
|
-
|
|
64
|
-
// Story 94-1: Handle streaming state updates
|
|
65
|
-
if (data.type === 'streaming') {
|
|
66
|
-
setIsStreaming(data.isStreaming ?? false);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Persona data (initial or agent change) — extract isStreaming if present
|
|
71
|
-
if (data.isStreaming !== undefined) {
|
|
72
|
-
setIsStreaming(data.isStreaming);
|
|
73
|
-
}
|
|
74
|
-
setPersona(data as PersonaData);
|
|
75
|
-
setIsLoading(false);
|
|
76
|
-
setError(null);
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error('[usePersona] Failed to parse message:', err);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
wsRef.current.onclose = () => {
|
|
83
|
-
console.debug('[usePersona] WebSocket closed, reconnecting...');
|
|
84
|
-
setIsStreaming(false);
|
|
85
|
-
reconnectTimeoutRef.current = setTimeout(connect, 2000);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
wsRef.current.onerror = (err) => {
|
|
89
|
-
console.error('[usePersona] WebSocket error:', err);
|
|
90
|
-
setError(new Error('WebSocket connection failed'));
|
|
91
|
-
};
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.error('[usePersona] WebSocket init failed:', err);
|
|
94
|
-
setError(err instanceof Error ? err : new Error('Failed to connect'));
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
connect();
|
|
100
|
-
|
|
101
|
-
return () => {
|
|
102
|
-
if (reconnectTimeoutRef.current) {
|
|
103
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
104
|
-
}
|
|
105
|
-
if (wsRef.current) {
|
|
106
|
-
wsRef.current.close();
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
return { persona, isStreaming, isLoading, error };
|
|
112
|
-
}
|