codex-autorunner 1.1.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 (127) hide show
  1. codex_autorunner/agents/opencode/client.py +113 -4
  2. codex_autorunner/agents/opencode/supervisor.py +4 -0
  3. codex_autorunner/agents/registry.py +17 -7
  4. codex_autorunner/bootstrap.py +219 -1
  5. codex_autorunner/core/__init__.py +17 -1
  6. codex_autorunner/core/about_car.py +114 -1
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +236 -1
  9. codex_autorunner/core/context_awareness.py +38 -0
  10. codex_autorunner/core/docs.py +0 -122
  11. codex_autorunner/core/filebox.py +265 -0
  12. codex_autorunner/core/flows/controller.py +71 -1
  13. codex_autorunner/core/flows/reconciler.py +4 -1
  14. codex_autorunner/core/flows/runtime.py +22 -0
  15. codex_autorunner/core/flows/store.py +61 -9
  16. codex_autorunner/core/flows/transition.py +23 -16
  17. codex_autorunner/core/flows/ux_helpers.py +18 -3
  18. codex_autorunner/core/flows/worker_process.py +32 -6
  19. codex_autorunner/core/hub.py +198 -41
  20. codex_autorunner/core/lifecycle_events.py +253 -0
  21. codex_autorunner/core/path_utils.py +2 -1
  22. codex_autorunner/core/pma_audit.py +224 -0
  23. codex_autorunner/core/pma_context.py +496 -0
  24. codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
  25. codex_autorunner/core/pma_lifecycle.py +527 -0
  26. codex_autorunner/core/pma_queue.py +367 -0
  27. codex_autorunner/core/pma_safety.py +221 -0
  28. codex_autorunner/core/pma_state.py +115 -0
  29. codex_autorunner/core/ports/agent_backend.py +2 -5
  30. codex_autorunner/core/ports/run_event.py +1 -4
  31. codex_autorunner/core/prompt.py +0 -80
  32. codex_autorunner/core/prompts.py +56 -172
  33. codex_autorunner/core/redaction.py +0 -4
  34. codex_autorunner/core/review_context.py +11 -9
  35. codex_autorunner/core/runner_controller.py +35 -33
  36. codex_autorunner/core/runner_state.py +147 -0
  37. codex_autorunner/core/runtime.py +829 -0
  38. codex_autorunner/core/sqlite_utils.py +13 -4
  39. codex_autorunner/core/state.py +7 -10
  40. codex_autorunner/core/state_roots.py +5 -0
  41. codex_autorunner/core/templates/__init__.py +39 -0
  42. codex_autorunner/core/templates/git_mirror.py +234 -0
  43. codex_autorunner/core/templates/provenance.py +56 -0
  44. codex_autorunner/core/templates/scan_cache.py +120 -0
  45. codex_autorunner/core/ticket_linter_cli.py +17 -0
  46. codex_autorunner/core/ticket_manager_cli.py +154 -92
  47. codex_autorunner/core/time_utils.py +11 -0
  48. codex_autorunner/core/types.py +18 -0
  49. codex_autorunner/core/utils.py +34 -6
  50. codex_autorunner/flows/review/service.py +23 -25
  51. codex_autorunner/flows/ticket_flow/definition.py +43 -1
  52. codex_autorunner/integrations/agents/__init__.py +2 -0
  53. codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
  54. codex_autorunner/integrations/agents/codex_backend.py +19 -8
  55. codex_autorunner/integrations/agents/runner.py +3 -8
  56. codex_autorunner/integrations/agents/wiring.py +8 -0
  57. codex_autorunner/integrations/telegram/doctor.py +228 -6
  58. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  59. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  60. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  61. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  62. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  63. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  64. codex_autorunner/integrations/telegram/handlers/messages.py +26 -1
  65. codex_autorunner/integrations/telegram/helpers.py +1 -3
  66. codex_autorunner/integrations/telegram/runtime.py +9 -4
  67. codex_autorunner/integrations/telegram/service.py +30 -0
  68. codex_autorunner/integrations/telegram/state.py +38 -0
  69. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  70. codex_autorunner/integrations/telegram/transport.py +10 -3
  71. codex_autorunner/integrations/templates/__init__.py +27 -0
  72. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  73. codex_autorunner/server.py +2 -2
  74. codex_autorunner/static/agentControls.js +21 -5
  75. codex_autorunner/static/app.js +115 -11
  76. codex_autorunner/static/chatUploads.js +137 -0
  77. codex_autorunner/static/docChatCore.js +185 -13
  78. codex_autorunner/static/fileChat.js +68 -40
  79. codex_autorunner/static/fileboxUi.js +159 -0
  80. codex_autorunner/static/hub.js +46 -81
  81. codex_autorunner/static/index.html +303 -24
  82. codex_autorunner/static/messages.js +82 -4
  83. codex_autorunner/static/notifications.js +255 -0
  84. codex_autorunner/static/pma.js +1167 -0
  85. codex_autorunner/static/settings.js +3 -0
  86. codex_autorunner/static/streamUtils.js +57 -0
  87. codex_autorunner/static/styles.css +9125 -6742
  88. codex_autorunner/static/templateReposSettings.js +225 -0
  89. codex_autorunner/static/ticketChatActions.js +165 -3
  90. codex_autorunner/static/ticketChatStream.js +17 -119
  91. codex_autorunner/static/ticketEditor.js +41 -13
  92. codex_autorunner/static/ticketTemplates.js +798 -0
  93. codex_autorunner/static/tickets.js +69 -19
  94. codex_autorunner/static/turnEvents.js +27 -0
  95. codex_autorunner/static/turnResume.js +33 -0
  96. codex_autorunner/static/utils.js +28 -0
  97. codex_autorunner/static/workspace.js +258 -44
  98. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  99. codex_autorunner/surfaces/cli/cli.py +1465 -155
  100. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  101. codex_autorunner/surfaces/web/app.py +253 -49
  102. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  103. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  104. codex_autorunner/surfaces/web/routes/file_chat.py +317 -36
  105. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  106. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  107. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  108. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  109. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  110. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  111. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  112. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  113. codex_autorunner/surfaces/web/schemas.py +70 -18
  114. codex_autorunner/tickets/agent_pool.py +27 -0
  115. codex_autorunner/tickets/files.py +33 -16
  116. codex_autorunner/tickets/lint.py +50 -0
  117. codex_autorunner/tickets/models.py +3 -0
  118. codex_autorunner/tickets/outbox.py +41 -5
  119. codex_autorunner/tickets/runner.py +350 -69
  120. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/METADATA +15 -19
  121. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
  122. codex_autorunner/core/adapter_utils.py +0 -21
  123. codex_autorunner/core/engine.py +0 -3302
  124. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
  125. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
  126. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
  127. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
