@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.
- package/README.md +166 -4
- package/dist/components/ChatPanel.d.ts +4 -1
- package/dist/components/ChatPanel.d.ts.map +1 -1
- package/dist/components/ChatPanel.js +238 -55
- package/dist/components/ChatPanel.js.map +1 -1
- package/dist/components/TestStatusBar.d.ts +3 -1
- package/dist/components/TestStatusBar.d.ts.map +1 -1
- package/dist/components/TestStatusBar.js +7 -4
- package/dist/components/TestStatusBar.js.map +1 -1
- package/dist/components/TestsCard.d.ts +12 -1
- package/dist/components/TestsCard.d.ts.map +1 -1
- package/dist/components/TestsCard.js +31 -3
- package/dist/components/TestsCard.js.map +1 -1
- package/dist/components/tests-card-utilities.d.ts +31 -2
- package/dist/components/tests-card-utilities.d.ts.map +1 -1
- package/dist/components/tests-card-utilities.js +136 -6
- package/dist/components/tests-card-utilities.js.map +1 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -5
|
@@ -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" }) }),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2818
|
-
...
|
|
2868
|
+
const initial = {
|
|
2869
|
+
...EMPTY_RUN_STATE,
|
|
2819
2870
|
running: true,
|
|
2820
|
-
|
|
2821
|
-
error: null,
|
|
2822
|
-
output: [],
|
|
2823
|
-
currentId: null,
|
|
2824
|
-
queued: selection.ids ?? [],
|
|
2871
|
+
queued: [...(selection.ids ?? [])],
|
|
2825
2872
|
startedAt: Date.now(),
|
|
2826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
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
|
|
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(
|
|
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
|
-
},
|
|
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
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
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 &&
|
|
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
|