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
codex_autorunner/routes/voice.py
CHANGED
|
@@ -1,120 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Voice transcription and configuration routes.
|
|
3
|
-
"""
|
|
1
|
+
"""Backward-compatible voice routes."""
|
|
4
2
|
|
|
5
|
-
import
|
|
6
|
-
import logging
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
|
-
from fastapi import APIRouter, File, HTTPException, Request, UploadFile
|
|
10
|
-
|
|
11
|
-
from ..voice import VoiceService, VoiceServiceError
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger("codex_autorunner.routes.voice")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def build_voice_routes() -> APIRouter:
|
|
17
|
-
"""Build routes for voice transcription and config."""
|
|
18
|
-
router = APIRouter()
|
|
19
|
-
|
|
20
|
-
@router.get("/api/voice/config")
|
|
21
|
-
def get_voice_config(request: Request):
|
|
22
|
-
voice_service: Optional[VoiceService] = request.app.state.voice_service
|
|
23
|
-
voice_config = request.app.state.voice_config
|
|
24
|
-
missing_reason = getattr(request.app.state, "voice_missing_reason", None)
|
|
25
|
-
if missing_reason:
|
|
26
|
-
return {
|
|
27
|
-
"enabled": False,
|
|
28
|
-
"provider": voice_config.provider,
|
|
29
|
-
"latency_mode": voice_config.latency_mode,
|
|
30
|
-
"chunk_ms": voice_config.chunk_ms,
|
|
31
|
-
"sample_rate": voice_config.sample_rate,
|
|
32
|
-
"warn_on_remote_api": voice_config.warn_on_remote_api,
|
|
33
|
-
"has_api_key": False,
|
|
34
|
-
"push_to_talk": {
|
|
35
|
-
"max_ms": voice_config.push_to_talk.max_ms,
|
|
36
|
-
"silence_auto_stop_ms": voice_config.push_to_talk.silence_auto_stop_ms,
|
|
37
|
-
"min_hold_ms": voice_config.push_to_talk.min_hold_ms,
|
|
38
|
-
},
|
|
39
|
-
"missing_extra": missing_reason,
|
|
40
|
-
}
|
|
41
|
-
if voice_service is None:
|
|
42
|
-
# Degrade gracefully: still return config to the UI even if service init failed.
|
|
43
|
-
try:
|
|
44
|
-
return VoiceService(
|
|
45
|
-
voice_config, logger=request.app.state.logger
|
|
46
|
-
).config_payload()
|
|
47
|
-
except (ValueError, TypeError, OSError) as exc:
|
|
48
|
-
logger.debug("Failed to create VoiceService for config: %s", exc)
|
|
49
|
-
return {
|
|
50
|
-
"enabled": False,
|
|
51
|
-
"provider": voice_config.provider,
|
|
52
|
-
"latency_mode": voice_config.latency_mode,
|
|
53
|
-
"chunk_ms": voice_config.chunk_ms,
|
|
54
|
-
"sample_rate": voice_config.sample_rate,
|
|
55
|
-
"warn_on_remote_api": voice_config.warn_on_remote_api,
|
|
56
|
-
"has_api_key": False,
|
|
57
|
-
"push_to_talk": {
|
|
58
|
-
"max_ms": voice_config.push_to_talk.max_ms,
|
|
59
|
-
"silence_auto_stop_ms": voice_config.push_to_talk.silence_auto_stop_ms,
|
|
60
|
-
"min_hold_ms": voice_config.push_to_talk.min_hold_ms,
|
|
61
|
-
},
|
|
62
|
-
}
|
|
63
|
-
return voice_service.config_payload()
|
|
64
|
-
|
|
65
|
-
@router.post("/api/voice/transcribe")
|
|
66
|
-
async def transcribe_voice(
|
|
67
|
-
request: Request,
|
|
68
|
-
file: Optional[UploadFile] = File(None),
|
|
69
|
-
language: Optional[str] = None,
|
|
70
|
-
):
|
|
71
|
-
voice_service: Optional[VoiceService] = request.app.state.voice_service
|
|
72
|
-
voice_config = request.app.state.voice_config
|
|
73
|
-
missing_reason = getattr(request.app.state, "voice_missing_reason", None)
|
|
74
|
-
if missing_reason:
|
|
75
|
-
raise HTTPException(status_code=503, detail=missing_reason)
|
|
76
|
-
if not voice_service or not voice_config.enabled:
|
|
77
|
-
raise HTTPException(status_code=400, detail="Voice is disabled")
|
|
78
|
-
|
|
79
|
-
filename: Optional[str] = None
|
|
80
|
-
content_type: Optional[str] = None
|
|
81
|
-
if file is not None:
|
|
82
|
-
filename = file.filename
|
|
83
|
-
content_type = file.content_type
|
|
84
|
-
try:
|
|
85
|
-
audio_bytes = await file.read()
|
|
86
|
-
except Exception as exc:
|
|
87
|
-
raise HTTPException(
|
|
88
|
-
status_code=400, detail="Unable to read audio upload"
|
|
89
|
-
) from exc
|
|
90
|
-
else:
|
|
91
|
-
audio_bytes = await request.body()
|
|
92
|
-
try:
|
|
93
|
-
result = await asyncio.to_thread(
|
|
94
|
-
voice_service.transcribe,
|
|
95
|
-
audio_bytes,
|
|
96
|
-
client="web",
|
|
97
|
-
user_agent=request.headers.get("user-agent"),
|
|
98
|
-
language=language,
|
|
99
|
-
filename=filename,
|
|
100
|
-
content_type=content_type,
|
|
101
|
-
)
|
|
102
|
-
except VoiceServiceError as exc:
|
|
103
|
-
if exc.reason == "unauthorized":
|
|
104
|
-
status = 401
|
|
105
|
-
elif exc.reason == "forbidden":
|
|
106
|
-
status = 403
|
|
107
|
-
elif exc.reason == "audio_too_large":
|
|
108
|
-
status = 413
|
|
109
|
-
elif exc.reason == "rate_limited":
|
|
110
|
-
status = 429
|
|
111
|
-
else:
|
|
112
|
-
status = (
|
|
113
|
-
400
|
|
114
|
-
if exc.reason in ("disabled", "empty_audio", "invalid_audio")
|
|
115
|
-
else 502
|
|
116
|
-
)
|
|
117
|
-
raise HTTPException(status_code=status, detail=exc.detail) from exc
|
|
118
|
-
return {"status": "ok", **result}
|
|
119
|
-
|
|
120
|
-
return router
|
|
3
|
+
from ..surfaces.web.routes.voice import * # noqa: F401,F403
|
codex_autorunner/server.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from importlib import resources
|
|
2
2
|
|
|
3
3
|
from .core.engine import Engine, LockError, clear_stale_lock, doctor
|
|
4
|
-
from .web.app import create_app, create_hub_app
|
|
5
|
-
from .web.middleware import BasePathRouterMiddleware
|
|
4
|
+
from .surfaces.web.app import create_app, create_hub_app, create_repo_app
|
|
5
|
+
from .surfaces.web.middleware import BasePathRouterMiddleware
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
8
|
"Engine",
|
|
@@ -11,6 +11,7 @@ __all__ = [
|
|
|
11
11
|
"clear_stale_lock",
|
|
12
12
|
"create_app",
|
|
13
13
|
"create_hub_app",
|
|
14
|
+
"create_repo_app",
|
|
14
15
|
"doctor",
|
|
15
16
|
"resources",
|
|
16
17
|
]
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
1
2
|
import { api, flash } from "./utils.js";
|
|
3
|
+
import { createSmartRefresh } from "./smartRefresh.js";
|
|
2
4
|
const STORAGE_KEYS = {
|
|
3
5
|
selected: "car.agent.selected",
|
|
4
6
|
model: (agent) => `car.agent.${agent}.model`,
|
|
@@ -14,6 +16,22 @@ let agentList = [...FALLBACK_AGENTS];
|
|
|
14
16
|
let defaultAgent = "codex";
|
|
15
17
|
const modelCatalogs = new Map();
|
|
16
18
|
const modelCatalogPromises = new Map();
|
|
19
|
+
const agentControlsRefresh = createSmartRefresh({
|
|
20
|
+
getSignature: (payload) => {
|
|
21
|
+
const agentsSig = payload.agents
|
|
22
|
+
.map((agent) => `${agent.id}:${agent.name || ""}:${agent.version || ""}:${agent.protocol_version || ""}`)
|
|
23
|
+
.join("|");
|
|
24
|
+
const catalogSig = payload.catalog
|
|
25
|
+
? `${payload.catalog.default_model || ""}:${payload.catalog.models
|
|
26
|
+
.map((model) => `${model.id}:${model.display_name || ""}:${model.supports_reasoning ? "1" : "0"}:${model.reasoning_options.join(",")}`)
|
|
27
|
+
.join("|")}`
|
|
28
|
+
: "none";
|
|
29
|
+
return `${agentsSig}::${payload.defaultAgent}::${catalogSig}`;
|
|
30
|
+
},
|
|
31
|
+
render: (payload) => {
|
|
32
|
+
renderAgentControls(payload);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
17
35
|
function safeGetStorage(key) {
|
|
18
36
|
try {
|
|
19
37
|
return localStorage.getItem(key);
|
|
@@ -241,7 +259,7 @@ function resolveSelectedReasoning(agent, model) {
|
|
|
241
259
|
}
|
|
242
260
|
return model.reasoning_options[0] || "";
|
|
243
261
|
}
|
|
244
|
-
async function
|
|
262
|
+
async function loadAgentControlsPayload() {
|
|
245
263
|
try {
|
|
246
264
|
await loadAgents();
|
|
247
265
|
}
|
|
@@ -250,11 +268,6 @@ async function refreshControls() {
|
|
|
250
268
|
ensureFallbackAgents();
|
|
251
269
|
}
|
|
252
270
|
const selectedAgent = getSelectedAgent();
|
|
253
|
-
// Always update agent options first (uses in-memory agentList)
|
|
254
|
-
controls.forEach((control) => {
|
|
255
|
-
ensureAgentOptions(control.agentSelect);
|
|
256
|
-
});
|
|
257
|
-
// Then try to load model catalog
|
|
258
271
|
let catalog = modelCatalogs.get(selectedAgent);
|
|
259
272
|
if (!catalog) {
|
|
260
273
|
try {
|
|
@@ -265,6 +278,20 @@ async function refreshControls() {
|
|
|
265
278
|
catalog = null;
|
|
266
279
|
}
|
|
267
280
|
}
|
|
281
|
+
return {
|
|
282
|
+
agents: [...agentList],
|
|
283
|
+
defaultAgent,
|
|
284
|
+
selectedAgent,
|
|
285
|
+
catalog: catalog || null,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function renderAgentControls(payload) {
|
|
289
|
+
const selectedAgent = payload.selectedAgent;
|
|
290
|
+
// Always update agent options first (uses in-memory agentList)
|
|
291
|
+
controls.forEach((control) => {
|
|
292
|
+
ensureAgentOptions(control.agentSelect);
|
|
293
|
+
});
|
|
294
|
+
const catalog = payload.catalog;
|
|
268
295
|
// Update model and reasoning options
|
|
269
296
|
controls.forEach((control) => {
|
|
270
297
|
ensureModelOptions(control.modelSelect, catalog);
|
|
@@ -287,6 +314,9 @@ async function refreshControls() {
|
|
|
287
314
|
}
|
|
288
315
|
});
|
|
289
316
|
}
|
|
317
|
+
export async function refreshAgentControls(request = {}) {
|
|
318
|
+
await agentControlsRefresh.refresh(loadAgentControlsPayload, request);
|
|
319
|
+
}
|
|
290
320
|
async function handleAgentChange(nextAgent) {
|
|
291
321
|
const previous = getSelectedAgent();
|
|
292
322
|
setSelectedAgent(nextAgent);
|
|
@@ -297,17 +327,17 @@ async function handleAgentChange(nextAgent) {
|
|
|
297
327
|
setSelectedAgent(previous);
|
|
298
328
|
flash(`Failed to load ${getLabelText(nextAgent)} models; staying on ${getLabelText(previous)}.`, "error");
|
|
299
329
|
}
|
|
300
|
-
await
|
|
330
|
+
await refreshAgentControls({ force: true, reason: "manual" });
|
|
301
331
|
}
|
|
302
332
|
async function handleModelChange(nextModel) {
|
|
303
333
|
const agent = getSelectedAgent();
|
|
304
334
|
setSelectedModel(agent, nextModel);
|
|
305
|
-
await
|
|
335
|
+
await refreshAgentControls({ force: true, reason: "manual" });
|
|
306
336
|
}
|
|
307
337
|
async function handleReasoningChange(nextReasoning) {
|
|
308
338
|
const agent = getSelectedAgent();
|
|
309
339
|
setSelectedReasoning(agent, nextReasoning);
|
|
310
|
-
await
|
|
340
|
+
await refreshAgentControls({ force: true, reason: "manual" });
|
|
311
341
|
}
|
|
312
342
|
/**
|
|
313
343
|
* @param {AgentControlConfig} [config]
|
|
@@ -342,10 +372,10 @@ export function initAgentControls(config = {}) {
|
|
|
342
372
|
});
|
|
343
373
|
}
|
|
344
374
|
// Async refresh to load from API (will update if API returns different data)
|
|
345
|
-
|
|
375
|
+
refreshAgentControls({ force: true, reason: "initial" }).catch((err) => {
|
|
346
376
|
console.warn("Failed to refresh agent controls", err);
|
|
347
377
|
});
|
|
348
378
|
}
|
|
349
379
|
export async function ensureAgentCatalog() {
|
|
350
|
-
await
|
|
380
|
+
await refreshAgentControls({ force: true, reason: "manual" });
|
|
351
381
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
/**
|
|
3
|
+
* Shared parsing helpers for agent (app-server) events.
|
|
4
|
+
* Used by ticket chat and live agent output to render rich activity.
|
|
5
|
+
*/
|
|
6
|
+
function extractCommand(item, params) {
|
|
7
|
+
const command = item?.command ?? params?.command;
|
|
8
|
+
if (Array.isArray(command)) {
|
|
9
|
+
return command
|
|
10
|
+
.map((part) => String(part))
|
|
11
|
+
.join(" ")
|
|
12
|
+
.trim();
|
|
13
|
+
}
|
|
14
|
+
if (typeof command === "string")
|
|
15
|
+
return command.trim();
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function extractFiles(payload) {
|
|
19
|
+
const files = [];
|
|
20
|
+
const addEntry = (entry) => {
|
|
21
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
22
|
+
files.push(entry.trim());
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (entry && typeof entry === "object") {
|
|
26
|
+
const entryObj = entry;
|
|
27
|
+
const path = entryObj.path || entryObj.file || entryObj.name;
|
|
28
|
+
if (typeof path === "string" && path.trim()) {
|
|
29
|
+
files.push(path.trim());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
if (!payload || typeof payload !== "object")
|
|
34
|
+
return files;
|
|
35
|
+
for (const key of ["files", "fileChanges", "paths"]) {
|
|
36
|
+
const value = payload[key];
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
value.forEach(addEntry);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const key of ["path", "file", "name"]) {
|
|
42
|
+
addEntry(payload[key]);
|
|
43
|
+
}
|
|
44
|
+
return files;
|
|
45
|
+
}
|
|
46
|
+
function extractErrorMessage(params) {
|
|
47
|
+
if (!params || typeof params !== "object")
|
|
48
|
+
return "";
|
|
49
|
+
const err = params.error;
|
|
50
|
+
if (err && typeof err === "object") {
|
|
51
|
+
const errObj = err;
|
|
52
|
+
const message = typeof errObj.message === "string" ? errObj.message : "";
|
|
53
|
+
const details = typeof errObj.additionalDetails === "string"
|
|
54
|
+
? errObj.additionalDetails
|
|
55
|
+
: typeof errObj.details === "string"
|
|
56
|
+
? errObj.details
|
|
57
|
+
: "";
|
|
58
|
+
if (message && details && message !== details) {
|
|
59
|
+
return `${message} (${details})`;
|
|
60
|
+
}
|
|
61
|
+
return message || details;
|
|
62
|
+
}
|
|
63
|
+
if (typeof err === "string")
|
|
64
|
+
return err;
|
|
65
|
+
if (typeof params.message === "string")
|
|
66
|
+
return params.message;
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
function hasMeaningfulText(summary, detail) {
|
|
70
|
+
return Boolean(summary.trim() || detail.trim());
|
|
71
|
+
}
|
|
72
|
+
function inferSignificance(kind, method) {
|
|
73
|
+
if (kind === "thinking")
|
|
74
|
+
return true;
|
|
75
|
+
if (kind === "error")
|
|
76
|
+
return true;
|
|
77
|
+
if (["tool", "command", "file", "output"].includes(kind))
|
|
78
|
+
return true;
|
|
79
|
+
if (method.includes("requestApproval"))
|
|
80
|
+
return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extract output delta text from an event payload.
|
|
85
|
+
*/
|
|
86
|
+
export function extractOutputDelta(payload) {
|
|
87
|
+
const message = payload && typeof payload === "object" ? payload.message || payload : payload;
|
|
88
|
+
if (!message || typeof message !== "object")
|
|
89
|
+
return "";
|
|
90
|
+
const method = String(message.method || "").toLowerCase();
|
|
91
|
+
if (!method.includes("outputdelta"))
|
|
92
|
+
return "";
|
|
93
|
+
const params = message.params || {};
|
|
94
|
+
if (typeof params.delta === "string")
|
|
95
|
+
return params.delta;
|
|
96
|
+
if (typeof params.text === "string")
|
|
97
|
+
return params.text;
|
|
98
|
+
if (typeof params.output === "string")
|
|
99
|
+
return params.output;
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse an app-server event payload into a normalized AgentEvent plus merge hints.
|
|
104
|
+
*/
|
|
105
|
+
export function parseAppServerEvent(payload) {
|
|
106
|
+
const message = payload && typeof payload === "object" ? payload.message || payload : payload;
|
|
107
|
+
if (!message || typeof message !== "object")
|
|
108
|
+
return null;
|
|
109
|
+
const messageObj = message;
|
|
110
|
+
const method = messageObj.method || "app-server";
|
|
111
|
+
const params = messageObj.params || {};
|
|
112
|
+
const item = params.item || {};
|
|
113
|
+
const itemId = params.itemId || item.id || item.itemId || null;
|
|
114
|
+
const receivedAt = payload && typeof payload === "object"
|
|
115
|
+
? payload.received_at || payload.receivedAt || Date.now()
|
|
116
|
+
: Date.now();
|
|
117
|
+
// Handle reasoning/thinking deltas - accumulate into existing event
|
|
118
|
+
if (method === "item/reasoning/summaryTextDelta") {
|
|
119
|
+
const delta = params.delta || "";
|
|
120
|
+
if (!delta)
|
|
121
|
+
return null;
|
|
122
|
+
const event = {
|
|
123
|
+
id: payload?.id || `${Date.now()}`,
|
|
124
|
+
title: "Thinking",
|
|
125
|
+
summary: delta,
|
|
126
|
+
detail: "",
|
|
127
|
+
kind: "thinking",
|
|
128
|
+
isSignificant: true,
|
|
129
|
+
time: receivedAt,
|
|
130
|
+
itemId,
|
|
131
|
+
method,
|
|
132
|
+
};
|
|
133
|
+
return { event, mergeStrategy: "append" };
|
|
134
|
+
}
|
|
135
|
+
// Handle reasoning part added (paragraph break)
|
|
136
|
+
if (method === "item/reasoning/summaryPartAdded") {
|
|
137
|
+
const event = {
|
|
138
|
+
id: payload?.id || `${Date.now()}`,
|
|
139
|
+
title: "Thinking",
|
|
140
|
+
summary: "",
|
|
141
|
+
detail: "",
|
|
142
|
+
kind: "thinking",
|
|
143
|
+
isSignificant: true,
|
|
144
|
+
time: receivedAt,
|
|
145
|
+
itemId,
|
|
146
|
+
method,
|
|
147
|
+
};
|
|
148
|
+
return { event, mergeStrategy: "newline" };
|
|
149
|
+
}
|
|
150
|
+
let title = method;
|
|
151
|
+
let summary = "";
|
|
152
|
+
let detail = "";
|
|
153
|
+
let kind = "event";
|
|
154
|
+
// Handle generic status updates
|
|
155
|
+
if (method === "status" || params.status) {
|
|
156
|
+
title = "Status";
|
|
157
|
+
summary = params.status || "Processing";
|
|
158
|
+
kind = "status";
|
|
159
|
+
}
|
|
160
|
+
else if (method === "item/completed") {
|
|
161
|
+
const itemType = item.type;
|
|
162
|
+
if (itemType === "commandExecution") {
|
|
163
|
+
title = "Command";
|
|
164
|
+
summary = extractCommand(item, params);
|
|
165
|
+
kind = "command";
|
|
166
|
+
if (item.exitCode !== undefined && item.exitCode !== null) {
|
|
167
|
+
detail = `exit ${item.exitCode}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (itemType === "fileChange") {
|
|
171
|
+
title = "File change";
|
|
172
|
+
const files = extractFiles(item);
|
|
173
|
+
summary = files.join(", ") || "Updated files";
|
|
174
|
+
kind = "file";
|
|
175
|
+
}
|
|
176
|
+
else if (itemType === "tool") {
|
|
177
|
+
title = "Tool";
|
|
178
|
+
summary =
|
|
179
|
+
item.name ||
|
|
180
|
+
item.tool ||
|
|
181
|
+
item.id ||
|
|
182
|
+
"Tool call";
|
|
183
|
+
kind = "tool";
|
|
184
|
+
}
|
|
185
|
+
else if (itemType === "agentMessage") {
|
|
186
|
+
title = "Agent";
|
|
187
|
+
summary = item.text || "Agent message";
|
|
188
|
+
kind = "output";
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
title = itemType ? `Item ${itemType}` : "Item completed";
|
|
192
|
+
summary = item.text || item.message || "";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (method === "item/commandExecution/requestApproval") {
|
|
196
|
+
title = "Command approval";
|
|
197
|
+
summary = extractCommand(item, params) || "Approval requested";
|
|
198
|
+
kind = "command";
|
|
199
|
+
}
|
|
200
|
+
else if (method === "item/fileChange/requestApproval") {
|
|
201
|
+
title = "File approval";
|
|
202
|
+
const files = extractFiles(params);
|
|
203
|
+
summary = files.join(", ") || "Approval requested";
|
|
204
|
+
kind = "file";
|
|
205
|
+
}
|
|
206
|
+
else if (method === "turn/completed") {
|
|
207
|
+
title = "Turn completed";
|
|
208
|
+
summary = params.status || "completed";
|
|
209
|
+
kind = "status";
|
|
210
|
+
}
|
|
211
|
+
else if (method === "error") {
|
|
212
|
+
title = "Error";
|
|
213
|
+
summary = extractErrorMessage(params) || "App-server error";
|
|
214
|
+
kind = "error";
|
|
215
|
+
}
|
|
216
|
+
else if (method.includes("outputDelta")) {
|
|
217
|
+
title = "Output";
|
|
218
|
+
summary = params.delta || params.text || "";
|
|
219
|
+
kind = "output";
|
|
220
|
+
}
|
|
221
|
+
else if (params.delta) {
|
|
222
|
+
title = "Delta";
|
|
223
|
+
summary = params.delta;
|
|
224
|
+
}
|
|
225
|
+
const summaryText = typeof summary === "string" ? summary : String(summary ?? "");
|
|
226
|
+
const detailText = typeof detail === "string" ? detail : String(detail ?? "");
|
|
227
|
+
const meaningful = hasMeaningfulText(summaryText, detailText);
|
|
228
|
+
const isStarted = method.includes("item/started");
|
|
229
|
+
if (!meaningful && isStarted) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
if (!meaningful) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const isSignificant = inferSignificance(kind, method);
|
|
236
|
+
const event = {
|
|
237
|
+
id: payload?.id || `${Date.now()}`,
|
|
238
|
+
title,
|
|
239
|
+
summary: summaryText,
|
|
240
|
+
detail: detailText,
|
|
241
|
+
kind,
|
|
242
|
+
isSignificant,
|
|
243
|
+
time: receivedAt,
|
|
244
|
+
itemId,
|
|
245
|
+
method,
|
|
246
|
+
};
|
|
247
|
+
return { event };
|
|
248
|
+
}
|
codex_autorunner/static/app.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
1
2
|
import { REPO_ID, HUB_BASE } from "./env.js";
|
|
2
3
|
import { initHub } from "./hub.js";
|
|
3
|
-
import { initTabs, registerTab } from "./tabs.js";
|
|
4
|
-
import { initDashboard } from "./dashboard.js";
|
|
5
|
-
import { initDocs } from "./docs.js";
|
|
6
|
-
import { initLogs } from "./logs.js";
|
|
4
|
+
import { initTabs, registerTab, registerHamburgerAction } from "./tabs.js";
|
|
7
5
|
import { initTerminal } from "./terminal.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { initGitHub } from "./github.js";
|
|
6
|
+
import { initTicketFlow } from "./tickets.js";
|
|
7
|
+
import { initMessages, initMessageBell } from "./messages.js";
|
|
11
8
|
import { initMobileCompact } from "./mobileCompact.js";
|
|
12
9
|
import { subscribe } from "./bus.js";
|
|
13
|
-
import { initRepoSettingsPanel } from "./settings.js";
|
|
10
|
+
import { initRepoSettingsPanel, openRepoSettings } from "./settings.js";
|
|
14
11
|
import { flash } from "./utils.js";
|
|
15
12
|
import { initLiveUpdates } from "./liveUpdates.js";
|
|
16
|
-
|
|
13
|
+
import { initHealthGate } from "./health.js";
|
|
14
|
+
import { initWorkspace } from "./workspace.js";
|
|
15
|
+
import { initDashboard } from "./dashboard.js";
|
|
16
|
+
import { initArchive } from "./archive.js";
|
|
17
|
+
async function initRepoShell() {
|
|
18
|
+
await initHealthGate();
|
|
17
19
|
if (REPO_ID) {
|
|
18
20
|
const navBar = document.querySelector(".nav-bar");
|
|
19
21
|
if (navBar) {
|
|
@@ -32,23 +34,34 @@ function initRepoShell() {
|
|
|
32
34
|
brand.insertAdjacentElement("afterend", repoName);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
registerTab("
|
|
37
|
-
registerTab("
|
|
38
|
-
registerTab("
|
|
37
|
+
const defaultTab = REPO_ID ? "tickets" : "analytics";
|
|
38
|
+
registerTab("tickets", "Tickets");
|
|
39
|
+
registerTab("inbox", "Inbox");
|
|
40
|
+
registerTab("workspace", "Workspace");
|
|
39
41
|
registerTab("terminal", "Terminal");
|
|
42
|
+
// Menu tabs (shown in hamburger menu)
|
|
43
|
+
registerTab("analytics", "Analytics", { menuTab: true, icon: "📊" });
|
|
44
|
+
registerTab("archive", "Archive", { menuTab: true, icon: "📦" });
|
|
45
|
+
// Settings action in hamburger menu
|
|
46
|
+
registerHamburgerAction("settings", "Settings", "⚙", () => openRepoSettings());
|
|
40
47
|
const initializedTabs = new Set();
|
|
41
48
|
const lazyInit = (tabId) => {
|
|
42
49
|
if (initializedTabs.has(tabId))
|
|
43
50
|
return;
|
|
44
|
-
if (tabId === "
|
|
45
|
-
|
|
51
|
+
if (tabId === "workspace") {
|
|
52
|
+
initWorkspace();
|
|
53
|
+
}
|
|
54
|
+
else if (tabId === "inbox" || tabId === "messages") {
|
|
55
|
+
initMessages();
|
|
56
|
+
}
|
|
57
|
+
else if (tabId === "analytics") {
|
|
58
|
+
initDashboard();
|
|
46
59
|
}
|
|
47
|
-
else if (tabId === "
|
|
48
|
-
|
|
60
|
+
else if (tabId === "archive") {
|
|
61
|
+
initArchive();
|
|
49
62
|
}
|
|
50
|
-
else if (tabId === "
|
|
51
|
-
|
|
63
|
+
else if (tabId === "tickets") {
|
|
64
|
+
initTicketFlow();
|
|
52
65
|
}
|
|
53
66
|
initializedTabs.add(tabId);
|
|
54
67
|
};
|
|
@@ -58,7 +71,7 @@ function initRepoShell() {
|
|
|
58
71
|
}
|
|
59
72
|
lazyInit(tabId);
|
|
60
73
|
});
|
|
61
|
-
initTabs();
|
|
74
|
+
initTabs(defaultTab);
|
|
62
75
|
const activePanel = document.querySelector(".panel.active");
|
|
63
76
|
if (activePanel?.id) {
|
|
64
77
|
lazyInit(activePanel.id);
|
|
@@ -67,12 +80,10 @@ function initRepoShell() {
|
|
|
67
80
|
terminalPanel?.addEventListener("pointerdown", () => {
|
|
68
81
|
lazyInit("terminal");
|
|
69
82
|
}, { once: true });
|
|
70
|
-
|
|
83
|
+
initMessageBell();
|
|
71
84
|
initLiveUpdates();
|
|
72
85
|
initRepoSettingsPanel();
|
|
73
|
-
initGitHub();
|
|
74
86
|
initMobileCompact();
|
|
75
|
-
loadState();
|
|
76
87
|
const repoShell = document.getElementById("repo-shell");
|
|
77
88
|
if (repoShell?.hasAttribute("inert")) {
|
|
78
89
|
const openModals = document.querySelectorAll(".modal-overlay:not([hidden])");
|
|
@@ -97,6 +108,6 @@ function bootstrap() {
|
|
|
97
108
|
repoShell.classList.remove("hidden");
|
|
98
109
|
if (hubShell)
|
|
99
110
|
hubShell.classList.add("hidden");
|
|
100
|
-
initRepoShell();
|
|
111
|
+
void initRepoShell();
|
|
101
112
|
}
|
|
102
113
|
bootstrap();
|