rubino-agent 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +115 -0
- data/.rubocop_todo.yml +955 -0
- data/.ruby-version +1 -0
- data/AGENTS.md +97 -0
- data/CHANGELOG.md +344 -0
- data/CONTRIBUTING.md +69 -0
- data/LICENSE +21 -0
- data/README.md +200 -0
- data/Rakefile +8 -0
- data/docs/agents.md +190 -0
- data/docs/api/v1.md +414 -0
- data/docs/architecture.md +177 -0
- data/docs/commands.md +375 -0
- data/docs/configuration.md +590 -0
- data/docs/getting-started.md +143 -0
- data/docs/jobs.md +332 -0
- data/docs/mcp.md +128 -0
- data/docs/memory.md +98 -0
- data/docs/models-and-keys.md +173 -0
- data/docs/oauth-providers.md +145 -0
- data/docs/plugins.md +195 -0
- data/docs/security.md +145 -0
- data/docs/skills.md +322 -0
- data/docs/tools.md +395 -0
- data/docs/troubleshooting.md +73 -0
- data/exe/rubino +9 -0
- data/install.sh +275 -0
- data/lib/rubino/active_skill.rb +50 -0
- data/lib/rubino/agent/agent_registry.rb +120 -0
- data/lib/rubino/agent/backoff_policy.rb +116 -0
- data/lib/rubino/agent/definition.rb +128 -0
- data/lib/rubino/agent/degenerate_recovery.rb +271 -0
- data/lib/rubino/agent/fallback_chain.rb +194 -0
- data/lib/rubino/agent/iteration_budget.rb +50 -0
- data/lib/rubino/agent/loop.rb +617 -0
- data/lib/rubino/agent/model_call_runner.rb +383 -0
- data/lib/rubino/agent/prompts/build.txt +69 -0
- data/lib/rubino/agent/prompts/compaction.txt +20 -0
- data/lib/rubino/agent/prompts/explore.txt +19 -0
- data/lib/rubino/agent/prompts/general.txt +20 -0
- data/lib/rubino/agent/prompts/plan.txt +31 -0
- data/lib/rubino/agent/response_validator.rb +70 -0
- data/lib/rubino/agent/router.rb +65 -0
- data/lib/rubino/agent/runner.rb +195 -0
- data/lib/rubino/agent/tool_executor.rb +402 -0
- data/lib/rubino/agent/truncation_continuation.rb +137 -0
- data/lib/rubino/api/middleware/auth.rb +43 -0
- data/lib/rubino/api/middleware/error_handler.rb +65 -0
- data/lib/rubino/api/middleware/json_parser.rb +100 -0
- data/lib/rubino/api/middleware/observability.rb +59 -0
- data/lib/rubino/api/middleware/rate_limit.rb +136 -0
- data/lib/rubino/api/operations/approvals/decide_operation.rb +49 -0
- data/lib/rubino/api/operations/clarifications/decide_operation.rb +44 -0
- data/lib/rubino/api/operations/cron_jobs/create_operation.rb +46 -0
- data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +36 -0
- data/lib/rubino/api/operations/cron_jobs/list_operation.rb +55 -0
- data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/schedule_validation.rb +30 -0
- data/lib/rubino/api/operations/cron_jobs/show_operation.rb +32 -0
- data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +38 -0
- data/lib/rubino/api/operations/cron_jobs/update_operation.rb +42 -0
- data/lib/rubino/api/operations/files/read_operation.rb +40 -0
- data/lib/rubino/api/operations/files/upload_operation.rb +175 -0
- data/lib/rubino/api/operations/health_operation.rb +46 -0
- data/lib/rubino/api/operations/memory/delete_operation.rb +32 -0
- data/lib/rubino/api/operations/memory/index_operation.rb +80 -0
- data/lib/rubino/api/operations/memory/stats_operation.rb +28 -0
- data/lib/rubino/api/operations/metrics_operation.rb +18 -0
- data/lib/rubino/api/operations/mode/show_operation.rb +29 -0
- data/lib/rubino/api/operations/mode/update_operation.rb +42 -0
- data/lib/rubino/api/operations/models/list_operation.rb +45 -0
- data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +77 -0
- data/lib/rubino/api/operations/oauth/connections/list_operation.rb +36 -0
- data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +82 -0
- data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +44 -0
- data/lib/rubino/api/operations/oauth/providers/list_operation.rb +35 -0
- data/lib/rubino/api/operations/oauth/serializer.rb +21 -0
- data/lib/rubino/api/operations/runs/create_operation.rb +77 -0
- data/lib/rubino/api/operations/runs/events_operation.rb +195 -0
- data/lib/rubino/api/operations/runs/stop_operation.rb +34 -0
- data/lib/rubino/api/operations/sessions/create_operation.rb +46 -0
- data/lib/rubino/api/operations/sessions/delete_operation.rb +33 -0
- data/lib/rubino/api/operations/sessions/index_operation.rb +82 -0
- data/lib/rubino/api/operations/sessions/retry_operation.rb +45 -0
- data/lib/rubino/api/operations/sessions/show_operation.rb +59 -0
- data/lib/rubino/api/operations/sessions/undo_operation.rb +38 -0
- data/lib/rubino/api/operations/skills/list_operation.rb +34 -0
- data/lib/rubino/api/operations/skills/toggle_operation.rb +40 -0
- data/lib/rubino/api/operations/tasks/index_operation.rb +30 -0
- data/lib/rubino/api/operations/tasks/serializer.rb +60 -0
- data/lib/rubino/api/operations/tasks/show_operation.rb +33 -0
- data/lib/rubino/api/operations/tasks/stop_operation.rb +47 -0
- data/lib/rubino/api/request.rb +54 -0
- data/lib/rubino/api/responses.rb +64 -0
- data/lib/rubino/api/router.rb +72 -0
- data/lib/rubino/api/schemas.rb +103 -0
- data/lib/rubino/api/server.rb +102 -0
- data/lib/rubino/api/tls.rb +108 -0
- data/lib/rubino/attachments/classification.rb +16 -0
- data/lib/rubino/attachments/classify.rb +171 -0
- data/lib/rubino/attachments/defang.rb +47 -0
- data/lib/rubino/attachments/policy.rb +36 -0
- data/lib/rubino/attachments/preamble.rb +120 -0
- data/lib/rubino/boot/encryption_key.rb +32 -0
- data/lib/rubino/cli/chat/bang_shell.rb +257 -0
- data/lib/rubino/cli/chat/completion_builder.rb +290 -0
- data/lib/rubino/cli/chat/idle_card_host.rb +69 -0
- data/lib/rubino/cli/chat/image_inbox.rb +168 -0
- data/lib/rubino/cli/chat/session_resolver.rb +176 -0
- data/lib/rubino/cli/chat_command.rb +1674 -0
- data/lib/rubino/cli/commands.rb +250 -0
- data/lib/rubino/cli/config_command.rb +96 -0
- data/lib/rubino/cli/doctor_command.rb +251 -0
- data/lib/rubino/cli/jobs_command.rb +60 -0
- data/lib/rubino/cli/memory_command.rb +135 -0
- data/lib/rubino/cli/onboarding_wizard.rb +207 -0
- data/lib/rubino/cli/server_command.rb +139 -0
- data/lib/rubino/cli/session_command.rb +125 -0
- data/lib/rubino/cli/setup_command.rb +107 -0
- data/lib/rubino/cli/skills_command.rb +85 -0
- data/lib/rubino/cli/tools_command.rb +81 -0
- data/lib/rubino/cli/trust_gate.rb +71 -0
- data/lib/rubino/commands/built_ins.rb +46 -0
- data/lib/rubino/commands/command.rb +116 -0
- data/lib/rubino/commands/executor.rb +550 -0
- data/lib/rubino/commands/handlers/agents.rb +510 -0
- data/lib/rubino/commands/handlers/config.rb +88 -0
- data/lib/rubino/commands/handlers/help.rb +148 -0
- data/lib/rubino/commands/handlers/jobs.rb +71 -0
- data/lib/rubino/commands/handlers/mcp.rb +229 -0
- data/lib/rubino/commands/handlers/memory.rb +200 -0
- data/lib/rubino/commands/handlers/sessions.rb +207 -0
- data/lib/rubino/commands/handlers/skills.rb +195 -0
- data/lib/rubino/commands/handlers/status.rb +211 -0
- data/lib/rubino/commands/loader.rb +90 -0
- data/lib/rubino/config/configuration.rb +455 -0
- data/lib/rubino/config/defaults.rb +569 -0
- data/lib/rubino/config/loader.rb +115 -0
- data/lib/rubino/config/reasoning_prefs.rb +67 -0
- data/lib/rubino/config/writer.rb +72 -0
- data/lib/rubino/context/compressor.rb +149 -0
- data/lib/rubino/context/environment_inspector.rb +176 -0
- data/lib/rubino/context/file_discovery.rb +45 -0
- data/lib/rubino/context/message_boundary.rb +39 -0
- data/lib/rubino/context/prompt_assembler.rb +382 -0
- data/lib/rubino/context/summary_builder.rb +159 -0
- data/lib/rubino/context/token_budget.rb +68 -0
- data/lib/rubino/context/tool_pair_sanitizer.rb +70 -0
- data/lib/rubino/database/connection.rb +77 -0
- data/lib/rubino/database/migrations/001_create_initial_schema.rb +156 -0
- data/lib/rubino/database/migrations/002_create_runs.rb +45 -0
- data/lib/rubino/database/migrations/003_create_skill_states.rb +15 -0
- data/lib/rubino/database/migrations/004_create_cron_jobs.rb +36 -0
- data/lib/rubino/database/migrations/005_create_oauth_connections.rb +27 -0
- data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +34 -0
- data/lib/rubino/database/migrations/007_create_messages_fts.rb +59 -0
- data/lib/rubino/database/migrations/008_create_memory_facts.rb +75 -0
- data/lib/rubino/database/migrations/009_create_memory_graph.rb +55 -0
- data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +20 -0
- data/lib/rubino/database/migrator.rb +48 -0
- data/lib/rubino/documents/converters/csv.rb +79 -0
- data/lib/rubino/documents/converters/docx.rb +129 -0
- data/lib/rubino/documents/converters/html.rb +28 -0
- data/lib/rubino/documents/converters/json.rb +35 -0
- data/lib/rubino/documents/converters/pdf.rb +59 -0
- data/lib/rubino/documents/converters/plain.rb +68 -0
- data/lib/rubino/documents/converters/pptx.rb +64 -0
- data/lib/rubino/documents/converters/xlsx.rb +62 -0
- data/lib/rubino/documents/converters/xml.rb +45 -0
- data/lib/rubino/documents/html.rb +71 -0
- data/lib/rubino/documents/registry.rb +68 -0
- data/lib/rubino/documents/table.rb +63 -0
- data/lib/rubino/documents.rb +50 -0
- data/lib/rubino/errors.rb +119 -0
- data/lib/rubino/files/workspace.rb +93 -0
- data/lib/rubino/interaction/cancel_token.rb +43 -0
- data/lib/rubino/interaction/clipboard_image.rb +84 -0
- data/lib/rubino/interaction/event_bus.rb +48 -0
- data/lib/rubino/interaction/events.rb +101 -0
- data/lib/rubino/interaction/image_input.rb +127 -0
- data/lib/rubino/interaction/input_queue.rb +117 -0
- data/lib/rubino/interaction/lifecycle.rb +299 -0
- data/lib/rubino/interaction/probe.rb +65 -0
- data/lib/rubino/interaction/state.rb +56 -0
- data/lib/rubino/jobs/cron_job_repository.rb +75 -0
- data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +32 -0
- data/lib/rubino/jobs/handlers/compact_session_job.rb +21 -0
- data/lib/rubino/jobs/handlers/distill_skill_job.rb +186 -0
- data/lib/rubino/jobs/handlers/extract_memory_job.rb +37 -0
- data/lib/rubino/jobs/handlers/summarize_session_job.rb +21 -0
- data/lib/rubino/jobs/queue.rb +184 -0
- data/lib/rubino/jobs/registry.rb +45 -0
- data/lib/rubino/jobs/runner.rb +79 -0
- data/lib/rubino/jobs/scheduler.rb +138 -0
- data/lib/rubino/jobs/webhook_delivery.rb +225 -0
- data/lib/rubino/jobs/worker.rb +59 -0
- data/lib/rubino/llm/adapter_factory.rb +47 -0
- data/lib/rubino/llm/adapter_response.rb +65 -0
- data/lib/rubino/llm/auxiliary_client.rb +61 -0
- data/lib/rubino/llm/bedrock_bearer_client.rb +235 -0
- data/lib/rubino/llm/content_builder.rb +55 -0
- data/lib/rubino/llm/credential_check.rb +93 -0
- data/lib/rubino/llm/error_classifier.rb +364 -0
- data/lib/rubino/llm/fake_provider.rb +292 -0
- data/lib/rubino/llm/inline_think_filter.rb +58 -0
- data/lib/rubino/llm/model_catalog.rb +29 -0
- data/lib/rubino/llm/provider_resolver.rb +48 -0
- data/lib/rubino/llm/reasoning_manager.rb +100 -0
- data/lib/rubino/llm/request.rb +56 -0
- data/lib/rubino/llm/ruby_llm_adapter.rb +794 -0
- data/lib/rubino/llm/scenario_loader.rb +68 -0
- data/lib/rubino/llm/scenario_selector.rb +80 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron-failure.yml +29 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron.yml +36 -0
- data/lib/rubino/llm/scenarios/analysis.yml +501 -0
- data/lib/rubino/llm/scenarios/complex-analysis.yml +598 -0
- data/lib/rubino/llm/scenarios/failure.yml +65 -0
- data/lib/rubino/llm/scenarios/happy-path.yml +24 -0
- data/lib/rubino/llm/scenarios/provider-quota-completed.yml +14 -0
- data/lib/rubino/llm/scenarios/wide-table.yml +121 -0
- data/lib/rubino/llm/scenarios/with-approvals.yml +50 -0
- data/lib/rubino/llm/scenarios/with-artifacts.yml +98 -0
- data/lib/rubino/llm/scenarios/with-clarify.yml +32 -0
- data/lib/rubino/llm/scenarios/with-reasoning.yml +175 -0
- data/lib/rubino/llm/scenarios/with-uploads.yml +104 -0
- data/lib/rubino/llm/thinking_support.rb +84 -0
- data/lib/rubino/llm/tool_bridge.rb +89 -0
- data/lib/rubino/logger.rb +99 -0
- data/lib/rubino/mcp/manager.rb +180 -0
- data/lib/rubino/mcp/mcp_tool_wrapper.rb +69 -0
- data/lib/rubino/mcp.rb +57 -0
- data/lib/rubino/memory/backend.rb +104 -0
- data/lib/rubino/memory/backends/default.rb +101 -0
- data/lib/rubino/memory/backends/sqlite.rb +653 -0
- data/lib/rubino/memory/backends.rb +53 -0
- data/lib/rubino/memory/deduplicator.rb +74 -0
- data/lib/rubino/memory/extractor.rb +85 -0
- data/lib/rubino/memory/flusher.rb +31 -0
- data/lib/rubino/memory/retriever.rb +50 -0
- data/lib/rubino/memory/sqlite_extraction_prompt.rb +70 -0
- data/lib/rubino/memory/sqlite_graph.rb +154 -0
- data/lib/rubino/memory/store.rb +228 -0
- data/lib/rubino/memory/threat_scanner.rb +68 -0
- data/lib/rubino/metrics.rb +175 -0
- data/lib/rubino/modes.rb +93 -0
- data/lib/rubino/oauth/connection_repository.rb +95 -0
- data/lib/rubino/oauth/provider/github.rb +75 -0
- data/lib/rubino/oauth/provider/google.rb +59 -0
- data/lib/rubino/oauth/provider.rb +149 -0
- data/lib/rubino/oauth/registry.rb +86 -0
- data/lib/rubino/oauth/token_encryptor.rb +87 -0
- data/lib/rubino/plugins/registry.rb +75 -0
- data/lib/rubino/plugins.rb +86 -0
- data/lib/rubino/run/approval_gate.rb +243 -0
- data/lib/rubino/run/attachment_downloader.rb +166 -0
- data/lib/rubino/run/event_store.rb +74 -0
- data/lib/rubino/run/executor.rb +383 -0
- data/lib/rubino/run/gate_registry.rb +39 -0
- data/lib/rubino/run/recorder.rb +69 -0
- data/lib/rubino/run/repository.rb +118 -0
- data/lib/rubino/run/session_approval_cache.rb +118 -0
- data/lib/rubino/security/allowlist_persister.rb +55 -0
- data/lib/rubino/security/approval_policy.rb +227 -0
- data/lib/rubino/security/command_allowlist.rb +24 -0
- data/lib/rubino/security/dangerous_patterns.rb +118 -0
- data/lib/rubino/security/deny_persister.rb +73 -0
- data/lib/rubino/security/doom_loop_detector.rb +43 -0
- data/lib/rubino/security/hardline_guard.rb +105 -0
- data/lib/rubino/security/pattern_matcher.rb +62 -0
- data/lib/rubino/security/prefix_deriver.rb +124 -0
- data/lib/rubino/security/readonly_commands.rb +211 -0
- data/lib/rubino/session/exporter.rb +101 -0
- data/lib/rubino/session/message.rb +77 -0
- data/lib/rubino/session/repository.rb +295 -0
- data/lib/rubino/session/store.rb +198 -0
- data/lib/rubino/session/summary_store.rb +65 -0
- data/lib/rubino/skills/prompt_index.rb +85 -0
- data/lib/rubino/skills/registry.rb +208 -0
- data/lib/rubino/skills/skill.rb +176 -0
- data/lib/rubino/skills/skill_tool.rb +215 -0
- data/lib/rubino/skills/state_repository.rb +37 -0
- data/lib/rubino/skills/toggle.rb +26 -0
- data/lib/rubino/tools/answer_child_tool.rb +83 -0
- data/lib/rubino/tools/ask_parent_tool.rb +232 -0
- data/lib/rubino/tools/attach_file_tool.rb +120 -0
- data/lib/rubino/tools/background_tasks.rb +520 -0
- data/lib/rubino/tools/base.rb +222 -0
- data/lib/rubino/tools/custom_tool_loader.rb +119 -0
- data/lib/rubino/tools/edit_tool.rb +122 -0
- data/lib/rubino/tools/git_tool.rb +71 -0
- data/lib/rubino/tools/github_tool.rb +233 -0
- data/lib/rubino/tools/glob_tool.rb +69 -0
- data/lib/rubino/tools/grep_tool.rb +206 -0
- data/lib/rubino/tools/memory_tool.rb +184 -0
- data/lib/rubino/tools/multi_edit_tool.rb +110 -0
- data/lib/rubino/tools/patch_tool.rb +260 -0
- data/lib/rubino/tools/probe_tool.rb +175 -0
- data/lib/rubino/tools/question_tool.rb +128 -0
- data/lib/rubino/tools/read_attachment_tool.rb +180 -0
- data/lib/rubino/tools/read_tool.rb +212 -0
- data/lib/rubino/tools/read_tracker.rb +98 -0
- data/lib/rubino/tools/registry.rb +166 -0
- data/lib/rubino/tools/result.rb +113 -0
- data/lib/rubino/tools/ruby_tool.rb +0 -0
- data/lib/rubino/tools/session_search_tool.rb +103 -0
- data/lib/rubino/tools/shell_input_tool.rb +96 -0
- data/lib/rubino/tools/shell_kill_tool.rb +76 -0
- data/lib/rubino/tools/shell_output_tool.rb +72 -0
- data/lib/rubino/tools/shell_registry.rb +158 -0
- data/lib/rubino/tools/shell_tail_tool.rb +118 -0
- data/lib/rubino/tools/shell_tool.rb +330 -0
- data/lib/rubino/tools/steer_tool.rb +118 -0
- data/lib/rubino/tools/subagent_probe.rb +89 -0
- data/lib/rubino/tools/summarize_file_tool.rb +182 -0
- data/lib/rubino/tools/task_result_tool.rb +90 -0
- data/lib/rubino/tools/task_stop_tool.rb +80 -0
- data/lib/rubino/tools/task_tool.rb +622 -0
- data/lib/rubino/tools/test_tool.rb +454 -0
- data/lib/rubino/tools/todo_tool.rb +93 -0
- data/lib/rubino/tools/tool_call_repository.rb +33 -0
- data/lib/rubino/tools/vision_tool.rb +85 -0
- data/lib/rubino/tools/webfetch_tool.rb +153 -0
- data/lib/rubino/tools/websearch_tool.rb +179 -0
- data/lib/rubino/tools/write_tool.rb +61 -0
- data/lib/rubino/trust.rb +88 -0
- data/lib/rubino/ui/api.rb +296 -0
- data/lib/rubino/ui/base.rb +252 -0
- data/lib/rubino/ui/bottom_composer.rb +1599 -0
- data/lib/rubino/ui/cli.rb +1987 -0
- data/lib/rubino/ui/completion_menu.rb +321 -0
- data/lib/rubino/ui/completion_source.rb +284 -0
- data/lib/rubino/ui/escape_reader.rb +169 -0
- data/lib/rubino/ui/indented_io.rb +88 -0
- data/lib/rubino/ui/input_history.rb +108 -0
- data/lib/rubino/ui/live_region.rb +183 -0
- data/lib/rubino/ui/markdown_renderer.rb +506 -0
- data/lib/rubino/ui/notifier.rb +163 -0
- data/lib/rubino/ui/null.rb +195 -0
- data/lib/rubino/ui/paste_store.rb +176 -0
- data/lib/rubino/ui/printer_base.rb +79 -0
- data/lib/rubino/ui/probe_wait_indicator.rb +75 -0
- data/lib/rubino/ui/queued_indicators.rb +66 -0
- data/lib/rubino/ui/status_bar.rb +100 -0
- data/lib/rubino/ui/stdout_proxy.rb +161 -0
- data/lib/rubino/ui/streaming_markdown.rb +186 -0
- data/lib/rubino/ui/subagent_cards.rb +134 -0
- data/lib/rubino/ui/subagent_view.rb +255 -0
- data/lib/rubino/ui.rb +21 -0
- data/lib/rubino/update_check.rb +187 -0
- data/lib/rubino/util/duration.rb +23 -0
- data/lib/rubino/util/hyperlink.rb +105 -0
- data/lib/rubino/util/output.rb +145 -0
- data/lib/rubino/util/secrets_mask.rb +83 -0
- data/lib/rubino/version.rb +5 -0
- data/lib/rubino/workspace.rb +85 -0
- data/lib/rubino-agent.rb +5 -0
- data/lib/rubino.rb +318 -0
- data/mise.toml +2 -0
- data/rubino-agent.gemspec +103 -0
- data/skills/ruby-expert/SKILL.md +67 -0
- data/skills/ruby-expert/references/concurrency.md +357 -0
- data/skills/ruby-expert/references/datetime-and-encoding.md +363 -0
- data/skills/ruby-expert/references/errors-and-types.md +460 -0
- data/skills/ruby-expert/references/gem-authoring.md +459 -0
- data/skills/ruby-expert/references/language-idioms.md +465 -0
- data/skills/ruby-expert/references/metaprogramming.md +339 -0
- data/skills/ruby-expert/references/oo-design.md +553 -0
- data/skills/ruby-expert/references/performance.md +383 -0
- data/skills/ruby-expert/references/rails.md +424 -0
- data/skills/ruby-expert/references/security.md +404 -0
- data/skills/ruby-expert/references/testing.md +473 -0
- data/skills/ruby-expert/references/tooling.md +466 -0
- metadata +856 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sequel"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Rubino
|
|
7
|
+
module Database
|
|
8
|
+
# Manages the SQLite database connection via Sequel.
|
|
9
|
+
# Handles connection creation, WAL mode setup, and provides
|
|
10
|
+
# access to the underlying Sequel::Database instance.
|
|
11
|
+
class Connection
|
|
12
|
+
# SQLite path values that resolve to an ephemeral, in-memory database
|
|
13
|
+
# rather than an on-disk file. These must skip File.expand_path
|
|
14
|
+
# (which would turn ":memory:" into a literal "./:memory:" file) and
|
|
15
|
+
# FileUtils.mkdir_p on the parent directory.
|
|
16
|
+
MEMORY_PATHS = [":memory:", "file::memory:"].freeze
|
|
17
|
+
|
|
18
|
+
attr_reader :db_path
|
|
19
|
+
|
|
20
|
+
def initialize(db_path)
|
|
21
|
+
@db_path = memory_path?(db_path) ? db_path : File.expand_path(db_path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns the Sequel database connection (lazy-initialized)
|
|
25
|
+
def db
|
|
26
|
+
@db ||= connect!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Tests if the database is accessible
|
|
30
|
+
def healthy?
|
|
31
|
+
db.execute("SELECT 1")
|
|
32
|
+
true
|
|
33
|
+
rescue StandardError
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Closes the database connection
|
|
38
|
+
def close
|
|
39
|
+
@db&.disconnect
|
|
40
|
+
@db = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# True when @db_path refers to an in-memory SQLite instance.
|
|
44
|
+
def memory?
|
|
45
|
+
memory_path?(@db_path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def memory_path?(path)
|
|
51
|
+
MEMORY_PATHS.any? { |p| path == p } || path.to_s.start_with?("file::memory:")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def connect!
|
|
55
|
+
existed = memory? || File.exist?(@db_path)
|
|
56
|
+
FileUtils.mkdir_p(File.dirname(@db_path)) unless memory?
|
|
57
|
+
|
|
58
|
+
connection = Sequel.sqlite(@db_path)
|
|
59
|
+
|
|
60
|
+
# A freshly-created database holds session content — owner-only, like
|
|
61
|
+
# the rest of the home's secrets (#65). Creation-only so an operator
|
|
62
|
+
# who deliberately re-chmods an existing file is respected.
|
|
63
|
+
File.chmod(0o600, @db_path) unless existed
|
|
64
|
+
|
|
65
|
+
# WAL has no meaning for :memory: and triggers a warning; only apply on disk.
|
|
66
|
+
unless memory?
|
|
67
|
+
connection.run("PRAGMA journal_mode=WAL")
|
|
68
|
+
connection.run("PRAGMA synchronous=NORMAL")
|
|
69
|
+
end
|
|
70
|
+
connection.run("PRAGMA foreign_keys=ON")
|
|
71
|
+
connection.run("PRAGMA busy_timeout=5000")
|
|
72
|
+
|
|
73
|
+
connection
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:sessions) do
|
|
6
|
+
String :id, primary_key: true
|
|
7
|
+
String :parent_session_id
|
|
8
|
+
String :source, null: false
|
|
9
|
+
String :model
|
|
10
|
+
String :provider
|
|
11
|
+
String :title
|
|
12
|
+
Text :summary
|
|
13
|
+
String :status, null: false, default: "active"
|
|
14
|
+
Integer :message_count, null: false, default: 0
|
|
15
|
+
Integer :token_count, null: false, default: 0
|
|
16
|
+
String :created_at, null: false
|
|
17
|
+
String :updated_at, null: false
|
|
18
|
+
String :ended_at
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
create_table(:messages) do
|
|
22
|
+
String :id, primary_key: true
|
|
23
|
+
String :session_id, null: false
|
|
24
|
+
String :role, null: false
|
|
25
|
+
Text :content
|
|
26
|
+
String :tool_name
|
|
27
|
+
String :tool_call_id
|
|
28
|
+
Integer :token_count, default: 0
|
|
29
|
+
Text :metadata_json
|
|
30
|
+
String :created_at, null: false
|
|
31
|
+
|
|
32
|
+
foreign_key [:session_id], :sessions, key: :id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
add_index :messages, :session_id
|
|
36
|
+
add_index :messages, :created_at
|
|
37
|
+
|
|
38
|
+
create_table(:tool_calls) do
|
|
39
|
+
String :id, primary_key: true
|
|
40
|
+
String :session_id, null: false
|
|
41
|
+
String :message_id
|
|
42
|
+
String :tool_name, null: false
|
|
43
|
+
Text :input_json
|
|
44
|
+
Text :output
|
|
45
|
+
String :status, null: false
|
|
46
|
+
String :risk_level
|
|
47
|
+
String :started_at
|
|
48
|
+
String :finished_at
|
|
49
|
+
Text :error
|
|
50
|
+
|
|
51
|
+
foreign_key [:session_id], :sessions, key: :id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
add_index :tool_calls, :session_id
|
|
55
|
+
add_index :tool_calls, :tool_name
|
|
56
|
+
|
|
57
|
+
create_table(:memories) do
|
|
58
|
+
String :id, primary_key: true
|
|
59
|
+
String :kind, null: false
|
|
60
|
+
Text :content, null: false
|
|
61
|
+
String :source_session_id
|
|
62
|
+
Float :confidence, default: 1.0
|
|
63
|
+
Text :metadata_json
|
|
64
|
+
String :created_at, null: false
|
|
65
|
+
String :updated_at, null: false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
add_index :memories, :kind
|
|
69
|
+
add_index :memories, :created_at
|
|
70
|
+
|
|
71
|
+
create_table(:session_summaries) do
|
|
72
|
+
String :id, primary_key: true
|
|
73
|
+
String :session_id, null: false
|
|
74
|
+
String :parent_summary_id
|
|
75
|
+
Text :content, null: false
|
|
76
|
+
Integer :token_count, default: 0
|
|
77
|
+
String :created_at, null: false
|
|
78
|
+
|
|
79
|
+
foreign_key [:session_id], :sessions, key: :id
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
add_index :session_summaries, :session_id
|
|
83
|
+
|
|
84
|
+
create_table(:compactions) do
|
|
85
|
+
String :id, primary_key: true
|
|
86
|
+
String :source_session_id, null: false
|
|
87
|
+
String :target_session_id, null: false
|
|
88
|
+
String :previous_summary_id
|
|
89
|
+
String :new_summary_id
|
|
90
|
+
Integer :original_token_count
|
|
91
|
+
Integer :compacted_token_count
|
|
92
|
+
Integer :saved_token_count
|
|
93
|
+
String :created_at, null: false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
add_index :compactions, :source_session_id
|
|
97
|
+
|
|
98
|
+
create_table(:jobs) do
|
|
99
|
+
String :id, primary_key: true
|
|
100
|
+
String :type, null: false
|
|
101
|
+
String :status, null: false, default: "queued"
|
|
102
|
+
Integer :priority, null: false, default: 100
|
|
103
|
+
Text :payload_json, null: false
|
|
104
|
+
Integer :attempts, null: false, default: 0
|
|
105
|
+
Integer :max_attempts, null: false, default: 3
|
|
106
|
+
String :run_at, null: false
|
|
107
|
+
String :locked_at
|
|
108
|
+
String :locked_by
|
|
109
|
+
Text :last_error
|
|
110
|
+
String :created_at, null: false
|
|
111
|
+
String :updated_at, null: false
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
add_index :jobs, :status
|
|
115
|
+
add_index :jobs, :run_at
|
|
116
|
+
add_index :jobs, %i[status run_at]
|
|
117
|
+
|
|
118
|
+
create_table(:job_runs) do
|
|
119
|
+
String :id, primary_key: true
|
|
120
|
+
String :job_id, null: false
|
|
121
|
+
String :status, null: false
|
|
122
|
+
String :started_at, null: false
|
|
123
|
+
String :finished_at
|
|
124
|
+
Text :error
|
|
125
|
+
Text :metadata_json
|
|
126
|
+
|
|
127
|
+
foreign_key [:job_id], :jobs, key: :id
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
add_index :job_runs, :job_id
|
|
131
|
+
|
|
132
|
+
create_table(:events) do
|
|
133
|
+
String :id, primary_key: true
|
|
134
|
+
String :session_id
|
|
135
|
+
String :type, null: false
|
|
136
|
+
Text :payload_json
|
|
137
|
+
String :created_at, null: false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
add_index :events, :session_id
|
|
141
|
+
add_index :events, :type
|
|
142
|
+
add_index :events, :created_at
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
down do
|
|
146
|
+
drop_table(:events)
|
|
147
|
+
drop_table(:job_runs)
|
|
148
|
+
drop_table(:jobs)
|
|
149
|
+
drop_table(:compactions)
|
|
150
|
+
drop_table(:session_summaries)
|
|
151
|
+
drop_table(:memories)
|
|
152
|
+
drop_table(:tool_calls)
|
|
153
|
+
drop_table(:messages)
|
|
154
|
+
drop_table(:sessions)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:runs) do
|
|
6
|
+
String :id, primary_key: true
|
|
7
|
+
String :session_id, null: false
|
|
8
|
+
String :status, null: false, default: "queued" # queued|running|completed|failed|stopped
|
|
9
|
+
Text :input_text
|
|
10
|
+
Text :attachments_json
|
|
11
|
+
Text :skills_json
|
|
12
|
+
String :model
|
|
13
|
+
String :provider
|
|
14
|
+
Integer :tokens_input, default: 0
|
|
15
|
+
Integer :tokens_output, default: 0
|
|
16
|
+
Text :error
|
|
17
|
+
Boolean :stop_requested, null: false, default: false
|
|
18
|
+
String :started_at
|
|
19
|
+
String :finished_at
|
|
20
|
+
String :created_at, null: false
|
|
21
|
+
String :updated_at, null: false
|
|
22
|
+
|
|
23
|
+
foreign_key [:session_id], :sessions, key: :id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
add_index :runs, :session_id
|
|
27
|
+
add_index :runs, :status
|
|
28
|
+
|
|
29
|
+
alter_table(:events) do
|
|
30
|
+
add_column :run_id, String
|
|
31
|
+
add_column :seq, Integer # per-session monotonic seq for SSE Last-Event-ID
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
add_index :events, :run_id
|
|
35
|
+
add_index :events, %i[session_id seq]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
down do
|
|
39
|
+
alter_table(:events) do
|
|
40
|
+
drop_column :seq
|
|
41
|
+
drop_column :run_id
|
|
42
|
+
end
|
|
43
|
+
drop_table(:runs)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:skill_states) do
|
|
6
|
+
String :name, primary_key: true
|
|
7
|
+
Boolean :enabled, null: false, default: true
|
|
8
|
+
String :updated_at, null: false
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
down do
|
|
13
|
+
drop_table(:skill_states)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:cron_jobs) do
|
|
6
|
+
String :id, primary_key: true
|
|
7
|
+
String :name, null: false
|
|
8
|
+
String :schedule, null: false # cron expression
|
|
9
|
+
Text :prompt, null: false
|
|
10
|
+
Text :skills_json
|
|
11
|
+
String :model
|
|
12
|
+
String :provider
|
|
13
|
+
String :deliver, null: false, default: "local" # local|webhook
|
|
14
|
+
Boolean :enabled, null: false, default: true
|
|
15
|
+
String :last_run_at
|
|
16
|
+
String :last_run_id
|
|
17
|
+
String :created_at, null: false
|
|
18
|
+
String :updated_at, null: false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_index :cron_jobs, :enabled
|
|
22
|
+
add_index :cron_jobs, :name
|
|
23
|
+
|
|
24
|
+
alter_table(:runs) do
|
|
25
|
+
add_column :cron_job_id, String
|
|
26
|
+
end
|
|
27
|
+
add_index :runs, :cron_job_id
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
down do
|
|
31
|
+
alter_table(:runs) do
|
|
32
|
+
drop_column :cron_job_id
|
|
33
|
+
end
|
|
34
|
+
drop_table(:cron_jobs)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:oauth_connections) do
|
|
6
|
+
String :id, primary_key: true
|
|
7
|
+
String :provider, null: false
|
|
8
|
+
String :account_id, null: false
|
|
9
|
+
String :account_email
|
|
10
|
+
Text :access_token, null: false # encrypted
|
|
11
|
+
Text :refresh_token # encrypted
|
|
12
|
+
String :expires_at
|
|
13
|
+
Text :scopes_json, null: false
|
|
14
|
+
Text :metadata_json
|
|
15
|
+
String :created_at, null: false
|
|
16
|
+
String :updated_at, null: false
|
|
17
|
+
|
|
18
|
+
unique %i[provider account_id]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_index :oauth_connections, :provider
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
down do
|
|
25
|
+
drop_table(:oauth_connections)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
create_table(:webhook_deliveries) do
|
|
6
|
+
String :id, primary_key: true
|
|
7
|
+
String :job_id
|
|
8
|
+
String :run_id
|
|
9
|
+
String :target_url, null: false
|
|
10
|
+
# request_id (X-Rubino-Delivery-Id) is unique across delivery rows so
|
|
11
|
+
# a crash-then-restart cannot create two pending rows for the same logical
|
|
12
|
+
# attempt; the resume hook keys off this column.
|
|
13
|
+
String :request_id, null: false, unique: true
|
|
14
|
+
String :payload_sha256, null: false
|
|
15
|
+
Integer :attempt_count, null: false, default: 0
|
|
16
|
+
String :status, null: false, default: "pending" # pending|delivered|failed|dead
|
|
17
|
+
Text :last_error
|
|
18
|
+
Text :payload_json, null: false
|
|
19
|
+
String :scheduled_at, null: false
|
|
20
|
+
String :delivered_at
|
|
21
|
+
String :created_at, null: false
|
|
22
|
+
String :updated_at, null: false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
add_index :webhook_deliveries, :status
|
|
26
|
+
add_index :webhook_deliveries, :scheduled_at
|
|
27
|
+
add_index :webhook_deliveries, :job_id
|
|
28
|
+
add_index :webhook_deliveries, :run_id
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
down do
|
|
32
|
+
drop_table(:webhook_deliveries)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Full-text search over the `messages` table.
|
|
4
|
+
#
|
|
5
|
+
# Uses an external-content FTS5 table (content='messages') so the index never
|
|
6
|
+
# duplicates the body — FTS5 reaches into `messages` via rowid for each match.
|
|
7
|
+
# Triggers keep the index in sync on insert/update/delete; tokenizer is
|
|
8
|
+
# unicode61 with diacritic removal so "cafe"/"café" match.
|
|
9
|
+
Sequel.migration do
|
|
10
|
+
up do
|
|
11
|
+
run <<~SQL
|
|
12
|
+
CREATE VIRTUAL TABLE messages_fts USING fts5(
|
|
13
|
+
content,
|
|
14
|
+
tool_name,
|
|
15
|
+
role,
|
|
16
|
+
content='messages',
|
|
17
|
+
content_rowid='rowid',
|
|
18
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
19
|
+
);
|
|
20
|
+
SQL
|
|
21
|
+
|
|
22
|
+
run <<~SQL
|
|
23
|
+
CREATE TRIGGER messages_fts_ai AFTER INSERT ON messages BEGIN
|
|
24
|
+
INSERT INTO messages_fts(rowid, content, tool_name, role)
|
|
25
|
+
VALUES (new.rowid, new.content, new.tool_name, new.role);
|
|
26
|
+
END;
|
|
27
|
+
SQL
|
|
28
|
+
|
|
29
|
+
run <<~SQL
|
|
30
|
+
CREATE TRIGGER messages_fts_ad AFTER DELETE ON messages BEGIN
|
|
31
|
+
INSERT INTO messages_fts(messages_fts, rowid, content, tool_name, role)
|
|
32
|
+
VALUES ('delete', old.rowid, old.content, old.tool_name, old.role);
|
|
33
|
+
END;
|
|
34
|
+
SQL
|
|
35
|
+
|
|
36
|
+
run <<~SQL
|
|
37
|
+
CREATE TRIGGER messages_fts_au AFTER UPDATE ON messages BEGIN
|
|
38
|
+
INSERT INTO messages_fts(messages_fts, rowid, content, tool_name, role)
|
|
39
|
+
VALUES ('delete', old.rowid, old.content, old.tool_name, old.role);
|
|
40
|
+
INSERT INTO messages_fts(rowid, content, tool_name, role)
|
|
41
|
+
VALUES (new.rowid, new.content, new.tool_name, new.role);
|
|
42
|
+
END;
|
|
43
|
+
SQL
|
|
44
|
+
|
|
45
|
+
# Backfill any rows already present (no-op on a fresh DB; required when
|
|
46
|
+
# this migration runs against an existing install).
|
|
47
|
+
run <<~SQL
|
|
48
|
+
INSERT INTO messages_fts(rowid, content, tool_name, role)
|
|
49
|
+
SELECT rowid, content, tool_name, role FROM messages;
|
|
50
|
+
SQL
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
down do
|
|
54
|
+
run "DROP TRIGGER IF EXISTS messages_fts_au"
|
|
55
|
+
run "DROP TRIGGER IF EXISTS messages_fts_ad"
|
|
56
|
+
run "DROP TRIGGER IF EXISTS messages_fts_ai"
|
|
57
|
+
run "DROP TABLE IF EXISTS messages_fts"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tiny-Zep memory store for Memory::Backends::Sqlite.
|
|
4
|
+
#
|
|
5
|
+
# One ATOMIC declarative fact per row, with bi-temporal validity: `valid_from`
|
|
6
|
+
# is when the fact became true in the world, `valid_to` is set when a later,
|
|
7
|
+
# contradicting fact supersedes it (Graphiti-style edge invalidation). A "live"
|
|
8
|
+
# fact is `valid_to IS NULL`; superseded rows are kept (historical record), just
|
|
9
|
+
# excluded from injection. `entities_json` carries lightweight tags so a graph
|
|
10
|
+
# slice can be layered on later without a schema change.
|
|
11
|
+
#
|
|
12
|
+
# A companion FTS5 virtual table mirrors `text` (+ entities) for BM25 recall,
|
|
13
|
+
# kept in sync by triggers — same external-content pattern as messages_fts.
|
|
14
|
+
Sequel.migration do
|
|
15
|
+
up do
|
|
16
|
+
create_table?(:memory_facts) do
|
|
17
|
+
String :id, primary_key: true
|
|
18
|
+
Text :text, null: false
|
|
19
|
+
String :kind, null: false
|
|
20
|
+
Text :entities_json
|
|
21
|
+
String :source_session_id
|
|
22
|
+
Float :confidence, default: 1.0
|
|
23
|
+
String :valid_from
|
|
24
|
+
String :valid_to
|
|
25
|
+
String :superseded_by
|
|
26
|
+
File :embedding
|
|
27
|
+
String :created_at, null: false
|
|
28
|
+
String :updated_at, null: false
|
|
29
|
+
|
|
30
|
+
index :kind
|
|
31
|
+
index :valid_to
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
run <<~SQL
|
|
35
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_facts_fts USING fts5(
|
|
36
|
+
text,
|
|
37
|
+
entities,
|
|
38
|
+
content='memory_facts',
|
|
39
|
+
content_rowid='rowid',
|
|
40
|
+
tokenize='porter unicode61 remove_diacritics 2'
|
|
41
|
+
);
|
|
42
|
+
SQL
|
|
43
|
+
|
|
44
|
+
run <<~SQL
|
|
45
|
+
CREATE TRIGGER IF NOT EXISTS memory_facts_fts_ai AFTER INSERT ON memory_facts BEGIN
|
|
46
|
+
INSERT INTO memory_facts_fts(rowid, text, entities)
|
|
47
|
+
VALUES (new.rowid, new.text, new.entities_json);
|
|
48
|
+
END;
|
|
49
|
+
SQL
|
|
50
|
+
|
|
51
|
+
run <<~SQL
|
|
52
|
+
CREATE TRIGGER IF NOT EXISTS memory_facts_fts_ad AFTER DELETE ON memory_facts BEGIN
|
|
53
|
+
INSERT INTO memory_facts_fts(memory_facts_fts, rowid, text, entities)
|
|
54
|
+
VALUES ('delete', old.rowid, old.text, old.entities_json);
|
|
55
|
+
END;
|
|
56
|
+
SQL
|
|
57
|
+
|
|
58
|
+
run <<~SQL
|
|
59
|
+
CREATE TRIGGER IF NOT EXISTS memory_facts_fts_au AFTER UPDATE ON memory_facts BEGIN
|
|
60
|
+
INSERT INTO memory_facts_fts(memory_facts_fts, rowid, text, entities)
|
|
61
|
+
VALUES ('delete', old.rowid, old.text, old.entities_json);
|
|
62
|
+
INSERT INTO memory_facts_fts(rowid, text, entities)
|
|
63
|
+
VALUES (new.rowid, new.text, new.entities_json);
|
|
64
|
+
END;
|
|
65
|
+
SQL
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
down do
|
|
69
|
+
run "DROP TRIGGER IF EXISTS memory_facts_fts_au"
|
|
70
|
+
run "DROP TRIGGER IF EXISTS memory_facts_fts_ad"
|
|
71
|
+
run "DROP TRIGGER IF EXISTS memory_facts_fts_ai"
|
|
72
|
+
run "DROP TABLE IF EXISTS memory_facts_fts"
|
|
73
|
+
drop_table?(:memory_facts)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Graph-lite layer for Memory::Backends::Sqlite (Memory Phase 3b).
|
|
4
|
+
#
|
|
5
|
+
# Layers a tiny entity/relationship graph on top of the atomic-fact store so
|
|
6
|
+
# RELATIONAL queries ("what does X use for Y") surface facts connected via a
|
|
7
|
+
# 1-hop edge that pure FTS on the probe would miss. This is deliberately NOT a
|
|
8
|
+
# graph DB: two ordinary tables + bounded/recursive SQL.
|
|
9
|
+
#
|
|
10
|
+
# memory_entities — resolved nodes (people, tools, projects). `name_norm` is
|
|
11
|
+
# the lowercased key used for resolution/lookup so the same
|
|
12
|
+
# entity from different facts collapses to one node.
|
|
13
|
+
# memory_edges — typed relationships between two entities, each carrying
|
|
14
|
+
# the `source_fact_id` it was derived from and bi-temporal
|
|
15
|
+
# validity (`valid_from`/`valid_to`) exactly like facts:
|
|
16
|
+
# a contradicted relation is soft-retired, not deleted.
|
|
17
|
+
#
|
|
18
|
+
# An edge is "live" when `valid_to IS NULL`, matching the fact convention.
|
|
19
|
+
Sequel.migration do
|
|
20
|
+
up do
|
|
21
|
+
create_table?(:memory_entities) do
|
|
22
|
+
String :id, primary_key: true
|
|
23
|
+
String :name, null: false # display form, first-seen casing
|
|
24
|
+
String :name_norm, null: false # lowercased resolution key
|
|
25
|
+
String :kind # person | tool | project | ... (best-effort)
|
|
26
|
+
String :created_at, null: false
|
|
27
|
+
String :updated_at, null: false
|
|
28
|
+
|
|
29
|
+
index :name_norm, unique: true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
create_table?(:memory_edges) do
|
|
33
|
+
String :id, primary_key: true
|
|
34
|
+
String :src_entity_id, null: false
|
|
35
|
+
String :dst_entity_id, null: false
|
|
36
|
+
String :relation, null: false # lowercased relation label (uses, deploys_to, ...)
|
|
37
|
+
String :source_fact_id # the fact this edge was derived from
|
|
38
|
+
String :valid_from
|
|
39
|
+
String :valid_to # set when superseded; live edge = NULL
|
|
40
|
+
String :superseded_by # id of the edge that invalidated this one
|
|
41
|
+
String :created_at, null: false
|
|
42
|
+
String :updated_at, null: false
|
|
43
|
+
|
|
44
|
+
index :src_entity_id
|
|
45
|
+
index :dst_entity_id
|
|
46
|
+
index :valid_to
|
|
47
|
+
index %i[src_entity_id dst_entity_id relation]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
down do
|
|
52
|
+
drop_table?(:memory_edges)
|
|
53
|
+
drop_table?(:memory_entities)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Records the OS process that owns a live (status="active") session so an
|
|
4
|
+
# orphaned session — one whose process died without ending it (e.g. a hard
|
|
5
|
+
# terminal kill / SIGKILL that no trap can catch, #11) — can be reaped to
|
|
6
|
+
# "ended" the next time sessions are listed or resumed. NULL for ended
|
|
7
|
+
# sessions and for rows created before this migration.
|
|
8
|
+
Sequel.migration do
|
|
9
|
+
up do
|
|
10
|
+
alter_table(:sessions) do
|
|
11
|
+
add_column :owner_pid, Integer
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
down do
|
|
16
|
+
alter_table(:sessions) do
|
|
17
|
+
drop_column :owner_pid
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sequel"
|
|
4
|
+
require "sequel/extensions/migration"
|
|
5
|
+
|
|
6
|
+
module Rubino
|
|
7
|
+
module Database
|
|
8
|
+
# Handles database schema migrations in order.
|
|
9
|
+
# Migrations are stored as numbered Sequel migration files.
|
|
10
|
+
class Migrator
|
|
11
|
+
MIGRATIONS_PATH = File.expand_path("migrations", __dir__)
|
|
12
|
+
|
|
13
|
+
def initialize(connection)
|
|
14
|
+
@connection = connection
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Runs all pending migrations
|
|
18
|
+
def migrate!
|
|
19
|
+
Sequel::Migrator.run(@connection.db, MIGRATIONS_PATH)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns current migration version
|
|
23
|
+
def current_version
|
|
24
|
+
Sequel::Migrator.get_current_migration_version(@connection.db)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns true if there are unapplied migrations.
|
|
30
|
+
#
|
|
31
|
+
# Intentionally does NOT rescue: a connection/schema error here is a real
|
|
32
|
+
# health problem and must propagate so callers (e.g. doctor) can report a
|
|
33
|
+
# failure instead of silently treating an unreachable DB as "up to date".
|
|
34
|
+
def pending?
|
|
35
|
+
!Sequel::Migrator.is_current?(@connection.db, MIGRATIONS_PATH)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns list of pending migration files
|
|
39
|
+
def pending_migrations
|
|
40
|
+
Sequel::Migrator.migrator_class(MIGRATIONS_PATH)
|
|
41
|
+
.new(@connection.db, MIGRATIONS_PATH)
|
|
42
|
+
.files
|
|
43
|
+
rescue StandardError
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|