Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/features/agents/compaction.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ test.describe('History Compaction', () => {
await expect(page.locator('.assistant-content').last()).toContainText('world', { timeout: 10000 })

// Open debug dialog and go to Trace tab
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Trace/ }).click()

// Verify a compaction trace entry exists
Expand Down
4 changes: 2 additions & 2 deletions tests/features/chat-mcp/chat-mcp.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test.describe('Chat MCP UI', () => {
await goToWithAuth('/agents/_dev/chat-mcp', 'test-standalone1')

// Wait for tools to be discovered via BroadcastChannel before sending a message
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()
await expect(page.getByText('set_data')).toBeVisible({ timeout: 5000 })
await page.getByRole('button', { name: /Close|Fermer/ }).click()
Expand Down Expand Up @@ -67,7 +67,7 @@ test.describe('Chat MCP UI', () => {
await goToWithAuth('/agents/_dev/chat-mcp', 'test-standalone1')

// Open the debug dialog and go to the tools tab
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()
await expect(page.getByText('set_data')).toBeVisible({ timeout: 5000 })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const settingsData = {
}

async function waitForToolsReady (page: import('@playwright/test').Page, toolName: string, locateAsText = false) {
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()
const debugContent = page.locator('.v-dialog .v-window-item--active')
if (locateAsText) {
Expand Down Expand Up @@ -87,7 +87,7 @@ test.describe('Advanced Sub-Agent Scenarios', () => {
await expect(subAgentPanel.getByText('Analysis complete')).toBeVisible({ timeout: 10000 })

// Verify via trace that both tools were actually called
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Trace/ }).click()
const tracePanel = page.locator('.v-dialog .v-expansion-panels').last()
const subAgentEntry = tracePanel.locator('.v-expansion-panel', { hasText: /data_analyst/i })
Expand Down Expand Up @@ -149,7 +149,7 @@ test.describe('Advanced Sub-Agent Scenarios', () => {
await expect(page.getByPlaceholder('Type your message...')).toBeEnabled({ timeout: 15000 })

// Open debug dialog → Trace tab
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Trace/ }).click()

// A sub-agent trace entry should exist
Expand Down
4 changes: 2 additions & 2 deletions tests/features/chat-subagent/chat-subagent.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const settingsData = {
* Returns a locator scoped to the debug dialog's expansion panels.
*/
async function waitForToolsReady (page: import('@playwright/test').Page, toolName: string, locateAsText = false) {
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()
const debugContent = page.locator('.v-dialog .v-window-item--active')
if (locateAsText) {
Expand All @@ -61,7 +61,7 @@ test.describe('Chat Sub-Agent UI', () => {
test('All tools are discovered via MCP', async ({ page, goToWithAuth }) => {
await goToWithAuth('/agents/_dev/chat-subagent', 'test-standalone1')

await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()

// All tools + sub-agent should appear in the debug dialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ test.describe('Chat system prompt transmission', () => {
expect(frame.url()).not.toContain('SYSPROMPT_E2E_MARKER')

// Open the debug dialog (default tab is "System Prompt") and verify the prompt was applied
await frame.getByRole('button', { name: /Info|Informations/ }).click()
await frame.getByRole('button', { name: /Settings|Paramètres/ }).click()
await expect(frame.locator('.v-dialog')).toContainText('SYSPROMPT_E2E_MARKER', { timeout: 5000 })
})

Expand All @@ -79,7 +79,7 @@ test.describe('Chat system prompt transmission', () => {

await expect(page.getByPlaceholder('Type your message...')).toBeVisible({ timeout: 15000 })

await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await expect(page.locator('.v-dialog')).toContainText('SYSPROMPT_E2E_MARKER', { timeout: 5000 })
})
})
2 changes: 1 addition & 1 deletion tests/features/moderation/3.moderation.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test.describe('Moderation E2E', () => {
await expect(page.getByText("This request can't be processed as it falls outside what this assistant is meant to help with.")).toBeVisible({ timeout: 15000 })

// Open the info dialog and go to the Trace tab
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Trace/ }).click()

const tracePanel = page.locator('.v-dialog .v-expansion-panels').last()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ test.describe('Tool exploration E2E', () => {
await goToWithAuth('/agents/_dev/chat-subagent', 'test-standalone1')

// Enable exploration mode (requires debug=true which is set on the page)
await page.evaluate(() => sessionStorage.setItem('agent-chat-explore', '1'))
await page.evaluate(() => localStorage.setItem('agent-chat-explore', '1'))
await page.reload()

// Wait for the input to be ready
Expand Down Expand Up @@ -79,11 +79,11 @@ test.describe('Tool exploration E2E', () => {
test('without exploration mode, set_display is called directly and updates the output area', async ({ page, goToWithAuth }) => {
await goToWithAuth('/agents/_dev/chat-subagent', 'test-standalone1')

// Exploration mode is NOT enabled (no sessionStorage['agent-chat-explore'])
// Exploration mode is NOT enabled (no localStorage['agent-chat-explore'])

// Wait for tools to be registered via MCP before sending – mirrors the waitForToolsReady
// pattern from chat-subagent.e2e.spec.ts (avoids the race between MCP setup and sendMessage)
await page.getByRole('button', { name: /Info/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Tools/ }).click()
await expect(
page.locator('.v-dialog .v-window-item--active').getByRole('button', { name: 'set_display' })
Expand Down
4 changes: 2 additions & 2 deletions tests/features/trace-debug/trace-debug.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ test.describe('Trace Debug Dialog', () => {
await page.reload()

// Wait for tools to be ready
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Outils|Tools/ }).click()
const debugPanels = page.locator('.v-dialog .v-expansion-panels')
await expect(debugPanels.getByRole('button', { name: 'set_display' })).toBeVisible({ timeout: 5000 })
Expand All @@ -55,7 +55,7 @@ test.describe('Trace Debug Dialog', () => {
await expect(page.getByLabel('Output')).toHaveValue('trace test', { timeout: 15000 })

// Open debug dialog and go to Trace tab
await page.getByRole('button', { name: /Info|Informations/ }).click()
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()
await page.getByRole('tab', { name: /Trace/ }).click()

// Verify trace entries exist
Expand Down
6 changes: 3 additions & 3 deletions tests/features/trace-review/trace-review.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Scenario:
* 1. Send a chat message (always-on tracing records it).
* 2. Download the trace JSON from the Info dialog's Trace tab.
* 2. Download the trace JSON from the Settings dialog's Trace tab.
* 3. Navigate to the admin trace-review page.
* 4. Upload the downloaded trace file.
* 5. Verify the TraceView populated.
Expand Down Expand Up @@ -58,8 +58,8 @@ test.describe('Trace review flow', () => {
await page.getByRole('button', { name: 'Send' }).click()
await expect(page.locator('.assistant-content').last()).toContainText('world', { timeout: 15000 })

// Step 3: Open the Info dialog (admin-only button)
await page.getByRole('button', { name: 'Info' }).click()
// Step 3: Open the Settings dialog (admin-only button)
await page.getByRole('button', { name: /Settings|Paramètres/ }).click()

// Step 4: Navigate to the Trace tab in the dialog
await page.getByRole('tab', { name: /Trace/ }).click()
Expand Down
17 changes: 15 additions & 2 deletions ui/src/components/AgentChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
:is-admin="isAdmin"
:account-type="accountType"
:account-id="accountId"
:tool-exploration="explorationEnabled"
@update:tool-exploration="handleToolExploration"
/>
</v-card>
</template>
Expand Down Expand Up @@ -143,7 +145,9 @@ const finalSystemPrompt = computed(() => {
return parts.join(' ')
})

const explorationEnabled = props.isAdmin && sessionStorage.getItem('agent-chat-explore') === '1'
// Experimental tool-exploration mode: admin-only opt-in, persisted in localStorage
// and toggled from the debug dialog's Settings tab.
const explorationEnabled = ref(!!props.isAdmin && localStorage.getItem('agent-chat-explore') === '1')
const recorder = new SessionRecorder()
recorder.setSystemPrompt(finalSystemPrompt.value)

Expand All @@ -154,7 +158,7 @@ const chatResult = useAgentChat({
initialMessages: props.initialMessages,
recorder,
refusalMessage: t('moderationRefusal'),
toolExploration: explorationEnabled
toolExploration: explorationEnabled.value
})

if (!chatResult) {
Expand Down Expand Up @@ -263,6 +267,15 @@ function startActionSession (visiblePrompt: string, hiddenContext: string) {
chat.sendMessage(visiblePrompt, { hiddenContext })
}

function handleToolExploration (enabled: boolean) {
explorationEnabled.value = enabled
if (enabled) localStorage.setItem('agent-chat-explore', '1')
else localStorage.removeItem('agent-chat-explore')
chat.setToolExploration(enabled)
// Reset the conversation so the new tool set applies from a clean state.
handleReset()
}

function handleReset () {
chat.abort()
chat.reset(finalSystemPrompt.value)
Expand Down
27 changes: 27 additions & 0 deletions ui/src/components/agent-chat/AgentChatDebugDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<v-tab value="trace">
{{ t('trace') }}
</v-tab>
<v-tab value="settings">
{{ t('settings') }}
</v-tab>
</v-tabs>

<div
Expand Down Expand Up @@ -162,6 +165,22 @@
:recorder="recorder"
/>
</v-window-item>

<v-window-item value="settings">
<div class="pa-3">
<v-switch
:model-value="toolExploration"
color="primary"
density="compact"
hide-details
:label="t('toolExploration')"
@update:model-value="$emit('update:toolExploration', $event ?? false)"
/>
<p class="text-caption text-medium-emphasis mt-1">
{{ t('toolExplorationHint') }}
</p>
</div>
</v-window-item>
</v-window>
</v-card-text>
</v-card>
Expand All @@ -183,6 +202,9 @@ fr:
cacheWritten: cache écrit
download: Télécharger
openReview: Ouvrir l'analyse
settings: Paramètres
toolExploration: Exploration des outils (expérimental)
toolExplorationHint: "Masque les outils derrière un outil « explore_tools » que l'assistant appelle pour découvrir et activer les outils pertinents à la demande. Changer ce réglage réinitialise la conversation."
en:
close: Close
systemPrompt: System Prompt
Expand All @@ -197,6 +219,9 @@ en:
cacheWritten: cache write
download: Download
openReview: Open review
settings: Settings
toolExploration: Tool exploration (experimental)
toolExplorationHint: "Hides tools behind an 'explore_tools' tool the assistant calls to discover and enable relevant tools on demand. Changing this setting resets the conversation."
</i18n>

<script lang="ts" setup>
Expand All @@ -219,10 +244,12 @@ const props = defineProps<{
isAdmin?: boolean
accountType: string
accountId: string
toolExploration?: boolean
}>()

defineEmits<{
'update:modelValue': [value: boolean]
'update:toolExploration': [value: boolean]
}>()

const { t } = useI18n()
Expand Down
11 changes: 5 additions & 6 deletions ui/src/components/agent-chat/AgentChatHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
/>
<v-btn
v-if="isAdmin"
:icon="mdiInformationOutline"
:icon="mdiCog"
variant="flat"
color="success"
density="compact"
:title="t('info')"
:title="t('settings')"
class="ml-2"
@click="$emit('showDebug')"
/>
Expand All @@ -27,16 +26,16 @@

<i18n lang="yaml">
fr:
info: Informations
settings: Paramètres
reset: Réinitialiser la conversation
en:
info: Info
settings: Settings
reset: Reset conversation
</i18n>

<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
import { mdiInformationOutline, mdiRefresh } from '@mdi/js'
import { mdiCog, mdiRefresh } from '@mdi/js'

defineProps<{
isAdmin?: boolean
Expand Down
14 changes: 10 additions & 4 deletions ui/src/composables/use-agent-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ export function useAgentChat (options: UseAgentChatOptions) {
// 24000 is roughly equivalent to a 8k tokens context with 10-15 turns of dialogue an 2-3 tool calls
const COMPACTION_THRESHOLD = 24_000
const MODERATION_TIMEOUT_MS = 1500
const explorationEnabled = !!options.toolExploration
// Read live from `options` (like systemPrompt) so toggling exploration takes
// effect on the next turn; callers flip it via setToolExploration + reset.
const explorationEnabled = () => !!options.toolExploration
// Tools promoted to the callable set via explore_tools; persists across turns,
// cleared on compaction and reset. Read live by prepareStep.
let promotedTools = new Set<string>()
Expand Down Expand Up @@ -630,7 +632,7 @@ export function useAgentChat (options: UseAgentChatOptions) {
// explore_tools + sub-agent pseudo-tools + already-promoted tools per step.
let streamSystem = options.systemPrompt
let prepareStep: undefined | (() => { activeTools: string[] })
if (explorationEnabled) {
if (explorationEnabled()) {
const subAgentNames = Object.keys(subAgents)
const plainTools = { ...mainTools }
mainLLMTools[EXPLORE_TOOL_NAME] = createExploreTool({
Expand All @@ -646,7 +648,7 @@ export function useAgentChat (options: UseAgentChatOptions) {
})
}

debug('streaming with model=%s tools=%o exploration=%s', chatModelName, Object.keys(mainLLMTools), explorationEnabled)
debug('streaming with model=%s tools=%o exploration=%s', chatModelName, Object.keys(mainLLMTools), explorationEnabled())
const result = streamText({
model: provider.chat(chatModelName),
system: streamSystem,
Expand Down Expand Up @@ -771,7 +773,11 @@ export function useAgentChat (options: UseAgentChatOptions) {
options.systemPrompt = prompt
}

return { messages, status, error, tools, toolsVersion, resolvedPartition, sendMessage, abort, reset, setSystemPrompt, sessionUsage }
const setToolExploration = (enabled: boolean) => {
options.toolExploration = enabled
}

return { messages, status, error, tools, toolsVersion, resolvedPartition, sendMessage, abort, reset, setSystemPrompt, setToolExploration, sessionUsage }
}

export default useAgentChat
Loading