@molecule/app-ide-react 3.0.4 → 3.2.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.
@@ -51,7 +51,7 @@ import { ShareModal } from './ShareModal.js';
51
51
  import { SkillsCard } from './SkillsCard.js';
52
52
  import { StreamingIndicator } from './StreamingIndicator.js';
53
53
  import { getTestsBarVisible, parseTestsBarArg, setTestsBarVisible, shouldShowTestsBar, } from './tests-bar-utilities.js';
54
- import { applyTestRunEvent, buildTestFixMessage, EMPTY_RUN_STATE, failRun, markSkipUnavailable, parseTestCommand, requestSkip, } from './tests-card-utilities.js';
54
+ import { applyTestRunEvent, buildTestFixMessage, EMPTY_RUN_STATE, failRun, markSkipUnavailable, parseTestCommand, requestSkip, TestRunProjection, } from './tests-card-utilities.js';
55
55
  import { TestsCard } from './TestsCard.js';
56
56
  import { TestStatusBar } from './TestStatusBar.js';
57
57
  import { TipCard } from './TipCard.js';
@@ -650,7 +650,7 @@ function CollapsibleUserMessage({ content, isLight, }) {
650
650
  * @param props - Component props (see {@link MessageItemProps}).
651
651
  * @returns The rendered verification badge element.
652
652
  */
653
- function VerificationBadge({ status, output, categories, }) {
653
+ function VerificationBadge({ status, output, categories, renderCheckUnavailable, }) {
654
654
  const cm = getClassMap();
655
655
  const [expanded, setExpanded] = useState(false);
656
656
  if (status === 'ok') {
@@ -660,7 +660,9 @@ function VerificationBadge({ status, output, categories, }) {
660
660
  gap: '6px',
661
661
  padding: '3px 0',
662
662
  fontSize: '13px',
663
- }, className: cm.textMuted, children: [_jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", style: { flexShrink: 0, alignSelf: 'center', position: 'relative', top: '-1px' }, children: _jsx("path", { d: "M2 5.5 L4.2 7.8 L8 3", fill: "none", stroke: "#3fb950", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsx("span", { children: t('ide.chat.verificationPassed', undefined, { defaultValue: 'Checks passed' }) })] }));
663
+ }, className: cm.textMuted, children: [_jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", style: { flexShrink: 0, alignSelf: 'center', position: 'relative', top: '-1px' }, children: _jsx("path", { d: "M2 5.5 L4.2 7.8 L8 3", fill: "none", stroke: "#3fb950", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }), _jsxs("span", { children: [t('ide.chat.verificationPassed', undefined, { defaultValue: 'Checks passed' }), renderCheckUnavailable && (_jsxs("span", { "data-mol-id": "render-check-unavailable", className: cm.textWarning, children: [' · ', t('ide.chat.renderCheckUnavailable', undefined, {
664
+ defaultValue: 'Render check unavailable',
665
+ })] }))] })] }));
664
666
  }
665
667
  // Count errors from the output text
666
668
  const cats = categories ?? [];
@@ -1212,7 +1214,7 @@ const MessageItem = memo(function MessageItem(props) {
1212
1214
  }
1213
1215
  if (blockType === 'verification') {
1214
1216
  const vBlock = block;
1215
- return (_jsx(VerificationBadge, { status: vBlock.status, output: vBlock.output, workspaces: vBlock.workspaces, categories: vBlock.categories }, `verification-${bi}`));
1217
+ return (_jsx(VerificationBadge, { status: vBlock.status, output: vBlock.output, workspaces: vBlock.workspaces, categories: vBlock.categories, renderCheckUnavailable: vBlock.renderCheckUnavailable }, `verification-${bi}`));
1216
1218
  }
1217
1219
  if (blockType === 'resource_limit') {
1218
1220
  const rlBlock = block;
@@ -1321,12 +1323,24 @@ const MessageItem = memo(function MessageItem(props) {
1321
1323
  }, children: String.fromCharCode(65 + i) }), _jsx("span", { children: opt.label })] }, i)))] }));
1322
1324
  })()] })) }));