@@ -23,10 +23,25 @@
23
23
  </div>
24
24
  <div class="hub-hero-actions">
25
25
  <button class="primary sm" id="hub-new-repo">+ New</button>
26
- <button class="sm" id="hub-scan">Scan</button>
27
- <button class="ghost sm" id="hub-refresh">Refresh</button>
26
+ <div class="notifications-bell" data-notifications-root="hub">
27
+ <button class="ghost sm icon-btn notifications-bell-btn" data-notifications-trigger title="Notifications"
28
+ aria-label="Notifications">
29
+ <span aria-hidden="true">🔔</span>
30
+ <span class="notifications-bell-badge hidden" data-notifications-badge></span>
31
+ </button>
32
+ <div class="notifications-dropdown hidden" data-notifications-dropdown role="menu"
33
+ aria-label="Pending dispatches"></div>
34
+ </div>
28
35
  <button class="ghost sm icon-btn" id="hub-settings" title="Settings">⚙</button>
29
36
  </div>
37
+ <div class="hub-mode-toggle" role="tablist" aria-label="Hub mode">
38
+ <button class="hub-mode-btn active" id="hub-mode-manual" data-hub-mode="manual" role="tab"
39
+ aria-selected="true">Manual</button>
40
+ <button class="hub-mode-btn" id="hub-mode-pma" data-hub-mode="pma" role="tab" aria-selected="false">
41
+ <span class="hub-mode-long">PM Agent</span>
42
+ <span class="hub-mode-short">PMA</span>
43
+ </button>
44
+ </div>
30
45
  </header>
31
46
  <section class="hub-stats">
32
47
  <div class="hub-stat">
@@ -42,15 +57,6 @@
42
57
  <p class="muted small">missing</p>
43
58
  </div>
44
59
  </section>
45
- <section class="hub-inbox">
46
- <div class="hub-panel-header">
47
- <span class="label">Inbox</span>
48
- <div class="hub-panel-actions">
49
- <button class="ghost sm" id="hub-inbox-refresh">Refresh</button>
50
- </div>
51
- </div>
52
- <div class="hub-inbox-list" id="hub-inbox-list">Loading…</div>
53
- </section>
54
60
  <section class="hub-usage-chart">
55
61
  <div class="hub-usage-chart-header">
56
62
  <span class="label">Usage Trend</span>
@@ -83,6 +89,136 @@
83
89
  </div>
