protege 0.1.0.alpha.2 → 0.1.0.alpha.7

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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +21 -0
  3. data/Rakefile +1 -3
  4. data/app/assets/builds/protege.css +76 -83
  5. data/app/controllers/concerns/protege/agent_scoped.rb +22 -0
  6. data/app/controllers/concerns/protege/agent_toolkit_scoped.rb +22 -0
  7. data/app/controllers/protege/access_rules_controller.rb +13 -13
  8. data/app/controllers/protege/agent_toolkit_rules_controller.rb +62 -0
  9. data/app/controllers/protege/agent_toolkits_controller.rb +44 -0
  10. data/app/controllers/protege/agents/agent_toolkits_controller.rb +65 -0
  11. data/app/controllers/protege/agents_controller.rb +118 -0
  12. data/app/controllers/protege/archives_controller.rb +18 -18
  13. data/app/controllers/protege/home_controller.rb +1 -1
  14. data/app/controllers/protege/messages_controller.rb +1 -1
  15. data/app/controllers/protege/replies_controller.rb +3 -3
  16. data/app/controllers/protege/responsibilities_controller.rb +7 -7
  17. data/app/controllers/protege/threads_controller.rb +4 -4
  18. data/app/controllers/protege/toolkits_controller.rb +84 -0
  19. data/app/helpers/protege/agents_helper.rb +75 -0
  20. data/app/helpers/protege/application_helper.rb +3 -2
  21. data/app/helpers/protege/components/badges_helper.rb +11 -2
  22. data/app/helpers/protege/components/forms_helper.rb +47 -12
  23. data/app/helpers/protege/components/typography_helper.rb +1 -1
  24. data/app/helpers/protege/messages_helper.rb +2 -2
  25. data/app/helpers/protege/responsibilities_helper.rb +2 -2
  26. data/app/helpers/protege/threads_helper.rb +11 -11
  27. data/app/helpers/protege/toolkits_helper.rb +66 -0
  28. data/app/hooks/protege/event_logger_hook.rb +8 -8
  29. data/app/jobs/protege/console_inference_job.rb +8 -8
  30. data/app/jobs/protege/inference_job.rb +11 -11
  31. data/app/jobs/protege/responsibility_job.rb +10 -10
  32. data/app/mailboxes/protege/agent_mailbox.rb +54 -29
  33. data/app/mailers/protege/alert_mailer.rb +41 -0
  34. data/app/mailers/protege/application_mailer.rb +9 -8
  35. data/app/models/concerns/protege/broadcastable_agent.rb +50 -0
  36. data/app/models/concerns/protege/broadcastable_toolkit.rb +49 -0
  37. data/app/models/concerns/protege/toolable.rb +108 -0
  38. data/app/models/protege/access_rule.rb +12 -12
  39. data/app/models/protege/{persona.rb → agent.rb} +45 -45
  40. data/app/models/protege/agent_toolkit.rb +50 -0
  41. data/app/models/protege/agent_toolkit_rule.rb +25 -0
  42. data/app/models/protege/application_record.rb +1 -1
  43. data/app/models/protege/email_thread.rb +16 -13
  44. data/app/models/protege/message.rb +36 -20
  45. data/app/models/protege/responsibility.rb +12 -12
  46. data/app/models/protege/tool_use.rb +6 -6
  47. data/app/models/protege/toolkit.rb +115 -0
  48. data/app/models/protege/trace.rb +1 -1
  49. data/app/providers/protege/open_router_provider.rb +17 -0
  50. data/app/resolvers/README.md +4 -4
  51. data/app/resolvers/protege/load_file_resolver.rb +5 -5
  52. data/app/resolvers/protege/load_text_resolver.rb +3 -3
  53. data/app/resolvers/protege/thread_history_resolver.rb +1 -1
  54. data/app/services/protege/message_search.rb +2 -2
  55. data/app/services/protege/system_toolkits.rb +67 -0
  56. data/app/tools/protege/create_file_tool.rb +2 -0
  57. data/app/tools/protege/read_attachment_tool.rb +3 -1
  58. data/app/tools/protege/search_emails_tool.rb +15 -13
  59. data/app/tools/protege/send_email_tool.rb +60 -18
  60. data/app/tools/protege/web_fetch_tool.rb +2 -0
  61. data/app/tools/protege/web_search_tool.rb +3 -1
  62. data/app/views/layouts/mailer.text.erb +1 -0
  63. data/app/views/protege/access_rules/create.turbo_stream.slim +3 -3
  64. data/app/views/protege/agent_toolkit_rules/create.turbo_stream.slim +6 -0
  65. data/app/views/protege/agent_toolkits/_gate_rule.html.slim +7 -0
  66. data/app/views/protege/agent_toolkits/_gate_rule_form.html.slim +8 -0
  67. data/app/views/protege/agent_toolkits/_gate_rules.html.slim +19 -0
  68. data/app/views/protege/agent_toolkits/show.html.slim +17 -0
  69. data/app/views/protege/{personas → agents}/_access_rule.html.slim +1 -1
  70. data/app/views/protege/{personas → agents}/_access_rule_form.html.slim +1 -1
  71. data/app/views/protege/{personas → agents}/_access_rules.html.slim +5 -5
  72. data/app/views/protege/agents/_actions.html.slim +15 -0
  73. data/app/views/protege/agents/_agent_sidebar_item.html.slim +5 -0
  74. data/app/views/protege/agents/_agent_toolkit.html.slim +7 -0
  75. data/app/views/protege/agents/_agent_toolkit_form.html.slim +7 -0
  76. data/app/views/protege/agents/_form.html.slim +25 -0
  77. data/app/views/protege/agents/_sidebar.html.slim +11 -0
  78. data/app/views/protege/agents/_toolkits.html.slim +19 -0
  79. data/app/views/protege/agents/agent_toolkits/create.turbo_stream.slim +6 -0
  80. data/app/views/protege/agents/edit.html.slim +7 -0
  81. data/app/views/protege/agents/index.html.slim +6 -0
  82. data/app/views/protege/agents/new.html.slim +7 -0
  83. data/app/views/protege/agents/show.html.slim +29 -0
  84. data/app/views/protege/alert_mailer/inference_failed.text.erb +4 -0
  85. data/app/views/protege/email_domains/show.html.slim +1 -1
  86. data/app/views/protege/home/show.html.slim +13 -13
  87. data/app/views/protege/responsibilities/_form.html.slim +2 -2
  88. data/app/views/protege/responsibilities/_responsibility_sidebar_item.html.slim +1 -1
  89. data/app/views/protege/responsibilities/new.html.slim +1 -1
  90. data/app/views/protege/responsibilities/show.html.slim +2 -2
  91. data/app/views/protege/responsibility_runs/show.html.slim +1 -1
  92. data/app/views/protege/shared/_header.html.slim +2 -1
  93. data/app/views/protege/shared/_hotkeys.html.slim +15 -11
  94. data/app/views/protege/threads/new.html.slim +2 -2
  95. data/app/views/protege/toolkits/_form.html.slim +13 -0
  96. data/app/views/protege/toolkits/_sidebar.html.slim +11 -0
  97. data/app/views/protege/toolkits/_toolkit_sidebar_item.html.slim +5 -0
  98. data/app/views/protege/toolkits/edit.html.slim +7 -0
  99. data/app/views/protege/toolkits/index.html.slim +6 -0
  100. data/app/views/protege/toolkits/new.html.slim +7 -0
  101. data/app/views/protege/toolkits/show.html.slim +13 -0
  102. data/config/routes.rb +12 -1
  103. data/db/migrate/20260707120000_replace_persona_active_with_archived_at.rb +1 -1
  104. data/db/migrate/20260708120000_add_disabled_tool_ids_to_personas.rb +1 -1
  105. data/db/migrate/20260728140000_scope_message_and_thread_uniqueness_per_persona.rb +16 -0
  106. data/db/migrate/20260812000001_create_protege_toolkits.rb +37 -0
  107. data/db/migrate/20260815000001_remove_disabled_tool_ids_from_personas.rb +21 -0
  108. data/db/migrate/20260815000002_add_key_to_protege_toolkits.rb +22 -0
  109. data/db/migrate/20260815100000_rename_personas_to_agents.rb +24 -0
  110. data/lib/generators/protege/agent/agent_generator.rb +35 -0
  111. data/lib/generators/protege/{persona/templates/persona.rb.tt → agent/templates/agent.rb.tt} +9 -9
  112. data/lib/generators/protege/extension_naming.rb +1 -1
  113. data/lib/generators/protege/hook/templates/hook.rb.tt +2 -2
  114. data/lib/generators/protege/install/install_generator.rb +16 -11
  115. data/lib/generators/protege/install/templates/initializer.rb.tt +17 -6
  116. data/lib/generators/protege/postfix/templates/deploy/mail/MAIL.md +2 -2
  117. data/lib/generators/protege/resolver/resolver_generator.rb +2 -2
  118. data/lib/generators/protege/resolver/templates/resolver.rb.tt +4 -4
  119. data/lib/generators/protege/tool/templates/tool.rb.tt +7 -5
  120. data/lib/protege/configuration.rb +43 -8
  121. data/lib/protege/engine.rb +3 -1
  122. data/lib/protege/errors/tool_not_available_error.rb +7 -6
  123. data/lib/protege/events/event.rb +2 -2
  124. data/lib/protege/events/inference_chunk_event.rb +1 -1
  125. data/lib/protege/events/inference_completed_event.rb +1 -1
  126. data/lib/protege/events/inference_failed_event.rb +1 -1
  127. data/lib/protege/events/inference_generated_event.rb +1 -1
  128. data/lib/protege/events/inference_max_turns_reached_event.rb +1 -1
  129. data/lib/protege/events/inference_started_event.rb +1 -1
  130. data/lib/protege/events/loop_run_completed_event.rb +1 -1
  131. data/lib/protege/events/loop_run_enqueued_event.rb +1 -1
  132. data/lib/protege/events/loop_run_failed_event.rb +1 -1
  133. data/lib/protege/events/loop_run_started_event.rb +1 -1
  134. data/lib/protege/events/tool_call_completed_event.rb +1 -1
  135. data/lib/protege/events/tool_call_failed_event.rb +1 -1
  136. data/lib/protege/events/tool_call_started_event.rb +1 -1
  137. data/lib/protege/events/tool_calls_received_event.rb +1 -1
  138. data/lib/protege/extensions/hook.rb +1 -0
  139. data/lib/protege/extensions/hook_mixin.rb +1 -1
  140. data/lib/protege/extensions/manifest.rb +67 -0
  141. data/lib/protege/extensions/manifested.rb +81 -0
  142. data/lib/protege/extensions/provider.rb +1 -0
  143. data/lib/protege/extensions/provider_mixin.rb +9 -17
  144. data/lib/protege/extensions/resolver.rb +2 -1
  145. data/lib/protege/extensions/resolver_mixin.rb +2 -2
  146. data/lib/protege/extensions/tool.rb +1 -0
  147. data/lib/protege/extensions/tool_mixin.rb +16 -40
  148. data/lib/protege/gateway/access_control.rb +19 -19
  149. data/lib/protege/gateway/access_policy.rb +2 -2
  150. data/lib/protege/gateway/mail/outbound.rb +22 -5
  151. data/lib/protege/gateway.rb +48 -37
  152. data/lib/protege/loop/scheduler.rb +1 -1
  153. data/lib/protege/orchestrator/context.rb +8 -8
  154. data/lib/protege/orchestrator/harness.rb +26 -24
  155. data/lib/protege/orchestrator/reply_context.rb +3 -3
  156. data/lib/protege/orchestrator/reply_harness.rb +8 -8
  157. data/lib/protege/orchestrator/resolver_chain.rb +3 -3
  158. data/lib/protege/orchestrator/responsibility_context.rb +4 -4
  159. data/lib/protege/orchestrator/responsibility_harness.rb +8 -8
  160. data/lib/protege/orchestrator.rb +7 -7
  161. data/lib/protege/subscribers/alerter.rb +117 -0
  162. data/lib/protege/subscribers/tracing.rb +1 -1
  163. data/lib/protege/version.rb +1 -1
  164. data/lib/protege.rb +2 -2
  165. data/lib/tasks/protege_tasks.rake +12 -0
  166. metadata +66 -23
  167. data/app/controllers/concerns/protege/persona_scoped.rb +0 -22
  168. data/app/controllers/protege/personas_controller.rb +0 -118
  169. data/app/helpers/protege/personas_helper.rb +0 -90
  170. data/app/models/concerns/protege/broadcastable_persona.rb +0 -50
  171. data/app/models/concerns/protege/tool_scoped.rb +0 -88
  172. data/app/views/protege/personas/_actions.html.slim +0 -15
  173. data/app/views/protege/personas/_form.html.slim +0 -29
  174. data/app/views/protege/personas/_persona_sidebar_item.html.slim +0 -5
  175. data/app/views/protege/personas/_sidebar.html.slim +0 -11
  176. data/app/views/protege/personas/edit.html.slim +0 -7
  177. data/app/views/protege/personas/index.html.slim +0 -6
  178. data/app/views/protege/personas/new.html.slim +0 -7
  179. data/app/views/protege/personas/show.html.slim +0 -37
  180. data/lib/generators/protege/persona/persona_generator.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e4802d01b5b207b82c7c34dcc937de6d49b393d9d49fdc8e0410e0504cb67a1