1323
1325
  });
1326
+ /** Only COMPLETED own ids are bounded; active own ids are pinned separately. */
1327
+ const COMPLETED_OWN_TEST_RUN_ID_LIMIT = 32;
1328
+ /** Remember a completed own run for its delayed feed copy. */
1329
+ function rememberCompletedOwnTestRunId(ids, runId) {
1330
+ ids.delete(runId);
1331
+ ids.add(runId);
1332
+ if (ids.size <= COMPLETED_OWN_TEST_RUN_ID_LIMIT)
1333
+ return;
1334
+ const oldest = ids.values().next();
1335
+ if (!oldest.done)
1336
+ ids.delete(oldest.value);
1337
+ }
1324
1338
  /**
1325
1339
  * Inner chat component that owns useChat state, message rendering, and input handling.
1326
1340
  * @param props - Component props (see {@link MessageItemProps}).
1327
1341
  * @returns The rendered chat inner component.
1328
1342
  */
1329
- function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent, isPro, isAnonymous, canEdit, canSendTeamMessages = true, canShare, shareLinksAllowed, shareLinksRefreshSignal, shareModalContent, 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,
1343
+ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent, isPro, isAnonymous, canEdit, canSendTeamMessages = true, canShare, shareLinksAllowed, shareLinksRefreshSignal, shareModalContent, 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, watchTestRuns, nativeTests, canRunTests, testsAvailable, skipToolCall,
1330
1344
  // feedbackUrl: prop kept for back-compat (callers still pass it), but no longer
1331
1345
  // consumed here — its only use was the command-menu footer link removed in P3-21.
1332
1346
  }) {
@@ -2753,13 +2767,48 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2753
2767
  const [testsList, setTestsList] = useState([]);
2754
2768
  const [testsStatus, setTestsStatus] = useState('loading');
2755
2769
  const [testsRun, setTestsRun] = useState(EMPTY_RUN_STATE);
2756
- const testsRunHandleRef = useRef(null);
2770
+ // Each callback closes over its own folded run. Only this invocation owns
2771
+ // controls; stopped/completed invocations can still report trailing verdicts.
2772
+ const ownTestRunRef = useRef(null);
2757
2773
  // Live "is a run in flight?" for the dispatchers, so `startTestsRun` keeps a
2758
2774
  // stable identity — the command callbacks that hold it carry hand-written
2759
2775
  // dependency lists, and a value that changes every run transition is one they
2760
2776
  // would eventually capture stale.
2761
2777
  const testsRunningRef = useRef(false);
2762
2778
  testsRunningRef.current = testsRun.running;
2779
+ // One incremental projection: per-file verdicts plus bounded live/latest run state. Server ids
2780
+ // dedupe the direct stream and feed echo; legacy id-less custom hosts use the bounded id guard.
2781
+ const testRunProjectionRef = useRef(null);
2782
+ if (!testRunProjectionRef.current)
2783
+ testRunProjectionRef.current = new TestRunProjection();
2784
+ const activeOwnTestRunIdsRef = useRef(new Set());
2785
+ const completedOwnTestRunIdsRef = useRef(new Set());
2786
+ const projectTestsRun = useCallback(() => {
2787
+ const invocation = ownTestRunRef.current;
2788
+ if (invocation && invocation.state.runId == null) {
2789
+ setTestsRun(invocation.state);
2790
+ return;
2791
+ }
2792
+ const activeOwnRunId = invocation?.state.runId ?? null;
2793
+ const next = testRunProjectionRef.current.show(activeOwnRunId);
2794
+ setTestsRun(invocation && next.runId === invocation.state.runId
2795
+ ? {
2796
+ ...next,
2797
+ skipPending: invocation.state.skipPending,
2798
+ skipUnavailable: invocation.state.skipUnavailable,
2799
+ }
2800
+ : next);
2801
+ }, []);
2802
+ const recordOwnTestRunEvent = useCallback((event) => {
2803
+ const legacy = !event.eventId;
2804
+ if (legacy) {
2805
+ if (ownTestRunRef.current?.state.runId === event.runId)
2806
+ activeOwnTestRunIdsRef.current.add(event.runId);
2807
+ else
2808
+ rememberCompletedOwnTestRunId(completedOwnTestRunIdsRef.current, event.runId);
2809
+ }
2810
+ testRunProjectionRef.current.apply(event, ownTestRunRef.current?.state.runId ?? null);
2811
+ }, []);
2763
2812
  // Whether a TURN is in flight — a fix is a real message, so it waits its turn.
2764
2813
  const testsBusyRef = useRef(false);
2765
2814
  // Live for the async callbacks, so a list resolving after an unmount, or a
@@ -2771,7 +2820,9 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2771
2820
  testsMountedRef.current = true;
2772
2821
  return () => {
2773
2822
  testsMountedRef.current = false;
2774
- testsRunHandleRef.current?.cancel();
2823
+ const own = ownTestRunRef.current;
2824
+ ownTestRunRef.current = null;
2825
+ own?.handle?.cancel();
2775
2826
  };
2776
2827
  }, []);
