codex-autorunner 1.0.0__py3-none-any.whl → 1.2.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/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/runtime.py +59 -18
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +36 -7
- codex_autorunner/bootstrap.py +226 -4
- codex_autorunner/cli.py +5 -1174
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +20 -0
- codex_autorunner/core/about_car.py +119 -1
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_threads.py +17 -2
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +6 -2
- codex_autorunner/core/config.py +433 -4
- codex_autorunner/core/context_awareness.py +38 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/drafts.py +58 -4
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +96 -2
- codex_autorunner/core/flows/models.py +13 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +134 -0
- codex_autorunner/core/flows/runtime.py +57 -4
- codex_autorunner/core/flows/store.py +142 -7
- codex_autorunner/core/flows/transition.py +27 -15
- codex_autorunner/core/flows/ux_helpers.py +272 -0
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +291 -20
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +496 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +13 -8
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/{integrations/agents → core/ports}/run_event.py +23 -6
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +62 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +218 -0
- codex_autorunner/core/ticket_manager_cli.py +494 -0
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/update.py +4 -5
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +125 -15
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +37 -34
- codex_autorunner/flows/ticket_flow/definition.py +52 -3
- codex_autorunner/integrations/agents/__init__.py +11 -19
- codex_autorunner/integrations/agents/backend_orchestrator.py +302 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +177 -25
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +305 -32
- codex_autorunner/integrations/agents/runner.py +86 -0
- codex_autorunner/integrations/agents/wiring.py +279 -0
- codex_autorunner/integrations/app_server/client.py +7 -60
- 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/telegram/adapter.py +65 -0
- codex_autorunner/integrations/telegram/config.py +46 -0
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1496 -71
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +206 -48
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +20 -3
- codex_autorunner/integrations/telegram/handlers/messages.py +27 -1
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +22 -1
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +45 -10
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +338 -43
- codex_autorunner/integrations/telegram/transport.py +13 -4
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/routes/__init__.py +37 -76
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +2 -238
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -596
- codex_autorunner/routes/file_chat.py +4 -833
- codex_autorunner/routes/flows.py +4 -977
- codex_autorunner/routes/messages.py +4 -456
- 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 -193
- codex_autorunner/routes/usage.py +2 -86
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +2 -270
- codex_autorunner/server.py +4 -4
- codex_autorunner/static/agentControls.js +61 -16
- codex_autorunner/static/app.js +126 -14
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +7 -7
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/dashboard.js +224 -171
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +114 -131
- codex_autorunner/static/index.html +375 -49
- codex_autorunner/static/messages.js +568 -87
- codex_autorunner/static/notifications.js +255 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +128 -6
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9798 -6143
- codex_autorunner/static/tabs.js +152 -11
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/terminal.js +18 -0
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +137 -15
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +821 -98
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +39 -0
- codex_autorunner/static/workspace.js +389 -82
- codex_autorunner/static/workspaceFileBrowser.js +15 -13
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +2534 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2223 -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 +82 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +284 -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 +1117 -0
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +1354 -0
- codex_autorunner/surfaces/web/routes/messages.py +490 -0
- codex_autorunner/surfaces/web/routes/pma.py +1652 -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 +277 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/templates.py +634 -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 +469 -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 +8 -1
- codex_autorunner/tickets/agent_pool.py +53 -4
- codex_autorunner/tickets/files.py +37 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +6 -1
- codex_autorunner/tickets/outbox.py +50 -2
- codex_autorunner/tickets/runner.py +396 -57
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1949
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -586
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -376
- codex_autorunner/web/static_assets.py +4 -441
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/paths.py +49 -33
- codex_autorunner-1.2.0.dist-info/METADATA +150 -0
- codex_autorunner-1.2.0.dist-info/RECORD +339 -0
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -2653
- codex_autorunner/core/static_assets.py +0 -55
- codex_autorunner-1.0.0.dist-info/METADATA +0 -246
- codex_autorunner-1.0.0.dist-info/RECORD +0 -251
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
codex_autorunner/static/tabs.js
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
import { publish } from "./bus.js";
|
|
3
3
|
import { escapeHtml, getUrlParams, updateUrlParams } from "./utils.js";
|
|
4
4
|
const tabs = [];
|
|
5
|
+
const hamburgerActions = [];
|
|
6
|
+
let hamburgerMenuOpen = false;
|
|
7
|
+
let hamburgerMenuEl = null;
|
|
8
|
+
let hamburgerBtnEl = null;
|
|
9
|
+
let hamburgerBackdropEl = null;
|
|
5
10
|
export function registerTab(id, label, opts = {}) {
|
|
6
|
-
tabs.push({ id, label, hidden: Boolean(opts.hidden) });
|
|
11
|
+
tabs.push({ id, label, hidden: Boolean(opts.hidden), menuTab: Boolean(opts.menuTab), icon: opts.icon });
|
|
12
|
+
}
|
|
13
|
+
export function registerHamburgerAction(id, label, icon, onClick) {
|
|
14
|
+
hamburgerActions.push({ id, label, icon, onClick });
|
|
7
15
|
}
|
|
8
16
|
let setActivePanelFn = null;
|
|
9
17
|
let pendingActivate = null;
|
|
@@ -15,24 +23,55 @@ export function activateTab(id) {
|
|
|
15
23
|
pendingActivate = id;
|
|
16
24
|
}
|
|
17
25
|
}
|
|
26
|
+
function closeHamburgerMenu() {
|
|
27
|
+
if (!hamburgerMenuOpen)
|
|
28
|
+
return;
|
|
29
|
+
hamburgerMenuOpen = false;
|
|
30
|
+
hamburgerMenuEl?.classList.remove("open");
|
|
31
|
+
hamburgerBtnEl?.classList.remove("active");
|
|
32
|
+
hamburgerBackdropEl?.classList.remove("open");
|
|
33
|
+
}
|
|
34
|
+
function toggleHamburgerMenu() {
|
|
35
|
+
hamburgerMenuOpen = !hamburgerMenuOpen;
|
|
36
|
+
hamburgerMenuEl?.classList.toggle("open", hamburgerMenuOpen);
|
|
37
|
+
hamburgerBtnEl?.classList.toggle("active", hamburgerMenuOpen);
|
|
38
|
+
hamburgerBackdropEl?.classList.toggle("open", hamburgerMenuOpen);
|
|
39
|
+
}
|
|
40
|
+
function updateHamburgerActiveState(activeTabId) {
|
|
41
|
+
if (!hamburgerMenuEl)
|
|
42
|
+
return;
|
|
43
|
+
const items = hamburgerMenuEl.querySelectorAll(".hamburger-item[data-target]");
|
|
44
|
+
items.forEach((item) => {
|
|
45
|
+
const target = item.dataset.target;
|
|
46
|
+
item.classList.toggle("active", target === activeTabId);
|
|
47
|
+
});
|
|
48
|
+
// Also update hamburger button active state if a menu tab is active
|
|
49
|
+
const isMenuTabActive = tabs.some((t) => t.menuTab && t.id === activeTabId);
|
|
50
|
+
hamburgerBtnEl?.classList.toggle("has-active", isMenuTabActive);
|
|
51
|
+
}
|
|
18
52
|
export function initTabs(defaultTab = "analytics") {
|
|
19
53
|
const container = document.querySelector(".tabs");
|
|
54
|
+
const navBar = document.querySelector(".nav-bar");
|
|
20
55
|
if (!container)
|
|
21
56
|
return;
|
|
22
57
|
container.innerHTML = "";
|
|
23
58
|
const panels = document.querySelectorAll(".panel");
|
|
24
59
|
const setActivePanel = (id) => {
|
|
25
60
|
panels.forEach((p) => p.classList.toggle("active", p.id === id));
|
|
61
|
+
// Update primary tab buttons
|
|
26
62
|
const buttons = container.querySelectorAll(".tab");
|
|
27
63
|
buttons.forEach((btn) => btn.classList.toggle("active", btn.dataset.target === id));
|
|
64
|
+
// Update hamburger menu items
|
|
65
|
+
updateHamburgerActiveState(id);
|
|
28
66
|
updateUrlParams({ tab: id });
|
|
29
67
|
publish("tab:change", id);
|
|
30
68
|
};
|
|
31
69
|
setActivePanelFn = setActivePanel;
|
|
32
|
-
tabs
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
// Separate primary tabs from menu tabs
|
|
71
|
+
const primaryTabs = tabs.filter((t) => !t.hidden && !t.menuTab);
|
|
72
|
+
const menuTabs = tabs.filter((t) => !t.hidden && t.menuTab);
|
|
73
|
+
// Render primary tabs
|
|
74
|
+
primaryTabs.forEach(tab => {
|
|
36
75
|
const btn = document.createElement("button");
|
|
37
76
|
btn.className = "tab";
|
|
38
77
|
btn.dataset.target = tab.id;
|
|
@@ -43,20 +82,122 @@ export function initTabs(defaultTab = "analytics") {
|
|
|
43
82
|
btn.addEventListener("click", () => setActivePanel(tab.id));
|
|
44
83
|
container.appendChild(btn);
|
|
45
84
|
});
|
|
85
|
+
// Create hamburger menu if there are menu tabs or actions
|
|
86
|
+
if (menuTabs.length > 0 || hamburgerActions.length > 0) {
|
|
87
|
+
const wrapper = document.createElement("div");
|
|
88
|
+
wrapper.className = "hamburger-wrapper";
|
|
89
|
+
// Hamburger button
|
|
90
|
+
const btn = document.createElement("button");
|
|
91
|
+
btn.className = "hamburger-btn";
|
|
92
|
+
btn.setAttribute("aria-label", "More options");
|
|
93
|
+
btn.setAttribute("aria-expanded", "false");
|
|
94
|
+
btn.innerHTML = `
|
|
95
|
+
<span class="hamburger-icon">
|
|
96
|
+
<span></span>
|
|
97
|
+
<span></span>
|
|
98
|
+
<span></span>
|
|
99
|
+
</span>
|
|
100
|
+
`;
|
|
101
|
+
hamburgerBtnEl = btn;
|
|
102
|
+
// Hamburger menu dropdown
|
|
103
|
+
const menu = document.createElement("div");
|
|
104
|
+
menu.className = "hamburger-menu";
|
|
105
|
+
menu.setAttribute("role", "menu");
|
|
106
|
+
hamburgerMenuEl = menu;
|
|
107
|
+
// Add menu tab items
|
|
108
|
+
menuTabs.forEach((tab) => {
|
|
109
|
+
const item = document.createElement("button");
|
|
110
|
+
item.className = "hamburger-item";
|
|
111
|
+
item.dataset.target = tab.id;
|
|
112
|
+
item.setAttribute("role", "menuitem");
|
|
113
|
+
const iconHtml = tab.icon ? `<span class="hamburger-item-icon">${tab.icon}</span>` : "";
|
|
114
|
+
item.innerHTML = `${iconHtml}<span>${escapeHtml(tab.label)}</span>`;
|
|
115
|
+
item.addEventListener("click", () => {
|
|
116
|
+
setActivePanel(tab.id);
|
|
117
|
+
closeHamburgerMenu();
|
|
118
|
+
});
|
|
119
|
+
menu.appendChild(item);
|
|
120
|
+
});
|
|
121
|
+
// Add divider if there are both tabs and actions
|
|
122
|
+
if (menuTabs.length > 0 && hamburgerActions.length > 0) {
|
|
123
|
+
const divider = document.createElement("div");
|
|
124
|
+
divider.className = "hamburger-divider";
|
|
125
|
+
menu.appendChild(divider);
|
|
126
|
+
}
|
|
127
|
+
// Add action items (like Settings)
|
|
128
|
+
hamburgerActions.forEach((action) => {
|
|
129
|
+
const item = document.createElement("button");
|
|
130
|
+
item.className = "hamburger-item";
|
|
131
|
+
item.dataset.action = action.id;
|
|
132
|
+
item.setAttribute("role", "menuitem");
|
|
133
|
+
const iconHtml = action.icon ? `<span class="hamburger-item-icon">${action.icon}</span>` : "";
|
|
134
|
+
item.innerHTML = `${iconHtml}<span>${escapeHtml(action.label)}</span>`;
|
|
135
|
+
item.addEventListener("click", () => {
|
|
136
|
+
action.onClick();
|
|
137
|
+
closeHamburgerMenu();
|
|
138
|
+
});
|
|
139
|
+
menu.appendChild(item);
|
|
140
|
+
});
|
|
141
|
+
// Mobile backdrop - appended to body for proper z-index stacking
|
|
142
|
+
const backdrop = document.createElement("div");
|
|
143
|
+
backdrop.className = "hamburger-backdrop";
|
|
144
|
+
backdrop.addEventListener("click", closeHamburgerMenu);
|
|
145
|
+
hamburgerBackdropEl = backdrop;
|
|
146
|
+
document.body.appendChild(backdrop);
|
|
147
|
+
// Append menu to body for mobile z-index stacking (above backdrop)
|
|
148
|
+
// On mobile, the nav-bar has z-index:100 which creates a stacking context
|
|
149
|
+
// that would trap the menu below the backdrop (z-index:1999)
|
|
150
|
+
document.body.appendChild(menu);
|
|
151
|
+
// Toggle menu on button click
|
|
152
|
+
const toggleHandler = (e) => {
|
|
153
|
+
e.stopPropagation();
|
|
154
|
+
// Prevent ghost clicks on touch devices
|
|
155
|
+
if (e.type === "touchend") {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
}
|
|
158
|
+
toggleHamburgerMenu();
|
|
159
|
+
btn.setAttribute("aria-expanded", String(hamburgerMenuOpen));
|
|
160
|
+
};
|
|
161
|
+
btn.addEventListener("click", toggleHandler);
|
|
162
|
+
btn.addEventListener("touchend", toggleHandler);
|
|
163
|
+
// Close menu on outside click (check both wrapper and menu since menu is in body)
|
|
164
|
+
document.addEventListener("click", (e) => {
|
|
165
|
+
if (hamburgerMenuOpen && !wrapper.contains(e.target) && !menu.contains(e.target)) {
|
|
166
|
+
closeHamburgerMenu();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// Close menu on Escape
|
|
170
|
+
document.addEventListener("keydown", (e) => {
|
|
171
|
+
if (e.key === "Escape" && hamburgerMenuOpen) {
|
|
172
|
+
closeHamburgerMenu();
|
|
173
|
+
hamburgerBtnEl?.focus();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
wrapper.appendChild(btn);
|
|
177
|
+
// Insert hamburger after tabs or at the end of nav bar
|
|
178
|
+
const navActions = navBar?.querySelector(".nav-actions");
|
|
179
|
+
if (navActions) {
|
|
180
|
+
navBar?.insertBefore(wrapper, navActions);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
navBar?.appendChild(wrapper);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
46
186
|
const params = getUrlParams();
|
|
47
187
|
const requested = params.get("tab");
|
|
48
|
-
const
|
|
188
|
+
const allVisibleTabs = tabs.filter((t) => !t.hidden);
|
|
189
|
+
const initialTab = allVisibleTabs.some((t) => t.id === requested)
|
|
49
190
|
? requested
|
|
50
|
-
:
|
|
191
|
+
: allVisibleTabs.some((t) => t.id === defaultTab)
|
|
51
192
|
? defaultTab
|
|
52
|
-
:
|
|
193
|
+
: allVisibleTabs[0]?.id;
|
|
53
194
|
if (initialTab) {
|
|
54
195
|
setActivePanel(initialTab);
|
|
55
196
|
}
|
|
56
|
-
else if (
|
|
57
|
-
setActivePanel(
|
|
197
|
+
else if (allVisibleTabs.length > 0) {
|
|
198
|
+
setActivePanel(allVisibleTabs[0].id);
|
|
58
199
|
}
|
|
59
|
-
if (pendingActivate &&
|
|
200
|
+
if (pendingActivate && allVisibleTabs.some((t) => t.id === pendingActivate)) {
|
|
60
201
|
const id = pendingActivate;
|
|
61
202
|
pendingActivate = null;
|
|
62
203
|
setActivePanel(id);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { api, confirmModal, flash } from "./utils.js";
|
|
3
|
+
import { checkTemplatesEnabled } from "./ticketTemplates.js";
|
|
4
|
+
function els() {
|
|
5
|
+
return {
|
|
6
|
+
list: document.getElementById("template-repos-list"),
|
|
7
|
+
addBtn: document.getElementById("template-repos-add"),
|
|
8
|
+
form: document.getElementById("template-repo-form"),
|
|
9
|
+
idInput: document.getElementById("repo-id"),
|
|
10
|
+
urlInput: document.getElementById("repo-url"),
|
|
11
|
+
refInput: document.getElementById("repo-ref"),
|
|
12
|
+
trustedInput: document.getElementById("repo-trusted"),
|
|
13
|
+
saveBtn: document.getElementById("repo-save"),
|
|
14
|
+
cancelBtn: document.getElementById("repo-cancel"),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const state = {
|
|
18
|
+
mode: "create",
|
|
19
|
+
editId: null,
|
|
20
|
+
enabled: false,
|
|
21
|
+
repos: [],
|
|
22
|
+
busy: false,
|
|
23
|
+
};
|
|
24
|
+
function setBusy(busy) {
|
|
25
|
+
state.busy = busy;
|
|
26
|
+
const { saveBtn, addBtn } = els();
|
|
27
|
+
if (saveBtn)
|
|
28
|
+
saveBtn.disabled = busy;
|
|
29
|
+
if (addBtn)
|
|
30
|
+
addBtn.disabled = busy;
|
|
31
|
+
}
|
|
32
|
+
function showForm(show) {
|
|
33
|
+
const { form } = els();
|
|
34
|
+
if (!form)
|
|
35
|
+
return;
|
|
36
|
+
if (show)
|
|
37
|
+
form.classList.remove("hidden");
|
|
38
|
+
else
|
|
39
|
+
form.classList.add("hidden");
|
|
40
|
+
}
|
|
41
|
+
function resetForm() {
|
|
42
|
+
const { idInput, urlInput, refInput, trustedInput } = els();
|
|
43
|
+
if (idInput)
|
|
44
|
+
idInput.value = "";
|
|
45
|
+
if (urlInput)
|
|
46
|
+
urlInput.value = "";
|
|
47
|
+
if (refInput)
|
|
48
|
+
refInput.value = "main";
|
|
49
|
+
if (trustedInput)
|
|
50
|
+
trustedInput.checked = false;
|
|
51
|
+
state.mode = "create";
|
|
52
|
+
state.editId = null;
|
|
53
|
+
if (idInput)
|
|
54
|
+
idInput.disabled = false;
|
|
55
|
+
}
|
|
56
|
+
function openCreateForm() {
|
|
57
|
+
resetForm();
|
|
58
|
+
showForm(true);
|
|
59
|
+
const { idInput } = els();
|
|
60
|
+
idInput?.focus();
|
|
61
|
+
}
|
|
62
|
+
function openEditForm(repo) {
|
|
63
|
+
const { idInput, urlInput, refInput, trustedInput } = els();
|
|
64
|
+
state.mode = "edit";
|
|
65
|
+
state.editId = repo.id;
|
|
66
|
+
if (idInput) {
|
|
67
|
+
idInput.value = repo.id;
|
|
68
|
+
idInput.disabled = true;
|
|
69
|
+
}
|
|
70
|
+
if (urlInput)
|
|
71
|
+
urlInput.value = repo.url;
|
|
72
|
+
if (refInput)
|
|
73
|
+
refInput.value = repo.default_ref || "main";
|
|
74
|
+
if (trustedInput)
|
|
75
|
+
trustedInput.checked = Boolean(repo.trusted);
|
|
76
|
+
showForm(true);
|
|
77
|
+
urlInput?.focus();
|
|
78
|
+
}
|
|
79
|
+
function normalizeRequired(value, label) {
|
|
80
|
+
const v = (value || "").trim();
|
|
81
|
+
if (!v) {
|
|
82
|
+
flash(`${label} is required`, "error");
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return v;
|
|
86
|
+
}
|
|
87
|
+
function renderRepos() {
|
|
88
|
+
const { list } = els();
|
|
89
|
+
if (!list)
|
|
90
|
+
return;
|
|
91
|
+
list.innerHTML = "";
|
|
92
|
+
if (!state.enabled) {
|
|
93
|
+
const hint = document.createElement("div");
|
|
94
|
+
hint.className = "muted small";
|
|
95
|
+
hint.textContent = "Templates are disabled (templates.enabled=false).";
|
|
96
|
+
list.appendChild(hint);
|
|
97
|
+
}
|
|
98
|
+
if (!state.repos.length) {
|
|
99
|
+
const empty = document.createElement("div");
|
|
100
|
+
empty.className = "muted small";
|
|
101
|
+
empty.textContent = "No template repos configured.";
|
|
102
|
+
list.appendChild(empty);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
for (const repo of state.repos) {
|
|
106
|
+
const row = document.createElement("div");
|
|
107
|
+
row.className = "template-repo-item";
|
|
108
|
+
row.innerHTML = `
|
|
109
|
+
<div class="template-repo-meta">
|
|
110
|
+
<span class="template-repo-id">${repo.id}</span>
|
|
111
|
+
<span class="template-repo-url">${repo.url}</span>
|
|
112
|
+
<span class="muted small">ref: ${repo.default_ref}${repo.trusted ? " · trusted" : ""}</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="template-repo-actions">
|
|
115
|
+
<button class="ghost sm" data-action="edit" data-id="${repo.id}">Edit</button>
|
|
116
|
+
<button class="danger sm" data-action="delete" data-id="${repo.id}">Delete</button>
|
|
117
|
+
</div>
|
|
118
|
+
`;
|
|
119
|
+
list.appendChild(row);
|
|
120
|
+
}
|
|
121
|
+
list.querySelectorAll("button[data-action]").forEach((btn) => {
|
|
122
|
+
btn.addEventListener("click", async () => {
|
|
123
|
+
const action = btn.dataset.action;
|
|
124
|
+
const id = btn.dataset.id;
|
|
125
|
+
if (!action || !id)
|
|
126
|
+
return;
|
|
127
|
+
const repo = state.repos.find((r) => r.id === id);
|
|
128
|
+
if (!repo)
|
|
129
|
+
return;
|
|
130
|
+
if (action === "edit") {
|
|
131
|
+
openEditForm(repo);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (action === "delete") {
|
|
135
|
+
await deleteRepo(id);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export async function loadTemplateRepos() {
|
|
141
|
+
const { list } = els();
|
|
142
|
+
if (!list)
|
|
143
|
+
return;
|
|
144
|
+
try {
|
|
145
|
+
const data = (await api("/api/templates/repos"));
|
|
146
|
+
state.enabled = Boolean(data.enabled);
|
|
147
|
+
state.repos = Array.isArray(data.repos) ? data.repos : [];
|
|
148
|
+
renderRepos();
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
state.enabled = false;
|
|
152
|
+
state.repos = [];
|
|
153
|
+
renderRepos();
|
|
154
|
+
flash(err.message || "Failed to load template repos", "error");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function saveRepo() {
|
|
158
|
+
const { idInput, urlInput, refInput, trustedInput } = els();
|
|
159
|
+
if (!idInput || !urlInput || !refInput || !trustedInput)
|
|
160
|
+
return;
|
|
161
|
+
const id = normalizeRequired(idInput.value, "ID");
|
|
162
|
+
const url = normalizeRequired(urlInput.value, "Git URL");
|
|
163
|
+
const ref = normalizeRequired(refInput.value, "Default ref");
|
|
164
|
+
if (!id || !url || !ref)
|
|
165
|
+
return;
|
|
166
|
+
setBusy(true);
|
|
167
|
+
try {
|
|
168
|
+
if (state.mode === "create") {
|
|
169
|
+
await api("/api/templates/repos", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: { id, url, trusted: Boolean(trustedInput.checked), default_ref: ref },
|
|
172
|
+
});
|
|
173
|
+
flash("Template repo added", "success");
|
|
174
|
+
}
|
|
175
|
+
else if (state.mode === "edit" && state.editId) {
|
|
176
|
+
await api(`/api/templates/repos/${encodeURIComponent(state.editId)}`, {
|
|
177
|
+
method: "PUT",
|
|
178
|
+
body: { url, trusted: Boolean(trustedInput.checked), default_ref: ref },
|
|
179
|
+
});
|
|
180
|
+
flash("Template repo updated", "success");
|
|
181
|
+
}
|
|
182
|
+
await loadTemplateRepos();
|
|
183
|
+
await checkTemplatesEnabled();
|
|
184
|
+
showForm(false);
|
|
185
|
+
resetForm();
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
flash(err.message || "Failed to save template repo", "error");
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
setBusy(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function deleteRepo(id) {
|
|
195
|
+
const confirmed = await confirmModal(`Delete template repo "${id}"?`, {
|
|
196
|
+
confirmText: "Delete",
|
|
197
|
+
danger: true,
|
|
198
|
+
});
|
|
199
|
+
if (!confirmed)
|
|
200
|
+
return;
|
|
201
|
+
setBusy(true);
|
|
202
|
+
try {
|
|
203
|
+
await api(`/api/templates/repos/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
204
|
+
flash("Template repo deleted", "success");
|
|
205
|
+
await loadTemplateRepos();
|
|
206
|
+
await checkTemplatesEnabled();
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
flash(err.message || "Failed to delete template repo", "error");
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
setBusy(false);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
export function initTemplateReposSettings() {
|
|
216
|
+
const { list, addBtn, saveBtn, cancelBtn } = els();
|
|
217
|
+
if (!list || !addBtn || !saveBtn || !cancelBtn)
|
|
218
|
+
return;
|
|
219
|
+
addBtn.addEventListener("click", () => openCreateForm());
|
|
220
|
+
saveBtn.addEventListener("click", () => void saveRepo());
|
|
221
|
+
cancelBtn.addEventListener("click", () => {
|
|
222
|
+
showForm(false);
|
|
223
|
+
resetForm();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
2
|
import { TerminalManager } from "./terminalManager.js";
|
|
3
|
+
import { refreshAgentControls } from "./agentControls.js";
|
|
4
|
+
import { subscribe } from "./bus.js";
|
|
5
|
+
import { isRepoHealthy } from "./health.js";
|
|
3
6
|
let terminalManager = null;
|
|
7
|
+
let terminalHealthRefreshInitialized = false;
|
|
4
8
|
export function getTerminalManager() {
|
|
5
9
|
return terminalManager;
|
|
6
10
|
}
|
|
@@ -15,6 +19,7 @@ export function initTerminal() {
|
|
|
15
19
|
}
|
|
16
20
|
terminalManager = new TerminalManager();
|
|
17
21
|
terminalManager.init();
|
|
22
|
+
initTerminalHealthRefresh();
|
|
18
23
|
// Ensure terminal is resized to fit container after initialization
|
|
19
24
|
if (terminalManager) {
|
|
20
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -24,3 +29,16 @@ export function initTerminal() {
|
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
}
|
|
32
|
+
function initTerminalHealthRefresh() {
|
|
33
|
+
if (terminalHealthRefreshInitialized)
|
|
34
|
+
return;
|
|
35
|
+
terminalHealthRefreshInitialized = true;
|
|
36
|
+
subscribe("repo:health", (payload) => {
|
|
37
|
+
const status = payload?.status || "";
|
|
38
|
+
if (status !== "ok" && status !== "degraded")
|
|
39
|
+
return;
|
|
40
|
+
if (!isRepoHealthy())
|
|
41
|
+
return;
|
|
42
|
+
void refreshAgentControls({ reason: "background" });
|
|
43
|
+
});
|
|
44
|
+
}
|