protege 0.1.0.alpha.1
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/README.md +24 -0
- data/Rakefile +12 -0
- data/app/assets/builds/protege.css +1128 -0
- data/app/assets/javascripts/protege/attachment_picker.js +163 -0
- data/app/assets/javascripts/protege/copy.js +80 -0
- data/app/assets/javascripts/protege/forms.js +28 -0
- data/app/assets/javascripts/protege/keys.js +212 -0
- data/app/assets/javascripts/protege/resize.js +149 -0
- data/app/assets/javascripts/protege/theme.js +25 -0
- data/app/assets/stylesheets/protege/application.css +15 -0
- data/app/assets/tailwind/application.css +137 -0
- data/app/controllers/concerns/protege/persona_scoped.rb +22 -0
- data/app/controllers/concerns/protege/thread_scoped.rb +22 -0
- data/app/controllers/protege/access_rules_controller.rb +72 -0
- data/app/controllers/protege/application_controller.rb +18 -0
- data/app/controllers/protege/archives_controller.rb +52 -0
- data/app/controllers/protege/email_domains_controller.rb +72 -0
- data/app/controllers/protege/home_controller.rb +15 -0
- data/app/controllers/protege/messages_controller.rb +46 -0
- data/app/controllers/protege/mta_domains_syncs_controller.rb +22 -0
- data/app/controllers/protege/personas_controller.rb +118 -0
- data/app/controllers/protege/replies_controller.rb +64 -0
- data/app/controllers/protege/responsibilities_controller.rb +82 -0
- data/app/controllers/protege/responsibility_runs_controller.rb +46 -0
- data/app/controllers/protege/threads_controller.rb +75 -0
- data/app/controllers/protege/traces/searches_controller.rb +19 -0
- data/app/controllers/protege/traces_controller.rb +52 -0
- data/app/helpers/protege/application_helper.rb +30 -0
- data/app/helpers/protege/components/badges_helper.rb +59 -0
- data/app/helpers/protege/components/buttons_helper.rb +141 -0
- data/app/helpers/protege/components/cards_helper.rb +48 -0
- data/app/helpers/protege/components/components_helper.rb +30 -0
- data/app/helpers/protege/components/dialogs_helper.rb +36 -0
- data/app/helpers/protege/components/forms_helper.rb +223 -0
- data/app/helpers/protege/components/hotkeys_helper.rb +60 -0
- data/app/helpers/protege/components/icons_helper.rb +41 -0
- data/app/helpers/protege/components/introspection_helper.rb +162 -0
- data/app/helpers/protege/components/layout_helper.rb +169 -0
- data/app/helpers/protege/components/menus_helper.rb +68 -0
- data/app/helpers/protege/components/pagination_helper.rb +100 -0
- data/app/helpers/protege/components/sidebar_helper.rb +211 -0
- data/app/helpers/protege/components/tables_helper.rb +59 -0
- data/app/helpers/protege/components/typography_helper.rb +64 -0
- data/app/helpers/protege/email_domains_helper.rb +22 -0
- data/app/helpers/protege/messages_helper.rb +152 -0
- data/app/helpers/protege/personas_helper.rb +90 -0
- data/app/helpers/protege/responsibilities_helper.rb +63 -0
- data/app/helpers/protege/threads_helper.rb +118 -0
- data/app/helpers/protege/trace_helper.rb +22 -0
- data/app/hooks/protege/event_logger_hook.rb +109 -0
- data/app/jobs/protege/application_job.rb +41 -0
- data/app/jobs/protege/console_inference_job.rb +57 -0
- data/app/jobs/protege/inference_job.rb +60 -0
- data/app/jobs/protege/mail_provisioning_job.rb +21 -0
- data/app/jobs/protege/responsibility_job.rb +70 -0
- data/app/jobs/protege/responsibility_tick_job.rb +19 -0
- data/app/mailboxes/protege/agent_mailbox.rb +123 -0
- data/app/mailers/protege/application_mailer.rb +65 -0
- data/app/models/concerns/protege/broadcastable_email_domain.rb +48 -0
- data/app/models/concerns/protege/broadcastable_message.rb +47 -0
- data/app/models/concerns/protege/broadcastable_persona.rb +50 -0
- data/app/models/concerns/protege/broadcastable_responsibility.rb +51 -0
- data/app/models/concerns/protege/broadcastable_thread.rb +55 -0
- data/app/models/concerns/protege/tool_scoped.rb +88 -0
- data/app/models/protege/access_rule.rb +50 -0
- data/app/models/protege/application_record.rb +18 -0
- data/app/models/protege/current.rb +23 -0
- data/app/models/protege/email_domain.rb +136 -0
- data/app/models/protege/email_thread.rb +81 -0
- data/app/models/protege/message.rb +401 -0
- data/app/models/protege/persona.rb +229 -0
- data/app/models/protege/responsibility.rb +98 -0
- data/app/models/protege/responsibility_run.rb +68 -0
- data/app/models/protege/tool_use.rb +115 -0
- data/app/models/protege/trace.rb +71 -0
- data/app/providers/protege/open_router_provider.rb +317 -0
- data/app/resolvers/README.md +255 -0
- data/app/resolvers/protege/load_file_resolver.rb +86 -0
- data/app/resolvers/protege/load_record_resolver.rb +80 -0
- data/app/resolvers/protege/load_text_resolver.rb +40 -0
- data/app/resolvers/protege/thread_history_resolver.rb +116 -0
- data/app/services/protege/introspection_tool_call_presentation.rb +134 -0
- data/app/services/protege/message_search.rb +114 -0
- data/app/services/protege/trace_search.rb +102 -0
- data/app/subscribers/protege/introspection_broadcaster.rb +324 -0
- data/app/tools/protege/create_file_tool.rb +109 -0
- data/app/tools/protege/read_attachment_tool.rb +101 -0
- data/app/tools/protege/search_emails_tool.rb +129 -0
- data/app/tools/protege/send_email_tool.rb +240 -0
- data/app/tools/protege/web_fetch_tool.rb +224 -0
- data/app/tools/protege/web_search_tool.rb +131 -0
- data/app/views/layouts/protege/application.html.slim +19 -0
- data/app/views/protege/access_rules/create.turbo_stream.slim +6 -0
- data/app/views/protege/email_domains/_email_domain_sidebar_item.html.slim +4 -0
- data/app/views/protege/email_domains/_form.html.slim +10 -0
- data/app/views/protege/email_domains/_sidebar.html.slim +11 -0
- data/app/views/protege/email_domains/index.html.slim +6 -0
- data/app/views/protege/email_domains/new.html.slim +7 -0
- data/app/views/protege/email_domains/show.html.slim +36 -0
- data/app/views/protege/home/show.html.slim +26 -0
- data/app/views/protege/messages/_message.html.slim +19 -0
- data/app/views/protege/messages/_sidebar.html.slim +13 -0
- data/app/views/protege/messages/index.html.slim +20 -0
- data/app/views/protege/personas/_access_rule.html.slim +7 -0
- data/app/views/protege/personas/_access_rule_form.html.slim +8 -0
- data/app/views/protege/personas/_access_rules.html.slim +19 -0
- data/app/views/protege/personas/_actions.html.slim +15 -0
- data/app/views/protege/personas/_form.html.slim +29 -0
- data/app/views/protege/personas/_persona_sidebar_item.html.slim +5 -0
- data/app/views/protege/personas/_sidebar.html.slim +11 -0
- data/app/views/protege/personas/edit.html.slim +7 -0
- data/app/views/protege/personas/index.html.slim +6 -0
- data/app/views/protege/personas/new.html.slim +7 -0
- data/app/views/protege/personas/show.html.slim +37 -0
- data/app/views/protege/responsibilities/_form.html.slim +21 -0
- data/app/views/protege/responsibilities/_responsibility_sidebar_item.html.slim +5 -0
- data/app/views/protege/responsibilities/_sidebar.html.slim +11 -0
- data/app/views/protege/responsibilities/edit.html.slim +7 -0
- data/app/views/protege/responsibilities/index.html.slim +6 -0
- data/app/views/protege/responsibilities/new.html.slim +7 -0
- data/app/views/protege/responsibilities/show.html.slim +22 -0
- data/app/views/protege/responsibility_runs/show.html.slim +13 -0
- data/app/views/protege/shared/_confirm_dialog.html.slim +22 -0
- data/app/views/protege/shared/_header.html.slim +14 -0
- data/app/views/protege/shared/_hotkeys.html.slim +43 -0
- data/app/views/protege/threads/_composer_footer.html.slim +15 -0
- data/app/views/protege/threads/_introspection.html.slim +7 -0
- data/app/views/protege/threads/_reasoning.html.slim +4 -0
- data/app/views/protege/threads/_reply_form.html.slim +7 -0
- data/app/views/protege/threads/_run_marker.html.slim +3 -0
- data/app/views/protege/threads/_sidebar.html.slim +15 -0
- data/app/views/protege/threads/_thread.html.slim +6 -0
- data/app/views/protege/threads/_tool_call.html.slim +8 -0
- data/app/views/protege/threads/index.html.slim +6 -0
- data/app/views/protege/threads/new.html.slim +15 -0
- data/app/views/protege/threads/show.html.slim +25 -0
- data/app/views/protege/traces/_sidebar.html.slim +15 -0
- data/app/views/protege/traces/index.html.slim +11 -0
- data/app/views/protege/traces/searches/_sidebar.html.slim +13 -0
- data/app/views/protege/traces/searches/show.html.slim +22 -0
- data/app/views/protege/traces/show.html.slim +28 -0
- data/config/routes.rb +33 -0
- data/db/migrate/20260520000001_create_protege_tables.rb +105 -0
- data/db/migrate/20260706120130_create_protege_traces.rb +30 -0
- data/db/migrate/20260707120000_replace_persona_active_with_archived_at.rb +24 -0
- data/db/migrate/20260708120000_add_disabled_tool_ids_to_personas.rb +22 -0
- data/lib/generators/protege/extension_naming.rb +38 -0
- data/lib/generators/protege/hook/hook_generator.rb +33 -0
- data/lib/generators/protege/hook/templates/hook.rb.tt +36 -0
- data/lib/generators/protege/install/install_generator.rb +170 -0
- data/lib/generators/protege/install/templates/initializer.rb.tt +84 -0
- data/lib/generators/protege/persona/persona_generator.rb +35 -0
- data/lib/generators/protege/persona/templates/persona.rb.tt +31 -0
- data/lib/generators/protege/postfix/postfix_generator.rb +74 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/Dockerfile +43 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/KeyTable +3 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/MAIL.md +135 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/README.md +56 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/SigningTable +3 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/TrustedHosts +8 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/entrypoint.sh +118 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/kamal-accessory.example.yml +32 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/main.cf +68 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/opendkim.conf +36 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/opendmarc.conf +32 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/opendmarc.ignore.hosts +9 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/policyd-spf.conf +21 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/provision.py +182 -0
- data/lib/generators/protege/postfix/templates/deploy/mail/relay-to-app.sh +28 -0
- data/lib/generators/protege/postfix/templates/github/workflows/build-mail-image.example.yml +50 -0
- data/lib/generators/protege/provider/provider_generator.rb +44 -0
- data/lib/generators/protege/provider/templates/provider.rb.tt +50 -0
- data/lib/generators/protege/resolver/resolver_generator.rb +34 -0
- data/lib/generators/protege/resolver/templates/resolver.rb.tt +31 -0
- data/lib/generators/protege/tool/templates/tool.rb.tt +50 -0
- data/lib/generators/protege/tool/tool_generator.rb +34 -0
- data/lib/protege/configuration.rb +171 -0
- data/lib/protege/engine.rb +103 -0
- data/lib/protege/errors/bad_request_error.rb +8 -0
- data/lib/protege/errors/contract_violation_error.rb +8 -0
- data/lib/protege/errors/error.rb +30 -0
- data/lib/protege/errors/generic_error.rb +7 -0
- data/lib/protege/errors/internal_error.rb +8 -0
- data/lib/protege/errors/invalid_model_error.rb +8 -0
- data/lib/protege/errors/invalid_tool_result_error.rb +9 -0
- data/lib/protege/errors/rate_limited_error.rb +8 -0
- data/lib/protege/errors/resolver_file_not_found_error.rb +9 -0
- data/lib/protege/errors/response_parse_failed_error.rb +8 -0
- data/lib/protege/errors/timeout_error.rb +8 -0
- data/lib/protege/errors/tool_not_available_error.rb +12 -0
- data/lib/protege/errors/tool_not_found_error.rb +9 -0
- data/lib/protege/errors/unauthorized_error.rb +8 -0
- data/lib/protege/errors/unavailable_error.rb +8 -0
- data/lib/protege/errors/unsupported_capability_error.rb +8 -0
- data/lib/protege/events/event.rb +98 -0
- data/lib/protege/events/inference_chunk_event.rb +17 -0
- data/lib/protege/events/inference_completed_event.rb +12 -0
- data/lib/protege/events/inference_failed_event.rb +12 -0
- data/lib/protege/events/inference_generated_event.rb +19 -0
- data/lib/protege/events/inference_max_turns_reached_event.rb +12 -0
- data/lib/protege/events/inference_started_event.rb +11 -0
- data/lib/protege/events/loop_run_completed_event.rb +11 -0
- data/lib/protege/events/loop_run_enqueued_event.rb +12 -0
- data/lib/protege/events/loop_run_failed_event.rb +12 -0
- data/lib/protege/events/loop_run_started_event.rb +11 -0
- data/lib/protege/events/tool_call_completed_event.rb +12 -0
- data/lib/protege/events/tool_call_failed_event.rb +12 -0
- data/lib/protege/events/tool_call_started_event.rb +11 -0
- data/lib/protege/events/tool_calls_received_event.rb +11 -0
- data/lib/protege/extensions/hook.rb +13 -0
- data/lib/protege/extensions/hook_mixin.rb +90 -0
- data/lib/protege/extensions/provider.rb +12 -0
- data/lib/protege/extensions/provider_mixin.rb +124 -0
- data/lib/protege/extensions/resolver.rb +12 -0
- data/lib/protege/extensions/resolver_mixin.rb +58 -0
- data/lib/protege/extensions/tool.rb +12 -0
- data/lib/protege/extensions/tool_mixin.rb +217 -0
- data/lib/protege/gateway/access_control.rb +66 -0
- data/lib/protege/gateway/access_policy.rb +94 -0
- data/lib/protege/gateway/attachment_policy.rb +115 -0
- data/lib/protege/gateway/mail/address.rb +145 -0
- data/lib/protege/gateway/mail/attachment.rb +17 -0
- data/lib/protege/gateway/mail/message_id.rb +104 -0
- data/lib/protege/gateway/mail/outbound.rb +181 -0
- data/lib/protege/gateway/mail/references.rb +107 -0
- data/lib/protege/gateway.rb +398 -0
- data/lib/protege/inference/provider/request.rb +110 -0
- data/lib/protege/inference/provider/response.rb +50 -0
- data/lib/protege/inference/provider/tool_call.rb +52 -0
- data/lib/protege/inference.rb +121 -0
- data/lib/protege/loop/schedule.rb +66 -0
- data/lib/protege/loop/scheduler.rb +42 -0
- data/lib/protege/loop.rb +37 -0
- data/lib/protege/orchestrator/context.rb +65 -0
- data/lib/protege/orchestrator/harness.rb +305 -0
- data/lib/protege/orchestrator/reply_context.rb +21 -0
- data/lib/protege/orchestrator/reply_harness.rb +68 -0
- data/lib/protege/orchestrator/resolver_chain.rb +93 -0
- data/lib/protege/orchestrator/responsibility_context.rb +22 -0
- data/lib/protege/orchestrator/responsibility_harness.rb +60 -0
- data/lib/protege/orchestrator.rb +37 -0
- data/lib/protege/subscribers/hook_dispatcher.rb +140 -0
- data/lib/protege/subscribers/tracing.rb +65 -0
- data/lib/protege/types/document_part.rb +13 -0
- data/lib/protege/types/image_part.rb +11 -0
- data/lib/protege/types/model_message.rb +40 -0
- data/lib/protege/types/result.rb +113 -0
- data/lib/protege/types/stored_file.rb +127 -0
- data/lib/protege/types/text_part.rb +8 -0
- data/lib/protege/types/tool_result.rb +16 -0
- data/lib/protege/version.rb +6 -0
- data/lib/protege.rb +47 -0
- data/lib/tasks/protege_tasks.rake +16 -0
- metadata +494 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "sidebar", current: @persona
|
|
3
|
+
|
|
4
|
+
= page_container do
|
|
5
|
+
= page_header(@persona.name, @persona.class.display_name) do
|
|
6
|
+
= render("actions", persona: @persona)
|
|
7
|
+
|
|
8
|
+
.space-y-4
|
|
9
|
+
= info_card("Email address", { "Address" => @persona.email_address })
|
|
10
|
+
|
|
11
|
+
= card do
|
|
12
|
+
= heading_2("Instructions")
|
|
13
|
+
- if @persona.instructions.present?
|
|
14
|
+
.mt-3
|
|
15
|
+
= body_text(@persona.instructions)
|
|
16
|
+
- else
|
|
17
|
+
.mt-3
|
|
18
|
+
= hint("No instructions set yet — add them via Edit. They're used as the system prompt only when a resolver (e.g. LoadText) includes them in the agent's context.")
|
|
19
|
+
|
|
20
|
+
= card do
|
|
21
|
+
= heading_2("Available Tools")
|
|
22
|
+
- if @persona.available_tool_ids.any?
|
|
23
|
+
.mt-3.flex.flex-wrap.gap-2
|
|
24
|
+
- @persona.available_tool_ids.each do |tool_id|
|
|
25
|
+
= badge(tool_id)
|
|
26
|
+
- else
|
|
27
|
+
.mt-3
|
|
28
|
+
= hint("No tools are available to this persona — every tool in its scope has been disabled.")
|
|
29
|
+
|
|
30
|
+
= render("access_rules", persona: @persona)
|
|
31
|
+
|
|
32
|
+
= render("protege/shared/confirm_dialog",
|
|
33
|
+
id: "delete-persona",
|
|
34
|
+
title: "Delete #{@persona.name}?",
|
|
35
|
+
message: "This permanently removes the persona and ALL of its data — every thread, message, attachment, responsibility, access rule, and tool-use record. This cannot be undone. To keep the history but stop the persona instead, archive it.",
|
|
36
|
+
action_url: persona_path(@persona),
|
|
37
|
+
action_text: "Delete persona")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
- url = responsibility.new_record? ? responsibilities_path : responsibility_path(responsibility)
|
|
2
|
+
- method = responsibility.new_record? ? :post : :patch
|
|
3
|
+
|
|
4
|
+
= form_with url:, method:, scope: :responsibility, class: "space-y-5" do |f|
|
|
5
|
+
= form_errors(responsibility)
|
|
6
|
+
|
|
7
|
+
= form_select(f, :persona_id, all_personas.map { |p| [p.name, p.id] }, label: "Persona", prompt: "Choose a persona")
|
|
8
|
+
|
|
9
|
+
= form_field(f, :name, placeholder: "e.g. Morning digest")
|
|
10
|
+
|
|
11
|
+
= form_field(f, :schedule, label: "Schedule", placeholder: "0 9 * * 1-5", hint: "A 5-field cron expression (minute hour day-of-month month day-of-week) in the app timezone — e.g. \"0 9 * * 1-5\" is 9am on weekdays, \"*/15 * * * *\" every 15 minutes.")
|
|
12
|
+
|
|
13
|
+
= form_text_area(f, :instructions, label: "Instructions", rows: 8, hint: "The task the agent carries out on each run, delivered as the opening message. The persona's responsibility_resolvers chain supplies the system context around it.")
|
|
14
|
+
|
|
15
|
+
- unless responsibility.new_record?
|
|
16
|
+
= form_checkbox(f, :active, label: "Active")
|
|
17
|
+
|
|
18
|
+
.flex.items-center.gap-3.pt-2
|
|
19
|
+
= button(responsibility.new_record? ? "Create responsibility" : "Save changes", type: :submit)
|
|
20
|
+
|
|
21
|
+
= ui_link("Cancel", responsibility.new_record? ? responsibilities_path : responsibility_path(responsibility))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- pagy, responsibilities = paginate(all_responsibilities)
|
|
2
|
+
|
|
3
|
+
= sidebar_content do
|
|
4
|
+
= sidebar_header("Responsibilities (#{pagy.count})", new_path: new_responsibility_path)
|
|
5
|
+
|
|
6
|
+
= sidebar_stream("protege:responsibilities", pagy: pagy)
|
|
7
|
+
|
|
8
|
+
= sidebar_body(responsibilities, empty: "No responsibilities yet", id: "responsibilities_list") do |responsibility|
|
|
9
|
+
= render "responsibility_sidebar_item", responsibility: responsibility, active: local_assigns[:current] == responsibility
|
|
10
|
+
|
|
11
|
+
= sidebar_pagination(pagy)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "sidebar", current: @responsibility
|
|
3
|
+
|
|
4
|
+
= page_container do
|
|
5
|
+
= page_header(@responsibility.name, "#{@responsibility.persona.name} · #{@responsibility.schedule}") do
|
|
6
|
+
div
|
|
7
|
+
= ui_link("Edit", edit_responsibility_path(@responsibility), class: 'px-3')
|
|
8
|
+
= inline_button("Run now", responsibility_runs_path, params: { responsibility_run: { responsibility_id: @responsibility.id } })
|
|
9
|
+
|
|
10
|
+
.space-y-4
|
|
11
|
+
= info_card("Schedule", { "Cron" => @responsibility.schedule, "Persona" => @responsibility.persona.name, "Active" => (@responsibility.active? ? "Yes" : "No") })
|
|
12
|
+
|
|
13
|
+
= card do
|
|
14
|
+
= heading_2("Instructions")
|
|
15
|
+
.mt-3
|
|
16
|
+
= body_text(@responsibility.instructions)
|
|
17
|
+
|
|
18
|
+
= card do
|
|
19
|
+
.space-y-4
|
|
20
|
+
= heading_2("Recent runs")
|
|
21
|
+
- runs = responsibility_runs_table(@responsibility)
|
|
22
|
+
= table(headers: runs.headers, rows: runs.rows, empty: "No runs yet — use Run now to trigger one.")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
= page_container do
|
|
2
|
+
= page_header("Run · #{@responsibility.name}", @responsibility.persona.name) do
|
|
3
|
+
= ui_link("Back", responsibility_path(@responsibility))
|
|
4
|
+
|
|
5
|
+
.space-y-4
|
|
6
|
+
- details = { "Status" => @run.status, "Enqueued" => @run.created_at&.strftime("%Y-%m-%d %H:%M:%S"), "Started" => @run.started_at&.strftime("%Y-%m-%d %H:%M:%S"), "Finished" => @run.finished_at&.strftime("%Y-%m-%d %H:%M:%S"), "Correlation" => @run.correlation_id }
|
|
7
|
+
= info_card("Run", details, full_width: true)
|
|
8
|
+
|
|
9
|
+
- if @run.failed?
|
|
10
|
+
= card do
|
|
11
|
+
= heading_2("Error")
|
|
12
|
+
.mt-3
|
|
13
|
+
= body_text("#{@run.error_class}: #{@run.error_message}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/ Confirmation dialog.
|
|
2
|
+
/
|
|
3
|
+
/ Locals:
|
|
4
|
+
/ id: unique DOM id for the dialog
|
|
5
|
+
/ title: dialog heading
|
|
6
|
+
/ message: warning/explanation text
|
|
7
|
+
/ action_url: form action URL
|
|
8
|
+
/ action_text: confirm button label (default: "Confirm")
|
|
9
|
+
/ method: HTTP method (default: :delete)
|
|
10
|
+
|
|
11
|
+
- id ||= "confirm-dialog"
|
|
12
|
+
- action_text ||= "Confirm"
|
|
13
|
+
- method ||= :delete
|
|
14
|
+
|
|
15
|
+
= dialog(id) do
|
|
16
|
+
= heading_3(title)
|
|
17
|
+
|
|
18
|
+
p class="mt-2 text-sm text-gray-600 leading-relaxed"= message
|
|
19
|
+
|
|
20
|
+
div class="mt-6 flex items-center justify-between"
|
|
21
|
+
= button("Cancel", type: "button", onclick: "this.closest('dialog').close()")
|
|
22
|
+
= destructive_link(action_text, action_url, method: method)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
header class="flex h-14 flex-shrink-0 items-center justify-between px-5" style="border-bottom: 1px solid var(--border); background: var(--surface)"
|
|
2
|
+
div class="flex items-center gap-6"
|
|
3
|
+
= link_to Protege.configuration.nav_title, protege.root_path, class: "flex items-center gap-2 text-sm font-semibold"
|
|
4
|
+
|
|
5
|
+
nav class="flex items-center gap-1"
|
|
6
|
+
= nav_item("Inbox", protege.threads_path, request.path)
|
|
7
|
+
= nav_item("Responsibilities", protege.responsibilities_path, request.path)
|
|
8
|
+
= nav_item("Personas", protege.personas_path, request.path)
|
|
9
|
+
= nav_item("Domains", protege.email_domains_path, request.path)
|
|
10
|
+
= nav_item("Traces", protege.traces_path, request.path)
|
|
11
|
+
|
|
12
|
+
div class="flex items-center gap-3"
|
|
13
|
+
= hotkey_help_button
|
|
14
|
+
= theme_toggle
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/ The whole hotkey surface in one place (see keys.js): the global shortcut registry + the "?" help
|
|
2
|
+
/ overlay, rendered on every page.
|
|
3
|
+
/
|
|
4
|
+
/ Registry — the single source of truth for app-wide combos. Each is a hidden link keys.js activates on
|
|
5
|
+
/ its combo; it navigates via the same route helper the visible UI uses, so a global command works from
|
|
6
|
+
/ anywhere, independent of what's on screen. Nav combos are numbered by position (1..5, left to right)
|
|
7
|
+
/ rather than by initial — the "go to the Nth section" convention (⌘1..9 in browsers/editors). Rendered
|
|
8
|
+
/ after the page body, so a page-local hotkey reusing a combo is matched first and shadows the global one.
|
|
9
|
+
div hidden=true aria-hidden="true"
|
|
10
|
+
= link_to "Documentation", 'https://docs.protege.bot', data: { hotkey: "g 0" }, target: '_blank'
|
|
11
|
+
= link_to "Inbox", protege.threads_path, data: { hotkey: "g 1" }
|
|
12
|
+
= link_to "Responsibilities", protege.responsibilities_path, data: { hotkey: "g 2" }
|
|
13
|
+
= link_to "Personas", protege.personas_path, data: { hotkey: "g 3" }
|
|
14
|
+
= link_to "Domains", protege.email_domains_path, data: { hotkey: "g 4" }
|
|
15
|
+
= link_to "Traces", protege.traces_path, data: { hotkey: "g 5" }
|
|
16
|
+
= link_to "New Thread", protege.new_thread_path, data: { hotkey: "g c" }
|
|
17
|
+
= link_to "New Responsibility", protege.new_responsibility_path, data: { hotkey: "g r" }
|
|
18
|
+
= link_to "New Persona", protege.new_persona_path, data: { hotkey: "g p" }
|
|
19
|
+
= link_to "New Email Domain", protege.new_email_domain_path, data: { hotkey: "g d" }
|
|
20
|
+
|
|
21
|
+
/ Help overlay, opened with "?" or the header's "?" button (keys.js just toggles it open/closed). This
|
|
22
|
+
/ list is hand-authored — keep it in sync with the registry above; grouped into sections via hotkey_row
|
|
23
|
+
/ / hotkey_section (see Components::HotkeysHelper).
|
|
24
|
+
= dialog("protege-hotkey-help", data: { hotkey_help: "" }) do
|
|
25
|
+
= heading_2("Keyboard shortcuts")
|
|
26
|
+
.mt-3.space-y-1
|
|
27
|
+
= hotkey_section("Go to")
|
|
28
|
+
= hotkey_row("Inbox", "g", "1")
|
|
29
|
+
= hotkey_row("Responsibilities", "g", "2")
|
|
30
|
+
= hotkey_row("Personas", "g", "3")
|
|
31
|
+
= hotkey_row("Domains", "g", "4")
|
|
32
|
+
= hotkey_row("Traces", "g", "5")
|
|
33
|
+
= hotkey_row("Documentation", "g", "0")
|
|
34
|
+
|
|
35
|
+
= hotkey_section("Create")
|
|
36
|
+
= hotkey_row("New thread", "g", "c")
|
|
37
|
+
= hotkey_row("New responsibility", "g", "r")
|
|
38
|
+
= hotkey_row("New persona", "g", "p")
|
|
39
|
+
= hotkey_row("New email domain", "g", "d")
|
|
40
|
+
|
|
41
|
+
= hotkey_section("General")
|
|
42
|
+
= hotkey_row("Toggle theme", "g", "t")
|
|
43
|
+
= hotkey_row("Show this menu", "?")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/ Shared footer for the new-thread and reply composers: Send + shortcut hint on the left, the attach
|
|
2
|
+
/ (paperclip) control on the right, and any chosen files as removable badges below. The file input is
|
|
3
|
+
/ hidden — attachment_picker.js opens it from the paperclip and manages the badges. The enclosing form
|
|
4
|
+
/ must be multipart.
|
|
5
|
+
.space-y-2.flex-shrink-0 data-attachment-picker=true
|
|
6
|
+
= f.file_field :files, multiple: true, class: "hidden", data: { attachment_picker_input: true }
|
|
7
|
+
|
|
8
|
+
.flex.items-center.justify-between.pt-1
|
|
9
|
+
.flex.items-center.gap-3
|
|
10
|
+
= button("Send", type: :submit, data: { hotkey: "mod+enter" })
|
|
11
|
+
= hint("⌘ / Ctrl + ↵")
|
|
12
|
+
|
|
13
|
+
= icon_button(:paperclip, label: "Attach files", data: { attachment_picker_trigger: true })
|
|
14
|
+
|
|
15
|
+
ul.flex.flex-wrap.gap-1.5 hidden=true data-attachment-picker-list=true
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/ Thread introspection panel — the collapsible right sidebar surfacing the agent's live activity for this
|
|
2
|
+
/ thread: one chronological feed where reasoning tokens and tool-call cards are interleaved as they
|
|
3
|
+
/ happen. Fed entirely by IntrospectionBroadcaster over the thread's Turbo channel (turbo_stream_from
|
|
4
|
+
/ @thread in show.html.slim), which appends into the feed container. Live-only — a reload shows the
|
|
5
|
+
/ empty-state placeholder until the next run streams in.
|
|
6
|
+
= introspection_panel do
|
|
7
|
+
= introspection_feed(thread)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/ A reasoning block in the introspection activity feed — one paragraph of streamed reasoning. Appended to
|
|
2
|
+
/ the feed by IntrospectionBroadcaster when a reasoning turn opens; that turn's later tokens append into
|
|
3
|
+
/ it by id. Locals: id, content.
|
|
4
|
+
= introspection_reasoning(id: id, content: content)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
div class="relative flex flex-shrink-0 flex-col p-5" style="height: 13rem" data-resize="" data-resize-edge="top" data-resize-min="120" data-resize-max="520" data-resize-key="protege:reply-height"
|
|
2
|
+
= resize_handle(edge: :top)
|
|
3
|
+
= form_with url: thread_reply_path(thread), scope: :reply, multipart: true, html: { class: "flex min-h-0 flex-1 flex-col", data: { reset_on_submit: "" } } do |f|
|
|
4
|
+
= form_text_area(f, :body, placeholder: "Reply...", fill: true)
|
|
5
|
+
|
|
6
|
+
.mt-2
|
|
7
|
+
= render "protege/threads/composer_footer", f: f
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/ A run-lifecycle marker in the introspection activity feed — appended (processing) when a run starts and
|
|
2
|
+
/ replaced in place (done / failed) when it ends, by IntrospectionBroadcaster. Locals: id, status, detail.
|
|
3
|
+
= introspection_run_marker(id: id, status: status, detail: detail)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
- pagy, threads = paginate(threads)
|
|
2
|
+
|
|
3
|
+
= sidebar_content do
|
|
4
|
+
= sidebar_header("Inbox (#{pagy.count})", new_path: new_thread_path, new_label: "Compose")
|
|
5
|
+
|
|
6
|
+
= sidebar_search_link(messages_path, placeholder: "Search emails…")
|
|
7
|
+
|
|
8
|
+
= sidebar_stream("protege:threads", pagy: pagy)
|
|
9
|
+
|
|
10
|
+
= sidebar_body(threads, empty: "No threads yet", id: "threads_list") do |thread|
|
|
11
|
+
= render "protege/threads/thread", \
|
|
12
|
+
thread: thread, \
|
|
13
|
+
active: local_assigns[:active_thread] == thread
|
|
14
|
+
|
|
15
|
+
= sidebar_pagination(pagy)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/ One tool-call card in the introspection panel's live activity feed. Rendered from a broadcast with a
|
|
2
|
+
/ Protege::IntrospectionToolCallPresentation as +presentation+ — appended as a pending card when the model
|
|
3
|
+
/ requests the call, then replaced in place (by its stable card_id) with the completed/failed outcome.
|
|
4
|
+
= introspection_tool_card(id: presentation.card_id,
|
|
5
|
+
name: presentation.tool_name,
|
|
6
|
+
args: presentation.arguments_summary,
|
|
7
|
+
status: presentation.status,
|
|
8
|
+
preview: presentation.preview)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "protege/threads/sidebar", threads: inbox_threads
|
|
3
|
+
|
|
4
|
+
= page_container do
|
|
5
|
+
= page_header("New message", "Send a message to a persona to start a thread.")
|
|
6
|
+
|
|
7
|
+
= card do
|
|
8
|
+
= form_with url: threads_path, scope: :thread, class: "space-y-5", multipart: true do |f|
|
|
9
|
+
= form_select(f, :persona_id, available_persona_options, label: "To", prompt: "Select persona...")
|
|
10
|
+
|
|
11
|
+
= form_field(f, :subject, label: "Subject", placeholder: "Subject")
|
|
12
|
+
|
|
13
|
+
= form_text_area(f, :body, label: "Message", rows: 10, placeholder: "Write your message...")
|
|
14
|
+
|
|
15
|
+
= render "protege/threads/composer_footer", f: f
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "protege/threads/sidebar", threads: inbox_threads, active_thread: @thread
|
|
3
|
+
|
|
4
|
+
div class="flex min-h-0 flex-1"
|
|
5
|
+
div class="flex min-h-0 min-w-0 flex-1 flex-col"
|
|
6
|
+
= turbo_stream_from @thread
|
|
7
|
+
|
|
8
|
+
= thread_header(@thread) do
|
|
9
|
+
.flex.items-center.gap-2
|
|
10
|
+
= introspection_toggle
|
|
11
|
+
= button("Delete", type: "button", variant: :danger, onclick: show_dialog("delete-thread"))
|
|
12
|
+
|
|
13
|
+
div id=dom_id(@thread, :messages) class="min-h-0 flex-1 overflow-y-auto px-5"
|
|
14
|
+
= render partial: "protege/messages/message", collection: @messages, as: :message
|
|
15
|
+
|
|
16
|
+
= render "protege/threads/reply_form", thread: @thread
|
|
17
|
+
|
|
18
|
+
= render "protege/threads/introspection", thread: @thread
|
|
19
|
+
|
|
20
|
+
= render("protege/shared/confirm_dialog",
|
|
21
|
+
id: "delete-thread",
|
|
22
|
+
title: "Delete this thread?",
|
|
23
|
+
message: "This will permanently delete the thread and all its messages. This cannot be undone.",
|
|
24
|
+
action_url: thread_path(@thread),
|
|
25
|
+
action_text: "Delete thread")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
- pagy, page = paginate(traces)
|
|
2
|
+
|
|
3
|
+
= sidebar_content do
|
|
4
|
+
= sidebar_header("Traces (#{pagy.count})")
|
|
5
|
+
|
|
6
|
+
= sidebar_search_link(traces_search_path, placeholder: "Search traces…")
|
|
7
|
+
|
|
8
|
+
= sidebar_body(page, empty: "No traces captured yet", id: "traces_list") do |trace|
|
|
9
|
+
= sidebar_item \
|
|
10
|
+
"#{trace.id} | #{trace.created_at}", \
|
|
11
|
+
trace_path(trace), \
|
|
12
|
+
subtitle: trace.label.humanize, \
|
|
13
|
+
active: local_assigns[:current] == trace
|
|
14
|
+
|
|
15
|
+
= sidebar_pagination(pagy)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "sidebar", traces: @traces, current: nil
|
|
3
|
+
|
|
4
|
+
= page_empty do
|
|
5
|
+
- if tracing_enabled?
|
|
6
|
+
| Select a captured turn from the sidebar to review and label it.
|
|
7
|
+
- else
|
|
8
|
+
.max-w-sm.space-y-3.text-center
|
|
9
|
+
= hint("Tracing is off — no new turns are being captured.")
|
|
10
|
+
= code_block("config.tracing = { enabled: true }")
|
|
11
|
+
= hint("Set this in your Protege initializer to start collecting.")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- pagy, traces = paginate(results)
|
|
2
|
+
- empty_message = search.active? ? "No traces match your search" : "Enter search terms to find traces"
|
|
3
|
+
|
|
4
|
+
= sidebar_content(frame: "search_results") do
|
|
5
|
+
= sidebar_header("Results (#{pagy.count})")
|
|
6
|
+
|
|
7
|
+
= sidebar_body(traces, empty: empty_message) do |trace|
|
|
8
|
+
= sidebar_item(trace.correlation_id.presence&.truncate(28) || "Trace ##{trace.id}", trace_path(trace),
|
|
9
|
+
detail: trace.created_at.to_fs(:short),
|
|
10
|
+
subtitle: trace.model,
|
|
11
|
+
data: { turbo_frame: "_top" })
|
|
12
|
+
|
|
13
|
+
= sidebar_pagination(pagy)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "sidebar", search: @search, results: @traces
|
|
3
|
+
|
|
4
|
+
= page_container do
|
|
5
|
+
= page_header("Search traces", "Find captured turns by run, model, request or response text, note, or label. Matches appear in the sidebar.")
|
|
6
|
+
|
|
7
|
+
= card do
|
|
8
|
+
= form_with url: traces_search_path, method: :get, data: { turbo_frame: "search_results" }, class: "space-y-5" do |f|
|
|
9
|
+
= form_field(f, :correlation_id, label: "Run", placeholder: "correlation id contains…", value: params[:correlation_id], onblur: "this.form.requestSubmit()")
|
|
10
|
+
|
|
11
|
+
= form_field(f, :model, label: "Model", placeholder: "model contains…", value: params[:model], onblur: "this.form.requestSubmit()")
|
|
12
|
+
|
|
13
|
+
= form_select(f, :label, [["Any", ""]] + Protege::Trace.labels.keys.map { |l| [l.humanize, l] }, label: "Label", selected: params[:label], onchange: "this.form.requestSubmit()")
|
|
14
|
+
|
|
15
|
+
= form_field(f, :request, label: "Request", placeholder: "prompt or tools contain…", value: params[:request], onblur: "this.form.requestSubmit()")
|
|
16
|
+
|
|
17
|
+
= form_field(f, :response, label: "Response", placeholder: "output contains…", value: params[:response], onblur: "this.form.requestSubmit()")
|
|
18
|
+
|
|
19
|
+
= form_field(f, :annotation, label: "Annotation", placeholder: "note contains…", value: params[:annotation], onblur: "this.form.requestSubmit()")
|
|
20
|
+
|
|
21
|
+
.pt-2
|
|
22
|
+
= button("Search", type: :submit)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
- content_for :sidebar do
|
|
2
|
+
= render "sidebar", traces: recent_traces, current: @trace
|
|
3
|
+
|
|
4
|
+
= page_container do
|
|
5
|
+
= page_header("Turn #{@trace.turn_index}", @trace.model.presence || "no model recorded") do
|
|
6
|
+
= ui_link("Back", traces_path)
|
|
7
|
+
|
|
8
|
+
.space-y-4
|
|
9
|
+
= card do
|
|
10
|
+
= heading_2("Review")
|
|
11
|
+
= form_with model: @trace, url: trace_path(@trace), method: :patch, class: "mt-3 space-y-4" do |f|
|
|
12
|
+
= form_select(f, :label, Protege::Trace.labels.keys.map { |l| [l.humanize, l] }, label: "Label")
|
|
13
|
+
= form_text_area(f, :annotation, label: "Note", placeholder: "Why is this turn good/bad? (optional)")
|
|
14
|
+
.pt-2
|
|
15
|
+
= button("Save review", type: :submit)
|
|
16
|
+
|
|
17
|
+
- meta = { "Correlation ID" => @trace.correlation_id, "Model" => @trace.model, "Settings" => @trace.settings.presence&.to_json, "Label" => @trace.label.humanize, "Reviewed" => @trace.reviewed_at&.strftime("%Y-%m-%d %H:%M"), "Captured" => @trace.created_at.strftime("%Y-%m-%d %H:%M") }
|
|
18
|
+
= info_card("Turn", meta.compact, full_width: true)
|
|
19
|
+
|
|
20
|
+
= card do
|
|
21
|
+
= heading_2("Request")
|
|
22
|
+
pre class="mt-3 overflow-x-auto text-xs" style="white-space: pre-wrap; word-break: break-word"
|
|
23
|
+
= JSON.pretty_generate(@trace.request)
|
|
24
|
+
|
|
25
|
+
= card do
|
|
26
|
+
= heading_2("Response")
|
|
27
|
+
pre class="mt-3 overflow-x-auto text-xs" style="white-space: pre-wrap; word-break: break-word"
|
|
28
|
+
= JSON.pretty_generate(@trace.response)
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Never use 'only' or 'except' in a Rails engine's routes.rb: Any missing controller action will return 404, just
|
|
4
|
+
# like a missing route, so the engine's routes are always fully defined. The host app can override any route by
|
|
5
|
+
# defining it in its own routes.rb, but it cannot remove a route from the engine.
|
|
6
|
+
Protege::Engine.routes.draw do
|
|
7
|
+
root to: 'home#show'
|
|
8
|
+
|
|
9
|
+
# Trace search is its own view — Protege::Traces::SearchesController#show, reached from the traces
|
|
10
|
+
# sidebar's search link. Declared before +resources :traces+ so /traces/search resolves here rather
|
|
11
|
+
# than being captured as /traces/:id.
|
|
12
|
+
namespace :traces do
|
|
13
|
+
resource :search
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
resources :traces
|
|
17
|
+
resources :messages
|
|
18
|
+
resources :responsibilities
|
|
19
|
+
resources :responsibility_runs
|
|
20
|
+
|
|
21
|
+
resources :threads do
|
|
22
|
+
resource :reply
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
resources :personas do
|
|
26
|
+
resource :archive
|
|
27
|
+
resources :access_rules
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
resources :email_domains do
|
|
31
|
+
resource :mta_domains_sync
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Consolidated install migration for all Protege engine tables — the squashed replacement for the
|
|
4
|
+
# engine's incremental history, collapsed once every environment was synced to the final schema. It
|
|
5
|
+
# reuses the original first-migration version (20260520000001): any database that has ever run the engine
|
|
6
|
+
# already records this version and skips it, so only fresh installs execute it.
|
|
7
|
+
class CreateProtegeTables < ActiveRecord::Migration[7.2]
|
|
8
|
+
def change
|
|
9
|
+
create_table :protege_personas do |t|
|
|
10
|
+
t.string :type, null: false
|
|
11
|
+
t.string :name, null: false, index: { unique: true }
|
|
12
|
+
t.string :email_address, null: false, index: { unique: true }
|
|
13
|
+
t.boolean :active, null: false, default: true
|
|
14
|
+
t.text :instructions
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
create_table :protege_email_domains do |t|
|
|
19
|
+
t.string :domain, null: false, index: { unique: true }
|
|
20
|
+
t.string :dkim_selector, null: false, default: 'default'
|
|
21
|
+
t.text :dkim_private_key_pem, null: false
|
|
22
|
+
t.timestamps
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
create_table :protege_email_threads do |t|
|
|
26
|
+
t.references :persona, null: false, foreign_key: { to_table: :protege_personas }
|
|
27
|
+
t.string :thread_id, null: false, index: { unique: true }
|
|
28
|
+
t.string :subject
|
|
29
|
+
t.integer :message_count, null: false, default: 0
|
|
30
|
+
t.datetime :last_message_at, index: true
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
create_table :protege_messages do |t|
|
|
35
|
+
t.references :email_thread, null: false, foreign_key: { to_table: :protege_email_threads }
|
|
36
|
+
t.references :persona, null: false, foreign_key: { to_table: :protege_personas }
|
|
37
|
+
t.references :inbound_email, null: true, foreign_key: { to_table: :action_mailbox_inbound_emails }
|
|
38
|
+
t.string :message_id, null: false, index: { unique: true }
|
|
39
|
+
t.string :direction, null: false, index: true
|
|
40
|
+
t.string :from_address, null: false
|
|
41
|
+
t.text :to_addresses
|
|
42
|
+
t.text :cc_addresses
|
|
43
|
+
t.text :bcc_addresses
|
|
44
|
+
t.string :reply_to
|
|
45
|
+
t.string :subject
|
|
46
|
+
t.string :in_reply_to
|
|
47
|
+
t.text :references_header
|
|
48
|
+
t.text :text_body
|
|
49
|
+
t.text :html_body
|
|
50
|
+
t.datetime :sent_at, null: false, index: true
|
|
51
|
+
t.string :correlation_id, index: true
|
|
52
|
+
t.string :processing_status, index: true
|
|
53
|
+
t.datetime :processing_started_at
|
|
54
|
+
t.datetime :processing_completed_at
|
|
55
|
+
t.integer :processing_attempts, null: false, default: 0
|
|
56
|
+
t.text :last_processing_error
|
|
57
|
+
t.datetime :delivered_at
|
|
58
|
+
t.text :delivery_error
|
|
59
|
+
t.datetime :read_at
|
|
60
|
+
t.timestamps
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
create_table :protege_access_rules do |t|
|
|
64
|
+
t.references :persona, null: false, foreign_key: { to_table: :protege_personas }
|
|
65
|
+
t.string :kind, null: false
|
|
66
|
+
t.string :pattern, null: false
|
|
67
|
+
t.timestamps
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
create_table :protege_responsibilities do |t|
|
|
71
|
+
t.references :persona, null: false, foreign_key: { to_table: :protege_personas }
|
|
72
|
+
t.string :name, null: false
|
|
73
|
+
t.text :instructions, null: false
|
|
74
|
+
t.string :schedule, null: false
|
|
75
|
+
t.boolean :active, null: false, default: true
|
|
76
|
+
t.datetime :last_run_at
|
|
77
|
+
t.timestamps
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
create_table :protege_responsibility_runs do |t|
|
|
81
|
+
t.references :responsibility, null: false, foreign_key: { to_table: :protege_responsibilities }
|
|
82
|
+
t.string :status, null: false, default: 'pending', index: true
|
|
83
|
+
t.datetime :started_at
|
|
84
|
+
t.datetime :finished_at
|
|
85
|
+
t.string :error_class
|
|
86
|
+
t.text :error_message
|
|
87
|
+
t.string :correlation_id
|
|
88
|
+
t.timestamps
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
create_table :protege_tool_uses do |t|
|
|
92
|
+
t.references :source, polymorphic: true, null: false
|
|
93
|
+
t.references :persona, null: false, foreign_key: { to_table: :protege_personas }
|
|
94
|
+
t.string :run_id
|
|
95
|
+
t.integer :turn_index, null: false
|
|
96
|
+
t.integer :position, null: false
|
|
97
|
+
t.string :tool_call_id
|
|
98
|
+
t.string :tool_name, null: false
|
|
99
|
+
t.text :arguments
|
|
100
|
+
t.text :result
|
|
101
|
+
t.boolean :succeeded, null: false, default: false
|
|
102
|
+
t.timestamps
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|