2777
2828
  const refreshTests = useCallback(async () => {
@@ -2807,52 +2858,121 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2807
2858
  return;
2808
2859
  if (canRunTests === false || canEdit === false)
2809
2860
  return;
2810
- if (testsAvailable === false || testsRunningRef.current)
2861
+ if (testsAvailable === false || testsRunningRef.current || ownTestRunRef.current)
2811
2862
  return;
2812
2863
  if (selection.ids?.length === 0)
2813
2864
  return;
2814
2865
  // Seed a running state immediately: the host's `start` event may be a
2815
2866
  // round trip away, and a Run button that appears to do nothing is the
2816
2867
  // failure this feature exists to remove.
2817
- setTestsRun((prev) => ({
2818
- ...prev,
2868
+ const initial = {
2869
+ ...EMPTY_RUN_STATE,
2819
2870
  running: true,
2820
- outcome: null,
2821
- error: null,
2822
- output: [],
2823
- currentId: null,
2824
- queued: selection.ids ?? [],
2871
+ queued: [...(selection.ids ?? [])],
2825
2872
  startedAt: Date.now(),
2826
- durationMs: null,
2827
- }));
2873
+ external: false,
2874
+ };
2875
+ const invocation = {
2876
+ state: initial,
2877
+ handle: null,
2878
+ ended: null,
2879
+ };
2880
+ ownTestRunRef.current = invocation;
2881
+ setTestsRun((prev) => {
2882
+ const results = { ...prev.results };
2883
+ for (const id of initial.queued)
2884
+ delete results[id];
2885
+ return { ...initial, results };
2886
+ });
2828
2887
  try {
2829
2888
  const handle = runTests(selection, (event) => {
2830
2889
  if (!testsMountedRef.current)
2831
2890
  return;
2832
- setTestsRun((prev) => applyTestRunEvent(prev, event));
2891
+ const current = ownTestRunRef.current === invocation;
2892
+ const ended = invocation.ended;
2893
+ invocation.state = applyTestRunEvent(invocation.state, event);
2894
+ const runId = invocation.state.runId;
2895
+ // Completion owns cleanup even when no start ever supplied an id. Otherwise one failed
2896
+ // start leaves this invocation occupied and every later Run click returns early.
2833
2897
  if (event.type === 'done') {
2834
- testsRunHandleRef.current = null;
2835
- // A run cannot create a test, but it CAN finish after the agent wrote
2836
- // one — re-list once it settles so the card is current.
2898
+ invocation.ended = event;
2899
+ if (current)
2900
+ ownTestRunRef.current = null;
2837
2901
  void refreshTests();
2838
2902
  }
2903
+ if (runId == null) {
2904
+ if (current)
2905
+ setTestsRun(invocation.state);
2906
+ return;
2907
+ }
2908
+ if (event.type === 'start') {
2909
+ recordOwnTestRunEvent(event);
2910
+ if (ended) {
2911
+ // A start delayed past cancellation is history, never a live own run.
2912
+ invocation.state = applyTestRunEvent(invocation.state, ended);
2913
+ recordOwnTestRunEvent({ ...ended, runId });
2914
+ }
2915
+ }
2916
+ else {
2917
+ recordOwnTestRunEvent({ ...event, runId });
2918
+ }
2919
+ if (event.type === 'done' && !event.eventId) {
2920
+ activeOwnTestRunIdsRef.current.delete(runId);
2921
+ rememberCompletedOwnTestRunId(completedOwnTestRunIdsRef.current, runId);
2922
+ }
2923
+ projectTestsRun();
2839
2924
  });
2840
- testsRunHandleRef.current = handle;
2925
+ // The host is allowed to finish synchronously, before returning its
2926
+ // handle. It no longer owns controls after handing the card onward.
2927
+ if (ownTestRunRef.current !== invocation)
2928
+ return;
2929
+ invocation.handle = handle;
2841
2930
  // Whether a Skip control belongs on screen is decided HERE, by whether
2842
2931
  // this host's handle serves one — not by the card guessing, and not by
2843
2932
  // a button that would do nothing when clicked.
2844
2933
  const skips = typeof handle.skipCurrent === 'function';
2934
+ invocation.state = {
2935
+ ...invocation.state,
2936
+ skipPending: false,
2937
+ skipUnavailable: !skips,
2938
+ };
2845
2939
  setTestsRun((prev) => ({ ...prev, skipPending: false, skipUnavailable: !skips }));
2846
2940
  }
2847
2941
  catch (error) {
2848
2942
  logger.warn('Failed to start a test run', { error });
2849
- setTestsRun((prev) => failRun(prev, error instanceof Error
2943
+ if (ownTestRunRef.current !== invocation)
2944
+ return;
2945
+ ownTestRunRef.current = null;
2946
+ const message = error instanceof Error
2850
2947
  ? error.message
2851
2948
  : t('ide.tests.runError', undefined, {
2852
2949
  defaultValue: 'The test run could not start.',
2853
- })));
2950
+ });
2951
+ const runId = invocation.state.runId;
2952
+ if (runId != null) {
2953
+ const done = { type: 'done', outcome: 'error', error: message };
2954
+ invocation.ended = done;
2955
+ invocation.state = applyTestRunEvent(invocation.state, done);
2956
+ activeOwnTestRunIdsRef.current.delete(runId);
2957
+ recordOwnTestRunEvent({ ...done, runId });
2958
+ projectTestsRun();
2959
+ }
2960
+ else if (testRunProjectionRef.current.show().running) {
2961
+ projectTestsRun();
2962
+ }
2963
+ else {
2964
+ setTestsRun((prev) => failRun(prev, message));
2965
+ }
2854
2966
  }
