create-workframe 0.1.12 → 0.1.13

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 (188) hide show
  1. package/README.md +1 -1
  2. package/bin/create-workframe.js +2 -2
  3. package/bin/workframe.js +130 -2
  4. package/docs/security.md +2 -2
  5. package/package.json +1 -1
  6. package/scripts/bundle-workframe-ui.mjs +9 -0
  7. package/scripts/sync-canonical-to-package.mjs +1 -0
  8. package/scripts/verify-public-deploy.sh +65 -3
  9. package/workframe-api/action_proxy.py +23 -17
  10. package/workframe-api/activity_feed.py +798 -0
  11. package/workframe-api/api_meta.py +157 -0
  12. package/workframe-api/auth_gate.py +566 -0
  13. package/workframe-api/avatar_registry.py +343 -0
  14. package/workframe-api/broker_audit.py +164 -0
  15. package/workframe-api/cell_authority.py +231 -0
  16. package/workframe-api/chat_bind.py +319 -0
  17. package/workframe-api/chat_sessions.py +509 -0
  18. package/workframe-api/chat_stream.py +694 -0
  19. package/workframe-api/cleanup_dogfood_smoke.py +252 -0
  20. package/workframe-api/credential_broker.py +162 -0
  21. package/workframe-api/credential_resolve.py +202 -0
  22. package/workframe-api/credential_store.py +245 -0
  23. package/workframe-api/crew_registry.py +250 -0
  24. package/workframe-api/db_schema.py +755 -0
  25. package/workframe-api/docker_gateway.py +166 -0
  26. package/workframe-api/doctor_runtime.py +109 -0
  27. package/workframe-api/domain/__init__.py +47 -0
  28. package/workframe-api/domain/entities.py +252 -0
  29. package/workframe-api/domain/schema/workframe-domain.schema.json +278 -0
  30. package/workframe-api/egress_policy.py +42 -0
  31. package/workframe-api/handler_modules/__init__.py +17 -0
  32. package/workframe-api/handler_modules/handler_admin.py +542 -0
  33. package/workframe-api/handler_modules/handler_auth.py +512 -0
  34. package/workframe-api/handler_modules/handler_chat.py +641 -0
  35. package/workframe-api/handler_modules/handler_install.py +178 -0
  36. package/workframe-api/handler_modules/handler_provider.py +325 -0
  37. package/workframe-api/handler_modules/handler_workspace.py +1420 -0
  38. package/workframe-api/health_monitor.py +80 -0
  39. package/workframe-api/hermes_admin.py +756 -0
  40. package/workframe-api/hermes_profiles.py +1328 -0
  41. package/workframe-api/install_api.py +123 -0
  42. package/workframe-api/kanban_cron.py +473 -0
  43. package/workframe-api/lane_bindings.py +423 -0
  44. package/workframe-api/llm_proxy.py +17 -43
  45. package/workframe-api/mention_helpers.py +180 -0
  46. package/workframe-api/mention_invoke.py +431 -0
  47. package/workframe-api/model_surface.py +1139 -0
  48. package/workframe-api/oauth_pending.py +85 -0
  49. package/workframe-api/oauth_redirect.py +381 -0
  50. package/workframe-api/package.json +2 -2
  51. package/workframe-api/profile_api_lifecycle.py +66 -0
  52. package/workframe-api/profile_gateway.py +436 -0
  53. package/workframe-api/provider_bindings.py +673 -0
  54. package/workframe-api/provider_bootstrap.py +347 -0
  55. package/workframe-api/provider_catalog.py +180 -0
  56. package/workframe-api/rooms.py +1613 -0
  57. package/workframe-api/route_registry.py +331 -191
  58. package/workframe-api/run-typecheck.mjs +2 -1
  59. package/workframe-api/run_authority.py +287 -0
  60. package/workframe-api/run_ledger.py +470 -0
  61. package/workframe-api/run_surface_wiring.py +344 -0
  62. package/workframe-api/runtime_cohort.py +752 -0
  63. package/workframe-api/runtime_tokens.py +127 -0
  64. package/workframe-api/server.py +1453 -19513
  65. package/workframe-api/snapshot_feed.py +49 -0
  66. package/workframe-api/stack_config.py +33 -1
  67. package/workframe-api/supervisor_client.py +154 -0
  68. package/workframe-api/test_api_meta_build_stamp.py +34 -0
  69. package/workframe-api/test_cell_authority.py +79 -0
  70. package/workframe-api/test_chat_bind.py +48 -0
  71. package/workframe-api/test_credential_lease_matrix.py +171 -0
  72. package/workframe-api/test_credential_resolve.py +51 -0
  73. package/workframe-api/test_credential_store.py +27 -0
  74. package/workframe-api/test_dogfood_flows.py +346 -0
  75. package/workframe-api/test_domain_entities.py +152 -0
  76. package/workframe-api/test_exception_hygiene.py +12 -0
  77. package/workframe-api/test_hermes_admin.py +72 -0
  78. package/workframe-api/test_install_wizard_resume.py +78 -0
  79. package/workframe-api/test_local_bootstrap.py +67 -0
  80. package/workframe-api/test_mention_helpers.py +35 -0
  81. package/workframe-api/test_mention_invoke.py +26 -0
  82. package/workframe-api/test_model_surface_consistency.py +1 -0
  83. package/workframe-api/test_oauth_pending.py +41 -0
  84. package/workframe-api/test_provider_bindings.py +52 -0
  85. package/workframe-api/test_provider_catalog.py +28 -0
  86. package/workframe-api/test_route_registry.py +171 -35
  87. package/workframe-api/test_run_authority.py +112 -0
  88. package/workframe-api/test_run_ledger.py +157 -0
  89. package/workframe-api/test_run_surface_wiring.py +130 -0
  90. package/workframe-api/test_runtime_cohort.py +85 -0
  91. package/workframe-api/test_secure_mode_docker_boundary.py +39 -0
  92. package/workframe-api/test_server_reexports.py +99 -0
  93. package/workframe-api/test_stack_config_install_smtp.py +7 -0
  94. package/workframe-api/turn_credentials.py +28 -13
  95. package/workframe-api/turn_overlay.py +715 -0
  96. package/workframe-api/updates.py +16 -1
  97. package/workframe-api/user_prefs.py +387 -0
  98. package/workframe-api/wf032_extract_remaining_handlers.py +417 -0
  99. package/workframe-api/workspace_bootstrap.py +536 -0
  100. package/workframe-api/workspace_files.py +344 -0
  101. package/workframe-api/workspace_messaging.py +206 -0
  102. package/workframe-api/zk_auth.py +3 -2
  103. package/workframe-supervisor/server.py +10 -1
  104. package/workframe-supervisor/test_supervisor_negative.py +75 -0
  105. package/workframe-ui/public/assets/{arc-B0OFRGmJ.js → arc-aUUffclm.js} +1 -1
  106. package/workframe-ui/public/assets/architecture-7EHR7CIX-BSUP4S8J.js +1 -0
  107. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-DeBNltHS.js → architectureDiagram-3BPJPVTR-DU6oaqIb.js} +1 -1
  108. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-BDhCHu7I.js → blockDiagram-GPEHLZMM-D2p8BU6-.js} +1 -1
  109. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-olcDYvtI.js → c4Diagram-AAUBKEIU-ouwyPmTJ.js} +1 -1
  110. package/workframe-ui/public/assets/channel-DOunZZ0X.js +1 -0
  111. package/workframe-ui/public/assets/{chunk-2J33WTMH-D0obCBn_.js → chunk-2J33WTMH-DsE7U5dj.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-3OPIFGDE-DX93f-ZZ.js → chunk-3OPIFGDE-DM0kkZ9h.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-4BX2VUAB-BX9B5wUs.js → chunk-4BX2VUAB-CQ8mAyXp.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-55IACEB6-C4DGc6RO.js → chunk-55IACEB6-DN1BDtbH.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-Crlja9Lf.js → chunk-5ZQYHXKU-BzK2KqOt.js} +1 -1
  116. package/workframe-ui/public/assets/{chunk-727SXJPM-JYSm3szI.js → chunk-727SXJPM-C3AxtOi9.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-_KslxVEl.js → chunk-AQP2D5EJ-4mVwEFuD.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-BSJP7CBP-CpTO-jU8.js → chunk-BSJP7CBP-23kN2q0A.js} +1 -1
  119. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-JGUkCmbI.js → chunk-CSCIHK7Q-CPBKVW-L.js} +2 -2
  120. package/workframe-ui/public/assets/{chunk-FMBD7UC4-BscBwGaC.js → chunk-FMBD7UC4-CKqcYq7K.js} +1 -1
  121. package/workframe-ui/public/assets/{chunk-KSCS5N6A-5ZTZ0bpX.js → chunk-KSCS5N6A-JRQnhxQE.js} +1 -1
  122. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-DxvitYbW.js → chunk-L5ZTLDWV-BLZrdIwa.js} +1 -1
  123. package/workframe-ui/public/assets/chunk-LZXEDZCA-DisG0XJT.js +2 -0
  124. package/workframe-ui/public/assets/{chunk-ND2GUHAM-Cayl0iV9.js → chunk-ND2GUHAM-ClAOxArS.js} +1 -1
  125. package/workframe-ui/public/assets/{chunk-NZK2D7GU-_7dyBR5G.js → chunk-NZK2D7GU-XUE1aNkm.js} +1 -1
  126. package/workframe-ui/public/assets/{chunk-O5CBEL6O--xTpD0yi.js → chunk-O5CBEL6O-DDN-Ifon.js} +1 -1
  127. package/workframe-ui/public/assets/chunk-QZHKN3VN-EVQ0VMd5.js +1 -0
  128. package/workframe-ui/public/assets/chunk-WU5MYG2G-k_-ZsLOS.js +1 -0
  129. package/workframe-ui/public/assets/{chunk-XPW4576I-CwxJQaeK.js → chunk-XPW4576I-B4_iYZ6r.js} +1 -1
  130. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-W9WVQsby.js +1 -0
  131. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-W9WVQsby.js +1 -0
  132. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-CCspENYv.js → cose-bilkent-S5V4N54A-Cy1HhS7j.js} +1 -1
  133. package/workframe-ui/public/assets/{dagre-BM42HDAG-Dv8V6R60.js → dagre-BM42HDAG-Bjal81Ie.js} +1 -1
  134. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Da48hFPT.js → diagram-2AECGRRQ-v5Gdvzp_.js} +1 -1
  135. package/workframe-ui/public/assets/{diagram-5GNKFQAL-dTihc7-3.js → diagram-5GNKFQAL-DuyRdfmY.js} +1 -1
  136. package/workframe-ui/public/assets/{diagram-KO2AKTUF-D_Jqu699.js → diagram-KO2AKTUF-WwWCGbIW.js} +1 -1
  137. package/workframe-ui/public/assets/{diagram-LMA3HP47-Bt8PtEaZ.js → diagram-LMA3HP47-DTYwZALT.js} +1 -1
  138. package/workframe-ui/public/assets/{diagram-OG6HWLK6-BtmPjlh0.js → diagram-OG6HWLK6-B46kBIqE.js} +1 -1
  139. package/workframe-ui/public/assets/{dist-Cz2turIT.js → dist-mQWmB3z6.js} +1 -1
  140. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-BxMGCflX.js → erDiagram-TEJ5UH35-__40xO-y.js} +1 -1
  141. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Cm1uRTxU.js +1 -0
  142. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-ivyroIZt.js → flowDiagram-I6XJVG4X-Cwj6dVUz.js} +1 -1
  143. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-B9llwShx.js → ganttDiagram-6RSMTGT7-D_IOSwAS.js} +1 -1
  144. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-BFQHk4bw.js → gitGraph-WXDBUCRP-C1vGecee.js} +1 -1
  145. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-ursegLbc.js → gitGraphDiagram-PVQCEYII-DMTxFJxn.js} +1 -1
  146. package/workframe-ui/public/assets/index-BdiM4-lI.js +130 -0
  147. package/workframe-ui/public/assets/index-Fnad47vV.css +1 -0
  148. package/workframe-ui/public/assets/{info-J43DQDTF-DQpifAsB.js → info-J43DQDTF-CwjmyPmD.js} +1 -1
  149. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-C16XP2Q7.js → infoDiagram-5YYISTIA-BGzh7ZL2.js} +1 -1
  150. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-CiJoz7rP.js → ishikawaDiagram-YF4QCWOH-BLXdnR_5.js} +1 -1
  151. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-CVwEvvUL.js → journeyDiagram-JHISSGLW-BK3behMe.js} +1 -1
  152. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CWQ6hX5i.js → kanban-definition-UN3LZRKU-Bx172cCl.js} +1 -1
  153. package/workframe-ui/public/assets/{line-CgQsmU35.js → line-BTCEHb7l.js} +1 -1
  154. package/workframe-ui/public/assets/{linear-Bxbc77dL.js → linear-CkbydpnK.js} +1 -1
  155. package/workframe-ui/public/assets/{mermaid-parser.core-C_jMeF0a.js → mermaid-parser.core-DvrtArpk.js} +2 -2
  156. package/workframe-ui/public/assets/{mermaid.core-4RKV9MZP.js → mermaid.core-yj_1x_qj.js} +3 -3
  157. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DZ7y7Sz0.js → mindmap-definition-RKZ34NQL-BXbMltQ0.js} +1 -1
  158. package/workframe-ui/public/assets/{packet-YPE3B663-B9h2I7Vq.js → packet-YPE3B663-9pVCBQ7S.js} +1 -1
  159. package/workframe-ui/public/assets/{pie-LRSECV5Y-B2mP5uHm.js → pie-LRSECV5Y-CdqoWesP.js} +1 -1
  160. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-DyYKyKiP.js → pieDiagram-4H26LBE5-kGs-VfXd.js} +1 -1
  161. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-CL_6nej-.js → quadrantDiagram-W4KKPZXB-D9D8gQw5.js} +1 -1
  162. package/workframe-ui/public/assets/{radar-GUYGQ44K-CNKNDQ7S.js → radar-GUYGQ44K-d4zMZpQS.js} +1 -1
  163. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-eWU_mhFa.js → requirementDiagram-4Y6WPE33-BI7EQ9zc.js} +1 -1
  164. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DrjcXvEQ.js → sankeyDiagram-5OEKKPKP-Bcz19jQB.js} +1 -1
  165. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-BHWR1xTJ.js → sequenceDiagram-3UESZ5HK-iQiCEfYp.js} +1 -1
  166. package/workframe-ui/public/assets/{src-DfJB8kV1.js → src-D6mvlP96.js} +1 -1
  167. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BGJbSd3I.js → stateDiagram-AJRCARHV-CxaTXs02.js} +1 -1
  168. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CXikj9xi.js +1 -0
  169. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DkwoCjiZ.js → timeline-definition-PNZ67QCA-Bej-EAnf.js} +1 -1
  170. package/workframe-ui/public/assets/{treeView-BLDUP644-GxfhiqoW.js → treeView-BLDUP644-C7PnnzwX.js} +1 -1
  171. package/workframe-ui/public/assets/{treemap-LRROVOQU-DxrOsars.js → treemap-LRROVOQU-CqISLmkO.js} +1 -1
  172. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-BsWhpUfr.js → vennDiagram-CIIHVFJN-eJMTTEW-.js} +1 -1
  173. package/workframe-ui/public/assets/{wardley-L42UT6IY-CrFZWMHS.js → wardley-L42UT6IY-D4sMroBF.js} +1 -1
  174. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DV8Xpf5I.js → wardleyDiagram-YWT4CUSO-DC3DCUs7.js} +1 -1
  175. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DqMqnwdZ.js → xychartDiagram-2RQKCTM6-BeMaM6Aq.js} +1 -1
  176. package/workframe-ui/public/index.html +7 -5
  177. package/workframe-ui/public/workframe-build.json +5 -0
  178. package/workframe-ui/public/assets/architecture-7EHR7CIX-Dwwi9yA0.js +0 -1
  179. package/workframe-ui/public/assets/channel-fNHbDpV4.js +0 -1
  180. package/workframe-ui/public/assets/chunk-LZXEDZCA-DMfdMUwz.js +0 -2
  181. package/workframe-ui/public/assets/chunk-QZHKN3VN-ewTgEQK8.js +0 -1
  182. package/workframe-ui/public/assets/chunk-WU5MYG2G-D4Tg-A0l.js +0 -1
  183. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-CFAgBpEl.js +0 -1
  184. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-CFAgBpEl.js +0 -1
  185. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BIJTP5k-.js +0 -1
  186. package/workframe-ui/public/assets/index-8CuZDEIG.css +0 -1
  187. package/workframe-ui/public/assets/index-xS9lFekI.js +0 -129
  188. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-CMLuNyeC.js +0 -1