84
90
  </section>
85
91
  </div>
92
+ <div class="hub-shell hidden" id="pma-shell">
93
+ <header class="hub-hero pma-hero">
94
+ <div class="hub-hero-text pma-hero-text">
95
+ <h1>Project Manager</h1>
96
+ <span class="pill pill-small pill-idle" id="pma-last-scan">–</span>
97
+ <span class="hub-version" id="pma-version">v–</span>
98
+ </div>
99
+ <div class="hub-hero-actions pma-hero-actions">
100
+ <div class="notifications-bell" data-notifications-root="pma">
101
+ <button class="ghost sm icon-btn notifications-bell-btn" data-notifications-trigger title="Notifications"
102
+ aria-label="Notifications">
103
+ <span aria-hidden="true">🔔</span>
104
+ <span class="notifications-bell-badge hidden" data-notifications-badge></span>
105
+ </button>
106
+ <div class="notifications-dropdown hidden" data-notifications-dropdown role="menu"
107
+ aria-label="Pending dispatches"></div>
108
+ </div>
109
+ <button class="ghost sm icon-btn" id="pma-settings" title="Settings">⚙</button>
110
+ </div>
111
+ <div class="hub-mode-toggle" role="tablist" aria-label="Hub mode">
112
+ <button class="hub-mode-btn" id="pma-mode-manual" data-hub-mode="manual" role="tab"
113
+ aria-selected="false">Manual</button>
114
+ <button class="hub-mode-btn active" id="pma-mode-pma" data-hub-mode="pma" role="tab" aria-selected="true">
115
+ <span class="hub-mode-long">PM Agent</span>
116
+ <span class="hub-mode-short">PMA</span>
117
+ </button>
118
+ </div>
119
+ </header>
120
+ <section class="pma-top-bar">
121
+ <div class="pma-chat-controls-row">
122
+ <div class="pma-chat-agent-controls">
123
+ <select id="pma-chat-agent-select" title="Agent"></select>
124
+ <select id="pma-chat-model-select" title="Model"></select>
125
+ <select id="pma-chat-reasoning-select" title="Reasoning"></select>
126
+ </div>
127
+ <div class="pma-chat-actions">
128
+ <button class="ghost sm hidden" id="pma-chat-cancel" title="Cancel">✕</button>
129
+ <button class="pma-new-thread-btn" id="pma-chat-new-thread" title="Start a new chat thread">New
130
+ thread</button>
131
+ <span class="pma-status-pill" id="pma-chat-status">idle</span>
132
+ </div>
133
+ </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
+ </section>
158
+ <section class="pma-chat-section">
159
+ <div class="pma-chat-main">
160
+ <div class="pma-chat-stream" id="pma-chat-stream">
161
+ <div class="pma-chat-error hidden" id="pma-chat-error"></div>
162
+ <div class="pma-history-header" id="pma-chat-history-header">
163
+ <span class="pma-history-label">History</span>
164
+ </div>
165
+ <div class="pma-chat-messages" id="pma-chat-messages"></div>
166
+ <div class="pma-chat-events hidden" id="pma-chat-events">
167
+ <div class="pma-events-header">
168
+ <span class="pma-events-label">Agent</span>
169
+ <span class="pma-events-count" id="pma-chat-events-count">0</span>
170
+ <button class="ghost sm hidden" id="pma-chat-events-toggle">more</button>
171
+ </div>
172
+ <div class="pma-chat-events-list" id="pma-chat-events-list"></div>
173
+ </div>
174
+ </div>
175
+ <div class="pma-chat-compose">
176
+ <div class="pma-attachments-area" id="pma-attachments-area">
177
+ <div class="pma-files-row">
178
+ <span class="pma-files-label">In</span>
179
+ <div class="pma-file-list filebox-list" id="pma-inbox-files"></div>
180
+ </div>
181
+ <div class="pma-files-row">
182
+ <span class="pma-files-label">Out</span>
183
+ <div class="pma-file-list filebox-list" id="pma-outbox-files"></div>
184
+ <button class="pma-icon-btn-small" id="pma-outbox-refresh" title="Refresh files">↻</button>
185
+ </div>
186
+ </div>
187
+ <div class="pma-chat-input-row">
188
+ <textarea id="pma-chat-input" class="pma-chat-input" enterkeyhint="enter"
189
+ placeholder="Ask PMA to help coordinate work..." rows="1" spellcheck="false"></textarea>
190
+ <input type="file" id="pma-chat-upload-input" multiple hidden />
191
+ <button class="ghost sm icon-btn pma-upload-btn" id="pma-chat-upload-btn" title="Upload file">📎</button>
192
+ <button class="pma-send-btn" id="pma-chat-send" title="Send (Cmd+Enter)">↑</button>
193
+ </div>
194
+ <div class="pma-chat-hint">
195
+ <span class="muted small">⌘↵ send · ↵ newline</span>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </section>
200
+ <section class="pma-docs-section hidden" id="pma-docs-section">
201
+ <div class="pma-docs-header">
202
+ <h2>PMA Docs</h2>
203
+ <div class="pma-docs-tabs">
204
+ <button class="pma-docs-tab active" data-doc="AGENTS.md">AGENTS</button>
205
+ <button class="pma-docs-tab" data-doc="active_context.md">Active Context</button>
206
+ <button class="pma-docs-tab" data-doc="context_log.md">Context Log</button>
207
+ </div>
208
+ </div>
209
+ <div class="pma-docs-content">
210
+ <div class="pma-docs-meta" id="pma-docs-meta"></div>
211
+ <div class="pma-docs-editor-wrapper">
212
+ <textarea id="pma-docs-editor" class="pma-docs-editor" spellcheck="false"></textarea>
213
+ </div>
214
+ <div class="pma-docs-actions">
215
+ <button class="ghost sm" id="pma-docs-reset" title="Reset to default">Reset</button>
216
+ <button class="ghost sm" id="pma-docs-snapshot" title="Snapshot active context to log">Snapshot to log</button>
217
+ <button class="primary sm" id="pma-docs-save">Save</button>
218
+ </div>
219
+ </div>
220
+ </section>
221
+ </div>
86
222
  <div class="app-shell" id="repo-shell">
