@molecule/app-ide-react 1.15.0 → 1.17.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 (44) hide show
  1. package/README.md +193 -7
  2. package/dist/components/ChatPanel.d.ts +10 -1
  3. package/dist/components/ChatPanel.d.ts.map +1 -1
  4. package/dist/components/ChatPanel.js +136 -14
  5. package/dist/components/ChatPanel.js.map +1 -1
  6. package/dist/components/MarkdownContent.d.ts.map +1 -1
  7. package/dist/components/MarkdownContent.js +27 -7
  8. package/dist/components/MarkdownContent.js.map +1 -1
  9. package/dist/components/TestStatusBar.d.ts +56 -0
  10. package/dist/components/TestStatusBar.d.ts.map +1 -0
  11. package/dist/components/TestStatusBar.js +105 -0
  12. package/dist/components/TestStatusBar.js.map +1 -0
  13. package/dist/components/TestsCard.d.ts +15 -2
  14. package/dist/components/TestsCard.d.ts.map +1 -1
  15. package/dist/components/TestsCard.js +42 -9
  16. package/dist/components/TestsCard.js.map +1 -1
  17. package/dist/components/ToolCallCard.d.ts +7 -1
  18. package/dist/components/ToolCallCard.d.ts.map +1 -1
  19. package/dist/components/ToolCallCard.js +516 -161
  20. package/dist/components/ToolCallCard.js.map +1 -1
  21. package/dist/components/tests-bar-utilities.d.ts +78 -103
  22. package/dist/components/tests-bar-utilities.d.ts.map +1 -1
  23. package/dist/components/tests-bar-utilities.js +130 -201
  24. package/dist/components/tests-bar-utilities.js.map +1 -1
  25. package/dist/components/tests-card-utilities.d.ts +78 -2
  26. package/dist/components/tests-card-utilities.d.ts.map +1 -1
  27. package/dist/components/tests-card-utilities.js +134 -6
  28. package/dist/components/tests-card-utilities.js.map +1 -1
  29. package/dist/components/tool-call-utilities.d.ts +81 -7
  30. package/dist/components/tool-call-utilities.d.ts.map +1 -1
  31. package/dist/components/tool-call-utilities.js +146 -6
  32. package/dist/components/tool-call-utilities.js.map +1 -1
  33. package/dist/hooks/useTestsBarVisible.d.ts +16 -0
  34. package/dist/hooks/useTestsBarVisible.d.ts.map +1 -0
  35. package/dist/hooks/useTestsBarVisible.js +39 -0
  36. package/dist/hooks/useTestsBarVisible.js.map +1 -0
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +6 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/types.d.ts +60 -3
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { Tooltip } from '@molecule/app-ui-react/components/Tooltip.js';
11
11
  import { timelineSortKey } from '../chatTimelineOrdering.js';
12
12
  import { getCustomEventCardFactory } from '../customEventCards.js';
13
13
  import { useChatTimestampsVisible, useMinuteNow } from '../hooks/useChatTimestampsVisible.js';
14
+ import { useTestsBarVisible } from '../hooks/useTestsBarVisible.js';
14
15
  import { useCoarsePointer, useNarrowViewport } from '../hooks/useViewport.js';
15
16
  import { activityFromEvent } from './activity-utilities.js';
16
17
  import { ActivityCard } from './ActivityCard.js';
@@ -44,8 +45,10 @@ import { SettingsCard } from './SettingsCard.js';
44
45
  import { ShareModal } from './ShareModal.js';
45
46
  import { SkillsCard } from './SkillsCard.js';
46
47
  import { StreamingIndicator } from './StreamingIndicator.js';
47
- import { applyTestRunEvent, buildTestFixMessage, EMPTY_RUN_STATE, failRun, parseTestCommand, } from './tests-card-utilities.js';
48
+ import { getTestsBarVisible, parseTestsBarArg, setTestsBarVisible, shouldShowTestsBar, } from './tests-bar-utilities.js';
49
+ import { applyTestRunEvent, buildTestFixMessage, EMPTY_RUN_STATE, failRun, markSkipUnavailable, parseTestCommand, requestSkip, } from './tests-card-utilities.js';
48
50
  import { TestsCard } from './TestsCard.js';
