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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protege
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.2
4
+ version: 0.1.0.alpha.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Scholl
@@ -205,14 +205,17 @@ dependencies:
205
205
  - - "~>"
206
206
  - !ruby/object:Gem::Version
207
207
  version: '3.0'
208
- description: Protege is an email-native AI agent toolkit focused on simplicity, interoperability,
209
- and self-sovereignty.
208
+ description: 'Protege gives your Rails app AI teammates: inbound mail via Action Mailbox,
209
+ replies over SMTP, tools that act on your own models, cron-scheduled responsibilities
210
+ dispatched through Active Job, and a built-in dashboard to manage and observe it
211
+ all — in process, no separate service.'
210
212
  email:
211
213
  - sebscholl@gmail.com
212
214
  executables: []
213
215
  extensions: []
214
216
  extra_rdoc_files: []
215
217
  files:
218
+ - MIT-LICENSE
216
219
  - README.md
217
220
  - Rakefile
218
221
  - app/assets/builds/protege.css
@@ -224,22 +227,28 @@ files:
224
227
  - app/assets/javascripts/protege/theme.js
225
228
  - app/assets/stylesheets/protege/application.css
226
229
  - app/assets/tailwind/application.css
227
- - app/controllers/concerns/protege/persona_scoped.rb
230
+ - app/controllers/concerns/protege/agent_scoped.rb
231
+ - app/controllers/concerns/protege/agent_toolkit_scoped.rb
228
232
  - app/controllers/concerns/protege/thread_scoped.rb
229
233
  - app/controllers/protege/access_rules_controller.rb
234
+ - app/controllers/protege/agent_toolkit_rules_controller.rb
235
+ - app/controllers/protege/agent_toolkits_controller.rb
236
+ - app/controllers/protege/agents/agent_toolkits_controller.rb
237
+ - app/controllers/protege/agents_controller.rb
230
238
  - app/controllers/protege/application_controller.rb
231
239
  - app/controllers/protege/archives_controller.rb
232
240
  - app/controllers/protege/email_domains_controller.rb
233
241
  - app/controllers/protege/home_controller.rb
234
242
  - app/controllers/protege/messages_controller.rb
235
243
  - app/controllers/protege/mta_domains_syncs_controller.rb
236
- - app/controllers/protege/personas_controller.rb
237
244
  - app/controllers/protege/replies_controller.rb
238
245
  - app/controllers/protege/responsibilities_controller.rb
239
246
  - app/controllers/protege/responsibility_runs_controller.rb
240
247
  - app/controllers/protege/threads_controller.rb
248
+ - app/controllers/protege/toolkits_controller.rb
241
249
  - app/controllers/protege/traces/searches_controller.rb
242
250
  - app/controllers/protege/traces_controller.rb
251
+ - app/helpers/protege/agents_helper.rb
243
252
  - app/helpers/protege/application_helper.rb
244
253
  - app/helpers/protege/components/badges_helper.rb
245
254
  - app/helpers/protege/components/buttons_helper.rb
@@ -258,9 +267,9 @@ files:
258
267
  - app/helpers/protege/components/typography_helper.rb
259
268
  - app/helpers/protege/email_domains_helper.rb
260
269
  - app/helpers/protege/messages_helper.rb
261
- - app/helpers/protege/personas_helper.rb
262
270
  - app/helpers/protege/responsibilities_helper.rb
263
271
  - app/helpers/protege/threads_helper.rb
272
+ - app/helpers/protege/toolkits_helper.rb
264
273
  - app/helpers/protege/trace_helper.rb
265
274
  - app/hooks/protege/event_logger_hook.rb
266
275
  - app/jobs/protege/application_job.rb
@@ -270,23 +279,28 @@ files:
270
279
  - app/jobs/protege/responsibility_job.rb
271
280
  - app/jobs/protege/responsibility_tick_job.rb
272
281
  - app/mailboxes/protege/agent_mailbox.rb