87
223
  <nav class="nav-bar">
88
224
  <span class="nav-brand">CAR</span>
@@ -262,12 +398,33 @@
262
398
  </div>
263
399
  </div>
264
400
  </div>
265
- <div class="messages-reply-box">
266
- <textarea class="messages-reply-body" id="messages-reply-body"
267
- placeholder="Write a reply…"></textarea>
268
- <div class="messages-reply-row">
269
- <input type="file" id="messages-reply-files" multiple />
270
- <div class="messages-reply-actions">
401
+ <div class="messages-compose">
402
+ <div class="messages-attachments">
403
+ <input type="file" id="messages-filebox-upload" multiple hidden />
404
+ <div class="chat-files-row">
405
+ <span class="chat-files-label">In</span>
406
+ <div class="filebox-list" id="messages-filebox-inbox"></div>
407
+ <div class="chat-filebox-actions">
408
+ <button class="ghost sm icon-btn chat-icon-btn" id="messages-filebox-upload-btn"
409
+ title="Upload to inbox">⭱</button>
410
+ <button class="ghost sm icon-btn chat-icon-btn" id="messages-filebox-refresh"
411
+ title="Refresh FileBox">↻</button>
412
+ </div>
413
+ </div>
414
+ <div class="chat-files-row">
415
+ <span class="chat-files-label">Out</span>
416
+ <div class="filebox-list" id="messages-filebox-outbox"></div>
417
+ </div>
418
+ </div>
419
+ <div class="messages-input-row">
420
+ <textarea class="messages-reply-body" id="messages-reply-body" enterkeyhint="enter"
421
+ placeholder="Write a reply…" rows="2" spellcheck="false"></textarea>
422
+ <div class="messages-input-actions">
423
+ <input type="file" id="messages-reply-files" multiple hidden />
424
+ <button class="ghost sm icon-btn messages-attach-btn" id="messages-reply-attach"
425
+ title="Attach files">📎</button>
426
+ <span class="muted small messages-attach-summary hidden" id="messages-reply-attach-summary">No
427
+ files</span>
271
428
  <button class="primary sm" id="messages-reply-send">Send</button>
272
429
  </div>
273
430
  </div>
@@ -358,6 +515,18 @@
358
515
  </button>
359
516
  <div class="workspace-mobile-actions">
360
517
  <span class="muted small" id="workspace-status-mobile"></span>
518
+ <div class="workspace-mobile-menu">
519
+ <button class="ghost sm icon-btn" id="workspace-mobile-menu-toggle"
520
+ title="Workspace actions">⋯</button>
521
+ <div class="workspace-mobile-dropdown hidden" id="workspace-mobile-dropdown">
522
+ <button class="workspace-mobile-item" id="workspace-mobile-upload">Upload files</button>
523
+ <button class="workspace-mobile-item" id="workspace-mobile-new-folder">New folder</button>
524
+ <button class="workspace-mobile-item" id="workspace-mobile-new-file">New file</button>
525
+ <button class="workspace-mobile-item" id="workspace-mobile-download">Download ZIP</button>
526
+ <button class="workspace-mobile-item hidden" id="workspace-mobile-generate">Generate
527
+ tickets</button>
528
+ </div>
529
+ </div>
361
530
  <button class="ghost sm" id="workspace-reload-mobile" title="Reload">↻</button>