@@ -0,0 +1,641 @@
1
+ """Chat / Hermes / room route handlers (WF-032 slice)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import queue
7
+ import re
8
+ import sqlite3
9
+ import time
10
+ import urllib.parse
11
+ import uuid
12
+ from typing import Any
13
+
14
+ import activity_feed
15
+ import chat_bind
16
+ import chat_sessions
17
+ import chat_stream
18
+ import hermes_admin
19
+ import hermes_profiles
20
+ import lane_bindings
21
+ import mention_helpers
22
+ import model_surface
23
+ import profile_gateway
24
+ import workspace_bootstrap
25
+ from api_meta import hermes_bootstrap, hermes_skills
26
+
27
+ chat_bootstrap = chat_sessions.chat_bootstrap
28
+ chat_messages = chat_sessions.chat_messages
29
+ chat_session = chat_sessions.chat_session
30
+ chat_resolve = lane_bindings.chat_resolve
31
+ chat_dispatch = lane_bindings.chat_dispatch
32
+ hermes_commands_catalog = hermes_admin.hermes_commands_catalog
33
+ hermes_commands_exec = hermes_admin.hermes_commands_exec
34
+ hermes_usage = hermes_admin.hermes_usage
35
+ hermes_profile = hermes_admin.hermes_profile
36
+ hermes_debug = hermes_admin.hermes_debug
37
+ hermes_insights = hermes_admin.hermes_insights
38
+ hermes_gquota = hermes_admin.hermes_gquota
39
+ hermes_models = model_surface.hermes_models
40
+ hermes_model_set = model_surface.hermes_model_set
41
+ hermes_apply_default_model_config = model_surface.hermes_apply_default_model_config
42
+ hermes_fallback_chain_set = model_surface.hermes_fallback_chain_set
43
+ hermes_gateway_exec = hermes_admin.hermes_gateway_exec
44
+ hermes_profile_detail = hermes_admin.hermes_profile_detail
45
+ hermes_profile_update = hermes_admin.hermes_profile_update
46
+ profile_gateway_stop = profile_gateway.profile_gateway_stop
47
+ profile_gateway_steer = profile_gateway.profile_gateway_steer
48
+ profile_gateway_lifecycle = profile_gateway.profile_gateway_lifecycle
49
+ profile_create = hermes_profiles.profile_create
50
+ profile_delete = hermes_profiles.profile_delete
51
+ profile_soul_get = hermes_admin.profile_soul_get
52
+ profile_soul_set = hermes_admin.profile_soul_set
53
+ resolve_hermes_profile = hermes_profiles.resolve_hermes_profile
54
+ resolve_validated_profile = hermes_profiles.resolve_validated_profile
55
+ profile_chat_session = lane_bindings.profile_chat_session
56
+ profile_chat_bind = chat_bind.profile_chat_bind
57
+ profile_chat_message = chat_bind.profile_chat_message
58
+ profile_chat_activate_room_session = chat_bind.profile_chat_activate_room_session
59
+ room_chat_bind = chat_bind.room_chat_bind
60
+ _enrich_room_chat_payload = chat_bind._enrich_room_chat_payload
61
+ stream_profile_chat = chat_stream.stream_profile_chat
62
+ list_room_sessions = chat_bind.list_room_sessions
63
+ room_activity_data = activity_feed.room_activity_data
64
+ bootstrap_agent_dm_lane = workspace_bootstrap.bootstrap_agent_dm_lane
65
+ _process_space_message_mentions = mention_helpers.process_space_message_mentions
66
+
67
+
68
+ def _srv():
69
+ import server
70
+
71
+ return server
72
+
73
+
74
+ class ChatRoutesMixin:
75
+ _HERMES_PROFILE_RESERVED = frozenset({"status", "create", "start", "stop", "delete", "disable"})
76
+
77
+ def _route_get_chat_messages(self, qs: dict[str, list[str]]) -> None:
78
+ srv = _srv()
79
+ profile = resolve_hermes_profile(qs.get("profile", [""])[0] or srv._primary_profile())
80
+ session = qs.get("session", [""])[0]
81
+ source_id = qs.get("source", ["ui"])[0]
82
+ self._json(200, chat_messages(profile, session, source_id))
83
+
84
+
85
+ def _route_get_chat_session(self, qs: dict[str, list[str]]) -> None:
86
+ srv = _srv()
87
+ profile = resolve_hermes_profile(qs.get("profile", [""])[0] or srv._primary_profile())
88
+ session = qs.get("session", [""])[0]
89
+ source_id = qs.get("source", ["ui"])[0]
90
+ self._json(200, chat_session(profile, session, source_id))
91
+
92
+
93
+ def _route_get_chat_resolve(self, qs: dict[str, list[str]]) -> None:
94
+ srv = _srv()
95
+ profile = resolve_validated_profile(qs.get("profile", [""])[0] or srv._primary_profile())
96
+ source_id = qs.get("source", ["ui"])[0]
97
+ client_id = qs.get("client", ["default"])[0]
98
+ self._json(
99
+ 200,
100
+ chat_resolve({"profile": profile, "source_id": source_id, "client_id": client_id}),
101
+ )
102
+
103
+
104
+ def _route_get_hermes_skills(self, qs: dict[str, list[str]]) -> None:
105
+ srv = _srv()
106
+ self._json(200, {"skills": hermes_skills()})
107
+
108
+
109
+ def _route_get_hermes_commands(self, qs: dict[str, list[str]]) -> None:
110
+ srv = _srv()
111
+ self._json(200, hermes_commands_catalog())
112
+
113
+
114
+ def _route_get_hermes_usage(self, qs: dict[str, list[str]]) -> None:
115
+ srv = _srv()
116
+ self._json(200, hermes_usage())
117
+
118
+
119
+ def _route_get_hermes_profile(self, qs: dict[str, list[str]]) -> None:
120
+ srv = _srv()
121
+ self._json(200, hermes_profile())
122
+
123
+
124
+ def _route_get_hermes_models(self, qs: dict[str, list[str]]) -> None:
125
+ srv = _srv()
126
+ profile = qs.get("profile", [""])[0]
127
+ user_id = str(getattr(self, "auth_user", "") or "")
128
+ workspace_id = qs.get("workspace_id", [""])[0]
129
+ selection_only = qs.get("selection_only", ["0"])[0] in {"1", "true", "yes"}
130
+ payload = hermes_models(profile, user_id, workspace_id, selection_only=selection_only)
131
+ status = 200 if payload.get("ok", True) else 400
132
+ self._json(status, payload)
133
+
134
+
135
+ def _route_get_hermes_debug(self, qs: dict[str, list[str]]) -> None:
136
+ srv = _srv()
137
+ self._json(200, hermes_debug())
138
+
139
+
140
+ def _route_get_hermes_insights(self, qs: dict[str, list[str]]) -> None:
141
+ srv = _srv()
142
+ self._json(200, hermes_insights())
143
+
144
+
145
+ def _route_get_hermes_gquota(self, qs: dict[str, list[str]]) -> None:
146
+ srv = _srv()
147
+ self._json(200, hermes_gquota())
148
+
149
+ # WF-037 auth-flow + hermes/chat + me credentials POST handlers
150
+
151
+ def _route_post_hermes_commands_exec(self, body: dict) -> None:
152
+ srv = _srv()
153
+ if not srv._check_auth(self):
154
+ self._json(401, {"error": "unauthorized"})
155
+ return
156
+ line = str(body.get("line", "")).strip()
157
+ if not line:
158
+ self._json(400, {"error": "line required"})
159
+ return
160
+ self._json(200, hermes_commands_exec(line))
161
+
162
+
163
+ def _route_post_hermes_gateway_exec(self, body: dict) -> None:
164
+ srv = _srv()
165
+ if not srv._check_auth(self):
166
+ self._json(401, {"error": "unauthorized"})
167
+ return
168
+ line = str(body.get("line", "")).strip()
169
+ profile = str(body.get("profile", "")).strip()
170
+ if not line:
171
+ self._json(400, {"error": "line required"})
172
+ return
173
+ user_id = str(getattr(self, "auth_user", "") or "")
174
+ workspace_id = str(body.get("workspace_id", "") or "")
175
+ self._json(
176
+ 200,
177
+ hermes_gateway_exec(line, profile, user_id=user_id, workspace_id=workspace_id),
178
+ )
179
+
180
+
181
+ def _route_post_chat_stop(self, body: dict) -> None:
182
+ srv = _srv()
183
+ if not srv._check_auth(self):
184
+ self._json(401, {"error": "unauthorized"})
185
+ return
186
+ profile = resolve_validated_profile(str(body.get("profile") or srv._primary_profile()))
187
+ run_id = str(body.get("run_id") or "").strip()
188
+ if not run_id:
189
+ run_id = srv._latest_active_run_id(profile)
190
+ if not run_id:
191
+ self._json(400, {"error": "no active run to stop"})
192
+ return
193
+ self._json(200, profile_gateway_stop(profile, run_id))
194
+
195
+
196
+ def _route_post_chat_steer(self, body: dict) -> None:
197
+ srv = _srv()
198
+ if not srv._check_auth(self):
199
+ self._json(401, {"error": "unauthorized"})
200
+ return
201
+ profile = resolve_validated_profile(str(body.get("profile") or srv._primary_profile()))
202
+ run_id = str(body.get("run_id") or "").strip()
203
+ text = str(body.get("text") or "").strip()
204
+ if not text:
205
+ self._json(400, {"error": "text required"})
206
+ return
207
+ if not run_id:
208
+ run_id = srv._latest_active_run_id(profile)
209
+ self._json(200, profile_gateway_steer(profile, run_id or "", text))
210
+
211
+
212
+ def _route_post_hermes_model(self, body: dict) -> None:
213
+ srv = _srv()
214
+ if not srv._check_auth(self):
215
+ self._json(401, {"error": "unauthorized"})
216
+ return
217
+ model_id = str(body.get("model", "")).strip()
218
+ profile = str(body.get("profile", "")).strip()
219
+ user_id = str(getattr(self, "auth_user", "") or "")
220
+ workspace_id = str(body.get("workspace_id", "")).strip()
221
+ selection_only = bool(body.get("selection_only"))
222
+ if not model_id:
223
+ self._json(400, {"error": "model required"})
224
+ return
225
+ billing_provider = str(body.get("billing_provider", "")).strip()
226
+ self._json(
227
+ 200,
228
+ hermes_model_set(
229
+ profile,
230
+ model_id,
231
+ user_id,
232
+ workspace_id,
233
+ selection_only=selection_only,
234
+ billing_provider=billing_provider,
235
+ ),
236
+ )
237
+
238
+
239
+ def _route_post_hermes_model_apply_default(self, body: dict) -> None:
240
+ srv = _srv()
241
+ if not srv._check_auth(self):
242
+ self._json(401, {"error": "unauthorized"})
243
+ return
244
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
245
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
246
+ return
247
+ self._json(200, hermes_apply_default_model_config())
248
+
249
+
250
+ def _route_post_hermes_fallback_chain(self, body: dict) -> None:
251
+ srv = _srv()
252
+ if not srv._check_auth(self):
253
+ self._json(401, {"error": "unauthorized"})
254
+ return
255
+ profile = str(body.get("profile", "")).strip()
256
+ chain = body.get("chain", [])
257
+ selection_only = bool(body.get("selection_only"))
258
+ user_id = str(getattr(self, "auth_user", "") or "")
259
+ if not isinstance(chain, list):
260
+ self._json(400, {"error": "chain must be a list"})
261
+ return
262
+ if not selection_only and not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
263
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
264
+ return
265
+ self._json(
266
+ 200,
267
+ hermes_fallback_chain_set(
268
+ profile,
269
+ chain,
270
+ selection_only=selection_only,
271
+ user_id=user_id,
272
+ ),
273
+ )
274
+
275
+
276
+ def _route_post_chat_dispatch(self, body: dict) -> None:
277
+ srv = _srv()
278
+ if not srv._check_auth(self):
279
+ self._json(401, {"error": "unauthorized"})
280
+ return
281
+ self._json(200, chat_dispatch(body))
282
+
283
+ # WF-037 admin/install/oauth GET handlers (batch 2)
284
+
285
+ def _route_get_chat_bootstrap(self, qs: dict[str, list[str]]) -> None:
286
+ srv = _srv()
287
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
288
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
289
+ return
290
+ profile = resolve_validated_profile(qs.get("profile", [""])[0] or srv._primary_profile())
291
+ persistent = qs.get("persistent", [""])[0]
292
+ source_id = qs.get("source", ["ui"])[0]
293
+ self._json(200, chat_bootstrap(profile, persistent, source_id))
294
+
295
+
296
+ def _route_get_hermes_bootstrap(self, qs: dict[str, list[str]]) -> None:
297
+ srv = _srv()
298
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
299
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
300
+ return
301
+ self._json(200, hermes_bootstrap())
302
+
303
+
304
+ def _route_get_hermes_profiles_status(self, qs: dict[str, list[str]]) -> None:
305
+ srv = _srv()
306
+ profile = resolve_validated_profile(qs.get("profile", [""])[0] or srv._primary_profile())
307
+ if srv.SUPERVISOR_URL:
308
+ self._json(
309
+ *srv._supervisor_request(
310
+ "GET",
311
+ f"/v1/profile.status?profile={urllib.parse.quote(profile, safe='')}",
312
+ timeout=10.0,
313
+ )
314
+ )
315
+ return
316
+ self._json(200, profile_gateway_lifecycle(profile, "status"))
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+ def _route_pattern_get_hermes_profile_soul(self, path: str, qs: dict[str, list[str]]) -> None:
326
+ srv = _srv()
327
+ profile_soul_match = re.fullmatch(r"/api/hermes/profiles/([^/]+)/soul", path)
328
+ if not profile_soul_match:
329
+ self._json(404, {"error": "not found"})
330
+ return
331
+ profile = resolve_validated_profile(profile_soul_match.group(1))
332
+ self._json(200, profile_soul_get(profile))
333
+
334
+
335
+ def _route_pattern_get_hermes_profile_detail(self, path: str, qs: dict[str, list[str]]) -> None:
336
+ srv = _srv()
337
+ profile_detail_match = re.fullmatch(r"/api/hermes/profiles/([^/]+)", path)
338
+ if not profile_detail_match or profile_detail_match.group(1) in self._HERMES_PROFILE_RESERVED:
339
+ self._json(404, {"error": "not found"})
340
+ return
341
+ profile = resolve_validated_profile(profile_detail_match.group(1))
342
+ ws_id = ""
343
+ user_id = str(getattr(self, "auth_user", "") or "")
344
+ if user_id:
345
+ try:
346
+ conn = srv._workframe_db()
347
+ row = conn.execute(
348
+ """
349
+ SELECT workspace_id FROM workspace_memberships
350
+ WHERE user_id = ? AND status = 'active' AND deleted_at IS NULL
351
+ ORDER BY created_at ASC LIMIT 1
352
+ """,
353
+ (user_id,),
354
+ ).fetchone()
355
+ conn.close()
356
+ if row:
357
+ ws_id = str(row["workspace_id"])
358
+ except sqlite3.Error:
359
+ pass
360
+ self._json(200, hermes_profile_detail(profile, ws_id))
361
+
362
+
363
+ def _route_pattern_get_hermes_profile_sessions(self, path: str, qs: dict[str, list[str]]) -> None:
364
+ srv = _srv()
365
+ parts = path.strip("/").split("/")
366
+ if len(parts) < 5:
367
+ self._json(404, {"error": "not found"})
368
+ return
369
+ profile_raw = urllib.parse.unquote(parts[3])
370
+ source_id = qs.get("source", ["ui"])[0]
371
+ client_id = qs.get("client", ["default"])[0]
372
+ self._json(
373
+ 200,
374
+ profile_chat_session(
375
+ profile_raw,
376
+ {"source_id": source_id, "client_id": client_id, "new_session": False},
377
+ ),
378
+ )
379
+
380
+
381
+ def _route_pattern_get_hermes_profile_bind(self, path: str, qs: dict[str, list[str]]) -> None:
382
+ srv = _srv()
383
+ parts = path.strip("/").split("/")
384
+ if len(parts) < 5:
385
+ self._json(404, {"error": "not found"})
386
+ return
387
+ profile_raw = urllib.parse.unquote(parts[3])
388
+ source_id = qs.get("source", ["ui"])[0]
389
+ client_id = qs.get("client", ["default"])[0]
390
+ self._json(
391
+ 200,
392
+ profile_chat_bind(
393
+ profile_raw,
394
+ {"source_id": source_id, "client_id": client_id, "new_session": False},
395
+ ),
396
+ )
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+ def _route_post_hermes_profiles_create(self, body: dict) -> None:
408
+ srv = _srv()
409
+ if not srv._check_auth(self):
410
+ self._json(401, {"error": "unauthorized"})
411
+ return
412
+ if not srv._handler_is_active_workspace_member(self):
413
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "workspace_member"})
414
+ return
415
+ name = str(body.get("name") or "").strip()
416
+ if not name:
417
+ self._json(400, {"error": "name required"})
418
+ return
419
+ model = str(body.get("model") or "").strip()
420
+ description = str(body.get("description") or "").strip()
421
+ clone_from = str(body.get("clone_from") or "").strip()
422
+ soul = str(body.get("soul") or "").strip()
423
+ display_name = str(body.get("display_name") or "").strip()
424
+ role = str(body.get("role") or "").strip()
425
+ tagline = str(body.get("tagline") or "").strip()
426
+ avatar_url = str(body.get("avatar_url") or "").strip()
427
+ avatar_id = str(body.get("avatar_id") or "").strip()
428
+ user_id = str(getattr(self, "auth_user", "") or "").strip()
429
+ workspace_id = str(body.get("workspace_id") or "").strip()
430
+ if not workspace_id and user_id:
431
+ workspaces = srv._get_user_workspaces(user_id)
432
+ current = srv._resolve_current_workspace(user_id, workspaces)
433
+ workspace_id = str((current or {}).get("id") or (workspaces[0] or {}).get("id") or "")
434
+ self._json(
435
+ 200,
436
+ profile_create(
437
+ name,
438
+ model,
439
+ description,
440
+ clone_from,
441
+ soul,
442
+ display_name,
443
+ role,
444
+ tagline,
445
+ avatar_url,
446
+ avatar_id,
447
+ user_id=user_id,
448
+ workspace_id=workspace_id,
449
+ ),
450
+ )
451
+
452
+
453
+ def _route_post_hermes_profiles_start(self, body: dict) -> None:
454
+ srv = _srv()
455
+ if not srv._check_auth(self):
456
+ self._json(401, {"error": "unauthorized"})
457
+ return
458
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
459
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
460
+ return
461
+ profile = resolve_validated_profile(str(body.get("profile") or srv._primary_profile()))
462
+ if srv.SUPERVISOR_URL:
463
+ self._json(*srv._supervisor_request("POST", "/v1/profile.start", body))
464
+ return
465
+ self._json(200, profile_gateway_lifecycle(profile, "start"))
466
+
467
+
468
+ def _route_post_hermes_profiles_stop(self, body: dict) -> None:
469
+ srv = _srv()
470
+ if not srv._check_auth(self):
471
+ self._json(401, {"error": "unauthorized"})
472
+ return
473
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
474
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
475
+ return
476
+ profile = resolve_validated_profile(str(body.get("profile") or srv._primary_profile()))
477
+ if srv.SUPERVISOR_URL:
478
+ self._json(*srv._supervisor_request("POST", "/v1/profile.stop", body))
479
+ return
480
+ self._json(200, profile_gateway_lifecycle(profile, "stop"))
481
+
482
+
483
+ def _route_post_hermes_profiles_delete(self, body: dict) -> None:
484
+ srv = _srv()
485
+ if not srv._check_auth(self):
486
+ self._json(401, {"error": "unauthorized"})
487
+ return
488
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
489
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
490
+ return
491
+ profile = str(body.get("profile") or "").strip()
492
+ if not profile:
493
+ self._json(400, {"error": "profile required"})
494
+ return
495
+ self._json(200, profile_delete(profile))
496
+
497
+
498
+ def _route_post_hermes_profiles_disable(self, body: dict) -> None:
499
+ srv = _srv()
500
+ if not srv._check_auth(self):
501
+ self._json(401, {"error": "unauthorized"})
502
+ return
503
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
504
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
505
+ return
506
+ profile = resolve_validated_profile(str(body.get("profile") or srv._primary_profile()))
507
+ if srv.SUPERVISOR_URL:
508
+ self._json(*srv._supervisor_request("POST", "/v1/profile.disable", body))
509
+ return
510
+ self._json(200, profile_gateway_lifecycle(profile, "disable"))
511
+
512
+
513
+ def _route_pattern_post_hermes_profile_bootstrap_dm(self, path: str, body: dict) -> None:
514
+ srv = _srv()
515
+ profile_bootstrap_post = re.fullmatch(r"/api/hermes/profiles/([^/]+)/bootstrap-dm", path)
516
+ if not profile_bootstrap_post:
517
+ self._json(404, {"error": "not found"})
518
+ return
519
+ if not srv._check_auth(self):
520
+ self._json(401, {"error": "unauthorized"})
521
+ return
522
+ if not srv._handler_is_active_workspace_member(self):
523
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "workspace_member"})
524
+ return
525
+ template = resolve_validated_profile(profile_bootstrap_post.group(1))
526
+ user_id = str(getattr(self, "auth_user", "") or "").strip()
527
+ workspace_id = str(body.get("workspace_id") or "").strip()
528
+ if not workspace_id and user_id:
529
+ workspaces = srv._get_user_workspaces(user_id)
530
+ current = srv._resolve_current_workspace(user_id, workspaces)
531
+ workspace_id = str((current or {}).get("id") or (workspaces[0] or {}).get("id") or "")
532
+ if not user_id or not workspace_id:
533
+ self._json(400, {"error": "workspace_id and session required"})
534
+ return
535
+ model = str(body.get("model") or "").strip()
536
+ soul = str(body.get("soul") or "").strip()
537
+ display_name = str(body.get("display_name") or body.get("room_name") or "").strip()
538
+ role = str(body.get("role") or "").strip()
539
+ tagline = str(body.get("tagline") or "").strip()
540
+ lane = bootstrap_agent_dm_lane(
541
+ user_id,
542
+ workspace_id,
543
+ template,
544
+ model=model,
545
+ soul=soul,
546
+ bind_session=bool(body.get("bind_session", True)),
547
+ room_name=display_name or str(body.get("room_name") or "").strip(),
548
+ role=role,
549
+ tagline=tagline,
550
+ created_by=user_id,
551
+ )
552
+ status = 200 if lane.get("ok") else 500
553
+ self._json(status, lane)
554
+
555
+
556
+ def _route_pattern_post_hermes_profile_soul(self, path: str, body: dict) -> None:
557
+ srv = _srv()
558
+ profile_soul_post = re.fullmatch(r"/api/hermes/profiles/([^/]+)/soul", path)
559
+ if not profile_soul_post:
560
+ self._json(404, {"error": "not found"})
561
+ return
562
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
563
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
564
+ return
565
+ profile = resolve_validated_profile(profile_soul_post.group(1))
566
+ soul = str(body.get("soul", body.get("text", "")))
567
+ self._json(200, profile_soul_set(profile, soul))
568
+
569
+
570
+ def _route_pattern_post_hermes_profile_bind(self, path: str, body: dict) -> None:
571
+ srv = _srv()
572
+ parts = path.strip("/").split("/")
573
+ if len(parts) < 5:
574
+ self._json(404, {"error": "not found"})
575
+ return
576
+ profile_raw = urllib.parse.unquote(parts[3])
577
+ user_id = str(getattr(self, "auth_user", "") or "")
578
+ self._json(200, profile_chat_bind(profile_raw, body, user_id))
579
+
580
+
581
+ def _route_pattern_post_hermes_profile_sessions(self, path: str, body: dict) -> None:
582
+ srv = _srv()
583
+ parts = path.strip("/").split("/")
584
+ if len(parts) < 5:
585
+ self._json(404, {"error": "not found"})
586
+ return
587
+ profile_raw = urllib.parse.unquote(parts[3])
588
+ user_id = str(getattr(self, "auth_user", "") or "")
589
+ self._json(200, profile_chat_session(profile_raw, body, user_id))
590
+
591
+
592
+ def _route_pattern_post_hermes_profile_messages(self, path: str, body: dict) -> None:
593
+ srv = _srv()
594
+ parts = path.strip("/").split("/")
595
+ if len(parts) < 5:
596
+ self._json(404, {"error": "not found"})
597
+ return
598
+ profile_raw = urllib.parse.unquote(parts[3])
599
+ stream_body = _enrich_room_chat_payload(body, str(getattr(self, "auth_user", "") or ""))
600
+ self._json(200, profile_chat_message(profile_raw, stream_body))
601
+
602
+
603
+ def _route_pattern_post_hermes_profile_messages_stream(self, path: str, body: dict) -> None:
604
+ srv = _srv()
605
+ parts = path.strip("/").split("/")
606
+ if len(parts) < 6:
607
+ self._json(404, {"error": "not found"})
608
+ return
609
+ profile_raw = urllib.parse.unquote(parts[3])
610
+ stream_body = _enrich_room_chat_payload(body, str(getattr(self, "auth_user", "") or ""))
611
+ stream_profile_chat(self, profile_raw, stream_body)
612
+
613
+
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+ def _route_pattern_patch_hermes_profile(self, path: str, body: dict) -> None:
631
+ srv = _srv()
632
+ profile_patch_match = re.fullmatch(r"/api/hermes/profiles/([^/]+)", path)
633
+ if not profile_patch_match or profile_patch_match.group(1) in self._HERMES_PROFILE_RESERVED:
634
+ self._json(404, {"error": "not found"})
635
+ return
636
+ if not srv._role_allows(self, srv.OWNER_ADMIN_ROLES):
637
+ self._json(403, {"ok": False, "error": "forbidden", "required_role": "owner_or_admin"})
638
+ return
639
+ profile = resolve_validated_profile(profile_patch_match.group(1))
640
+ self._json(200, hermes_profile_update(profile, body))
641
+