@bitseek/hermes-webui 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +213 -0
  2. package/bin/hermes-webui.mjs +588 -0
  3. package/package.json +25 -0
  4. package/scripts/sync-vendor.mjs +74 -0
  5. package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
  6. package/templates/systemd/hermes-webui.service +13 -0
  7. package/templates/windows/hermes-webui-task.ps1 +3 -0
  8. package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
  9. package/vendor/agent-frontend-shell/.dockerignore +7 -0
  10. package/vendor/agent-frontend-shell/.env.docker.example +89 -0
  11. package/vendor/agent-frontend-shell/.env.example +34 -0
  12. package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
  13. package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
  14. package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
  15. package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
  16. package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
  17. package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
  18. package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
  19. package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
  20. package/vendor/agent-frontend-shell/AGENTS.md +80 -0
  21. package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
  22. package/vendor/agent-frontend-shell/BUGS.md +52 -0
  23. package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
  24. package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
  25. package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
  26. package/vendor/agent-frontend-shell/DESIGN.md +173 -0
  27. package/vendor/agent-frontend-shell/Dockerfile +91 -0
  28. package/vendor/agent-frontend-shell/LICENSE +21 -0
  29. package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
  30. package/vendor/agent-frontend-shell/README.md +705 -0
  31. package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
  32. package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
  33. package/vendor/agent-frontend-shell/TESTING.md +1932 -0
  34. package/vendor/agent-frontend-shell/THEMES.md +170 -0
  35. package/vendor/agent-frontend-shell/api/__init__.py +1 -0
  36. package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
  37. package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
  38. package/vendor/agent-frontend-shell/api/auth.py +592 -0
  39. package/vendor/agent-frontend-shell/api/background.py +87 -0
  40. package/vendor/agent-frontend-shell/api/clarify.py +238 -0
  41. package/vendor/agent-frontend-shell/api/commands.py +124 -0
  42. package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
  43. package/vendor/agent-frontend-shell/api/config.py +5178 -0
  44. package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
  45. package/vendor/agent-frontend-shell/api/extensions.py +253 -0
  46. package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
  47. package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
  48. package/vendor/agent-frontend-shell/api/goals.py +608 -0
  49. package/vendor/agent-frontend-shell/api/helpers.py +474 -0
  50. package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
  51. package/vendor/agent-frontend-shell/api/metering.py +194 -0
  52. package/vendor/agent-frontend-shell/api/models.py +4210 -0
  53. package/vendor/agent-frontend-shell/api/oauth.py +770 -0
  54. package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
  55. package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
  56. package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
  57. package/vendor/agent-frontend-shell/api/providers.py +2175 -0
  58. package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
  59. package/vendor/agent-frontend-shell/api/rollback.py +320 -0
  60. package/vendor/agent-frontend-shell/api/routes.py +13990 -0
  61. package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
  62. package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
  63. package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
  64. package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
  65. package/vendor/agent-frontend-shell/api/session_events.py +45 -0
  66. package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
  67. package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
  68. package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
  69. package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
  70. package/vendor/agent-frontend-shell/api/startup.py +128 -0
  71. package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
  72. package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
  73. package/vendor/agent-frontend-shell/api/system_health.py +167 -0
  74. package/vendor/agent-frontend-shell/api/terminal.py +410 -0
  75. package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
  76. package/vendor/agent-frontend-shell/api/updates.py +1261 -0
  77. package/vendor/agent-frontend-shell/api/upload.py +322 -0
  78. package/vendor/agent-frontend-shell/api/usage.py +26 -0
  79. package/vendor/agent-frontend-shell/api/workspace.py +867 -0
  80. package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
  81. package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
  82. package/vendor/agent-frontend-shell/bootstrap.py +492 -0
  83. package/vendor/agent-frontend-shell/ctl.sh +427 -0
  84. package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
  85. package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
  86. package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
  87. package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
  88. package/vendor/agent-frontend-shell/docker_init.bash +459 -0
  89. package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
  90. package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
  91. package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
  92. package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
  93. package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
  94. package/vendor/agent-frontend-shell/docs/docker.md +337 -0
  95. package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
  96. package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
  97. package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
  98. package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
  99. package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
  100. package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
  101. package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
  102. package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
  103. package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
  104. package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
  105. package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
  106. package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
  107. package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
  108. package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
  109. package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
  110. package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
  111. package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
  112. package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
  113. package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
  114. package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
  115. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
  116. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
  117. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
  118. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
  119. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
  120. package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
  121. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
  122. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
  123. package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
  124. package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
  125. package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
  126. package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
  127. package/vendor/agent-frontend-shell/extensions/index.js +67 -0
  128. package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
  129. package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
  130. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
  131. package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
  132. package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
  133. package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
  134. package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
  135. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
  136. package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
  137. package/vendor/agent-frontend-shell/install.sh +63 -0
  138. package/vendor/agent-frontend-shell/mcp_server.py +567 -0
  139. package/vendor/agent-frontend-shell/package.json +12 -0
  140. package/vendor/agent-frontend-shell/pyproject.toml +56 -0
  141. package/vendor/agent-frontend-shell/pytest.ini +3 -0
  142. package/vendor/agent-frontend-shell/requirements.txt +5 -0
  143. package/vendor/agent-frontend-shell/server.py +624 -0
  144. package/vendor/agent-frontend-shell/start.ps1 +210 -0
  145. package/vendor/agent-frontend-shell/start.sh +65 -0
  146. package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
  147. package/vendor/agent-frontend-shell/static/boot.js +1990 -0
  148. package/vendor/agent-frontend-shell/static/commands.js +1402 -0
  149. package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
  150. package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
  151. package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
  152. package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
  153. package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
  154. package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
  155. package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
  156. package/vendor/agent-frontend-shell/static/icons.js +92 -0
  157. package/vendor/agent-frontend-shell/static/index.html +1506 -0
  158. package/vendor/agent-frontend-shell/static/login.js +177 -0
  159. package/vendor/agent-frontend-shell/static/manifest.json +53 -0
  160. package/vendor/agent-frontend-shell/static/messages.js +3521 -0
  161. package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
  162. package/vendor/agent-frontend-shell/static/panels.js +7995 -0
  163. package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
  164. package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
  165. package/vendor/agent-frontend-shell/static/style.css +4774 -0
  166. package/vendor/agent-frontend-shell/static/sw.js +173 -0
  167. package/vendor/agent-frontend-shell/static/terminal.js +632 -0
  168. package/vendor/agent-frontend-shell/static/ui.js +8997 -0
  169. package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
  170. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
  171. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
  172. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  173. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  174. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  175. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  176. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  177. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  178. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  179. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  180. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  181. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  182. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  183. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  184. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  185. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
  186. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
  187. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
  188. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  189. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  190. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  191. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
  192. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
  193. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
  194. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
  195. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
  196. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
  197. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  198. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  199. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  200. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
  201. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
  202. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
  203. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  204. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  205. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  206. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  207. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  208. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  209. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  210. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  211. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  212. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
  213. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
  214. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
  215. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
  216. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
  217. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  218. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
  219. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
  220. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  221. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
  222. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
  223. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  224. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
  225. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
  226. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  227. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  228. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  229. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  230. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
  231. package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
  232. package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
  233. package/vendor/agent-frontend-shell/static/workspace.js +680 -0