362
531
  <button class="primary sm" id="workspace-save-mobile">Save</button>
363
532
  </div>
@@ -434,13 +603,21 @@
434
603
  <button class="ghost sm icon-btn" id="ticket-overflow-toggle" title="More actions">⋯</button>
435
604
  <div class="ticket-overflow-dropdown hidden" id="ticket-overflow-dropdown">
436
605
  <button class="ticket-overflow-item" id="ticket-overflow-new">+ New Ticket</button>
606
+ <button class="ticket-overflow-item hidden" id="ticket-overflow-template">From Template</button>
437
607
  <button class="ticket-overflow-item" id="ticket-overflow-restart" style="display:none;">Restart</button>
438
608
  <button class="ticket-overflow-item" id="ticket-overflow-archive" style="display:none;">Archive
439
609
  Flow</button>
440
610
  </div>
441
611
  </div>
442
- <button class="ghost sm ticket-desktop-only" id="ticket-new-btn" title="Create new ticket">+ New
443
- Ticket</button>
612
+ <!-- Split button: New Ticket + Template dropdown -->
613
+ <div class="ticket-new-split ticket-desktop-only">
614
+ <button class="ghost sm" id="ticket-new-btn" title="Create new ticket">+ New Ticket</button>
615
+ <button class="ghost sm icon-btn ticket-new-dropdown-toggle hidden" id="ticket-new-dropdown-toggle"
616
+ title="Create from template">▾</button>
617
+ <div class="ticket-new-dropdown hidden" id="ticket-new-dropdown">
618
+ <button class="ticket-new-dropdown-item" id="ticket-new-from-template">From Template</button>
619
+ </div>
620
+ </div>
444
621
  </div>
445
622
  <div class="ticket-flow-meta" id="ticket-flow-meta">
446
623
  <div class="ticket-meta-details" id="ticket-meta-details">
@@ -547,10 +724,10 @@
547
724
  <input class="ticket-fm-input" id="ticket-fm-title" placeholder="Title (optional)" spellcheck="false"
548
725
  type="text">
549
726
  <div class="ticket-header-actions">
550
- <button class="ghost sm icon-btn ticket-nav-btn" id="ticket-nav-prev" title="Previous ticket ()"
551
- aria-label="Previous ticket" aria-keyshortcuts="ArrowLeft Alt+ArrowLeft">←</button>
552
- <button class="ghost sm icon-btn ticket-nav-btn" id="ticket-nav-next" title="Next ticket ()"
553
- aria-label="Next ticket" aria-keyshortcuts="ArrowRight Alt+ArrowRight">→</button>
727
+ <button class="ghost sm icon-btn ticket-nav-btn" id="ticket-nav-prev" title="Previous ticket (Alt+←)"
728
+ aria-label="Previous ticket" aria-keyshortcuts="Alt+ArrowLeft">←</button>
729
+ <button class="ghost sm icon-btn ticket-nav-btn" id="ticket-nav-next" title="Next ticket (Alt+→)"
730
+ aria-label="Next ticket" aria-keyshortcuts="Alt+ArrowRight">→</button>
554
731
  <label class="ticket-fm-toggle" title="Mark as done">
555
732
  <input id="ticket-fm-done" type="checkbox">
556
733
  <span class="ticket-fm-toggle-label">Done</span>
@@ -636,6 +813,60 @@
636
813
  </div>
637
814
  </div>
638
815
  </div>
