codex-autorunner 1.2.1__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.
Files changed (55) hide show
  1. codex_autorunner/bootstrap.py +26 -5
  2. codex_autorunner/core/config.py +176 -59
  3. codex_autorunner/core/filesystem.py +24 -0
  4. codex_autorunner/core/flows/controller.py +50 -12
  5. codex_autorunner/core/flows/runtime.py +8 -3
  6. codex_autorunner/core/hub.py +293 -16
  7. codex_autorunner/core/lifecycle_events.py +44 -5
  8. codex_autorunner/core/pma_delivery.py +81 -0
  9. codex_autorunner/core/pma_dispatches.py +224 -0
  10. codex_autorunner/core/pma_lane_worker.py +122 -0
  11. codex_autorunner/core/pma_queue.py +167 -18
  12. codex_autorunner/core/pma_reactive.py +91 -0
  13. codex_autorunner/core/pma_safety.py +58 -0
  14. codex_autorunner/core/pma_sink.py +104 -0
  15. codex_autorunner/core/pma_transcripts.py +183 -0
  16. codex_autorunner/core/safe_paths.py +117 -0
  17. codex_autorunner/housekeeping.py +77 -23
  18. codex_autorunner/integrations/agents/codex_backend.py +18 -12
  19. codex_autorunner/integrations/agents/wiring.py +2 -0
  20. codex_autorunner/integrations/app_server/client.py +31 -0
  21. codex_autorunner/integrations/app_server/supervisor.py +3 -0
  22. codex_autorunner/integrations/telegram/constants.py +1 -1
  23. codex_autorunner/integrations/telegram/handlers/commands/execution.py +16 -15
  24. codex_autorunner/integrations/telegram/handlers/commands/files.py +5 -8
  25. codex_autorunner/integrations/telegram/handlers/commands/github.py +10 -6
  26. codex_autorunner/integrations/telegram/handlers/commands/shared.py +9 -8
  27. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +85 -2
  28. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +29 -8
  29. codex_autorunner/integrations/telegram/helpers.py +30 -2
  30. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +54 -3
  31. codex_autorunner/static/docChatCore.js +2 -0
  32. codex_autorunner/static/hub.js +59 -0
  33. codex_autorunner/static/index.html +70 -54
  34. codex_autorunner/static/notificationBell.js +173 -0
  35. codex_autorunner/static/notifications.js +154 -36
  36. codex_autorunner/static/pma.js +96 -35
  37. codex_autorunner/static/styles.css +415 -4
  38. codex_autorunner/static/utils.js +5 -1
  39. codex_autorunner/surfaces/cli/cli.py +206 -129
  40. codex_autorunner/surfaces/cli/template_repos.py +157 -0
  41. codex_autorunner/surfaces/web/app.py +193 -5
  42. codex_autorunner/surfaces/web/routes/file_chat.py +109 -61
  43. codex_autorunner/surfaces/web/routes/flows.py +125 -67
  44. codex_autorunner/surfaces/web/routes/pma.py +638 -57
  45. codex_autorunner/tickets/agent_pool.py +6 -1
  46. codex_autorunner/tickets/outbox.py +27 -14
  47. codex_autorunner/tickets/replies.py +4 -10
  48. codex_autorunner/tickets/runner.py +1 -0
  49. codex_autorunner/workspace/paths.py +8 -3
  50. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/METADATA +1 -1
  51. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/RECORD +55 -45
  52. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/WHEEL +0 -0
  53. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/entry_points.txt +0 -0
  54. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/licenses/LICENSE +0 -0
  55. {codex_autorunner-1.2.1.dist-info → codex_autorunner-1.3.0.dist-info}/top_level.txt +0 -0
@@ -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
+ };
@@ -1,8 +1,8 @@
1
1
  // GENERATED FILE - do not edit directly. Source: static_src/
2
- import { api, escapeHtml, openModal, resolvePath } from "./utils.js";
2
+ import { api, confirmModal, escapeHtml, flash, inputModal, openModal, resolvePath } from "./utils.js";
3
3
  import { registerAutoRefresh } from "./autoRefresh.js";