@@ -0,0 +1,431 @@
1
+ """RuntimeAdapter seam for WebUI-owned run execution.
2
+
3
+ This is the #1925 RuntimeAdapter seam. The default WebUI chat path remains the
4
+ legacy direct route; enabling ``HERMES_WEBUI_RUNTIME_ADAPTER=legacy-journal``
5
+ routes through this protocol-translator facade over the same legacy execution
6
+ path plus the Slice 1 run journal. Slice 4 adds a default-off runner-local
7
+ selection point for tests and future runner backends, but live chat routes still
8
+ stay on the legacy path until a separate route-wiring slice is reviewed. This
9
+ module intentionally does not own AIAgent instances, cancellation flags,
10
+ approval callbacks, clarify callbacks, or new long-lived queues.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ import os
16
+ from pathlib import Path
17
+ from typing import Any, Callable, Iterable, Literal, Protocol
18
+
19
+ _RUNTIME_ADAPTER_ENV = "HERMES_WEBUI_RUNTIME_ADAPTER"
20
+ _RUNTIME_ADAPTER_DIRECT = "legacy-direct"
21
+ _RUNTIME_ADAPTER_JOURNAL = "legacy-journal"
22
+ _RUNTIME_ADAPTER_RUNNER_LOCAL = "runner-local"
23
+ _VALID_RUNTIME_ADAPTER_MODES = {
24
+ _RUNTIME_ADAPTER_DIRECT,
25
+ _RUNTIME_ADAPTER_JOURNAL,
26
+ _RUNTIME_ADAPTER_RUNNER_LOCAL,
27
+ }
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class StartRunRequest:
32
+ session_id: str
33
+ message: str
34
+ attachments: list[dict[str, Any]] = field(default_factory=list)
35
+ workspace: str | None = None
36
+ profile: str | None = None
37
+ provider: str | None = None
38
+ model: str | None = None
39
+ toolsets: list[str] = field(default_factory=list)
40
+ source: str = "webui"
41
+ metadata: dict[str, Any] = field(default_factory=dict)
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class RunStartResult:
46
+ run_id: str
47
+ session_id: str
48
+ stream_id: str
49
+ status: str = "started"
50
+ started_at: float | None = None
51
+ cursor: str | None = None
52
+ active_controls: list[str] = field(default_factory=list)
53
+ payload: dict[str, Any] = field(default_factory=dict)
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class RunEventStream:
58
+ run_id: str
59
+ events: list[dict[str, Any]] = field(default_factory=list)
60
+ cursor: str | None = None
61
+ last_event_id: str | None = None
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class RunStatus:
66
+ run_id: str
67
+ session_id: str | None = None
68
+ status: str = "unknown"
69
+ last_event_id: str | None = None
70
+ terminal_state: str | None = None
71
+ active_controls: list[str] = field(default_factory=list)
72
+ pending_approval_id: str | None = None
73
+ pending_clarify_id: str | None = None
74
+
75
+
76
+ @dataclass(frozen=True, eq=True, unsafe_hash=False)
77
+ class ControlResult:
78
+ # NOTE: `payload: dict` makes this dataclass unhashable by design.
79
+ # `unsafe_hash=False` makes that explicit so future maintainers don't try
80
+ # to add `frozen=True`-implied hashability back (would silently break the
81
+ # moment any caller adds dict / list fields). Opus advisor stage-384 followup.
82
+ accepted: bool
83
+ status: str = "accepted"
84
+ event_id: str | None = None
85
+ safe_message: str | None = None
86
+ payload: dict[str, Any] = field(default_factory=dict)
87
+
88
+
89
+ class RuntimeAdapter(Protocol):
90
+ def start_run(self, request: StartRunRequest) -> RunStartResult: ...
91
+ def observe_run(self, run_id: str, *, cursor: str | None = None) -> RunEventStream: ...
92
+ def get_run(self, run_id: str) -> RunStatus: ...
93
+ def cancel_run(self, run_id: str) -> ControlResult: ...
94
+ def respond_approval(self, run_id: str, approval_id: str, choice: str) -> ControlResult: ...
95
+ def respond_clarify(self, run_id: str, clarify_id: str, response: str) -> ControlResult: ...
96
+ def queue_message(self, run_id: str, message: str, *, mode: str = "queue") -> ControlResult: ...
97
+ def update_goal(
98
+ self,
99
+ session_id: str,
100
+ action: Literal["set", "pause", "resume", "clear", "status", "edit"],
101
+ text: str = "",
102
+ ) -> ControlResult: ...
103
+
104
+
105
+ def runtime_adapter_mode(environ: dict[str, str] | None = None) -> str:
106
+ """Return the configured adapter mode, defaulting safely to legacy-direct."""
107
+ source = os.environ if environ is None else environ
108
+ raw = str(source.get(_RUNTIME_ADAPTER_ENV, _RUNTIME_ADAPTER_DIRECT) or "").strip().lower()
109
+ return raw if raw in _VALID_RUNTIME_ADAPTER_MODES else _RUNTIME_ADAPTER_DIRECT
110
+
111
+
112
+ def runtime_adapter_enabled(environ: dict[str, str] | None = None) -> bool:
113
+ return runtime_adapter_mode(environ) == _RUNTIME_ADAPTER_JOURNAL
114
+
115
+
116
+ def runtime_adapter_runner_enabled(environ: dict[str, str] | None = None) -> bool:
117
+ return runtime_adapter_mode(environ) == _RUNTIME_ADAPTER_RUNNER_LOCAL
118
+
119
+
120
+ def build_runtime_adapter(
121
+ *,
122
+ environ: dict[str, str] | None = None,
123
+ legacy_adapter_factory: Callable[[], RuntimeAdapter] | None = None,
124
+ runner_client_factory: Callable[[], Any] | None = None,
125
+ ) -> RuntimeAdapter | None:
126
+ """Build the configured RuntimeAdapter without changing route behavior.
127
+
128
+ ``None`` means the safe default ``legacy-direct`` path should keep using the
129
+ existing direct route. ``legacy-journal`` is opt-in and delegates to the
130
+ supplied legacy factory. ``runner-local`` is also opt-in and only constructs
131
+ a ``RunnerRuntimeAdapter`` around an injected client; this function does not
132
+ create process-global runner state or wire live chat to the runner backend.
133
+ """
134
+ mode = runtime_adapter_mode(environ)
135
+ if mode == _RUNTIME_ADAPTER_DIRECT:
136
+ return None
137
+ if mode == _RUNTIME_ADAPTER_JOURNAL:
138
+ if legacy_adapter_factory is None:
139
+ raise NotImplementedError("legacy-journal mode requires a legacy adapter factory")
140
+ return legacy_adapter_factory()
141
+ if runner_client_factory is None:
142
+ raise NotImplementedError("runner-local mode requires a runner client factory")
143
+ return RunnerRuntimeAdapter(client=runner_client_factory())
144
+
145
+
146
+ def _cursor_to_after_seq(cursor: str | None) -> int | None:
147
+ if cursor in (None, ""):
148
+ return None
149
+ try:
150
+ text = str(cursor)
151
+ if ":" in text:
152
+ text = text.rsplit(":", 1)[-1]
153
+ return max(0, int(text))
154
+ except (TypeError, ValueError):
155
+ return 0
156
+
157
+
158
+ def _active_control_result(value: Any) -> ControlResult:
159
+ """Normalize legacy delegate responses without changing their payloads.
160
+
161
+ ``status`` is an adapter-level summary used by current control tests and
162
+ future runtime backends. For legacy goal payloads it may mirror the goal
163
+ action (``set`` / ``pause`` / ``status``), while public route behavior keeps
164
+ using the payload itself to preserve existing HTTP response shapes.
165
+ """
166
+ if isinstance(value, ControlResult):
167
+ return value
168
+ if isinstance(value, dict):
169
+ accepted = bool(value.get("ok", True))
170
+ return ControlResult(
171
+ accepted=accepted,
172
+ status=str(value.get("status") or value.get("action") or ("accepted" if accepted else "not-active")),
173
+ safe_message=value.get("message") if not accepted else None,
174
+ payload=dict(value),
175
+ )
176
+ accepted = bool(value)
177
+ return ControlResult(
178
+ accepted=accepted,
179
+ status="accepted" if accepted else "not-active",
180
+ safe_message=None if accepted else "Legacy control did not accept the request.",
181
+ )
182
+
183
+
184
+ def _runner_unsupported_control(name: str) -> ControlResult:
185
+ return ControlResult(
186
+ False,
187
+ status="unsupported",
188
+ safe_message=f"{name} is not supported by this runner backend.",
189
+ )
190
+
191
+
192
+ class RunnerRuntimeAdapter:
193
+ """Protocol-translator facade for a future runner/sidecar backend.
194
+
195
+ Slice 4 moves runtime ownership behind a runner boundary, but the WebUI
196
+ adapter must remain a translator. This class deliberately delegates to an
197
+ injected client instead of owning process-local streams, cancellation flags,
198
+ approval queues, clarify queues, or cached agent instances itself.
199
+ """
200
+
201
+ def __init__(self, *, client: Any):
202
+ self._client = client
203
+
204
+ def start_run(self, request: StartRunRequest) -> RunStartResult:
205
+ start_run = getattr(self._client, "start_run", None)
206
+ if start_run is None:
207
+ raise NotImplementedError("RunnerRuntimeAdapter.start_run requires a runner client")
208
+ payload = start_run(request)
209
+ if isinstance(payload, RunStartResult):
210
+ return payload
211
+ payload = dict(payload or {})
212
+ run_id = str(payload.get("run_id") or payload.get("stream_id") or "")
213
+ stream_id = str(payload.get("stream_id") or run_id)
214
+ session_id = str(payload.get("session_id") or request.session_id)
215
+ active_controls = payload.get("active_controls")
216
+ if not isinstance(active_controls, list):
217
+ active_controls = []
218
+ return RunStartResult(
219
+ run_id=run_id,
220
+ session_id=session_id,
221
+ stream_id=stream_id,
222
+ status=str(payload.get("status") or "started"),
223
+ started_at=payload.get("started_at"),
224
+ cursor=payload.get("cursor"),
225
+ active_controls=active_controls,
226
+ payload=payload,
227
+ )
228
+
229
+ def observe_run(self, run_id: str, *, cursor: str | None = None) -> RunEventStream:
230
+ observe_run = getattr(self._client, "observe_run", None)
231
+ if observe_run is None:
232
+ return RunEventStream(run_id=run_id, events=[], cursor=cursor, last_event_id=None)
233
+ result = observe_run(run_id, cursor=cursor)
234
+ if isinstance(result, RunEventStream):
235
+ return result
236
+ payload = dict(result or {})
237
+ events = list(payload.get("events") or [])
238
+ last_event_id = payload.get("last_event_id") or (events[-1].get("event_id") if events else None)
239
+ next_cursor = payload.get("cursor")
240
+ if next_cursor is None and events:
241
+ next_cursor = str(events[-1].get("seq") or "")
242
+ return RunEventStream(
243
+ run_id=str(payload.get("run_id") or run_id),
244
+ events=events,
245
+ cursor=str(next_cursor) if next_cursor is not None else cursor,
246
+ last_event_id=last_event_id,
247
+ )
248
+
249
+ def get_run(self, run_id: str) -> RunStatus:
250
+ get_run = getattr(self._client, "get_run", None)
251
+ if get_run is None:
252
+ return RunStatus(run_id=run_id)
253
+ result = get_run(run_id)
254
+ if isinstance(result, RunStatus):
255
+ return result
256
+ payload = dict(result or {})
257
+ active_controls = payload.get("active_controls")
258
+ if not isinstance(active_controls, list):
259
+ active_controls = []
260
+ return RunStatus(
261
+ run_id=str(payload.get("run_id") or run_id),
262
+ session_id=str(payload.get("session_id") or "") or None,
263
+ status=str(payload.get("status") or "unknown"),
264
+ last_event_id=payload.get("last_event_id"),
265
+ terminal_state=payload.get("terminal_state"),
266
+ active_controls=active_controls,
267
+ pending_approval_id=payload.get("pending_approval_id"),
268
+ pending_clarify_id=payload.get("pending_clarify_id"),
269
+ )
270
+
271
+ def cancel_run(self, run_id: str) -> ControlResult:
272
+ cancel_run = getattr(self._client, "cancel_run", None)
273
+ if cancel_run is None:
274
+ return _runner_unsupported_control("Cancel")
275
+ return _active_control_result(cancel_run(run_id))
276
+
277
+ def respond_approval(self, run_id: str, approval_id: str, choice: str) -> ControlResult:
278
+ respond_approval = getattr(self._client, "respond_approval", None)
279
+ if respond_approval is None:
280
+ return _runner_unsupported_control("Approval")
281
+ return _active_control_result(respond_approval(run_id, approval_id, choice))
282
+
283
+ def respond_clarify(self, run_id: str, clarify_id: str, response: str) -> ControlResult:
284
+ respond_clarify = getattr(self._client, "respond_clarify", None)
285
+ if respond_clarify is None:
286
+ return _runner_unsupported_control("Clarify")
287
+ return _active_control_result(respond_clarify(run_id, clarify_id, response))
288
+
289
+ def queue_message(self, run_id: str, message: str, *, mode: str = "queue") -> ControlResult:
290
+ queue_message = getattr(self._client, "queue_message", None)
291
+ if queue_message is None:
292
+ return _runner_unsupported_control("Queue")
293
+ return _active_control_result(queue_message(run_id, message, mode=mode))
294
+
295
+ def update_goal(
296
+ self,
297
+ session_id: str,
298
+ action: Literal["set", "pause", "resume", "clear", "status", "edit"],
299
+ text: str = "",
300
+ ) -> ControlResult:
301
+ update_goal = getattr(self._client, "update_goal", None)
302
+ if update_goal is None:
303
+ return _runner_unsupported_control("Goal")
304
+ return _active_control_result(update_goal(session_id, action, text))
305
+
306
+
307
+ class LegacyJournalRuntimeAdapter:
308
+ """Protocol-translator facade over the current legacy streaming path.
309
+
310
+ Delegates keep Slice 2 honest: this adapter has no worker thread, AIAgent
311
+ cache, cancellation registry, approval queue, or clarify queue of its own.
312
+ """
313
+
314
+ def __init__(
315
+ self,
316
+ *,
317
+ start_run_delegate: Callable[[StartRunRequest], dict[str, Any]] | None = None,
318
+ cancel_delegate: Callable[[str], Any] | None = None,
319
+ approval_delegate: Callable[[str, str, str], Any] | None = None,
320
+ clarify_delegate: Callable[[str, str, str], Any] | None = None,
321
+ queue_delegate: Callable[[str, str, str], Any] | None = None,
322
+ goal_delegate: Callable[[str, str, str], Any] | None = None,
323
+ live_stream_lookup: Callable[[str], bool] | None = None,
324
+ session_dir: Path | None = None,
325
+ ):
326
+ self._start_run_delegate = start_run_delegate
327
+ self._cancel_delegate = cancel_delegate
328
+ self._approval_delegate = approval_delegate
329
+ self._clarify_delegate = clarify_delegate
330
+ self._queue_delegate = queue_delegate
331
+ self._goal_delegate = goal_delegate
332
+ self._live_stream_lookup = live_stream_lookup or (lambda _run_id: False)
333
+ self._session_dir = Path(session_dir) if session_dir is not None else None
334
+
335
+ def start_run(self, request: StartRunRequest) -> RunStartResult:
336
+ if self._start_run_delegate is None:
337
+ raise NotImplementedError("LegacyJournalRuntimeAdapter.start_run requires a legacy delegate")
338
+ payload = dict(self._start_run_delegate(request) or {})
339
+ stream_id = str(payload.get("stream_id") or payload.get("run_id") or "")
340
+ run_id = str(payload.get("run_id") or stream_id)
341
+ session_id = str(payload.get("session_id") or request.session_id)
342
+ active_controls = payload.get("active_controls")
343
+ if not isinstance(active_controls, list):
344
+ active_controls = ["cancel"] if stream_id else []
345
+ return RunStartResult(
346
+ run_id=run_id,
347
+ session_id=session_id,
348
+ stream_id=stream_id,
349
+ status=str(payload.get("status") or "started"),
350
+ started_at=payload.get("started_at"),
351
+ cursor=payload.get("cursor"),
352
+ active_controls=active_controls,
353
+ payload=payload,
354
+ )
355
+
356
+ def observe_run(self, run_id: str, *, cursor: str | None = None) -> RunEventStream:
357
+ from api.run_journal import find_run_summary, read_run_events
358
+
359
+ summary = find_run_summary(run_id, session_dir=self._session_dir)
360
+ if not summary:
361
+ return RunEventStream(run_id=run_id, events=[], cursor=cursor, last_event_id=None)
362
+ journal = read_run_events(
363
+ str(summary.get("session_id") or ""),
364
+ run_id,
365
+ after_seq=_cursor_to_after_seq(cursor),
366
+ session_dir=self._session_dir,
367
+ )
368
+ events = list(journal.get("events") or [])
369
+ last_event_id = events[-1].get("event_id") if events else summary.get("last_event_id")
370
+ return RunEventStream(
371
+ run_id=run_id,
372
+ events=events,
373
+ cursor=str(events[-1].get("seq")) if events else cursor,
374
+ last_event_id=last_event_id,
375
+ )
376
+
377
+ def get_run(self, run_id: str) -> RunStatus:
378
+ from api.run_journal import find_run_summary
379
+
380
+ live = bool(self._live_stream_lookup(run_id))
381
+ summary = find_run_summary(run_id, session_dir=self._session_dir)
382
+ if live:
383
+ return RunStatus(
384
+ run_id=run_id,
385
+ session_id=str((summary or {}).get("session_id") or "") or None,
386
+ status="running",
387
+ last_event_id=(summary or {}).get("last_event_id"),
388
+ terminal_state=None,
389
+ active_controls=["cancel"],
390
+ )
391
+ if summary:
392
+ terminal_state = summary.get("terminal_state")
393
+ return RunStatus(
394
+ run_id=run_id,
395
+ session_id=str(summary.get("session_id") or "") or None,
396
+ status=str(terminal_state or "unknown"),
397
+ last_event_id=summary.get("last_event_id"),
398
+ terminal_state=terminal_state,
399
+ active_controls=[],
400
+ )
401
+ return RunStatus(run_id=run_id)
402
+
403
+ def cancel_run(self, run_id: str) -> ControlResult:
404
+ if self._cancel_delegate is None:
405
+ return ControlResult(False, status="unsupported", safe_message="Cancel is not wired for this adapter.")
406
+ return _active_control_result(self._cancel_delegate(run_id))
407
+
408
+ def respond_approval(self, run_id: str, approval_id: str, choice: str) -> ControlResult:
409
+ if self._approval_delegate is None:
410
+ return ControlResult(False, status="unsupported", safe_message="Approval is delegated to the legacy path.")
411
+ return _active_control_result(self._approval_delegate(run_id, approval_id, choice))
412
+
413
+ def respond_clarify(self, run_id: str, clarify_id: str, response: str) -> ControlResult:
414
+ if self._clarify_delegate is None:
415
+ return ControlResult(False, status="unsupported", safe_message="Clarify is delegated to the legacy path.")
416
+ return _active_control_result(self._clarify_delegate(run_id, clarify_id, response))
417
+
418
+ def queue_message(self, run_id: str, message: str, *, mode: str = "queue") -> ControlResult:
419
+ if self._queue_delegate is None:
420
+ return ControlResult(False, status="unsupported", safe_message="Queue is delegated to the legacy path.")
421
+ return _active_control_result(self._queue_delegate(run_id, message, mode))
422
+
423
+ def update_goal(
424
+ self,
425
+ session_id: str,
426
+ action: Literal["set", "pause", "resume", "clear", "status", "edit"],
427
+ text: str = "",
428
+ ) -> ControlResult:
429
+ if self._goal_delegate is None:
430
+ return ControlResult(False, status="unsupported", safe_message="Goal is delegated to the legacy path.")
431
+ return _active_control_result(self._goal_delegate(session_id, action, text))