@iblai/iblai-js 1.8.4 → 1.8.7

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.
@@ -1713,6 +1713,821 @@ async function verifyMemoryNotExists(page, content) {
1713
1713
  logger.info(`Verified memory removed: "${content}"`);
1714
1714
  }
1715
1715
 
1716
+ // ============================
1717
+ // Navigation Helpers
1718
+ // ============================
1719
+ /**
1720
+ * Check if the Sandbox tab is visible in the edit-mentor modal.
1721
+ * Returns true if the tab exists and is visible (i.e. mentor has is_claw_enabled === true).
1722
+ */
1723
+ async function isSandboxTabVisible(page) {
1724
+ const tab = page.getByRole('tab', { name: 'Sandbox', exact: true });
1725
+ try {
1726
+ await test$1.expect(tab).toBeVisible({ timeout: 5000 });
1727
+ return true;
1728
+ }
1729
+ catch (_a) {
1730
+ return false;
1731
+ }
1732
+ }
1733
+ /**
1734
+ * Switch to the Sandbox tab inside the edit-mentor modal.
1735
+ * Assumes the modal is already open and the user has permission to see the tab.
1736
+ */
1737
+ async function switchToSandboxTab(page) {
1738
+ const tab = page.getByRole('tab', { name: 'Sandbox', exact: true });
1739
+ await test$1.expect(tab).toBeVisible({ timeout: 10000 });
1740
+ await tab.click();
1741
+ await test$1.expect(page.getByRole('heading', { name: 'Sandbox' }).or(page.getByText('Sandbox'))).toBeVisible({ timeout: 10000 });
1742
+ logger.info('Switched to Sandbox tab');
1743
+ }
1744
+ /**
1745
+ * Switch to the Skills tab inside the edit-mentor modal.
1746
+ */
1747
+ async function switchToSkillsTab(page) {
1748
+ const tab = page.getByRole('tab', { name: 'Skills', exact: true });
1749
+ await test$1.expect(tab).toBeVisible({ timeout: 10000 });
1750
+ await tab.click();
1751
+ logger.info('Switched to Skills tab');
1752
+ }
1753
+ // ============================
1754
+ // Instance Table Helpers (not-connected state)
1755
+ // ============================
1756
+ /**
1757
+ * Verify the instance table is visible with all expected column headers.
1758
+ * Should be called when the mentor has no connected sandbox instance.
1759
+ */
1760
+ async function verifyInstanceTableVisible(page) {
1761
+ const table = page.locator('table');
1762
+ await test$1.expect(table).toBeVisible({ timeout: 15000 });
1763
+ await test$1.expect(table.locator('th').filter({ hasText: 'NAME' })).toBeVisible();
1764
+ await test$1.expect(table.locator('th').filter({ hasText: 'URL' })).toBeVisible();
1765
+ await test$1.expect(table.locator('th').filter({ hasText: 'TYPE' })).toBeVisible();
1766
+ await test$1.expect(table.locator('th').filter({ hasText: 'STATUS' })).toBeVisible();
1767
+ await test$1.expect(table.locator('th').filter({ hasText: 'HEALTH' })).toBeVisible();
1768
+ await test$1.expect(table.locator('th').filter({ hasText: 'VERSION' })).toBeVisible();
1769
+ await test$1.expect(table.locator('th').filter({ hasText: 'LAST CHECK' })).toBeVisible();
1770
+ logger.info('Instance table is visible with correct headers');
1771
+ }
1772
+ /**
1773
+ * Get the number of rows currently displayed in the instance table (excluding empty-state rows).
1774
+ */
1775
+ async function getInstanceRowCount(page) {
1776
+ const rows = page.locator('table tbody tr').filter({ hasNot: page.locator('td[colspan]') });
1777
+ const count = await rows.count();
1778
+ logger.info(`Instance table has ${count} rows`);
1779
+ return count;
1780
+ }
1781
+ /**
1782
+ * Verify the empty state is shown when there are no instances.
1783
+ */
1784
+ async function verifyInstanceTableEmpty(page) {
1785
+ await test$1.expect(page.getByText(/No instances available/i)).toBeVisible({ timeout: 10000 });
1786
+ logger.info('Instance table empty state is displayed');
1787
+ }
1788
+ /**
1789
+ * Filter the instance table by search query (name or URL).
1790
+ */
1791
+ async function searchInstances(page, query) {
1792
+ const searchInput = page.getByPlaceholder('Search instances...');
1793
+ await test$1.expect(searchInput).toBeVisible({ timeout: 10000 });
1794
+ await searchInput.fill(query);
1795
+ // Let the filter apply
1796
+ await page.waitForTimeout(300);
1797
+ logger.info(`Searched instances with query: "${query}"`);
1798
+ }
1799
+ /**
1800
+ * Clear the instance table search query.
1801
+ */
1802
+ async function clearInstanceSearch(page) {
1803
+ const searchInput = page.getByPlaceholder('Search instances...');
1804
+ await searchInput.fill('');
1805
+ await page.waitForTimeout(300);
1806
+ logger.info('Cleared instance search');
1807
+ }
1808
+ /**
1809
+ * Open the Actions dropdown menu for a specific instance row.
1810
+ * Returns the dropdown menu locator.
1811
+ */
1812
+ async function openInstanceActionsMenu(page, instanceName) {
1813
+ const row = page.locator('table tbody tr').filter({ hasText: instanceName });
1814
+ await test$1.expect(row).toBeVisible({ timeout: 10000 });
1815
+ const actionsButton = row.getByRole('button', { name: 'Actions' });
1816
+ await test$1.expect(actionsButton).toBeVisible({ timeout: 5000 });
1817
+ await actionsButton.click();
1818
+ const menu = page.getByRole('menu');
1819
+ await test$1.expect(menu).toBeVisible({ timeout: 5000 });
1820
+ return menu;
1821
+ }
1822
+ // ============================
1823
+ // Create Instance Helpers
1824
+ // ============================
1825
+ /**
1826
+ * Open the "New Instance" dialog by clicking the Add Instance button.
1827
+ * Returns the dialog locator.
1828
+ */
1829
+ async function openNewInstanceDialog(page) {
1830
+ const addButton = page.getByRole('button', { name: /Add Instance/i });
1831
+ await test$1.expect(addButton).toBeVisible({ timeout: 10000 });
1832
+ await addButton.click();
1833
+ const dialog = page.getByRole('dialog').filter({ hasText: 'New Instance' });
1834
+ return waitForDialogReady(page, dialog);
1835
+ }
1836
+ /**
1837
+ * Fill the New Instance form and submit it.
1838
+ * @param name - Display name for the instance
1839
+ * @param serverUrl - Fully qualified https URL
1840
+ * @param clawType - 'openclaw' (default) or 'ironclaw'
1841
+ * @param gatewayToken - Optional auth token
1842
+ */
1843
+ async function createInstance(page, { name, serverUrl, clawType = 'openclaw', gatewayToken, }) {
1844
+ await openNewInstanceDialog(page);
1845
+ await page.getByLabel('Name').fill(name);
1846
+ await page.getByLabel('Server URL').fill(serverUrl);
1847
+ if (clawType !== 'openclaw') {
1848
+ const typeSelect = page.getByLabel('Type');
1849
+ await typeSelect.click();
1850
+ const option = page.getByRole('option', { name: new RegExp(clawType, 'i') });
1851
+ await test$1.expect(option).toBeVisible({ timeout: 5000 });
1852
+ await option.click();
1853
+ }
1854
+ if (gatewayToken) {
1855
+ await page.getByLabel('Gateway Token').fill(gatewayToken);
1856
+ }
1857
+ const createButton = page.getByRole('button', { name: /^Create$/i });
1858
+ await test$1.expect(createButton).toBeEnabled({ timeout: 5000 });
1859
+ await createButton.click();
1860
+ await test$1.expect(page.getByText('Instance created', { exact: false })).toBeVisible({
1861
+ timeout: 10000,
1862
+ });
1863
+ logger.info(`Created instance "${name}" at ${serverUrl}`);
1864
+ }
1865
+ /**
1866
+ * Cancel the New Instance dialog without submitting.
1867
+ */
1868
+ async function cancelNewInstanceDialog(page) {
1869
+ const dialog = page.getByRole('dialog').filter({ hasText: 'New Instance' });
1870
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1871
+ await page.keyboard.press('Escape');
1872
+ await test$1.expect(dialog).not.toBeVisible({ timeout: 5000 });
1873
+ logger.info('Cancelled New Instance dialog');
1874
+ }
1875
+ // ============================
1876
+ // Edit Instance Helpers
1877
+ // ============================
1878
+ /**
1879
+ * Open the Edit dialog for an instance from its Actions menu.
1880
+ * Returns the edit dialog locator.
1881
+ */
1882
+ async function openEditInstanceDialog(page, instanceName) {
1883
+ await openInstanceActionsMenu(page, instanceName);
1884
+ const editItem = page.getByRole('menuitem', { name: /Edit/i });
1885
+ await test$1.expect(editItem).toBeVisible({ timeout: 5000 });
1886
+ await editItem.click();
1887
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Edit Instance' });
1888
+ return waitForDialogReady(page, dialog);
1889
+ }
1890
+ /**
1891
+ * Edit an existing instance with new values.
1892
+ * @param instanceName - The current name of the instance to edit
1893
+ * @param updates - Fields to update
1894
+ */
1895
+ async function editInstance(page, instanceName, updates) {
1896
+ await openEditInstanceDialog(page, instanceName);
1897
+ if (updates.name !== undefined) {
1898
+ const nameInput = page.getByLabel('Name');
1899
+ await nameInput.fill(updates.name);
1900
+ }
1901
+ if (updates.serverUrl !== undefined) {
1902
+ const urlInput = page.getByLabel('Server URL');
1903
+ await urlInput.fill(updates.serverUrl);
1904
+ }
1905
+ if (updates.clawType !== undefined) {
1906
+ const typeSelect = page.getByLabel('Type');
1907
+ await typeSelect.click();
1908
+ const option = page.getByRole('option', { name: new RegExp(updates.clawType, 'i') });
1909
+ await test$1.expect(option).toBeVisible({ timeout: 5000 });
1910
+ await option.click();
1911
+ }
1912
+ const saveButton = page.getByRole('button', { name: /^Save$/i });
1913
+ await test$1.expect(saveButton).toBeEnabled({ timeout: 5000 });
1914
+ await saveButton.click();
1915
+ await test$1.expect(page.getByText('Instance updated', { exact: false })).toBeVisible({
1916
+ timeout: 10000,
1917
+ });
1918
+ logger.info(`Edited instance "${instanceName}" with updates: ${JSON.stringify(updates)}`);
1919
+ }
1920
+ // ============================
1921
+ // Delete Instance Helpers
1922
+ // ============================
1923
+ /**
1924
+ * Delete an instance via the Actions menu, confirming the deletion dialog.
1925
+ */
1926
+ async function deleteInstance(page, instanceName) {
1927
+ await openInstanceActionsMenu(page, instanceName);
1928
+ const deleteItem = page.getByRole('menuitem', { name: /Delete/i });
1929
+ await test$1.expect(deleteItem).toBeVisible({ timeout: 5000 });
1930
+ await deleteItem.click();
1931
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Delete Instance' });
1932
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1933
+ const confirmButton = dialog.getByRole('button', { name: /^Delete$/i });
1934
+ await test$1.expect(confirmButton).toBeVisible({ timeout: 5000 });
1935
+ await confirmButton.click();
1936
+ await test$1.expect(page.getByText('Instance deleted', { exact: false })).toBeVisible({
1937
+ timeout: 10000,
1938
+ });
1939
+ logger.info(`Deleted instance "${instanceName}"`);
1940
+ }
1941
+ /**
1942
+ * Open the delete confirmation dialog and cancel it without deleting.
1943
+ */
1944
+ async function cancelDeleteInstance(page, instanceName) {
1945
+ await openInstanceActionsMenu(page, instanceName);
1946
+ const deleteItem = page.getByRole('menuitem', { name: /Delete/i });
1947
+ await deleteItem.click();
1948
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Delete Instance' });
1949
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1950
+ const cancelButton = dialog.getByRole('button', { name: /Cancel/i });
1951
+ await cancelButton.click();
1952
+ await test$1.expect(dialog).not.toBeVisible({ timeout: 5000 });
1953
+ logger.info(`Cancelled delete of instance "${instanceName}"`);
1954
+ }
1955
+ // ============================
1956
+ // Connect / Disconnect Helpers
1957
+ // ============================
1958
+ /**
1959
+ * Connect the current mentor to an instance via the Actions menu.
1960
+ */
1961
+ async function connectToInstance(page, instanceName) {
1962
+ await openInstanceActionsMenu(page, instanceName);
1963
+ const connectItem = page.getByRole('menuitem', { name: /Connect/i });
1964
+ await test$1.expect(connectItem).toBeVisible({ timeout: 5000 });
1965
+ await connectItem.click();
1966
+ await test$1.expect(page.getByText('Instance connected', { exact: false })).toBeVisible({
1967
+ timeout: 10000,
1968
+ });
1969
+ // After connect, the UI should switch to show the Connected Instance card
1970
+ await test$1.expect(page.getByText('Connected Instance')).toBeVisible({ timeout: 10000 });
1971
+ logger.info(`Connected mentor to instance "${instanceName}"`);
1972
+ }
1973
+ /**
1974
+ * Disconnect the current mentor from its connected instance,
1975
+ * confirming the disconnect dialog.
1976
+ */
1977
+ async function disconnectInstance(page) {
1978
+ const disconnectButton = page.getByRole('button', { name: /^Disconnect$/i });
1979
+ await test$1.expect(disconnectButton).toBeVisible({ timeout: 10000 });
1980
+ await disconnectButton.click();
1981
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Disconnect Instance' });
1982
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1983
+ const confirmButton = dialog.getByRole('button', { name: /^Disconnect$/i });
1984
+ await confirmButton.click();
1985
+ await test$1.expect(page.getByText('Instance disconnected', { exact: false })).toBeVisible({
1986
+ timeout: 10000,
1987
+ });
1988
+ logger.info('Disconnected mentor from instance');
1989
+ }
1990
+ /**
1991
+ * Open the disconnect confirmation dialog and cancel it.
1992
+ */
1993
+ async function cancelDisconnectInstance(page) {
1994
+ const disconnectButton = page.getByRole('button', { name: /^Disconnect$/i });
1995
+ await disconnectButton.click();
1996
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Disconnect Instance' });
1997
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
1998
+ const cancelButton = dialog.getByRole('button', { name: /Cancel/i });
1999
+ await cancelButton.click();
2000
+ await test$1.expect(dialog).not.toBeVisible({ timeout: 5000 });
2001
+ logger.info('Cancelled disconnect');
2002
+ }
2003
+ // ============================
2004
+ // Health-check / Connectivity Helpers
2005
+ // ============================
2006
+ /**
2007
+ * Open an instance's actions menu and click "Run checks". Resolves once the
2008
+ * health-check + connectivity-test toasts have surfaced (success or error).
2009
+ */
2010
+ async function runInstanceChecks(page, instanceName) {
2011
+ await openInstanceActionsMenu(page, instanceName);
2012
+ const runChecks = page.getByRole('menuitem', { name: /Run checks/i });
2013
+ await test$1.expect(runChecks).toBeVisible({ timeout: 5000 });
2014
+ await runChecks.click();
2015
+ // Each action toasts independently. We don't assert success vs. error here —
2016
+ // callers can inspect the row's status/health afterwards via `getInstanceHealthLabel`.
2017
+ await test$1.expect(page.getByText(/Health check (passed|failed)|Connectivity test (passed|failed)/i).first()).toBeVisible({ timeout: 15000 });
2018
+ logger.info(`Ran checks for instance "${instanceName}"`);
2019
+ }
2020
+ /**
2021
+ * Trigger the "Run checks" button on the connected-instance card. Used for the
2022
+ * connected-state flow rather than the table dropdown.
2023
+ */
2024
+ async function runConnectedInstanceChecks(page) {
2025
+ await test$1.expect(page.getByText('Connected Instance')).toBeVisible({ timeout: 10000 });
2026
+ const runChecks = page.getByRole('button', { name: /Run checks/i });
2027
+ await test$1.expect(runChecks).toBeVisible({ timeout: 5000 });
2028
+ await test$1.expect(runChecks).toBeEnabled();
2029
+ await runChecks.click();
2030
+ await test$1.expect(page.getByText(/Health check (passed|failed)|Connectivity test (passed|failed)/i).first()).toBeVisible({ timeout: 15000 });
2031
+ logger.info('Ran checks on connected instance');
2032
+ }
2033
+ /**
2034
+ * Read the canonical health label rendered by `StatusDot` for the given row.
2035
+ * Returns "Healthy" / "Unhealthy" / null (em-dash for unknown).
2036
+ */
2037
+ async function getInstanceHealthLabel(page, instanceName) {
2038
+ const row = page.locator('table tbody tr').filter({ hasText: instanceName });
2039
+ await test$1.expect(row).toBeVisible({ timeout: 10000 });
2040
+ // Health is the 5th data cell (Name, URL, Type, Status, Health, …).
2041
+ const healthCell = row.locator('td').nth(4);
2042
+ const text = (await healthCell.innerText()).trim();
2043
+ if (text === 'Healthy' || text === 'Unhealthy')
2044
+ return text;
2045
+ return null;
2046
+ }
2047
+ /**
2048
+ * Read the canonical status label rendered by `StatusDot` for the given row.
2049
+ * Returns "Active" / "Error" / null (em-dash for unknown).
2050
+ */
2051
+ async function getInstanceStatusLabel(page, instanceName) {
2052
+ const row = page.locator('table tbody tr').filter({ hasText: instanceName });
2053
+ await test$1.expect(row).toBeVisible({ timeout: 10000 });
2054
+ // Status is the 4th data cell (Name, URL, Type, Status, …).
2055
+ const statusCell = row.locator('td').nth(3);
2056
+ const text = (await statusCell.innerText()).trim();
2057
+ if (text === 'Active' || text === 'Error')
2058
+ return text;
2059
+ return null;
2060
+ }
2061
+ /**
2062
+ * Verify that the Connect dropdown item is blocked (dimmed + cursor-not-allowed)
2063
+ * because the instance status is unhealthy. Hovering should reveal the unhealthy
2064
+ * tooltip explaining why.
2065
+ */
2066
+ async function verifyConnectDisabledForUnhealthy(page, instanceName) {
2067
+ await openInstanceActionsMenu(page, instanceName);
2068
+ const connectItem = page.getByRole('menuitem', { name: /^Connect$/i });
2069
+ await test$1.expect(connectItem).toBeVisible({ timeout: 5000 });
2070
+ await test$1.expect(connectItem).toHaveClass(/opacity-50/);
2071
+ await test$1.expect(connectItem).toHaveClass(/cursor-not-allowed/);
2072
+ // Close the menu without firing Connect.
2073
+ await page.keyboard.press('Escape');
2074
+ logger.info(`Verified Connect is blocked for unhealthy instance "${instanceName}"`);
2075
+ }
2076
+ // ============================
2077
+ // Connected-State Helpers
2078
+ // ============================
2079
+ /**
2080
+ * Verify the Connected Instance card shows the expected fields.
2081
+ * The URL field shows the full server_url; other fields are status/health/last-check.
2082
+ */
2083
+ async function verifyConnectedInstanceCard(page) {
2084
+ await test$1.expect(page.getByText('Connected Instance')).toBeVisible({ timeout: 10000 });
2085
+ // Labels inside the card
2086
+ await test$1.expect(page.getByText('Name', { exact: true })).toBeVisible();
2087
+ await test$1.expect(page.getByText('URL', { exact: true })).toBeVisible();
2088
+ await test$1.expect(page.getByText('Status', { exact: true })).toBeVisible();
2089
+ await test$1.expect(page.getByText('Health', { exact: true })).toBeVisible();
2090
+ await test$1.expect(page.getByText('Last Check', { exact: true })).toBeVisible();
2091
+ logger.info('Connected instance card is displayed with all expected fields');
2092
+ }
2093
+ /**
2094
+ * Toggle the Auto Push on Save switch.
2095
+ * Returns the new checked state.
2096
+ */
2097
+ async function toggleAutoPush(page) {
2098
+ const switchEl = page
2099
+ .locator('div')
2100
+ .filter({ hasText: /^Auto Push on Save/ })
2101
+ .getByRole('switch')
2102
+ .first();
2103
+ await test$1.expect(switchEl).toBeVisible({ timeout: 10000 });
2104
+ const wasChecked = await switchEl.isChecked();
2105
+ await switchEl.click();
2106
+ await test$1.expect(page.getByText(/Configuration updated/i)).toBeVisible({ timeout: 10000 });
2107
+ const isNowChecked = await switchEl.isChecked();
2108
+ logger.info(`Toggled auto-push from ${wasChecked} to ${isNowChecked}`);
2109
+ return isNowChecked;
2110
+ }
2111
+ /**
2112
+ * Click the Push button to push the current configuration to the worker.
2113
+ */
2114
+ async function pushConfiguration(page) {
2115
+ const pushButton = page.getByRole('button', { name: /^Push$/i });
2116
+ await test$1.expect(pushButton).toBeVisible({ timeout: 10000 });
2117
+ await pushButton.click();
2118
+ await test$1.expect(page.getByText(/Configuration push queued/i)).toBeVisible({ timeout: 10000 });
2119
+ logger.info('Pushed configuration to worker');
2120
+ }
2121
+ // ============================
2122
+ // LLM Model Selection Helpers
2123
+ // ============================
2124
+ /**
2125
+ * Open the LLM provider picker by clicking Change (or Select Model) in the Model row.
2126
+ * Returns the picker dialog locator.
2127
+ */
2128
+ async function openLLMProviderPicker(page) {
2129
+ const triggerButton = page.getByRole('button', { name: /^Change$|^Select Model$/i });
2130
+ await test$1.expect(triggerButton).toBeVisible({ timeout: 10000 });
2131
+ await triggerButton.click();
2132
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Select Provider' });
2133
+ return waitForDialogReady(page, dialog);
2134
+ }
2135
+ /**
2136
+ * Pick an LLM provider + model combination via the provider picker flow.
2137
+ * @param providerName - The provider key (e.g. 'anthropic', 'openai')
2138
+ * @param modelName - The model name (e.g. 'claude-sonnet-4-5-20250929')
2139
+ */
2140
+ async function selectLLMModel(page, providerName, modelName) {
2141
+ await openLLMProviderPicker(page);
2142
+ // Click the provider card
2143
+ const providerCard = page.getByRole('dialog').getByText(new RegExp(`^${providerName}$`, 'i'));
2144
+ await test$1.expect(providerCard).toBeVisible({ timeout: 10000 });
2145
+ await providerCard.click();
2146
+ // Model selection modal ("LLM Selection")
2147
+ const modelDialog = page.getByRole('dialog').filter({ hasText: 'LLM Selection' });
2148
+ await test$1.expect(modelDialog).toBeVisible({ timeout: 10000 });
2149
+ const modelButton = modelDialog.getByRole('button', { name: new RegExp(modelName, 'i') });
2150
+ await test$1.expect(modelButton).toBeVisible({ timeout: 10000 });
2151
+ await modelButton.click();
2152
+ await test$1.expect(page.getByText('Model updated', { exact: false })).toBeVisible({
2153
+ timeout: 10000,
2154
+ });
2155
+ logger.info(`Selected model ${providerName}/${modelName}`);
2156
+ }
2157
+ /**
2158
+ * Get the currently-selected model identifier shown next to the Model row.
2159
+ * Returns null if no model is set (button reads "Select Model").
2160
+ */
2161
+ async function getCurrentModel(page) {
2162
+ var _a;
2163
+ const selectButton = page.getByRole('button', { name: /^Select Model$/i });
2164
+ const hasSelectButton = await selectButton.isVisible().catch(() => false);
2165
+ if (hasSelectButton)
2166
+ return null;
2167
+ // When a model is set, the row shows: Model <tooltip> <model-text> [Change]
2168
+ const modelRow = page
2169
+ .locator('div.rounded-lg.border')
2170
+ .filter({ has: page.getByRole('button', { name: /^Change$/i }) });
2171
+ const text = await modelRow.textContent();
2172
+ // Extract the model identifier — it's the text before "Change"
2173
+ const cleaned = (_a = text === null || text === void 0 ? void 0 : text.replace(/Change$/, '').replace(/^Model/, '').trim()) !== null && _a !== void 0 ? _a : null;
2174
+ logger.info(`Current model: ${cleaned}`);
2175
+ return cleaned;
2176
+ }
2177
+ // ============================
2178
+ // Agent Config Prompts Helpers (Prompts tab)
2179
+ // ============================
2180
+ const AGENT_PROMPT_LABELS = [
2181
+ 'Identity',
2182
+ 'Soul',
2183
+ 'User Context',
2184
+ 'Tools',
2185
+ 'Agents',
2186
+ 'Bootstrap',
2187
+ 'Heartbeat',
2188
+ 'Memory',
2189
+ ];
2190
+ /**
2191
+ * Verify that all 8 agent config prompt fields are visible in the Prompts tab.
2192
+ * Only renders when the mentor has enable_claw === true on its settings.
2193
+ *
2194
+ * Note: PATCH on the agent-config endpoint is upsert — there's no separate
2195
+ * "Create Agent Config" button anymore. The first edit on any field bootstraps
2196
+ * the row.
2197
+ */
2198
+ async function verifyAgentConfigPromptsVisible(page) {
2199
+ for (const label of AGENT_PROMPT_LABELS) {
2200
+ await test$1.expect(page.getByText(label, { exact: true })).toBeVisible({ timeout: 10000 });
2201
+ }
2202
+ logger.info('All agent config prompt fields are visible');
2203
+ }
2204
+ /**
2205
+ * Open the edit modal for a specific agent prompt field (e.g. Identity, Soul).
2206
+ */
2207
+ async function openAgentPromptEditModal(page, field) {
2208
+ const row = page
2209
+ .locator('div.rounded-lg.border.p-6')
2210
+ .filter({ hasText: new RegExp(`^${field}$`) });
2211
+ const editButton = row.getByRole('button', { name: /Edit/i });
2212
+ await test$1.expect(editButton).toBeVisible({ timeout: 10000 });
2213
+ await editButton.click();
2214
+ const dialog = page.getByRole('dialog').filter({ hasText: `Edit ${field}` });
2215
+ return waitForDialogReady(page, dialog);
2216
+ }
2217
+ /**
2218
+ * Edit an agent prompt field (opens the modal, types content, saves).
2219
+ */
2220
+ async function editAgentPrompt(page, field, content) {
2221
+ await openAgentPromptEditModal(page, field);
2222
+ // The RichTextEditor renders into a contenteditable element; fall back to textarea if simpler.
2223
+ const editorRegion = page.locator('[contenteditable="true"]').first();
2224
+ const textarea = page.locator('textarea').first();
2225
+ const hasEditor = await editorRegion.isVisible().catch(() => false);
2226
+ if (hasEditor) {
2227
+ await editorRegion.click();
2228
+ await page.keyboard.press('ControlOrMeta+A');
2229
+ await page.keyboard.press('Delete');
2230
+ await page.keyboard.type(content);
2231
+ }
2232
+ else {
2233
+ await textarea.fill(content);
2234
+ }
2235
+ const saveButton = page.getByRole('button', { name: /^Save$/i });
2236
+ await test$1.expect(saveButton).toBeEnabled({ timeout: 5000 });
2237
+ await saveButton.click();
2238
+ await test$1.expect(page.getByText(`${field} updated successfully`, { exact: false })).toBeVisible({
2239
+ timeout: 10000,
2240
+ });
2241
+ logger.info(`Edited agent prompt "${field}" with ${content.length} chars`);
2242
+ }
2243
+ // ============================
2244
+ // Skills Tab Helpers
2245
+ // ============================
2246
+ /**
2247
+ * Verify the Skills tab content is loaded (either showing skill rows or the empty state).
2248
+ */
2249
+ async function verifySkillsTabVisible(page) {
2250
+ // Either at least one skill toggle row is visible, or the empty message is shown
2251
+ const hasSkills = await page
2252
+ .getByRole('switch')
2253
+ .first()
2254
+ .isVisible()
2255
+ .catch(() => false);
2256
+ if (hasSkills) {
2257
+ logger.info('Skills tab is visible with skills rendered');
2258
+ return;
2259
+ }
2260
+ await test$1.expect(page.getByText(/No skills available for this platform/i)).toBeVisible({
2261
+ timeout: 10000,
2262
+ });
2263
+ logger.info('Skills tab is visible with empty state');
2264
+ }
2265
+ /**
2266
+ * Verify that the "No skills available" empty state is shown.
2267
+ */
2268
+ async function verifySkillsEmptyState(page) {
2269
+ await test$1.expect(page.getByText(/No skills available for this platform/i)).toBeVisible({
2270
+ timeout: 10000,
2271
+ });
2272
+ logger.info('Skills empty state is displayed');
2273
+ }
2274
+ /**
2275
+ * Count the number of skill rows rendered in the Skills tab.
2276
+ * Each row represents an available (enabled) platform skill.
2277
+ */
2278
+ async function getSkillRowCount(page) {
2279
+ const rows = page.locator('div.rounded-lg.border.p-6').filter({ has: page.getByRole('switch') });
2280
+ const count = await rows.count();
2281
+ logger.info(`${count} skill row(s) displayed`);
2282
+ return count;
2283
+ }
2284
+ /**
2285
+ * Verify that a specific skill row is visible in the Skills tab.
2286
+ */
2287
+ async function verifySkillVisible(page, skillName) {
2288
+ const row = page
2289
+ .locator('div.rounded-lg.border.p-6')
2290
+ .filter({ hasText: skillName })
2291
+ .filter({ has: page.getByRole('switch') });
2292
+ await test$1.expect(row).toBeVisible({ timeout: 10000 });
2293
+ logger.info(`Skill "${skillName}" is visible`);
2294
+ }
2295
+ /**
2296
+ * Check if a specific skill is currently enabled for the mentor.
2297
+ */
2298
+ async function isSkillEnabled(page, skillName) {
2299
+ const switchEl = page.getByLabel(new RegExp(`^${skillName} (enabled|disabled)$`));
2300
+ await test$1.expect(switchEl).toBeVisible({ timeout: 10000 });
2301
+ const enabled = await switchEl.isChecked();
2302
+ logger.info(`Skill "${skillName}" is ${enabled ? 'enabled' : 'disabled'}`);
2303
+ return enabled;
2304
+ }
2305
+ /**
2306
+ * Enable a skill for the current mentor by toggling it on.
2307
+ * No-op if already enabled. Waits for the success toast.
2308
+ */
2309
+ async function enableSkill(page, skillName) {
2310
+ const disabledSwitch = page.getByLabel(`${skillName} disabled`);
2311
+ const isDisabled = await disabledSwitch.isVisible().catch(() => false);
2312
+ if (!isDisabled) {
2313
+ logger.info(`Skill "${skillName}" already enabled`);
2314
+ return;
2315
+ }
2316
+ await disabledSwitch.click();
2317
+ await test$1.expect(page.getByText(`${skillName} enabled`, { exact: false })).toBeVisible({
2318
+ timeout: 10000,
2319
+ });
2320
+ logger.info(`Enabled skill "${skillName}"`);
2321
+ }
2322
+ /**
2323
+ * Disable a skill for the current mentor by toggling it off.
2324
+ * No-op if already disabled. Waits for the success toast.
2325
+ */
2326
+ async function disableSkill(page, skillName) {
2327
+ const enabledSwitch = page.getByLabel(`${skillName} enabled`);
2328
+ const isEnabled = await enabledSwitch.isVisible().catch(() => false);
2329
+ if (!isEnabled) {
2330
+ logger.info(`Skill "${skillName}" already disabled`);
2331
+ return;
2332
+ }
2333
+ await enabledSwitch.click();
2334
+ await test$1.expect(page.getByText(`${skillName} disabled`, { exact: false })).toBeVisible({
2335
+ timeout: 10000,
2336
+ });
2337
+ logger.info(`Disabled skill "${skillName}"`);
2338
+ }
2339
+ /**
2340
+ * Toggle a skill's enabled state (flips whatever the current state is).
2341
+ * Returns the new checked state.
2342
+ */
2343
+ async function toggleSkill(page, skillName) {
2344
+ const wasEnabled = await isSkillEnabled(page, skillName);
2345
+ if (wasEnabled) {
2346
+ await disableSkill(page, skillName);
2347
+ return false;
2348
+ }
2349
+ await enableSkill(page, skillName);
2350
+ return true;
2351
+ }
2352
+ /**
2353
+ * Open the "New Skill" dialog by clicking the New Skill button in the Skills tab.
2354
+ * Returns the dialog locator.
2355
+ */
2356
+ async function openNewSkillDialog(page) {
2357
+ const newButton = page.getByRole('button', { name: /^New Skill$/i });
2358
+ await test$1.expect(newButton).toBeVisible({ timeout: 10000 });
2359
+ await newButton.click();
2360
+ const dialog = page.getByRole('dialog').filter({ hasText: 'New Skill' });
2361
+ return waitForDialogReady(page, dialog);
2362
+ }
2363
+ /**
2364
+ * Type content into the RichTextEditor Instruction field.
2365
+ * The editor renders as a contenteditable element.
2366
+ */
2367
+ async function fillInstructionEditor(page, content) {
2368
+ // RichTextEditor uses a contenteditable region
2369
+ const editor = page.locator('[contenteditable="true"]').first();
2370
+ const hasEditor = await editor.isVisible().catch(() => false);
2371
+ if (hasEditor) {
2372
+ await editor.click();
2373
+ await page.keyboard.press('ControlOrMeta+A');
2374
+ await page.keyboard.press('Delete');
2375
+ if (content) {
2376
+ await page.keyboard.type(content);
2377
+ }
2378
+ return;
2379
+ }
2380
+ // Fallback: a plain labelled input/textarea named "Instruction"
2381
+ const textarea = page.getByLabel('Instruction');
2382
+ await textarea.fill(content);
2383
+ }
2384
+ /**
2385
+ * Fill the skill form fields (name, slug, description, version, instruction).
2386
+ * The form is used by both the New and Edit dialogs.
2387
+ * The Instruction field is a rich-text (markdown) editor.
2388
+ */
2389
+ async function fillSkillForm(page, values) {
2390
+ await page.getByLabel('Name').fill(values.name);
2391
+ await page.getByLabel('Slug').fill(values.slug);
2392
+ if (values.version !== undefined) {
2393
+ await page.getByLabel('Version').fill(values.version);
2394
+ }
2395
+ if (values.description !== undefined) {
2396
+ await page.getByLabel('Description').fill(values.description);
2397
+ }
2398
+ if (values.instruction !== undefined) {
2399
+ await fillInstructionEditor(page, values.instruction);
2400
+ }
2401
+ }
2402
+ /**
2403
+ * Create a new platform-level skill via the Skills tab.
2404
+ */
2405
+ async function createSkill(page, values) {
2406
+ await openNewSkillDialog(page);
2407
+ await fillSkillForm(page, values);
2408
+ const createButton = page.getByRole('button', { name: /^Create$/i });
2409
+ await test$1.expect(createButton).toBeEnabled({ timeout: 5000 });
2410
+ await createButton.click();
2411
+ await test$1.expect(page.getByText('Skill created', { exact: false })).toBeVisible({
2412
+ timeout: 10000,
2413
+ });
2414
+ logger.info(`Created skill "${values.name}"`);
2415
+ }
2416
+ /**
2417
+ * Open the Actions dropdown for a specific skill row.
2418
+ * Returns the dropdown menu locator.
2419
+ */
2420
+ async function openSkillActionsMenu(page, skillName) {
2421
+ const actionsButton = page.getByRole('button', { name: `${skillName} actions` });
2422
+ await test$1.expect(actionsButton).toBeVisible({ timeout: 10000 });
2423
+ await actionsButton.click();
2424
+ const menu = page.getByRole('menu');
2425
+ await test$1.expect(menu).toBeVisible({ timeout: 5000 });
2426
+ return menu;
2427
+ }
2428
+ /**
2429
+ * Open the Edit Skill dialog from the actions menu.
2430
+ * Returns the dialog locator.
2431
+ */
2432
+ async function openEditSkillDialog(page, skillName) {
2433
+ await openSkillActionsMenu(page, skillName);
2434
+ const editItem = page.getByRole('menuitem', { name: /Edit/i });
2435
+ await test$1.expect(editItem).toBeVisible({ timeout: 5000 });
2436
+ await editItem.click();
2437
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Edit Skill' });
2438
+ return waitForDialogReady(page, dialog);
2439
+ }
2440
+ /**
2441
+ * Edit an existing platform-level skill.
2442
+ * @param skillName - The current name of the skill to edit
2443
+ * @param updates - Fields to update on the skill
2444
+ */
2445
+ async function editSkill(page, skillName, updates) {
2446
+ await openEditSkillDialog(page, skillName);
2447
+ if (updates.name !== undefined) {
2448
+ await page.getByLabel('Name').fill(updates.name);
2449
+ }
2450
+ if (updates.slug !== undefined) {
2451
+ await page.getByLabel('Slug').fill(updates.slug);
2452
+ }
2453
+ if (updates.version !== undefined) {
2454
+ await page.getByLabel('Version').fill(updates.version);
2455
+ }
2456
+ if (updates.description !== undefined) {
2457
+ await page.getByLabel('Description').fill(updates.description);
2458
+ }
2459
+ if (updates.instruction !== undefined) {
2460
+ await fillInstructionEditor(page, updates.instruction);
2461
+ }
2462
+ const saveButton = page.getByRole('button', { name: /^Save$/i });
2463
+ await test$1.expect(saveButton).toBeEnabled({ timeout: 5000 });
2464
+ await saveButton.click();
2465
+ await test$1.expect(page.getByText('Skill updated', { exact: false })).toBeVisible({
2466
+ timeout: 10000,
2467
+ });
2468
+ logger.info(`Edited skill "${skillName}" with updates: ${JSON.stringify(updates)}`);
2469
+ }
2470
+ /**
2471
+ * Delete a platform-level skill, confirming the deletion dialog.
2472
+ */
2473
+ async function deleteSkill(page, skillName) {
2474
+ await openSkillActionsMenu(page, skillName);
2475
+ const deleteItem = page.getByRole('menuitem', { name: /Delete/i });
2476
+ await test$1.expect(deleteItem).toBeVisible({ timeout: 5000 });
2477
+ await deleteItem.click();
2478
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Delete Skill' });
2479
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
2480
+ const confirmButton = dialog.getByRole('button', { name: /^Delete$/i });
2481
+ await test$1.expect(confirmButton).toBeVisible({ timeout: 5000 });
2482
+ await confirmButton.click();
2483
+ await test$1.expect(page.getByText('Skill deleted', { exact: false })).toBeVisible({
2484
+ timeout: 10000,
2485
+ });
2486
+ logger.info(`Deleted skill "${skillName}"`);
2487
+ }
2488
+ /**
2489
+ * Open the delete confirmation for a skill and cancel it (no deletion).
2490
+ */
2491
+ async function cancelDeleteSkill(page, skillName) {
2492
+ await openSkillActionsMenu(page, skillName);
2493
+ const deleteItem = page.getByRole('menuitem', { name: /Delete/i });
2494
+ await deleteItem.click();
2495
+ const dialog = page.getByRole('dialog').filter({ hasText: 'Delete Skill' });
2496
+ await test$1.expect(dialog).toBeVisible({ timeout: 5000 });
2497
+ const cancelButton = dialog.getByRole('button', { name: /Cancel/i });
2498
+ await cancelButton.click();
2499
+ await test$1.expect(dialog).not.toBeVisible({ timeout: 5000 });
2500
+ logger.info(`Cancelled delete of skill "${skillName}"`);
2501
+ }
2502
+ // ============================
2503
+ // Combined Workflow Helpers
2504
+ // ============================
2505
+ /**
2506
+ * Full workflow: open Sandbox tab, create a new instance, connect the mentor to it.
2507
+ * Returns when the mentor is successfully connected.
2508
+ */
2509
+ async function setupSandboxInstance(page, instance) {
2510
+ await switchToSandboxTab(page);
2511
+ await createInstance(page, instance);
2512
+ await connectToInstance(page, instance.name);
2513
+ await verifyConnectedInstanceCard(page);
2514
+ logger.info(`Sandbox setup complete for "${instance.name}"`);
2515
+ }
2516
+ /**
2517
+ * Full workflow: open Sandbox tab, disconnect the current instance (if any),
2518
+ * then delete it from the table.
2519
+ */
2520
+ async function teardownSandboxInstance(page, instanceName) {
2521
+ await switchToSandboxTab(page);
2522
+ // If a connected instance is present, disconnect first
2523
+ const disconnectButton = page.getByRole('button', { name: /^Disconnect$/i });
2524
+ if (await disconnectButton.isVisible().catch(() => false)) {
2525
+ await disconnectInstance(page);
2526
+ }
2527
+ await deleteInstance(page, instanceName);
2528
+ logger.info(`Sandbox teardown complete for "${instanceName}"`);
2529
+ }
2530
+
1716
2531
  // ============================