4
- data.tar.gz: b62d7562b055d40a2d884814aed9cf524f2c7e838ee30161d0f496abc649c8dc
3
+ metadata.gz: d413f57504bbdb848d33fd0afd7fdc616a409de2b5d3107c38a2d169bd1e1c6f
4
+ data.tar.gz: a80f880c36d5b1add111ca5176abfb27416fcb18ae63d2bf4504fd97d61e0912
5
5
  SHA512:
6
- metadata.gz: e4ba3c6c051cab06814ed5b3437f2a6e7164a97982dc23a14247d153ec19479a4caf30b325c48d50fe4d91ec41322ad8aba3193972b2af335b27d79de92f29d4
7
- data.tar.gz: b68781555b283c9e3954bced61070ec78df944c190439ad9d05505ad434b73cfc830c7a3691d0f8cbd26c0ab1b102a67ac9dd2e8c68a95ac4f3a47f9dc639250
6
+ metadata.gz: 0f9ebf231169742d602abad8f806793599636a6e15ec196d19672128e9ac123fb9db72ba4738e33cc86fd1bcf546dfb9f69ae0aebc9f935400a4a24347b584a0
7
+ data.tar.gz: 211573228c71e00cd37ea479419fe13cf7260bcc303443ed3720a7c991f9c2634e6fb1628958c3cb23f9e77121055e2d81a479a44d33eebf3ff7444964a6ff65
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Scholl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Rakefile CHANGED
@@ -8,9 +8,7 @@ require 'rake/testtask'
8
8
  # `git push origin HEAD`) is both redundant and fails on the detached HEAD. Skip it there so
