From a85d194a6c5904fae536c3e7d70729d24df65fd9 Mon Sep 17 00:00:00 2001 From: SahanUday Date: Thu, 16 Apr 2026 17:41:39 +0530 Subject: [PATCH 1/2] fix: Refactor canvas element handling in orchestrator and analyzer; update ChatPanel model options --- agent/orchestrator.jac | 18 ++++++-- agent/subagents/analyzer.jac | 43 ++++--------------- components/layout/ChatPanel.cl.jac | 2 +- hooks/useElements.cl.jac | 4 ++ hooks/useWebSocket.cl.jac | 7 +-- .../jasketch_mcp/canvas_tool_provider.jac | 2 +- 6 files changed, 32 insertions(+), 44 deletions(-) diff --git a/agent/orchestrator.jac b/agent/orchestrator.jac index 1c4d31e..fafb175 100644 --- a/agent/orchestrator.jac +++ b/agent/orchestrator.jac @@ -47,7 +47,6 @@ import from .subagents { DiagramComposer, DiagramEditor } -import from .subagents.analyzer { _set_analyst_provider } import from .subagents.synthesizer { _set_composer_provider } import from .subagents.refiner { _set_editor_provider } import from .llm_factory { create_llm } @@ -167,6 +166,7 @@ def _send_command(action: str, data: dict | None = None, session_id: str = "", t } payload: str = json.dumps({ + "role": "controller", "requestId": request_id, "sessionId": session_id, "action": action, @@ -258,7 +258,7 @@ walker AgentOrchestrator { can init_context with Root entry { # Find or create SessionContext for this session self._provider = CanvasToolProvider( - send_fn=lambda (action: str, data: dict[str, Any]) -> Any {_send_command(action, data, self.session_id);} + send_fn=lambda (action: str, data: dict[str, Any]) -> Any {return _send_command(action, data, self.session_id);} ); # Find or create session context (persists across turns) @@ -431,6 +431,7 @@ walker AgentOrchestrator { log(f"compose_diagram: response = {response[:80]}"); self._session_context.clear_diagram_blueprint(); + self._session_context.canvas_summary = "non-empty"; self._session_context.append_turn("composer", response); _send_ws(self.ws, {"type": "message", "sessionId": self.session_id, "content": response}, self.event_loop); _send_ws(self.ws, {"type": "done", "sessionId": self.session_id}, self.event_loop); @@ -443,11 +444,20 @@ walker AgentOrchestrator { if _diagram_analyst is None { return; } log("explain_canvas: starting DiagramAnalyst"); - _set_analyst_provider(self._provider); + + # Pre-fetch canvas elements directly (same pattern as _check_canvas_empty) + canvas_elements_json: str = '{"elements":[],"total":0}'; + try { + canvas_elements_json = self._provider.call_tool("get_canvas_elements", {}); + } except Exception as e { + log(f"explain_canvas: error fetching elements: {e}"); + } + log(f"explain_canvas: canvas_elements_json = {canvas_elements_json[:120]}"); response: str = _diagram_analyst.explain( self.message, - self._session_context.conversation_log + self._session_context.conversation_log, + canvas_elements_json ); log(f"explain_canvas: response = {response[:80]}"); diff --git a/agent/subagents/analyzer.jac b/agent/subagents/analyzer.jac index 706a5ae..2ec2d7d 100644 --- a/agent/subagents/analyzer.jac +++ b/agent/subagents/analyzer.jac @@ -1,6 +1,6 @@ """DiagramAnalyst — explains/describes the diagram on the canvas. -Fetches canvas elements via get_canvas_elements tool and provides analysis. +Canvas elements are pre-fetched by the orchestrator and passed as canvas_elements_json. Read-only agent: no modification tools. """ @@ -12,12 +12,11 @@ node DiagramAnalyst { has api_key: str = ""; has llm: Any = None; - def explain(message: str, conversation_log: str) -> str + def explain(message: str, conversation_log: str, canvas_elements_json: str) -> str by self.llm( - tools=[use_canvas_tool], - max_react_iterations=2, + tools=[], incl_info={ - "available_canvas_tools": [t for t in _analyst_provider.list_tools() if t["name"] in ANALYST_TOOLS], + "canvas_elements_json": canvas_elements_json, "conversation": conversation_log } ); @@ -26,10 +25,7 @@ node DiagramAnalyst { sem DiagramAnalyst.explain = """ Explain the JaSketch canvas diagram to the user. -Workflow: - 1. Call use_canvas_tool("get_canvas_elements", "{}") to fetch the current diagram - 2. Analyze the elements to understand the diagram structure - 3. Explain to the user what the diagram shows, its purpose, key components, and relationships +Canvas data is already provided in `canvas_elements_json` from context — parse it directly. Element Types and Key Fields (runtime format): - rectangle / circle / diamond: x, y, width, height, shapeText (label inside shape), color, fillColor, boundElements (list of connected arrow IDs) @@ -41,39 +37,16 @@ Element Types and Key Fields (runtime format): - freehand: x, y, points (free-drawn path) Your Task: - 1. Fetch canvas elements via use_canvas_tool("get_canvas_elements", "{}") + 1. Parse canvas_elements_json from incl_info to extract the "elements" array 2. Identify shapes (by type + shapeText labels) and connections (arrows via startBinding/endBinding elementIds) 3. Understand the overall diagram structure: what it represents, its purpose, layout 4. Explain clearly: what diagram this is, what it shows, key components, and how they relate +If canvas_elements_json is empty or has no elements, explain that the canvas is currently empty. + Read-only: explain only, do NOT attempt to modify the canvas. User message: {{message}} Respond now. """; - -# ── Tool binding (populated by orchestrator before each call) ────────────────── - -glob ANALYST_TOOLS: list[str] = ["get_canvas_elements"]; - -glob _analyst_provider: Any = None; - -def use_canvas_tool(name: str, arguments: str) -> str { - args: dict = {}; - try { - if isinstance(arguments, dict) { args = arguments; } - elif arguments { args = json.loads(arguments); } - } except Exception { args = {}; } - return _analyst_provider.call_tool(name, args); -} -sem use_canvas_tool = "Call a canvas tool by name. Only use tools listed in available_canvas_tools from context."; -sem use_canvas_tool.name = "Tool name — must exactly match one of the names in available_canvas_tools."; -sem use_canvas_tool.arguments = "JSON object string matching the inputSchema of the named tool. Build from the schema in available_canvas_tools."; - -# ── Provider injection (called by orchestrator) ──────────────────────────────── - -def _set_analyst_provider(provider: Any) -> None { - global _analyst_provider; - _analyst_provider = provider; -} diff --git a/components/layout/ChatPanel.cl.jac b/components/layout/ChatPanel.cl.jac index 43042e9..16ff258 100644 --- a/components/layout/ChatPanel.cl.jac +++ b/components/layout/ChatPanel.cl.jac @@ -222,7 +222,7 @@ def:pub ChatPanel(props: dict) -> JsxElement { modelOptions = [ {"id": "gpt-4o", "label": "GPT-4o"}, {"id": "gpt-4o-mini", "label": "GPT-4o Mini"}, - {"id": "gpt-5.4-mini", "label": "GPT-5.4 Mini"}, + {"id": "gpt-5-mini", "label": "GPT-5 Mini"}, {"id": "claude-sonnet-4-20250514", "label": "Claude Sonnet"}, {"id": "claude-haiku-4-20250414", "label": "Claude Haiku"}, {"id": "gemini/gemini-2.0-flash", "label": "Gemini Flash"}, diff --git a/hooks/useElements.cl.jac b/hooks/useElements.cl.jac index 41ea8b0..c093ef9 100644 --- a/hooks/useElements.cl.jac +++ b/hooks/useElements.cl.jac @@ -5,11 +5,13 @@ def:pub useElements() -> dict { historyRef = useRef([]); historyIndexRef = useRef(-1); loadedRef = useRef(False); + elementsRef = useRef([]); STORAGE_KEY = "jasketch_elements"; def setElementsInternal(newElements: list) -> None { elements = newElements; + elementsRef.current = newElements; # Strip non-serializable imageObj before saving to localStorage toStore = [ el.type == "image" and el.imageObj and { @@ -36,6 +38,7 @@ def:pub useElements() -> dict { mi = mi + 1; } elements = parsed; + elementsRef.current = parsed; pushHistory(parsed); } } @@ -194,6 +197,7 @@ def:pub useElements() -> dict { return { "elements": elements, + "elementsRef": elementsRef, "setElements": setElementsWithHistory, "addElement": addElement, "addMultipleElements": addMultipleElements, diff --git a/hooks/useWebSocket.cl.jac b/hooks/useWebSocket.cl.jac index e3b3887..000549a 100644 --- a/hooks/useWebSocket.cl.jac +++ b/hooks/useWebSocket.cl.jac @@ -35,7 +35,7 @@ def:pub useWebSocket(elementsHook: dict, viewportHook: dict, canvasRef: any, can } def sendResponse(ws: any, requestId: str, success: bool, data: any) -> None { - msg = {"requestId": requestId, "success": success, "data": data}; + msg = {"role": "canvas", "requestId": requestId, "success": success, "data": data}; ws.send(JSON.stringify(msg)); } @@ -70,7 +70,8 @@ def:pub useWebSocket(elementsHook: dict, viewportHook: dict, canvasRef: any, can } sendResponse(ws, requestId, True, {"ids": addedIds, "count": items.length, "action": "created"}); } elif action == "getElements" { - elems = hook.elements; + # Use elementsRef for synchronous read (avoids stale React state snapshot) + elems = (hook.elementsRef and hook.elementsRef.current) or hook.elements; result = []; for el in elems { if el and (not data.type or el.type == data.type) { @@ -208,8 +209,8 @@ def:pub useWebSocket(elementsHook: dict, viewportHook: dict, canvasRef: any, can sendError(ws, requestId, "Element not found: " + findTargetId); } } elif action == "setElements" { - hook.clearElements(); newElements = data.elements or []; + hook.clearElements(); hook.addMultipleElements(newElements); # Auto-fit viewport: compute bounding box of all elements, then zoom to fit with 60px padding diff --git a/mcp-server/jasketch_mcp/canvas_tool_provider.jac b/mcp-server/jasketch_mcp/canvas_tool_provider.jac index 380c6a4..38b6dbb 100644 --- a/mcp-server/jasketch_mcp/canvas_tool_provider.jac +++ b/mcp-server/jasketch_mcp/canvas_tool_provider.jac @@ -572,7 +572,7 @@ obj CanvasToolProvider { """Fetch all canvas elements with defaults stripped for token efficiency.""" def _get_canvas_elements(args: dict) -> str { result: Any = self._send("getElements", {}); - if not result or not result.get("elements") { + if not result or "elements" not in result { return json.dumps({"elements": [], "total": 0}, separators=(',', ':')); } # Strip {id, element} wrapper → plain element objects From cb361b9da9c66e6dc5614957217cd26275732a65 Mon Sep 17 00:00:00 2001 From: SahanUday Date: Thu, 16 Apr 2026 17:51:00 +0530 Subject: [PATCH 2/2] test --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 547c155..2b46c35 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A sketching and diagramming app built with [Jaclang](https://docs.jaseci.org/) a python -m venv .venv source .venv/bin/activate pip install jaclang jac-client jac-scale -jac start +jac start main.jac ``` ## Project Structure