1717
2532
  // Navigation Helpers
1718
2533
  // ============================
@@ -2625,8 +3440,13 @@ exports.billingCreditsSection = billingCreditsSection;
2625
3440
  exports.billingPlanSection = billingPlanSection;
2626
3441
  exports.buildReportUrl = buildReportUrl;
2627
3442
  exports.canChatWithEmbedMentor = canChatWithEmbedMentor;
3443
+ exports.cancelDeleteInstance = cancelDeleteInstance;
3444
+ exports.cancelDeleteSkill = cancelDeleteSkill;
3445
+ exports.cancelDisconnectInstance = cancelDisconnectInstance;
3446
+ exports.cancelNewInstanceDialog = cancelNewInstanceDialog;
2628
3447
  exports.checkAdminStatus = checkAdminStatus;
2629
3448
  exports.clearDateRangeFilter = clearDateRangeFilter;
3449
+ exports.clearInstanceSearch = clearInstanceSearch;
2630
3450
  exports.clickBackHome = clickBackHome;
2631
3451
  exports.clickBillingAddCredits = clickBillingAddCredits;
2632
3452
  exports.clickBillingManageBilling = clickBillingManageBilling;
@@ -2636,14 +3456,25 @@ exports.clickDownloadAgain = clickDownloadAgain;
2636
3456
  exports.clickManualDownloadLink = clickManualDownloadLink;
