fix: persist turns after approval and add agent-level tests (#250)

* fix: persist turns after approval and add agent-level tests

Port relevant changes from PR #112 that were not carried over to #237:

- Add persist_turn calls in process_approval for the response, error,
  and auth-required paths. Previously, turns completed after tool
  approval were never persisted to DB — if the process crashed after
  approval the entire turn (user message + assistant response) was lost.

- Add agent-level unit tests: StaticLlmProvider mock, make_test_agent
  helper, tests for auto-approval logic, destructive shell command
  detection, and PendingApproval backward-compatible deserialization
  (without deferred_tool_calls field).

- Remove unused _thread_state binding in process_approval.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address 14 audit findings in src/agent/

Audit of the agent module found 2 High, 7 Medium, 3 Low, and 2 Nit
severity issues. This commit fixes all of them:

High:
- Remove 4 `.expect()` calls in session.rs (entry API, match, direct
  indexing, if-let) to eliminate panic paths in production
- Add typed RoutineError enum replacing Result<_, String> across
  routine.rs, routine_engine.rs, and callers in history/store.rs and
  db/libsql/mod.rs

Medium:
- Sanitize routine names in path construction to prevent directory
  traversal (routine_engine.rs)
- Log warnings for 5 silently-swallowed errors in scheduler.rs,
  compaction.rs, and worker.rs
- Extract shared handle_auth_intercept helper to deduplicate auth
  interception in thread_ops.rs
- Add session count warning threshold in session_manager.rs
- Make FullJob stub degradation visible via warn-level log and
  prepended warning in output

Low:
- Restrict dead code visibility with #[cfg(test)] on 19 unused items
  in submission.rs, task.rs, and undo.rs
- Narrow pub to pub(crate) on self_repair.rs builder methods
- Remove TaskStatus from mod.rs re-exports (test-only type)

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review comments

- Reorder persist_turn before persist_response_chain so the
  conversation row exists before the metadata UPDATE runs
- Add persist_response_chain call to handle_auth_intercept so
  auth-required paths preserve the response chain
- Harden sanitize_routine_name to use allowlist (alphanumeric,
  dash, underscore) instead of denylist replacements
- Fix stale active_thread ID in get_or_create_thread: fall back
  to create_thread() when the stored ID is missing from the map
- Persist turn on approval rejection so user messages survive
  crashes after a tool is rejected

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-20 02:28:15 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 97a7637f30
commit 3f135bdde9
17 changed files with 583 additions and 156 deletions
+43
View File
@@ -48,6 +48,9 @@ pub enum Error {
#[error("Worker error: {0}")]
Worker(#[from] WorkerError),
#[error("Routine error: {0}")]
Routine(#[from] RoutineError),
}
/// Configuration-related errors.
@@ -365,5 +368,45 @@ pub enum WorkerError {
MissingToken,
}
/// Routine-related errors.
#[derive(Debug, thiserror::Error)]
pub enum RoutineError {
#[error("Unknown trigger type: {trigger_type}")]
UnknownTriggerType { trigger_type: String },
#[error("Unknown action type: {action_type}")]
UnknownActionType { action_type: String },
#[error("Missing field in {context}: {field}")]
MissingField { context: String, field: String },
#[error("Invalid cron expression: {reason}")]
InvalidCron { reason: String },
#[error("Unknown run status: {status}")]
UnknownRunStatus { status: String },
#[error("Routine {name} is disabled")]
Disabled { name: String },
#[error("Routine not found: {id}")]
NotFound { id: Uuid },
#[error("Routine {name} at max concurrent runs")]
MaxConcurrent { name: String },
#[error("Database error: {reason}")]
Database { reason: String },
#[error("LLM call failed: {reason}")]
LlmFailed { reason: String },
#[error("LLM returned empty content")]
EmptyResponse,
#[error("LLM response truncated (finish_reason=length) with no content")]
TruncatedResponse,
}
/// Result type alias for the agent.
pub type Result<T> = std::result::Result<T, Error>;