@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.
- package/README.md +213 -0
- package/bin/hermes-webui.mjs +588 -0
- package/package.json +25 -0
- package/scripts/sync-vendor.mjs +74 -0
- package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
- package/templates/systemd/hermes-webui.service +13 -0
- package/templates/windows/hermes-webui-task.ps1 +3 -0
- package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
- package/vendor/agent-frontend-shell/.dockerignore +7 -0
- package/vendor/agent-frontend-shell/.env.docker.example +89 -0
- package/vendor/agent-frontend-shell/.env.example +34 -0
- package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
- package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
- package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
- package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
- package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
- package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
- package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
- package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
- package/vendor/agent-frontend-shell/AGENTS.md +80 -0
- package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
- package/vendor/agent-frontend-shell/BUGS.md +52 -0
- package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
- package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
- package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
- package/vendor/agent-frontend-shell/DESIGN.md +173 -0
- package/vendor/agent-frontend-shell/Dockerfile +91 -0
- package/vendor/agent-frontend-shell/LICENSE +21 -0
- package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
- package/vendor/agent-frontend-shell/README.md +705 -0
- package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
- package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
- package/vendor/agent-frontend-shell/TESTING.md +1932 -0
- package/vendor/agent-frontend-shell/THEMES.md +170 -0
- package/vendor/agent-frontend-shell/api/__init__.py +1 -0
- package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
- package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
- package/vendor/agent-frontend-shell/api/auth.py +592 -0
- package/vendor/agent-frontend-shell/api/background.py +87 -0
- package/vendor/agent-frontend-shell/api/clarify.py +238 -0
- package/vendor/agent-frontend-shell/api/commands.py +124 -0
- package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
- package/vendor/agent-frontend-shell/api/config.py +5178 -0
- package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
- package/vendor/agent-frontend-shell/api/extensions.py +253 -0
- package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
- package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
- package/vendor/agent-frontend-shell/api/goals.py +608 -0
- package/vendor/agent-frontend-shell/api/helpers.py +474 -0
- package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
- package/vendor/agent-frontend-shell/api/metering.py +194 -0
- package/vendor/agent-frontend-shell/api/models.py +4210 -0
- package/vendor/agent-frontend-shell/api/oauth.py +770 -0
- package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
- package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
- package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
- package/vendor/agent-frontend-shell/api/providers.py +2175 -0
- package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
- package/vendor/agent-frontend-shell/api/rollback.py +320 -0
- package/vendor/agent-frontend-shell/api/routes.py +13990 -0
- package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
- package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
- package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
- package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
- package/vendor/agent-frontend-shell/api/session_events.py +45 -0
- package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
- package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
- package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
- package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
- package/vendor/agent-frontend-shell/api/startup.py +128 -0
- package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
- package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
- package/vendor/agent-frontend-shell/api/system_health.py +167 -0
- package/vendor/agent-frontend-shell/api/terminal.py +410 -0
- package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
- package/vendor/agent-frontend-shell/api/updates.py +1261 -0
- package/vendor/agent-frontend-shell/api/upload.py +322 -0
- package/vendor/agent-frontend-shell/api/usage.py +26 -0
- package/vendor/agent-frontend-shell/api/workspace.py +867 -0
- package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
- package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
- package/vendor/agent-frontend-shell/bootstrap.py +492 -0
- package/vendor/agent-frontend-shell/ctl.sh +427 -0
- package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
- package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
- package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
- package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
- package/vendor/agent-frontend-shell/docker_init.bash +459 -0
- package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
- package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
- package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
- package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
- package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
- package/vendor/agent-frontend-shell/docs/docker.md +337 -0
- package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
- package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
- package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
- package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
- package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
- package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
- package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
- package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
- package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
- package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
- package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
- package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
- package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
- package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
- package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
- package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
- package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
- package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
- package/vendor/agent-frontend-shell/extensions/index.js +67 -0
- package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
- package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
- package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
- package/vendor/agent-frontend-shell/install.sh +63 -0
- package/vendor/agent-frontend-shell/mcp_server.py +567 -0
- package/vendor/agent-frontend-shell/package.json +12 -0
- package/vendor/agent-frontend-shell/pyproject.toml +56 -0
- package/vendor/agent-frontend-shell/pytest.ini +3 -0
- package/vendor/agent-frontend-shell/requirements.txt +5 -0
- package/vendor/agent-frontend-shell/server.py +624 -0
- package/vendor/agent-frontend-shell/start.ps1 +210 -0
- package/vendor/agent-frontend-shell/start.sh +65 -0
- package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/static/boot.js +1990 -0
- package/vendor/agent-frontend-shell/static/commands.js +1402 -0
- package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
- package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
- package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
- package/vendor/agent-frontend-shell/static/icons.js +92 -0
- package/vendor/agent-frontend-shell/static/index.html +1506 -0
- package/vendor/agent-frontend-shell/static/login.js +177 -0
- package/vendor/agent-frontend-shell/static/manifest.json +53 -0
- package/vendor/agent-frontend-shell/static/messages.js +3521 -0
- package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
- package/vendor/agent-frontend-shell/static/panels.js +7995 -0
- package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
- package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
- package/vendor/agent-frontend-shell/static/style.css +4774 -0
- package/vendor/agent-frontend-shell/static/sw.js +173 -0
- package/vendor/agent-frontend-shell/static/terminal.js +632 -0
- package/vendor/agent-frontend-shell/static/ui.js +8997 -0
- package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
- package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
- 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))
|