2637
3457
  exports.closeCreditBalanceDropdown = closeCreditBalanceDropdown;
2638
3458
  exports.closeWithEsc = closeWithEsc;
3459
+ exports.connectToInstance = connectToInstance;
2639
3460
  exports.createAuthSetup = createAuthSetup;
2640
3461
  exports.createEnvConfig = createEnvConfig;
3462
+ exports.createInstance = createInstance;
2641
3463
  exports.createPlaywrightConfig = createPlaywrightConfig;
3464
+ exports.createSkill = createSkill;
2642
3465
  exports.creditBalancePanel = creditBalancePanel;
2643
3466
  exports.creditBalancePlanBadge = creditBalancePlanBadge;
2644
3467
  exports.creditBalanceTrigger = creditBalanceTrigger;
2645
3468
  exports.deleteFirstMemory = deleteFirstMemory;
3469
+ exports.deleteInstance = deleteInstance;
2646
3470
  exports.deleteMemoryByContent = deleteMemoryByContent;
3471
+ exports.deleteSkill = deleteSkill;
3472
+ exports.disableSkill = disableSkill;
3473
+ exports.disconnectInstance = disconnectInstance;
3474
+ exports.editAgentPrompt = editAgentPrompt;
3475
+ exports.editInstance = editInstance;
3476
+ exports.editSkill = editSkill;
3477
+ exports.enableSkill = enableSkill;
2647
3478
  exports.expectBillingAutoRechargeSection = expectBillingAutoRechargeSection;