282
+ - app/mailers/protege/alert_mailer.rb
273
283
  - app/mailers/protege/application_mailer.rb
284
+ - app/models/concerns/protege/broadcastable_agent.rb
274
285
  - app/models/concerns/protege/broadcastable_email_domain.rb
275
286
  - app/models/concerns/protege/broadcastable_message.rb
276
- - app/models/concerns/protege/broadcastable_persona.rb
277
287
  - app/models/concerns/protege/broadcastable_responsibility.rb
278
288
  - app/models/concerns/protege/broadcastable_thread.rb
279
- - app/models/concerns/protege/tool_scoped.rb
289
+ - app/models/concerns/protege/broadcastable_toolkit.rb
290
+ - app/models/concerns/protege/toolable.rb
280
291
  - app/models/protege/access_rule.rb
292
+ - app/models/protege/agent.rb
293
+ - app/models/protege/agent_toolkit.rb
294
+ - app/models/protege/agent_toolkit_rule.rb
281
295
  - app/models/protege/application_record.rb
282
296
  - app/models/protege/current.rb
283
297
  - app/models/protege/email_domain.rb
284
298
  - app/models/protege/email_thread.rb
285
299
  - app/models/protege/message.rb
286
- - app/models/protege/persona.rb
287
300
  - app/models/protege/responsibility.rb
288
301
  - app/models/protege/responsibility_run.rb
289
302
  - app/models/protege/tool_use.rb
303
+ - app/models/protege/toolkit.rb
290
304
  - app/models/protege/trace.rb
291
305
  - app/providers/protege/open_router_provider.rb
292
306
  - app/resolvers/README.md
@@ -296,6 +310,7 @@ files:
296
310
  - app/resolvers/protege/thread_history_resolver.rb
297
311
  - app/services/protege/introspection_tool_call_presentation.rb
298
312
  - app/services/protege/message_search.rb
313
+ - app/services/protege/system_toolkits.rb
299
314
  - app/services/protege/trace_search.rb
300
315
  - app/subscribers/protege/introspection_broadcaster.rb
301
316
  - app/tools/protege/create_file_tool.rb
@@ -304,8 +319,30 @@ files:
304
319
  - app/tools/protege/send_email_tool.rb
305
320
  - app/tools/protege/web_fetch_tool.rb
306
321
  - app/tools/protege/web_search_tool.rb
322
+ - app/views/layouts/mailer.text.erb
307
323
  - app/views/layouts/protege/application.html.slim
308
324
  - app/views/protege/access_rules/create.turbo_stream.slim
325
+ - app/views/protege/agent_toolkit_rules/create.turbo_stream.slim
326
+ - app/views/protege/agent_toolkits/_gate_rule.html.slim
327
+ - app/views/protege/agent_toolkits/_gate_rule_form.html.slim
328
+ - app/views/protege/agent_toolkits/_gate_rules.html.slim
329
+ - app/views/protege/agent_toolkits/show.html.slim
330
+ - app/views/protege/agents/_access_rule.html.slim
331
+ - app/views/protege/agents/_access_rule_form.html.slim
332
+ - app/views/protege/agents/_access_rules.html.slim
333
+ - app/views/protege/agents/_actions.html.slim
334
+ - app/views/protege/agents/_agent_sidebar_item.html.slim
335
+ - app/views/protege/agents/_agent_toolkit.html.slim
336
+ - app/views/protege/agents/_agent_toolkit_form.html.slim
337
+ - app/views/protege/agents/_form.html.slim
338
+ - app/views/protege/agents/_sidebar.html.slim
339
+ - app/views/protege/agents/_toolkits.html.slim
340
+ - app/views/protege/agents/agent_toolkits/create.turbo_stream.slim
341
+ - app/views/protege/agents/edit.html.slim
342
+ - app/views/protege/agents/index.html.slim
343
+ - app/views/protege/agents/new.html.slim
344
+ - app/views/protege/agents/show.html.slim
345
+ - app/views/protege/alert_mailer/inference_failed.text.erb
309
346
  - app/views/protege/email_domains/_email_domain_sidebar_item.html.slim