51
+ import { TestStatusBar } from './TestStatusBar.js';
49
52
  import { TipCard } from './TipCard.js';
50
53
  import { ToolCallCard } from './ToolCallCard.js';
51
54
  import { UserAvatar } from './UserAvatar.js';
@@ -973,7 +976,7 @@ AuthorNameButton.displayName = 'AuthorNameButton';
973
976
  * @returns The rendered message item.
974
977
  */
975
978
  const MessageItem = memo(function MessageItem(props) {
976
- const { msg, sendMessage, handleAskUserResponse, isLoading, streamingStatus, onNavigatePreview, undoneTcIds, handleUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, handleFileRevert, setInputAndCursorEnd, setModelPicker, chatMode, userAvatar, onProfileClick, currentUserId, discovery, buildUpgradeCta, agentName, canEdit, showTimestamp, } = props;
979
+ const { msg, sendMessage, handleAskUserResponse, isLoading, streamingStatus, onNavigatePreview, undoneTcIds, handleUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, handleFileRevert, setInputAndCursorEnd, setModelPicker, chatMode, userAvatar, onProfileClick, currentUserId, discovery, buildUpgradeCta, agentName, canEdit, onSkipToolCall, skipToolCallReason, showTimestamp, } = props;
977
980
  const cm = getClassMap();
978
981
  const themeMode = useThemeMode();
979
982
  const isLight = themeMode === 'light';
@@ -1176,7 +1179,7 @@ const MessageItem = memo(function MessageItem(props) {
1176
1179
  const tc = msg.toolCalls?.find((c) => c.id === block.id);
1177
1180
  if (!tc)
1178
1181
  return null;
1179
- return (_jsx("div", { style: { marginTop: '4px' }, children: _jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse }) }, tc.id));
1182
+ return (_jsx("div", { style: { marginTop: '4px' }, children: _jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse, onSkip: onSkipToolCall, skipDisabledReason: skipToolCallReason ?? null }) }, tc.id));
1180
1183
  })) : msg.content ? (_jsx(MarkdownContent, { text: msg.content, isStreaming: msg.isStreaming, statusLabel: msg.isStreaming ? streamingStatus : undefined, statusStartedAt: typeof msg.timestamp === 'number' ? msg.timestamp : undefined, onNavigatePreview: onNavigatePreview, hideStreamingIndicator: true })) : null, msg.toolCalls &&
1181
1184
  msg.blocks &&
1182
1185
  msg.blocks.length > 0 &&
@@ -1187,10 +1190,10 @@ const MessageItem = memo(function MessageItem(props) {
1187
1190
  tc.output.status === 'awaiting_response' &&
1188
1191
  !msg.blocks.some((b) => b.type === 'tool_call' &&
1189
1192
  b.id === tc.id))
1190
- .map((tc) => (_jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse }, `orphan-ask-${tc.id}`))), msg.toolCalls &&
1193
+ .map((tc) => (_jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse, onSkip: onSkipToolCall, skipDisabledReason: skipToolCallReason ?? null }, `orphan-ask-${tc.id}`))), msg.toolCalls &&
1191
1194
  msg.toolCalls.length > 0 &&
1192
1195
  (!msg.blocks || msg.blocks.length === 0) &&