2855
- }, [runTests, canRunTests, canEdit, testsAvailable, refreshTests]);
2967
+ }, [
2968
+ runTests,
2969
+ canRunTests,
2970
+ canEdit,
2971
+ testsAvailable,
2972
+ refreshTests,
2973
+ projectTestsRun,
2974
+ recordOwnTestRunEvent,
2975
+ ]);
2856
2976
  // Hand the failing tests to the agent as ONE ordinary user message — the same
2857
2977
  // move as the editor’s "Fix with AI" and the auto-fix loop: compose the
2858
2978
  // problem, send it through the normal path, let the turn answer. The overlay
@@ -2869,18 +2989,70 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2869
2989
  sendMessageRef.current(message);
2870
2990
  }, [canEdit]);
2871
2991
  const cancelTestsRun = useCallback(() => {
2872
- testsRunHandleRef.current?.cancel();
2873
- testsRunHandleRef.current = null;
2874
- setTestsRun((prev) => ({
2875
- ...prev,
2876
- running: false,
2877
- currentId: null,
2878
- cases: {},
2879
- skipPending: false,
2880
- skipUnavailable: true,
2881
- outcome: 'cancelled',
2882
- }));
2883
- }, []);
2992
+ // Only this panel's own run can be stopped from here; the controls are not
2993
+ // offered on a run it is only watching.
2994
+ const invocation = ownTestRunRef.current;
2995
+ if (!invocation)
2996
+ return;
2997
+ // Relinquish ownership before cancel(), which may synchronously call back.
2998
+ ownTestRunRef.current = null;
2999
+ const done = { type: 'done', outcome: 'cancelled' };
3000
+ invocation.ended = done;
3001
+ invocation.state = applyTestRunEvent(invocation.state, done);
3002
+ const runId = invocation.state.runId;
3003
+ if (runId != null) {
3004
+ activeOwnTestRunIdsRef.current.delete(runId);
3005
+ recordOwnTestRunEvent({ ...done, runId });
3006
+ projectTestsRun();
3007
+ }
3008
+ else {
3009
+ setTestsRun({ ...invocation.state, skipUnavailable: true });
3010
+ }
3011
+ invocation.handle?.cancel();
3012
+ }, [projectTestsRun, recordOwnTestRunEvent]);
3013
+ // Every run in the project, however it was started, reaches the same bar and
3014
+ // browser: the host replays the runs it still holds, then streams new ones.
3015
+ // The ref keeps the subscription from restarting (and replaying) each time
3016
+ // `refreshTests` changes identity.
3017
+ const refreshTestsRef = useRef(refreshTests);
3018
+ refreshTestsRef.current = refreshTests;
3019
+ useEffect(() => {
3020
+ if (!watchTestRuns)
3021
+ return;
3022
+ let relist = null;
3023
+ let unsubscribe = null;
3024
+ try {
3025
+ unsubscribe = watchTestRuns((event) => {
3026
+ if (!testsMountedRef.current)
3027
+ return;
3028
+ // Only id-less legacy hosts need run-id suppression. Ordered copies dedupe by event id.
3029
+ if (!event.eventId &&
3030
+ (activeOwnTestRunIdsRef.current.has(event.runId) ||
3031
+ completedOwnTestRunIdsRef.current.has(event.runId))) {
3032
+ return;
3033
+ }
3034
+ testRunProjectionRef.current.apply(event, ownTestRunRef.current?.state.runId ?? null);
3035
+ projectTestsRun();
3036
+ if (event.type === 'done') {
3037
+ if (relist)
3038
+ clearTimeout(relist);
3039
+ relist = setTimeout(() => {
3040
+ relist = null;
3041
+ void refreshTestsRef.current();
3042
+ }, 500);
3043
+ }
3044
+ });
3045
+ projectTestsRun();
3046
+ }
3047
+ catch (error) {
3048
+ logger.warn('Failed to watch the project test runs', { error });
3049
+ }
3050
+ return () => {
3051
+ if (relist)
3052
+ clearTimeout(relist);
3053
+ unsubscribe?.();
3054
+ };
3055
+ }, [watchTestRuns, projectTestsRun]);
2884
3056
  // Skip the executor's in-flight tool call WITHOUT ending the turn — the