9
9
  # `rake release` only builds and publishes the gem (via trusted publishing); local `rake release`
10
10
  # keeps its full behavior.
11
- if ENV['CI'] && Rake::Task.task_defined?('release:source_control_push')
12
- Rake::Task['release:source_control_push'].clear
13
- end
11
+ Rake::Task['release:source_control_push'].clear if ENV['CI'] && Rake::Task.task_defined?('release:source_control_push')
14
12
 
15
13
  Rake::TestTask.new(:test) do |t|
16
14
  t.libs << 'test'
@@ -1,4 +1,4 @@
1
- /*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
1
+ /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
2
  @layer properties;
3
3
  @layer theme, base, components, utilities;
4
4
  @layer theme {
@@ -38,6 +38,7 @@
38
38
  --font-weight-bold: 700;
39
39
  --tracking-wide: 0.025em;
40
40
  --tracking-wider: 0.05em;
41
+ --leading-snug: 1.375;
41
42
  --leading-relaxed: 1.625;
42
43
  --radius-sm: 0.25rem;
43
44
  --radius-md: 0.375rem;
@@ -218,10 +219,10 @@
218
219
  position: static;
219
220
  }
220
221
  .inset-0 {
221
- inset: 0;
222
+ inset: calc(var(--spacing) * 0);
222
223
  }
223
224
  .right-0 {
224
- right: 0;
225
+ right: calc(var(--spacing) * 0);
225
226
  }
226
227
  .z-10 {
227
228
  z-index: 10;
@@ -260,7 +261,7 @@
260
261
  margin-top: calc(var(--spacing) * 0.5);
261
262
  }
262
263
  .mt-1 {
263
- margin-top: var(--spacing);
264
+ margin-top: calc(var(--spacing) * 1);
264
265
  }
265
266
  .mt-2 {
266
267
  margin-top: calc(var(--spacing) * 2);
@@ -275,7 +276,7 @@
275
276
  margin-top: calc(var(--spacing) * 10);
276
277
  }
277
278
  .mb-1 {
278
- margin-bottom: var(--spacing);
279
+ margin-bottom: calc(var(--spacing) * 1);
279
280
  }
280
281
  .mb-3 {
281
282
  margin-bottom: calc(var(--spacing) * 3);
@@ -344,7 +345,7 @@
344
345
  height: 100%;
345
346
  }
346
347
  .min-h-0 {
347
- min-height: 0;
348
+ min-height: calc(var(--spacing) * 0);
348
349
  }
349
350
  .min-h-\[6rem\] {
350
351
  min-height: 6rem;
@@ -358,6 +359,9 @@
358
359
  .w-4 {
359
360
  width: calc(var(--spacing) * 4);
360
361
  }
362
+ .w-5 {
363
+ width: calc(var(--spacing) * 5);
364
+ }
361
365
  .w-9 {
362
366
  width: calc(var(--spacing) * 9);
363
367
  }
@@ -389,7 +393,7 @@
389
393
  max-width: var(--container-sm);
390
394
  }
391
395
  .min-w-0 {
392
- min-width: 0;
396
+ min-width: calc(var(--spacing) * 0);
393
397
  }
394
398
  .flex-1 {
395
399
  flex: 1;
@@ -401,7 +405,7 @@
401
405
  flex-shrink: 0;
402
406
  }
403
407
  .translate-x-1 {
404
- --tw-translate-x: var(--spacing);
408
+ --tw-translate-x: calc(var(--spacing) * 1);
405
409
  translate: var(--tw-translate-x) var(--tw-translate-y);
406
410
  }
407
411
  .translate-x-4 {
@@ -462,8 +466,14 @@
462
466
  .justify-center {
463
467
  justify-content: center;
464
468
  }
469
+ .justify-end {
470
+ justify-content: flex-end;
471
+ }
472
+ .gap-0\.5 {
473
+ gap: calc(var(--spacing) * 0.5);
474
+ }
465
475
  .gap-1 {
466
- gap: var(--spacing);
476
+ gap: calc(var(--spacing) * 1);
467
477
  }
468
478
  .gap-1\.5 {
469
479
  gap: calc(var(--spacing) * 1.5);
@@ -493,8 +503,8 @@
493
503
  .space-y-1 {
494
504
  :where(& > :not(:last-child)) {
495
505
  --tw-space-y-reverse: 0;
496
- margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse));
497
- margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)));
506
+ margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
507
+ margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
498
508
  }
499
509
  }
500
510
  .space-y-2 {
@@ -590,7 +600,10 @@
590
600
  background-color: var(--color-yellow-600);
591
601
  }
592
602
  .p-0 {
593
- padding: 0;
603
+ padding: calc(var(--spacing) * 0);
604
+ }
605
+ .p-1\.5 {
606
+ padding: calc(var(--spacing) * 1.5);
594
607
  }
595
608
  .p-5 {
596
609
  padding: calc(var(--spacing) * 5);
@@ -599,7 +612,7 @@
599
612
  padding: calc(var(--spacing) * 6);
600
613
  }
601
614
  .px-1 {
602
- padding-inline: var(--spacing);
615
+ padding-inline: calc(var(--spacing) * 1);
603
616
  }
604
617
  .px-1\.5 {
605
618
  padding-inline: calc(var(--spacing) * 1.5);
@@ -620,7 +633,7 @@
620
633
  padding-block: calc(var(--spacing) * 0.5);
621
634
  }
622
635
  .py-1 {
623
- padding-block: var(--spacing);
636
+ padding-block: calc(var(--spacing) * 1);
624
637
  }
625
638
  .py-1\.5 {
626
639
  padding-block: calc(var(--spacing) * 1.5);
@@ -637,12 +650,24 @@
637
650
  .py-8 {
638
651
  padding-block: calc(var(--spacing) * 8);
639
652
  }
653
+ .py-px {
654
+ padding-block: 1px;
655
+ }
656
+ .pt-1 {
657
+ padding-top: calc(var(--spacing) * 1);
658
+ }
640
659
  .pt-2 {
641
660
  padding-top: calc(var(--spacing) * 2);
642
661
  }
662
+ .pt-3 {
663
+ padding-top: calc(var(--spacing) * 3);
664
+ }
643
665
  .pr-4 {
644
666
  padding-right: calc(var(--spacing) * 4);
645
667
  }
668
+ .pr-6 {
669
+ padding-right: calc(var(--spacing) * 6);
670
+ }
646
671
  .pb-2 {
647
672
  padding-bottom: calc(var(--spacing) * 2);
648
673
  }
@@ -683,10 +708,21 @@
683
708
  font-size: var(--text-xs);
684
709
  line-height: var(--tw-leading, var(--text-xs--line-height));
685
710
  }
711
+ .text-\[10px\] {
712
+ font-size: 10px;
713
+ }
714
+ .leading-none {
715
+ --tw-leading: 1;
716
+ line-height: 1;
717
+ }
686
718
  .leading-relaxed {
687
719
  --tw-leading: var(--leading-relaxed);
688
720
  line-height: var(--leading-relaxed);
689
721
  }
722
+ .leading-snug {
723
+ --tw-leading: var(--leading-snug);
724
+ line-height: var(--leading-snug);
725
+ }
690
726
  .font-bold {
691
727
  --tw-font-weight: var(--font-weight-bold);
692
728
  font-weight: var(--font-weight-bold);
@@ -710,6 +746,9 @@
710
746
  .break-all {
711
747
  word-break: break-all;
712
748
  }
749
+ .whitespace-nowrap {
750
+ white-space: nowrap;
751
+ }
713
752
  .whitespace-pre-wrap {
714
753
  white-space: pre-wrap;
715
754
  }
@@ -747,6 +786,12 @@
747
786
  -webkit-font-smoothing: antialiased;
748
787
  -moz-osx-font-smoothing: grayscale;
749
788
  }
789
+ .opacity-60 {
790
+ opacity: 60%;
791
+ }
792
+ .opacity-70 {
793
+ opacity: 70%;
794
+ }
750
795
  .shadow-lg {
751
796
  --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
752
797
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -755,14 +800,16 @@
755
800
  --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
756
801
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
757
802
  }
758
- .filter {
759
- filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
760
- }
761
803
  .transition-colors {
762
804
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
763
805
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
764
806
  transition-duration: var(--tw-duration, var(--default-transition-duration));
765
807
  }
808
+ .transition-opacity {
809
+ transition-property: opacity;
810
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
811
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
812
+ }
766
813
  .transition-transform {
767
814
  transition-property: transform, translate, scale, rotate;
768
815
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -780,6 +827,11 @@
780
827
  }
781
828
  }
782
829
  }
830
+ .first\:pt-0 {
831
+ &:first-child {
832
+ padding-top: calc(var(--spacing) * 0);
833
+ }
834
+ }
783
835
  .hover\:text-amber-600 {
784
836
  &:hover {
785
837
  @media (hover: hover) {
@@ -815,6 +867,13 @@
815
867
  }
816
868
  }
817
869
  }
870
+ .hover\:opacity-100 {
871
+ &:hover {
872
+ @media (hover: hover) {
873
+ opacity: 100%;
874
+ }
875
+ }
876
+ }
818
877
  .focus\:border-amber-500 {
819
878
  &:focus {
820
879
  border-color: var(--color-amber-500);
@@ -1027,59 +1086,6 @@ body {
1027
1086
  inherits: false;
1028
1087
  initial-value: 0 0 #0000;
1029
1088
  }
1030
- @property --tw-blur {
1031
- syntax: "*";
1032
- inherits: false;
1033
- }
1034
- @property --tw-brightness {
1035
- syntax: "*";
1036
- inherits: false;
1037
- }
1038
- @property --tw-contrast {
1039
- syntax: "*";
1040
- inherits: false;
1041
- }
1042
- @property --tw-grayscale {
1043
- syntax: "*";
1044
- inherits: false;
1045
- }
1046
- @property --tw-hue-rotate {
1047
- syntax: "*";
1048
- inherits: false;
1049
- }
1050
- @property --tw-invert {
1051
- syntax: "*";
1052
- inherits: false;
1053
- }
1054
- @property --tw-opacity {
1055
- syntax: "*";
1056
- inherits: false;
1057
- }
1058
- @property --tw-saturate {
1059
- syntax: "*";
1060
- inherits: false;
1061
- }
1062
- @property --tw-sepia {
1063
- syntax: "*";
1064
- inherits: false;
1065
- }
1066
- @property --tw-drop-shadow {
1067
- syntax: "*";
1068
- inherits: false;
1069
- }
1070
- @property --tw-drop-shadow-color {
1071
- syntax: "*";
1072
- inherits: false;
1073
- }
1074
- @property --tw-drop-shadow-alpha {
1075
- syntax: "<percentage>";
1076
- inherits: false;
1077
- initial-value: 100%;
1078
- }
1079
- @property --tw-drop-shadow-size {
1080
- syntax: "*";
1081
- inherits: false;
1082
- }
1083
1089
  @keyframes pulse {
1084
1090
  50% {
1085
1091
  opacity: 0.5;
@@ -1110,19 +1116,6 @@ body {
1110
1116
  --tw-ring-offset-width: 0px;
1111
1117
  --tw-ring-offset-color: #fff;
1112
1118
  --tw-ring-offset-shadow: 0 0 #0000;
1113
- --tw-blur: initial;
1114
- --tw-brightness: initial;
1115
- --tw-contrast: initial;
1116
- --tw-grayscale: initial;
1117
- --tw-hue-rotate: initial;
1118
- --tw-invert: initial;
1119
- --tw-opacity: initial;
1120
- --tw-saturate: initial;
1121
- --tw-sepia: initial;
1122
- --tw-drop-shadow: initial;
1123
- --tw-drop-shadow-color: initial;
1124
- --tw-drop-shadow-alpha: 100%;
1125
- --tw-drop-shadow-size: initial;
1126
1119
  }
1127
1120
  }
1128
1121
  }
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protege
4
+ # Shared concern for controllers nested under an agent.
5
+ # Sets +@agent+ from +params[:agent_id]+.
6
+ module AgentScoped
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_action :set_agent
11
+ end
12
+
13
+ private
14
+
15
+ # Load the +Agent+ named by +params[:agent_id]+ into +@agent+.
16
+ #
17
+ # @return [Protege::Agent] the loaded agent
18
+ def set_agent
19
+ @agent = Agent.find(params[:agent_id])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protege
4
+ # Shared concern for controllers nested under a toolkit grant.
5
+ # Sets +@agent_toolkit+ from +params[:agent_toolkit_id]+.
6
+ module AgentToolkitScoped
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_action :set_agent_toolkit
11
+ end
12
+
13
+ private
14
+
15
+ # Load the +AgentToolkit+ named by +params[:agent_toolkit_id]+ into +@agent_toolkit+.
16
+ #
17
+ # @return [Protege::AgentToolkit] the loaded grant
18
+ def set_agent_toolkit
19
+ @agent_toolkit = AgentToolkit.find(params[:agent_toolkit_id])
20
+ end
21
+ end
22
+ end
@@ -1,35 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Protege
4
- # Manages a persona's runtime inbound access rules — the dashboard-editable, narrowing layer of the
5
- # access guardrail (see +AccessRule+ and +AccessControl+). Nested under personas via the
6
- # +PersonaScoped+ concern, which loads +@persona+ from +params[:persona_id]+ before every action; each
7
- # action renders back to the persona's page.
4
+ # Manages an agent's runtime inbound access rules — the dashboard-editable, narrowing layer of the
5
+ # access guardrail (see +AccessRule+ and +AccessControl+). Nested under agents via the
6
+ # +AgentScoped+ concern, which loads +@agent+ from +params[:agent_id]+ before every action; each
7
+ # action renders back to the agent's page.
8
8
  class AccessRulesController < ApplicationController
9
- include PersonaScoped
9
+ include AgentScoped
10
10
 
11
11
  before_action :set_access_rule, only: [:destroy]
12
12
 
13
- # Add an access rule to the persona. Serves +POST /personas/:persona_id/access_rules+.
13
+ # Add an access rule to the agent. Serves +POST /agents/:agent_id/access_rules+.
14
14
  #
15
15
  # Turbo requests update the rules table in place (append the row + reset the form on success, or
16
16
  # re-render the form with errors); non-Turbo requests redirect back with the result in the flash.
17
17
  #
18
18
  # @return [void]
19
19
  def create
20
- @access_rule = @persona.access_rules.build(access_rule_params)
20
+ @access_rule = @agent.access_rules.build(access_rule_params)
21
21
  saved = @access_rule.save
22
22
 
23
23
  respond_to do |format|
24
24
  format.turbo_stream { render :create, status: saved ? :ok : :unprocessable_entity }
25
- format.html { redirect_to persona_path(@persona), **create_flash(saved) }
25
+ format.html { redirect_to agent_path(@agent), **create_flash(saved) }
26
26
  end
27
27
  end
28
28
 
29
- # Remove an access rule from the persona. Serves
30
- # +DELETE /personas/:persona_id/access_rules/:id+.
29
+ # Remove an access rule from the agent. Serves
30
+ # +DELETE /agents/:agent_id/access_rules/:id+.
31
31
  #
32
- # Turbo requests remove the row in place; non-Turbo requests redirect back to the persona.
32
+ # Turbo requests remove the row in place; non-Turbo requests redirect back to the agent.
33
33
  #
34
34
  # @return [void]
35
35
  def destroy
@@ -37,7 +37,7 @@ module Protege
37
37
 
38
38
  respond_to do |format|
39
39
  format.turbo_stream { render turbo_stream: turbo_stream.remove(@access_rule) }
40
- format.html { redirect_to persona_path(@persona), notice: 'Access rule removed.' }
40
+ format.html { redirect_to agent_path(@agent), notice: 'Access rule removed.' }
41
41
  end
42
42
  end
43
43
 
@@ -59,7 +59,7 @@ module Protege
59
59
  #
60
60
  # @return [Protege::AccessRule] the access rule to destroy
61
61
  def set_access_rule
62
- @access_rule = @persona.access_rules.find(params[:id])
62
+ @access_rule = @agent.access_rules.find(params[:id])
63
63
  end
64
64
 
65
65
  # Permit the rule's kind and address pattern.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protege
4
+ # Manages a toolkit grant's allow/deny gate rules — the sender gate for one agent's copy of a toolkit.
5
+ # Nested under the grant via the +AgentToolkitScoped+ concern (which loads +@agent_toolkit+); mirrors
6
+ # +AccessRulesController+ (Turbo child rows), rendering back to the grant's page.
7
+ class AgentToolkitRulesController < ApplicationController
8
+ include AgentToolkitScoped
9
+
10
+ before_action :set_rule, only: [:destroy]
11
+
12
+ # Add a gate rule to the grant. Turbo requests append the row and reset the form; non-Turbo requests
13
+ # redirect back to the grant.
14
+ #
15
+ # @return [void]
16
+ def create
17
+ @agent_toolkit_rule = @agent_toolkit.agent_toolkit_rules.build(rule_params)
18
+ saved = @agent_toolkit_rule.save
19
+
20
+ respond_to do |format|
21
+ format.turbo_stream { render :create, status: saved ? :ok : :unprocessable_entity }
22
+ format.html { redirect_to agent_toolkit_path(@agent_toolkit), **create_flash(saved) }
23
+ end
24
+ end
25
+
26
+ # Remove a gate rule from the grant. Turbo requests remove the row in place.
27
+ #
28
+ # @return [void]
29
+ def destroy
30
+ @agent_toolkit_rule.destroy
31
+
32
+ respond_to do |format|
33
+ format.turbo_stream { render turbo_stream: turbo_stream.remove(@agent_toolkit_rule) }
34
+ format.html { redirect_to agent_toolkit_path(@agent_toolkit), notice: 'Rule removed.' }
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Load the rule to destroy, scoped to the grant.
41
+ #
42
+ # @return [Protege::AgentToolkitRule] the rule
43
+ def set_rule
44
+ @agent_toolkit_rule = @agent_toolkit.agent_toolkit_rules.find(params[:id])
45
+ end
46
+
47
+ # The flash for the non-Turbo create redirect — a success notice, or the validation errors.
48
+ #
49
+ # @param saved [Boolean] whether the rule saved
50
+ # @return [Hash] the +redirect_to+ flash options
51
+ def create_flash(saved)
52
+ saved ? { notice: 'Rule added.' } : { alert: @agent_toolkit_rule.errors.full_messages.to_sentence }
53
+ end
54
+
55
+ # Permit the rule's kind and address pattern.
56
+ #
57
+ # @return [ActionController::Parameters] the permitted +:kind+ and +:pattern+
58
+ def rule_params
59
+ params.require(:agent_toolkit_rule).permit(:kind, :pattern)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protege
4
+ # Manages a created toolkit grant — the page where you configure how one agent uses one toolkit: its
5
+ # proactive flag and its sender-gate rules (the rules nest under this via +AgentToolkitRulesController+).
6
+ # Top-level and keyed by the grant's own id, so nesting never goes two deep. Attaching/detaching grants
7
+ # lives in the nested +Agents::AgentToolkitsController+.
8
+ class AgentToolkitsController < ApplicationController
9
+ before_action :set_agent_toolkit
10
+
11
+ # Show the grant's configuration page (its toolkit's tools, proactive flag, and gate rules).
12
+ #
13
+ # @return [void]
14
+ def show; end
15
+
16
+ # Update the grant's proactive flag. On failure, re-render its page.
17
+ #
18
+ # @return [void]
19
+ def update
20
+ if @agent_toolkit.update(agent_toolkit_params)
21
+ redirect_to agent_toolkit_path(@agent_toolkit), notice: 'Grant updated.'
22
+ else
23
+ render :show, status: :unprocessable_entity
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # Load the grant named by the route.
30
+ #
31
+ # @return [Protege::AgentToolkit] the grant
32
+ def set_agent_toolkit
33
+ @agent_toolkit = AgentToolkit.find(params[:id])
34
+ end
35
+
36
+ # Permit the grant's editable attributes — only the proactive flag; the tool set is the toolkit's and
37
+ # the gate is edited as child rules.
38
+ #
39
+ # @return [ActionController::Parameters] the permitted attributes
40
+ def agent_toolkit_params
41
+ params.require(:agent_toolkit).permit(:allow_proactive)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protege
4
+ module Agents
5
+ # Attaches and detaches an agent's toolkit grants — the list of toolkits given to one agent, in the
6
+ # agent's scope. Nested under agents via +AgentScoped+; mirrors +AccessRulesController+ (Turbo
7
+ # child rows), rendering back to the agent's page. Managing a created grant (its gate, proactive
8
+ # flag, rules) is the top-level +AgentToolkitsController+.
9
+ class AgentToolkitsController < ApplicationController
10
+ include AgentScoped
11
+
12
+ before_action :set_agent_toolkit, only: [:destroy]
13
+
14
+ # Attach a toolkit to the agent. Turbo requests append the grant row and reset the form; non-Turbo
15
+ # requests redirect back to the agent.
16
+ #
17
+ # @return [void]
18
+ def create
19
+ @agent_toolkit = @agent.agent_toolkits.build(agent_toolkit_params)
20
+ saved = @agent_toolkit.save
21
+
22
+ respond_to do |format|
23
+ format.turbo_stream { render :create, status: saved ? :ok : :unprocessable_entity }
24
+ format.html { redirect_to agent_path(@agent), **create_flash(saved) }
25
+ end
26
+ end
27
+
28
+ # Detach a toolkit from the agent. Turbo requests remove the row in place.
29
+ #
30
+ # @return [void]
31
+ def destroy
32
+ @agent_toolkit.destroy
33
+
34
+ respond_to do |format|
35
+ format.turbo_stream { render turbo_stream: turbo_stream.remove(@agent_toolkit) }
36
+ format.html { redirect_to agent_path(@agent), notice: 'Toolkit detached.' }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Load the grant to destroy, scoped to the agent.
43
+ #
44
+ # @return [Protege::AgentToolkit] the grant
45
+ def set_agent_toolkit
46
+ @agent_toolkit = @agent.agent_toolkits.find(params[:id])
47
+ end
48
+
49
+ # The flash for the non-Turbo create redirect — a success notice, or the validation errors.
50
+ #
51
+ # @param saved [Boolean] whether the grant saved
52
+ # @return [Hash] the +redirect_to+ flash options
53
+ def create_flash(saved)
54
+ saved ? { notice: 'Toolkit attached.' } : { alert: @agent_toolkit.errors.full_messages.to_sentence }
55
+ end
56
+
57
+ # Permit the toolkit being attached.
58
+ #
59
+ # @return [ActionController::Parameters] the permitted +:toolkit_id+
60
+ def agent_toolkit_params
61
+ params.require(:agent_toolkit).permit(:toolkit_id)
62
+ end
63
+ end
64
+ end
65
+ end