1193
- msg.toolCalls.map((tc) => (_jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse }, tc.id))), msg.aborted && (_jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { display: 'block', marginTop: 4, fontStyle: 'italic' }, children: t('ide.chat.responseStopped', undefined, {
1196
+ msg.toolCalls.map((tc) => (_jsx(ToolCallCard, { id: tc.id, name: tc.name, input: tc.input, output: tc.output, status: tc.status, fileDiff: tc.fileDiff, isUndone: undoneTcIds.has(tc.id), onUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: canEdit === false ? undefined : handleFileRevert, onAskUserResponse: canEdit === false ? undefined : handleAskUserResponse, onSkip: onSkipToolCall, skipDisabledReason: skipToolCallReason ?? null }, tc.id))), msg.aborted && (_jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { display: 'block', marginTop: 4, fontStyle: 'italic' }, children: t('ide.chat.responseStopped', undefined, {
1194
1197
  defaultValue: 'Response stopped',
1195
1198
  }) })), msg.loopLimitReached &&
1196
1199
  !msg.isStreaming &&
@@ -1281,7 +1284,7 @@ const MessageItem = memo(function MessageItem(props) {
1281
1284
  * @param props - Component props (see {@link MessageItemProps}).
1282
1285
  * @returns The rendered chat inner component.
1283
1286
  */
1284
- function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent, isPro, isAnonymous, canEdit, canShare, buildUpgradeCta, buildHelpUpgradeSection, activeFile, openTabs, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onFileChange, onFileDeleted, onCommit, onConversationId, onActivityClick, onRenderError, onProfileClick, currentUserId, onReadyToBuild, awaitingSandboxBoot, onClientAction, onTurnComplete, onLoadingChange, onNavigatePreview, onRegisterPushHandler, onRegisterHistoryReconcile, autoSubmitSignal, openSettingsSignal, onManageCustomModels, modelSelectionSignal, openReportSignal, openShareSignal, initialInputValue, pendingMessage, pendingMessageKey, pendingMessageSuppressUser, pendingMessageUserInitiated, userEditedFile, userEditedFileKey, gitStatusTick: externalGitStatusTick, discovery, userAvatar, agentName = DEFAULT_AGENT_NAME, productName = DEFAULT_PRODUCT_NAME, version, extraCommands, listTests, runTests, canRunTests, testsAvailable,
1287
+ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent, isPro, isAnonymous, canEdit, canShare, buildUpgradeCta, buildHelpUpgradeSection, activeFile, openTabs, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onFileChange, onFileDeleted, onCommit, onConversationId, onActivityClick, onRenderError, onProfileClick, currentUserId, onReadyToBuild, awaitingSandboxBoot, onClientAction, onTurnComplete, onLoadingChange, onNavigatePreview, onRegisterPushHandler, onRegisterHistoryReconcile, autoSubmitSignal, openSettingsSignal, onManageCustomModels, modelSelectionSignal, openReportSignal, openShareSignal, initialInputValue, pendingMessage, pendingMessageKey, pendingMessageSuppressUser, pendingMessageUserInitiated, userEditedFile, userEditedFileKey, gitStatusTick: externalGitStatusTick, discovery, userAvatar, agentName = DEFAULT_AGENT_NAME, productName = DEFAULT_PRODUCT_NAME, version, extraCommands, listTests, runTests, canRunTests, testsAvailable, skipToolCall,
1285
1288
  // feedbackUrl: prop kept for back-compat (callers still pass it), but no longer
1286
1289
  // consumed here — its only use was the command-menu footer link removed in P3-21.
1287
1290
  }) {
@@ -1656,6 +1659,8 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
1656
1659
  const [commitState, setCommitState] = useState(null);
1657
1660
  const [pendingFiles, setPendingFiles] = useState(null);
1658
1661
  const [commitBarExpanded, setCommitBarExpanded] = useState(false);
1662
+ // The tests strip sits above the commit bar and starts collapsed, like it.
1663
+ const [testsBarExpanded, setTestsBarExpanded] = useState(false);
1659
1664
  const [commitCards, setCommitCards] = useState([]);
1660
1665
  /** Number of timeline items rendered in the DOM. Increases when user clicks "Show earlier". */
1661
1666
  const [maxVisibleItems, setMaxVisibleItems] = useState(60);
@@ -2600,6 +2605,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2600
2605
  // The minute clock re-plans which items show a time (see planChatTimestamps),
2601
2606
  // so labels advance and runs of identical labels regroup as time passes.
2602
2607
  const timestampsVisible = useChatTimestampsVisible();
2608
+ const testsBarVisible = useTestsBarVisible();
2603
2609
  const minuteNow = useMinuteNow();
2604
2610
  const applyTimestampsVisible = useCallback((visible) => {
2605
2611
  setChatTimestampsVisible(visible);
@@ -2702,7 +2708,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2702
2708
  durationMs: null,
2703
2709
  }));
2704
2710
  try {
2705
- testsRunHandleRef.current = runTests(selection, (event) => {
2711
+ const handle = runTests(selection, (event) => {
2706
2712
  if (!testsMountedRef.current)
2707
2713
  return;
2708
2714
  setTestsRun((prev) => applyTestRunEvent(prev, event));
@@ -2713,6 +2719,12 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2713
2719
  void refreshTests();
2714
2720
  }
2715
2721
  });
2722
+ testsRunHandleRef.current = handle;
2723
+ // Whether a Skip control belongs on screen is decided HERE, by whether
2724
+ // this host's handle serves one — not by the card guessing, and not by
2725
+ // a button that would do nothing when clicked.
2726
+ const skips = typeof handle.skipCurrent === 'function';
2727
+ setTestsRun((prev) => ({ ...prev, skipPending: false, skipUnavailable: !skips }));
2716
2728
  }
2717
2729
  catch (error) {
2718
2730
  logger.warn('Failed to start a test run', { error });
@@ -2741,8 +2753,64 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2741
2753
  const cancelTestsRun = useCallback(() => {
2742
2754
  testsRunHandleRef.current?.cancel();
2743
2755
  testsRunHandleRef.current = null;
2744
- setTestsRun((prev) => ({ ...prev, running: false, currentId: null, outcome: 'cancelled' }));
2756
+ setTestsRun((prev) => ({
2757
+ ...prev,
2758
+ running: false,
2759
+ currentId: null,
2760
+ cases: {},
2761
+ skipPending: false,
2762
+ skipUnavailable: true,
2763
+ outcome: 'cancelled',
2764
+ }));
2745
2765
  }, []);
2766
+ // Skip the executor's in-flight tool call WITHOUT ending the turn — the
2767
+ // model is told the command did not run and carries on. The card owns its own
2768
+ // pressed state, so this just forwards the host's answer: `false` means the
2769
+ // call had already finished (a race the person did not cause), which the card
2770
+ // answers by returning the button to rest rather than reporting anything.
2771
+ const handleSkipToolCall = useCallback(async (toolCallId) => {
2772
+ if (canEdit === false || !skipToolCall)
2773
+ return false;
2774
+ try {
2775
+ return await skipToolCall(toolCallId);
2776
+ }
2777
+ catch (error) {
2778
+ logger.warn('Failed to skip the running tool call', { error });
2779
+ return false;
2780
+ }
2781
+ }, [canEdit, skipToolCall]);
2782
+ // Skip the command the run is ON, leaving the run itself alive. The state
2783
+ // only records that the request went out — the run's own `result` for that
2784
+ // file is what moves the row, so a skip can never paint itself as a pass. A
2785
+ // host answering "there was nothing to skip" is a race the person did not
2786
+ // cause: the control goes away, no error line.
2787
+ const skipCurrentTest = useCallback(() => {
2788
+ if (canEdit === false || canRunTests === false)
2789
+ return;
2790
+ const skip = testsRunHandleRef.current?.skipCurrent;
2791
+ if (!skip) {
2792
+ setTestsRun((prev) => markSkipUnavailable(prev));
2793
+ return;
2794
+ }
2795
+ setTestsRun((prev) => requestSkip(prev));
2796
+ const unavailable = () => {
2797
+ if (testsMountedRef.current)
2798
+ setTestsRun((prev) => markSkipUnavailable(prev));
2799
+ };
2800
+ try {
2801
+ const outcome = skip.call(testsRunHandleRef.current);
2802
+ if (outcome) {
2803
+ void outcome.then((accepted) => {
2804
+ if (accepted === false)
2805
+ unavailable();
2806
+ }, unavailable);
2807
+ }
2808
+ }
2809
+ catch (error) {
2810
+ logger.warn('Failed to skip the running test', { error });
2811
+ unavailable();
2812
+ }
2813
+ }, [canEdit, canRunTests]);
2746
2814
  /** Open the tests browser, re-listing so a spec just written shows up. */
2747
2815
  const openTestsBrowser = useCallback((query = '', runAll = false) => {
2748
2816
  openPanelOverlay('tests', query);
@@ -3169,6 +3237,35 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
3169
3237
  const id = setInterval(() => dispatchAutoCommit({ type: 'tick' }), 1000);
3170
3238
  return () => clearInterval(id);
3171
3239
  }, [autoCommitArmed, autoCommitHeld]);
3240
+ // The tests bar hides itself on a project with no tests, so it has to LEARN
3241
+ // whether any exist without the person opening the browser first — otherwise
3242
+ // "shown by default" would mean "shown after you ask for it once", which is
3243
+ // not a default at all.
3244
+ //
3245
+ // Discovery runs when the environment comes up, and again when a turn ends
3246
+ // while we still know of none: that is the turn that wrote the project's
3247
+ // first spec. A project that already has tests never pays for the second
3248
+ // probe, because the effect returns the moment the list is non-empty.
3249
+ const turnInFlight = isLoading || isRemoteStreaming;
3250
+ useEffect(() => {
3251
+ if (!testsBarVisible || !listTests)
3252
+ return;
3253
+ if (testsAvailable !== true)
3254
+ return;
3255
+ if (turnInFlight || testsRun.running)
3256
+ return;
3257
+ if (testsList.length > 0)
3258
+ return;
3259
+ void refreshTests();
3260
+ }, [
3261
+ testsBarVisible,
3262
+ listTests,
3263
+ testsAvailable,
3264
+ turnInFlight,
3265
+ testsRun.running,
3266
+ testsList.length,
3267
+ refreshTests,
3268
+ ]);
3172
3269
  // Wrap onFileRevert so undo/redo also refreshes git status
3173
3270
  const handleFileRevert = useCallback(async (path, content) => {
3174
3271
  await onFileRevert?.(path, content);
@@ -4474,6 +4571,22 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
4474
4571
  setSoundsPicker({ selectedIdx: -1 });
4475
4572
  return;
4476
4573
  }
4574
+ // `/test on | off | toggle` sets whether the TESTS STRIP is shown — a
4575
+ // per-device display preference, the same shape as /timestamps. Bare
4576
+ // `/test` is left alone: it opens the tests browser, which is a different
4577
+ // thing and is what the command menu offers.
4578
+ const testsBarMatch = /^\/test(?:s)?\s+(.+)$/i.exec(trimmed);
4579
+ if (testsBarMatch) {
4580
+ const next = parseTestsBarArg(testsBarMatch[1] ?? '', getTestsBarVisible());
4581
+ if (next !== null) {
4582
+ setInputValue('');
4583
+ setTestsBarVisible(next);
4584
+ addSystemCard(next
4585
+ ? t('ide.chat.testsBarShown', undefined, { defaultValue: 'Tests bar shown.' })
4586
+ : t('ide.chat.testsBarHidden', undefined, { defaultValue: 'Tests bar hidden.' }), { clientOnly: true });
4587
+ return;
4588
+ }
4589
+ }
4477
4590
  // Handle /timestamps [on | off] locally — a per-device display preference.
4478
4591
  const timestampsCommand = parseTimestampsCommand(trimmed);
4479
4592
  if (timestampsCommand) {
@@ -5842,7 +5955,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
5842
5955
  return (_jsx(ScriptsCard, { projectId: projectId, initialQuery: item.card.query ?? '', isLight: isLight, agentName: agentName }, item.card.id));
5843
5956
  }
5844
5957
  if (item.card.variant === 'tests') {
5845
- return (_jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, initialQuery: item.card.query ?? '', canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onFix: fixTests, fixDisabledReason: canEdit === false
5958
+ return (_jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, initialQuery: item.card.query ?? '', canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onSkipCurrent: skipCurrentTest, onFix: fixTests, fixDisabledReason: canEdit === false
5846
5959
  ? t('ide.tests.viewerCannotRun', undefined, {
5847
5960
  defaultValue: 'Only editors can run this project’s tests.',
5848
5961
  })
@@ -5943,7 +6056,11 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
5943
6056
  // MessageItem builds the identity from the message's own
5944
6057
  // author (teammates included), so the host shows their
5945
6058
  // view-only profile and the viewer's editable own.
5946
- onProfileClick: onProfileClick, currentUserId: currentUserId, showTimestamp: shownTimestampIds.has(msg.id), discovery: discovery, buildUpgradeCta: buildUpgradeCta, agentName: agentName, canEdit: canEdit }, msg.id));
6059
+ onProfileClick: onProfileClick, currentUserId: currentUserId, showTimestamp: shownTimestampIds.has(msg.id), discovery: discovery, buildUpgradeCta: buildUpgradeCta, agentName: agentName, canEdit: canEdit, onSkipToolCall: skipToolCall ? handleSkipToolCall : undefined, skipToolCallReason: canEdit === false
6060
+ ? t('ide.chat.skipToolCallViewer', undefined, {
6061
+ defaultValue: 'Only editors can skip this.',
6062
+ })
6063
+ : null }, msg.id));
5947
6064
  })()) }, item.kind === 'message' ? item.msg.id : item.card.id))), error &&
