codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,487 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"""Backward-compatible static asset exports."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
import shutil
|
|
7
|
-
import time
|
|
8
|
-
from contextlib import ExitStack
|
|
9
|
-
from importlib import resources
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Iterable, Optional
|
|
12
|
-
from uuid import uuid4
|
|
3
|
+
import sys
|
|
13
4
|
|
|
14
|
-
from ..
|
|
5
|
+
from ..surfaces.web import static_assets as _static_assets
|
|
15
6
|
|
|
16
|
-
|
|
17
|
-
_REQUIRED_STATIC_ASSETS = (
|
|
18
|
-
"index.html",
|
|
19
|
-
"styles.css",
|
|
20
|
-
"bootstrap.js",
|
|
21
|
-
"loader.js",
|
|
22
|
-
"app.js",
|
|
23
|
-
"github.js",
|
|
24
|
-
"vendor/xterm.js",
|
|
25
|
-
"vendor/xterm-addon-fit.js",
|
|
26
|
-
"vendor/xterm.css",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def missing_static_assets(static_dir: Path) -> list[str]:
|
|
31
|
-
missing: list[str] = []
|
|
32
|
-
for rel_path in _REQUIRED_STATIC_ASSETS:
|
|
33
|
-
try:
|
|
34
|
-
if not (static_dir / rel_path).exists():
|
|
35
|
-
missing.append(rel_path)
|
|
36
|
-
except OSError:
|
|
37
|
-
missing.append(rel_path)
|
|
38
|
-
return missing
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _iter_static_source_files(source_dir: Path) -> Iterable[Path]:
|
|
42
|
-
try:
|
|
43
|
-
for path in source_dir.rglob("*.ts"):
|
|
44
|
-
try:
|
|
45
|
-
if path.name.endswith(".d.ts"):
|
|
46
|
-
continue
|
|
47
|
-
if path.is_dir():
|
|
48
|
-
continue
|
|
49
|
-
yield path
|
|
50
|
-
except OSError:
|
|
51
|
-
continue
|
|
52
|
-
except Exception:
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _stale_static_sources(source_dir: Path, static_dir: Path) -> list[str]:
|
|
57
|
-
stale: list[str] = []
|
|
58
|
-
for source in _iter_static_source_files(source_dir):
|
|
59
|
-
try:
|
|
60
|
-
rel_path = source.relative_to(source_dir)
|
|
61
|
-
except ValueError:
|
|
62
|
-
rel_path = Path(source.name)
|
|
63
|
-
target = (static_dir / rel_path).with_suffix(".js")
|
|
64
|
-
try:
|
|
65
|
-
source_mtime = source.stat().st_mtime
|
|
66
|
-
except OSError:
|
|
67
|
-
continue
|
|
68
|
-
try:
|
|
69
|
-
target_mtime = target.stat().st_mtime
|
|
70
|
-
except OSError:
|
|
71
|
-
stale.append(rel_path.as_posix())
|
|
72
|
-
continue
|
|
73
|
-
if source_mtime > target_mtime:
|
|
74
|
-
stale.append(rel_path.as_posix())
|
|
75
|
-
return stale
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def warn_on_stale_static_assets(static_dir: Path, logger: logging.Logger) -> None:
|
|
79
|
-
source_dir = Path(__file__).resolve().parent.parent / "static_src"
|
|
80
|
-
if not source_dir.exists():
|
|
81
|
-
return
|
|
82
|
-
stale = _stale_static_sources(source_dir, static_dir)
|
|
83
|
-
if not stale:
|
|
84
|
-
return
|
|
85
|
-
preview = ", ".join(stale[:5])
|
|
86
|
-
suffix = f" (+{len(stale) - 5} more)" if len(stale) > 5 else ""
|
|
87
|
-
safe_log(
|
|
88
|
-
logger,
|
|
89
|
-
logging.WARNING,
|
|
90
|
-
"Static assets appear stale; run `pnpm run build`. Newer sources: %s%s",
|
|
91
|
-
preview,
|
|
92
|
-
suffix,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _iter_asset_files(static_dir: Path) -> Iterable[Path]:
|
|
97
|
-
try:
|
|
98
|
-
for path in static_dir.rglob("*"):
|
|
99
|
-
try:
|
|
100
|
-
if path.is_dir():
|
|
101
|
-
continue
|
|
102
|
-
yield path
|
|
103
|
-
except OSError:
|
|
104
|
-
continue
|
|
105
|
-
except Exception:
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _hash_file(path: Path, digest: "hashlib._Hash") -> None:
|
|
110
|
-
try:
|
|
111
|
-
if path.is_symlink():
|
|
112
|
-
digest.update(b"SYMLINK:")
|
|
113
|
-
try:
|
|
114
|
-
target = path.readlink()
|
|
115
|
-
except OSError:
|
|
116
|
-
target = Path("dangling")
|
|
117
|
-
digest.update(str(target).encode("utf-8", errors="replace"))
|
|
118
|
-
return
|
|
119
|
-
with path.open("rb") as handle:
|
|
120
|
-
while True:
|
|
121
|
-
chunk = handle.read(1024 * 1024)
|
|
122
|
-
if not chunk:
|
|
123
|
-
break
|
|
124
|
-
digest.update(chunk)
|
|
125
|
-
except OSError:
|
|
126
|
-
digest.update(b"UNREADABLE")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def asset_version(static_dir: Path) -> str:
|
|
130
|
-
digest = hashlib.sha256()
|
|
131
|
-
files = sorted(_iter_asset_files(static_dir), key=lambda p: p.as_posix())
|
|
132
|
-
if not files:
|
|
133
|
-
return "0"
|
|
134
|
-
for path in files:
|
|
135
|
-
try:
|
|
136
|
-
rel_path = path.relative_to(static_dir)
|
|
137
|
-
except ValueError:
|
|
138
|
-
rel_path = path
|
|
139
|
-
digest.update(rel_path.as_posix().encode("utf-8", errors="replace"))
|
|
140
|
-
_hash_file(path, digest)
|
|
141
|
-
return digest.hexdigest()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def render_index_html(static_dir: Path, version: Optional[str]) -> str:
|
|
145
|
-
index_path = static_dir / "index.html"
|
|
146
|
-
text = index_path.read_text(encoding="utf-8")
|
|
147
|
-
if version:
|
|
148
|
-
text = text.replace(_ASSET_VERSION_TOKEN, version)
|
|
149
|
-
return text
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def security_headers() -> dict[str, str]:
|
|
153
|
-
# CSP: scripts are all local with no inline JS; runtime UI uses inline styles.
|
|
154
|
-
return {
|
|
155
|
-
"Content-Security-Policy": (
|
|
156
|
-
"default-src 'self'; "
|
|
157
|
-
"script-src 'self'; "
|
|
158
|
-
"style-src 'self' 'unsafe-inline'; "
|
|
159
|
-
"img-src 'self' data:; "
|
|
160
|
-
"font-src 'self' data:; "
|
|
161
|
-
"connect-src 'self' ws: wss:; "
|
|
162
|
-
"frame-ancestors 'none'"
|
|
163
|
-
),
|
|
164
|
-
"Referrer-Policy": "same-origin",
|
|
165
|
-
"X-Content-Type-Options": "nosniff",
|
|
166
|
-
"X-Frame-Options": "DENY",
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def index_response_headers() -> dict[str, str]:
|
|
171
|
-
headers = {
|
|
172
|
-
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
|
173
|
-
"Pragma": "no-cache",
|
|
174
|
-
"Expires": "0",
|
|
175
|
-
}
|
|
176
|
-
headers.update(security_headers())
|
|
177
|
-
return headers
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def resolve_static_dir() -> tuple[Path, Optional[ExitStack]]:
|
|
181
|
-
static_root = resources.files("codex_autorunner").joinpath("static")
|
|
182
|
-
if isinstance(static_root, Path):
|
|
183
|
-
if static_root.exists():
|
|
184
|
-
return static_root, None
|
|
185
|
-
fallback = Path(__file__).resolve().parent.parent / "static"
|
|
186
|
-
return fallback, None
|
|
187
|
-
stack = ExitStack()
|
|
188
|
-
try:
|
|
189
|
-
static_path = stack.enter_context(resources.as_file(static_root))
|
|
190
|
-
except Exception:
|
|
191
|
-
stack.close()
|
|
192
|
-
fallback = Path(__file__).resolve().parent.parent / "static"
|
|
193
|
-
return fallback, None
|
|
194
|
-
if static_path.exists():
|
|
195
|
-
return static_path, stack
|
|
196
|
-
stack.close()
|
|
197
|
-
fallback = Path(__file__).resolve().parent.parent / "static"
|
|
198
|
-
return fallback, None
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _cleanup_temp_dir(path: Path, logger: logging.Logger) -> None:
|
|
202
|
-
try:
|
|
203
|
-
shutil.rmtree(path)
|
|
204
|
-
except FileNotFoundError:
|
|
205
|
-
return
|
|
206
|
-
except Exception as exc:
|
|
207
|
-
safe_log(
|
|
208
|
-
logger,
|
|
209
|
-
logging.WARNING,
|
|
210
|
-
"Failed to remove temporary static cache dir %s",
|
|
211
|
-
path,
|
|
212
|
-
exc=exc,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def _acquire_cache_lock(lock_path: Path, logger: logging.Logger) -> bool:
|
|
217
|
-
try:
|
|
218
|
-
fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
219
|
-
except FileExistsError:
|
|
220
|
-
return False
|
|
221
|
-
except Exception as exc:
|
|
222
|
-
safe_log(
|
|
223
|
-
logger,
|
|
224
|
-
logging.WARNING,
|
|
225
|
-
"Failed to create static cache lock %s",
|
|
226
|
-
lock_path,
|
|
227
|
-
exc=exc,
|
|
228
|
-
)
|
|
229
|
-
return False
|
|
230
|
-
try:
|
|
231
|
-
os.write(fd, str(os.getpid()).encode("utf-8"))
|
|
232
|
-
except Exception:
|
|
233
|
-
pass
|
|
234
|
-
finally:
|
|
235
|
-
try:
|
|
236
|
-
os.close(fd)
|
|
237
|
-
except Exception:
|
|
238
|
-
pass
|
|
239
|
-
return True
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _release_cache_lock(lock_path: Path, logger: logging.Logger) -> None:
|
|
243
|
-
try:
|
|
244
|
-
lock_path.unlink()
|
|
245
|
-
except FileNotFoundError:
|
|
246
|
-
return
|
|
247
|
-
except Exception as exc:
|
|
248
|
-
safe_log(
|
|
249
|
-
logger,
|
|
250
|
-
logging.WARNING,
|
|
251
|
-
"Failed to remove static cache lock %s",
|
|
252
|
-
lock_path,
|
|
253
|
-
exc=exc,
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _cache_dir_mtime(path: Path) -> float:
|
|
258
|
-
index_path = path / "index.html"
|
|
259
|
-
try:
|
|
260
|
-
return index_path.stat().st_mtime
|
|
261
|
-
except OSError:
|
|
262
|
-
try:
|
|
263
|
-
return path.stat().st_mtime
|
|
264
|
-
except OSError:
|
|
265
|
-
return 0.0
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def _list_cache_entries(cache_root: Path) -> list[Path]:
|
|
269
|
-
if not cache_root.exists():
|
|
270
|
-
return []
|
|
271
|
-
entries: list[Path] = []
|
|
272
|
-
try:
|
|
273
|
-
for entry in cache_root.iterdir():
|
|
274
|
-
if entry.name.startswith("."):
|
|
275
|
-
continue
|
|
276
|
-
try:
|
|
277
|
-
if entry.is_dir():
|
|
278
|
-
entries.append(entry)
|
|
279
|
-
except OSError:
|
|
280
|
-
continue
|
|
281
|
-
except OSError:
|
|
282
|
-
return []
|
|
283
|
-
return entries
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def _select_latest_valid_cache(cache_root: Path) -> Optional[Path]:
|
|
287
|
-
candidates = []
|
|
288
|
-
for entry in _list_cache_entries(cache_root):
|
|
289
|
-
if not missing_static_assets(entry):
|
|
290
|
-
candidates.append(entry)
|
|
291
|
-
if not candidates:
|
|
292
|
-
return None
|
|
293
|
-
candidates.sort(key=_cache_dir_mtime, reverse=True)
|
|
294
|
-
return candidates[0]
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def _prune_cache_entries(
|
|
298
|
-
cache_root: Path,
|
|
299
|
-
*,
|
|
300
|
-
keep: set[Path],
|
|
301
|
-
max_cache_entries: int,
|
|
302
|
-
max_cache_age_days: Optional[int],
|
|
303
|
-
logger: logging.Logger,
|
|
304
|
-
) -> None:
|
|
305
|
-
if max_cache_entries <= 0 and max_cache_age_days is None:
|
|
306
|
-
return
|
|
307
|
-
entries = _list_cache_entries(cache_root)
|
|
308
|
-
if not entries:
|
|
309
|
-
return
|
|
310
|
-
now = time.time()
|
|
311
|
-
if max_cache_age_days is not None:
|
|
312
|
-
cutoff = now - (max_cache_age_days * 86400)
|
|
313
|
-
for entry in list(entries):
|
|
314
|
-
if entry in keep:
|
|
315
|
-
continue
|
|
316
|
-
if _cache_dir_mtime(entry) < cutoff:
|
|
317
|
-
try:
|
|
318
|
-
shutil.rmtree(entry)
|
|
319
|
-
entries.remove(entry)
|
|
320
|
-
except Exception as exc:
|
|
321
|
-
safe_log(
|
|
322
|
-
logger,
|
|
323
|
-
logging.WARNING,
|
|
324
|
-
"Failed to remove stale static cache dir %s",
|
|
325
|
-
entry,
|
|
326
|
-
exc=exc,
|
|
327
|
-
)
|
|
328
|
-
if max_cache_entries > 0 and len(entries) > max_cache_entries:
|
|
329
|
-
removable = [entry for entry in entries if entry not in keep]
|
|
330
|
-
removable.sort(key=_cache_dir_mtime)
|
|
331
|
-
for entry in removable[: len(entries) - max_cache_entries]:
|
|
332
|
-
try:
|
|
333
|
-
shutil.rmtree(entry)
|
|
334
|
-
except Exception as exc:
|
|
335
|
-
safe_log(
|
|
336
|
-
logger,
|
|
337
|
-
logging.WARNING,
|
|
338
|
-
"Failed to remove old static cache dir %s",
|
|
339
|
-
entry,
|
|
340
|
-
exc=exc,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def materialize_static_assets(
|
|
345
|
-
cache_root: Path,
|
|
346
|
-
*,
|
|
347
|
-
max_cache_entries: int,
|
|
348
|
-
max_cache_age_days: Optional[int],
|
|
349
|
-
logger: logging.Logger,
|
|
350
|
-
) -> tuple[Path, Optional[ExitStack]]:
|
|
351
|
-
static_dir, static_context = resolve_static_dir()
|
|
352
|
-
existing_cache = _select_latest_valid_cache(cache_root)
|
|
353
|
-
missing_source = missing_static_assets(static_dir)
|
|
354
|
-
if missing_source:
|
|
355
|
-
if static_context is not None:
|
|
356
|
-
static_context.close()
|
|
357
|
-
if existing_cache is not None:
|
|
358
|
-
_prune_cache_entries(
|
|
359
|
-
cache_root,
|
|
360
|
-
keep={existing_cache},
|
|
361
|
-
max_cache_entries=max_cache_entries,
|
|
362
|
-
max_cache_age_days=max_cache_age_days,
|
|
363
|
-
logger=logger,
|
|
364
|
-
)
|
|
365
|
-
return existing_cache, None
|
|
366
|
-
raise RuntimeError("Static UI assets missing; reinstall package")
|
|
367
|
-
fingerprint = asset_version(static_dir)
|
|
368
|
-
target_dir = cache_root / fingerprint
|
|
369
|
-
if target_dir.exists() and not missing_static_assets(target_dir):
|
|
370
|
-
if static_context is not None:
|
|
371
|
-
static_context.close()
|
|
372
|
-
_prune_cache_entries(
|
|
373
|
-
cache_root,
|
|
374
|
-
keep={target_dir},
|
|
375
|
-
max_cache_entries=max_cache_entries,
|
|
376
|
-
max_cache_age_days=max_cache_age_days,
|
|
377
|
-
logger=logger,
|
|
378
|
-
)
|
|
379
|
-
return target_dir, None
|
|
380
|
-
try:
|
|
381
|
-
cache_root.mkdir(parents=True, exist_ok=True)
|
|
382
|
-
except Exception as exc:
|
|
383
|
-
safe_log(
|
|
384
|
-
logger,
|
|
385
|
-
logging.WARNING,
|
|
386
|
-
"Failed to create static cache root %s",
|
|
387
|
-
cache_root,
|
|
388
|
-
exc=exc,
|
|
389
|
-
)
|
|
390
|
-
if static_context is not None:
|
|
391
|
-
static_context.close()
|
|
392
|
-
if existing_cache is not None:
|
|
393
|
-
return existing_cache, None
|
|
394
|
-
raise RuntimeError("Static UI assets missing; reinstall package") from exc
|
|
395
|
-
lock_path = cache_root / f".lock-{fingerprint}"
|
|
396
|
-
lock_acquired = _acquire_cache_lock(lock_path, logger)
|
|
397
|
-
if not lock_acquired:
|
|
398
|
-
deadline = time.monotonic() + 5.0
|
|
399
|
-
while time.monotonic() < deadline:
|
|
400
|
-
if target_dir.exists() and not missing_static_assets(target_dir):
|
|
401
|
-
if static_context is not None:
|
|
402
|
-
static_context.close()
|
|
403
|
-
_prune_cache_entries(
|
|
404
|
-
cache_root,
|
|
405
|
-
keep={target_dir},
|
|
406
|
-
max_cache_entries=max_cache_entries,
|
|
407
|
-
max_cache_age_days=max_cache_age_days,
|
|
408
|
-
logger=logger,
|
|
409
|
-
)
|
|
410
|
-
return target_dir, None
|
|
411
|
-
time.sleep(0.2)
|
|
412
|
-
temp_dir = cache_root / f".tmp-{fingerprint}-{uuid4().hex}"
|
|
413
|
-
try:
|
|
414
|
-
shutil.copytree(static_dir, temp_dir, symlinks=True)
|
|
415
|
-
missing = missing_static_assets(temp_dir)
|
|
416
|
-
if missing:
|
|
417
|
-
safe_log(
|
|
418
|
-
logger,
|
|
419
|
-
logging.WARNING,
|
|
420
|
-
"Static UI assets missing in cache copy %s: %s",
|
|
421
|
-
temp_dir,
|
|
422
|
-
", ".join(missing),
|
|
423
|
-
)
|
|
424
|
-
raise RuntimeError("Static UI assets missing; reinstall package")
|
|
425
|
-
if target_dir.exists():
|
|
426
|
-
existing_missing = missing_static_assets(target_dir)
|
|
427
|
-
if not existing_missing:
|
|
428
|
-
_cleanup_temp_dir(temp_dir, logger)
|
|
429
|
-
if static_context is not None:
|
|
430
|
-
static_context.close()
|
|
431
|
-
_prune_cache_entries(
|
|
432
|
-
cache_root,
|
|
433
|
-
keep={target_dir},
|
|
434
|
-
max_cache_entries=max_cache_entries,
|
|
435
|
-
max_cache_age_days=max_cache_age_days,
|
|
436
|
-
logger=logger,
|
|
437
|
-
)
|
|
438
|
-
return target_dir, None
|
|
439
|
-
try:
|
|
440
|
-
shutil.rmtree(target_dir)
|
|
441
|
-
except Exception as exc:
|
|
442
|
-
safe_log(
|
|
443
|
-
logger,
|
|
444
|
-
logging.WARNING,
|
|
445
|
-
"Failed to replace stale static cache dir %s",
|
|
446
|
-
target_dir,
|
|
447
|
-
exc=exc,
|
|
448
|
-
)
|
|
449
|
-
raise RuntimeError(
|
|
450
|
-
"Static UI assets missing; reinstall package"
|
|
451
|
-
) from exc
|
|
452
|
-
temp_dir.replace(target_dir)
|
|
453
|
-
except Exception as exc:
|
|
454
|
-
_cleanup_temp_dir(temp_dir, logger)
|
|
455
|
-
if static_context is not None:
|
|
456
|
-
static_context.close()
|
|
457
|
-
if existing_cache is not None:
|
|
458
|
-
return existing_cache, None
|
|
459
|
-
raise RuntimeError("Static UI assets missing; reinstall package") from exc
|
|
460
|
-
finally:
|
|
461
|
-
if lock_acquired:
|
|
462
|
-
_release_cache_lock(lock_path, logger)
|
|
463
|
-
if static_context is not None:
|
|
464
|
-
static_context.close()
|
|
465
|
-
_prune_cache_entries(
|
|
466
|
-
cache_root,
|
|
467
|
-
keep={target_dir},
|
|
468
|
-
max_cache_entries=max_cache_entries,
|
|
469
|
-
max_cache_age_days=max_cache_age_days,
|
|
470
|
-
logger=logger,
|
|
471
|
-
)
|
|
472
|
-
return target_dir, None
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
def require_static_assets(static_dir: Path, logger: logging.Logger) -> None:
|
|
476
|
-
missing = missing_static_assets(static_dir)
|
|
477
|
-
if not missing:
|
|
478
|
-
warn_on_stale_static_assets(static_dir, logger)
|
|
479
|
-
return
|
|
480
|
-
safe_log(
|
|
481
|
-
logger,
|
|
482
|
-
logging.ERROR,
|
|
483
|
-
"Static UI assets missing in %s: %s",
|
|
484
|
-
static_dir,
|
|
485
|
-
", ".join(missing),
|
|
486
|
-
)
|
|
487
|
-
raise RuntimeError("Static UI assets missing; reinstall package")
|
|
7
|
+
sys.modules[__name__] = _static_assets
|
|
@@ -1,86 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
"""Backward-compatible static refresh exports."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
from ..core.logging_utils import safe_log
|
|
8
|
-
from .static_assets import (
|
|
9
|
-
asset_version,
|
|
10
|
-
materialize_static_assets,
|
|
11
|
-
missing_static_assets,
|
|
12
|
-
require_static_assets,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _update_static_files(static_files: object, static_dir: Path) -> None:
|
|
17
|
-
try:
|
|
18
|
-
static_files.directory = static_dir
|
|
19
|
-
static_files.all_directories = static_files.get_directories( # type: ignore[attr-defined]
|
|
20
|
-
static_dir,
|
|
21
|
-
static_files.packages, # type: ignore[attr-defined]
|
|
22
|
-
)
|
|
23
|
-
static_files.config_checked = False
|
|
24
|
-
except Exception:
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def refresh_static_assets(app: object) -> bool:
|
|
29
|
-
lock = getattr(getattr(app, "state", None), "static_assets_lock", None)
|
|
30
|
-
if lock is None or not lock.acquire(blocking=False):
|
|
31
|
-
return False
|
|
32
|
-
try:
|
|
33
|
-
state = getattr(app, "state", None)
|
|
34
|
-
if state is None:
|
|
35
|
-
return False
|
|
36
|
-
current_dir = getattr(state, "static_dir", None)
|
|
37
|
-
if isinstance(current_dir, Path) and not missing_static_assets(current_dir):
|
|
38
|
-
return True
|
|
39
|
-
config = getattr(state, "config", None)
|
|
40
|
-
logger = getattr(state, "logger", None)
|
|
41
|
-
static_candidates = []
|
|
42
|
-
if config is not None:
|
|
43
|
-
static_candidates.append(config.static_assets)
|
|
44
|
-
hub_static = getattr(state, "hub_static_assets", None)
|
|
45
|
-
if hub_static is not None and (
|
|
46
|
-
not static_candidates
|
|
47
|
-
or hub_static.cache_root != static_candidates[0].cache_root
|
|
48
|
-
):
|
|
49
|
-
static_candidates.append(hub_static)
|
|
50
|
-
for static_cfg in static_candidates:
|
|
51
|
-
try:
|
|
52
|
-
static_dir, static_context = materialize_static_assets(
|
|
53
|
-
static_cfg.cache_root,
|
|
54
|
-
max_cache_entries=static_cfg.max_cache_entries,
|
|
55
|
-
max_cache_age_days=static_cfg.max_cache_age_days,
|
|
56
|
-
logger=logger,
|
|
57
|
-
)
|
|
58
|
-
require_static_assets(static_dir, logger)
|
|
59
|
-
except Exception as exc:
|
|
60
|
-
if logger is not None:
|
|
61
|
-
safe_log(
|
|
62
|
-
logger,
|
|
63
|
-
logging.WARNING,
|
|
64
|
-
"Static assets refresh failed for cache root %s",
|
|
65
|
-
static_cfg.cache_root,
|
|
66
|
-
exc=exc,
|
|
67
|
-
)
|
|
68
|
-
continue
|
|
69
|
-
old_context: Optional[object] = getattr(
|
|
70
|
-
state, "static_assets_context", None
|
|
71
|
-
)
|
|
72
|
-
if old_context is not None:
|
|
73
|
-
try:
|
|
74
|
-
old_context.close()
|
|
75
|
-
except Exception:
|
|
76
|
-
pass
|
|
77
|
-
state.static_dir = static_dir
|
|
78
|
-
state.static_assets_context = static_context
|
|
79
|
-
state.asset_version = asset_version(static_dir)
|
|
80
|
-
static_files = getattr(state, "static_files", None)
|
|
81
|
-
if static_files is not None:
|
|
82
|
-
_update_static_files(static_files, static_dir)
|
|
83
|
-
return True
|
|
84
|
-
return False
|
|
85
|
-
finally:
|
|
86
|
-
lock.release()
|
|
3
|
+
from ..surfaces.web.static_refresh import * # noqa: F401,F403
|
|
@@ -1,78 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
"""Backward-compatible terminal session exports."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from datetime import datetime, timezone
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
from ..core.state import persist_session_registry
|
|
9
|
-
from .pty_session import ActiveSession
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def parse_last_seen_at(value: Optional[str]) -> Optional[float]:
|
|
13
|
-
if not value:
|
|
14
|
-
return None
|
|
15
|
-
try:
|
|
16
|
-
parsed = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
|
|
17
|
-
except ValueError:
|
|
18
|
-
return None
|
|
19
|
-
return parsed.replace(tzinfo=timezone.utc).timestamp()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def session_last_touch(session: ActiveSession, record) -> float:
|
|
23
|
-
last_seen = parse_last_seen_at(getattr(record, "last_seen_at", None))
|
|
24
|
-
if last_seen is None:
|
|
25
|
-
return session.pty.last_active
|
|
26
|
-
return max(last_seen, session.pty.last_active)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def parse_tui_idle_seconds(config) -> Optional[float]:
|
|
30
|
-
notifications_cfg = (
|
|
31
|
-
config.notifications if isinstance(config.notifications, dict) else {}
|
|
32
|
-
)
|
|
33
|
-
idle_seconds = notifications_cfg.get("tui_idle_seconds")
|
|
34
|
-
if idle_seconds is None:
|
|
35
|
-
return None
|
|
36
|
-
try:
|
|
37
|
-
idle_seconds = float(idle_seconds)
|
|
38
|
-
except (TypeError, ValueError):
|
|
39
|
-
return None
|
|
40
|
-
if idle_seconds <= 0:
|
|
41
|
-
return None
|
|
42
|
-
return idle_seconds
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def prune_terminal_registry(
|
|
46
|
-
state_path: Path,
|
|
47
|
-
terminal_sessions: dict[str, ActiveSession],
|
|
48
|
-
session_registry: dict,
|
|
49
|
-
repo_to_session: dict[str, str],
|
|
50
|
-
max_idle_seconds: Optional[int],
|
|
51
|
-
) -> bool:
|
|
52
|
-
now = time.time()
|
|
53
|
-
removed_any = False
|
|
54
|
-
for session_id, session in list(terminal_sessions.items()):
|
|
55
|
-
if not session.pty.isalive():
|
|
56
|
-
session.close()
|
|
57
|
-
terminal_sessions.pop(session_id, None)
|
|
58
|
-
session_registry.pop(session_id, None)
|
|
59
|
-
removed_any = True
|
|
60
|
-
continue
|
|
61
|
-
if max_idle_seconds is not None and max_idle_seconds > 0:
|
|
62
|
-
last_touch = session_last_touch(session, session_registry.get(session_id))
|
|
63
|
-
if now - last_touch > max_idle_seconds:
|
|
64
|
-
session.close()
|
|
65
|
-
terminal_sessions.pop(session_id, None)
|
|
66
|
-
session_registry.pop(session_id, None)
|
|
67
|
-
removed_any = True
|
|
68
|
-
for session_id in list(session_registry.keys()):
|
|
69
|
-
if session_id not in terminal_sessions:
|
|
70
|
-
session_registry.pop(session_id, None)
|
|
71
|
-
removed_any = True
|
|
72
|
-
for repo_path, session_id in list(repo_to_session.items()):
|
|
73
|
-
if session_id not in session_registry:
|
|
74
|
-
repo_to_session.pop(repo_path, None)
|
|
75
|
-
removed_any = True
|
|
76
|
-
if removed_any:
|
|
77
|
-
persist_session_registry(state_path, session_registry, repo_to_session)
|
|
78
|
-
return removed_any
|
|
3
|
+
from ..surfaces.web.terminal_sessions import * # noqa: F401,F403
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Workspace docs helpers (active context, decisions, spec).
|
|
2
|
+
|
|
3
|
+
Workspace docs are optional and live under `.codex-autorunner/workspace/`.
|
|
4
|
+
They are distinct from tickets, which live under `.codex-autorunner/tickets/`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..core.utils import canonicalize_path
|
|
11
|
+
from .paths import (
|
|
12
|
+
WORKSPACE_DOC_KINDS,
|
|
13
|
+
WorkspaceDocKind,
|
|
14
|
+
read_workspace_doc,
|
|
15
|
+
workspace_doc_path,
|
|
16
|
+
write_workspace_doc,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
WORKSPACE_ID_HEX_LEN = 12
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def canonical_workspace_root(path: Path) -> Path:
|
|
23
|
+
return canonicalize_path(path)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def workspace_id_for_path(path: Path) -> str:
|
|
27
|
+
canonical = canonical_workspace_root(path)
|
|
28
|
+
digest = hashlib.sha256(str(canonical).encode("utf-8")).hexdigest()
|
|
29
|
+
return digest[:WORKSPACE_ID_HEX_LEN]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"WORKSPACE_DOC_KINDS",
|
|
34
|
+
"WorkspaceDocKind",
|
|
35
|
+
"workspace_doc_path",
|
|
36
|
+
"read_workspace_doc",
|
|
37
|
+
"write_workspace_doc",
|
|
38
|
+
"canonical_workspace_root",
|
|
39
|
+
"workspace_id_for_path",
|
|
40
|
+
]
|