@pennyfarthing/cyclist 10.0.2 → 10.1.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.
Files changed (102) hide show
  1. package/dist/api/agent-load.d.ts +3 -0
  2. package/dist/api/agent-load.d.ts.map +1 -0
  3. package/dist/api/agent-load.js +124 -0
  4. package/dist/api/agent-load.js.map +1 -0
  5. package/dist/api/code-markers.d.ts +9 -0
  6. package/dist/api/code-markers.d.ts.map +1 -0
  7. package/dist/api/code-markers.js +62 -0
  8. package/dist/api/code-markers.js.map +1 -0
  9. package/dist/api/complexity.d.ts +3 -0
  10. package/dist/api/complexity.d.ts.map +1 -0
  11. package/dist/api/complexity.js +47 -0
  12. package/dist/api/complexity.js.map +1 -0
  13. package/dist/api/dead-code.d.ts +3 -0
  14. package/dist/api/dead-code.d.ts.map +1 -0
  15. package/dist/api/dead-code.js +70 -0
  16. package/dist/api/dead-code.js.map +1 -0
  17. package/dist/api/dependencies.d.ts +3 -0
  18. package/dist/api/dependencies.d.ts.map +1 -0
  19. package/dist/api/dependencies.js +43 -0
  20. package/dist/api/dependencies.js.map +1 -0
  21. package/dist/api/git.d.ts +3 -2
  22. package/dist/api/git.d.ts.map +1 -1
  23. package/dist/api/git.js +11 -6
  24. package/dist/api/git.js.map +1 -1
  25. package/dist/api/health-score.d.ts +3 -0
  26. package/dist/api/health-score.d.ts.map +1 -0
  27. package/dist/api/health-score.js +38 -0
  28. package/dist/api/health-score.js.map +1 -0
  29. package/dist/api/hotspots.d.ts.map +1 -1
  30. package/dist/api/hotspots.js +9 -1
  31. package/dist/api/hotspots.js.map +1 -1
  32. package/dist/api/index.d.ts +6 -0
  33. package/dist/api/index.d.ts.map +1 -1
  34. package/dist/api/index.js +11 -0
  35. package/dist/api/index.js.map +1 -1
  36. package/dist/api/theme-agents.d.ts +1 -0
  37. package/dist/api/theme-agents.d.ts.map +1 -1
  38. package/dist/api/theme-agents.js +1 -1
  39. package/dist/api/theme-agents.js.map +1 -1
  40. package/dist/git-diff.d.ts.map +1 -1
  41. package/dist/git-diff.js +6 -5
  42. package/dist/git-diff.js.map +1 -1
  43. package/dist/main.js +9 -3
  44. package/dist/main.js.map +1 -1
  45. package/dist/preload.js +11 -0
  46. package/dist/preload.js.map +1 -1
  47. package/dist/prime.d.ts +3 -2
  48. package/dist/prime.d.ts.map +1 -1
  49. package/dist/prime.js +25 -8
  50. package/dist/prime.js.map +1 -1
  51. package/dist/public/css/react.css +1 -1
  52. package/dist/public/js/react/react.js +50 -39
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +16 -1
  55. package/dist/server.js.map +1 -1
  56. package/dist/sprint-data.d.ts +6 -0
  57. package/dist/sprint-data.d.ts.map +1 -1
  58. package/dist/sprint-data.js +79 -3
  59. package/dist/sprint-data.js.map +1 -1
  60. package/dist/websocket.d.ts.map +1 -1
  61. package/dist/websocket.js +6 -5
  62. package/dist/websocket.js.map +1 -1
  63. package/package.json +32 -31
  64. package/src/public/App.tsx +0 -2
  65. package/src/public/components/AgentLoadDialog.tsx +202 -0
  66. package/src/public/components/ControlBar.tsx +4 -3
  67. package/src/public/components/DeadCodeDialog.tsx +169 -0
  68. package/src/public/components/DockviewWorkspace.tsx +0 -3
  69. package/src/public/components/FullFileTree.tsx +18 -4
  70. package/src/public/components/HealthGauge.tsx +144 -0
  71. package/src/public/components/MessageView.tsx +25 -22
  72. package/src/public/components/SubagentSpan.tsx +2 -2
  73. package/src/public/components/ToolCallBlock.tsx +21 -6
  74. package/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  75. package/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  76. package/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  77. package/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  78. package/src/public/components/dialogs/ToolDialog.tsx +43 -0
  79. package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +15 -30
  80. package/src/public/components/panels/DebugPanel.tsx +83 -0
  81. package/src/public/components/panels/GitPanel.tsx +12 -18
  82. package/src/public/components/panels/SprintPanel.tsx +84 -15
  83. package/src/public/components/panels/index.ts +0 -1
  84. package/src/public/components/ui/dialog.tsx +3 -3
  85. package/src/public/css/theme-system.css +5 -11
  86. package/src/public/hooks/index.ts +4 -0
  87. package/src/public/hooks/useAgentLoad.ts +105 -0
  88. package/src/public/hooks/useCodeMarkers.ts +101 -0
  89. package/src/public/hooks/useColorScheme.ts +25 -10
  90. package/src/public/hooks/useComplexity.ts +80 -0
  91. package/src/public/hooks/useDeadCode.ts +99 -0
  92. package/src/public/hooks/useDependencies.ts +82 -0
  93. package/src/public/hooks/useHealthScore.ts +77 -0
  94. package/src/public/hooks/useHotspots.ts +11 -1
  95. package/src/public/hooks/useSprint.ts +6 -0
  96. package/src/public/styles/tailwind.css +90 -78
  97. package/src/public/utils/messageFilters.ts +77 -6
  98. package/src/public/utils/slash-commands.ts +2 -18
  99. package/src/public/utils/subagent-display.ts +6 -3
  100. package/LICENSE +0 -14
  101. package/src/public/components/AskUserQuestionBlock.tsx +0 -162
  102. package/src/public/utils/askUserQuestion.ts +0 -21
