|
|
@@ -1393,6 +1393,218 @@ def test_static_chat_agent_prompt_is_a_single_sortable_list_with_event_instructi
|
|
|
assert "promptDialog" not in js
|
|
|
|
|
|
|
|
|
+def test_static_event_prompt_refresh_uses_unified_tool_mode_state_path():
|
|
|
+ js = Path("src/agent_lab/presentation/static/app.js").read_text()
|
|
|
+ render_tools = js[
|
|
|
+ js.index("function renderTools(") :
|
|
|
+ js.index("function formatRequiredParameters(")
|
|
|
+ ]
|
|
|
+ set_all_tools = js[
|
|
|
+ js.index("function setAllTools(") :
|
|
|
+ js.index("function updateToolCount(")
|
|
|
+ ]
|
|
|
+ restore_snapshot = js[
|
|
|
+ js.index("function restoreWorkspaceSnapshot(") :
|
|
|
+ js.index("function updateToolModeHelp(")
|
|
|
+ ]
|
|
|
+ apply_selected_tools = js[
|
|
|
+ js.index("function applySelectedTools(") :
|
|
|
+ js.index("function initializePromptList(")
|
|
|
+ ]
|
|
|
+ initialize_prompt_list = js[
|
|
|
+ js.index("function initializePromptList(") :
|
|
|
+ js.index("function buildPromptItems(")
|
|
|
+ ]
|
|
|
+ refresh_tool_mode = js[
|
|
|
+ js.index("function refreshToolModeState(") :
|
|
|
+ js.index("function updateToolModeHelp(")
|
|
|
+ ]
|
|
|
+ remove_generated_prompts = js[
|
|
|
+ js.index("function removeGeneratedEventPromptItems(") :
|
|
|
+ js.index("function ensureEventPromptItem(")
|
|
|
+ ]
|
|
|
+
|
|
|
+ assert (
|
|
|
+ 'toolInvocationMode.addEventListener("change", refreshToolModeState);'
|
|
|
+ in js
|
|
|
+ )
|
|
|
+ assert render_tools.count("refreshToolModeState();") == 3
|
|
|
+ assert "updateEventPromptItem(" not in render_tools
|
|
|
+ assert "refreshToolModeState();" in set_all_tools
|
|
|
+ assert "updateEventPromptItem(" not in set_all_tools
|
|
|
+ assert "refreshToolModeState();" in restore_snapshot
|
|
|
+ assert "refreshToolModeState();" in apply_selected_tools
|
|
|
+ assert "refreshToolModeState();" in initialize_prompt_list
|
|
|
+ assert js.count("updateEventPromptItem(") == 2
|
|
|
+ assert "removeGeneratedEventPromptItems();" in refresh_tool_mode
|
|
|
+ assert "if (!isChatAgentTools)" in refresh_tool_mode
|
|
|
+ assert "updateEventPromptItem(availableTools);" in refresh_tool_mode
|
|
|
+ assert 'querySelectorAll(".prompt-item")' in remove_generated_prompts
|
|
|
+ assert 'item.dataset.promptKind === "event_agent_system"' in remove_generated_prompts
|
|
|
+ assert "isGeneratedEventPromptContent(content)" in remove_generated_prompts
|
|
|
+ assert "item.remove();" in remove_generated_prompts
|
|
|
+ assert 'textarea.readOnly = promptItem.prompt_kind === "event_agent_system"' in js
|
|
|
+
|
|
|
+
|
|
|
+def test_static_prompt_restore_discards_generated_event_prompt_derived_state():
|
|
|
+ js = Path("src/agent_lab/presentation/static/app.js").read_text()
|
|
|
+ ordinary = "Keep this custom prompt with an <agent_events> example."
|
|
|
+ exact_name_docs = (
|
|
|
+ "Use exact event names only, one per line. Do not include parameters."
|
|
|
+ )
|
|
|
+
|
|
|
+ assert "function isGeneratedEventPromptContent(" in js
|
|
|
+ normalization = js[
|
|
|
+ js.index("function normalizePromptItems(") :
|
|
|
+ js.index("function buildAvailableEventsPrompt(")
|
|
|
+ ]
|
|
|
+ result = _run_node_json(
|
|
|
+ "\n".join(
|
|
|
+ [
|
|
|
+ normalization,
|
|
|
+ """
|
|
|
+const generated = [
|
|
|
+ "You may request EventAgent work using this text protocol.",
|
|
|
+ "Available events:",
|
|
|
+ "- handoff_note: Handoff a note",
|
|
|
+ "First write the user-facing reply normally. If events are needed, append this block after the visible reply:",
|
|
|
+ "<agent_events>",
|
|
|
+ "handoff_note",
|
|
|
+ "</agent_events>",
|
|
|
+ "Use exact event names only, one per line. Do not include parameters.",
|
|
|
+].join("\\n");
|
|
|
+const ordinary = "Keep this custom prompt with an <agent_events> example.";
|
|
|
+const exactNameDocs = "Use exact event names only, one per line. Do not include parameters.";
|
|
|
+console.log(JSON.stringify({
|
|
|
+ matches: [
|
|
|
+ isGeneratedEventPromptContent(generated),
|
|
|
+ isGeneratedEventPromptContent(ordinary),
|
|
|
+ isGeneratedEventPromptContent(exactNameDocs),
|
|
|
+ ],
|
|
|
+ normalized: normalizePromptItems([
|
|
|
+ { prompt_kind: "event_agent_system", role: "system", content: generated },
|
|
|
+ generated,
|
|
|
+ ordinary,
|
|
|
+ { prompt_kind: "custom", role: "system", content: exactNameDocs },
|
|
|
+ ]),
|
|
|
+ empty: normalizePromptItems([]),
|
|
|
+}));
|
|
|
+""",
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ assert result["matches"] == [True, False, False]
|
|
|
+ assert result["normalized"] == [
|
|
|
+ {"prompt_kind": "custom", "role": "system", "content": ordinary},
|
|
|
+ {"prompt_kind": "custom", "role": "system", "content": exact_name_docs},
|
|
|
+ ]
|
|
|
+ assert result["empty"] == [
|
|
|
+ {"prompt_kind": "custom", "role": "system", "content": ""}
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+def test_static_event_prompt_is_excluded_from_chat_agent_tools_payload():
|
|
|
+ js = Path("src/agent_lab/presentation/static/app.js").read_text()
|
|
|
+ generated = "\n".join(
|
|
|
+ [
|
|
|
+ "You may request EventAgent work using this text protocol.",
|
|
|
+ "Available events:",
|
|
|
+ "- handoff_note: Handoff a note",
|
|
|
+ "<agent_events>",
|
|
|
+ "handoff_note",
|
|
|
+ "</agent_events>",
|
|
|
+ "Use exact event names only, one per line. Do not include parameters.",
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ prompt_collection = js[
|
|
|
+ js.index("function buildPromptItems(") :
|
|
|
+ js.index("function normalizePromptItems(")
|
|
|
+ ]
|
|
|
+ generated_matcher = js[
|
|
|
+ js.index("function isGeneratedEventPromptContent(") :
|
|
|
+ js.index("function buildAvailableEventsPrompt(")
|
|
|
+ ]
|
|
|
+ result = _run_node_json(
|
|
|
+ "\n".join(
|
|
|
+ [
|
|
|
+ prompt_collection,
|
|
|
+ generated_matcher,
|
|
|
+ """
|
|
|
+const generated = [
|
|
|
+ "You may request EventAgent work using this text protocol.",
|
|
|
+ "Available events:",
|
|
|
+ "- handoff_note: Handoff a note",
|
|
|
+ "<agent_events>",
|
|
|
+ "handoff_note",
|
|
|
+ "</agent_events>",
|
|
|
+ "Use exact event names only, one per line. Do not include parameters.",
|
|
|
+].join("\\n");
|
|
|
+function promptItem(promptKind, content) {
|
|
|
+ return {
|
|
|
+ dataset: { promptKind, promptRole: "system" },
|
|
|
+ querySelector() {
|
|
|
+ return { value: content };
|
|
|
+ },
|
|
|
+ };
|
|
|
+}
|
|
|
+const systemPrompts = {
|
|
|
+ querySelectorAll() {
|
|
|
+ return [
|
|
|
+ promptItem("custom", "ordinary custom"),
|
|
|
+ promptItem("event_agent_system", generated),
|
|
|
+ promptItem("custom", generated),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+};
|
|
|
+const toolInvocationMode = { value: "chat_agent_tools" };
|
|
|
+const chatAgentTools = collectSystemPrompts();
|
|
|
+toolInvocationMode.value = "dual_agent";
|
|
|
+const dualAgent = collectSystemPrompts();
|
|
|
+console.log(JSON.stringify({ chatAgentTools, dualAgent }));
|
|
|
+""",
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ assert result["chatAgentTools"] == ["ordinary custom"]
|
|
|
+ assert result["dualAgent"] == ["ordinary custom", generated, generated]
|
|
|
+
|
|
|
+
|
|
|
+def test_static_tool_mode_disables_only_event_agent_model_settings_group():
|
|
|
+ html = Path("src/agent_lab/presentation/static/index.html").read_text()
|
|
|
+ js = Path("src/agent_lab/presentation/static/app.js").read_text()
|
|
|
+ model_settings_start = html.index('id="event-agent-model-settings"')
|
|
|
+ shared_settings_start = html.index('id="max-event-loops"')
|
|
|
+ model_settings_html = html[model_settings_start:shared_settings_start]
|
|
|
+ refresh_tool_mode = js[
|
|
|
+ js.index("function refreshToolModeState(") :
|
|
|
+ js.index("function updateToolModeHelp(")
|
|
|
+ ]
|
|
|
+
|
|
|
+ for control_id in [
|
|
|
+ "event-model",
|
|
|
+ "event-temperature",
|
|
|
+ "event-max-tokens",
|
|
|
+ "event-system-prompt",
|
|
|
+ "event-thinking-disabled",
|
|
|
+ "event-enable-search",
|
|
|
+ "event-forced-search",
|
|
|
+ ]:
|
|
|
+ assert f'id="{control_id}"' in model_settings_html
|
|
|
+
|
|
|
+ for control_id in [
|
|
|
+ "max-event-loops",
|
|
|
+ "max-parallel-events",
|
|
|
+ "batch-timeout-seconds",
|
|
|
+ ]:
|
|
|
+ assert f'id="{control_id}"' not in model_settings_html
|
|
|
+
|
|
|
+ assert 'document.querySelector("#event-agent-model-settings")' in js
|
|
|
+ assert '.querySelectorAll("input, textarea, select, button")' in refresh_tool_mode
|
|
|
+ assert "control.disabled = isChatAgentTools;" in refresh_tool_mode
|
|
|
+
|
|
|
+
|
|
|
def test_static_app_script_is_versioned_and_click_binding_is_guarded():
|
|
|
html = Path("src/agent_lab/presentation/static/index.html").read_text()
|
|
|
js = Path("src/agent_lab/presentation/static/app.js").read_text()
|
|
|
@@ -1496,7 +1708,7 @@ def test_static_tool_mode_config_roundtrips_request_snapshot_and_session_restore
|
|
|
assert 'snapshot.tool_invocation_mode || "dual_agent"' in restore_snapshot
|
|
|
assert 'eventAgent.max_parallel_events ?? 4' in restore_snapshot
|
|
|
assert 'eventAgent.batch_timeout_seconds ?? 15' in restore_snapshot
|
|
|
- assert "updateToolModeHelp();" in restore_snapshot
|
|
|
+ assert "refreshToolModeState();" in restore_snapshot
|
|
|
assert '`/api/sessions/${sessionId}`' in load_replay
|
|
|
assert "const [session, messages, audit, usage] = await Promise.all([" in load_replay
|
|
|
assert "restoreWorkspaceSnapshot(session.config || {});" in load_replay
|