auq-mcp-server 3.2.11 → 3.3.1

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.
@@ -128,7 +128,7 @@ const App = ({ config }) => {
128
128
  watcherInstance.stop();
129
129
  }
130
130
  if (notificationBatcherRef.current) {
131
- notificationBatcherRef.current.cancel();
131
+ notificationBatcherRef.current.flush();
132
132
  }
133
133
  // Clear progress bar on unmount
134
134
  clearProgress(notificationConfig);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "3.2.11",
3
+ "version": "3.3.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "bin/auq"
@@ -164,6 +164,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
164
164
  // Track mount status to avoid state updates after unmount
165
165
  const isMountedRef = useRef(true);
166
166
  const skipSnapshotRef = useRef(true);
167
+ const sessionIdRef = useRef(null);
167
168
  useEffect(() => {
168
169
  isMountedRef.current = true;
169
170
  return () => {
@@ -172,6 +173,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
172
173
  }, []);
173
174
  // Reset internal stepper state when the session changes (safety in case component isn't remounted)
174
175
  useEffect(() => {
176
+ // Only run full initialization when the session actually changes.
177
+ // When a snapshot is saved (cursor movement), initialState prop changes but
178
+ // sessionId stays the same — skip the reset to avoid resetting the timer.
179
+ if (sessionId === sessionIdRef.current)
180
+ return;
181
+ sessionIdRef.current = sessionId;
175
182
  const maxQuestionIndex = Math.max(0, sessionRequest.questions.length - 1);
176
183
  if (initialState) {
177
184
  const hydratedQuestionIndex = Math.min(Math.max(initialState.currentQuestionIndex, 0), maxQuestionIndex);
@@ -99,4 +99,79 @@ describe("StepperView SessionUIState boundary", () => {
99
99
  showAbandonedConfirm: false,
100
100
  });
101
101
  });
102
+ it("should NOT reset answers when initialState changes for the same sessionId", async () => {
103
+ const onStateSnapshot = vi.fn();
104
+ const initialState = {
105
+ currentQuestionIndex: 0,
106
+ answers: new Map([[0, { selectedOption: "TypeScript" }]]),
107
+ elaborateMarks: new Map(),
108
+ focusContext: "option",
109
+ focusedOptionIndex: 0,
110
+ showReview: false,
111
+ };
112
+ const refreshedInitialState = {
113
+ currentQuestionIndex: 0,
114
+ answers: new Map([[0, { selectedOption: "Python" }]]),
115
+ elaborateMarks: new Map(),
116
+ focusContext: "option",
117
+ focusedOptionIndex: 0,
118
+ showReview: false,
119
+ };
120
+ const instance = renderStepper({ initialState, onStateSnapshot });
121
+ instance.stdin.write("\t");
122
+ await vi.waitFor(() => {
123
+ expect(onStateSnapshot).toHaveBeenCalled();
124
+ });
125
+ instance.rerender(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
126
+ React.createElement(ConfigProvider, null,
127
+ React.createElement(StepperView, { sessionId: sessionRequest.sessionId, sessionRequest: sessionRequest, initialState: refreshedInitialState, onStateSnapshot: onStateSnapshot }))));
128
+ instance.stdin.write("\t");
129
+ await vi.waitFor(() => {
130
+ expect(onStateSnapshot.mock.calls.length).toBeGreaterThanOrEqual(2);
131
+ });
132
+ const [, snapshotState] = onStateSnapshot.mock.lastCall;
133
+ expect(snapshotState.currentQuestionIndex).toBe(2);
134
+ expect(snapshotState.answers.get(0)?.selectedOption).toBe("TypeScript");
135
+ expect(snapshotState.answers.get(0)?.selectedOption).not.toBe("Python");
136
+ });
137
+ it("should reset answers when sessionId changes", async () => {
138
+ const onStateSnapshot = vi.fn();
139
+ const initialState = {
140
+ currentQuestionIndex: 0,
141
+ answers: new Map([[0, { selectedOption: "TypeScript" }]]),
142
+ elaborateMarks: new Map(),
143
+ focusContext: "option",
144
+ focusedOptionIndex: 0,
145
+ showReview: false,
146
+ };
147
+ const newSessionInitialState = {
148
+ currentQuestionIndex: 0,
149
+ answers: new Map([[0, { selectedOption: "Python" }]]),
150
+ elaborateMarks: new Map(),
151
+ focusContext: "option",
152
+ focusedOptionIndex: 0,
153
+ showReview: false,
154
+ };
155
+ const instance = renderStepper({
156
+ sessionId: "test-1",
157
+ initialState,
158
+ onStateSnapshot,
159
+ });
160
+ instance.stdin.write("\t");
161
+ await vi.waitFor(() => {
162
+ expect(onStateSnapshot).toHaveBeenCalled();
163
+ });
164
+ instance.rerender(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
165
+ React.createElement(ConfigProvider, null,
166
+ React.createElement(StepperView, { sessionId: "test-2", sessionRequest: sessionRequest, initialState: newSessionInitialState, onStateSnapshot: onStateSnapshot }))));
167
+ instance.stdin.write("\t");
168
+ await vi.waitFor(() => {
169
+ expect(onStateSnapshot.mock.calls.length).toBeGreaterThanOrEqual(2);
170
+ });
171
+ const [snapshotSessionId, snapshotState] = onStateSnapshot.mock.lastCall;
172
+ expect(snapshotSessionId).toBe("test-2");
173
+ expect(snapshotState.currentQuestionIndex).toBe(1);
174
+ expect(snapshotState.answers.get(0)?.selectedOption).toBe("Python");
175
+ expect(snapshotState.answers.get(0)?.selectedOption).not.toBe("TypeScript");
176
+ });
102
177
  });