816
+ <!-- Template Picker Modal -->
817
+ <div class="modal-overlay hidden" id="ticket-template-modal">
818
+ <div class="modal-content ticket-template-modal-content">
819
+ <div class="modal-header ticket-template-header">
820
+ <span class="ticket-template-title">New Ticket from Template</span>
821
+ <button class="ghost sm icon-btn" id="ticket-template-close" title="Close">×</button>
822
+ </div>
823
+ <div class="ticket-template-body">
824
+ <!-- Recent templates (shown if history exists) -->
825
+ <div class="ticket-template-recent hidden" id="ticket-template-recent"></div>
826
+
827
+ <!-- Main input section -->
828
+ <div class="ticket-template-input-section">
829
+ <div class="ticket-template-input-wrapper">
830
+ <input class="ticket-template-input" id="ticket-template-ref" type="text"
831
+ placeholder="repo:path/to/template.md" spellcheck="false" autocomplete="off">
832
+ <button class="ticket-template-clear hidden" id="ticket-template-clear" type="button"
833
+ title="Clear">×</button>
834
+ <span class="ticket-template-input-hint" id="ticket-template-hint">or paste GitHub URL</span>
835
+ </div>
836
+ <div class="ticket-template-repos" id="ticket-template-repos"></div>
837
+ </div>
838
+
839
+ <!-- Preview -->
840
+ <div class="ticket-template-preview-section">
841
+ <div class="ticket-template-preview-header">
842
+ <span class="ticket-template-preview-label">Preview</span>
843
+ <span class="ticket-template-preview-status" id="ticket-template-preview-status"></span>
844
+ </div>
845
+ <pre class="ticket-template-preview" id="ticket-template-preview"></pre>
846
+ </div>
847
+
848
+ <!-- Error -->
849
+ <div class="ticket-template-error hidden" id="ticket-template-error"></div>
850
+
851
+ <!-- Footer -->
852
+ <div class="ticket-template-footer">
853
+ <div class="ticket-template-options">
854
+ <label class="ticket-template-option-label">Agent:</label>
855
+ <select class="ticket-template-agent-select" id="ticket-template-agent">
856
+ <option value="">default</option>
857
+ <option value="codex">codex</option>
858
+ <option value="opencode">opencode</option>
859
+ <option value="user">user</option>
860
+ </select>
861
+ </div>
862
+ <div class="ticket-template-actions">
863
+ <button class="ghost sm" id="ticket-template-cancel">Cancel</button>
864
+ <button class="primary sm" id="ticket-template-apply" disabled>Create</button>
865
+ </div>
866
+ </div>
867
+ </div>
868
+ </div>
869
+ </div>
639
870
  </section>
640
871
  <section class="panel" id="terminal">
641
872
  <div class="terminal-toolbar">
@@ -879,12 +1110,60 @@
879
1110
  </div>
880
1111
  <span class="form-hint">Resetting conversations clears stored thread IDs and restarts chats.</span>
881
1112
  </div>
1113
+ <div class="form-group">
1114
+ <label>Template Repositories</label>
1115
+ <div class="template-repos-list" id="template-repos-list"></div>
1116
+ <div class="settings-actions">
1117
+ <button class="ghost sm" id="template-repos-add">+ Add repository</button>
1118
+ </div>
1119
+ <div class="template-repo-form hidden" id="template-repo-form">
1120
+ <div class="settings-grid">
1121
+ <div>
1122
+ <label class="muted small" for="repo-id">ID</label>
1123
+ <input autocomplete="off" id="repo-id" placeholder="my-team" spellcheck="false" type="text" />
1124
+ <span class="form-hint">Used in refs like <span class="mono">id:path/to/file.md</span></span>
1125
+ </div>
1126
+ <div>
1127
+ <label class="muted small" for="repo-url">Git URL</label>
1128
+ <input autocomplete="off" id="repo-url" placeholder="https://github.com/org/repo.git" spellcheck="false"
1129
+ type="text" />
1130
+ </div>
1131
+ <div>
1132
+ <label class="muted small" for="repo-ref">Default ref</label>
1133
+ <input autocomplete="off" id="repo-ref" placeholder="main" spellcheck="false" type="text"
1134
+ value="main" />
1135
+ </div>
1136
+ <div class="settings-toggle-row">
1137
+ <label class="form-checkbox">
1138
+ <input id="repo-trusted" type="checkbox" />
1139
+ <span>Trusted (skip scanning)</span>
1140
+ </label>
1141
+ </div>
1142
+ </div>
1143
+ <div class="settings-actions">
1144
+ <button class="primary sm" id="repo-save">Save</button>
1145
+ <button class="ghost sm" id="repo-cancel">Cancel</button>
1146
+ </div>
1147
+ </div>
1148
+ <span class="form-hint">Edits are written to <span class="mono">codex-autorunner.override.yml</span>.</span>
1149
+ </div>
882
1150
  </div>
883
1151
  <div class="modal-actions">
884
1152
  <button class="ghost" id="repo-settings-close">Close</button>
885
1153
  </div>
886
1154
  </div>
887
1155
  </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>
888
1167
  <!-- Reason Details Modal -->
889
1168
  <div class="modal-overlay" hidden="" id="reason-modal">
890
1169
  <div aria-describedby="reason-modal-content" aria-labelledby="reason-modal-title" aria-modal="true"
@@ -953,4 +1232,4 @@
953
1232
  <script data-car-loader="" src="static/loader.js?v=__CAR_ASSET_VERSION__"></script>