310
347
  - app/views/protege/email_domains/_form.html.slim
311
348
  - app/views/protege/email_domains/_sidebar.html.slim
@@ -316,17 +353,6 @@ files:
316
353
  - app/views/protege/messages/_message.html.slim
317
354
  - app/views/protege/messages/_sidebar.html.slim
318
355
  - app/views/protege/messages/index.html.slim
319
- - app/views/protege/personas/_access_rule.html.slim
320
- - app/views/protege/personas/_access_rule_form.html.slim
321
- - app/views/protege/personas/_access_rules.html.slim
322
- - app/views/protege/personas/_actions.html.slim
323
- - app/views/protege/personas/_form.html.slim
324
- - app/views/protege/personas/_persona_sidebar_item.html.slim
325
- - app/views/protege/personas/_sidebar.html.slim
326
- - app/views/protege/personas/edit.html.slim
327
- - app/views/protege/personas/index.html.slim
328
- - app/views/protege/personas/new.html.slim
329
- - app/views/protege/personas/show.html.slim
330
356
  - app/views/protege/responsibilities/_form.html.slim
331
357
  - app/views/protege/responsibilities/_responsibility_sidebar_item.html.slim
332
358
  - app/views/protege/responsibilities/_sidebar.html.slim
@@ -349,6 +375,13 @@ files:
349
375
  - app/views/protege/threads/index.html.slim
350
376
  - app/views/protege/threads/new.html.slim
351
377
  - app/views/protege/threads/show.html.slim
378
+ - app/views/protege/toolkits/_form.html.slim
379
+ - app/views/protege/toolkits/_sidebar.html.slim
380
+ - app/views/protege/toolkits/_toolkit_sidebar_item.html.slim
381
+ - app/views/protege/toolkits/edit.html.slim
382
+ - app/views/protege/toolkits/index.html.slim
383
+ - app/views/protege/toolkits/new.html.slim
384
+ - app/views/protege/toolkits/show.html.slim
352
385
  - app/views/protege/traces/_sidebar.html.slim
353
386
  - app/views/protege/traces/index.html.slim
354
387
  - app/views/protege/traces/searches/_sidebar.html.slim
@@ -359,13 +392,18 @@ files:
359
392
  - db/migrate/20260706120130_create_protege_traces.rb
360
393
  - db/migrate/20260707120000_replace_persona_active_with_archived_at.rb
361
394
  - db/migrate/20260708120000_add_disabled_tool_ids_to_personas.rb
395
+ - db/migrate/20260728140000_scope_message_and_thread_uniqueness_per_persona.rb
396
+ - db/migrate/20260812000001_create_protege_toolkits.rb
397
+ - db/migrate/20260815000001_remove_disabled_tool_ids_from_personas.rb
398
+ - db/migrate/20260815000002_add_key_to_protege_toolkits.rb
399
+ - db/migrate/20260815100000_rename_personas_to_agents.rb
400
+ - lib/generators/protege/agent/agent_generator.rb
401
+ - lib/generators/protege/agent/templates/agent.rb.tt
362
402
  - lib/generators/protege/extension_naming.rb
363
403
  - lib/generators/protege/hook/hook_generator.rb
364
404
  - lib/generators/protege/hook/templates/hook.rb.tt
365
405
  - lib/generators/protege/install/install_generator.rb
366
406
  - lib/generators/protege/install/templates/initializer.rb.tt
367
- - lib/generators/protege/persona/persona_generator.rb
368
- - lib/generators/protege/persona/templates/persona.rb.tt
369
407
  - lib/generators/protege/postfix/postfix_generator.rb
370
408
  - lib/generators/protege/postfix/templates/deploy/mail/Dockerfile
371
409
  - lib/generators/protege/postfix/templates/deploy/mail/KeyTable
@@ -425,6 +463,8 @@ files:
425
463
  - lib/protege/events/tool_calls_received_event.rb