@@ -21,6 +21,7 @@ import { SessionPicker as _SessionPicker } from "./components/SessionPicker.js";
21
21
  import { UpdateOverlay as _UpdateOverlay } from "./components/UpdateOverlay.js";
22
22
  import { Toast as _Toast } from "./components/Toast.js";
23
23
  import { ThemeIndicator as _ThemeIndicator } from "./components/ThemeIndicator.js";
24
+ import { createNotificationBatcher, } from "../tui/notifications/index.js";
24
25
  // Cast to FC to avoid React class component type mismatch between @opentui/react
25
26
  // bundled React version and the project's @types/react (structural type incompatibility).
26
27
  // ErrorBoundary is still a valid class component at runtime.
@@ -54,6 +55,14 @@ function AppInner({ config }) {
54
55
  const [changelogContent, setChangelogContent] = useState(null);
55
56
  const [updateDismissed, setUpdateDismissed] = useState(false);
56
57
  const [isCheckingUpdate, setIsCheckingUpdate] = useState(false);
58
+ // Notification configuration from config
59
+ const notificationConfig = useMemo(() => config?.notifications ?? { enabled: true, sound: true }, [config?.notifications]);
60
+ // Create notification batcher (memoized to persist across renders)
61
+ const notificationBatcherRef = useRef(null);
62
+ if (!notificationBatcherRef.current) {
63
+ notificationBatcherRef.current =
64
+ createNotificationBatcher(notificationConfig);
65
+ }
57
66
  const sessionDir = getSessionDirectory();
58
67
  // ── Show toast helper ────────────────────────────────────────
59
68
  const showToast = useCallback((message, type = "success", title) => {
@@ -95,6 +104,10 @@ function AppInner({ config }) {
95
104
  setSessionQueue((prev) => {
96
105
  if (prev.some((s) => s.sessionId === event.sessionId))
97
106
  return prev;
107
+ // Queue notification for new session (batched)
108
+ if (notificationBatcherRef.current) {
109
+ notificationBatcherRef.current.queue(event.sessionId);
110
+ }
98
111
  return [
99
112
  ...prev,
100
113
  {
@@ -115,6 +128,9 @@ function AppInner({ config }) {
115
128
  return () => {
116
129
  if (watcherInstance)
117
130
  watcherInstance.stop();
131
+ if (notificationBatcherRef.current) {
132
+ notificationBatcherRef.current.flush();
133
+ }
118
134
  };
119
135
  // eslint-disable-next-line react-hooks/exhaustive-deps
120
136
  }, []);
@@ -8,7 +8,7 @@ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧",
8
8
  * Footer component — displays context-aware keybinding hints.
9
9
  * Shows different shortcuts based on current focus context and question type.
10
10
  */
11
- export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, showSessionSwitching = false, customInputValue: _customInputValue = "", hasRecommendedOptions = false, hasAnyRecommendedInSession = false, isSubmitting = false, hasUpdate = false, }) => {
11
+ export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, showSessionSwitching = false, customInputValue: _customInputValue = "", hasRecommendedOptions = false, hasAnyRecommendedInSession = false, isSubmitting = false, hasUpdate = false, showMultiToggleHint = false, isForceMultiActive = false, }) => {
12
12
  const { theme } = useTheme();
13
13
  const [spinnerFrame, setSpinnerFrame] = useState(0);
14
14
  // Animate spinner when submitting
@@ -63,6 +63,12 @@ export const Footer = ({ focusContext, multiSelect, isReviewScreen = false, show
63
63
  bindings.push({ key: KEY_LABELS.SELECT, action: t("footer.select") });
64
64
  bindings.push({ key: KEY_LABELS.SELECT_NEXT, action: t("footer.selectNext") });
65
65
  }
66
+ if (showMultiToggleHint) {
67
+ bindings.push({
68
+ key: "M",
69
+ action: isForceMultiActive ? "Disable Multi-select" : "Toggle Multi-select",
70
+ });
71
+ }
66
72
  if (hasRecommendedOptions) {
67
73
  bindings.push({ key: KEY_LABELS.RECOMMEND, action: t("footer.recommended") });
68
74
  }
@@ -9,7 +9,7 @@ import { TabBar } from "./TabBar.js";
9
9
  * QuestionDisplay shows a single question with its options.
10
10
  * Composes TabBar, MarkdownPrompt, OptionsList, and Footer.
11
11
  */
12
- export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", showSessionSwitching, elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, focusContext, onFocusContextChange, focusedOptionIndex, onFocusedOptionIndexChange, workingDirectory, onRecommendedDetected, hasRecommendedOptions, hasAnyRecommendedInSession, elaborateMarks, onElaborateSelect, elaborateText = "", onElaborateTextChange, onSelectIndex, }) => {
12
+ export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customAnswer = "", showSessionSwitching, elapsedLabel, onChangeCustomAnswer, onSelectOption, questions, selectedOption, onAdvanceToNext, answers, onToggleOption, multiSelect, focusContext, onFocusContextChange, focusedOptionIndex, onFocusedOptionIndexChange, workingDirectory, onRecommendedDetected, hasRecommendedOptions, hasAnyRecommendedInSession, elaborateMarks, onElaborateSelect, elaborateText = "", onElaborateTextChange, onSelectIndex, showMultiToggleHint = false, isForceMultiActive = false, }) => {
13
13
  const { theme } = useTheme();
14
14
  // Handle option selection
15
15
  const handleSelectOption = (label) => {
@@ -19,5 +19,5 @@ export const QuestionDisplay = ({ currentQuestion, currentQuestionIndex, customA
19
19
  const handleCustomAnswerChange = (text) => {
20
20
  onChangeCustomAnswer(text);
21
21
  };
22
- return (_jsxs("box", { style: { flexDirection: "column" }, children: [workingDirectory && (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.directory.label, children: "📁" }), _jsx("text", { style: { fg: theme.components.directory.path }, children: ` ${workingDirectory}` })] })), _jsx(TabBar, { currentIndex: currentQuestionIndex, questions: questions, answers: answers, elaborateMarks: elaborateMarks, onSelectIndex: onSelectIndex }), _jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("box", { children: _jsx(MarkdownPrompt, { text: currentQuestion.prompt }) }), _jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.questionDisplay.elapsed, children: elapsedLabel }), _jsx("text", { style: { fg: theme.components.questionDisplay.typeIndicator }, children: ` [${multiSelect ? t("question.multipleChoice") : t("question.singleChoice")}]` })] })] }), _jsx(OptionsList, { customValue: customAnswer, isFocused: true, onAdvance: onAdvanceToNext, onCustomChange: handleCustomAnswerChange, onSelect: handleSelectOption, options: currentQuestion.options, selectedOption: selectedOption, showCustomInput: true, onToggle: onToggleOption, multiSelect: multiSelect, selectedOptions: answers.get(currentQuestionIndex)?.selectedOptions, onFocusContextChange: onFocusContextChange, focusedIndex: focusedOptionIndex, onFocusedIndexChange: onFocusedOptionIndexChange, onRecommendedDetected: onRecommendedDetected, questionKey: currentQuestionIndex, isElaborateMarked: elaborateMarks?.has(currentQuestionIndex), onElaborateSelect: onElaborateSelect, elaborateText: elaborateText, onElaborateTextChange: onElaborateTextChange }), _jsx("box", { style: { marginTop: 1 }, children: _jsx(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, showSessionSwitching: showSessionSwitching }) })] }));
22
+ return (_jsxs("box", { style: { flexDirection: "column" }, children: [workingDirectory && (_jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.directory.label, children: "📁" }), _jsx("text", { style: { fg: theme.components.directory.path }, children: ` ${workingDirectory}` })] })), _jsx(TabBar, { currentIndex: currentQuestionIndex, questions: questions, answers: answers, elaborateMarks: elaborateMarks, onSelectIndex: onSelectIndex }), _jsxs("box", { style: { flexDirection: "column" }, children: [_jsx("box", { children: _jsx(MarkdownPrompt, { text: currentQuestion.prompt }) }), _jsxs("box", { style: { flexDirection: "row" }, children: [_jsx("text", { fg: theme.components.questionDisplay.elapsed, children: elapsedLabel }), _jsx("text", { style: { fg: theme.components.questionDisplay.typeIndicator }, children: ` [${multiSelect ? t("question.multipleChoice") : t("question.singleChoice")}]` })] })] }), _jsx(OptionsList, { customValue: customAnswer, isFocused: true, onAdvance: onAdvanceToNext, onCustomChange: handleCustomAnswerChange, onSelect: handleSelectOption, options: currentQuestion.options, selectedOption: selectedOption, showCustomInput: true, onToggle: onToggleOption, multiSelect: multiSelect, selectedOptions: answers.get(currentQuestionIndex)?.selectedOptions, onFocusContextChange: onFocusContextChange, focusedIndex: focusedOptionIndex, onFocusedIndexChange: onFocusedOptionIndexChange, onRecommendedDetected: onRecommendedDetected, questionKey: currentQuestionIndex, isElaborateMarked: elaborateMarks?.has(currentQuestionIndex), onElaborateSelect: onElaborateSelect, elaborateText: elaborateText, onElaborateTextChange: onElaborateTextChange }), _jsx("box", { style: { marginTop: 1 }, children: _jsx(Footer, { focusContext: focusContext, multiSelect: multiSelect ?? false, customInputValue: customAnswer, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, showSessionSwitching: showSessionSwitching, showMultiToggleHint: showMultiToggleHint, isForceMultiActive: isForceMultiActive }) })] }));
23
23
  };