954
1233
  </body>
955
1234
 
956
- </html>
1235
+ </html>
@@ -4,6 +4,7 @@ import { subscribe } from "./bus.js";
4
4
  import { isRepoHealthy } from "./health.js";
5
5
  import { preserveScroll } from "./preserve.js";
6
6
  import { createSmartRefresh } from "./smartRefresh.js";
7
+ import { createFileBoxWidget } from "./fileboxUi.js";
7
8
  let bellInitialized = false;
8
9
  let messagesInitialized = false;
9
10
  let activeRunId = null;
@@ -17,8 +18,16 @@ const refreshEl = document.getElementById("messages-refresh");
17
18
  const replyBodyEl = document.getElementById("messages-reply-body");
18
19
  const replyFilesEl = document.getElementById("messages-reply-files");
19
20
  const replySendEl = document.getElementById("messages-reply-send");
21
+ const replyAttachBtn = document.getElementById("messages-reply-attach");
22
+ const replyAttachSummary = document.getElementById("messages-reply-attach-summary");
23
+ const fileBoxInboxEl = document.getElementById("messages-filebox-inbox");
24
+ const fileBoxOutboxEl = document.getElementById("messages-filebox-outbox");
25
+ const fileBoxUploadEl = document.getElementById("messages-filebox-upload");
26
+ const fileBoxUploadBtn = document.getElementById("messages-filebox-upload-btn");
27
+ const fileBoxRefreshBtn = document.getElementById("messages-filebox-refresh");
20
28
  let threadListRefreshCount = 0;
21
29
  let threadDetailRefreshCount = 0;
30
+ let fileBoxCtrl = null;
22
31
  function isMobileViewport() {
23
32
  return window.innerWidth <= 640;
24
33
  }
@@ -30,6 +39,21 @@ function showThreadDetail() {
30
39
  layoutEl?.classList.add("viewing-detail");
31
40
  }
32
41
  }
42
+ function initFileBox() {
43
+ if (fileBoxCtrl || (!fileBoxInboxEl && !fileBoxOutboxEl))
44
+ return;
45
+ fileBoxCtrl = createFileBoxWidget({
46
+ scope: "repo",
47
+ inboxEl: fileBoxInboxEl,
48
+ outboxEl: fileBoxOutboxEl,
49
+ uploadInput: fileBoxUploadEl,
50
+ uploadBtn: fileBoxUploadBtn,
51
+ refreshBtn: fileBoxRefreshBtn,
52
+ uploadBox: "inbox",
53
+ emptyMessage: "No files",
54
+ });
55
+ void fileBoxCtrl.refresh();
56
+ }
33
57
  function setThreadListRefreshing(active) {
34
58
  if (!threadsEl)
35
59
  return;
@@ -342,6 +366,27 @@ function formatBytes(size) {
342
366
  return `${(size / 1000).toFixed(0)} KB`;
343
367
  return `${size} B`;
344
368
  }
369
+ function isSafeHref(url) {
370
+ const trimmed = (url || "").trim();
371
+ if (!trimmed)
372
+ return false;
373
+ const lower = trimmed.toLowerCase();
374
+ if (lower.startsWith("javascript:"))
375
+ return false;
376
+ if (lower.startsWith("data:"))
377
+ return false;
378
+ if (lower.startsWith("vbscript:"))
379
+ return false;
380
+ if (lower.startsWith("file:"))
381
+ return false;
382
+ return (lower.startsWith("http://") ||
383
+ lower.startsWith("https://") ||
384
+ trimmed.startsWith("/") ||
385
+ trimmed.startsWith("./") ||
386
+ trimmed.startsWith("../") ||
387
+ trimmed.startsWith("#") ||
388
+ lower.startsWith("mailto:"));
389
+ }
345
390
  export function renderMarkdown(body) {
346
391
  if (!body)
347
392
  return "";
@@ -365,7 +410,11 @@ export function renderMarkdown(body) {
365
410
  text = text.replace(/\*([^*]+)\*/g, "<em>$1</em>");
366
411
  // Extract markdown links [text](url) to avoid double-linking
367
412
  const links = [];
368
- text = text.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, (_m, label, url) => {
413
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, label, rawUrl) => {
414
+ const url = (rawUrl || "").trim();
415
+ if (!isSafeHref(url)) {
416
+ return match;
417
+ }
369
418
  const placeholder = `@@LINK_${links.length}@@`;
370
419
  // Note: label and url are already escaped because text is escaped.
371
420
  links.push(`<a href="${url}" target="_blank" rel="noopener">${label}</a>`);
@@ -433,7 +482,8 @@ export function renderMarkdown(body) {
433
482
  const idx = Number(match[1]);
434
483
  return codeBlocks[idx] ?? "";
435
484
  }
436
- return `<p>${block.replace(/\n/g, "<br>")}</p>`;
485
+ const content = block.replace(/\n/g, "<br>").replace(/@@CODEBLOCK_(\d+)@@/g, (_m, id) => codeBlocks[Number(id)] ?? "");
486
+ return `<p>${content}</p>`;
437
487
  })
438
488
  .join("");
439
489
  }
@@ -444,9 +494,12 @@ function renderFiles(files) {
444
494
  .map((f) => {
445
495
  const size = formatBytes(f.size);
446
496
  const href = resolvePath(f.url || "");
497
+ const isHttp = /^https?:\/\//.test(href);
498
+ const targetAttrs = isHttp ? ' target="_blank" rel="noopener"' : "";
499
+ const downloadAttr = isHttp ? "" : " download";
447
500
  return `<li class="messages-file">
448
501
  <span class="messages-file-icon">📎</span>
449
- <a href="${escapeHtml(href)}" target="_blank" rel="noopener">${escapeHtml(f.name)}</a>
502
+ <a href="${escapeHtml(href)}"${downloadAttr}${targetAttrs}>${escapeHtml(f.name)}</a>
450
503
  ${size ? `<span class="messages-file-size muted small">${escapeHtml(size)}</span>` : ""}
451
504
  </li>`;
452
505
  })
@@ -758,7 +811,7 @@ function renderThreadDetail(detail, runId, ctx) {
758
811
  attachCollapseHandlers();
759
812
  }
760
813
  // Only show reply box for paused runs - replies to other states won't be seen
761
- const replyBoxEl = document.querySelector(".messages-reply-box");
814
+ const replyBoxEl = document.querySelector(".messages-compose");
762
815
  if (replyBoxEl) {
763
816
  replyBoxEl.classList.toggle("hidden", !isPaused);
764
817
  }
@@ -770,6 +823,19 @@ function renderThreadDetail(detail, runId, ctx) {
770
823
  });
771
824
  }