2648
3479
  exports.expectBillingCreditsSection = expectBillingCreditsSection;
2649
3480
  exports.expectBillingPlanSection = expectBillingPlanSection;
@@ -2672,10 +3503,15 @@ exports.getBillingPlanLabel = getBillingPlanLabel;
2672
3503
  exports.getBrowserKey = getBrowserKey;
2673
3504
  exports.getCreditBalancePlanLabel = getCreditBalancePlanLabel;
2674
3505
  exports.getCreditBalanceRemaining = getCreditBalanceRemaining;
3506
+ exports.getCurrentModel = getCurrentModel;
2675
3507
  exports.getCurrentTenantShowPaywall = getCurrentTenantShowPaywall;
3508
+ exports.getInstanceHealthLabel = getInstanceHealthLabel;
3509
+ exports.getInstanceRowCount = getInstanceRowCount;
3510
+ exports.getInstanceStatusLabel = getInstanceStatusLabel;
2676
3511
  exports.getMemoryCount = getMemoryCount;
2677
3512
  exports.getMentorIdFromUrl = getMentorIdFromUrl;
2678
3513
  exports.getPaginationInfo = getPaginationInfo;
3514
+ exports.getSkillRowCount = getSkillRowCount;
2679
3515
  exports.goToFirstPage = goToFirstPage;