426
464
  - lib/protege/extensions/hook.rb
427
465
  - lib/protege/extensions/hook_mixin.rb
466
+ - lib/protege/extensions/manifest.rb
467
+ - lib/protege/extensions/manifested.rb
428
468
  - lib/protege/extensions/provider.rb
429
469
  - lib/protege/extensions/provider_mixin.rb
430
470
  - lib/protege/extensions/resolver.rb
@@ -455,6 +495,7 @@ files:
455
495
  - lib/protege/orchestrator/resolver_chain.rb
456
496
  - lib/protege/orchestrator/responsibility_context.rb
457
497
  - lib/protege/orchestrator/responsibility_harness.rb
498
+ - lib/protege/subscribers/alerter.rb
458
499
  - lib/protege/subscribers/hook_dispatcher.rb
459
500
  - lib/protege/subscribers/tracing.rb
460
501
  - lib/protege/types/document_part.rb
@@ -473,6 +514,8 @@ metadata:
473
514
  homepage_uri: https://github.com/sebscholl/protege
474
515
  source_code_uri: https://github.com/sebscholl/protege
475
516
  bug_tracker_uri: https://github.com/sebscholl/protege/issues
517
+ documentation_uri: https://docs.protege.bot
518
+ changelog_uri: https://github.com/sebscholl/protege/blob/main/CHANGELOG.md
476
519
  rubygems_mfa_required: 'true'
477
520
  rdoc_options: []
478
521
  require_paths:
@@ -490,5 +533,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
490
533
  requirements: []
491
534
  rubygems_version: 3.6.9
492
535
  specification_version: 4
