From dde039b09a7df753e2dbf55c74734d72a3088644 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 15 Jun 2026 13:01:51 -0500 Subject: [PATCH 1/2] fix(routes): remove duplicate GET /api/sessions that shadowed session_list() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first registration called route_sessions() which searched for a 'session-start' label that no longer exists, returning an empty array on every list request and making the sidebar appear empty after restart. The second registration (dead code) called the correct session_list(). Removes route_sessions() entirely and the stale first route block. Also wires up session_delete() and session_update_patch() — both existed in sessions.el but had no HTTP routes — via new DELETE and PATCH blocks. --- routes.el | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/routes.el b/routes.el index f049987..9977aec 100644 --- a/routes.el +++ b/routes.el @@ -201,13 +201,6 @@ fn handle_dharma_recv(body: String) -> String { return "{\"error\":\"unknown event_type\",\"event_type\":\"" + eff_event + "\"}" } -fn route_sessions() -> String { - let results: String = engram_search_json("session-start", 20) - if str_eq(results, "") { return "[]" } - if str_eq(results, "[]") { return "[]" } - return results -} - // --------------------------------------------------------------------------- // MCP Connectors proxy — thin pass-through to neuron-connectd on :7771. // The UI talks to ONE origin (the soul); all MCP/config complexity lives in @@ -272,9 +265,6 @@ fn handle_request(method: String, path: String, body: String) -> String { if str_eq(clean, "/health") { return route_health() } - if str_eq(clean, "/api/sessions") { - return route_sessions() - } if str_eq(clean, "/lineage") { return route_lineage() } @@ -548,5 +538,31 @@ fn handle_request(method: String, path: String, body: String) -> String { return err_404(clean) } + if str_eq(method, "DELETE") { + // DELETE /api/sessions/:id — delete a session and its history + if str_starts_with(clean, "/api/sessions/") { + let del_after: String = str_slice(clean, 14, str_len(clean)) + let del_slash: Int = str_index_of(del_after, "/") + let del_id: String = if del_slash < 0 { del_after } else { str_slice(del_after, 0, del_slash) } + if !str_eq(del_id, "") { + return session_delete(del_id) + } + } + return err_404(clean) + } + + if str_eq(method, "PATCH") { + // PATCH /api/sessions/:id — update session title and/or folder + if str_starts_with(clean, "/api/sessions/") { + let patch_after: String = str_slice(clean, 14, str_len(clean)) + let patch_slash: Int = str_index_of(patch_after, "/") + let patch_id: String = if patch_slash < 0 { patch_after } else { str_slice(patch_after, 0, patch_slash) } + if !str_eq(patch_id, "") { + return session_update_patch(patch_id, body) + } + } + return err_404(clean) + } + return err_405(method, clean) } From 644d9915bfeee091df716e2e1d2407f031087345 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 15 Jun 2026 13:04:51 -0500 Subject: [PATCH 2/2] fix(chat): store bridge messages/tools as raw JSON to prevent double-escape corruption on agentic_resume bridge_save was wrapping messages and tools_json with json_safe() before storing them as string fields. Since both are already well-formed JSON arrays containing double quotes, json_safe added a second escape layer. agentic_resume then called json_get() which stripped only one layer, leaving the messages array corrupted before it was passed back into agentic_loop. Fix: store messages as messages_raw and tools_json as tools_raw as inline raw JSON values (unquoted), and read them back with json_get_raw. Backward compatibility: fall back to the old string-escaped fields if the raw fields are absent, so sessions saved before this fix can still be resumed. Also fixes write_file returning a pre-escaped literal instead of calling json_safe consistently with every other tool result. --- chat.el | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/chat.el b/chat.el index 3e7f903..858ccea 100644 --- a/chat.el +++ b/chat.el @@ -387,7 +387,7 @@ fn dispatch_tool(tool_name: String, tool_input: String) -> String { let path: String = json_get(tool_input, "path") let content: String = json_get(tool_input, "content") fs_write(path, content) - return "{\\\"ok\\\":true}" + return json_safe("{\"ok\":true}") } if str_eq(tool_name, "web_get") { let url: String = json_get(tool_input, "url") @@ -766,10 +766,14 @@ fn agentic_loop(session_id: String, model: String, safe_sys: String, tools_json: // stored `messages` already includes the assistant turn that requested the tool, so // resume just appends the client's tool_result for `tool_use_id`. fn bridge_save(session_id: String, model: String, safe_sys: String, tools_json: String, messages: String, tools_log: String, tool_use_id: String) -> Bool { + // messages and tools_json are already well-formed JSON arrays; embed them as raw + // JSON values (not string-escaped) so the round-trip through state_get/json_get_raw + // never corrupts nested quotes. Scalar strings (model, safe_sys, tools_log, + // tool_use_id) stay as string fields via json_safe as before. let blob: String = "{\"model\":\"" + json_safe(model) + "\"" + ",\"safe_sys\":\"" + json_safe(safe_sys) + "\"" - + ",\"tools_json\":\"" + json_safe(tools_json) + "\"" - + ",\"messages\":\"" + json_safe(messages) + "\"" + + ",\"messages_raw\":" + messages + + ",\"tools_raw\":" + tools_json + ",\"tools_log\":\"" + json_safe(tools_log) + "\"" + ",\"tool_use_id\":\"" + json_safe(tool_use_id) + "\"}" state_set("mcp_bridge:" + session_id, blob) @@ -789,8 +793,12 @@ fn agentic_resume(session_id: String, tool_use_id: String, content: String) -> S let model: String = json_get(blob, "model") let safe_sys: String = json_get(blob, "safe_sys") - let tools_json: String = json_get(blob, "tools_json") - let messages: String = json_get(blob, "messages") + // messages_raw and tools_raw are embedded as raw JSON (not string-escaped); + // fall back to legacy string-escaped fields for sessions saved before this fix. + let messages: String = json_get_raw(blob, "messages_raw") + let messages: String = if str_eq(messages, "") { json_get(blob, "messages") } else { messages } + let tools_json: String = json_get_raw(blob, "tools_raw") + let tools_json: String = if str_eq(tools_json, "") { json_get(blob, "tools_json") } else { tools_json } let tools_log: String = json_get(blob, "tools_log") let saved_use_id: String = json_get(blob, "tool_use_id")