772
825
  }
826
+ function refreshAttachSummary() {
827
+ if (!replyAttachSummary || !replyFilesEl)
828
+ return;
829
+ const files = Array.from(replyFilesEl.files || []);
830
+ if (!files.length) {
831
+ replyAttachSummary.textContent = "";
832
+ replyAttachSummary.classList.add("hidden");
833
+ return;
834
+ }
835
+ const label = files.length > 2 ? `${files.length} files` : files.map((f) => f.name).join(", ");
836
+ replyAttachSummary.textContent = label;
837
+ replyAttachSummary.classList.remove("hidden");
838
+ }
773
839
  async function sendReply() {
774
840
  const runId = selectedRunId;
775
841
  if (!runId) {
@@ -795,6 +861,7 @@ async function sendReply() {
795
861
  replyBodyEl.value = "";
796
862
  if (replyFilesEl)
797
863
  replyFilesEl.value = "";
864
+ refreshAttachSummary();
798
865
  flash("Reply sent", "success");
799
866
  // Always resume after sending
800
867
  await api(`/api/flows/${encodeURIComponent(runId)}/resume`, { method: "POST" });
@@ -812,6 +879,7 @@ export function initMessages() {
812
879
  if (!threadsEl || !detailEl)
813
880
  return;
814
881
  messagesInitialized = true;
882
+ initFileBox();
815
883
  backBtn?.addEventListener("click", showThreadList);
816
884
  window.addEventListener("resize", () => {
817
885
  if (!isMobileViewport()) {
@@ -827,6 +895,13 @@ export function initMessages() {
827
895
  replySendEl?.addEventListener("click", () => {
828
896
  void sendReply();
829
897
  });
898
+ replyAttachBtn?.addEventListener("click", () => {
899
+ replyFilesEl?.click();
900
+ });
901
+ replyFilesEl?.addEventListener("change", () => {
902
+ refreshAttachSummary();
903
+ });
904
+ refreshAttachSummary();
830
905
  // Load threads immediately, and try to open run_id from URL if present.
831
906
  void loadThreads("initial").then(() => {
832
907
  const params = getUrlParams();
@@ -868,6 +943,9 @@ export function initMessages() {
868
943
  if (selectedRunId) {
869
944
  void loadThread(selectedRunId, "background");
870
945
  }
946
+ if (status === "ok" || status === "degraded") {
947
+ void fileBoxCtrl?.refresh();
948
+ }
871
949
  }
872
950
  });
873
951
  }