2680
3516
  exports.goToLastPage = goToLastPage;
2681
3517
  exports.goToNextPage = goToNextPage;
@@ -2687,6 +3523,8 @@ exports.isJSON = isJSON;
2687
3523
  exports.isMemoryTabVisible = isMemoryTabVisible;
2688
3524
  exports.isOnFirstPage = isOnFirstPage;
2689
3525
  exports.isOnLastPage = isOnLastPage;
3526
+ exports.isSandboxTabVisible = isSandboxTabVisible;
3527
+ exports.isSkillEnabled = isSkillEnabled;
2690
3528
  exports.logger = logger;
2691
3529
  exports.loginWithEmailAndPassword = loginWithEmailAndPassword;
2692
3530
  exports.loginWithMicrosoftIdp = loginWithMicrosoftIdp;
@@ -2696,13 +3534,27 @@ exports.navigateToAuditLogAndWaitForData = navigateToAuditLogAndWaitForData;
2696
3534
  exports.navigateToDataReports = navigateToDataReports;
2697
3535
  exports.navigateToReportDownload = navigateToReportDownload;
2698
3536
  exports.openAddMemoryDialog = openAddMemoryDialog;
3537
+ exports.openAgentPromptEditModal = openAgentPromptEditModal;
2699
3538
  exports.openCreditBalanceDropdown = openCreditBalanceDropdown;
