mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-27 08:00:17 +00:00
aa35c2e1ce294e61ba4ec9b9eec80764543dd6eb
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aa35c2e1ce |
refactor: remove GATEWAY_USER_TOKENS, fix review feedback
GATEWAY_USER_TOKENS never went to production — replaced entirely by DB-backed user management via /api/admin/users and /api/tokens. Removed: - UserTokenConfig struct and GATEWAY_USER_TOKENS env var parsing - user_tokens field from GatewayConfig - GatewayChannel::new_multi_auth() constructor - Env-var user migration block in main.rs (~90 lines) - multi_tenant auto-detection from GATEWAY_USER_TOKENS (now runtime via db.has_any_users() in app.rs) Review fixes (zmanian): - User ID generation: UUID instead of display-name derivation (#1) - Invitation accept moved to public router (no auth needed) (#3) - libSQL get_invitation_by_hash aligned with postgres: filters status='pending' AND expires_at > now (#4) - UUID parse: returns DatabaseError::Serialization instead of unwrap_or_default (#7) - PostgreSQL SELECT * replaced with explicit column lists (#8) - Sort order aligned (both backends use DESC) (#6) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
b441ebec02 |
feat: multi-tenant auth with per-user workspace isolation (#1118)
* feat: multi-tenant auth with per-user scoping Multi-user authentication and authorization for IronClaw gateway: - Token-based auth mapping tokens to user IDs via GATEWAY_USER_TOKENS - Per-user SSE broadcast scoping - Per-user rate limiting with poisoned lock recovery - Handler auth and ownership checks for jobs, settings, routines - Extension secrets scoped per-user - Chat handlers use authenticated identity - Reverse proxy deployment documentation - Comprehensive integration tests for auth, SSE, rate limiting, and job isolation * fix: scope memory tools per-user in multi-tenant mode Memory tools (search, write, read, tree) held a single workspace created at startup with GATEWAY_USER_ID. In multi-tenant mode, all users' tool calls searched the default user's scope. Add WorkspaceResolver trait that resolves workspaces per-request using JobContext.user_id. In single-user mode, returns the startup workspace. In multi-tenant mode (GATEWAY_USER_TOKENS configured), creates and caches per-user workspaces on demand. Includes regression tests for workspace resolution and user isolation. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: comprehensive multi-tenant isolation audit Address all review findings from @serrrfirat plus 7 additional gaps found via full security audit: Reviewer findings (5): - WorkspacePool now applies search config, memory layers, embedding cache, identity read scopes, and global config scopes (was bare) - jobs_summary_handler uses per-user queries instead of global counters - jobs_prompt_handler restructured to not 404 agent jobs + ownership check - jobs_restart_handler agent branch now verifies user ownership - agent_job_summary_for_user added to Database trait + both backends Audit findings (7): - Delete dead handlers/memory.rs (stale copies with no auth) - Add AuthenticatedUser to logs_events, logs_level_get, logs_level_set - Add AuthenticatedUser to extensions_tools_handler, gateway_status_handler - Add auth + ownership checks to all 6 routines handlers - Add auth to all 4 skills handlers with audit logging on mutations - Scope extension setup SSE broadcast to user (broadcast_for_user) - Fix pre-existing test compilation errors in extensions/manager.rs 17 new multi-tenant isolation tests covering: - WorkspacePool config propagation and scope merging - Jobs handler per-user isolation (summary, restart, prompt, cancel) - Routines handler auth enforcement and cross-user rejection - Auth middleware enforcement on logs, skills, status endpoints Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: second-pass multi-tenant audit — scope SSE broadcasts, DB queries, dead handlers Second audit pass applying learned patterns across the codebase: - OAuth callback SSE broadcasts now use broadcast_for_user (lines 773, 912) - jobs_list_handler uses list_agent_jobs_for_user instead of fetching all users' jobs and filtering in Rust - list_agent_jobs_for_user added to Database trait + postgres + libsql - Dead handler files (extensions.rs, static_files.rs) hardened with AuthenticatedUser to prevent auth regression if migrated Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address review findings — token hashing, broadcast scoping, error handling Security fixes: - Hash tokens with SHA-256 at construction time so authentication compares fixed-size 32-byte digests, eliminating length-oracle timing leaks - Scope auth SSE broadcasts per-user in chat_auth_token_handler — AuthRequired/AuthCompleted events were leaking across tenants - Propagate DB errors in restart handlers instead of silently swallowing via `if let Ok(Some(...))` pattern Code quality: - Log SSE serialization failures instead of silently producing empty strings via unwrap_or_default() - Remove dead `pub type AuthState = MultiAuthState` alias - Replace `.unwrap()` with `Arc::clone(db)` in app.rs multi-tenant workspace setup (db is guaranteed Some in context, but unwrap violates project convention) - Fix telegram setup test to inject UserIdentity into request extensions (handler now requires AuthenticatedUser) - Add safety comments on test-only expect/unwrap calls for CI - Apply cargo fmt to fix pre-existing formatting Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address review findings — unify workspace pool, fix SSE regression, cache job owners - Unify WorkspacePool and PerUserWorkspaceResolver: WorkspacePool now implements WorkspaceResolver, eliminating duplicate per-user workspace construction logic. app.rs uses WorkspacePool directly. - Fix sse_tx: None scheduler regression: change scheduler/worker SSE broadcasting from broadcast::Sender<SseEvent> to Arc<SseManager>, restoring SSE event delivery for scheduled agent jobs. - Cache job owner in orchestrator: add job_owner_cache to OrchestratorState so job_event_handler avoids a DB round-trip on every event after the first per job. - Deduplicate ext_user_id computation in main.rs. - Remove unused _gateway_state variable. - Fix pre-existing test: first_token() returns None in multi-user mode by design; align test assertion. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: fix formatting in app.rs Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * refactor: extract memory handlers back into handlers/memory.rs Move memory API handlers out of server.rs into their own module, consistent with how jobs, routines, and skills handlers are organized. The resolve_workspace() helper moves with them since it is only used by memory handlers. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: [email protected] <[email protected]> |
||
|
|
3aa36c8f55 |
fix(tests): eliminate env mutex poison cascade (#1558)
* fix(tests): eliminate env mutex poison cascade and fix test flakiness The shared ENV_MUTEX used by ~68 config tests would cascade a single test panic into failures across every module. Replace all .unwrap() / .expect() lock acquisitions with a poison-recovering lock_env() helper. Consolidate rogue module-local ENV_LOCK instances (workspace, orchestrator, bootstrap) onto the shared global mutex to prevent cross-module races. Also fixes: - gateway user_id fallback was hardcoded to "default" instead of owner_id - test_ironclaw_env_path used LazyLock which is order-dependent Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test(helpers): add regression test for lock_env poison recovery Satisfies the regression-test-check CI gate by adding a test that intentionally poisons ENV_MUTEX and verifies lock_env() recovers. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): detect test changes inside #[cfg(test)] regions The regression test check relied on git diff -W to expand context to function boundaries, but git doesn't recognize Rust `mod tests {}` as a function boundary. Changes to imports, helpers, or lock calls inside test modules were invisible to the check. Add a line-level fallback: for each changed .rs file, find where #[cfg(test)] starts and check if any diff hunk targets a line at or after that boundary. This catches edits anywhere inside test modules regardless of git's language awareness. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: address PR review feedback - Clear ENV_MUTEX poison after regression test so it doesn't leave global state dirty for subsequent tests. - Fix CI regression-test-check to match #[cfg(test)] only when followed by `mod` (the test module pattern), avoiding false positives from standalone #[cfg(test)] items like statics or functions. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
212d661e20 |
feat(workspace): layered memory with sensitivity-based privacy redirect (#1112)
* feat(workspace): layered memory with sensitivity-based privacy redirect Introduce MemoryLayer type for named memory layers with sensitivity levels and write permissions. Layers map to synthetic user_id values in workspace tables, enabling shared/private memory isolation. - Add MemoryLayer, LayerSensitivity types with default_for_user() - Add layer-aware write methods (write_to_layer, append_to_layer) - Add PatternPrivacyClassifier to guard shared layer writes - Add optional 'layer' parameter to memory_write tool and HTTP API - Add 'redirected' and 'actual_layer' fields to write response - Add MEMORY_LAYERS env var (JSON) for layer configuration - Workspace user_id now derived from GATEWAY_USER_ID (was hardcoded "default") - 10 integration tests for layered memory operations Addresses prerequisite for Issue #59 (multi-tenancy). Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: add explicit default to memory_write layer schema Add "default": "private" to the layer parameter's JSON schema so LLM tool consumers can see the default without reading code. Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: extract resolve_layer_target to deduplicate layer writes Consolidate shared layer-lookup, writable check, and privacy classification logic from write_to_layer and append_to_layer into a single resolve_layer_target helper. Flagged on #349 review — the duplication originates in this PR. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address review feedback on layered memory PR - Fix email regex pipe bug in TLD character class (privacy.rs) - Add append support to web memory_write handler via `append` field - Validate MemoryLayer name/scope: reject empty, check duplicates - Remove hardcoded 'private' default from tool schema; omit layer fields from output when no layer specified - Document scope isolation risk for multi-tenant (Issue #59) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address adversarial review findings - CRITICAL: fix identity file protection bypass via trailing slash (normalize target path before protection checks) - HIGH: check private layer is writable before privacy redirect - HIGH: map LayerNotFound/ReadOnly to proper 4xx HTTP status codes - HIGH: honor `append` field in non-layer HTTP write path - MEDIUM: remove redundant DB fetch in append_to_layer (narrower TOCTOU window) - MEDIUM: remove dead memory_write_handler from handlers/memory.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: opt-in privacy classifier, force override, confidence scoring Address review feedback from @zmanian: - Privacy classifier is now opt-in via with_privacy_classifier() instead of always-on. Default hardcoded patterns (doctor, therapy, email, phone) had unacceptable false positive rates in household contexts. LLM chooses the correct layer via system prompt; regex can't improve on that. - Add ConfigurablePrivacyClassifier for operator-supplied patterns. - PatternPrivacyClassifier defaults narrowed to hard PII only (SSN, credit card, credentials). - Add force param to write_to_layer/append_to_layer to skip classifier. - PrivacyClassifier trait returns SensitivityResult { is_sensitive, confidence } instead of bool, ready for probabilistic classifiers. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: remove redundant heartbeat match arm in memory_write The heartbeat arm was identical to the catch-all — resolved_path already points to paths::HEARTBEAT when target is "heartbeat". Addresses review feedback from gemini-code-assist on #1112. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: return Result from PatternPrivacyClassifier::new() Replace .expect() with proper error propagation per project no-panics policy. Remove Default impl (unused in production). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * refactor: move memory_layers from GatewayConfig to WorkspaceConfig Resolve merge conflicts between HEAD (transcription, search, env helpers) and the workspace config branch. GatewayConfig no longer owns memory_layers; WorkspaceConfig::resolve() handles parsing, validation (name length >64, character set, empty scope, duplicates), and fallback defaults. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: strengthen privacy classifier and layer isolation coverage Add 8 privacy classifier edge case tests (format variants, keywords, longer documents, empty/partial inputs) and 5 layer write isolation integration tests (cross-scope invisibility, overwrite, empty path, sensitive-to-private no-redirect). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix: tautological test assertion and add WorkspaceConfig validation tests Replace always-true `is_ok() || is_err()` in write_empty_path_to_layer with actual behavior assertion (write succeeds with normalized empty path). Add 8 unit tests for WorkspaceConfig::resolve() covering valid JSON parsing, invalid JSON, empty/long/invalid-char layer names, empty scopes, duplicates, and default fallback behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * style: cargo fmt after staging merge Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: [email protected] <[email protected]> |
||
|
|
878a67cdb6 |
Refactor owner scope across channels and fix default routing fallback (#1151)
* refactor: add explicit owner scope across channels * fix: tighten routine owner target routing * fix: address owner scope review feedback * Fix owner-scope onboarding and event trigger isolation * Tighten routing fallback and wizard owner validation * fix: address owner-scope follow-up review * fix: tighten owner-scope follow-up details * fix: import Channel trait in telegram test * fix: normalize http webhook sender ids * fix: address remaining owner-scope review issues * fix: reconcile config rebase fallout * fix: reconcile extension manager rebase drift * fix: address current copilot review regressions * fix: restore clippy matrix after rebase |
||
|
|
6aaa89010a |
fix(security): default webhook server to loopback when tunnel is configured (#1194)
When a tunnel provider (ngrok, cloudflare, tailscale, etc.) or static TUNNEL_URL is configured, external traffic arrives through the tunnel, so binding 0.0.0.0 is unnecessary attack surface. The webhook server now defaults to 127.0.0.1 when a tunnel is active. Explicit HTTP_HOST still overrides the default in all cases. Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
e74214dce8 |
fix(config): unify ChannelsConfig resolution to env > settings > default (#1124)
ChannelsConfig::resolve() ignored most ChannelSettings fields, reading exclusively from env vars. This made `config set` ineffective for gateway, HTTP, CLI, and WASM channel settings — a prerequisite blocker for #86 (hot-reload) and CLI management commands. - Add gateway and CLI fields to ChannelSettings with correct defaults - Rewrite resolve() to fall back to settings when env var is unset - Keep strict boolean validation via parse_bool_env for all bool fields - Fix GATEWAY_PORT default divergence (3001 -> 3000) in extension manager - Export DEFAULT_GATEWAY_PORT constant as single source of truth - Add 8 tests: settings fallback, env override, DB roundtrip, invalid bool rejection Part of #1119 (Phase 1: Channels pilot) [skip-regression-check] |
||
|
|
cf96a3253c |
fix(tests): replace hardcoded /tmp paths with tempdir + add 300 unit tests (#659)
* test: add unit tests across 20 modules for coverage push Add 300+ unit tests covering config, context, evaluation, extensions, LLM, secrets, tools/builder, and tools/mcp modules. All tests are pure unit tests (no mocks) exercising serde roundtrips, edge cases, error paths, and business logic. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(tests): replace hardcoded /tmp paths with tempfile::tempdir The e2e_metrics_test::test_metrics_collected_from_tool_trace test was failing because setup_test_dir() created /tmp/ironclaw_metrics_test but the fixture referenced /tmp/ironclaw_e2e_test/hello.txt (path mismatch). Added LlmTrace::replace_paths() to substitute fixture paths at runtime, then converted all 12 test files from hardcoded /tmp/ironclaw_* paths to tempfile::tempdir(). Tests are now isolated, parallel-safe, and leave no debris on disk. Regression test: test_metrics_collected_from_tool_trace now passes consistently regardless of prior /tmp state. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
78878ad7ef |
Remove restart infrastructure, generalize WASM channel setup (#493)
* refactor: remove restart infrastructure and generalize Telegram-specific code Remove the gateway restart mechanism (hot-activation works, restart won't fix activation failures) and generalize Telegram-specific hardcoded checks so all WASM channels get equal treatment. Part 1 - Remove restart infrastructure: - Remove needs_restart from ActionResponse, restart_requested from GatewayState - Remove gateway_restart_handler, /api/gateway/restart route, exit code 75 - Remove restart overlay JS/CSS (dead code - restartGateway() never called) - Surface actual activation errors instead of suggesting restart Part 2 - Generalize Telegram-specific code: - Replace telegram_owner_id: Option<i64> with generic wasm_channel_owner_ids: HashMap<String, i64> (backwards-compatible via TELEGRAM_OWNER_ID env var) - Pairing status check now applies to all active WASM channels - All channels get 3-step stepper in web UI, remove "coming soon" note - Remove dead setup_telegram() code (~700 lines) - Telegram's capabilities.json declares required_secrets, so the generic setup_wasm_channel() path handles it Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * test: add Settings::set() test for wasm_channel_owner_ids Addresses review feedback: verify that setting per-channel owner IDs via the dotted-path Settings::set() API works correctly with the new HashMap<String, i64> type. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(web): refresh extension stepper after pairing approval loadPairingRequests only refreshed the pairing section, not the stepper status. Call loadExtensions() instead so the stepper updates from "Awaiting Pairing" to "Active" immediately after approval. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
c592a8f2de | feat: add IRONCLAW_BASE_DIR env var with LazyLock caching (#397) | ||
|
|
b0b3a50fa3 |
feat(channels): add native Signal channel via signal-cli HTTP daemon (#271)
* feat(channels): add native Signal channel via signal-cli HTTP daemon Implement a native Rust Signal channel that connects to a running signal-cli daemon's HTTP endpoint, enabling Signal messaging without WASM overhead. Architecture: - SSE listener at /api/v1/events for receiving messages with automatic reconnection and exponential backoff - JSON-RPC client at /api/v1/rpc for sending messages and typing indicators - Reply target tracking via Arc<RwLock<HashMap>> to route responses back to the correct DM or group conversation Features: - User allowlisting supporting E.164 phone numbers, bare UUIDs, and uuid:-prefixed identifiers (matching OpenClaw's format) - Group allowlisting with wildcard (*) support - Configurable story and attachment-only message filtering - Health check via signal-cli /api/v1/check - Broadcast support to all tracked reply targets Configuration via environment variables: - SIGNAL_HTTP_URL, SIGNAL_ACCOUNT (required) - SIGNAL_ALLOWED_USERS, SIGNAL_ALLOWED_GROUPS - SIGNAL_IGNORE_ATTACHMENTS (default: false) - SIGNAL_IGNORE_STORIES (default: true) Includes unit tests covering allowlist logic, envelope parsing, recipient targeting, SSE deserialization, and edge cases. * refactor(signal): remove expect|unwrap calls - Change SignalChannel::new to return Result<Self, ChannelError> - Replace .expect() on reqwest client build with proper error handling - Replace .expect() on NonZeroUsize with compile-time const using unsafe new_unchecked - Propagate errors through test helpers to avoid unwraps in tests * fix(signal): prevent OOM from chunked response without Content-Length Use bytes_stream() to check response size during download rather than buffering entire body first. This closes the OOM vector where a malicious signal-cli daemon could send unbounded chunked data. * fix(signal): align is_e164 minimum digits with setup wizard Both now require 7-15 digits after '+', preventing environment variable bypass of the stricter onboarding validation. * refactor(signal): extract from_parts constructor Extract SignalChannel::from_parts() used by both new() and sse_listener() to ensure consistent object construction. * chore: remove redundant unused var * refactor(signal): rename allowed_users to allow_from and add dm_policy/group_policy - Rename allowed_users -> allow_from for consistency with other channels - Rename allowed_groups -> allow_from_groups - Add dm_policy field: 'open', 'allowlist', or 'pairing' (default: 'pairing') - Add group_policy field: 'allowlist', 'open', or 'disabled' (default: 'allowlist') - Add group_allow_from field that inherits from allow_from if empty - Implement dm_policy and group_policy logic in message processing - Add environment variable resolution: SIGNAL_ALLOW_FROM, SIGNAL_ALLOW_FROM_GROUPS, SIGNAL_DM_POLICY, SIGNAL_GROUP_POLICY, SIGNAL_GROUP_ALLOW_FROM - Add setup wizard prompts for new policy options - Note: full pairing flow (PairingStore integration) marked as pending for future PR * feat(signal): implement DM pairing workflow for unapproved senders - Add PairingStore integration to check approved senders - Handle pairing requests for unknown senders with dm_policy=pairing - Send pairing reply message with approval instructions - Update FEATURE_PARITY.md to reflect DM pairing support * chore(ci): fix clippy warnings |
||
|
|
c3ce26278a |
refactor: simplify config resolution and consolidate main.rs init (#287)
* refactor: simplify config resolution and consolidate main.rs init into AppBuilder - Add parse_bool_env() and parse_string_env() helpers to eliminate repetitive 5-line optional_env/parse/map_err/unwrap_or boilerplate across 12 config files - Add EmbeddingsConfig::create_provider() to centralize embeddings construction (fixes hardcoded 1536 dimensions and missing Ollama provider in app.rs) - Extract init_cli_tracing(), setup_wasm_channels(), start_tunnel(), run_memory_command(), run_worker(), run_claude_bridge() from main.rs - Replace ~600 lines of inline init in main.rs with AppBuilder::build_all() - Expose catalog_entries from AppComponents for gateway registry entries - Net reduction: ~738 lines across 15 files Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: propagate dev_loaded_tool_names from AppBuilder and add parse_option_env helper Address PR review feedback: - Capture dev_loaded_tool_names from WASM loading in init_extensions() and expose via AppComponents so bootstrap_hooks receives the actual dev tool names instead of an empty slice (fixes silent hook skip) - Add parse_option_env<T>() helper for Option<T> config fields, simplifying max_cost_per_day_cents and max_actions_per_hour in agent.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: fetch real NEAR AI pricing and unify cost calculation path CostGuard was independently looking up pricing via costs::model_cost(), falling back to GPT-4o default rates when NEAR AI model names didn't match the static table — causing ~3x cost overestimates in logs. - Add pricing map to NearAiChatProvider that fetches real rates from /v1/model/list at startup (background, non-blocking) - Update cost_per_token() to check fetched pricing first, then static table, then default - Add cost_per_token parameter to CostGuard::record_llm_call() so the dispatcher passes provider-sourced rates directly Co-Authored-By: Claude Opus 4.6 <[email protected]> * chore: update default NEAR AI model to GLM-latest Replace fireworks llama4-maverick-instruct-basic with zai-org/GLM-latest as the default model in config and setup wizard. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: align wizard default model name with config Change "zai/GLM-latest" to "zai-org/GLM-latest" in wizard.rs to match the default in config/llm.rs. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> |
||
|
|
ffb1cc9be8 |
refactor: architecture improvements for contributor velocity (#198)
* refactor: split large files and consolidate test stubs for contributor velocity - Extract 7 Database sub-traits (ConversationStore, JobStore, SandboxStore, RoutineStore, ToolFailureStore, SettingsStore, WorkspaceStore) with Database as a supertrait combining them all - Split libsql_backend.rs (2769 lines) into src/db/libsql/ directory with one file per sub-trait implementation - Split config.rs (1753 lines) into src/config/ directory with 16 domain files - Consolidate 3 duplicate test LLM stubs into shared StubLlm in src/testing.rs - Split server.rs handlers into src/channels/web/handlers/ directory - Extract main.rs init phases into AppBuilder (src/app.rs) - Add developer setup script (scripts/dev-setup.sh) Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: move heartbeat test from examples/ to tests/ Convert standalone example binary into a proper #[ignore] integration test, matching the convention of the other integration tests. Co-Authored-By: Claude Opus 4.6 <[email protected]> * style: fix rustfmt formatting for CI Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address PR review comments from Copilot - tunnel.rs: replace .ok().flatten() with ? to propagate env var errors - secrets.rs: remove misleading "process-wide cache" comment - database.rs: use uppercase "DATABASE_URL" in error key - testing.rs: gate harness tests with #[cfg(feature = "libsql")] Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> |