Serialize async model/tool output with linenoise. This is the central * terminal contract. In scroll-region mode the live prompt stays painted: * output is appended in the upper scroll area, then the cursor is returned to * linenoise's remembered prompt position. The fallback path still hides and * redraws because it has no protected prompt rows. */
| 9298 | |
| 9299 | static bool agent_tool_observation_commit(agent_worker *w, |
| 9300 | agent_tool_observation *obs, |
| 9301 | char *err, size_t err_len) { |
| 9302 | ds4_tokens next = {0}; |
| 9303 | ds4_vision_span *spans = NULL; |
| 9304 | if (!agent_tool_observation_build(w, obs, &next, &spans, err, err_len)) |
| 9305 | return false; |
| 9306 | ds4_tokens_free(&w->transcript); |
| 9307 | w->transcript = next; |
| 9308 | agent_worker_images_append(w, spans, obs->image_count); |
| 9309 | free(spans); |
| 9310 | return true; |
| 9311 | } |
| 9312 | |
| 9313 | /* If compaction happens while a bash process is still alive, inject a small |
| 9314 | * tool-role reminder into the rebuilt transcript. Otherwise the summary could |
| 9315 | * preserve the user's task but lose the fact that an external process still |
| 9316 | * needs status/wait/stop handling. */ |
| 9317 | static char *agent_bash_jobs_compaction_observation(agent_worker *w) { |
| 9318 | if (!w->bash_jobs) return NULL; |
| 9319 | agent_buf out = {0}; |
| 9320 | agent_buf_puts(&out, |
| 9321 | "Bash job update after context compaction. Running jobs still need explicit bash_status or bash_stop if relevant.\n"); |
| 9322 | for (agent_bash_job *job = w->bash_jobs, *next = NULL; job; job = next) { |
| 9323 | next = job->next; |
| 9324 | bool done = false; |
| 9325 | char *obs = agent_bash_observation(job, true, &done); |
| 9326 | char hdr[64]; |
| 9327 | snprintf(hdr, sizeof(hdr), "\nJob %d:\n", job->id); |
| 9328 | agent_buf_puts(&out, hdr); |
| 9329 | agent_buf_puts(&out, obs); |
| 9330 | free(obs); |
| 9331 | if (done) agent_bash_remove_job(w, job); |
| 9332 | } |
| 9333 | return agent_buf_take(&out); |
| 9334 | } |
| 9335 | |
| 9336 | /* ============================================================================ |
| 9337 | * Context Compaction |
| 9338 | * ============================================================================ |
| 9339 | * |
| 9340 | * Compaction asks the model for durable task state, then rebuilds the live |
| 9341 | * transcript as: system prompt + summary + recent verbatim tail. This keeps |
| 9342 | * the active KV usable while avoiding unbounded transcript growth. |
| 9343 | */ |
| 9344 | |
| 9345 | /* Decide when to compact before an ordinary turn or before appending a large |
| 9346 | * tool result. The fixed free-token threshold is capped proportionally for |
| 9347 | * smaller contexts so tests with tiny contexts still compact rather than fail. */ |
| 9348 | static bool agent_worker_should_compact(agent_worker *w) { |
| 9349 | int ctx = agent_worker_effective_ctx_size(w); |
| 9350 | int used = w->transcript.len; |
| 9351 | if (ctx <= 0 || used <= 0) return false; |
| 9352 | if (used >= (ctx * AGENT_COMPACT_SOFT_PERCENT) / 100) return true; |
| 9353 | int free_threshold = AGENT_COMPACT_MIN_FREE_TOKENS; |
no test coverage detected