@@ -35,8 +35,14 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
35
35
  const [hasRecommendedOptions, setHasRecommendedOptions] = useState(false);
36
36
  const [hasAnyRecommendedInSession, setHasAnyRecommendedInSession] = useState(false);
37
37
  const [elaborateMarks, setElaborateMarks] = useState(new Map());
38
+ const [forceMultiByQuestion, setForceMultiByQuestion] = useState(new Set());
38
39
  const safeIndex = Math.min(currentQuestionIndex, sessionRequest.questions.length - 1);
39
40
  const currentQuestion = sessionRequest.questions[safeIndex];
41
+ const isQuestionMultiSelect = (questionIndex) => {
42
+ const question = sessionRequest.questions[questionIndex];
43
+ return Boolean(question?.multiSelect || forceMultiByQuestion.has(questionIndex));
44
+ };
45
+ const currentQuestionMultiSelect = isQuestionMultiSelect(currentQuestionIndex);
40
46
  const sessionCreatedAt = useMemo(() => {
41
47
  const parsed = Date.parse(sessionRequest.timestamp);
42
48
  return Number.isNaN(parsed) ? Date.now() : parsed;
@@ -82,12 +88,14 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
82
88
  newAnswers.set(currentQuestionIndex, {
83
89
  ...existingAnswer,
84
90
  selectedOption: undefined,
91
+ selectedOptions: undefined,
85
92
  });
86
93
  }
87
94
  else {
88
95
  newAnswers.set(currentQuestionIndex, {
89
96
  ...existingAnswer,
90
97
  selectedOption: label,
98
+ selectedOptions: undefined,
91
99
  customText: undefined,
92
100
  });
93
101
  }
@@ -115,6 +123,8 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
115
123
  ? [...currentSelections, label]
116
124
  : currentSelections.filter((l) => l !== label);
117
125
  newAnswers.set(currentQuestionIndex, {
126
+ ...existing,
127
+ selectedOption: undefined,
118
128
  selectedOptions: newSelections,
119
129
  customText: existing.customText,
120
130
  });
@@ -136,12 +146,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
136
146
  setAnswers((prev) => {
137
147
  const newAnswers = new Map(prev);
138
148
  const existing = newAnswers.get(currentQuestionIndex) || {};
139
- const question = sessionRequest.questions[currentQuestionIndex];
149
+ const isMultiSelect = isQuestionMultiSelect(currentQuestionIndex);
140
150
  newAnswers.set(currentQuestionIndex, {
141
151
  ...existing,
142
152
  customText: text,
143
153
  // Single-choice: clear selectedOption when typing custom text
144
- ...(text.trim().length > 0 && !question?.multiSelect ? { selectedOption: undefined } : {}),
154
+ ...(text.trim().length > 0 && !isMultiSelect ? { selectedOption: undefined } : {}),
145
155
  });
146
156
  return newAnswers;
147
157
  });
@@ -193,6 +203,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
193
203
  setShowReview(false);
194
204
  }