493
- summary: Email-native AI agent toolkit.
536
+ summary: Rails engine for email-native AI agents.
494
537
  test_files: []
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Protege
4
- # Shared concern for controllers nested under a persona.
5
- # Sets +@persona+ from +params[:persona_id]+.
6
- module PersonaScoped
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- before_action :set_persona
11
- end
12
-
13
- private
14
-
15
- # Load the +Persona+ named by +params[:persona_id]+ into +@persona+.
16
- #
17
- # @return [Protege::Persona] the loaded persona
18
- def set_persona
19
- @persona = Persona.find(params[:persona_id])
20
- end
21
- end
22
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Protege
4
- # Manages persona records — listing, creating, viewing, editing, and permanent deletion.
5
- #
6
- # A persona is the identity behind an inbox; the controller surfaces the full CRUD the dashboard
7
- # needs. +destroy+ is the hard, irreversible removal (cascading all the persona's data); the
8
- # reversible, history-preserving alternative is archiving — see +ArchivesController+.
9
- class PersonasController < ApplicationController
10
- before_action :set_persona, only: %i[show edit update destroy]
11
-
12
- # List every persona. Serves +GET /personas+.
13
- #
14
- # @return [void]
15
- def index
16
- @personas = Persona.all
17
- end
18
-
19
- # Show a single persona. Serves +GET /personas/:id+.
20
- #
21
- # @return [void]
22
- def show; end
23
-
24
- # Render the form for creating a persona. Serves +GET /personas/new+.
25
- #
26
- # @return [void]
27
- def new
28
- @persona = Persona.new
29
- end
30
-
31
- # Render the form for editing a persona. Serves +GET /personas/:id/edit+.
32
- #
33
- # @return [void]
34
- def edit; end
35
-
36
- # Create a persona from the form's split email fields. Serves +POST /personas+.
37
- #
38
- # On failure, re-render the form with validation errors.
39
- #
40
- # @return [void]
41
- def create
42
- @persona = Persona.new(create_params)
43
-
44
- if @persona.save
45
- redirect_to persona_path(@persona), notice: 'Persona created.'
46
- else
47
- render :new, status: :unprocessable_entity
48
- end
49
- end
50
-
51
- # Update an existing persona. Serves +PATCH /personas/:id+.
52
- #
53
- # On failure, re-render the edit form with validation errors.
54
- #
55
- # @return [void]
56
- def update
57
- if @persona.update(update_params)
58
- redirect_to persona_path(@persona), notice: 'Persona updated.'
59
- else
60
- render :edit, status: :unprocessable_entity
61
- end
62
- end
63
-
64
- # Permanently delete a persona and everything it owns. Serves +DELETE /personas/:id+.
65
- #
66
- # This is the hard, irreversible removal (the reversible path is Archive — see +ArchivesController+):
67
- # the model cascades the persona's threads, messages, responsibilities, access rules, and tool-use
68
- # history. +BroadcastablePersona+ removes its sidebar row.
69
- #
70
- # @return [void]
71
- def destroy
72
- @persona.destroy!
73
-
74
- redirect_to personas_path, notice: 'Persona deleted.'
75
- end
76
-
77
- private
78
-
79
- # Load the +Persona+ named by +params[:id]+ into +@persona+.
80
- #
81
- # @return [Protege::Persona] the loaded persona
82
- def set_persona
83
- @persona = Persona.find(params[:id])
84
- end
85
-
86
- # Permit creation attributes and fold the split email fields into a single address.
87
- #
88
- # @return [ActionController::Parameters] the permitted attributes plus +:email_address+
89
- def create_params
90
- params.require(:persona)
91
- .permit(:type, :name, :instructions)
92
- .merge(email_address: compose_email_address)
93
- end
94
-
95
- # Permit the editable attributes and recompose the address from the split email fields. Changing the
96
- # address is allowed — only mail to the *old* address stops routing here; all history keys off
97
- # +persona_id+, not the address. +disabled_tool_ids+ is the tool checklist submitted straight from
98
- # the form (the model strips the hidden blank); a PATCH without that field leaves the scope untouched.
99
- #
100
- # @return [ActionController::Parameters] the permitted attributes plus +:email_address+
101
- def update_params
102
- params.require(:persona)
103
- .permit(:name, :instructions, disabled_tool_ids: [])
104
- .merge(email_address: compose_email_address)
105
- end
106
-
107
- # Combine the split prefix + domain fields from the form into a single
108
- # email address.
109
- #
110
- # @return [String] the +prefix@domain+ address with surrounding whitespace stripped
111
- def compose_email_address
112
- params[:persona].values_at(:email_prefix, :email_domain)
113
- .map(&:to_s)
114
- .map(&:strip)
115
- .join('@')
116
- end
117
- end
118
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Protege
4
- # View helpers for persona pages — selectors, labels, status, and resolver display.
5
- module PersonasHelper
6
- # List the loaded +Persona+ subclasses for the new-persona type selector.
7
- #
8
- # @return [Array<Class>] the +Persona+ descendant classes
9
- def available_persona_types
10
- Persona.descendants
11
- end
12
-
13
- # List active email domains for the persona form's domain selector.
14
- #
15
- # @return [ActiveRecord::Relation<Protege::EmailDomain>] domains eligible for selection
16
- def available_email_domains
17
- EmailDomain.all
18
- end
19
-
20
- # Fetch every +Persona+ ordered by name for sidebar listing.
21
- #
22
- # @return [ActiveRecord::Relation<Protege::Persona>] personas ordered alphabetically
23
- def all_personas
24
- Persona.order(:name)
25
- end
26
-
27
- # Format a persona's name together with its type for display.
28
- #
29
- # persona_label(persona) # => "Bob <Agent>"
30
- #
31
- # @param persona [Protege::Persona] the persona to label
32
- # @return [String] the +"name <Type>"+ label
33
- def persona_label(persona)
34
- "#{persona.name} <#{persona.class.display_name}>"
35
- end
36
-
37
- # Render a status dot reflecting whether a persona is available.
38
- #
39
- # @param persona [Protege::Persona] the persona whose availability to show
40
- # @return [ActiveSupport::SafeBuffer] a green dot when active, red otherwise
41
- def persona_status(persona)
42
- if persona.active?
43
- status_dot('Available', :green)
44
- else
45
- status_dot('Unavailable', :red)
46
- end
47
- end
48
-
49
- # Format a persona's resolver chain as a hash suitable for +info_card+ (rendered full-width, since
50
- # the resolver class names are long). Each entry maps the resolver's short class name to a summary
51
- # of its positional args, keyword args, and whether it was given a block.
52
- #
53
- # @param persona [Protege::Persona] the persona whose reply (+message_resolvers+) chain to format
54
- # @return [Hash{String => String}] resolver-name => configuration summary
55
- def resolver_pairs(persona)
56
- pairs = persona.message_resolvers.entries.each_with_object({}) do |(klass, args, kwargs, block), accumulator|
57
- accumulator[klass.name.demodulize] = resolver_config(args, kwargs, block)
58
- end
59
-
60
- pairs.any? ? pairs : { 'Resolvers' => 'None configured' }
61
- end
62
-
63
- # The tool classes a persona's operator may toggle — the checkbox universe on the persona form.
64
- #
65
- # This is the persona's *grant* (its code-declared ceiling), or every registered tool when no grant
66
- # was declared. It is deliberately the grant rather than the effective set: the operator toggles
67
- # within what the code allows, so a tool disabled in the DB still appears (unchecked) and can be
68
- # switched back on, while a tool outside the grant is never offered.
69
- #
70
- # @param persona [Protege::Persona] the persona being edited
71
- # @return [Array<Class>] the toggleable tool classes
72
- def persona_tool_options(persona)
73
- grant = persona.class.tool_grant
74
-
75
- grant ? ToolMixin.registered.select { grant.include?(_1.id) } : ToolMixin.registered
76
- end
77
-
78
- private
79
-
80
- # Summarize a resolver entry's positional args, keyword args, and whether it was given a block.
81
- #
82
- # @return [String] a comma-joined summary, or "" when the resolver takes no configuration
83
- def resolver_config(args, kwargs, block)
84
- parts = args.map(&:inspect)
85
- parts += kwargs.map { |key, value| "#{key}: #{value.inspect}" }
86
- parts << '{ … }' if block
87
- parts.join(', ')
88
- end
89
- end
90
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Protege
4
- # Drives real-time Turbo Stream broadcasting for +Persona+ records — keeping the Personas sidebar
5
- # live: the row is appended on create, replaced on update, and removed on destroy. First-page viewers
6
- # subscribe via +turbo_stream_from "protege:personas"+ (see +SidebarHelper#sidebar_stream+).
7
- module BroadcastablePersona
8
- extend ActiveSupport::Concern
9
-
10
- # Turbo Stream channel for all +Persona+ records.
11
- # @return [String]
12
- STREAM = 'protege:personas'
13
-
14
- included do
15
- after_create_commit :broadcast_persona_created
16
- after_update_commit :broadcast_persona_updated
17
- after_destroy_commit :broadcast_persona_removed
18
- end
19
-
20
- private
21
-
22
- # Append this persona's row to the Personas sidebar on creation.
23
- #
24
- # @return [void]
25
- def broadcast_persona_created
26
- broadcast_append_to STREAM,
27
- target: 'personas_list',
28
- partial: 'protege/personas/persona_sidebar_item',
29
- locals: { persona: self }
30
- end
31
-
32
- # Replace this persona's sidebar row in place when its attributes change.
33
- #
34
- # @return [void]
35
- def broadcast_persona_updated
36
- broadcast_replace_to STREAM,
37
- target: ActionView::RecordIdentifier.dom_id(self),
38
- partial: 'protege/personas/persona_sidebar_item',
39
- locals: { persona: self }
40
- end
41
-
42
- # Remove this persona's sidebar row on destruction.
43
- #
44
- # @return [void]
45
- def broadcast_persona_removed
46
- broadcast_remove_to STREAM,
47
- target: ActionView::RecordIdentifier.dom_id(self)
48
- end
49
- end
50
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Protege
4
- # Per-persona tool scoping — mixed into +Persona+ to bound which tools an agent may use.
5
- #
6
- # A persona's effective tools are the code-declared *grant* (the ceiling, set by the {ClassMethods#tools}
7
- # macro) minus the operator's +disabled_tool_ids+ override, and never wider than the grant. Two layers,
8
- # deliberately different in reach:
9
- #
10
- # * The grant lives on a +class_attribute+ so it survives Active Record's schema-loading reloads (the
11
- # same reason +Persona._display_name+ is one). Unlike the resolver chains — per-subclass +@ivar+s that
12
- # never inherit — the grant *inherits* down the STI tree, so a base persona narrows a whole family's
13
- # ceiling unless a subclass declares its own. A nil grant means "every registered tool", so an
14
- # unconfigured persona keeps the full catalogue (the pre-scoping behaviour).
15
- # * +disabled_tool_ids+ is a dashboard-editable JSON array on the row (text column; SQLite has no native
16
- # JSON type), normalised to a clean string array so it is never nil. It can only *subtract* from the
17
- # grant, never add to it.
18
- #
19
- # {#available_tools} resolves both layers against the live registry, so a stale grant or disabled id —
20
- # naming a tool since deleted from the code — is simply skipped rather than conjuring or breaking a tool.
21
- module ToolScoped
22
- extend ActiveSupport::Concern
23
-
24
- included do
25
- serialize :disabled_tool_ids, coder: JSON
26
- before_validation :normalize_disabled_tool_ids
27
-
28
- # Backing store for the code-declared grant (tool ids, or nil for "all tools"). Inherits down STI.
29
- class_attribute :_tool_grant, instance_accessor: false
30
- end
31
-
32
- # Class-level DSL for declaring the grant.
33
- class_methods do
34
- # Declare the tools this persona subclass may use — the code-side ceiling of its tool scope.
35
- #
36
- # A DSL macro (positional by convention, like +message_resolver+): given tool ids it records them as
37
- # symbols. Omitting it leaves the grant nil ("every registered tool"). The operator can only narrow
38
- # this grant at runtime via +disabled_tool_ids+, never widen it.
39
- #
40
- # @param ids [Array<Symbol, String>] the tool ids the persona is allowed to use
41
- # @return [Array<Symbol>] the recorded grant
42
- def tools(*ids)
43
- self._tool_grant = ids.map(&:to_sym)
44
- end
45
-
46
- # Return this subclass's declared tool grant, or nil when none was declared ("all tools").
47
- #
48
- # @return [Array<Symbol>, nil] the granted tool ids, or nil for the full catalogue
49
- def tool_grant
50
- _tool_grant
51
- end
52
- end
53
-
54
- # Return the tool classes this persona may currently use — its effective, scoped catalogue.
55
- #
56
- # Computed as one filter over the live registry (+ToolMixin.registered+): drop anything the operator
57
- # has switched off in +disabled_tool_ids+, then keep only what the subclass's grant permits (or
58
- # everything, when the grant is nil). Iterating the registry rather than the grant means a grant id
59
- # with no matching registered tool is simply skipped — a stale scope can never conjure or break a tool.
60
- #
61
- # @return [Array<Class>] the tool classes offered to the model for this persona
62
- def available_tools
63
- disabled = disabled_tool_ids.map(&:to_sym)
64
- grant = self.class.tool_grant
65
-
66
- ToolMixin.registered
67
- .reject { |tool| disabled.include?(tool.id) }
68
- .select { |tool| grant.nil? || grant.include?(tool.id) }
69
- end
70
-
71
- # The ids of this persona's effective tools — a convenience over {#available_tools} for UI and tests.
72
- #
73
- # @return [Array<Symbol>] the effective tool ids
74
- def available_tool_ids
75
- available_tools.map(&:id)
76
- end
77
-
78
- private
79
-
80
- # Coerce +disabled_tool_ids+ to a deduped, blank-stripped array of strings so the JSON column stays
81
- # clean regardless of how it was assigned (symbols, blanks, nils, duplicates) and is never nil.
82
- #
83
- # @return [void]
84
- def normalize_disabled_tool_ids
85
- self.disabled_tool_ids = Array(disabled_tool_ids).map(&:to_s).reject(&:blank?).uniq
86
- end
87
- end
88
- end
@@ -1,15 +0,0 @@
1
- / The persona show page's action bar — Edit, Archive/Unarchive, and permanent Delete — plus an "Archived"
2
- / badge when soft-removed. Wrapped in a dom_id so Archive/Unarchive can replace it in place over Turbo
3
- / (see ArchivesController). The Delete button opens the confirm dialog rendered by the show template.
4
- div id=dom_id(persona, :actions) class="flex items-center gap-3"
5
- - if persona.archived?
6
- = badge("Archived")
7
-
8
- = ui_link("Edit", edit_persona_path(persona))
9
-
10
- - if persona.archived?
11
- = action_button("Unarchive", persona_archive_path(persona), method: :delete)
12
- - else
13
- = action_button("Archive", persona_archive_path(persona), method: :post, variant: :secondary)
14
-
15
- = button("Delete", type: "button", variant: :danger, onclick: show_dialog("delete-persona"))
@@ -1,29 +0,0 @@
1
- - url = persona.new_record? ? personas_path : persona_path(persona)
2
- - method = persona.new_record? ? :post : :patch
3
-
4
- = form_with url:, method:, scope: :persona, class: "space-y-5" do |f|
5
- = form_errors(persona)
6
-
7
- = form_field(f, :name, placeholder: "e.g. alice")
8
-
9
- = form_email_address(persona, available_email_domains)
10
-
11
- - unless persona.new_record?
12
- = hint("Changing the address only affects new mail — messages to the old address will no longer reach this persona. Existing threads and history are unaffected.")
13
-
14
- - if persona.new_record?
15
- = form_select(f, :type, available_persona_types.map { |k| [k.display_name, k.name] }, label: "Type")
16
-
17
- = form_text_area(f, :instructions, \
18
- label: "Instructions", \
19
- rows: 10, \
20
- hint: "The agent's system prompt — who it is and how it behaves. Only sent to the model when a resolver (such as LoadText) includes it in the agent's context.")
21
-
22
- - unless persona.new_record?
23
- - tool_id = ->(tool) { tool.id.to_s }
24
- = form_checklist(f, :disabled_tool_ids, persona_tool_options(persona), label: "Blocked Tools", value: tool_id, text: tool_id, hint: "Disabled tools aren't offered to the agent; everything else stays available.")
25
-
26
- .flex.items-center.gap-3.pt-2
27
- = button(persona.new_record? ? "Create persona" : "Save changes", type: :submit)
28
-
29
- = ui_link("Cancel", persona.new_record? ? personas_path : persona_path(persona))
@@ -1,5 +0,0 @@
1
- = sidebar_item(persona_label(persona), persona_path(persona),
2
- detail: persona_status(persona),
3
- subtitle: persona.email_address,
4
- active: local_assigns[:active],
5
- id: dom_id(persona))
@@ -1,11 +0,0 @@
1
- - pagy, personas = paginate(all_personas)
2
-
3
- = sidebar_content do
4
- = sidebar_header("Personas (#{pagy.count})", new_path: new_persona_path)
5
-
6
- = sidebar_stream("protege:personas", pagy: pagy)
7
-
8
- = sidebar_body(personas, empty: "No personas yet", id: "personas_list") do |persona|
9
- = render "persona_sidebar_item", persona: persona, active: local_assigns[:current] == persona
10
-
11
- = sidebar_pagination(pagy)
@@ -1,7 +0,0 @@
1
- - content_for :sidebar do
2
- = render "sidebar", current: @persona
3
-
4
- = page_container do
5
- = page_header("Edit #{@persona.name}")
6
-
7
- = card { render "form", persona: @persona }