5948
6065
  !isStaleAnonymousLimit &&
5949
6066
  (errorMeta?.limitType ? (_jsx(ResourceLimitBanner, { message: error, action: buildUpgradeCta?.({
@@ -7088,7 +7205,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
7088
7205
  minHeight: 36,
7089
7206
  }
7090
7207
  : {}),
7091
- }, children: '✕' })] }), _jsxs("div", { style: { overflowY: 'auto', flex: 1 }, children: [panelOverlay === 'settings' && (_jsx(SettingsCard, { settings: computeSettingsList(), onRunCommand: (commandId) => void executeCommand(commandId), onPrefillInput: (input) => setInputAndCursorEnd(`${input} `), isLight: isLight, agentName: agentName, embedded: true })), panelOverlay === 'skills' && (_jsx(SkillsCard, { projectId: projectId, initialQuery: panelOverlayQuery, onLoad: loadSkill, onCreate: createSkill, loadedSkillPaths: loadedSkillPaths, defaultSkillPaths: defaultSkillPaths, onToggleDefault: toggleDefaultSkill, onResetDefault: resetDefaultSkills, defaultsExplicit: defaultSkillsExplicitRef.current, isLight: isLight, embedded: true })), panelOverlay === 'scripts' && (_jsx(ScriptsCard, { projectId: projectId, initialQuery: panelOverlayQuery, isLight: isLight, agentName: agentName, embedded: true })), panelOverlay === 'tests' && (_jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, initialQuery: panelOverlayQuery, canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onFix: fixTests, fixDisabledReason: canEdit === false
7208
+ }, children: '✕' })] }), _jsxs("div", { style: { overflowY: 'auto', flex: 1 }, children: [panelOverlay === 'settings' && (_jsx(SettingsCard, { settings: computeSettingsList(), onRunCommand: (commandId) => void executeCommand(commandId), onPrefillInput: (input) => setInputAndCursorEnd(`${input} `), isLight: isLight, agentName: agentName, embedded: true })), panelOverlay === 'skills' && (_jsx(SkillsCard, { projectId: projectId, initialQuery: panelOverlayQuery, onLoad: loadSkill, onCreate: createSkill, loadedSkillPaths: loadedSkillPaths, defaultSkillPaths: defaultSkillPaths, onToggleDefault: toggleDefaultSkill, onResetDefault: resetDefaultSkills, defaultsExplicit: defaultSkillsExplicitRef.current, isLight: isLight, embedded: true })), panelOverlay === 'scripts' && (_jsx(ScriptsCard, { projectId: projectId, initialQuery: panelOverlayQuery, isLight: isLight, agentName: agentName, embedded: true })), panelOverlay === 'tests' && (_jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, initialQuery: panelOverlayQuery, canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onSkipCurrent: skipCurrentTest, onFix: fixTests, fixDisabledReason: canEdit === false
7092
7209
  ? t('ide.tests.viewerCannotRun', undefined, {
7093
7210
  defaultValue: 'Only editors can run this project’s tests.',
7094
7211
  })
@@ -7289,7 +7406,12 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
7289
7406
  }, onMouseLeave: (e) => {
7290
7407
  e.currentTarget.style.opacity = '0.7';
7291
7408
  e.currentTarget.style.background = 'none';
7292
- }, children: _jsx("svg", { width: "13", height: "13", viewBox: "0 0 12 12", style: { display: 'block' }, children: _jsx("path", { d: "M 2.5,6.5 L 6,3 L 9.5,6.5 M 6,3.5 L 6,10", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }, qm.id))) })] })), canEdit !== false &&
7409
+ }, children: _jsx("svg", { width: "13", height: "13", viewBox: "0 0 12 12", style: { display: 'block' }, children: _jsx("path", { d: "M 2.5,6.5 L 6,3 L 9.5,6.5 M 6,3.5 L 6,10", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }, qm.id))) })] })), testsBarVisible &&
7410
+ shouldShowTestsBar(testsRun, testsList) &&
7411
+ !commandMenu &&
7412
+ !modelPicker &&
7413
+ !effortPicker &&
7414
+ !panelOverlay && (_jsx(TestStatusBar, { run: testsRun, tests: testsList, expanded: testsBarExpanded, onToggle: () => setTestsBarExpanded((v) => !v), onOpen: () => openTestsBrowser(''), onStop: testsRun.running ? cancelTestsRun : undefined })), canEdit !== false &&
7293
7415
  pendingFiles != null &&