2885
3057
  // model is told the command did not run and carries on. The card owns its own
2886
3058
  // pressed state, so this just forwards the host's answer: `false` means the
@@ -2905,23 +3077,33 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
2905
3077
  const skipCurrentTest = useCallback(() => {
2906
3078
  if (canEdit === false || canRunTests === false)
2907
3079
  return;
2908
- const skip = testsRunHandleRef.current?.skipCurrent;
3080
+ const invocation = ownTestRunRef.current;
3081
+ if (!invocation)
3082
+ return;
3083
+ const skip = invocation.handle?.skipCurrent;
2909
3084
  if (!skip) {
3085
+ invocation.state = markSkipUnavailable(invocation.state);
2910
3086
  setTestsRun((prev) => markSkipUnavailable(prev));
2911
3087
  return;
2912
3088
  }
3089
+ invocation.state = requestSkip(invocation.state);
2913
3090
  setTestsRun((prev) => requestSkip(prev));
2914
3091
  const unavailable = () => {
2915
- if (testsMountedRef.current)
3092
+ if (testsMountedRef.current && ownTestRunRef.current === invocation) {
3093
+ invocation.state = markSkipUnavailable(invocation.state);
2916
3094
  setTestsRun((prev) => markSkipUnavailable(prev));
3095
+ }
2917
3096
  };
2918
3097
  try {
2919
- const outcome = skip.call(testsRunHandleRef.current);
3098
+ const outcome = skip.call(invocation.handle);
2920
3099
  if (outcome) {
2921
3100
  void outcome.then((accepted) => {
2922
3101
  if (accepted === false)
2923
3102
  unavailable();
2924
- }, unavailable);
3103
+ }, (error) => {
3104
+ logger.warn('Failed to skip the running test', { error });
3105
+ unavailable();
3106
+ });
2925
3107
  }
2926
3108
  }
