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
@@ -2,14 +2,16 @@
2
2
  /**
3
3
  * Ticket Editor Modal - handles creating, editing, and deleting tickets
4
4
  */
5
- import { api, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
5
+ import { api, confirmModal, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
6
6
  import { publish } from "./bus.js";
7
7
  import { clearTicketChatHistory } from "./ticketChatStorage.js";
8
- import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, } from "./ticketChatActions.js";
8
+ import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, resumeTicketPendingTurn, } from "./ticketChatActions.js";
9
9
  import { initAgentControls } from "./agentControls.js";
10
10
  import { initTicketVoice } from "./ticketVoice.js";
11
11
  import { initTicketChatEvents, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
12
+ import { initChatPasteUpload } from "./chatUploads.js";
12
13
  import { DocEditor } from "./docEditor.js";
14
+ import { initTicketTemplates } from "./ticketTemplates.js";
13
15
  const DEFAULT_FRONTMATTER = {
14
16
  agent: "codex",
15
17
  done: false,
@@ -30,6 +32,74 @@ const state = {
30
32
  // Autosave debounce timer
31
33
  const AUTOSAVE_DELAY_MS = 1000;
32
34
  let ticketDocEditor = null;
35
+ let ticketNavCache = [];
36
+ function isTypingTarget(target) {
37
+ if (!(target instanceof HTMLElement))
38
+ return false;
39
+ const tag = target.tagName;
40
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
41
+ }
42
+ async function fetchTicketList() {
43
+ const data = (await api("/api/flows/ticket_flow/tickets"));
44
+ const list = (data?.tickets || []).filter((ticket) => typeof ticket.index === "number");
45
+ list.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
46
+ return list;
47
+ }
48
+ async function updateTicketNavButtons() {
49
+ const { prevBtn, nextBtn } = els();
50
+ if (!prevBtn || !nextBtn)
51
+ return;
52
+ if (state.mode !== "edit" || state.ticketIndex == null) {
53
+ prevBtn.disabled = true;
54
+ nextBtn.disabled = true;
55
+ return;
56
+ }
57
+ try {
58
+ const list = await fetchTicketList();
59
+ ticketNavCache = list;
60
+ }
61
+ catch {
62
+ // If fetch fails, fall back to the last known list.
63
+ }
64
+ const list = ticketNavCache;
65
+ if (!list.length) {
66
+ prevBtn.disabled = true;
67
+ nextBtn.disabled = true;
68
+ return;
69
+ }
70
+ const idx = list.findIndex((ticket) => ticket.index === state.ticketIndex);
71
+ const hasPrev = idx > 0;
72
+ const hasNext = idx >= 0 && idx < list.length - 1;
73
+ prevBtn.disabled = !hasPrev;
74
+ nextBtn.disabled = !hasNext;
75
+ }
76
+ async function navigateTicket(delta) {
77
+ if (state.mode !== "edit" || state.ticketIndex == null)
78
+ return;
79
+ await performAutosave();
80
+ let list = ticketNavCache;
81
+ if (!list.length) {
82
+ try {
83
+ list = await fetchTicketList();
84
+ ticketNavCache = list;
85
+ }
86
+ catch {
87
+ return;
88
+ }
89
+ }
90
+ const idx = list.findIndex((ticket) => ticket.index === state.ticketIndex);
91
+ const target = idx >= 0 ? list[idx + delta] : null;
92
+ if (target && target.index != null) {
93
+ try {
94
+ const data = (await api(`/api/flows/ticket_flow/tickets/${target.index}`));
95
+ openTicketEditor(data);
96
+ }
97
+ catch (err) {
98
+ flash(`Failed to navigate to ticket: ${err.message}`, "error");
99
+ }
100
+ }
101
+ void updateTicketNavButtons();
102
+ }
33
103
  function els() {
34
104
  return {
35
105
  modal: document.getElementById("ticket-editor-modal"),
@@ -40,6 +110,8 @@ function els() {
40
110
  newBtn: document.getElementById("ticket-new-btn"),
41
111
  insertCheckboxBtn: document.getElementById("ticket-insert-checkbox"),
42
112
  undoBtn: document.getElementById("ticket-undo-btn"),
113
+ prevBtn: document.getElementById("ticket-nav-prev"),
114
+ nextBtn: document.getElementById("ticket-nav-next"),
43
115
  autosaveStatus: document.getElementById("ticket-autosave-status"),
44
116
  // Frontmatter form elements
45
117
  fmAgent: document.getElementById("ticket-fm-agent"),
@@ -234,20 +306,24 @@ function extractFrontmatter(ticket) {
234
306
  /**
235
307
  * Build full markdown content from frontmatter form + body textarea
236
308
  */
309
+ function yamlQuote(value) {
310
+ // Use JSON.stringify for simple, safe double-quoted scalars (handles colons, quotes, newlines).
311
+ return JSON.stringify(value ?? "");
312
+ }
237
313
  function buildTicketContent() {
238
314
  const { content } = els();
239
315
  const fm = getFrontmatterFromForm();
240
316
  const body = content?.value || "";
241
- // Reconstruct frontmatter YAML
317
+ // Reconstruct frontmatter YAML with quoted scalars to tolerate special characters.
242
318
  const lines = ["---"];
243
- lines.push(`agent: ${fm.agent}`);
319
+ lines.push(`agent: ${yamlQuote(fm.agent)}`);
244
320
  lines.push(`done: ${fm.done}`);
245
321
  if (fm.title)
246
- lines.push(`title: ${fm.title}`);
322
+ lines.push(`title: ${yamlQuote(fm.title)}`);
247
323
  if (fm.model)
248
- lines.push(`model: ${fm.model}`);
324
+ lines.push(`model: ${yamlQuote(fm.model)}`);
249
325
  if (fm.reasoning)
250
- lines.push(`reasoning: ${fm.reasoning}`);
326
+ lines.push(`reasoning: ${yamlQuote(fm.reasoning)}`);
251
327
  lines.push("---");
252
328
  lines.push("");
253
329
  lines.push(body);
@@ -414,8 +490,12 @@ async function performAutosave() {
414
490
  // Notify that tickets changed
415
491
  publish("tickets:updated", {});
416
492
  }
417
- catch {
493
+ catch (err) {
494
+ // Surface the failure to the user and let DocEditor keep the "dirty" state
495
+ // so a retry is attempted instead of falsely reporting success.
418
496
  setAutosaveStatus("error");
497
+ flash(err?.message || "Failed to save ticket", "error");
498
+ throw err;
419
499
  }
420
500
  }
421
501
  /**
@@ -526,19 +606,21 @@ export function openTicketEditor(ticket) {
526
606
  renderTicketChat();
527
607
  renderTicketEvents();
528
608
  renderTicketMessages();
609
+ void resumeTicketPendingTurn(ticket?.index ?? null);
529
610
  state.isOpen = true;
530
611
  modal.classList.remove("hidden");
531
612
  // Update URL with ticket index
532
613
  if (ticket?.index != null) {
533
614
  updateUrlParams({ ticket: ticket.index });
534
615
  }
535
- // Focus on title field for new tickets, body for existing
616
+ if (ticket?.path) {
617
+ publish("ticket-editor:opened", { path: ticket.path, index: ticket.index ?? null });
618
+ }
619
+ void updateTicketNavButtons();
620
+ // Focus on title field for new tickets
536
621
  if (state.mode === "create" && fmTitle) {
537
622
  fmTitle.focus();
538
623
  }
539
- else {
540
- content.focus();
541
- }
542
624
  }
543
625
  /**
544
626
  * Close the ticket editor modal (autosaves on close)
@@ -549,7 +631,9 @@ export function closeTicketEditor() {
549
631
  return;
550
632
  // Autosave on close if there are changes
551
633
  if (hasUnsavedChanges()) {
552
- void performAutosave();
634
+ // Fire-and-forget: swallow rejection because the error is already flashed
635
+ // inside performAutosave and DocEditor keeps the buffer dirty for retry.
636
+ void performAutosave().catch(() => { });
553
637
  }
554
638
  // Cancel any running chat
555
639
  if (ticketChatState.status === "running") {
@@ -568,9 +652,12 @@ export function closeTicketEditor() {
568
652
  ticketDocEditor = null;
569
653
  // Clear ticket from URL
570
654
  updateUrlParams({ ticket: null });
655
+ void updateTicketNavButtons();
571
656
  // Reset chat state
572
657
  resetTicketChatState();
573
658
  setTicketIndex(null);
659
+ // Notify that editor was closed (for selection state cleanup)
660
+ publish("ticket-editor:closed", {});
574
661
  }
575
662
  /**
576
663
  * Save the current ticket (triggers immediate autosave)
@@ -586,7 +673,7 @@ export async function deleteTicket() {
586
673
  flash("Cannot delete: no ticket selected", "error");
587
674
  return;
588
675
  }
589
- const confirmed = window.confirm(`Delete TICKET-${String(state.ticketIndex).padStart(3, "0")}.md? This cannot be undone.`);
676
+ const confirmed = await confirmModal(`Delete TICKET-${String(state.ticketIndex).padStart(3, "0")}.md? This cannot be undone.`);
590
677
  if (!confirmed)
591
678
  return;
592
679
  setButtonsLoading(true);
@@ -618,7 +705,7 @@ export async function deleteTicket() {
618
705
  * Initialize the ticket editor - wire up event listeners
619
706
  */
620
707
  export function initTicketEditor() {
621
- const { modal, content, deleteBtn, closeBtn, newBtn, insertCheckboxBtn, undoBtn, fmAgent, fmModel, fmReasoning, fmDone, fmTitle, chatInput, chatSendBtn, chatCancelBtn, patchApplyBtn, patchDiscardBtn, agentSelect, modelSelect, reasoningSelect, } = els();
708
+ const { modal, content, deleteBtn, closeBtn, newBtn, insertCheckboxBtn, undoBtn, prevBtn, nextBtn, fmAgent, fmModel, fmReasoning, fmDone, fmTitle, chatInput, chatSendBtn, chatCancelBtn, patchApplyBtn, patchDiscardBtn, agentSelect, modelSelect, reasoningSelect, } = els();
622
709
  if (!modal)
623
710
  return;
624
711
  // Prevent double initialization
@@ -635,6 +722,8 @@ export function initTicketEditor() {
635
722
  void initTicketVoice();
636
723
  // Initialize rich chat experience (events toggle, etc.)
637
724
  initTicketChatEvents();
725
+ // Initialize ticket templates picker
726
+ initTicketTemplates();
638
727
  // Button handlers
639
728
  if (deleteBtn)
640
729
  deleteBtn.addEventListener("click", () => void deleteTicket());
@@ -646,6 +735,16 @@ export function initTicketEditor() {
646
735
  insertCheckboxBtn.addEventListener("click", insertCheckbox);
647
736
  if (undoBtn)
648
737
  undoBtn.addEventListener("click", undoChange);
738
+ if (prevBtn)
739
+ prevBtn.addEventListener("click", (e) => {
740
+ e.preventDefault();
741
+ void navigateTicket(-1);
742
+ });
743
+ if (nextBtn)
744
+ nextBtn.addEventListener("click", (e) => {
745
+ e.preventDefault();
746
+ void navigateTicket(1);
747
+ });
649
748
  // Autosave on content changes
650
749
  if (content) {
651
750
  content.addEventListener("input", onContentChange);
@@ -694,6 +793,13 @@ export function initTicketEditor() {
694
793
  chatInput.style.height = "auto";
695
794
  chatInput.style.height = Math.min(chatInput.scrollHeight, 100) + "px";
696
795
  });
796
+ initChatPasteUpload({
797
+ textarea: chatInput,
798
+ basePath: "/api/filebox",
799
+ box: "inbox",
800
+ insertStyle: "both",
801
+ pathPrefix: ".codex-autorunner/filebox",
802
+ });
697
803
  }
698
804
  // Close on backdrop click
699
805
  modal.addEventListener("click", (e) => {
@@ -718,6 +824,22 @@ export function initTicketEditor() {
718
824
  undoChange();
719
825
  }
720
826
  });
827
+ // Left/Right arrows navigate between tickets when editor is open and not typing
828
+ document.addEventListener("keydown", (e) => {
829
+ if (!state.isOpen)
830
+ return;
831
+ // Check for navigation keys
832
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
833
+ return;
834
+ // Don't interfere with typing
835
+ if (isTypingTarget(e.target))
836
+ return;
837
+ // Require Alt modifier for navigation (no Ctrl/Meta/Shift)
838
+ if (!e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
839
+ return;
840
+ e.preventDefault();
841
+ void navigateTicket(e.key === "ArrowLeft" ? -1 : 1);
842
+ });
721
843
  // Enter key creates new TODO checkbox when on a checkbox line
722
844
  if (content) {
723
845
  content.addEventListener("keydown", (e) => {