195
205
  setSubmitting(false);
206
+ setForceMultiByQuestion(new Set());
196
207
  setShowRejectionConfirm(false);
197
208
  setElapsedSeconds(0);
198
209
  skipSnapshotRef.current = true;
@@ -266,14 +277,34 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
266
277
  const sessionManager = new SessionManager({
267
278
  baseDir: getSessionDirectory(),
268
279
  });
269
- // Add elaborate requests for marked questions
270
- const allAnswers = [...userAnswers];
280
+ // Apply forced single/multi override before persisting answers
281
+ const normalizedAnswers = userAnswers.map((answer) => {
282
+ const isMultiSelect = isQuestionMultiSelect(answer.questionIndex);
283
+ if (isMultiSelect) {
284
+ const selectedOptions = answer.selectedOptions ??
285
+ (answer.selectedOption ? [answer.selectedOption] : undefined);
286
+ return {
287
+ ...answer,
288
+ selectedOption: undefined,
289
+ selectedOptions,
290
+ };
291
+ }
292
+ const selectedOption = answer.selectedOption ?? answer.selectedOptions?.[0];
293
+ return {
294
+ ...answer,
295
+ selectedOption,
296
+ selectedOptions: undefined,
297
+ };
298
+ });
299
+ const allAnswers = [...normalizedAnswers];
271
300
  elaborateMarks.forEach((customExplanation, questionIndex) => {
272
301
  const question = sessionRequest.questions[questionIndex];
273
302
  if (question) {
274
303
  const elaborateRequest = ResponseFormatter.formatElaborateRequest(questionIndex, question.title, question.prompt, customExplanation || undefined);
275
304
  allAnswers.push({
276
305
  questionIndex,
306
+ selectedOption: undefined,
307
+ selectedOptions: undefined,
277
308
  customText: elaborateRequest,
278
309
  timestamp: new Date().toISOString(),
279
310
  });
@@ -340,7 +371,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
340
371
  return newMarks;
341
372
  });
342
373
  // In single-select mode, clear selected option when marking elaborate
343
- if (isMarking && !currentQuestion.multiSelect) {
374
+ if (isMarking && !currentQuestionMultiSelect) {
344
375
  setAnswers((prev) => {
345
376
  const existing = prev.get(currentQuestionIndex);
346
377
  if (existing?.selectedOption || existing?.customText) {
@@ -377,6 +408,48 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
377
408
  });
378
409
  }
379
410
  };
411
+ const handleToggleForceMulti = () => {
412
+ if (currentQuestion.multiSelect)
413
+ return;
414
+ const wasForced = forceMultiByQuestion.has(currentQuestionIndex);
415
+ setForceMultiByQuestion((prev) => {
416
+ const next = new Set(prev);
417
+ if (next.has(currentQuestionIndex)) {
418
+ next.delete(currentQuestionIndex);
419
+ }
420
+ else {
421
+ next.add(currentQuestionIndex);
422
+ }
423
+ return next;
424
+ });
425
+ setAnswers((prev) => {
426
+ const existing = prev.get(currentQuestionIndex);
427
+ if (!existing)
428
+ return prev;
429
+ const nextAnswers = new Map(prev);
430
+ if (wasForced) {
431
+ const firstSelected = existing.selectedOptions?.[0] ?? existing.selectedOption;
432
+ nextAnswers.set(currentQuestionIndex, {
433
+ ...existing,
434
+ selectedOption: firstSelected,
435
+ selectedOptions: undefined,
436
+ });
437
+ }
438
+ else {
439
+ const promotedSelections = existing.selectedOptions?.length
440
+ ? existing.selectedOptions
441
+ : existing.selectedOption
442
+ ? [existing.selectedOption]
443
+ : [];
444
+ nextAnswers.set(currentQuestionIndex, {
445
+ ...existing,
446
+ selectedOption: undefined,
447
+ selectedOptions: promotedSelections,
448
+ });
449
+ }
450
+ return nextAnswers;
451
+ });
452
+ };
380
453
  // Keyboard handling for abandoned confirmation dialog
381
454
  useKeyboard((key) => {
382
455
  if (!showAbandonedConfirm)
@@ -430,7 +503,7 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
430
503
  }
431
504
  const recommendedOptions = question.options.filter((opt) => isRecommendedOption(opt.label));
432
505
  if (recommendedOptions.length > 0) {
433
- if (question.multiSelect) {
506
+ if (isQuestionMultiSelect(i)) {
434
507
  newAnswers.set(i, {
435
508
  selectedOptions: recommendedOptions.map((opt) => opt.label),
436
509
  });
@@ -454,11 +527,12 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
454
527
  const question = currentQuestion;
455
528
  const recommendedOptions = question.options.filter((opt) => isRecommendedOption(opt.label));
456
529
  if (recommendedOptions.length > 0) {
457
- if (question.multiSelect) {
530
+ if (currentQuestionMultiSelect) {
458
531
  setAnswers((prev) => {
459
532
  const newAnswers = new Map(prev);
460
533
  newAnswers.set(currentQuestionIndex, {
461
534
  ...newAnswers.get(currentQuestionIndex),
535
+ selectedOption: undefined,
462
536
  selectedOptions: recommendedOptions.map((opt) => opt.label),
463
537
  });
464
538
  return newAnswers;
@@ -470,6 +544,13 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
470
544
  }
471
545
  return;
472
546
  }
547
+ if (key.name?.toLowerCase() === "m" &&
548
+ !key.ctrl &&
549
+ !isInTextInput &&
550
+ !currentQuestion.multiSelect) {
551
+ handleToggleForceMulti();
552
+ return;
553
+ }
473
554
  // Tab/Shift+Tab: Global question navigation
474
555
  if (key.name === "tab" && !isInTextInput) {
475
556
  if (key.shift) {
@@ -519,5 +600,5 @@ export const StepperView = ({ onComplete, onProgress, hasMultipleSessions, initi
519
600
  return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1 }, children: _jsx(ReviewScreen, { isSubmitting: submitting, answers: answers, elapsedLabel: elapsedLabel, onConfirm: handleConfirm, onGoBack: handleGoBack, questions: sessionRequest.questions, sessionId: sessionId, elaborateMarks: elaborateMarks }) }));
520
601
  }
521
602
  // Show question display (default)
522
- return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2 }, children: _jsx(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, onToggleOption: handleToggleOption, multiSelect: currentQuestion.multiSelect, questions: sessionRequest.questions, selectedOption: currentAnswer?.selectedOption, answers: answers, focusContext: focusContext, onFocusContextChange: setFocusContext, focusedOptionIndex: focusedOptionIndex, onFocusedOptionIndexChange: setFocusedOptionIndex, workingDirectory: sessionRequest.workingDirectory, onRecommendedDetected: setHasRecommendedOptions, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, elaborateMarks: elaborateMarks, onElaborateSelect: handleElaborateSelect, elaborateText: elaborateMarks.get(currentQuestionIndex) || "", onElaborateTextChange: handleElaborateTextChange, onSelectIndex: (idx) => setCurrentQuestionIndex(Math.max(0, Math.min(idx, sessionRequest.questions.length - 1))), showSessionSwitching: !showReview && !showRejectionConfirm }) }));
603
+ return (_jsx("box", { style: { flexDirection: "column", paddingLeft: 2, paddingRight: 2 }, children: _jsx(QuestionDisplay, { currentQuestion: currentQuestion, currentQuestionIndex: currentQuestionIndex, customAnswer: currentAnswer?.customText, elapsedLabel: elapsedLabel, onAdvanceToNext: handleAdvanceToNext, onChangeCustomAnswer: handleChangeCustomAnswer, onSelectOption: handleSelectOption, questions: sessionRequest.questions, onToggleOption: handleToggleOption, multiSelect: currentQuestionMultiSelect, showMultiToggleHint: !currentQuestion.multiSelect, isForceMultiActive: forceMultiByQuestion.has(currentQuestionIndex), selectedOption: currentAnswer?.selectedOption, answers: answers, focusContext: focusContext, onFocusContextChange: setFocusContext, focusedOptionIndex: focusedOptionIndex, onFocusedOptionIndexChange: setFocusedOptionIndex, workingDirectory: sessionRequest.workingDirectory, onRecommendedDetected: setHasRecommendedOptions, hasRecommendedOptions: hasRecommendedOptions, hasAnyRecommendedInSession: hasAnyRecommendedInSession, elaborateMarks: elaborateMarks, onElaborateSelect: handleElaborateSelect, elaborateText: elaborateMarks.get(currentQuestionIndex) || "", onElaborateTextChange: handleElaborateTextChange, onSelectIndex: (idx) => setCurrentQuestionIndex(Math.max(0, Math.min(idx, sessionRequest.questions.length - 1))), showSessionSwitching: !showReview && !showRejectionConfirm }) }));
523
604
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "3.2.11",
3
+ "version": "3.3.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "bin/auq"