3539
+ exports.openEditInstanceDialog = openEditInstanceDialog;
3540
+ exports.openEditSkillDialog = openEditSkillDialog;
3541
+ exports.openInstanceActionsMenu = openInstanceActionsMenu;
3542
+ exports.openLLMProviderPicker = openLLMProviderPicker;
3543
+ exports.openNewInstanceDialog = openNewInstanceDialog;
3544
+ exports.openNewSkillDialog = openNewSkillDialog;
3545
+ exports.openSkillActionsMenu = openSkillActionsMenu;
2700
3546
  exports.parseReportUrlParams = parseReportUrlParams;
3547
+ exports.pushConfiguration = pushConfiguration;
2701
3548
  exports.reliableClick = reliableClick;
2702
3549
  exports.reliableFill = reliableFill;
2703
3550
  exports.retry = retry;
3551
+ exports.runConnectedInstanceChecks = runConnectedInstanceChecks;
3552
+ exports.runInstanceChecks = runInstanceChecks;
2704
3553
  exports.safeWaitForURL = safeWaitForURL;
3554
+ exports.searchInstances = searchInstances;
2705
3555
  exports.selectDateFromCalendar = selectDateFromCalendar;
3556
+ exports.selectLLMModel = selectLLMModel;
3557
+ exports.setupSandboxInstance = setupSandboxInstance;
2706
3558
  exports.shouldAddNewRowWhenClickingAddRowButton = shouldAddNewRowWhenClickingAddRowButton;
