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,169 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Session settings routes for autorunner overrides.
|
|
3
|
-
"""
|
|
1
|
+
"""Backward-compatible settings routes."""
|
|
4
2
|
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, HTTPException, Request
|
|
8
|
-
|
|
9
|
-
from ..core.state import RunnerState, load_state, save_state, state_lock
|
|
10
|
-
from ..web.schemas import SessionSettingsRequest, SessionSettingsResponse
|
|
11
|
-
|
|
12
|
-
ALLOWED_APPROVAL_POLICIES = {"never", "unlessTrusted"}
|
|
13
|
-
ALLOWED_SANDBOX_MODES = {"dangerFullAccess", "workspaceWrite"}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _normalize_optional_string(value: object, field: str) -> Optional[str]:
|
|
17
|
-
if value is None:
|
|
18
|
-
return None
|
|
19
|
-
if not isinstance(value, str):
|
|
20
|
-
raise HTTPException(status_code=400, detail=f"{field} must be a string")
|
|
21
|
-
cleaned = value.strip()
|
|
22
|
-
return cleaned or None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def build_settings_routes() -> APIRouter:
|
|
26
|
-
router = APIRouter()
|
|
27
|
-
|
|
28
|
-
@router.get("/api/session/settings", response_model=SessionSettingsResponse)
|
|
29
|
-
def get_session_settings(request: Request):
|
|
30
|
-
state = load_state(request.app.state.engine.state_path)
|
|
31
|
-
return {
|
|
32
|
-
"autorunner_model_override": state.autorunner_model_override,
|
|
33
|
-
"autorunner_effort_override": state.autorunner_effort_override,
|
|
34
|
-
"autorunner_approval_policy": state.autorunner_approval_policy,
|
|
35
|
-
"autorunner_sandbox_mode": state.autorunner_sandbox_mode,
|
|
36
|
-
"autorunner_workspace_write_network": state.autorunner_workspace_write_network,
|
|
37
|
-
"runner_stop_after_runs": state.runner_stop_after_runs,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@router.post("/api/session/settings", response_model=SessionSettingsResponse)
|
|
41
|
-
def update_session_settings(request: Request, payload: SessionSettingsRequest):
|
|
42
|
-
updates = payload.model_dump(exclude_unset=True)
|
|
43
|
-
engine = request.app.state.engine
|
|
44
|
-
manager = request.app.state.manager
|
|
45
|
-
registry = request.app.state.app_server_threads
|
|
46
|
-
with state_lock(engine.state_path):
|
|
47
|
-
state = load_state(engine.state_path)
|
|
48
|
-
model_override = (
|
|
49
|
-
_normalize_optional_string(
|
|
50
|
-
updates.get("autorunner_model_override"),
|
|
51
|
-
"autorunner_model_override",
|
|
52
|
-
)
|
|
53
|
-
if "autorunner_model_override" in updates
|
|
54
|
-
else state.autorunner_model_override
|
|
55
|
-
)
|
|
56
|
-
effort_override = (
|
|
57
|
-
_normalize_optional_string(
|
|
58
|
-
updates.get("autorunner_effort_override"),
|
|
59
|
-
"autorunner_effort_override",
|
|
60
|
-
)
|
|
61
|
-
if "autorunner_effort_override" in updates
|
|
62
|
-
else state.autorunner_effort_override
|
|
63
|
-
)
|
|
64
|
-
approval_policy = (
|
|
65
|
-
_normalize_optional_string(
|
|
66
|
-
updates.get("autorunner_approval_policy"),
|
|
67
|
-
"autorunner_approval_policy",
|
|
68
|
-
)
|
|
69
|
-
if "autorunner_approval_policy" in updates
|
|
70
|
-
else state.autorunner_approval_policy
|
|
71
|
-
)
|
|
72
|
-
if approval_policy and approval_policy not in ALLOWED_APPROVAL_POLICIES:
|
|
73
|
-
raise HTTPException(
|
|
74
|
-
status_code=400,
|
|
75
|
-
detail="approval policy must be never or unlessTrusted",
|
|
76
|
-
)
|
|
77
|
-
sandbox_mode = (
|
|
78
|
-
_normalize_optional_string(
|
|
79
|
-
updates.get("autorunner_sandbox_mode"),
|
|
80
|
-
"autorunner_sandbox_mode",
|
|
81
|
-
)
|
|
82
|
-
if "autorunner_sandbox_mode" in updates
|
|
83
|
-
else state.autorunner_sandbox_mode
|
|
84
|
-
)
|
|
85
|
-
if sandbox_mode and sandbox_mode not in ALLOWED_SANDBOX_MODES:
|
|
86
|
-
raise HTTPException(
|
|
87
|
-
status_code=400,
|
|
88
|
-
detail="sandbox mode must be dangerFullAccess or workspaceWrite",
|
|
89
|
-
)
|
|
90
|
-
workspace_write_network = (
|
|
91
|
-
updates.get("autorunner_workspace_write_network")
|
|
92
|
-
if "autorunner_workspace_write_network" in updates
|
|
93
|
-
else state.autorunner_workspace_write_network
|
|
94
|
-
)
|
|
95
|
-
if (
|
|
96
|
-
"autorunner_workspace_write_network" in updates
|
|
97
|
-
and workspace_write_network is not None
|
|
98
|
-
and not isinstance(workspace_write_network, bool)
|
|
99
|
-
):
|
|
100
|
-
raise HTTPException(
|
|
101
|
-
status_code=400,
|
|
102
|
-
detail="autorunner_workspace_write_network must be a boolean",
|
|
103
|
-
)
|
|
104
|
-
runner_stop_after_runs = (
|
|
105
|
-
updates.get("runner_stop_after_runs")
|
|
106
|
-
if "runner_stop_after_runs" in updates
|
|
107
|
-
else state.runner_stop_after_runs
|
|
108
|
-
)
|
|
109
|
-
if (
|
|
110
|
-
"runner_stop_after_runs" in updates
|
|
111
|
-
and runner_stop_after_runs is not None
|
|
112
|
-
and (
|
|
113
|
-
not isinstance(runner_stop_after_runs, int)
|
|
114
|
-
or isinstance(runner_stop_after_runs, bool)
|
|
115
|
-
or runner_stop_after_runs <= 0
|
|
116
|
-
)
|
|
117
|
-
):
|
|
118
|
-
raise HTTPException(
|
|
119
|
-
status_code=400,
|
|
120
|
-
detail="runner_stop_after_runs must be a positive integer",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
thread_reset_required = any(
|
|
124
|
-
(
|
|
125
|
-
model_override != state.autorunner_model_override,
|
|
126
|
-
effort_override != state.autorunner_effort_override,
|
|
127
|
-
approval_policy != state.autorunner_approval_policy,
|
|
128
|
-
sandbox_mode != state.autorunner_sandbox_mode,
|
|
129
|
-
workspace_write_network != state.autorunner_workspace_write_network,
|
|
130
|
-
runner_stop_after_runs != state.runner_stop_after_runs,
|
|
131
|
-
)
|
|
132
|
-
)
|
|
133
|
-
if thread_reset_required and manager.running:
|
|
134
|
-
raise HTTPException(
|
|
135
|
-
status_code=409,
|
|
136
|
-
detail="Cannot change autorunner settings while a run is active",
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
new_state = RunnerState(
|
|
140
|
-
last_run_id=state.last_run_id,
|
|
141
|
-
status=state.status,
|
|
142
|
-
last_exit_code=state.last_exit_code,
|
|
143
|
-
last_run_started_at=state.last_run_started_at,
|
|
144
|
-
last_run_finished_at=state.last_run_finished_at,
|
|
145
|
-
autorunner_agent_override=state.autorunner_agent_override,
|
|
146
|
-
autorunner_model_override=model_override,
|
|
147
|
-
autorunner_effort_override=effort_override,
|
|
148
|
-
autorunner_approval_policy=approval_policy,
|
|
149
|
-
autorunner_sandbox_mode=sandbox_mode,
|
|
150
|
-
autorunner_workspace_write_network=workspace_write_network,
|
|
151
|
-
runner_stop_after_runs=runner_stop_after_runs,
|
|
152
|
-
runner_pid=state.runner_pid,
|
|
153
|
-
sessions=state.sessions,
|
|
154
|
-
repo_to_session=state.repo_to_session,
|
|
155
|
-
)
|
|
156
|
-
save_state(engine.state_path, new_state)
|
|
157
|
-
if thread_reset_required:
|
|
158
|
-
registry.reset_thread("autorunner")
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
"autorunner_model_override": model_override,
|
|
162
|
-
"autorunner_effort_override": effort_override,
|
|
163
|
-
"autorunner_approval_policy": approval_policy,
|
|
164
|
-
"autorunner_sandbox_mode": sandbox_mode,
|
|
165
|
-
"autorunner_workspace_write_network": workspace_write_network,
|
|
166
|
-
"runner_stop_after_runs": runner_stop_after_runs,
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return router
|
|
3
|
+
from ..surfaces.web.routes.settings import * # noqa: F401,F403
|
|
@@ -1,276 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Shared utilities for route modules.
|
|
3
|
-
"""
|
|
1
|
+
"""Backward-compatible shared route utilities."""
|
|
4
2
|
|
|
5
|
-
import
|
|
6
|
-
import json
|
|
7
|
-
import time
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Optional
|
|
10
|
-
|
|
11
|
-
from ..codex_cli import apply_codex_options, extract_flag_value, supports_reasoning
|
|
12
|
-
from ..core.locks import (
|
|
13
|
-
DEFAULT_RUNNER_CMD_HINTS,
|
|
14
|
-
assess_lock,
|
|
15
|
-
process_is_active,
|
|
16
|
-
read_lock_info,
|
|
17
|
-
)
|
|
18
|
-
from ..core.state import load_state
|
|
19
|
-
from ..core.utils import resolve_opencode_binary
|
|
20
|
-
|
|
21
|
-
BYPASS_FLAGS = {
|
|
22
|
-
"--yolo",
|
|
23
|
-
"--dangerously-bypass-approvals-and-sandbox",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
SSE_HEADERS = {
|
|
27
|
-
"Cache-Control": "no-cache",
|
|
28
|
-
"X-Accel-Buffering": "no",
|
|
29
|
-
"Connection": "keep-alive",
|
|
30
|
-
"Content-Encoding": "identity",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
async def _interruptible_sleep(
|
|
35
|
-
seconds: float, shutdown_event: Optional[asyncio.Event]
|
|
36
|
-
) -> bool:
|
|
37
|
-
"""Sleep that can be interrupted by shutdown_event. Returns True if interrupted."""
|
|
38
|
-
if shutdown_event is None:
|
|
39
|
-
await asyncio.sleep(seconds)
|
|
40
|
-
return False
|
|
41
|
-
try:
|
|
42
|
-
await asyncio.wait_for(shutdown_event.wait(), timeout=seconds)
|
|
43
|
-
return True # Event was set
|
|
44
|
-
except asyncio.TimeoutError:
|
|
45
|
-
return False # Normal timeout, continue
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _extract_bypass_flag(args: list[str]) -> tuple[str, list[str]]:
|
|
49
|
-
chosen = None
|
|
50
|
-
for arg in args:
|
|
51
|
-
if arg in BYPASS_FLAGS:
|
|
52
|
-
chosen = arg
|
|
53
|
-
break
|
|
54
|
-
filtered = [arg for arg in args if arg not in BYPASS_FLAGS]
|
|
55
|
-
return chosen or "--yolo", filtered
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def build_codex_terminal_cmd(
|
|
59
|
-
engine,
|
|
60
|
-
*,
|
|
61
|
-
resume_mode: bool,
|
|
62
|
-
model: Optional[str] = None,
|
|
63
|
-
reasoning: Optional[str] = None,
|
|
64
|
-
) -> list[str]:
|
|
65
|
-
"""
|
|
66
|
-
Build the subprocess argv for launching the Codex interactive CLI inside a PTY.
|
|
67
|
-
"""
|
|
68
|
-
bypass_flag, terminal_args = _extract_bypass_flag(
|
|
69
|
-
list(engine.config.codex_terminal_args)
|
|
70
|
-
)
|
|
71
|
-
if resume_mode:
|
|
72
|
-
cmd = [
|
|
73
|
-
engine.config.codex_binary,
|
|
74
|
-
bypass_flag,
|
|
75
|
-
"resume",
|
|
76
|
-
*terminal_args,
|
|
77
|
-
]
|
|
78
|
-
return apply_codex_options(
|
|
79
|
-
cmd,
|
|
80
|
-
model=model,
|
|
81
|
-
reasoning=reasoning,
|
|
82
|
-
supports_reasoning=supports_reasoning(engine.config.codex_binary),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
cmd = [
|
|
86
|
-
engine.config.codex_binary,
|
|
87
|
-
bypass_flag,
|
|
88
|
-
*terminal_args,
|
|
89
|
-
]
|
|
90
|
-
return apply_codex_options(
|
|
91
|
-
cmd,
|
|
92
|
-
model=model,
|
|
93
|
-
reasoning=reasoning,
|
|
94
|
-
supports_reasoning=supports_reasoning(engine.config.codex_binary),
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def build_opencode_terminal_cmd(binary: str, model: Optional[str] = None) -> list[str]:
|
|
99
|
-
resolved = resolve_opencode_binary(binary)
|
|
100
|
-
cmd = [resolved or binary]
|
|
101
|
-
if model:
|
|
102
|
-
cmd.extend(["--model", model])
|
|
103
|
-
return cmd
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def resolve_runner_status(engine, state) -> tuple[str, Optional[int], bool]:
|
|
107
|
-
pid = state.runner_pid
|
|
108
|
-
alive_pid = pid if pid and process_is_active(pid) else None
|
|
109
|
-
if alive_pid is None:
|
|
110
|
-
info = read_lock_info(engine.lock_path)
|
|
111
|
-
if info.pid and process_is_active(info.pid):
|
|
112
|
-
alive_pid = info.pid
|
|
113
|
-
running = alive_pid is not None
|
|
114
|
-
status = state.status
|
|
115
|
-
if status == "running" and not running:
|
|
116
|
-
status = "idle"
|
|
117
|
-
runner_pid = alive_pid if running else None
|
|
118
|
-
return status, runner_pid, running
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def resolve_lock_payload(engine) -> dict[str, object]:
|
|
122
|
-
assessment = assess_lock(
|
|
123
|
-
engine.lock_path,
|
|
124
|
-
expected_cmd_substrings=DEFAULT_RUNNER_CMD_HINTS,
|
|
125
|
-
)
|
|
126
|
-
return {
|
|
127
|
-
"lock_present": engine.lock_path.exists(),
|
|
128
|
-
"lock_pid": assessment.pid,
|
|
129
|
-
"lock_freeable": assessment.freeable,
|
|
130
|
-
"lock_freeable_reason": assessment.reason,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
async def log_stream(
|
|
135
|
-
log_path: Path,
|
|
136
|
-
heartbeat_interval: float = 15.0,
|
|
137
|
-
shutdown_event: Optional[asyncio.Event] = None,
|
|
138
|
-
max_seconds: float = 60.0,
|
|
139
|
-
):
|
|
140
|
-
"""SSE stream generator for log file tailing."""
|
|
141
|
-
if not log_path.exists():
|
|
142
|
-
yield "data: log file not found\n\n"
|
|
143
|
-
return
|
|
144
|
-
last_emit_at = time.monotonic()
|
|
145
|
-
start_time = time.monotonic()
|
|
146
|
-
with log_path.open("r", encoding="utf-8") as f:
|
|
147
|
-
f.seek(0, 2)
|
|
148
|
-
while True:
|
|
149
|
-
if shutdown_event is not None and shutdown_event.is_set():
|
|
150
|
-
return
|
|
151
|
-
if time.monotonic() - start_time > max_seconds:
|
|
152
|
-
yield "event: timeout\ndata: Stream timeout exceeded\n\n"
|
|
153
|
-
return
|
|
154
|
-
line = f.readline()
|
|
155
|
-
if line:
|
|
156
|
-
yield f"data: {line.rstrip()}\n\n"
|
|
157
|
-
last_emit_at = time.monotonic()
|
|
158
|
-
else:
|
|
159
|
-
now = time.monotonic()
|
|
160
|
-
if now - last_emit_at >= heartbeat_interval:
|
|
161
|
-
yield ": ping\n\n"
|
|
162
|
-
last_emit_at = now
|
|
163
|
-
if await _interruptible_sleep(0.5, shutdown_event):
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
async def jsonl_event_stream(
|
|
168
|
-
path: Path,
|
|
169
|
-
*,
|
|
170
|
-
event_name: str = "message",
|
|
171
|
-
heartbeat_interval: float = 15.0,
|
|
172
|
-
shutdown_event: Optional[asyncio.Event] = None,
|
|
173
|
-
):
|
|
174
|
-
"""SSE stream generator for JSONL event files."""
|
|
175
|
-
last_emit_at = time.monotonic()
|
|
176
|
-
position = 0
|
|
177
|
-
while True:
|
|
178
|
-
if shutdown_event is not None and shutdown_event.is_set():
|
|
179
|
-
return
|
|
180
|
-
if not path.exists():
|
|
181
|
-
now = time.monotonic()
|
|
182
|
-
if now - last_emit_at >= heartbeat_interval:
|
|
183
|
-
yield ": ping\n\n"
|
|
184
|
-
last_emit_at = now
|
|
185
|
-
if await _interruptible_sleep(1.0, shutdown_event):
|
|
186
|
-
return
|
|
187
|
-
continue
|
|
188
|
-
try:
|
|
189
|
-
with path.open("r", encoding="utf-8") as handle:
|
|
190
|
-
handle.seek(position)
|
|
191
|
-
while True:
|
|
192
|
-
if shutdown_event is not None and shutdown_event.is_set():
|
|
193
|
-
return
|
|
194
|
-
line = handle.readline()
|
|
195
|
-
if line:
|
|
196
|
-
position = handle.tell()
|
|
197
|
-
payload = line.strip()
|
|
198
|
-
if payload:
|
|
199
|
-
yield f"event: {event_name}\ndata: {payload}\n\n"
|
|
200
|
-
last_emit_at = time.monotonic()
|
|
201
|
-
else:
|
|
202
|
-
now = time.monotonic()
|
|
203
|
-
if now - last_emit_at >= heartbeat_interval:
|
|
204
|
-
yield ": ping\n\n"
|
|
205
|
-
last_emit_at = now
|
|
206
|
-
if await _interruptible_sleep(0.5, shutdown_event):
|
|
207
|
-
return
|
|
208
|
-
except OSError:
|
|
209
|
-
if await _interruptible_sleep(1.0, shutdown_event):
|
|
210
|
-
return
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
async def state_stream(
|
|
214
|
-
engine,
|
|
215
|
-
manager,
|
|
216
|
-
logger=None,
|
|
217
|
-
heartbeat_interval: float = 15.0,
|
|
218
|
-
shutdown_event: Optional[asyncio.Event] = None,
|
|
219
|
-
max_seconds: float = 60.0,
|
|
220
|
-
):
|
|
221
|
-
"""SSE stream generator for state updates."""
|
|
222
|
-
last_payload = None
|
|
223
|
-
last_error_log_at = 0.0
|
|
224
|
-
last_emit_at = time.monotonic()
|
|
225
|
-
start_time = time.monotonic()
|
|
226
|
-
terminal_idle_timeout_seconds = engine.config.terminal_idle_timeout_seconds
|
|
227
|
-
codex_model = engine.config.codex_model or extract_flag_value(
|
|
228
|
-
engine.config.codex_args, "--model"
|
|
229
|
-
)
|
|
230
|
-
while True:
|
|
231
|
-
if shutdown_event is not None and shutdown_event.is_set():
|
|
232
|
-
return
|
|
233
|
-
if time.monotonic() - start_time > max_seconds:
|
|
234
|
-
yield "event: timeout\ndata: Stream timeout exceeded\n\n"
|
|
235
|
-
return
|
|
236
|
-
emitted = False
|
|
237
|
-
try:
|
|
238
|
-
state = await asyncio.to_thread(load_state, engine.state_path)
|
|
239
|
-
outstanding, done = await asyncio.to_thread(engine.docs.todos)
|
|
240
|
-
status, runner_pid, running = resolve_runner_status(engine, state)
|
|
241
|
-
lock_payload = resolve_lock_payload(engine)
|
|
242
|
-
payload = {
|
|
243
|
-
"last_run_id": state.last_run_id,
|
|
244
|
-
"status": status,
|
|
245
|
-
"last_exit_code": state.last_exit_code,
|
|
246
|
-
"last_run_started_at": state.last_run_started_at,
|
|
247
|
-
"last_run_finished_at": state.last_run_finished_at,
|
|
248
|
-
"outstanding_count": len(outstanding),
|
|
249
|
-
"done_count": len(done),
|
|
250
|
-
"running": running,
|
|
251
|
-
"runner_pid": runner_pid,
|
|
252
|
-
**lock_payload,
|
|
253
|
-
"terminal_idle_timeout_seconds": terminal_idle_timeout_seconds,
|
|
254
|
-
"codex_model": codex_model or "auto",
|
|
255
|
-
}
|
|
256
|
-
if payload != last_payload:
|
|
257
|
-
yield f"data: {json.dumps(payload)}\n\n"
|
|
258
|
-
last_payload = payload
|
|
259
|
-
last_emit_at = time.monotonic()
|
|
260
|
-
emitted = True
|
|
261
|
-
except Exception:
|
|
262
|
-
# Don't spam logs, but don't swallow silently either.
|
|
263
|
-
now = time.time()
|
|
264
|
-
if logger is not None and (now - last_error_log_at) > 60:
|
|
265
|
-
last_error_log_at = now
|
|
266
|
-
try:
|
|
267
|
-
logger.warning("state stream error", exc_info=True)
|
|
268
|
-
except Exception:
|
|
269
|
-
pass
|
|
270
|
-
if not emitted:
|
|
271
|
-
now = time.monotonic()
|
|
272
|
-
if now - last_emit_at >= heartbeat_interval:
|
|
273
|
-
yield ": ping\n\n"
|
|
274
|
-
last_emit_at = now
|
|
275
|
-
if await _interruptible_sleep(1.0, shutdown_event):
|
|
276
|
-
return
|
|
3
|
+
from ..surfaces.web.routes.shared import * # noqa: F401,F403
|
|
@@ -1,191 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import logging
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
1
|
+
"""Backward-compatible system routes."""
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
from fastapi.responses import JSONResponse
|
|
3
|
+
import sys
|
|
8
4
|
|
|
9
|
-
from ..
|
|
10
|
-
from ..core.config import HubConfig
|
|
11
|
-
from ..core.update import (
|
|
12
|
-
UpdateInProgressError,
|
|
13
|
-
_normalize_update_ref,
|
|
14
|
-
_normalize_update_target,
|
|
15
|
-
_read_update_status,
|
|
16
|
-
_spawn_update_process,
|
|
17
|
-
_system_update_check,
|
|
18
|
-
)
|
|
19
|
-
from ..web.schemas import (
|
|
20
|
-
SystemHealthResponse,
|
|
21
|
-
SystemUpdateCheckResponse,
|
|
22
|
-
SystemUpdateRequest,
|
|
23
|
-
SystemUpdateResponse,
|
|
24
|
-
SystemUpdateStatusResponse,
|
|
25
|
-
)
|
|
26
|
-
from ..web.static_assets import missing_static_assets
|
|
27
|
-
from ..web.static_refresh import refresh_static_assets
|
|
5
|
+
from ..surfaces.web.routes import system as _system
|
|
28
6
|
|
|
29
|
-
|
|
30
|
-
_system_update_worker = update_core._system_update_worker
|
|
31
|
-
_update_lock_active = update_core._update_lock_active
|
|
32
|
-
_update_lock_path = update_core._update_lock_path
|
|
33
|
-
_update_status_path = update_core._update_status_path
|
|
34
|
-
shutil = update_core.shutil
|
|
35
|
-
subprocess = update_core.subprocess
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def build_system_routes() -> APIRouter:
|
|
39
|
-
router = APIRouter()
|
|
40
|
-
|
|
41
|
-
@router.get("/health", response_model=SystemHealthResponse)
|
|
42
|
-
async def system_health(request: Request):
|
|
43
|
-
try:
|
|
44
|
-
config = request.app.state.config
|
|
45
|
-
except AttributeError:
|
|
46
|
-
config = None
|
|
47
|
-
mode = "hub" if isinstance(config, HubConfig) else "repo"
|
|
48
|
-
base_path = getattr(request.app.state, "base_path", "")
|
|
49
|
-
asset_version = getattr(request.app.state, "asset_version", None)
|
|
50
|
-
static_dir = getattr(getattr(request.app, "state", None), "static_dir", None)
|
|
51
|
-
if not isinstance(static_dir, Path):
|
|
52
|
-
return JSONResponse(
|
|
53
|
-
{
|
|
54
|
-
"status": "error",
|
|
55
|
-
"detail": "Static UI assets missing; reinstall package",
|
|
56
|
-
"mode": mode,
|
|
57
|
-
"base_path": base_path,
|
|
58
|
-
},
|
|
59
|
-
status_code=500,
|
|
60
|
-
)
|
|
61
|
-
missing = await asyncio.to_thread(missing_static_assets, static_dir)
|
|
62
|
-
if missing:
|
|
63
|
-
if refresh_static_assets(request.app):
|
|
64
|
-
static_dir = getattr(
|
|
65
|
-
getattr(request.app, "state", None), "static_dir", None
|
|
66
|
-
)
|
|
67
|
-
if isinstance(static_dir, Path):
|
|
68
|
-
missing = await asyncio.to_thread(missing_static_assets, static_dir)
|
|
69
|
-
else:
|
|
70
|
-
missing = ["index.html"]
|
|
71
|
-
if not missing:
|
|
72
|
-
return {
|
|
73
|
-
"status": "ok",
|
|
74
|
-
"mode": mode,
|
|
75
|
-
"base_path": base_path,
|
|
76
|
-
"asset_version": asset_version,
|
|
77
|
-
}
|
|
78
|
-
return JSONResponse(
|
|
79
|
-
{
|
|
80
|
-
"status": "error",
|
|
81
|
-
"detail": "Static UI assets missing; reinstall package",
|
|
82
|
-
"missing": missing,
|
|
83
|
-
"mode": mode,
|
|
84
|
-
"base_path": base_path,
|
|
85
|
-
},
|
|
86
|
-
status_code=500,
|
|
87
|
-
)
|
|
88
|
-
return {
|
|
89
|
-
"status": "ok",
|
|
90
|
-
"mode": mode,
|
|
91
|
-
"base_path": base_path,
|
|
92
|
-
"asset_version": asset_version,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
@router.get("/system/update/check", response_model=SystemUpdateCheckResponse)
|
|
96
|
-
async def system_update_check(request: Request):
|
|
97
|
-
"""
|
|
98
|
-
Check if an update is available by comparing local git state vs remote.
|
|
99
|
-
If local git state is unavailable, report that an update may be available.
|
|
100
|
-
"""
|
|
101
|
-
try:
|
|
102
|
-
config = request.app.state.config
|
|
103
|
-
except AttributeError:
|
|
104
|
-
config = None
|
|
105
|
-
|
|
106
|
-
repo_url = "https://github.com/Git-on-my-level/codex-autorunner.git"
|
|
107
|
-
repo_ref = "main"
|
|
108
|
-
if config and isinstance(config, HubConfig):
|
|
109
|
-
configured_url = getattr(config, "update_repo_url", None)
|
|
110
|
-
if configured_url:
|
|
111
|
-
repo_url = configured_url
|
|
112
|
-
configured_ref = getattr(config, "update_repo_ref", None)
|
|
113
|
-
if configured_ref:
|
|
114
|
-
repo_ref = configured_ref
|
|
115
|
-
|
|
116
|
-
try:
|
|
117
|
-
return await asyncio.to_thread(
|
|
118
|
-
_system_update_check, repo_url=repo_url, repo_ref=repo_ref
|
|
119
|
-
)
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger = getattr(getattr(request.app, "state", None), "logger", None)
|
|
122
|
-
if logger:
|
|
123
|
-
logger.error("Update check error: %s", e, exc_info=True)
|
|
124
|
-
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
125
|
-
|
|
126
|
-
@router.post("/system/update", response_model=SystemUpdateResponse)
|
|
127
|
-
async def system_update(
|
|
128
|
-
request: Request, payload: Optional[SystemUpdateRequest] = None
|
|
129
|
-
):
|
|
130
|
-
"""
|
|
131
|
-
Pull latest code and refresh the running service.
|
|
132
|
-
This will restart the server if successful.
|
|
133
|
-
"""
|
|
134
|
-
try:
|
|
135
|
-
config = request.app.state.config
|
|
136
|
-
except AttributeError:
|
|
137
|
-
config = None
|
|
138
|
-
|
|
139
|
-
# Determine URL
|
|
140
|
-
repo_url = "https://github.com/Git-on-my-level/codex-autorunner.git"
|
|
141
|
-
repo_ref = "main"
|
|
142
|
-
if config and isinstance(config, HubConfig):
|
|
143
|
-
configured_url = getattr(config, "update_repo_url", None)
|
|
144
|
-
if configured_url:
|
|
145
|
-
repo_url = configured_url
|
|
146
|
-
configured_ref = getattr(config, "update_repo_ref", None)
|
|
147
|
-
if configured_ref:
|
|
148
|
-
repo_ref = configured_ref
|
|
149
|
-
|
|
150
|
-
home_dot_car = Path.home() / ".codex-autorunner"
|
|
151
|
-
update_dir = home_dot_car / "update_cache"
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
target_raw = payload.target if payload else None
|
|
155
|
-
if target_raw is None:
|
|
156
|
-
target_raw = request.query_params.get("target")
|
|
157
|
-
update_target = _normalize_update_target(target_raw)
|
|
158
|
-
logger = getattr(getattr(request.app, "state", None), "logger", None)
|
|
159
|
-
if logger is None:
|
|
160
|
-
logger = logging.getLogger("codex_autorunner.system_update")
|
|
161
|
-
await asyncio.to_thread(
|
|
162
|
-
_spawn_update_process,
|
|
163
|
-
repo_url=repo_url,
|
|
164
|
-
repo_ref=_normalize_update_ref(repo_ref),
|
|
165
|
-
update_dir=update_dir,
|
|
166
|
-
logger=logger,
|
|
167
|
-
update_target=update_target,
|
|
168
|
-
)
|
|
169
|
-
return {
|
|
170
|
-
"status": "ok",
|
|
171
|
-
"message": f"Update started ({update_target}). Service will restart shortly.",
|
|
172
|
-
"target": update_target,
|
|
173
|
-
}
|
|
174
|
-
except UpdateInProgressError as exc:
|
|
175
|
-
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
176
|
-
except ValueError as exc:
|
|
177
|
-
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
178
|
-
except Exception as e:
|
|
179
|
-
logger = getattr(getattr(request.app, "state", None), "logger", None)
|
|
180
|
-
if logger:
|
|
181
|
-
logger.error("Update error: %s", e, exc_info=True)
|
|
182
|
-
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
183
|
-
|
|
184
|
-
@router.get("/system/update/status", response_model=SystemUpdateStatusResponse)
|
|
185
|
-
async def system_update_status():
|
|
186
|
-
status = await asyncio.to_thread(_read_update_status)
|
|
187
|
-
if status is None:
|
|
188
|
-
return {"status": "unknown", "message": "No update status recorded."}
|
|
189
|
-
return status
|
|
190
|
-
|
|
191
|
-
return router
|
|
7
|
+
sys.modules[__name__] = _system
|