From 31ee4f112bb8e5e4e85575f96c3ca6ded7f5d4aa Mon Sep 17 00:00:00 2001 From: Henrique Tolentino Date: Tue, 16 Jun 2026 16:11:43 -0400 Subject: [PATCH] Improve code agent interactions and show jobs on the artifacts panel Signed-off-by: Henrique Tolentino --- .../src/nmp/studio/coding_agent_artifacts.py | 59 +++++++++++++++++ .../src/nmp/studio/coding_agent_mcp_tools.py | 16 ++++- .../studio/src/nmp/studio/coding_agents.py | 3 + .../studio/tests/unit/test_coding_agents.py | 65 +++++++++++++++++-- .../ClaudeCodeHistoryPanel.test.tsx | 30 +++++++++ .../agents/ClaudeCodeChatRoute/api.test.ts | 17 +++++ .../routes/agents/ClaudeCodeChatRoute/api.ts | 14 ++++ .../ClaudeCodeChatRoute/artifacts.test.ts | 23 ++++++- .../agents/ClaudeCodeChatRoute/artifacts.ts | 56 ++++++++++++++++ .../historyPanel/ArtifactSections.tsx | 58 ++++++++++++++++- .../historyPanel/ClaudeCodeArtifactsPane.tsx | 2 + .../historyPanel/helpers.ts | 1 + .../agents/ClaudeCodeChatRoute/types.ts | 8 +++ .../useClaudeCodeChatRuntime.test.ts | 1 + .../agents/ClaudeCodeChatRoute/util.test.ts | 6 +- 15 files changed, 349 insertions(+), 10 deletions(-) diff --git a/services/studio/src/nmp/studio/coding_agent_artifacts.py b/services/studio/src/nmp/studio/coding_agent_artifacts.py index f1815c2bd8..83d407360d 100644 --- a/services/studio/src/nmp/studio/coding_agent_artifacts.py +++ b/services/studio/src/nmp/studio/coding_agent_artifacts.py @@ -33,6 +33,15 @@ class ChatLinkArtifactResponse(BaseModel): href: str | None = None +class ChatJobArtifactResponse(BaseModel): + """A Studio job referenced during the chat.""" + + name: str + job_type: str | None = None + source: str | None = None + href: str | None = None + + class ChatArtifactsResponse(BaseModel): """Structured chat metadata shown in Studio's artifacts pane.""" @@ -44,6 +53,7 @@ class ChatArtifactsResponse(BaseModel): selections: list[ChatSelectionArtifactResponse] = Field(default_factory=list) files: list[ChatFileArtifactResponse] = Field(default_factory=list) links: list[ChatLinkArtifactResponse] = Field(default_factory=list) + jobs: list[ChatJobArtifactResponse] = Field(default_factory=list) tools: list[str] = Field(default_factory=list) @@ -247,6 +257,44 @@ def _append_link_artifact(artifacts: ChatArtifactsResponse, input_value: Any) -> artifacts.links.append(artifact) +def _upsert_job_artifact( + artifacts: ChatArtifactsResponse, + name: str, + job_type: str | None = None, + source: str | None = None, + href: str | None = None, +) -> None: + for index, job in enumerate(artifacts.jobs): + if job.name != name: + continue + artifacts.jobs[index] = ChatJobArtifactResponse( + name=name, + job_type=job_type or job.job_type, + source=source or job.source, + href=href or job.href, + ) + return + + artifacts.jobs.append(ChatJobArtifactResponse(name=name, job_type=job_type, source=source, href=href)) + + +def _append_job_artifact(artifacts: ChatArtifactsResponse, input_value: Any) -> None: + if not isinstance(input_value, dict): + return + + name = string_value(input_value.get("job_name")) or string_value(input_value.get("name")) + if not name: + return + + _upsert_job_artifact( + artifacts, + name=name, + job_type=string_value(input_value.get("job_type")) or string_value(input_value.get("type")), + source=string_value(input_value.get("source")), + href=string_value(input_value.get("href")) or string_value(input_value.get("url")), + ) + + def _normalize_spec_line(line: str) -> str: normalized = line.strip() normalized = re.sub(r"^#{1,6}\s+", "", normalized) @@ -320,6 +368,17 @@ def record_tool_artifacts( if tool_name == "studio_link" or tool_name.endswith("__studio_link"): _append_link_artifact(artifacts, input_value) + if isinstance(input_value, dict): + destination = ( + string_value(input_value.get("destination")) + or string_value(input_value.get("page")) + or string_value(input_value.get("resource_type")) + ) + if destination == "job": + _append_job_artifact(artifacts, input_value) + + if tool_name == "job_progress" or tool_name.endswith("__job_progress"): + _append_job_artifact(artifacts, input_value) def record_workspace_artifact(artifacts: ChatArtifactsResponse, content: str) -> None: diff --git a/services/studio/src/nmp/studio/coding_agent_mcp_tools.py b/services/studio/src/nmp/studio/coding_agent_mcp_tools.py index 15836fa306..d6d41be457 100644 --- a/services/studio/src/nmp/studio/coding_agent_mcp_tools.py +++ b/services/studio/src/nmp/studio/coding_agent_mcp_tools.py @@ -190,7 +190,21 @@ "'health/ready' checks unless the user explicitly asks you to. Assume services are up and " "go straight to the actual task." ), - ("Prefer NeMo Studio MCP tools and the nemo CLI over ad-hoc shell or filesystem commands. "), + ( + "Prefer NeMo Studio MCP tools and Studio views over CLI commands for user-facing " + "follow-up actions, navigation, inspection, and status/result review." + ), + ( + "Do not tell the user to run nemo CLI commands, shell commands, curl commands, or status " + "commands to inspect agents, jobs, evaluations, filesets, models, traces, logs, or results " + "when a Studio view, Studio link, or Studio progress card is available for the same purpose." + ), + ( + "Use CLI commands only to perform work that has no Studio UI equivalent, when the user " + "explicitly asks for CLI/debugging, or when you must gather data that Studio tools cannot " + "provide. For user-facing follow-up, prefer mcp__nemo_studio__studio_link and " + "mcp__nemo_studio__job_progress." + ), ( "When you need to prompt the user for input, use a Studio UI tool instead of writing a " "plain-text question whenever a suitable tool exists." diff --git a/services/studio/src/nmp/studio/coding_agents.py b/services/studio/src/nmp/studio/coding_agents.py index 04984d75e1..e87cc6264d 100644 --- a/services/studio/src/nmp/studio/coding_agents.py +++ b/services/studio/src/nmp/studio/coding_agents.py @@ -294,6 +294,9 @@ def _build_studio_system_prompt( "For finite choices that have no dedicated Studio picker (for example deployments, jobs, or next actions) and for yes/no or multiple-choice clarifications, use Claude Code's AskUserQuestion tool so Studio can render clickable options instead of asking the user to type.", "For AskUserQuestion, provide input shaped as {'questions': [{'header': '', 'question': '', 'options': [{'label': '