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.
Files changed (227) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/agents/codex/harness.py +1 -1
  3. codex_autorunner/agents/opencode/client.py +113 -4
  4. codex_autorunner/agents/opencode/constants.py +3 -0
  5. codex_autorunner/agents/opencode/harness.py +6 -1
  6. codex_autorunner/agents/opencode/runtime.py +59 -18
  7. codex_autorunner/agents/opencode/supervisor.py +4 -0
  8. codex_autorunner/agents/registry.py +36 -7
  9. codex_autorunner/bootstrap.py +226 -4
  10. codex_autorunner/cli.py +5 -1174
  11. codex_autorunner/codex_cli.py +20 -84
  12. codex_autorunner/core/__init__.py +20 -0
  13. codex_autorunner/core/about_car.py +119 -1
  14. codex_autorunner/core/app_server_ids.py +59 -0
  15. codex_autorunner/core/app_server_threads.py +17 -2
  16. codex_autorunner/core/app_server_utils.py +165 -0
  17. codex_autorunner/core/archive.py +349 -0
  18. codex_autorunner/core/codex_runner.py +6 -2
  19. codex_autorunner/core/config.py +433 -4
  20. codex_autorunner/core/context_awareness.py +38 -0
  21. codex_autorunner/core/docs.py +0 -122
  22. codex_autorunner/core/drafts.py +58 -4
  23. codex_autorunner/core/exceptions.py +4 -0
  24. codex_autorunner/core/filebox.py +265 -0
  25. codex_autorunner/core/flows/controller.py +96 -2
  26. codex_autorunner/core/flows/models.py +13 -0
  27. codex_autorunner/core/flows/reasons.py +52 -0
  28. codex_autorunner/core/flows/reconciler.py +134 -0
  29. codex_autorunner/core/flows/runtime.py +57 -4
  30. codex_autorunner/core/flows/store.py +142 -7
  31. codex_autorunner/core/flows/transition.py +27 -15
  32. codex_autorunner/core/flows/ux_helpers.py +272 -0
  33. codex_autorunner/core/flows/worker_process.py +32 -6
  34. codex_autorunner/core/git_utils.py +62 -0
  35. codex_autorunner/core/hub.py +291 -20
  36. codex_autorunner/core/lifecycle_events.py +253 -0
  37. codex_autorunner/core/notifications.py +14 -2
  38. codex_autorunner/core/path_utils.py +2 -1
  39. codex_autorunner/core/pma_audit.py +224 -0
  40. codex_autorunner/core/pma_context.py +496 -0
  41. codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
  42. codex_autorunner/core/pma_lifecycle.py +527 -0
  43. codex_autorunner/core/pma_queue.py +367 -0
  44. codex_autorunner/core/pma_safety.py +221 -0
  45. codex_autorunner/core/pma_state.py +115 -0
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/{integrations/agents → core/ports}/agent_backend.py +13 -8
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/{integrations/agents → core/ports}/run_event.py +23 -6
  50. codex_autorunner/core/prompt.py +0 -80
  51. codex_autorunner/core/prompts.py +56 -172
  52. codex_autorunner/core/redaction.py +0 -4
  53. codex_autorunner/core/review_context.py +11 -9
  54. codex_autorunner/core/runner_controller.py +35 -33
  55. codex_autorunner/core/runner_state.py +147 -0
  56. codex_autorunner/core/runtime.py +829 -0
  57. codex_autorunner/core/sqlite_utils.py +13 -4
  58. codex_autorunner/core/state.py +7 -10
  59. codex_autorunner/core/state_roots.py +62 -0
  60. codex_autorunner/core/supervisor_protocol.py +15 -0
  61. codex_autorunner/core/templates/__init__.py +39 -0
  62. codex_autorunner/core/templates/git_mirror.py +234 -0
  63. codex_autorunner/core/templates/provenance.py +56 -0
  64. codex_autorunner/core/templates/scan_cache.py +120 -0
  65. codex_autorunner/core/text_delta_coalescer.py +54 -0
  66. codex_autorunner/core/ticket_linter_cli.py +218 -0
  67. codex_autorunner/core/ticket_manager_cli.py +494 -0
  68. codex_autorunner/core/time_utils.py +11 -0
  69. codex_autorunner/core/types.py +18 -0
  70. codex_autorunner/core/update.py +4 -5
  71. codex_autorunner/core/update_paths.py +28 -0
  72. codex_autorunner/core/usage.py +164 -12
  73. codex_autorunner/core/utils.py +125 -15
  74. codex_autorunner/flows/review/__init__.py +17 -0
  75. codex_autorunner/{core/review.py → flows/review/service.py} +37 -34
  76. codex_autorunner/flows/ticket_flow/definition.py +52 -3
  77. codex_autorunner/integrations/agents/__init__.py +11 -19
  78. codex_autorunner/integrations/agents/backend_orchestrator.py +302 -0
  79. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  80. codex_autorunner/integrations/agents/codex_backend.py +177 -25
  81. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  82. codex_autorunner/integrations/agents/opencode_backend.py +305 -32
  83. codex_autorunner/integrations/agents/runner.py +86 -0
  84. codex_autorunner/integrations/agents/wiring.py +279 -0
  85. codex_autorunner/integrations/app_server/client.py +7 -60
  86. codex_autorunner/integrations/app_server/env.py +2 -107
  87. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  88. codex_autorunner/integrations/telegram/adapter.py +65 -0
  89. codex_autorunner/integrations/telegram/config.py +46 -0
  90. codex_autorunner/integrations/telegram/constants.py +1 -1
  91. codex_autorunner/integrations/telegram/doctor.py +228 -6
  92. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -0
  93. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  94. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  95. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1496 -71
  96. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  97. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +206 -48
  98. codex_autorunner/integrations/telegram/handlers/commands_spec.py +20 -3
  99. codex_autorunner/integrations/telegram/handlers/messages.py +27 -1
  100. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  101. codex_autorunner/integrations/telegram/helpers.py +22 -1
  102. codex_autorunner/integrations/telegram/runtime.py +9 -4
  103. codex_autorunner/integrations/telegram/service.py +45 -10
  104. codex_autorunner/integrations/telegram/state.py +38 -0
  105. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +338 -43
  106. codex_autorunner/integrations/telegram/transport.py +13 -4
  107. codex_autorunner/integrations/templates/__init__.py +27 -0
  108. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  109. codex_autorunner/routes/__init__.py +37 -76
  110. codex_autorunner/routes/agents.py +2 -137
  111. codex_autorunner/routes/analytics.py +2 -238
  112. codex_autorunner/routes/app_server.py +2 -131
  113. codex_autorunner/routes/base.py +2 -596
  114. codex_autorunner/routes/file_chat.py +4 -833
  115. codex_autorunner/routes/flows.py +4 -977
  116. codex_autorunner/routes/messages.py +4 -456
  117. codex_autorunner/routes/repos.py +2 -196
  118. codex_autorunner/routes/review.py +2 -147
  119. codex_autorunner/routes/sessions.py +2 -175
  120. codex_autorunner/routes/settings.py +2 -168
  121. codex_autorunner/routes/shared.py +2 -275
  122. codex_autorunner/routes/system.py +4 -193
  123. codex_autorunner/routes/usage.py +2 -86
  124. codex_autorunner/routes/voice.py +2 -119
  125. codex_autorunner/routes/workspace.py +2 -270
  126. codex_autorunner/server.py +4 -4
  127. codex_autorunner/static/agentControls.js +61 -16
  128. codex_autorunner/static/app.js +126 -14
  129. codex_autorunner/static/archive.js +826 -0
  130. codex_autorunner/static/archiveApi.js +37 -0
  131. codex_autorunner/static/autoRefresh.js +7 -7
  132. codex_autorunner/static/chatUploads.js +137 -0
  133. codex_autorunner/static/dashboard.js +224 -171
  134. codex_autorunner/static/docChatCore.js +185 -13
  135. codex_autorunner/static/fileChat.js +68 -40
  136. codex_autorunner/static/fileboxUi.js +159 -0
  137. codex_autorunner/static/hub.js +114 -131
  138. codex_autorunner/static/index.html +375 -49
  139. codex_autorunner/static/messages.js +568 -87
  140. codex_autorunner/static/notifications.js +255 -0
  141. codex_autorunner/static/pma.js +1167 -0
  142. codex_autorunner/static/preserve.js +17 -0
  143. codex_autorunner/static/settings.js +128 -6
  144. codex_autorunner/static/smartRefresh.js +52 -0
  145. codex_autorunner/static/streamUtils.js +57 -0
  146. codex_autorunner/static/styles.css +9798 -6143
  147. codex_autorunner/static/tabs.js +152 -11
  148. codex_autorunner/static/templateReposSettings.js +225 -0
  149. codex_autorunner/static/terminal.js +18 -0
  150. codex_autorunner/static/ticketChatActions.js +165 -3
  151. codex_autorunner/static/ticketChatStream.js +17 -119
  152. codex_autorunner/static/ticketEditor.js +137 -15
  153. codex_autorunner/static/ticketTemplates.js +798 -0
  154. codex_autorunner/static/tickets.js +821 -98
  155. codex_autorunner/static/turnEvents.js +27 -0
  156. codex_autorunner/static/turnResume.js +33 -0
  157. codex_autorunner/static/utils.js +39 -0
  158. codex_autorunner/static/workspace.js +389 -82
  159. codex_autorunner/static/workspaceFileBrowser.js +15 -13
  160. codex_autorunner/surfaces/__init__.py +5 -0
  161. codex_autorunner/surfaces/cli/__init__.py +6 -0
  162. codex_autorunner/surfaces/cli/cli.py +2534 -0
  163. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  164. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  165. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  166. codex_autorunner/surfaces/web/__init__.py +1 -0
  167. codex_autorunner/surfaces/web/app.py +2223 -0
  168. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  169. codex_autorunner/surfaces/web/middleware.py +587 -0
  170. codex_autorunner/surfaces/web/pty_session.py +370 -0
  171. codex_autorunner/surfaces/web/review.py +6 -0
  172. codex_autorunner/surfaces/web/routes/__init__.py +82 -0
  173. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  174. codex_autorunner/surfaces/web/routes/analytics.py +284 -0
  175. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  176. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  177. codex_autorunner/surfaces/web/routes/base.py +615 -0
  178. codex_autorunner/surfaces/web/routes/file_chat.py +1117 -0
  179. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  180. codex_autorunner/surfaces/web/routes/flows.py +1354 -0
  181. codex_autorunner/surfaces/web/routes/messages.py +490 -0
  182. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  183. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  184. codex_autorunner/surfaces/web/routes/review.py +148 -0
  185. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  186. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  187. codex_autorunner/surfaces/web/routes/shared.py +277 -0
  188. codex_autorunner/surfaces/web/routes/system.py +196 -0
  189. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  190. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  191. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  192. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  193. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  194. codex_autorunner/surfaces/web/schemas.py +469 -0
  195. codex_autorunner/surfaces/web/static_assets.py +490 -0
  196. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  197. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  198. codex_autorunner/tickets/__init__.py +8 -1
  199. codex_autorunner/tickets/agent_pool.py +53 -4
  200. codex_autorunner/tickets/files.py +37 -16
  201. codex_autorunner/tickets/lint.py +50 -0
  202. codex_autorunner/tickets/models.py +6 -1
  203. codex_autorunner/tickets/outbox.py +50 -2
  204. codex_autorunner/tickets/runner.py +396 -57
  205. codex_autorunner/web/__init__.py +5 -1
  206. codex_autorunner/web/app.py +2 -1949
  207. codex_autorunner/web/hub_jobs.py +2 -191
  208. codex_autorunner/web/middleware.py +2 -586
  209. codex_autorunner/web/pty_session.py +2 -369
  210. codex_autorunner/web/runner_manager.py +2 -24
  211. codex_autorunner/web/schemas.py +2 -376
  212. codex_autorunner/web/static_assets.py +4 -441
  213. codex_autorunner/web/static_refresh.py +2 -85
  214. codex_autorunner/web/terminal_sessions.py +2 -77
  215. codex_autorunner/workspace/paths.py +49 -33
  216. codex_autorunner-1.2.0.dist-info/METADATA +150 -0
  217. codex_autorunner-1.2.0.dist-info/RECORD +339 -0
  218. codex_autorunner/core/adapter_utils.py +0 -21
  219. codex_autorunner/core/engine.py +0 -2653
  220. codex_autorunner/core/static_assets.py +0 -55
  221. codex_autorunner-1.0.0.dist-info/METADATA +0 -246
  222. codex_autorunner-1.0.0.dist-info/RECORD +0 -251
  223. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  224. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
  225. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
  226. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
  227. {codex_autorunner-1.0.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,255 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ import { api, escapeHtml, openModal, resolvePath } from "./utils.js";
3
+ import { registerAutoRefresh } from "./autoRefresh.js";
4
+ let notificationsInitialized = false;
5
+ let notificationItems = [];
6
+ let activeRoot = null;
7
+ let closeModalFn = null;
8
+ let documentListenerInstalled = false;
9
+ let modalElements = null;
10
+ let isRefreshing = false;
11
+ const NOTIFICATIONS_REFRESH_ID = "notifications";
12
+ const NOTIFICATIONS_REFRESH_MS = 15000;
13
+ function getModalElements() {
14
+ if (modalElements)
15
+ return modalElements;
16
+ const overlay = document.getElementById("notifications-modal");
17
+ const body = document.getElementById("notifications-modal-body");
18
+ const closeBtn = document.getElementById("notifications-modal-close");
19
+ if (!overlay || !body || !closeBtn)
20
+ return null;
21
+ modalElements = { overlay, body, closeBtn };
22
+ return modalElements;
23
+ }
24
+ function getRootElements(root) {
25
+ const trigger = root.querySelector("[data-notifications-trigger]");
26
+ const badge = root.querySelector("[data-notifications-badge]");
27
+ const dropdown = root.querySelector("[data-notifications-dropdown]");
28
+ if (!trigger || !badge || !dropdown)
29
+ return null;
30
+ return { root, trigger, badge, dropdown };
31
+ }
32
+ function setBadgeCount(count) {
33
+ const roots = document.querySelectorAll("[data-notifications-root]");
34
+ roots.forEach((root) => {
35
+ const elements = getRootElements(root);
36
+ if (!elements)
37
+ return;
38
+ elements.badge.textContent = count > 0 ? String(count) : "";
39
+ elements.badge.classList.toggle("hidden", count <= 0);
40
+ elements.trigger.setAttribute("aria-label", count > 0 ? `Notifications (${count})` : "Notifications");
41
+ });
42
+ }
43
+ function normalizeItem(item) {
44
+ const repoId = String(item.repo_id || "");
45
+ const repoDisplay = item.repo_display_name || repoId;
46
+ const mode = item.dispatch?.mode || "";
47
+ const title = (item.dispatch?.title || "").trim();
48
+ const fallbackTitle = title || mode || "Dispatch";
49
+ const body = item.dispatch?.body || "";
50
+ const isHandoff = Boolean(item.dispatch?.is_handoff) || mode === "pause";
51
+ const runId = String(item.run_id || "");
52
+ const openUrl = item.open_url || `/repos/${repoId}/?tab=inbox&run_id=${runId}`;
53
+ return {
54
+ repoId,
55
+ repoDisplay,
56
+ runId,
57
+ status: item.status || "paused",
58
+ seq: item.seq,
59
+ title: fallbackTitle,
60
+ mode,
61
+ body,
62
+ isHandoff,
63
+ openUrl,
64
+ };
65
+ }
66
+ function renderDropdown(root) {
67
+ if (!root)
68
+ return;
69
+ if (!notificationItems.length) {
70
+ root.dropdown.innerHTML = '<div class="notifications-empty muted small">No pending dispatches</div>';
71
+ return;
72
+ }
73
+ const html = notificationItems
74
+ .map((item, index) => {
75
+ const pill = item.isHandoff ? "handoff" : "paused";
76
+ return `
77
+ <button class="notifications-item" type="button" data-index="${index}">
78
+ <span class="notifications-item-repo">${escapeHtml(item.repoDisplay)}</span>
79
+ <span class="notifications-item-title">${escapeHtml(item.title)}</span>
80
+ <span class="pill pill-small pill-warn notifications-item-pill">${escapeHtml(pill)}</span>
81
+ </button>
82
+ `;
83
+ })
84
+ .join("");
85
+ root.dropdown.innerHTML = html;
86
+ }
87
+ function renderDropdownError(root) {
88
+ if (!root)
89
+ return;
90
+ root.dropdown.innerHTML = '<div class="notifications-empty muted small">Failed to load dispatches</div>';
91
+ }
92
+ function closeDropdown() {
93
+ if (!activeRoot)
94
+ return;
95
+ activeRoot.dropdown.classList.add("hidden");
96
+ activeRoot.trigger.setAttribute("aria-expanded", "false");
97
+ activeRoot = null;
98
+ removeDocumentListener();
99
+ }
100
+ function openDropdown(root) {
101
+ if (activeRoot && activeRoot !== root) {
102
+ activeRoot.dropdown.classList.add("hidden");
103
+ activeRoot.trigger.setAttribute("aria-expanded", "false");
104
+ }
105
+ activeRoot = root;
106
+ renderDropdown(root);
107
+ root.dropdown.classList.remove("hidden");
108
+ root.trigger.setAttribute("aria-expanded", "true");
109
+ installDocumentListener();
110
+ }
111
+ function toggleDropdown(root) {
112
+ if (activeRoot && activeRoot === root && !root.dropdown.classList.contains("hidden")) {
113
+ closeDropdown();
114
+ return;
115
+ }
116
+ openDropdown(root);
117
+ }
118
+ function installDocumentListener() {
119
+ if (documentListenerInstalled)
120
+ return;
121
+ documentListenerInstalled = true;
122
+ document.addEventListener("pointerdown", handleDocumentPointerDown);
123
+ }
124
+ function removeDocumentListener() {
125
+ if (!documentListenerInstalled)
126
+ return;
127
+ documentListenerInstalled = false;
128
+ document.removeEventListener("pointerdown", handleDocumentPointerDown);
129
+ }
130
+ function handleDocumentPointerDown(event) {
131
+ if (!activeRoot)
132
+ return;
133
+ const target = event.target;
134
+ if (!target || !activeRoot.root.contains(target)) {
135
+ closeDropdown();
136
+ }
137
+ }
138
+ function closeNotificationsModal() {
139
+ if (!closeModalFn)
140
+ return;
141
+ closeModalFn();
142
+ closeModalFn = null;
143
+ }
144
+ function openNotificationsModal(item, returnFocusTo) {
145
+ const modal = getModalElements();
146
+ if (!modal)
147
+ return;
148
+ closeNotificationsModal();
149
+ const runLabel = item.seq ? `${item.runId.slice(0, 8)} (#${item.seq})` : item.runId.slice(0, 8);
150
+ const modeLabel = item.mode ? ` (${item.mode})` : "";
151
+ const body = item.body?.trim() ? escapeHtml(item.body) : '<span class="muted">No message body.</span>';
152
+ modal.body.innerHTML = `
153
+ <div class="notifications-modal-meta">
154
+ <div class="notifications-modal-row">
155
+ <span class="notifications-modal-label">Repo</span>
156
+ <span class="notifications-modal-value">${escapeHtml(item.repoDisplay)}</span>
157
+ </div>
158
+ <div class="notifications-modal-row">
159
+ <span class="notifications-modal-label">Run</span>
160
+ <span class="notifications-modal-value mono">${escapeHtml(runLabel)}</span>
161
+ </div>
162
+ <div class="notifications-modal-row">
163
+ <span class="notifications-modal-label">Dispatch</span>
164
+ <span class="notifications-modal-value">${escapeHtml(item.title)}${escapeHtml(modeLabel)}</span>
165
+ </div>
166
+ </div>
167
+ <div class="notifications-modal-body">${body}</div>
168
+ <div class="notifications-modal-actions">
169
+ <a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open run</a>
170
+ </div>
171
+ <div class="notifications-modal-placeholder">Reply here (coming soon).</div>
172
+ `;
173
+ closeModalFn = openModal(modal.overlay, {
174
+ closeOnEscape: true,
175
+ closeOnOverlay: true,
176
+ initialFocus: modal.closeBtn,
177
+ returnFocusTo: returnFocusTo || null,
178
+ });
179
+ }
180
+ async function refreshNotifications(_ctx) {
181
+ if (isRefreshing)
182
+ return;
183
+ isRefreshing = true;
184
+ try {
185
+ const payload = (await api("/hub/messages", { method: "GET" }));
186
+ const items = payload?.items || [];
187
+ notificationItems = items.map(normalizeItem);
188
+ setBadgeCount(notificationItems.length);
189
+ if (activeRoot) {
190
+ renderDropdown(activeRoot);
191
+ }
192
+ }
193
+ catch (_err) {
194
+ if (activeRoot) {
195
+ renderDropdownError(activeRoot);
196
+ }
197
+ }
198
+ finally {
199
+ isRefreshing = false;
200
+ }
201
+ }
202
+ function attachRoot(root) {
203
+ root.trigger.setAttribute("aria-haspopup", "menu");
204
+ root.trigger.setAttribute("aria-expanded", "false");
205
+ root.trigger.addEventListener("pointerdown", (event) => {
206
+ event.preventDefault();
207
+ event.stopPropagation();
208
+ toggleDropdown(root);
209
+ });
210
+ root.trigger.addEventListener("click", (event) => {
211
+ event.preventDefault();
212
+ event.stopPropagation();
213
+ });
214
+ root.dropdown.addEventListener("click", (event) => {
215
+ const target = event.target?.closest(".notifications-item");
216
+ if (!target)
217
+ return;
218
+ const index = Number(target.dataset.index || "-1");
219
+ const item = notificationItems[index];
220
+ if (!item)
221
+ return;
222
+ closeDropdown();
223
+ openNotificationsModal(item, root.trigger);
224
+ });
225
+ }
226
+ function attachModalHandlers() {
227
+ const modal = getModalElements();
228
+ if (!modal)
229
+ return;
230
+ modal.closeBtn.addEventListener("click", () => {
231
+ closeNotificationsModal();
232
+ });
233
+ }
234
+ export function initNotifications() {
235
+ if (notificationsInitialized)
236
+ return;
237
+ const roots = Array.from(document.querySelectorAll("[data-notifications-root]"));
238
+ if (!roots.length)
239
+ return;
240
+ roots.forEach((root) => {
241
+ const elements = getRootElements(root);
242
+ if (!elements)
243
+ return;
244
+ attachRoot(elements);
245
+ });
246
+ attachModalHandlers();
247
+ registerAutoRefresh(NOTIFICATIONS_REFRESH_ID, {
248
+ callback: refreshNotifications,
249
+ tabId: null,
250
+ interval: NOTIFICATIONS_REFRESH_MS,
251
+ refreshOnActivation: true,
252
+ immediate: true,
253
+ });
254
+ notificationsInitialized = true;
255
+ }