4
4
  let notificationsInitialized = false;
5
- let notificationItems = [];
5
+ const notificationItemsByRoot = {};
6
6
  let activeRoot = null;
7
7
  let closeModalFn = null;
8
8
  let documentListenerInstalled = false;
@@ -29,10 +29,11 @@ function getRootElements(root) {
29
29
  const dropdown = root.querySelector("[data-notifications-dropdown]");
30
30
  if (!trigger || !badge || !dropdown)
31
31
  return null;
32
- return { root, trigger, badge, dropdown };
32
+ const key = root.getAttribute("data-notifications-root") || "hub";
33
+ return { root, trigger, badge, dropdown, key };
33
34
  }
34
- function setBadgeCount(count) {
35
- const roots = document.querySelectorAll("[data-notifications-root]");
35
+ function setBadgeCount(rootKey, count) {
36
+ const roots = document.querySelectorAll(`[data-notifications-root="${rootKey}"]`);
36
37
  roots.forEach((root) => {
37
38
  const elements = getRootElements(root);
38
39
  if (!elements)
@@ -42,7 +43,7 @@ function setBadgeCount(count) {
42
43
  elements.trigger.setAttribute("aria-label", count > 0 ? `Notifications (${count})` : "Notifications");
43
44
  });
44
45
  }
45
- function normalizeItem(item) {
46
+ function normalizeHubItem(item) {
46
47
  const repoId = String(item.repo_id || "");
47
48
  const repoDisplay = item.repo_display_name || repoId;
48
49
  const mode = item.dispatch?.mode || "";
@@ -53,6 +54,7 @@ function normalizeItem(item) {
53
54
  const runId = String(item.run_id || "");
54
55
  const openUrl = item.open_url || `/repos/${repoId}/?tab=inbox&run_id=${runId}`;
55
56
  return {
57
+ kind: "hub",
56
58
  repoId,
57
59
  repoDisplay,
58
60
  runId,
@@ -63,21 +65,42 @@ function normalizeItem(item) {
63
65
  body,
64
66
  isHandoff,
65
67
  openUrl,
68
+ pillLabel: isHandoff ? "handoff" : "paused",
66
69
  };
67
70
  }
71
+ function normalizePmaItem(item) {
72
+ const title = (item.title || "PMA dispatch").trim();
73
+ const body = item.body || "";
74
+ const priority = (item.priority || "info").toLowerCase();
75
+ const isHandoff = priority === "action";
76
+ return {
77
+ kind: "pma",
78
+ title,
79
+ body,
80
+ isHandoff,
81
+ openUrl: "/?view=pma",
82
+ pillLabel: priority,
83
+ priority,
84
+ links: item.links || [],
85
+ };
86
+ }
87
+ function getItemsForRoot(rootKey) {
88
+ return notificationItemsByRoot[rootKey] || [];
89
+ }
68
90
  function renderDropdown(root) {
69
91
  if (!root)
70
92
  return;
71
- if (!notificationItems.length) {
93
+ const items = getItemsForRoot(root.key);
94
+ if (!items.length) {
72
95
  root.dropdown.innerHTML = '<div class="notifications-empty muted small">No pending dispatches</div>';
73
96
  return;
74
97
  }
75
- const html = notificationItems
98
+ const html = items
76
99
  .map((item, index) => {
77
- const pill = item.isHandoff ? "handoff" : "paused";
100
+ const pill = item.pillLabel || (item.isHandoff ? "handoff" : "paused");
78
101
  return `
79
102
  <button class="notifications-item" type="button" data-index="${index}">
80
- <span class="notifications-item-repo">${escapeHtml(item.repoDisplay)}</span>
103
+ <span class="notifications-item-repo">${escapeHtml(item.repoDisplay || "PMA")}</span>
81
104
  <span class="notifications-item-title">${escapeHtml(item.title)}</span>
82
105
  <span class="pill pill-small pill-warn notifications-item-pill">${escapeHtml(pill)}</span>
83
106
  </button>
@@ -174,35 +197,104 @@ function closeNotificationsModal() {
174
197
  closeModalFn();
175
198
  closeModalFn = null;
176
199
  }
200
+ function isSameNotification(a, b) {
201
+ return (a.kind === b.kind &&
202
+ a.repoId === b.repoId &&
203
+ a.runId === b.runId &&
204
+ a.seq === b.seq);
205
+ }
177
206
  function openNotificationsModal(item, returnFocusTo) {
178
207
  const modal = getModalElements();
179
208
  if (!modal)
180
209
  return;
181
210
  closeNotificationsModal();
182
- const runLabel = item.seq ? `${item.runId.slice(0, 8)} (#${item.seq})` : item.runId.slice(0, 8);
183
- const modeLabel = item.mode ? ` (${item.mode})` : "";
184
211
  const body = item.body?.trim() ? escapeHtml(item.body) : '<span class="muted">No message body.</span>';
185
- modal.body.innerHTML = `
186
- <div class="notifications-modal-meta">
187
- <div class="notifications-modal-row">
188
- <span class="notifications-modal-label">Repo</span>
189
- <span class="notifications-modal-value">${escapeHtml(item.repoDisplay)}</span>
212
+ if (item.kind === "pma") {
213
+ const priority = item.priority || "info";
214
+ const links = (item.links || [])
215
+ .map((link) => `<a href="${escapeHtml(link.href)}" target="_blank" rel="noopener">${escapeHtml(link.label)}</a>`)
216
+ .join("");
217
+ const linkBlock = links ? `<div class="notifications-modal-links">${links}</div>` : "";
218
+ modal.body.innerHTML = `
219
+ <div class="notifications-modal-meta">
220
+ <div class="notifications-modal-row">
221
+ <span class="notifications-modal-label">Dispatch</span>
222
+ <span class="notifications-modal-value">${escapeHtml(item.title)}</span>
223
+ </div>
224
+ <div class="notifications-modal-row">
225
+ <span class="notifications-modal-label">Priority</span>
226
+ <span class="notifications-modal-value">${escapeHtml(priority)}</span>
227
+ </div>
190
228
  </div>
191
- <div class="notifications-modal-row">
192
- <span class="notifications-modal-label">Run</span>
193
- <span class="notifications-modal-value mono">${escapeHtml(runLabel)}</span>
229
+ <div class="notifications-modal-body">${body}</div>
230
+ ${linkBlock}
231
+ <div class="notifications-modal-actions">
232
+ <a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open PMA</a>
194
233
  </div>
195
- <div class="notifications-modal-row">
196
- <span class="notifications-modal-label">Dispatch</span>
197
- <span class="notifications-modal-value">${escapeHtml(item.title)}${escapeHtml(modeLabel)}</span>
234
+ `;
235
+ }
236
+ else {
237
+ const runId = item.runId || "";
238
+ const runLabel = item.seq ? `${runId.slice(0, 8)} (#${item.seq})` : runId.slice(0, 8);
239
+ const modeLabel = item.mode ? ` (${item.mode})` : "";
240
+ modal.body.innerHTML = `
241
+ <div class="notifications-modal-meta">
242
+ <div class="notifications-modal-row">
243
+ <span class="notifications-modal-label">Repo</span>
244
+ <span class="notifications-modal-value">${escapeHtml(item.repoDisplay || "")}</span>
245
+ </div>
246
+ <div class="notifications-modal-row">
247
+ <span class="notifications-modal-label">Run</span>
248
+ <span class="notifications-modal-value mono">${escapeHtml(runLabel)}</span>
249
+ </div>
250
+ <div class="notifications-modal-row">
251
+ <span class="notifications-modal-label">Dispatch</span>
252
+ <span class="notifications-modal-value">${escapeHtml(item.title)}${escapeHtml(modeLabel)}</span>
253
+ </div>
198
254
  </div>
199
- </div>
200
- <div class="notifications-modal-body">${body}</div>
201
- <div class="notifications-modal-actions">
202
- <a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open run</a>
203
- </div>
204
- <div class="notifications-modal-placeholder">Reply here (coming soon).</div>
205
- `;
255
+ <div class="notifications-modal-body">${body}</div>
256
+ <div class="notifications-modal-actions">
257
+ <a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open run</a>
258
+ ${item.seq ? '<button class="ghost sm notifications-dismiss" type="button">Dismiss</button>' : ""}
259
+ </div>
260
+ <div class="notifications-modal-placeholder">Reply here (coming soon).</div>
261
+ `;
262
+ const dismissBtn = modal.body.querySelector(".notifications-dismiss");
263
+ if (dismissBtn && item.seq) {
264
+ dismissBtn.addEventListener("click", async () => {
265
+ const confirmed = await confirmModal("Dismiss this inbox item?", {
266
+ confirmText: "Dismiss",
267
+ danger: false,
268
+ });
269
+ if (!confirmed)
270
+ return;
271
+ const reason = await inputModal("Dismiss reason (optional)", {
272
+ placeholder: "obsolete, resolved elsewhere, ...",
273
+ confirmText: "Dismiss",
274
+ allowEmpty: true,
275
+ });
276
+ if (reason === null)
277
+ return;
278
+ await api("/hub/messages/dismiss", {
279
+ method: "POST",
280
+ body: {
281
+ repo_id: item.repoId,
282
+ run_id: item.runId,
283
+ seq: item.seq,
284
+ reason,
285
+ },
286
+ });
287
+ const hubItems = getItemsForRoot("hub").filter((entry) => !isSameNotification(entry, item));
288
+ notificationItemsByRoot.hub = hubItems;
289
+ setBadgeCount("hub", hubItems.length);
290
+ if (activeRoot && activeRoot.key === "hub") {
291
+ renderDropdown(activeRoot);
292
+ }
293
+ closeNotificationsModal();
294
+ flash("Dispatch dismissed");
295
+ });
296
+ }
297
+ }
206
298
  closeModalFn = openModal(modal.overlay, {
207
299
  closeOnEscape: true,
208
300
  closeOnOverlay: true,
@@ -215,10 +307,28 @@ async function refreshNotifications(_ctx) {
215
307
  return;
216
308
  isRefreshing = true;
217
309
  try {
218
- const payload = (await api("/hub/messages", { method: "GET" }));
219
- const items = payload?.items || [];
220
- notificationItems = items.map(normalizeItem);
221
- setBadgeCount(notificationItems.length);
310
+ let hubPayload = null;
311
+ let pmaPayload = null;
312
+ try {
313
+ hubPayload = (await api("/hub/messages", { method: "GET" }));
314
+ }
315
+ catch {
316
+ hubPayload = null;
317
+ }
318
+ try {
319
+ pmaPayload = (await api("/hub/pma/dispatches?include_resolved=false", {
320
+ method: "GET",
321
+ }));
322
+ }
323
+ catch {
324
+ pmaPayload = { items: [] };
325
+ }
326
+ const hubItems = (hubPayload?.items || []).map(normalizeHubItem);
327
+ const pmaItems = (pmaPayload?.items || []).map(normalizePmaItem);
328
+ notificationItemsByRoot.hub = hubItems;
329
+ notificationItemsByRoot.pma = pmaItems;
330
+ setBadgeCount("hub", hubItems.length);
331
+ setBadgeCount("pma", pmaItems.length);
222
332
  if (activeRoot) {
223
333
  renderDropdown(activeRoot);
224
334
  }
@@ -248,12 +358,20 @@ function attachRoot(root) {
248
358
  const target = event.target?.closest(".notifications-item");
249
359
  if (!target)
250
360
  return;
361
+ event.preventDefault();
362
+ event.stopPropagation();
251
363
  const index = Number(target.dataset.index || "-1");
252
- const item = notificationItems[index];
364
+ const items = getItemsForRoot(root.key);
365
+ const item = items[index];
253
366
  if (!item)
254
367
  return;
255
368
  closeDropdown();
256
- openNotificationsModal(item, root.trigger);
369
+ const mouseEvent = event;
370
+ if (mouseEvent.shiftKey) {
371
+ openNotificationsModal(item, root.trigger);
372
+ return;
373
+ }
374
+ window.location.href = resolvePath(item.openUrl);
257
375
  });
258
376
  }
259
377
  function attachModalHandlers() {