2927
3109
  catch (error) {
@@ -6120,7 +6302,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
6120
6302
  // Everything about the work in flight shares one card above the composer. The
6121
6303
  // tests strip also shows for a read-only viewer (watching is not editing); the
6122
6304
  // files strip does not (committing is editor work). Popups hide the card.
6123
- const showTests = testsBarVisible && shouldShowTestsBar(testsRun, testsList);
6305
+ const showTests = testsBarVisible && (nativeTests ? nativeTests.visible : shouldShowTestsBar(testsRun, testsList));
6124
6306
  const showFiles = canEdit !== false && pendingFiles != null && pendingFiles.length > 0;
6125
6307
  const workVisible = !commandMenu &&
6126
6308
  !modelPicker &&
@@ -6212,7 +6394,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
6212
6394
  return (_jsx(ScriptsCard, { projectId: projectId, initialQuery: item.card.query ?? '', isLight: isLight, agentName: agentName }, item.card.id));
6213
6395
  }
6214
6396
  if (item.card.variant === 'tests') {
6215
- 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
6397
+ return (_jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, agentName: agentName, initialQuery: item.card.query ?? '', canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onSkipCurrent: skipCurrentTest, onFix: fixTests, fixDisabledReason: canEdit === false
6216
6398
  ? t('ide.tests.viewerCannotRun', undefined, {
6217
6399
  defaultValue: 'Only editors can run this project’s tests.',
6218
6400
  })
@@ -7469,15 +7651,15 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
7469
7651
  minHeight: 36,
7470
7652
  }
7471
7653
  : {}),
7472
- }, 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
7473
- ? t('ide.tests.viewerCannotRun', undefined, {
7474
- defaultValue: 'Only editors can run this project’s tests.',
7475
- })
7476
- : isLoading || isRemoteStreaming
7477
- ? t('ide.tests.fixBusy', undefined, {
7478
- defaultValue: 'Wait for the current turn to finish.',
7479
- })
7480
- : null, isLight: isLight, embedded: true }))] })] })), filePicker &&
7654
+ }, 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' && (_jsxs(_Fragment, { children: [nativeTests?.history, _jsx(TestsCard, { tests: testsList, status: testsStatus, run: testsRun, agentName: agentName, initialQuery: panelOverlayQuery, canRun: (canRunTests ?? canEdit !== false) && canEdit !== false, onRun: startTestsRun, onCancel: cancelTestsRun, onSkipCurrent: skipCurrentTest, onFix: fixTests, fixDisabledReason: canEdit === false
7655
+ ? t('ide.tests.viewerCannotRun', undefined, {
7656
+ defaultValue: 'Only editors can run this project’s tests.',
7657
+ })
7658
+ : isLoading || isRemoteStreaming
7659
+ ? t('ide.tests.fixBusy', undefined, {
7660
+ defaultValue: 'Wait for the current turn to finish.',
7661
+ })
7662
+ : null, isLight: isLight, embedded: true })] }))] })] })), filePicker &&
7481
7663
  filteredEntries.length > 0 &&