@@ -616,6 +616,7 @@
616
616
  gap: 0.5rem;
617
617
  padding: 0.25rem 0;
618
618
  margin-bottom: 0.25rem;
619
+ padding-left: 2.75rem; /* align with message content: avatar 2rem + gap 0.75rem */
619
620
  }
620
621
 
621
622
  .turn-speaker {
@@ -958,7 +959,7 @@
958
959
  display: flex;
959
960
  align-items: center;
960
961
  justify-content: center;
961
- height: 100%;
962
+ flex: 1;
962
963
  color: var(--text-secondary, #8b8b8b);
963
964
  font-size: 0.875rem;
964
965
  text-align: center;
@@ -1510,15 +1511,69 @@
1510
1511
  background: var(--bg-hover, rgba(255, 255, 255, 0.03));
1511
1512
  }
1512
1513
 
1513
- .enhanced-sprint-panel .story-item .story-title {
1514
+ .enhanced-sprint-panel .story-item .story-info {
1514
1515
  flex: 1;
1515
1516
  min-width: 0;
1517
+ display: flex;
1518
+ flex-direction: column;
1519
+ gap: 1px;
1520
+ }
1521
+
1522
+ .enhanced-sprint-panel .story-item .story-title {
1516
1523
  overflow: hidden;
1517
1524
  text-overflow: ellipsis;
1518
1525
  white-space: nowrap;
1519
1526
  font-size: 0.75rem;
1520
1527
  }
1521
1528
 
1529
+ .enhanced-sprint-panel .story-item .story-meta {
1530
+ display: flex;
1531
+ align-items: center;
1532
+ gap: 6px;
1533
+ }
1534
+
1535
+ .enhanced-sprint-panel .story-item .story-assignee {
1536
+ font-size: 0.625rem;
1537
+ color: var(--text-tertiary, #6b6b6b);
1538
+ font-style: italic;
1539
+ }
1540
+
1541
+ .enhanced-sprint-panel .story-item .story-workflow-badge {
1542
+ font-size: 0.5625rem;
1543
+ text-transform: uppercase;
1544
+ letter-spacing: 0.05em;
1545
+ color: var(--text-secondary, #8b8b8b);
1546
+ background: var(--bg-tertiary, #2d2d2d);
1547
+ padding: 0 4px;
1548
+ border-radius: 2px;
1549
+ }
1550
+
1551
+ .enhanced-sprint-panel .story-item .story-completed-date {
1552
+ font-size: 0.625rem;
1553
+ font-variant-numeric: tabular-nums;
1554
+ font-family: var(--font-mono, monospace);
1555
+ color: var(--text-tertiary, #6b6b6b);
1556
+ }
1557
+
1558
+ .enhanced-sprint-panel .story-item .priority-dot {
1559
+ width: 6px;
1560
+ height: 6px;
1561
+ border-radius: 50%;
1562
+ flex-shrink: 0;
1563
+ }
1564
+
1565
+ .enhanced-sprint-panel .story-item .priority-dot.priority-p0 {
1566
+ background: var(--error-color, #f14c4c);
1567
+ }
1568
+
1569
+ .enhanced-sprint-panel .story-item .priority-dot.priority-p1 {
1570
+ background: var(--warning-color, #cca700);
1571
+ }
1572
+
1573
+ .enhanced-sprint-panel .story-item .priority-dot.priority-p2 {
1574
+ background: var(--text-tertiary, #6b6b6b);
1575
+ }
1576
+
1522
1577
  .enhanced-sprint-panel .story-item .story-points {
1523
1578
  font-size: 0.65rem;
1524
1579
  color: var(--text-secondary, #8b8b8b);
@@ -1791,42 +1846,25 @@
1791
1846
  margin-bottom: 4px;
1792
1847
  }
1793
1848
 
1794
- /* Develop behind warning */
1795
- .git-panel .develop-behind-warning {
1796
- display: flex;
1849
+ /* Sync develop button — subtle icon inline with sync counts */
1850
+ .git-panel .sync-develop-btn {
1851
+ display: inline-flex;
1797
1852
  align-items: center;
1798
- gap: 6px;
1799
- padding: 6px 8px;
1800
- margin-bottom: 8px;
1801
- background: rgba(204, 167, 0, 0.15);
1802
- border: 1px solid var(--warning-color, #cca700);
1803
- border-radius: 4px;
1804
- font-size: 0.75rem;
1805
- }
1806
-
1807
- .git-panel .develop-behind-warning .warning-icon {
1808
- flex-shrink: 0;
1809
- }
1810
-
1811
- .git-panel .develop-behind-warning .warning-text {
1812
- flex: 1;
1813
- color: var(--warning-color, #cca700);
1814
- }
1815
-
1816
- .git-panel .develop-behind-warning .pull-develop-btn {
1817
- padding: 2px 8px;
1818
- background: var(--warning-color, #cca700);
1819
- color: #000;
1853
+ gap: 3px;
1854
+ padding: 1px 5px;
1820
1855
  border: none;
1821
1856
  border-radius: 3px;
1857
+ background: none;
1858
+ color: var(--status-warning, #cca700);
1822
1859
  font-size: 0.7rem;
1823
- font-weight: 600;
1860
+ font-family: var(--font-mono);
1824
1861
  cursor: pointer;
1825
- text-transform: uppercase;
1862
+ transition: background-color 0.15s ease, color 0.15s ease;
1826
1863
  }
1827
1864
 
1828
- .git-panel .develop-behind-warning .pull-develop-btn:hover {
1829
- opacity: 0.9;
1865
+ .git-panel .sync-develop-btn:hover {
1866
+ background: rgba(204, 167, 0, 0.15);
1867
+ color: var(--text-primary);
1830
1868
  }
1831
1869
 
1832
1870
  .git-panel .repo-status .file-status {
@@ -2196,38 +2234,6 @@
2196
2234
  padding: 8px 0;
2197
2235
  }
2198
2236
 
2199
- /* AC content within Progress panel */
2200
- .ac-content {
2201
- padding: 4px 0;
2202
- }
2203
-
2204
- .ac-content .ac-list {
2205
- margin-top: 8px;
2206
- }
2207
-
2208
- .ac-content .ac-item {
2209
- display: flex;
2210
- align-items: flex-start;
2211
- gap: 6px;
2212
- padding: 4px 0;
2213
- font-size: 0.8rem;
2214
- color: var(--text-primary, #d4d4d4);
2215
- }
2216
-
2217
- .ac-content .ac-item.ac-done {
2218
- color: var(--success-color, #4ec9b0);
2219
- }
2220
-
2221
- .ac-content .ac-icon {
2222
- flex-shrink: 0;
2223
- width: 14px;
2224
- }
2225
-
2226
- .ac-content .ac-text {
2227
- flex: 1;
2228
- line-height: 1.3;
2229
- }
2230
-
2231
2237
  /* Todo content wrapper */
2232
2238
  .todo-content {
2233
2239
  padding: 4px 0;
@@ -2626,6 +2632,14 @@
2626
2632
  font-family: var(--font-mono, 'SF Mono', Monaco, monospace);
2627
2633
  }
2628
2634
 
2635
+ /* Tool launcher - MSSCI-14443 */
2636
+ .debug-panel .tool-launcher {
2637
+ display: flex;
2638
+ flex-wrap: wrap;
2639
+ gap: 6px;
2640
+ margin-top: 4px;
2641
+ }
2642
+
2629
2643
  /* Settings panel */
2630
2644
  .settings-panel {
2631
2645
  padding: 8px;
@@ -4017,15 +4031,13 @@
4017
4031
  border-color: var(--text-secondary, #8b8b8b);
4018
4032
  }
4019
4033
 
4020
- .btn-toggle .toggle-icon {
4021
- font-size: 1rem;
4022
- line-height: 1;
4023
- filter: grayscale(100%) opacity(0.5);
4024
- transition: filter 0.15s ease;
4034
+ .btn-toggle svg {
4035
+ opacity: 0.5;
4036
+ transition: opacity 0.15s ease;
4025
4037
  }
4026
4038
 
4027
- .btn-toggle:hover .toggle-icon {
4028
- filter: grayscale(50%) opacity(0.8);
4039
+ .btn-toggle:hover svg {
4040
+ opacity: 0.8;
4029
4041
  }
4030
4042
 
4031
4043
  /* Bell mode active */
@@ -4034,8 +4046,8 @@
4034
4046
  border-color: var(--color-warning, #f59e0b);
4035
4047
  }
4036
4048
 
4037
- .btn-toggle.bell-toggle.active .toggle-icon {
4038
- filter: none;
4049
+ .btn-toggle.bell-toggle.active svg {
4050
+ opacity: 1;
4039
4051
  }
4040
4052
 
4041
4053
  /* Relay mode active */
@@ -4044,8 +4056,8 @@
4044
4056
  border-color: var(--color-purple, #8b5cf6);
4045
4057
  }
4046
4058
 
4047
- .btn-toggle.relay-toggle.active .toggle-icon {
4048
- filter: none;
4059
+ .btn-toggle.relay-toggle.active svg {
4060
+ opacity: 1;
4049
4061
  }
4050
4062
 
4051
4063
  /* Active toggle colors (no animation - too distracting) */
@@ -4063,8 +4075,8 @@
4063
4075
  border-color: var(--color-cyan, #06b6d4);
4064
4076
  }
4065
4077
 
4066
- .btn-toggle.pump-toggle .toggle-icon {
4067
- filter: none;
4078
+ .btn-toggle.pump-toggle svg {
4079
+ opacity: 1;
4068
4080
  }
4069
4081
 
4070
4082
  /* TirePump warning state at 70%+ context */
@@ -4074,8 +4086,8 @@
4074
4086
  animation: pump-pulse 1.5s ease-in-out infinite;
4075
4087
  }
4076
4088
 
4077
- .btn-toggle.pump-toggle.warning .toggle-icon {
4078
- filter: none;
4089
+ .btn-toggle.pump-toggle.warning svg {
4090
+ opacity: 1;
4079
4091
  }
4080
4092
 
4081
4093
  @keyframes pump-pulse {
@@ -3,8 +3,10 @@
3
3
  *
4
4
  * Story MSSCI-12783 - Bug: Skill Content Displayed as User Message
5
5
  *
6
- * Functions to detect and filter skill/command content from user messages.
6
+ * Functions to detect and filter skill content from user messages.
7
7
  * Skill content is meant for Claude's context, not for display in the UI.
8
+ * Detected skill invocations are replaced with a short label (e.g. "reviewer")
9
+ * so the user sees what happened without the raw skill dump.
8
10
  */
9
11
 
10
12
  /**
@@ -18,6 +20,36 @@ const SKILL_CONTENT_MARKERS = [
18
20
  'Launching skill:',
19
21
  ] as const;
20
22
 
23
+ /**
24
+ * Patterns that detect skill body content leaked as separate user messages.
25
+ * These are the raw skill markdown bodies that Claude Code sends as user messages
26
+ * after the initial <command-message> wrapper.
27
+ */
28
+ const SKILL_BODY_PATTERNS = [
29
+ /^```bash\s*\npf agent start\b/, // Agent activation bash block
30
+ /^pf agent start\b/, // Bare agent start command
31
+ /^<purpose>/, // Skill body XML tags
32
+ /^<when-to-use>/,
33
+ /^<execution>/,
34
+ /^<critical>\s*\n/, // Skill body starting with critical block
35
+ ] as const;
36
+
37
+ /**
38
+ * Agent name to human-readable label mapping.
39
+ */
40
+ const AGENT_LABELS: Record<string, string> = {
41
+ sm: 'Scrum Master',
42
+ tea: 'Test Engineer',
43
+ dev: 'Developer',
44
+ reviewer: 'Reviewer',
45
+ architect: 'Architect',
46
+ pm: 'Product Manager',
47
+ 'tech-writer': 'Tech Writer',
48
+ 'ux-designer': 'UX Designer',
49
+ devops: 'DevOps',
50
+ orchestrator: 'Orchestrator',
51
+ };
52
+
21
53
  /**
22
54
  * Check if a message content string contains skill/command content
23
55
  * that should be filtered from user message display.
@@ -26,13 +58,54 @@ const SKILL_CONTENT_MARKERS = [
26
58
  * @returns true if the content contains skill markers and should be filtered
27
59
  */
28
60
  export function isSkillContent(content: string | null | undefined): boolean {
29
- // Handle null/undefined/empty gracefully
30
61
  if (!content || typeof content !== 'string') {
31
62
  return false;
32
63
  }
33
64
 
34
- // Check for any skill content marker
35
- return SKILL_CONTENT_MARKERS.some(marker => content.includes(marker));
65
+ if (SKILL_CONTENT_MARKERS.some(marker => content.includes(marker))) {
66
+ return true;
67
+ }
68
+
69
+ const trimmed = content.trimStart();
70
+ return SKILL_BODY_PATTERNS.some(pattern => pattern.test(trimmed));
71
+ }
72
+
73
+ /**
74
+ * Extract the skill/agent name from skill content for labeling.
75
+ * Returns a human-readable label like "Scrum Master" or the raw skill name.
76
+ */
77
+ export function extractSkillLabel(content: string | null | undefined): string | null {
78
+ if (!content || typeof content !== 'string') return null;
79
+
80
+ // Match <command-name>/foo</command-name>
81
+ const cmdMatch = content.match(/<command-name>\/?([^<]+)<\/command-name>/);
82
+ if (cmdMatch) {
83
+ const name = cmdMatch[1].trim();
84
+ return AGENT_LABELS[name] || name;
85
+ }
86
+
87
+ // Match <command-message>foo</command-message>
88
+ const msgMatch = content.match(/<command-message>([^<]+)<\/command-message>/);
89
+ if (msgMatch) {
90
+ const name = msgMatch[1].trim();
91
+ return AGENT_LABELS[name] || name;
92
+ }
93
+
94
+ // Match pf agent start "foo"
95
+ const agentMatch = content.match(/pf agent start\s+"([^"]+)"/);
96
+ if (agentMatch) {
97
+ const name = agentMatch[1].trim();
98
+ return AGENT_LABELS[name] || name;
99
+ }
100
+
101
+ // Match "Launching skill: foo"
102
+ const launchMatch = content.match(/Launching skill:\s*(\S+)/);
103
+ if (launchMatch) {
104
+ const name = launchMatch[1].trim();
105
+ return AGENT_LABELS[name] || name;
106
+ }
107
+
108
+ return null;
36
109
  }
37
110
 
38
111
  /**
@@ -46,11 +119,9 @@ export function filterSkillContentMessages<T extends { type: string; content?: s
46
119
  messages: T[]
47
120
  ): T[] {
48
121
  return messages.filter(msg => {
49
- // Only filter user messages
50
122
  if (msg.type !== 'user') {
51
123
  return true;
52
124
  }
53
- // Filter out skill content
54
125
  return !isSkillContent(msg.content);
55
126
  });
56
127
  }
@@ -88,10 +88,6 @@ export const SLASH_COMMANDS: SlashCommand[] = [
88
88
  "name": "/create-branches-from-story",
89
89
  "description": "Create feature branches in both repos from a story"
90
90
  },
91
- {
92
- "name": "/create-theme",
93
- "description": "Create a new custom persona theme"
94
- },
95
91
  {
96
92
  "name": "/dev",
97
93
  "description": "Developer - Feature implementation and coding"
@@ -128,10 +124,6 @@ export const SLASH_COMMANDS: SlashCommand[] = [
128
124
  "name": "/job-fair",
129
125
  "description": "Discover which characters in a theme excel at each role"
130
126
  },
131
- {
132
- "name": "/list-themes",
133
- "description": "List all available persona themes"
134
- },
135
127
  {
136
128
  "name": "/login",
137
129
  "description": "Authenticate with Anthropic"
@@ -208,18 +200,10 @@ export const SLASH_COMMANDS: SlashCommand[] = [
208
200
  "name": "/run-ci",
209
201
  "description": "Detect and run CI locally"
210
202
  },
211
- {
212
- "name": "/set-theme",
213
- "description": "Set the active persona theme"
214
- },
215
203
  {
216
204
  "name": "/setup",
217
205
  "description": "setup"
218
206
  },
219
- {
220
- "name": "/show-theme",
221
- "description": "Show details of a theme including all agent personas"
222
- },
223
207
  {
224
208
  "name": "/sm",
225
209
  "description": "Scrum Master - Story coordination and sprint management"
@@ -269,8 +253,8 @@ export const SLASH_COMMANDS: SlashCommand[] = [
269
253
  "description": "Configure terminal"
270
254
  },
271
255
  {
272
- "name": "/theme-maker",
273
- "description": "Interactive wizard for creating custom persona themes"
256
+ "name": "/theme",
257
+ "description": "Manage persona themes - list, show, set, create, or interactive wizard"
274
258
  },
275
259
  {
276
260
  "name": "/update-domain-docs",
@@ -18,6 +18,7 @@ export interface TaskInput {
18
18
  export interface Helper {
19
19
  name: string;
20
20
  style: string;
21
+ plural?: boolean;
21
22
  }
22
23
 
23
24
  // Cache for helper lookups
@@ -119,7 +120,8 @@ const SUBAGENT_TYPE_MESSAGES: Record<string, string> = {
119
120
  'Bash': 'Running commands',
120
121
  };
121
122
 
122
- export function generateFriendlyMessage(context: TaskInput): string {
123
+ export function generateFriendlyMessage(context: TaskInput, options?: { plural?: boolean }): string {
124
+ const verb = options?.plural ? 'are' : 'is';
123
125
  // If we have a description, include it
124
126
  const description = context.description;
125
127
 
@@ -127,10 +129,11 @@ export function generateFriendlyMessage(context: TaskInput): string {
127
129
  const subagentType = context.subagent_type;
128
130
  if (subagentType && SUBAGENT_TYPE_MESSAGES[subagentType]) {
129
131
  const action = SUBAGENT_TYPE_MESSAGES[subagentType];
132
+ const lowerAction = action.charAt(0).toLowerCase() + action.slice(1);
130
133
  if (description) {
131
- return `${action}: ${description}`;
134
+ return `${verb} ${lowerAction}: ${description}`;
132
135
  }
133
- return action;
136
+ return `${verb} ${lowerAction}`;
134
137
  }
135
138
 
136
139
  // Fall back to description if available
package/LICENSE DELETED
@@ -1,14 +0,0 @@
1
- Copyright (c) 2026 1898 & Co.
2
-
3
- All rights reserved.
4
-
5
- This software and associated documentation files (the "Software") are proprietary
6
- and confidential. No part of this Software may be reproduced, distributed, or
7
- transmitted in any form or by any means, including photocopying, recording, or
8
- other electronic or mechanical methods, without the prior written permission of
9
- the copyright holder.
10
-
11
- Unauthorized copying, modification, distribution, or use of this Software, via
12
- any medium, is strictly prohibited.
13
-
14
- For licensing inquiries, contact: 1898 & Co.
@@ -1,162 +0,0 @@
1
- /**
2
- * AskUserQuestionBlock Component
3
- *
4
- * Renders interactive buttons for AskUserQuestion tool_use messages.
5
- * Uses the existing ClaudeContext to send responses back via WebSocket.
6
- *
7
- * Story: MSSCI-14395 - Render AskUserQuestion tool via Reflector QuickActions
8
- */
9
-
10
- import React, { useState, useCallback } from 'react';
11
- import { Button } from '@/components/ui/button';
12
- import { Badge } from '@/components/ui/badge';
13
- import { useClaudeContext } from '../contexts/ClaudeContext';
14
-
15
- interface QuestionOption {
16
- label: string;
17
- description: string;
18
- }
19
-
20
- interface Question {
21
- question: string;
22
- header: string;
23
- options: QuestionOption[];
24
- multiSelect: boolean;
25
- }
26
-
27
- interface AskUserQuestionToolUse {
28
- type: 'tool_use';
29
- tool_name: string;
30
- tool_id: string;
31
- input: {
32
- questions: Question[];
33
- };
34
- timestamp: number;
35
- }
36
-
37
- interface AskUserQuestionBlockProps {
38
- toolUse: AskUserQuestionToolUse;
39
- }
40
-
41
- function SingleSelectQuestion({ question, onSubmit, disabled }: {
42
- question: Question;
43
- onSubmit: (answer: string) => void;
44
- disabled: boolean;
45
- }) {
46
- return (
47
- <div className="ask-question-group">
48
- <Badge variant="secondary" className="ask-question-header">{question.header}</Badge>
49
- <p className="ask-question-text">{question.question}</p>
50
- <div className="ask-question-options">
51
- {question.options.map((opt) => (
52
- <Button
53
- key={opt.label}
54
- variant="secondary"
55
- size="sm"
56
- className="ask-question-option"
57
- onClick={() => onSubmit(opt.label)}
58
- disabled={disabled}
59
- >
60
- <span className="ask-option-label">{opt.label}</span>
61
- <span className="ask-option-desc">{opt.description}</span>
62
- </Button>
63
- ))}
64
- </div>
65
- </div>
66
- );
67
- }
68
-
69
- function MultiSelectQuestion({ question, onSubmit, disabled }: {
70
- question: Question;
71
- onSubmit: (answers: string[]) => void;
72
- disabled: boolean;
73
- }) {
74
- const [selected, setSelected] = useState<Set<string>>(new Set());
75
-
76
- const toggleOption = useCallback((label: string) => {
77
- setSelected(prev => {
78
- const next = new Set(prev);
79
- if (next.has(label)) {
80
- next.delete(label);
81
- } else {
82
- next.add(label);
83
- }
84
- return next;
85
- });
86
- }, []);
87
-
88
- const handleSubmit = useCallback(() => {
89
- onSubmit(Array.from(selected));
90
- }, [selected, onSubmit]);
91
-
92
- return (
93
- <div className="ask-question-group">
94
- <Badge variant="secondary" className="ask-question-header">{question.header}</Badge>
95
- <p className="ask-question-text">{question.question}</p>
96
- <div className="ask-question-options">
97
- {question.options.map((opt) => (
98
- <Button
99
- key={opt.label}
100
- variant="secondary"
101
- size="sm"
102
- className="ask-question-option"
103
- onClick={() => toggleOption(opt.label)}
104
- disabled={disabled}
105
- aria-pressed={selected.has(opt.label)}
106
- >
107
- <span className="ask-option-label">{opt.label}</span>
108
- <span className="ask-option-desc">{opt.description}</span>
109
- </Button>
110
- ))}
111
- </div>
112
- <Button
113
- variant="default"
114
- size="sm"
115
- className="ask-question-submit"
116
- onClick={handleSubmit}
117
- disabled={disabled || selected.size === 0}
118
- aria-label="Confirm"
119
- >
120
- Confirm
121
- </Button>
122
- </div>
123
- );
124
- }
125
-
126
- export function AskUserQuestionBlock({ toolUse }: AskUserQuestionBlockProps): React.ReactElement {
127
- const { send } = useClaudeContext();
128
- const [isDisabled, setIsDisabled] = useState(false);
129
- const questions = toolUse.input?.questions || [];
130
-
131
- const handleSingleSelect = useCallback((answer: string) => {
132
- setIsDisabled(true);
133
- send(answer, []);
134
- }, [send]);
135
-
136
- const handleMultiSelect = useCallback((answers: string[]) => {
137
- setIsDisabled(true);
138
- send(answers.join(', '), []);
139
- }, [send]);
140
-
141
- return (
142
- <div className="ask-user-question-block">
143
- {questions.map((q, i) => (
144
- q.multiSelect ? (
145
- <MultiSelectQuestion
146
- key={i}
147
- question={q}
148
- onSubmit={handleMultiSelect}
149
- disabled={isDisabled}
150
- />
151
- ) : (
152
- <SingleSelectQuestion
153
- key={i}
154
- question={q}
155
- onSubmit={handleSingleSelect}
156
- disabled={isDisabled}
157
- />
158
- )
159
- ))}
160
- </div>
161
- );
162
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * AskUserQuestion Utility
3
- *
4
- * Utility functions for detecting AskUserQuestion tool_use messages.
5
- *
6
- * Story: MSSCI-14395 - Render AskUserQuestion tool via Reflector QuickActions
7
- */
8
-
9
- interface ToolUseMessage {
10
- type: string;
11
- tool_name?: string;
12
- tool_id?: string;
13
- input?: Record<string, unknown>;
14
- }
15
-
16
- /**
17
- * Check if a tool_use message is an AskUserQuestion tool call.
18
- */
19
- export function isAskUserQuestion(toolUse: ToolUseMessage): boolean {
20
- return toolUse.tool_name === 'AskUserQuestion';
21
- }