2707
3559
  exports.shouldAllowEditingCellValuesInCSVEditor = shouldAllowEditingCellValuesInCSVEditor;
2708
3560
  exports.shouldCancelCombiningReports = shouldCancelCombiningReports;
@@ -2721,23 +3573,36 @@ exports.shouldShowCombiningReportsDialog = shouldShowCombiningReportsDialog;
2721
3573
  exports.shouldVerifyCSVEditorDialogAccessibility = shouldVerifyCSVEditorDialogAccessibility;
2722
3574
  exports.signUpWithEmailAndPassword = signUpWithEmailAndPassword;
2723
3575
  exports.switchToMemoryTab = switchToMemoryTab;
3576
+ exports.switchToSandboxTab = switchToSandboxTab;
3577
+ exports.switchToSkillsTab = switchToSkillsTab;
3578
+ exports.teardownSandboxInstance = teardownSandboxInstance;
2724
3579
  exports.test = test;
3580
+ exports.toggleAutoPush = toggleAutoPush;
2725
3581
  exports.toggleMemorySwitch = toggleMemorySwitch;
3582
+ exports.toggleSkill = toggleSkill;
3583
+ exports.verifyAgentConfigPromptsVisible = verifyAgentConfigPromptsVisible;
2726
3584
  exports.verifyAuditLogEmptyState = verifyAuditLogEmptyState;
