fix(chat): harden bridge_save/agentic_resume against empty and corrupt state
Neuron Soul CI / build (pull_request) Failing after 8m44s
Neuron Soul CI / build (pull_request) Failing after 8m44s
BLOCKER 1: use untyped reassignment (let x = ...) for the fallback bindings in agentic_resume instead of re-declaring typed let bindings (let x: Type = ...) for the same variable in the same scope. The typed form risks shadowing semantics that differ from the established pattern used everywhere else in the loop (e.g. agentic_loop line 720). BLOCKER 2: add empty-string guards in both bridge_save and agentic_resume. bridge_save now returns false without writing state if messages or tools_json is empty — preventing syntactically invalid JSON blobs. agentic_resume now returns an error envelope after the fallback resolution if either field is still empty, rather than passing empty strings into agentic_loop which would silently start a fresh turn with no context. Also add tests: - test_bridge_serialization.el: covers bridge_save empty-guard, golden-path raw-JSON round-trip, agentic_resume unknown/corrupt/missing-fields paths, and legacy string-escaped fallback path - test_sessions_routes.el: covers DELETE and PATCH /api/sessions/:id routes (valid args, unknown id, empty body) and GET /api/sessions regression after removal of the duplicate route_sessions() handler
This commit is contained in:
@@ -766,6 +766,12 @@ 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 {
|
||||
// Guard: empty messages or tools_json would produce syntactically invalid JSON.
|
||||
// Return false so the caller detects the failure rather than writing a corrupt
|
||||
// blob that agentic_resume would later resume with no context.
|
||||
if str_eq(messages, "") || str_eq(tools_json, "") {
|
||||
return false
|
||||
}
|
||||
// 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,
|
||||
@@ -796,9 +802,15 @@ fn agentic_resume(session_id: String, tool_use_id: String, content: String) -> S
|
||||
// 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 messages = 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_json = if str_eq(tools_json, "") { json_get(blob, "tools_json") } else { tools_json }
|
||||
// Guard: a corrupt or missing bridge blob (e.g. state cleared mid-flight)
|
||||
// yields empty messages/tools. Return an error envelope rather than resuming
|
||||
// with no context, which would cause the model to start a fresh turn.
|
||||
if str_eq(messages, "") || str_eq(tools_json, "") {
|
||||
return "{\"error\":\"corrupt bridge state\",\"reply\":\"\"}"
|
||||
}
|
||||
let tools_log: String = json_get(blob, "tools_log")
|
||||
let saved_use_id: String = json_get(blob, "tool_use_id")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user