mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page. The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows. Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths Confidence: high Scope-risk: moderate Reversibility: clean Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
255 lines
9.9 KiB
Bash
255 lines
9.9 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
|
|
if ! command -v agent-browser >/dev/null 2>&1; then
|
|
echo "agent-browser is required but was not found in PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
API_URL="${API_URL:-http://127.0.0.1:3457}"
|
|
WEB_URL="${WEB_URL:-http://127.0.0.1:5175/?serverUrl=http://127.0.0.1:3457}"
|
|
RUN_ID="$(date +%s)-$RANDOM"
|
|
SESSION_NAME="cc-haha-webui-e2e-${RUN_ID}"
|
|
ARTIFACT_DIR="$(mktemp -d "/tmp/cc-haha-webui-e2e-${RUN_ID}-XXXX")"
|
|
AB=(agent-browser --session "${SESSION_NAME}")
|
|
|
|
cleanup() {
|
|
local exit_code=$?
|
|
"${AB[@]}" close >/dev/null 2>&1 || true
|
|
echo "Artifacts kept at: ${ARTIFACT_DIR}" >&2
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
wait_for_text() {
|
|
local needle="$1"
|
|
if ! "${AB[@]}" wait "text=${needle}" >/dev/null 2>&1; then
|
|
echo "Timed out waiting for page text: ${needle}" >&2
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/failure-wait-$(echo "${needle}" | tr ' /' '__').png" >/dev/null 2>&1 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
press_escape() {
|
|
"${AB[@]}" press Escape >/dev/null 2>&1 || true
|
|
}
|
|
|
|
focus_composer() {
|
|
"${AB[@]}" click textarea >/dev/null 2>&1 || "${AB[@]}" focus textarea >/dev/null 2>&1 || true
|
|
}
|
|
|
|
submit_slash_command() {
|
|
local command="$1"
|
|
focus_composer
|
|
"${AB[@]}" fill textarea "${command}"
|
|
"${AB[@]}" press Enter
|
|
}
|
|
|
|
healthcheck() {
|
|
curl -fsS "${API_URL}/health" >/dev/null
|
|
}
|
|
|
|
select_plugin_targets() {
|
|
DETAIL_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
|
|
.plugins
|
|
| map(select((.componentCounts.commands + .componentCounts.agents + .componentCounts.hooks + .componentCounts.skills) > 0))
|
|
| sort_by(-(.componentCounts.commands + .componentCounts.agents + .componentCounts.hooks + .componentCounts.skills))
|
|
| .[0].id // empty
|
|
')"
|
|
|
|
ENABLED_SKILL_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
|
|
.plugins
|
|
| map(select(.enabled == true and .componentCounts.skills > 0))
|
|
| sort_by(-.componentCounts.skills)
|
|
| .[0].id // empty
|
|
')"
|
|
|
|
MCP_PLUGIN_ID="$(curl -fsS "${API_URL}/api/plugins" | jq -r '
|
|
.plugins
|
|
| map(select(.enabled == true and .componentCounts.mcpServers > 0))
|
|
| sort_by(-.componentCounts.mcpServers)
|
|
| .[0].id // empty
|
|
')"
|
|
|
|
if [[ -z "${DETAIL_PLUGIN_ID}" ]]; then
|
|
echo "No plugin with commands/agents/hooks/skills was found in current API data." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${ENABLED_SKILL_PLUGIN_ID}" ]]; then
|
|
echo "No enabled plugin with skills was found in current API data." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${MCP_PLUGIN_ID}" ]]; then
|
|
echo "No plugin with MCP servers was found in current API data." >&2
|
|
exit 1
|
|
fi
|
|
|
|
DETAIL_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.name')"
|
|
DETAIL_PLUGIN_ENABLED="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.enabled')"
|
|
DETAIL_COMMAND="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.commandEntries[0].name // empty')"
|
|
DETAIL_AGENT="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.agentEntries[0].name // empty')"
|
|
DETAIL_AGENT_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.agentEntries[0].displayName // .detail.agentEntries[0].name // empty')"
|
|
DETAIL_HOOK_EVENT="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.hookEntries[0].event // empty')"
|
|
DETAIL_HOOK_ACTION="$(curl -fsS "${API_URL}/api/plugins/detail?id=${DETAIL_PLUGIN_ID}" | jq -r '.detail.hookEntries[0].actions[0] // empty')"
|
|
DETAIL_SKILL_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].name // empty')"
|
|
DETAIL_SKILL_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].displayName // .detail.skillEntries[0].name // empty')"
|
|
DETAIL_SKILL_DESCRIPTION="$(curl -fsS --get --data-urlencode "source=plugin" --data-urlencode "name=${DETAIL_SKILL_NAME}" "${API_URL}/api/skills/detail" | jq -r '.detail.meta.description // empty')"
|
|
DETAIL_SKILL_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${ENABLED_SKILL_PLUGIN_ID}" | jq -r '.detail.name')"
|
|
|
|
MCP_PLUGIN_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.name')"
|
|
MCP_SERVER_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.mcpServerEntries[0].name // empty')"
|
|
MCP_SERVER_LABEL="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.mcpServerEntries[0].displayName // .detail.mcpServerEntries[0].name // empty')"
|
|
MCP_SKILL_NAME="$(curl -fsS "${API_URL}/api/plugins/detail?id=${MCP_PLUGIN_ID}" | jq -r '.detail.skillEntries[0].name // empty')"
|
|
}
|
|
|
|
open_plugin_detail() {
|
|
local plugin_name="$1"
|
|
local escaped_name="${plugin_name//\"/\\\"}"
|
|
if "${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes(\"${escaped_name}\")); if(!target) throw new Error('plugin button not found'); target.click();" >/dev/null 2>&1; then
|
|
wait_for_text "Bundled capabilities"
|
|
return 0
|
|
fi
|
|
|
|
echo "Failed to open plugin detail for: ${plugin_name}" >&2
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/failure-open-plugin-${plugin_name}.png" >/dev/null 2>&1 || true
|
|
return 1
|
|
}
|
|
|
|
go_back_to_plugin_list() {
|
|
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes('Back to list')); if(!target) throw new Error('back button not found'); target.click();" >/dev/null
|
|
wait_for_text "Browse installed plugins"
|
|
}
|
|
|
|
open_settings_sidebar_tab() {
|
|
local tab_name="$1"
|
|
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.trim()==='${tab_name}'); if(!target) throw new Error('settings tab not found: ${tab_name}'); target.click();" >/dev/null
|
|
}
|
|
|
|
click_visible_card_text() {
|
|
local text="$1"
|
|
local escaped_text="${text//\"/\\\"}"
|
|
"${AB[@]}" eval "const target=[...document.querySelectorAll('button')].find((node)=>node.textContent?.includes(\"${escaped_text}\")); if(!target) throw new Error('button not found: ${escaped_text}'); target.click();" >/dev/null
|
|
}
|
|
|
|
healthcheck
|
|
select_plugin_targets
|
|
|
|
echo "Using detail plugin: ${DETAIL_PLUGIN_NAME} (${DETAIL_PLUGIN_ID})"
|
|
echo "Using enabled skill plugin: ${DETAIL_SKILL_PLUGIN_NAME} (${ENABLED_SKILL_PLUGIN_ID})"
|
|
echo "Using MCP plugin: ${MCP_PLUGIN_NAME} (${MCP_PLUGIN_ID})"
|
|
|
|
"${AB[@]}" open "${WEB_URL}"
|
|
"${AB[@]}" wait --load networkidle
|
|
wait_for_text "Claude Code Haha"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/01-home.png" >/dev/null
|
|
|
|
# Always work from a fresh chat surface so slash-command behavior is deterministic.
|
|
"${AB[@]}" find role button click --name "New session"
|
|
"${AB[@]}" wait textarea >/dev/null
|
|
|
|
submit_slash_command "/mcp"
|
|
wait_for_text "Available MCP tools"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/02-mcp-panel.png" >/dev/null
|
|
press_escape
|
|
"${AB[@]}" wait 300 >/dev/null
|
|
|
|
submit_slash_command "/skills"
|
|
wait_for_text "Available skills"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/03-skills-panel.png" >/dev/null
|
|
press_escape
|
|
"${AB[@]}" wait 300 >/dev/null
|
|
|
|
submit_slash_command "/plugin"
|
|
wait_for_text "Browse installed plugins"
|
|
wait_for_text "Plugin Manager"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/04-plugins-list.png" >/dev/null
|
|
|
|
open_plugin_detail "${DETAIL_PLUGIN_NAME}"
|
|
wait_for_text "Commands"
|
|
wait_for_text "Agents"
|
|
wait_for_text "Hooks"
|
|
wait_for_text "Skills"
|
|
if [[ -n "${DETAIL_COMMAND}" ]]; then
|
|
wait_for_text "/${DETAIL_COMMAND}"
|
|
fi
|
|
if [[ -n "${DETAIL_AGENT}" ]]; then
|
|
wait_for_text "${DETAIL_AGENT}"
|
|
fi
|
|
if [[ -n "${DETAIL_HOOK_EVENT}" ]]; then
|
|
wait_for_text "${DETAIL_HOOK_EVENT}"
|
|
fi
|
|
if [[ -n "${DETAIL_HOOK_ACTION}" ]]; then
|
|
wait_for_text "${DETAIL_HOOK_ACTION}"
|
|
fi
|
|
if [[ -n "${DETAIL_SKILL_NAME}" ]]; then
|
|
wait_for_text "/${DETAIL_SKILL_NAME}"
|
|
fi
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/05-plugin-detail-main.png" >/dev/null
|
|
|
|
open_settings_sidebar_tab "Plugins"
|
|
wait_for_text "Plugin Detail"
|
|
go_back_to_plugin_list
|
|
open_plugin_detail "${DETAIL_SKILL_PLUGIN_NAME}"
|
|
|
|
if [[ -n "${DETAIL_SKILL_LABEL}" ]]; then
|
|
click_visible_card_text "${DETAIL_SKILL_LABEL}"
|
|
wait_for_text "Skill metadata"
|
|
wait_for_text "${DETAIL_SKILL_LABEL}"
|
|
if [[ -n "${DETAIL_SKILL_DESCRIPTION}" ]]; then
|
|
wait_for_text "${DETAIL_SKILL_DESCRIPTION}"
|
|
fi
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/06-skill-detail-from-plugin.png" >/dev/null
|
|
|
|
open_settings_sidebar_tab "Skills"
|
|
wait_for_text "Skill Browser"
|
|
wait_for_text "Plugin"
|
|
wait_for_text "${DETAIL_SKILL_LABEL}"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/07-skills-list-with-plugin-group.png" >/dev/null
|
|
fi
|
|
|
|
open_settings_sidebar_tab "Plugins"
|
|
wait_for_text "Plugin Detail"
|
|
|
|
if [[ "${DETAIL_PLUGIN_ENABLED}" == "true" && -n "${DETAIL_AGENT_LABEL}" ]]; then
|
|
click_visible_card_text "${DETAIL_AGENT_LABEL}"
|
|
wait_for_text "Agent Profile"
|
|
wait_for_text "${DETAIL_AGENT}"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/08-agent-detail-from-plugin.png" >/dev/null
|
|
|
|
open_settings_sidebar_tab "Agents"
|
|
wait_for_text "Agent Browser"
|
|
wait_for_text "Plugin"
|
|
wait_for_text "${DETAIL_AGENT}"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/09-agents-list-with-plugin-group.png" >/dev/null
|
|
fi
|
|
|
|
go_back_to_plugin_list
|
|
open_plugin_detail "${MCP_PLUGIN_NAME}"
|
|
wait_for_text "MCP servers"
|
|
if [[ -n "${MCP_SERVER_NAME}" ]]; then
|
|
wait_for_text "${MCP_SERVER_LABEL:-$MCP_SERVER_NAME}"
|
|
fi
|
|
if [[ -n "${MCP_SKILL_NAME}" ]]; then
|
|
wait_for_text "/${MCP_SKILL_NAME}"
|
|
fi
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/10-plugin-detail-mcp.png" >/dev/null
|
|
|
|
if [[ -n "${MCP_SERVER_LABEL}" ]]; then
|
|
click_visible_card_text "${MCP_SERVER_LABEL}"
|
|
wait_for_text "${MCP_SERVER_NAME}"
|
|
wait_for_text "Plugin"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/11-mcp-detail-from-plugin.png" >/dev/null
|
|
|
|
open_settings_sidebar_tab "MCP"
|
|
wait_for_text "MCP servers"
|
|
wait_for_text "Plugin"
|
|
wait_for_text "${MCP_SERVER_NAME}"
|
|
"${AB[@]}" screenshot "${ARTIFACT_DIR}/12-mcp-list-with-plugin-group.png" >/dev/null
|
|
fi
|
|
|
|
echo "agent-browser web UI regression passed"
|
|
echo "Artifacts: ${ARTIFACT_DIR}"
|