7482
7664
  (() => {
7483
7665
  const normalizeTabPath = (p) => p.startsWith('/workspace/')
@@ -7665,7 +7847,8 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
7665
7847
  }, onMouseLeave: (e) => {
7666
7848
  e.currentTarget.style.opacity = '0.7';
7667
7849
  e.currentTarget.style.background = 'none';
7668
- }, 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))) })] })), workVisible && (_jsxs(WorkCard, { children: [_jsx(PlanCard, { todos: plan, live: planLive, awaitingUser: planAwaitingUser }), _jsx(BackgroundTasksStrip, { tasks: backgroundTasks }), showTests && (_jsx(TestStatusBar, { run: testsRun, tests: testsList, onOpen: () => openTestsBrowser(''), onStop: testsRun.running ? cancelTestsRun : undefined })), showFiles && pendingFiles != null && (_jsx(DockStrip, { molId: "chat-commit-bar", toggleMolId: "chat-commit-toggle", expanded: commitBarExpanded, onToggle: () => setCommitBarExpanded((v) => !v), hideLabel: true, label: t('ide.chat.showChangedFiles', undefined, {
7850
+ }, 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))) })] })), workVisible && (_jsxs(WorkCard, { children: [_jsx(PlanCard, { todos: plan, live: planLive, awaitingUser: planAwaitingUser }), _jsx(BackgroundTasksStrip, { tasks: backgroundTasks }), showTests &&
7851
+ (nativeTests ? (nativeTests.renderStatus(() => openTestsBrowser(''))) : (_jsx(TestStatusBar, { run: testsRun, agentName: agentName, tests: testsList, onOpen: () => openTestsBrowser(''), onStop: testsRun.running && !testsRun.external ? cancelTestsRun : undefined }))), showFiles && pendingFiles != null && (_jsx(DockStrip, { molId: "chat-commit-bar", toggleMolId: "chat-commit-toggle", expanded: commitBarExpanded, onToggle: () => setCommitBarExpanded((v) => !v), hideLabel: true, label: t('ide.chat.showChangedFiles', undefined, {
7669
7852
  defaultValue: 'Show changed files',
7670
7853
  }), actions: _jsx(_Fragment, { children: isAutoCommitEnabled(autoCommit) &&
7671
7854
  commitState?.status !== 'committing' &&
@@ -8115,7 +8298,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
8115
8298
  * @param props - Component props (see {@link MessageItemProps}).
8116
8299
  * @returns The rendered chat panel element.
8117
8300
  */
8118
- 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, canSendTeamMessages = true, canShare, shareLinksAllowed, shareLinksRefreshSignal, shareModalContent, buildUpgradeCta, buildHelpUpgradeSection, userAvatar, agentName, productName, version, extraCommands, feedbackUrl, listTests, runTests, canRunTests, testsAvailable, skipToolCall, className, }) {
8301
+ 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, canSendTeamMessages = true, canShare, shareLinksAllowed, shareLinksRefreshSignal, shareModalContent, buildUpgradeCta, buildHelpUpgradeSection, userAvatar, agentName, productName, version, extraCommands, feedbackUrl, listTests, runTests, watchTestRuns, nativeTests, canRunTests, testsAvailable, skipToolCall, className, }) {
8119
8302
  const cm = getClassMap();
8120
8303
  // Share management may be gated ABOVE canEdit by the host (see
8121
8304
  // ChatPanelProps.canShare) — gates the built-in header share button here and
@@ -8330,7 +8513,7 @@ export function ChatPanel({ projectId, endpoint, initialMessage, onInitialMessag
8330
8513
  textOverflow: 'ellipsis',
8331
8514
  whiteSpace: 'nowrap',
8332
8515
  width: '100%',
8333
- }, 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, canSendTeamMessages: canSendTeamMessages, canShare: shareAllowed, shareLinksAllowed: shareLinksAllowed, shareLinksRefreshSignal: shareLinksRefreshSignal, shareModalContent: shareModalContent, 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)] }));
8516
+ }, 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, canSendTeamMessages: canSendTeamMessages, canShare: shareAllowed, shareLinksAllowed: shareLinksAllowed, shareLinksRefreshSignal: shareLinksRefreshSignal, shareModalContent: shareModalContent, 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, watchTestRuns: watchTestRuns, nativeTests: nativeTests, canRunTests: canRunTests, testsAvailable: testsAvailable, skipToolCall: skipToolCall }, chatKey)] }));
8334
8517
  }
8335
8518
  ChatPanel.displayName = 'ChatPanel';
8336
8519
  //# sourceMappingURL=ChatPanel.js.map