codex-autorunner 1.2.0__py3-none-any.whl → 1.3.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/bootstrap.py +26 -5
- codex_autorunner/core/about_car.py +12 -12
- codex_autorunner/core/config.py +178 -61
- codex_autorunner/core/context_awareness.py +1 -0
- codex_autorunner/core/filesystem.py +24 -0
- codex_autorunner/core/flows/controller.py +50 -12
- codex_autorunner/core/flows/runtime.py +8 -3
- codex_autorunner/core/hub.py +293 -16
- codex_autorunner/core/lifecycle_events.py +44 -5
- codex_autorunner/core/pma_context.py +188 -1
- codex_autorunner/core/pma_delivery.py +81 -0
- codex_autorunner/core/pma_dispatches.py +224 -0
- codex_autorunner/core/pma_lane_worker.py +122 -0
- codex_autorunner/core/pma_queue.py +167 -18
- codex_autorunner/core/pma_reactive.py +91 -0
- codex_autorunner/core/pma_safety.py +58 -0
- codex_autorunner/core/pma_sink.py +104 -0
- codex_autorunner/core/pma_transcripts.py +183 -0
- codex_autorunner/core/safe_paths.py +117 -0
- codex_autorunner/housekeeping.py +77 -23
- codex_autorunner/integrations/agents/codex_backend.py +18 -12
- codex_autorunner/integrations/agents/wiring.py +2 -0
- codex_autorunner/integrations/app_server/client.py +31 -0
- codex_autorunner/integrations/app_server/supervisor.py +3 -0
- codex_autorunner/integrations/telegram/adapter.py +1 -1
- codex_autorunner/integrations/telegram/config.py +1 -1
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +16 -15
- codex_autorunner/integrations/telegram/handlers/commands/files.py +5 -8
- codex_autorunner/integrations/telegram/handlers/commands/github.py +10 -6
- codex_autorunner/integrations/telegram/handlers/commands/shared.py +9 -8
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +85 -2
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +29 -8
- codex_autorunner/integrations/telegram/handlers/messages.py +8 -2
- codex_autorunner/integrations/telegram/helpers.py +30 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +54 -3
- codex_autorunner/static/archive.js +274 -81
- codex_autorunner/static/archiveApi.js +21 -0
- codex_autorunner/static/constants.js +1 -1
- codex_autorunner/static/docChatCore.js +2 -0
- codex_autorunner/static/hub.js +59 -0
- codex_autorunner/static/index.html +70 -54
- codex_autorunner/static/notificationBell.js +173 -0
- codex_autorunner/static/notifications.js +187 -36
- codex_autorunner/static/pma.js +96 -35
- codex_autorunner/static/styles.css +431 -4
- codex_autorunner/static/terminalManager.js +22 -3
- codex_autorunner/static/utils.js +5 -1
- codex_autorunner/surfaces/cli/cli.py +206 -129
- codex_autorunner/surfaces/cli/template_repos.py +157 -0
- codex_autorunner/surfaces/web/app.py +193 -5
- codex_autorunner/surfaces/web/routes/archive.py +197 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +115 -87
- codex_autorunner/surfaces/web/routes/flows.py +125 -67
- codex_autorunner/surfaces/web/routes/pma.py +638 -57
- codex_autorunner/surfaces/web/schemas.py +11 -0
- codex_autorunner/tickets/agent_pool.py +6 -1
- codex_autorunner/tickets/outbox.py +27 -14
- codex_autorunner/tickets/replies.py +4 -10
- codex_autorunner/tickets/runner.py +1 -0
- codex_autorunner/workspace/paths.py +8 -3
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/METADATA +1 -1
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/RECORD +67 -57
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/top_level.txt +0 -0
codex_autorunner/static/hub.js
CHANGED
|
@@ -3,8 +3,10 @@ import { api, flash, statusPill, resolvePath, escapeHtml, confirmModal, inputMod
|
|
|
3
3
|
import { registerAutoRefresh } from "./autoRefresh.js";
|
|
4
4
|
import { HUB_BASE } from "./env.js";
|
|
5
5
|
import { preserveScroll } from "./preserve.js";
|
|
6
|
+
import { initNotificationBell } from "./notificationBell.js";
|
|
6
7
|
let hubData = { repos: [], last_scan_at: null };
|
|
7
8
|
const prefetchedUrls = new Set();
|
|
9
|
+
let hubInboxHydrated = false;
|
|
8
10
|
const HUB_CACHE_TTL_MS = 30000;
|
|
9
11
|
const HUB_CACHE_KEY = `car:hub:${HUB_BASE || "/"}`;
|
|
10
12
|
const HUB_USAGE_CACHE_KEY = `car:hub-usage:${HUB_BASE || "/"}`;
|
|
@@ -24,6 +26,8 @@ const hubUsageChartRange = document.getElementById("hub-usage-chart-range");
|
|
|
24
26
|
const hubUsageChartSegment = document.getElementById("hub-usage-chart-segment");
|
|
25
27
|
const hubVersionEl = document.getElementById("hub-version");
|
|
26
28
|
const pmaVersionEl = document.getElementById("pma-version");
|
|
29
|
+
const hubInboxList = document.getElementById("hub-inbox-list");
|
|
30
|
+
const hubInboxRefresh = document.getElementById("hub-inbox-refresh");
|
|
27
31
|
const UPDATE_STATUS_SEEN_KEY = "car_update_status_seen";
|
|
28
32
|
const HUB_JOB_POLL_INTERVAL_MS = 1200;
|
|
29
33
|
const HUB_JOB_TIMEOUT_MS = 180000;
|
|
@@ -83,7 +87,9 @@ function formatLastActivity(repo) {
|
|
|
83
87
|
}
|
|
84
88
|
function setButtonLoading(scanning) {
|
|
85
89
|
const buttons = [
|
|
90
|
+
document.getElementById("hub-scan"),
|
|
86
91
|
document.getElementById("hub-quick-scan"),
|
|
92
|
+
document.getElementById("hub-refresh"),
|
|
87
93
|
];
|
|
88
94
|
buttons.forEach((btn) => {
|
|
89
95
|
if (!btn)
|
|
@@ -915,6 +921,7 @@ async function refreshHub() {
|
|
|
915
921
|
saveSessionCache(HUB_CACHE_KEY, hubData);
|
|
916
922
|
renderSummary(data.repos || []);
|
|
917
923
|
renderReposWithScroll(data.repos || []);
|
|
924
|
+
await loadHubInbox().catch(() => { });
|
|
918
925
|
loadHubUsage({ silent: true }).catch(() => { });
|
|
919
926
|
}
|
|
920
927
|
catch (err) {
|
|
@@ -924,6 +931,46 @@ async function refreshHub() {
|
|
|
924
931
|
setButtonLoading(false);
|
|
925
932
|
}
|
|
926
933
|
}
|
|
934
|
+
async function loadHubInbox(ctx) {
|
|
935
|
+
if (!hubInboxList)
|
|
936
|
+
return;
|
|
937
|
+
if (!hubInboxHydrated || ctx?.reason === "manual") {
|
|
938
|
+
hubInboxList.textContent = "Loading…";
|
|
939
|
+
}
|
|
940
|
+
try {
|
|
941
|
+
const payload = (await api("/hub/messages", { method: "GET" }));
|
|
942
|
+
const items = payload?.items || [];
|
|
943
|
+
const html = !items.length
|
|
944
|
+
? '<div class="muted">No paused runs</div>'
|
|
945
|
+
: items
|
|
946
|
+
.map((item) => {
|
|
947
|
+
const title = item.message?.title || item.message?.mode || "Message";
|
|
948
|
+
const excerpt = item.message?.body ? item.message.body.slice(0, 160) : "";
|
|
949
|
+
const repoLabel = item.repo_display_name || item.repo_id;
|
|
950
|
+
const href = item.open_url || `/repos/${item.repo_id}/?tab=messages&run_id=${item.run_id}`;
|
|
951
|
+
return `
|
|
952
|
+
<a class="hub-inbox-item" href="${escapeHtml(resolvePath(href))}">
|
|
953
|
+
<div class="hub-inbox-item-header">
|
|
954
|
+
<span class="hub-inbox-repo">${escapeHtml(repoLabel)}</span>
|
|
955
|
+
<span class="pill pill-small pill-warn">paused</span>
|
|
956
|
+
</div>
|
|
957
|
+
<div class="hub-inbox-title">${escapeHtml(title)}</div>
|
|
958
|
+
<div class="hub-inbox-excerpt muted small">${escapeHtml(excerpt)}</div>
|
|
959
|
+
</a>
|
|
960
|
+
`;
|
|
961
|
+
})
|
|
962
|
+
.join("");
|
|
963
|
+
preserveScroll(hubInboxList, () => {
|
|
964
|
+
hubInboxList.innerHTML = html;
|
|
965
|
+
}, { restoreOnNextFrame: true });
|
|
966
|
+
hubInboxHydrated = true;
|
|
967
|
+
}
|
|
968
|
+
catch (_err) {
|
|
969
|
+
preserveScroll(hubInboxList, () => {
|
|
970
|
+
hubInboxList.innerHTML = "";
|
|
971
|
+
}, { restoreOnNextFrame: true });
|
|
972
|
+
}
|
|
973
|
+
}
|
|
927
974
|
async function triggerHubScan() {
|
|
928
975
|
setButtonLoading(true);
|
|
929
976
|
try {
|
|
@@ -1138,14 +1185,22 @@ async function handleRepoAction(repoId, action) {
|
|
|
1138
1185
|
}
|
|
1139
1186
|
function attachHubHandlers() {
|
|
1140
1187
|
initHubSettings();
|
|
1188
|
+
const scanBtn = document.getElementById("hub-scan");
|
|
1189
|
+
const refreshBtn = document.getElementById("hub-refresh");
|
|
1141
1190
|
const quickScanBtn = document.getElementById("hub-quick-scan");
|
|
1142
1191
|
const newRepoBtn = document.getElementById("hub-new-repo");
|
|
1143
1192
|
const createCancelBtn = document.getElementById("create-repo-cancel");
|
|
1144
1193
|
const createSubmitBtn = document.getElementById("create-repo-submit");
|
|
1145
1194
|
const createRepoId = document.getElementById("create-repo-id");
|
|
1195
|
+
if (scanBtn) {
|
|
1196
|
+
scanBtn.addEventListener("click", () => triggerHubScan());
|
|
1197
|
+
}
|
|
1146
1198
|
if (quickScanBtn) {
|
|
1147
1199
|
quickScanBtn.addEventListener("click", () => triggerHubScan());
|
|
1148
1200
|
}
|
|
1201
|
+
if (refreshBtn) {
|
|
1202
|
+
refreshBtn.addEventListener("click", () => refreshHub());
|
|
1203
|
+
}
|
|
1149
1204
|
if (hubUsageRefresh) {
|
|
1150
1205
|
hubUsageRefresh.addEventListener("click", () => loadHubUsage());
|
|
1151
1206
|
}
|
|
@@ -1290,6 +1345,10 @@ export function initHub() {
|
|
|
1290
1345
|
return;
|
|
1291
1346
|
attachHubHandlers();
|
|
1292
1347
|
initHubUsageChartControls();
|
|
1348
|
+
initNotificationBell();
|
|
1349
|
+
hubInboxRefresh?.addEventListener("click", () => {
|
|
1350
|
+
void loadHubInbox({ reason: "manual" });
|
|
1351
|
+
});
|
|
1293
1352
|
const cachedHub = loadSessionCache(HUB_CACHE_KEY, HUB_CACHE_TTL_MS);
|
|
1294
1353
|
if (cachedHub) {
|
|
1295
1354
|
hubData = cachedHub;
|
|
@@ -23,15 +23,15 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
<div class="hub-hero-actions">
|
|
25
25
|
<button class="primary sm" id="hub-new-repo">+ New</button>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</
|
|
26
|
+
<button class="sm" id="hub-scan">Scan</button>
|
|
27
|
+
<button class="ghost sm" id="hub-refresh">Refresh</button>
|
|
28
|
+
<button class="ghost sm icon-btn notification-bell" id="hub-notification-bell" title="Dispatches">
|
|
29
|
+
<svg class="notification-bell-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
30
|
+
<path d="M6 9.5a6 6 0 0 1 12 0v3.5l1.6 2.2H4.4L6 13z" />
|
|
31
|
+
<path d="M9.8 18.2a2.2 2.2 0 0 0 4.4 0" />
|
|
32
|
+
</svg>
|
|
33
|
+
<span class="notification-badge hidden"></span>
|
|
34
|
+
</button>
|
|
35
35
|
<button class="ghost sm icon-btn" id="hub-settings" title="Settings">⚙</button>
|
|
36
36
|
</div>
|
|
37
37
|
<div class="hub-mode-toggle" role="tablist" aria-label="Hub mode">
|
|
@@ -57,6 +57,15 @@
|
|
|
57
57
|
<p class="muted small">missing</p>
|
|
58
58
|
</div>
|
|
59
59
|
</section>
|
|
60
|
+
<section class="hub-inbox">
|
|
61
|
+
<div class="hub-panel-header">
|
|
62
|
+
<span class="label">Inbox</span>
|
|
63
|
+
<div class="hub-panel-actions">
|
|
64
|
+
<button class="ghost sm" id="hub-inbox-refresh">Refresh</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="hub-inbox-list" id="hub-inbox-list">Loading…</div>
|
|
68
|
+
</section>
|
|
60
69
|
<section class="hub-usage-chart">
|
|
61
70
|
<div class="hub-usage-chart-header">
|
|
62
71
|
<span class="label">Usage Trend</span>
|
|
@@ -89,7 +98,7 @@
|
|
|
89
98
|
</div>
|
|
90
99
|
</section>
|
|
91
100
|
</div>
|
|
92
|
-
<div class="hub-shell hidden" id="pma-shell">
|
|
101
|
+
<div class="hub-shell hidden" id="pma-shell" data-pma-view="chat">
|
|
93
102
|
<header class="hub-hero pma-hero">
|
|
94
103
|
<div class="hub-hero-text pma-hero-text">
|
|
95
104
|
<h1>Project Manager</h1>
|
|
@@ -97,15 +106,13 @@
|
|
|
97
106
|
<span class="hub-version" id="pma-version">v–</span>
|
|
98
107
|
</div>
|
|
99
108
|
<div class="hub-hero-actions pma-hero-actions">
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
aria-label="Pending dispatches"></div>
|
|
108
|
-
</div>
|
|
109
|
+
<button class="ghost sm icon-btn notification-bell" id="pma-notification-bell" title="Dispatches">
|
|
110
|
+
<svg class="notification-bell-icon" viewBox="0 0 24 24" aria-hidden="true">
|
|
111
|
+
<path d="M6 9.5a6 6 0 0 1 12 0v3.5l1.6 2.2H4.4L6 13z" />
|
|
112
|
+
<path d="M9.8 18.2a2.2 2.2 0 0 0 4.4 0" />
|
|
113
|
+
</svg>
|
|
114
|
+
<span class="notification-badge hidden"></span>
|
|
115
|
+
</button>
|
|
109
116
|
<button class="ghost sm icon-btn" id="pma-settings" title="Settings">⚙</button>
|
|
110
117
|
</div>
|
|
111
118
|
<div class="hub-mode-toggle" role="tablist" aria-label="Hub mode">
|
|
@@ -123,42 +130,44 @@
|
|
|
123
130
|
<select id="pma-chat-agent-select" title="Agent"></select>
|
|
124
131
|
<select id="pma-chat-model-select" title="Model"></select>
|
|
125
132
|
<select id="pma-chat-reasoning-select" title="Reasoning"></select>
|
|
133
|
+
<div class="pma-view-toggle" role="tablist" aria-label="PMA view">
|
|
134
|
+
<button class="pma-view-btn active" data-view="chat" role="tab" aria-selected="true" type="button">Chat</button>
|
|
135
|
+
<button class="pma-view-btn" data-view="memory" role="tab" aria-selected="false" type="button">Memory</button>
|
|
136
|
+
</div>
|
|
126
137
|
</div>
|
|
127
138
|
<div class="pma-chat-actions">
|
|
139
|
+
<button class="ghost sm icon-btn" id="pma-scan-repos-btn" title="Rescan hub repositories">↻</button>
|
|
128
140
|
<button class="ghost sm hidden" id="pma-chat-cancel" title="Cancel">✕</button>
|
|
129
141
|
<button class="pma-new-thread-btn" id="pma-chat-new-thread" title="Start a new chat thread">New
|
|
130
142
|
thread</button>
|
|
131
143
|
<span class="pma-status-pill" id="pma-chat-status">idle</span>
|
|
132
144
|
</div>
|
|
133
145
|
</div>
|
|
134
|
-
<div class="pma-thread-info hidden" id="pma-thread-info">
|
|
135
|
-
<div class="pma-thread-info-header">
|
|
136
|
-
<span class="pma-thread-info-label">Thread</span>
|
|
137
|
-
<span class="pill pill-small pill-idle" id="pma-thread-info-status">idle</span>
|
|
138
|
-
</div>
|
|
139
|
-
<div class="pma-thread-info-details">
|
|
140
|
-
<div class="pma-thread-info-row">
|
|
141
|
-
<span class="pma-thread-info-key muted">Agent</span>
|
|
142
|
-
<span class="pma-thread-info-value" id="pma-thread-info-agent">–</span>
|
|
143
|
-
</div>
|
|
144
|
-
<div class="pma-thread-info-row">
|
|
145
|
-
<span class="pma-thread-info-key muted">Thread ID</span>
|
|
146
|
-
<span class="pma-thread-info-value" id="pma-thread-info-thread-id" title="Click to copy">–</span>
|
|
147
|
-
</div>
|
|
148
|
-
<div class="pma-thread-info-row">
|
|
149
|
-
<span class="pma-thread-info-key muted">Turn ID</span>
|
|
150
|
-
<span class="pma-thread-info-value" id="pma-thread-info-turn-id" title="Click to copy">–</span>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="pma-repo-actions" id="pma-repo-actions">
|
|
155
|
-
<button class="ghost sm" id="pma-scan-repos-btn">Scan repos</button>
|
|
156
|
-
</div>
|
|
157
146
|
</section>
|
|
158
|
-
<section class="pma-chat-section">
|
|
147
|
+
<section class="pma-chat-section" id="pma-chat-section">
|
|
159
148
|
<div class="pma-chat-main">
|
|
160
149
|
<div class="pma-chat-stream" id="pma-chat-stream">
|
|
161
150
|
<div class="pma-chat-error hidden" id="pma-chat-error"></div>
|
|
151
|
+
<div class="pma-thread-info hidden" id="pma-thread-info">
|
|
152
|
+
<div class="pma-thread-info-header">
|
|
153
|
+
<span class="pma-thread-info-label">Thread</span>
|
|
154
|
+
<span class="pill pill-small pill-idle" id="pma-thread-info-status">idle</span>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="pma-thread-info-details">
|
|
157
|
+
<div class="pma-thread-info-row">
|
|
158
|
+
<span class="pma-thread-info-key muted">Agent</span>
|
|
159
|
+
<span class="pma-thread-info-value" id="pma-thread-info-agent">–</span>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="pma-thread-info-row">
|
|
162
|
+
<span class="pma-thread-info-key muted">Thread ID</span>
|
|
163
|
+
<span class="pma-thread-info-value" id="pma-thread-info-thread-id" title="Click to copy">–</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="pma-thread-info-row">
|
|
166
|
+
<span class="pma-thread-info-key muted">Turn ID</span>
|
|
167
|
+
<span class="pma-thread-info-value" id="pma-thread-info-turn-id" title="Click to copy">–</span>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
162
171
|
<div class="pma-history-header" id="pma-chat-history-header">
|
|
163
172
|
<span class="pma-history-label">History</span>
|
|
164
173
|
</div>
|
|
@@ -176,10 +185,12 @@
|
|
|
176
185
|
<div class="pma-attachments-area" id="pma-attachments-area">
|
|
177
186
|
<div class="pma-files-row">
|
|
178
187
|
<span class="pma-files-label">In</span>
|
|
188
|
+
<button class="pma-icon-btn-small hidden pma-clear-btn" id="pma-inbox-clear" title="Clear inbox">🗑</button>
|
|
179
189
|
<div class="pma-file-list filebox-list" id="pma-inbox-files"></div>
|
|
180
190
|
</div>
|
|
181
191
|
<div class="pma-files-row">
|
|
182
192
|
<span class="pma-files-label">Out</span>
|
|
193
|
+
<button class="pma-icon-btn-small hidden pma-clear-btn" id="pma-outbox-clear" title="Clear outbox">🗑</button>
|
|
183
194
|
<div class="pma-file-list filebox-list" id="pma-outbox-files"></div>
|
|
184
195
|
<button class="pma-icon-btn-small" id="pma-outbox-refresh" title="Refresh files">↻</button>
|
|
185
196
|
</div>
|
|
@@ -1042,6 +1053,22 @@
|
|
|
1042
1053
|
</div>
|
|
1043
1054
|
</div>
|
|
1044
1055
|
</div>
|
|
1056
|
+
<!-- Dispatch Notifications Modal -->
|
|
1057
|
+
<div class="modal-overlay" hidden="" id="notification-modal">
|
|
1058
|
+
<div aria-describedby="notification-modal-body" aria-labelledby="notification-modal-title" aria-modal="true"
|
|
1059
|
+
class="modal-dialog notification-dialog" role="dialog" tabindex="-1">
|
|
1060
|
+
<div class="modal-header notification-modal-header">
|
|
1061
|
+
<span class="label" id="notification-modal-title">Dispatches</span>
|
|
1062
|
+
<div class="notification-modal-actions">
|
|
1063
|
+
<button class="ghost sm" id="notification-refresh">Refresh</button>
|
|
1064
|
+
<button class="ghost sm icon-btn" id="notification-close" title="Close">✕</button>
|
|
1065
|
+
</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="modal-body" id="notification-modal-body">
|
|
1068
|
+
<div class="notification-list" id="notification-list">Loading…</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
</div>
|
|
1045
1072
|
<!-- Repo Settings Modal -->
|
|
1046
1073
|
<div class="modal-overlay" hidden="" id="repo-settings-modal">
|
|
1047
1074
|
<div aria-describedby="repo-settings-modal-body" aria-labelledby="repo-settings-modal-title" aria-modal="true"
|
|
@@ -1153,17 +1180,6 @@
|
|
|
1153
1180
|
</div>
|
|
1154
1181
|
</div>
|
|
1155
1182
|
</div>
|
|
1156
|
-
<!-- Notifications Modal -->
|
|
1157
|
-
<div class="modal-overlay hidden" id="notifications-modal">
|
|
1158
|
-
<div aria-labelledby="notifications-modal-title" aria-modal="true" class="modal-dialog notifications-modal-dialog"
|
|
1159
|
-
role="dialog" tabindex="-1">
|
|
1160
|
-
<div class="notifications-modal-header">
|
|
1161
|
-
<span class="label" id="notifications-modal-title">Pending dispatch</span>
|
|
1162
|
-
<button class="ghost sm icon-btn" id="notifications-modal-close" title="Close">×</button>
|
|
1163
|
-
</div>
|
|
1164
|
-
<div class="notifications-modal-body-wrapper" id="notifications-modal-body"></div>
|
|
1165
|
-
</div>
|
|
1166
|
-
</div>
|
|
1167
1183
|
<!-- Reason Details Modal -->
|
|
1168
1184
|
<div class="modal-overlay" hidden="" id="reason-modal">
|
|
1169
1185
|
<div aria-describedby="reason-modal-content" aria-labelledby="reason-modal-title" aria-modal="true"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { api, escapeHtml, flash, openModal, resolvePath } from "./utils.js";
|
|
3
|
+
let bellInitialized = false;
|
|
4
|
+
let modalOpen = false;
|
|
5
|
+
let closeModal = null;
|
|
6
|
+
function getBellButtons() {
|
|
7
|
+
return Array.from(document.querySelectorAll(".notification-bell"));
|
|
8
|
+
}
|
|
9
|
+
function setBadges(count) {
|
|
10
|
+
getBellButtons().forEach((btn) => {
|
|
11
|
+
const badge = btn.querySelector(".notification-badge");
|
|
12
|
+
if (!badge)
|
|
13
|
+
return;
|
|
14
|
+
if (count > 0) {
|
|
15
|
+
badge.textContent = String(count);
|
|
16
|
+
badge.classList.remove("hidden");
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
badge.textContent = "";
|
|
20
|
+
badge.classList.add("hidden");
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function itemTitle(item) {
|
|
25
|
+
const payload = item.dispatch || item.message || {};
|
|
26
|
+
return payload.title || payload.mode || "Message";
|
|
27
|
+
}
|
|
28
|
+
function itemBody(item) {
|
|
29
|
+
const payload = item.dispatch || item.message || {};
|
|
30
|
+
return payload.body || "";
|
|
31
|
+
}
|
|
32
|
+
function renderList(items) {
|
|
33
|
+
const listEl = document.getElementById("notification-list");
|
|
34
|
+
if (!listEl)
|
|
35
|
+
return;
|
|
36
|
+
if (!items.length) {
|
|
37
|
+
listEl.innerHTML = '<div class="muted">No dispatches</div>';
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const html = items
|
|
41
|
+
.map((item) => {
|
|
42
|
+
const title = itemTitle(item);
|
|
43
|
+
const excerpt = itemBody(item).slice(0, 180);
|
|
44
|
+
const repoLabel = item.repo_display_name || item.repo_id;
|
|
45
|
+
const href = item.open_url || `/repos/${item.repo_id}/?tab=inbox&run_id=${item.run_id}`;
|
|
46
|
+
const seq = item.seq ? `#${item.seq}` : "";
|
|
47
|
+
return `
|
|
48
|
+
<div class="notification-item">
|
|
49
|
+
<div class="notification-item-header">
|
|
50
|
+
<span class="notification-repo">${escapeHtml(repoLabel)} <span class="muted">(${item.run_id.slice(0, 8)}${seq})</span></span>
|
|
51
|
+
<span class="pill pill-small pill-warn">paused</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="notification-title">${escapeHtml(title)}</div>
|
|
54
|
+
<div class="notification-excerpt">${escapeHtml(excerpt)}</div>
|
|
55
|
+
<div class="notification-actions">
|
|
56
|
+
<a class="notification-action" href="${escapeHtml(resolvePath(href))}">Open run</a>
|
|
57
|
+
<button class="notification-action" data-action="copy-run-id" data-run-id="${escapeHtml(item.run_id)}">Copy ID</button>
|
|
58
|
+
${item.repo_id ? `<button class="notification-action" data-action="copy-repo-id" data-repo-id="${escapeHtml(item.repo_id)}">Copy repo</button>` : ""}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
`;
|
|
62
|
+
})
|
|
63
|
+
.join("");
|
|
64
|
+
listEl.innerHTML = html;
|
|
65
|
+
}
|
|
66
|
+
async function fetchNotifications() {
|
|
67
|
+
const payload = (await api("/hub/messages", { method: "GET" }));
|
|
68
|
+
return payload?.items || [];
|
|
69
|
+
}
|
|
70
|
+
async function refreshNotifications(options = {}) {
|
|
71
|
+
const { silent = true, render = false } = options;
|
|
72
|
+
try {
|
|
73
|
+
const items = await fetchNotifications();
|
|
74
|
+
setBadges(items.length);
|
|
75
|
+
if (modalOpen || render) {
|
|
76
|
+
renderList(items);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (!silent) {
|
|
81
|
+
flash(err.message || "Failed to load dispatches", "error");
|
|
82
|
+
}
|
|
83
|
+
setBadges(0);
|
|
84
|
+
if (modalOpen || render) {
|
|
85
|
+
renderList([]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function openNotificationsModal() {
|
|
90
|
+
const modal = document.getElementById("notification-modal");
|
|
91
|
+
const closeBtn = document.getElementById("notification-close");
|
|
92
|
+
if (!modal)
|
|
93
|
+
return;
|
|
94
|
+
if (closeModal)
|
|
95
|
+
closeModal();
|
|
96
|
+
closeModal = openModal(modal, {
|
|
97
|
+
initialFocus: closeBtn || modal,
|
|
98
|
+
onRequestClose: () => {
|
|
99
|
+
modalOpen = false;
|
|
100
|
+
if (closeModal) {
|
|
101
|
+
const close = closeModal;
|
|
102
|
+
closeModal = null;
|
|
103
|
+
close();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
modalOpen = true;
|
|
108
|
+
void refreshNotifications({ render: true, silent: true });
|
|
109
|
+
}
|
|
110
|
+
function attachModalHandlers() {
|
|
111
|
+
const modal = document.getElementById("notification-modal");
|
|
112
|
+
if (!modal)
|
|
113
|
+
return;
|
|
114
|
+
const closeBtn = document.getElementById("notification-close");
|
|
115
|
+
const refreshBtn = document.getElementById("notification-refresh");
|
|
116
|
+
closeBtn?.addEventListener("click", () => {
|
|
117
|
+
if (closeModal) {
|
|
118
|
+
const close = closeModal;
|
|
119
|
+
closeModal = null;
|
|
120
|
+
modalOpen = false;
|
|
121
|
+
close();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
refreshBtn?.addEventListener("click", () => {
|
|
125
|
+
void refreshNotifications({ render: true, silent: false });
|
|
126
|
+
});
|
|
127
|
+
const listEl = document.getElementById("notification-list");
|
|
128
|
+
listEl?.addEventListener("click", (event) => {
|
|
129
|
+
const target = event.target;
|
|
130
|
+
if (!target)
|
|
131
|
+
return;
|
|
132
|
+
const action = target.dataset.action || "";
|
|
133
|
+
if (action === "copy-run-id") {
|
|
134
|
+
const runId = target.dataset.runId || "";
|
|
135
|
+
if (runId) {
|
|
136
|
+
void navigator.clipboard.writeText(runId).then(() => {
|
|
137
|
+
flash("Copied run ID", "info");
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (action === "copy-repo-id") {
|
|
142
|
+
const repoId = target.dataset.repoId || "";
|
|
143
|
+
if (repoId) {
|
|
144
|
+
void navigator.clipboard.writeText(repoId).then(() => {
|
|
145
|
+
flash("Copied repo ID", "info");
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export function initNotificationBell() {
|
|
152
|
+
if (bellInitialized)
|
|
153
|
+
return;
|
|
154
|
+
bellInitialized = true;
|
|
155
|
+
const buttons = getBellButtons();
|
|
156
|
+
if (!buttons.length)
|
|
157
|
+
return;
|
|
158
|
+
buttons.forEach((btn) => {
|
|
159
|
+
btn.addEventListener("click", () => {
|
|
160
|
+
openNotificationsModal();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
attachModalHandlers();
|
|
164
|
+
void refreshNotifications({ render: false, silent: true });
|
|
165
|
+
window.setInterval(() => {
|
|
166
|
+
if (document.hidden)
|
|
167
|
+
return;
|
|
168
|
+
void refreshNotifications({ render: false, silent: true });
|
|
169
|
+
}, 15000);
|
|
170
|
+
}
|
|
171
|
+
export const __notificationBellTest = {
|
|
172
|
+
refreshNotifications,
|
|
173
|
+
};
|