2727
3585
  exports.verifyAuditLogEntryStructure = verifyAuditLogEntryStructure;
2728
3586
  exports.verifyAuditLogGenericError = verifyAuditLogGenericError;
2729
3587
  exports.verifyAuditLogLoading = verifyAuditLogLoading;
2730
3588
  exports.verifyAuditLogPermissionError = verifyAuditLogPermissionError;
2731
3589
  exports.verifyAuditLogTableVisible = verifyAuditLogTableVisible;
3590
+ exports.verifyConnectDisabledForUnhealthy = verifyConnectDisabledForUnhealthy;
3591
+ exports.verifyConnectedInstanceCard = verifyConnectedInstanceCard;
2732
3592
  exports.verifyCurrentPage = verifyCurrentPage;
2733
3593
  exports.verifyDonePhase = verifyDonePhase;
2734
3594
  exports.verifyDownloadingPhase = verifyDownloadingPhase;
2735
3595
  exports.verifyErrorPhase = verifyErrorPhase;
3596
+ exports.verifyInstanceTableEmpty = verifyInstanceTableEmpty;
3597
+ exports.verifyInstanceTableVisible = verifyInstanceTableVisible;
2736
3598
  exports.verifyMemoryExists = verifyMemoryExists;
2737
3599
  exports.verifyMemoryNotExists = verifyMemoryNotExists;
2738
3600
  exports.verifyMemoryTabMemoriesList = verifyMemoryTabMemoriesList;
2739
3601
  exports.verifyMemoryTabSettings = verifyMemoryTabSettings;
2740
3602
  exports.verifyPreparingPhase = verifyPreparingPhase;
3603
+ exports.verifySkillVisible = verifySkillVisible;
3604
+ exports.verifySkillsEmptyState = verifySkillsEmptyState;
3605
+ exports.verifySkillsTabVisible = verifySkillsTabVisible;
2741
3606
  exports.waitForAuditLogDataLoaded = waitForAuditLogDataLoaded;
2742
3607
  exports.waitForBillingTabReady = waitForBillingTabReady;
2743
3608
  exports.waitForCreditBalanceLoaded = waitForCreditBalanceLoaded;