7294
7416
  pendingFiles.length > 0 &&
7295
7417
  !commandMenu &&
@@ -7843,7 +7965,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
7843
7965
  * @param props - Component props (see {@link MessageItemProps}).
7844
7966
  * @returns The rendered chat panel element.
7845
7967
  */
7846
- export function ChatPanel({ projectId, endpoint, initialMessage, onInitialMessageSent, activeFile, openTabs, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onFileChange, onFileDeleted, onCommit, onActivityClick, onRenderError, onProfileClick, currentUserId, onReadyToBuild, awaitingSandboxBoot, onClientAction, onTurnComplete, onLoadingChange, onNavigatePreview, onRegisterPushHandler, onRegisterHistoryReconcile, autoSubmitSignal, initialInputValue, hideConversationMenu, renderConversationHeader = true, conversationId: controlledConversationId, chatKey: controlledChatKey, onConversationId: controlledOnConversationId, openShareSignal: controlledShareSignal, openReportSignal: controlledReportSignal, openSettingsSignal: controlledSettingsSignal, onManageCustomModels, gitStatusTick, pendingMessage, pendingMessageKey, pendingMessageSuppressUser, pendingMessageUserInitiated, userEditedFile, userEditedFileKey, isPro, isAnonymous, canEdit = true, canShare, buildUpgradeCta, buildHelpUpgradeSection, userAvatar, agentName, productName, version, extraCommands, feedbackUrl, listTests, runTests, canRunTests, testsAvailable, className, }) {
7968
+ export function ChatPanel({ projectId, endpoint, initialMessage, onInitialMessageSent, activeFile, openTabs, onFileOpen, onFileDoubleClick, onFileDiff, onFileRevert, onFileChange, onFileDeleted, onCommit, onActivityClick, onRenderError, onProfileClick, currentUserId, onReadyToBuild, awaitingSandboxBoot, onClientAction, onTurnComplete, onLoadingChange, onNavigatePreview, onRegisterPushHandler, onRegisterHistoryReconcile, autoSubmitSignal, initialInputValue, hideConversationMenu, renderConversationHeader = true, conversationId: controlledConversationId, chatKey: controlledChatKey, onConversationId: controlledOnConversationId, openShareSignal: controlledShareSignal, openReportSignal: controlledReportSignal, openSettingsSignal: controlledSettingsSignal, onManageCustomModels, gitStatusTick, pendingMessage, pendingMessageKey, pendingMessageSuppressUser, pendingMessageUserInitiated, userEditedFile, userEditedFileKey, isPro, isAnonymous, canEdit = true, canShare, buildUpgradeCta, buildHelpUpgradeSection, userAvatar, agentName, productName, version, extraCommands, feedbackUrl, listTests, runTests, canRunTests, testsAvailable, skipToolCall, className, }) {
7847
7969
  const cm = getClassMap();
7848
7970
  // Share management may be gated ABOVE canEdit by the host (see
7849
7971
  // ChatPanelProps.canShare) — gates the built-in header share button here and
@@ -8056,7 +8178,7 @@ export function ChatPanel({ projectId, endpoint, initialMessage, onInitialMessag
8056
8178
  textOverflow: 'ellipsis',
8057
8179
  whiteSpace: 'nowrap',
8058
8180
  width: '100%',
8059
- }, children: conv.preview ?? 'New conversation' }), _jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { opacity: 0.55 }, children: relativeTime(conv.updatedAt) })] }, conv.id)))] }))] })), _jsx(ChatInner, { projectId: projectId, endpoint: chatEndpoint, initialMessage: initialMessage, onInitialMessageSent: onInitialMessageSent, isPro: isPro, isAnonymous: isAnonymous, canEdit: canEdit, canShare: shareAllowed, buildUpgradeCta: buildUpgradeCta, buildHelpUpgradeSection: buildHelpUpgradeSection, activeFile: activeFile, openTabs: openTabs, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: onFileRevert, onFileChange: onFileChange, onFileDeleted: onFileDeleted, onCommit: onCommit, onConversationId: reportConversationId, onActivityClick: onActivityClick, onRenderError: onRenderError, onProfileClick: onProfileClick, currentUserId: currentUserId, onReadyToBuild: onReadyToBuild, awaitingSandboxBoot: awaitingSandboxBoot, onClientAction: onClientAction, onTurnComplete: onTurnComplete, onLoadingChange: onLoadingChange, onNavigatePreview: onNavigatePreview, onRegisterPushHandler: onRegisterPushHandler, onRegisterHistoryReconcile: onRegisterHistoryReconcile, autoSubmitSignal: autoSubmitSignal, openSettingsSignal: effectiveSettingsSignal, onManageCustomModels: onManageCustomModels, openReportSignal: effectiveReportSignal, openShareSignal: effectiveShareSignal, initialInputValue: initialInputValue, pendingMessage: pendingMessage, pendingMessageKey: pendingMessageKey, pendingMessageSuppressUser: pendingMessageSuppressUser, pendingMessageUserInitiated: pendingMessageUserInitiated, userEditedFile: userEditedFile, userEditedFileKey: userEditedFileKey, gitStatusTick: gitStatusTick, discovery: hideConversationMenu, userAvatar: userAvatar, agentName: agentName, productName: productName, version: version, extraCommands: extraCommands, feedbackUrl: feedbackUrl, listTests: listTests, runTests: runTests, canRunTests: canRunTests, testsAvailable: testsAvailable }, chatKey)] }));
8181
+ }, children: conv.preview ?? 'New conversation' }), _jsx("span", { className: cm.cn(cm.textMuted, cm.textSize('xs')), style: { opacity: 0.55 }, children: relativeTime(conv.updatedAt) })] }, conv.id)))] }))] })), _jsx(ChatInner, { projectId: projectId, endpoint: chatEndpoint, initialMessage: initialMessage, onInitialMessageSent: onInitialMessageSent, isPro: isPro, isAnonymous: isAnonymous, canEdit: canEdit, canShare: shareAllowed, buildUpgradeCta: buildUpgradeCta, buildHelpUpgradeSection: buildHelpUpgradeSection, activeFile: activeFile, openTabs: openTabs, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, onFileRevert: onFileRevert, onFileChange: onFileChange, onFileDeleted: onFileDeleted, onCommit: onCommit, onConversationId: reportConversationId, onActivityClick: onActivityClick, onRenderError: onRenderError, onProfileClick: onProfileClick, currentUserId: currentUserId, onReadyToBuild: onReadyToBuild, awaitingSandboxBoot: awaitingSandboxBoot, onClientAction: onClientAction, onTurnComplete: onTurnComplete, onLoadingChange: onLoadingChange, onNavigatePreview: onNavigatePreview, onRegisterPushHandler: onRegisterPushHandler, onRegisterHistoryReconcile: onRegisterHistoryReconcile, autoSubmitSignal: autoSubmitSignal, openSettingsSignal: effectiveSettingsSignal, onManageCustomModels: onManageCustomModels, openReportSignal: effectiveReportSignal, openShareSignal: effectiveShareSignal, initialInputValue: initialInputValue, pendingMessage: pendingMessage, pendingMessageKey: pendingMessageKey, pendingMessageSuppressUser: pendingMessageSuppressUser, pendingMessageUserInitiated: pendingMessageUserInitiated, userEditedFile: userEditedFile, userEditedFileKey: userEditedFileKey, gitStatusTick: gitStatusTick, discovery: hideConversationMenu, userAvatar: userAvatar, agentName: agentName, productName: productName, version: version, extraCommands: extraCommands, feedbackUrl: feedbackUrl, listTests: listTests, runTests: runTests, canRunTests: canRunTests, testsAvailable: testsAvailable, skipToolCall: skipToolCall }, chatKey)] }));
8060
8182
  }
8061
8183
  ChatPanel.displayName = 'ChatPanel';
8062
8184
  //# sourceMappingURL=ChatPanel.js.map