codex-autorunner 0.1.2__py3-none-any.whl → 1.1.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 (276) hide show
  1. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/__main__.py +4 -0
  3. codex_autorunner/agents/codex/harness.py +1 -1
  4. codex_autorunner/agents/opencode/client.py +68 -35
  5. codex_autorunner/agents/opencode/constants.py +3 -0
  6. codex_autorunner/agents/opencode/harness.py +6 -1
  7. codex_autorunner/agents/opencode/logging.py +21 -5
  8. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  9. codex_autorunner/agents/opencode/runtime.py +176 -47
  10. codex_autorunner/agents/opencode/supervisor.py +36 -48
  11. codex_autorunner/agents/registry.py +155 -8
  12. codex_autorunner/api.py +25 -0
  13. codex_autorunner/bootstrap.py +22 -37
  14. codex_autorunner/cli.py +5 -1156
  15. codex_autorunner/codex_cli.py +20 -84
  16. codex_autorunner/core/__init__.py +4 -0
  17. codex_autorunner/core/about_car.py +49 -32
  18. codex_autorunner/core/adapter_utils.py +21 -0
  19. codex_autorunner/core/app_server_ids.py +59 -0
  20. codex_autorunner/core/app_server_logging.py +7 -3
  21. codex_autorunner/core/app_server_prompts.py +27 -260
  22. codex_autorunner/core/app_server_threads.py +26 -28
  23. codex_autorunner/core/app_server_utils.py +165 -0
  24. codex_autorunner/core/archive.py +349 -0
  25. codex_autorunner/core/codex_runner.py +12 -2
  26. codex_autorunner/core/config.py +587 -103
  27. codex_autorunner/core/docs.py +10 -2
  28. codex_autorunner/core/drafts.py +136 -0
  29. codex_autorunner/core/engine.py +1531 -866
  30. codex_autorunner/core/exceptions.py +4 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +202 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +88 -0
  35. codex_autorunner/core/flows/reasons.py +52 -0
  36. codex_autorunner/core/flows/reconciler.py +131 -0
  37. codex_autorunner/core/flows/runtime.py +382 -0
  38. codex_autorunner/core/flows/store.py +568 -0
  39. codex_autorunner/core/flows/transition.py +138 -0
  40. codex_autorunner/core/flows/ux_helpers.py +257 -0
  41. codex_autorunner/core/flows/worker_process.py +242 -0
  42. codex_autorunner/core/git_utils.py +62 -0
  43. codex_autorunner/core/hub.py +136 -16
  44. codex_autorunner/core/locks.py +4 -0
  45. codex_autorunner/core/notifications.py +14 -2
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/core/ports/agent_backend.py +150 -0
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/core/ports/run_event.py +91 -0
  50. codex_autorunner/core/prompt.py +15 -7
  51. codex_autorunner/core/redaction.py +29 -0
  52. codex_autorunner/core/review_context.py +5 -8
  53. codex_autorunner/core/run_index.py +6 -0
  54. codex_autorunner/core/runner_process.py +5 -2
  55. codex_autorunner/core/state.py +0 -88
  56. codex_autorunner/core/state_roots.py +57 -0
  57. codex_autorunner/core/supervisor_protocol.py +15 -0
  58. codex_autorunner/core/supervisor_utils.py +67 -0
  59. codex_autorunner/core/text_delta_coalescer.py +54 -0
  60. codex_autorunner/core/ticket_linter_cli.py +201 -0
  61. codex_autorunner/core/ticket_manager_cli.py +432 -0
  62. codex_autorunner/core/update.py +24 -16
  63. codex_autorunner/core/update_paths.py +28 -0
  64. codex_autorunner/core/update_runner.py +2 -0
  65. codex_autorunner/core/usage.py +164 -12
  66. codex_autorunner/core/utils.py +120 -11
  67. codex_autorunner/discovery.py +2 -4
  68. codex_autorunner/flows/review/__init__.py +17 -0
  69. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  70. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  71. codex_autorunner/flows/ticket_flow/definition.py +98 -0
  72. codex_autorunner/integrations/agents/__init__.py +17 -0
  73. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  74. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  75. codex_autorunner/integrations/agents/codex_backend.py +448 -0
  76. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  77. codex_autorunner/integrations/agents/opencode_backend.py +598 -0
  78. codex_autorunner/integrations/agents/runner.py +91 -0
  79. codex_autorunner/integrations/agents/wiring.py +271 -0
  80. codex_autorunner/integrations/app_server/client.py +583 -152
  81. codex_autorunner/integrations/app_server/env.py +2 -107
  82. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  83. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  84. codex_autorunner/integrations/telegram/adapter.py +204 -165
  85. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  86. codex_autorunner/integrations/telegram/config.py +221 -0
  87. codex_autorunner/integrations/telegram/constants.py +17 -2
  88. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  89. codex_autorunner/integrations/telegram/doctor.py +47 -0
  90. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
  91. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  92. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  93. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  94. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
  95. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  96. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  97. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  98. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
  99. codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
  100. codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
  101. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  102. codex_autorunner/integrations/telegram/helpers.py +111 -16
  103. codex_autorunner/integrations/telegram/outbox.py +208 -37
  104. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  105. codex_autorunner/integrations/telegram/service.py +221 -42
  106. codex_autorunner/integrations/telegram/state.py +100 -2
  107. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
  108. codex_autorunner/integrations/telegram/transport.py +39 -4
  109. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  110. codex_autorunner/manifest.py +2 -0
  111. codex_autorunner/plugin_api.py +22 -0
  112. codex_autorunner/routes/__init__.py +37 -67
  113. codex_autorunner/routes/agents.py +2 -137
  114. codex_autorunner/routes/analytics.py +3 -0
  115. codex_autorunner/routes/app_server.py +2 -131
  116. codex_autorunner/routes/base.py +2 -624
  117. codex_autorunner/routes/file_chat.py +7 -0
  118. codex_autorunner/routes/flows.py +7 -0
  119. codex_autorunner/routes/messages.py +7 -0
  120. codex_autorunner/routes/repos.py +2 -196
  121. codex_autorunner/routes/review.py +2 -147
  122. codex_autorunner/routes/sessions.py +2 -175
  123. codex_autorunner/routes/settings.py +2 -168
  124. codex_autorunner/routes/shared.py +2 -275
  125. codex_autorunner/routes/system.py +4 -188
  126. codex_autorunner/routes/usage.py +3 -0
  127. codex_autorunner/routes/voice.py +2 -119
  128. codex_autorunner/routes/workspace.py +3 -0
  129. codex_autorunner/server.py +3 -2
  130. codex_autorunner/static/agentControls.js +41 -11
  131. codex_autorunner/static/agentEvents.js +248 -0
  132. codex_autorunner/static/app.js +35 -24
  133. codex_autorunner/static/archive.js +826 -0
  134. codex_autorunner/static/archiveApi.js +37 -0
  135. codex_autorunner/static/autoRefresh.js +36 -8
  136. codex_autorunner/static/bootstrap.js +1 -0
  137. codex_autorunner/static/bus.js +1 -0
  138. codex_autorunner/static/cache.js +1 -0
  139. codex_autorunner/static/constants.js +20 -4
  140. codex_autorunner/static/dashboard.js +344 -325
  141. codex_autorunner/static/diffRenderer.js +37 -0
  142. codex_autorunner/static/docChatCore.js +324 -0
  143. codex_autorunner/static/docChatStorage.js +65 -0
  144. codex_autorunner/static/docChatVoice.js +65 -0
  145. codex_autorunner/static/docEditor.js +133 -0
  146. codex_autorunner/static/env.js +1 -0
  147. codex_autorunner/static/eventSummarizer.js +166 -0
  148. codex_autorunner/static/fileChat.js +182 -0
  149. codex_autorunner/static/health.js +155 -0
  150. codex_autorunner/static/hub.js +126 -185
  151. codex_autorunner/static/index.html +839 -863
  152. codex_autorunner/static/liveUpdates.js +1 -0
  153. codex_autorunner/static/loader.js +1 -0
  154. codex_autorunner/static/messages.js +873 -0
  155. codex_autorunner/static/mobileCompact.js +2 -1
  156. codex_autorunner/static/preserve.js +17 -0
  157. codex_autorunner/static/settings.js +149 -217
  158. codex_autorunner/static/smartRefresh.js +52 -0
  159. codex_autorunner/static/styles.css +8850 -3876
  160. codex_autorunner/static/tabs.js +175 -11
  161. codex_autorunner/static/terminal.js +32 -0
  162. codex_autorunner/static/terminalManager.js +34 -59
  163. codex_autorunner/static/ticketChatActions.js +333 -0
  164. codex_autorunner/static/ticketChatEvents.js +16 -0
  165. codex_autorunner/static/ticketChatStorage.js +16 -0
  166. codex_autorunner/static/ticketChatStream.js +264 -0
  167. codex_autorunner/static/ticketEditor.js +844 -0
  168. codex_autorunner/static/ticketVoice.js +9 -0
  169. codex_autorunner/static/tickets.js +1988 -0
  170. codex_autorunner/static/utils.js +43 -3
  171. codex_autorunner/static/voice.js +1 -0
  172. codex_autorunner/static/workspace.js +765 -0
  173. codex_autorunner/static/workspaceApi.js +53 -0
  174. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  175. codex_autorunner/surfaces/__init__.py +5 -0
  176. codex_autorunner/surfaces/cli/__init__.py +6 -0
  177. codex_autorunner/surfaces/cli/cli.py +1224 -0
  178. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  179. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  180. codex_autorunner/surfaces/web/__init__.py +1 -0
  181. codex_autorunner/surfaces/web/app.py +2019 -0
  182. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  183. codex_autorunner/surfaces/web/middleware.py +587 -0
  184. codex_autorunner/surfaces/web/pty_session.py +370 -0
  185. codex_autorunner/surfaces/web/review.py +6 -0
  186. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  187. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  188. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  189. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  190. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  191. codex_autorunner/surfaces/web/routes/base.py +615 -0
  192. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  193. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  194. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  195. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  196. codex_autorunner/surfaces/web/routes/review.py +148 -0
  197. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  198. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  199. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  200. codex_autorunner/surfaces/web/routes/system.py +196 -0
  201. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  202. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  203. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  204. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  205. codex_autorunner/surfaces/web/schemas.py +417 -0
  206. codex_autorunner/surfaces/web/static_assets.py +490 -0
  207. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  208. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  209. codex_autorunner/tickets/__init__.py +27 -0
  210. codex_autorunner/tickets/agent_pool.py +399 -0
  211. codex_autorunner/tickets/files.py +89 -0
  212. codex_autorunner/tickets/frontmatter.py +55 -0
  213. codex_autorunner/tickets/lint.py +102 -0
  214. codex_autorunner/tickets/models.py +97 -0
  215. codex_autorunner/tickets/outbox.py +244 -0
  216. codex_autorunner/tickets/replies.py +179 -0
  217. codex_autorunner/tickets/runner.py +881 -0
  218. codex_autorunner/tickets/spec_ingest.py +77 -0
  219. codex_autorunner/web/__init__.py +5 -1
  220. codex_autorunner/web/app.py +2 -1771
  221. codex_autorunner/web/hub_jobs.py +2 -191
  222. codex_autorunner/web/middleware.py +2 -587
  223. codex_autorunner/web/pty_session.py +2 -369
  224. codex_autorunner/web/runner_manager.py +2 -24
  225. codex_autorunner/web/schemas.py +2 -396
  226. codex_autorunner/web/static_assets.py +4 -484
  227. codex_autorunner/web/static_refresh.py +2 -85
  228. codex_autorunner/web/terminal_sessions.py +2 -77
  229. codex_autorunner/workspace/__init__.py +40 -0
  230. codex_autorunner/workspace/paths.py +335 -0
  231. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  232. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  233. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
  234. codex_autorunner/agents/execution/policy.py +0 -292
  235. codex_autorunner/agents/factory.py +0 -52
  236. codex_autorunner/agents/orchestrator.py +0 -358
  237. codex_autorunner/core/doc_chat.py +0 -1446
  238. codex_autorunner/core/snapshot.py +0 -580
  239. codex_autorunner/integrations/github/chatops.py +0 -268
  240. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  241. codex_autorunner/routes/docs.py +0 -381
  242. codex_autorunner/routes/github.py +0 -327
  243. codex_autorunner/routes/runs.py +0 -250
  244. codex_autorunner/spec_ingest.py +0 -812
  245. codex_autorunner/static/docChatActions.js +0 -287
  246. codex_autorunner/static/docChatEvents.js +0 -300
  247. codex_autorunner/static/docChatRender.js +0 -205
  248. codex_autorunner/static/docChatStream.js +0 -361
  249. codex_autorunner/static/docs.js +0 -20
  250. codex_autorunner/static/docsClipboard.js +0 -69
  251. codex_autorunner/static/docsCrud.js +0 -257
  252. codex_autorunner/static/docsDocUpdates.js +0 -62
  253. codex_autorunner/static/docsDrafts.js +0 -16
  254. codex_autorunner/static/docsElements.js +0 -69
  255. codex_autorunner/static/docsInit.js +0 -285
  256. codex_autorunner/static/docsParse.js +0 -160
  257. codex_autorunner/static/docsSnapshot.js +0 -87
  258. codex_autorunner/static/docsSpecIngest.js +0 -263
  259. codex_autorunner/static/docsState.js +0 -127
  260. codex_autorunner/static/docsThreadRegistry.js +0 -44
  261. codex_autorunner/static/docsUi.js +0 -153
  262. codex_autorunner/static/docsVoice.js +0 -56
  263. codex_autorunner/static/github.js +0 -504
  264. codex_autorunner/static/logs.js +0 -678
  265. codex_autorunner/static/review.js +0 -157
  266. codex_autorunner/static/runs.js +0 -418
  267. codex_autorunner/static/snapshot.js +0 -124
  268. codex_autorunner/static/state.js +0 -94
  269. codex_autorunner/static/todoPreview.js +0 -27
  270. codex_autorunner/workspace.py +0 -16
  271. codex_autorunner-0.1.2.dist-info/METADATA +0 -249
  272. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  273. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  274. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  275. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  276. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,333 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ /**
3
+ * Ticket Chat Actions - handles sending messages, applying/discarding patches
4
+ */
5
+ import { api, flash, splitMarkdownFrontmatter } from "./utils.js";
6
+ import { performTicketChatRequest } from "./ticketChatStream.js";
7
+ import { renderTicketMessages } from "./ticketChatEvents.js";
8
+ import { publish } from "./bus.js";
9
+ import { createDocChat } from "./docChatCore.js";
10
+ import { saveTicketChatHistory } from "./ticketChatStorage.js";
11
+ import { renderDiff } from "./diffRenderer.js";
12
+ // Limits for events display
13
+ export const TICKET_CHAT_EVENT_LIMIT = 8;
14
+ export const TICKET_CHAT_EVENT_MAX = 50;
15
+ export const ticketChat = createDocChat({
16
+ idPrefix: "ticket-chat",
17
+ storage: { keyPrefix: "car-ticket-chat-", maxMessages: 50, version: 1 },
18
+ limits: { eventVisible: TICKET_CHAT_EVENT_LIMIT, eventMax: TICKET_CHAT_EVENT_MAX },
19
+ styling: {
20
+ eventClass: "ticket-chat-event",
21
+ eventTitleClass: "ticket-chat-event-title",
22
+ eventSummaryClass: "ticket-chat-event-summary",
23
+ eventDetailClass: "ticket-chat-event-detail",
24
+ eventMetaClass: "ticket-chat-event-meta",
25
+ eventsEmptyClass: "ticket-chat-events-empty",
26
+ eventsWaitingClass: "ticket-chat-events-waiting",
27
+ eventsHiddenClass: "hidden",
28
+ messagesClass: "ticket-chat-message",
29
+ messageRoleClass: "ticket-chat-message-role",
30
+ messageContentClass: "ticket-chat-message-content",
31
+ messageMetaClass: "ticket-chat-message-meta",
32
+ messageUserClass: "user",
33
+ messageAssistantClass: "assistant",
34
+ messageAssistantThinkingClass: "thinking",
35
+ messageAssistantFinalClass: "final",
36
+ },
37
+ });
38
+ // Extend state with ticket-specific fields
39
+ export const ticketChatState = Object.assign(ticketChat.state, {
40
+ ticketIndex: null,
41
+ draft: null,
42
+ });
43
+ export function getTicketChatElements() {
44
+ const base = ticketChat.elements;
45
+ return {
46
+ input: base.input,
47
+ sendBtn: base.sendBtn,
48
+ voiceBtn: base.voiceBtn,
49
+ cancelBtn: base.cancelBtn,
50
+ newThreadBtn: base.newThreadBtn,
51
+ statusEl: base.statusEl,
52
+ streamEl: base.streamEl,
53
+ eventsMain: base.eventsMain,
54
+ eventsList: base.eventsList,
55
+ eventsCount: base.eventsCount,
56
+ eventsToggle: base.eventsToggle,
57
+ messagesEl: base.messagesEl,
58
+ // Content area elements - mutually exclusive with patch preview
59
+ contentTextarea: document.getElementById("ticket-editor-content"),
60
+ contentToolbar: document.getElementById("ticket-editor-toolbar"),
61
+ // Patch preview elements - mutually exclusive with content area
62
+ patchMain: document.getElementById("ticket-patch-main"),
63
+ patchBody: document.getElementById("ticket-patch-body"),
64
+ patchStatus: document.getElementById("ticket-patch-status"),
65
+ applyBtn: document.getElementById("ticket-patch-apply"),
66
+ discardBtn: document.getElementById("ticket-patch-discard"),
67
+ agentSelect: document.getElementById("ticket-chat-agent-select"),
68
+ modelSelect: document.getElementById("ticket-chat-model-select"),
69
+ reasoningSelect: document.getElementById("ticket-chat-reasoning-select"),
70
+ };
71
+ }
72
+ export function resetTicketChatState() {
73
+ ticketChatState.status = "idle";
74
+ ticketChatState.error = "";
75
+ ticketChatState.streamText = "";
76
+ ticketChatState.statusText = "";
77
+ ticketChatState.controller = null;
78
+ // Note: events are cleared at the start of each new request, not here
79
+ // Messages persist across requests within the same ticket
80
+ }
81
+ export async function startNewTicketChatThread() {
82
+ if (ticketChatState.ticketIndex == null)
83
+ return;
84
+ const confirmed = window.confirm("Start a new conversation thread for this ticket?");
85
+ if (!confirmed)
86
+ return;
87
+ try {
88
+ const key = `ticket_chat.${ticketChatState.ticketIndex}`;
89
+ await api(`/api/app-server/threads/reset`, {
90
+ method: "POST",
91
+ body: { key },
92
+ });
93
+ // Clear local message history
94
+ ticketChatState.messages = [];
95
+ saveTicketChatHistory(ticketChatState.ticketIndex, []);
96
+ clearTicketEvents();
97
+ flash("New thread started");
98
+ }
99
+ catch (err) {
100
+ flash(`Failed to reset thread: ${err.message}`, "error");
101
+ }
102
+ finally {
103
+ renderTicketChat();
104
+ renderTicketMessages();
105
+ }
106
+ }
107
+ /**
108
+ * Clear events at the start of a new request.
109
+ * Events are transient (thinking/tool calls) and reset each turn.
110
+ */
111
+ export function clearTicketEvents() {
112
+ ticketChat.clearEvents();
113
+ }
114
+ /**
115
+ * Add a user message to the chat history.
116
+ */
117
+ export function addUserMessage(content) {
118
+ ticketChat.addUserMessage(content);
119
+ }
120
+ /**
121
+ * Add an assistant message to the chat history.
122
+ * Prevents duplicates by checking if the same content was just added.
123
+ */
124
+ export function addAssistantMessage(content, isFinal = true) {
125
+ ticketChat.addAssistantMessage(content, isFinal);
126
+ }
127
+ export function setTicketIndex(index) {
128
+ const changed = ticketChatState.ticketIndex !== index;
129
+ ticketChatState.ticketIndex = index;
130
+ ticketChatState.draft = null;
131
+ resetTicketChatState();
132
+ // Clear chat history when switching tickets
133
+ if (changed) {
134
+ ticketChat.setTarget(index != null ? String(index) : null);
135
+ }
136
+ }
137
+ export function renderTicketChat() {
138
+ const els = getTicketChatElements();
139
+ // Shared chat render (status, events, messages)
140
+ ticketChat.render();
141
+ // MUTUALLY EXCLUSIVE: Show either the content editor OR the patch preview, never both.
142
+ // This prevents confusion about which view is the "current" state.
143
+ const hasDraft = !!ticketChatState.draft;
144
+ // Hide content area when showing patch preview
145
+ if (els.contentTextarea) {
146
+ els.contentTextarea.classList.toggle("hidden", hasDraft);
147
+ }
148
+ if (els.contentToolbar) {
149
+ els.contentToolbar.classList.toggle("hidden", hasDraft);
150
+ }
151
+ // Show patch preview only when there's a draft
152
+ if (els.patchMain) {
153
+ els.patchMain.classList.toggle("hidden", !hasDraft);
154
+ if (hasDraft) {
155
+ if (els.patchBody) {
156
+ renderDiff(ticketChatState.draft.patch || "(no changes)", els.patchBody);
157
+ }
158
+ if (els.patchStatus) {
159
+ els.patchStatus.textContent = ticketChatState.draft.agentMessage || "";
160
+ }
161
+ }
162
+ }
163
+ }
164
+ export async function sendTicketChat() {
165
+ const els = getTicketChatElements();
166
+ const message = (els.input?.value || "").trim();
167
+ if (!message) {
168
+ ticketChatState.error = "Enter a message to send.";
169
+ renderTicketChat();
170
+ return;
171
+ }
172
+ if (ticketChatState.status === "running") {
173
+ ticketChatState.error = "Ticket chat already running.";
174
+ renderTicketChat();
175
+ flash("Ticket chat already running", "error");
176
+ return;
177
+ }
178
+ if (ticketChatState.ticketIndex == null) {
179
+ ticketChatState.error = "No ticket selected.";
180
+ renderTicketChat();
181
+ return;
182
+ }
183
+ resetTicketChatState();
184
+ ticketChatState.status = "running";
185
+ ticketChatState.statusText = "queued";
186
+ ticketChatState.controller = new AbortController();
187
+ renderTicketChat();
188
+ if (els.input) {
189
+ els.input.value = "";
190
+ }
191
+ const agent = els.agentSelect?.value || "codex";
192
+ const model = els.modelSelect?.value || undefined;
193
+ const reasoning = els.reasoningSelect?.value || undefined;
194
+ try {
195
+ await performTicketChatRequest(ticketChatState.ticketIndex, message, ticketChatState.controller.signal, {
196
+ agent,
197
+ model,
198
+ reasoning,
199
+ });
200
+ // Try to load any pending draft
201
+ await loadTicketPending(ticketChatState.ticketIndex, true);
202
+ if (ticketChatState.status === "running") {
203
+ ticketChatState.status = "done";
204
+ }
205
+ }
206
+ catch (err) {
207
+ const error = err;
208
+ if (error.name === "AbortError") {
209
+ ticketChatState.status = "interrupted";
210
+ ticketChatState.error = "";
211
+ }
212
+ else {
213
+ ticketChatState.status = "error";
214
+ ticketChatState.error = error.message || "Ticket chat failed";
215
+ }
216
+ }
217
+ finally {
218
+ ticketChatState.controller = null;
219
+ renderTicketChat();
220
+ }
221
+ }
222
+ export async function cancelTicketChat() {
223
+ if (ticketChatState.status !== "running")
224
+ return;
225
+ // Abort the request
226
+ if (ticketChatState.controller) {
227
+ ticketChatState.controller.abort();
228
+ }
229
+ // Send interrupt to server
230
+ if (ticketChatState.ticketIndex != null) {
231
+ try {
232
+ await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/interrupt`, {
233
+ method: "POST",
234
+ });
235
+ }
236
+ catch (err) {
237
+ // Ignore interrupt errors
238
+ }
239
+ }
240
+ ticketChatState.status = "interrupted";
241
+ ticketChatState.error = "";
242
+ ticketChatState.statusText = "";
243
+ ticketChatState.controller = null;
244
+ renderTicketChat();
245
+ }
246
+ export async function applyTicketPatch() {
247
+ if (ticketChatState.ticketIndex == null) {
248
+ flash("No ticket selected", "error");
249
+ return;
250
+ }
251
+ if (!ticketChatState.draft) {
252
+ flash("No draft to apply", "error");
253
+ return;
254
+ }
255
+ try {
256
+ const res = await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/apply`, { method: "POST" });
257
+ ticketChatState.draft = null;
258
+ flash("Draft applied");
259
+ // Notify that tickets changed
260
+ publish("tickets:updated", {});
261
+ // Update the editor textarea if content is returned
262
+ if (res.content) {
263
+ const textarea = document.getElementById("ticket-editor-content");
264
+ if (textarea) {
265
+ const [fmYaml, body] = splitMarkdownFrontmatter(res.content);
266
+ if (fmYaml !== null) {
267
+ textarea.value = body.trimStart();
268
+ }
269
+ else {
270
+ textarea.value = res.content.trimStart();
271
+ }
272
+ // Trigger input event to update undo stack and autosave
273
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
274
+ }
275
+ }
276
+ }
277
+ catch (err) {
278
+ const error = err;
279
+ flash(error.message || "Failed to apply draft", "error");
280
+ }
281
+ finally {
282
+ renderTicketChat();
283
+ }
284
+ }
285
+ export async function discardTicketPatch() {
286
+ if (ticketChatState.ticketIndex == null) {
287
+ flash("No ticket selected", "error");
288
+ return;
289
+ }
290
+ try {
291
+ await api(`/api/tickets/${ticketChatState.ticketIndex}/chat/discard`, { method: "POST" });
292
+ ticketChatState.draft = null;
293
+ flash("Draft discarded");
294
+ }
295
+ catch (err) {
296
+ const error = err;
297
+ flash(error.message || "Failed to discard draft", "error");
298
+ }
299
+ finally {
300
+ renderTicketChat();
301
+ }
302
+ }
303
+ export async function loadTicketPending(index, silent = false) {
304
+ try {
305
+ const res = await api(`/api/tickets/${index}/chat/pending`, { method: "GET" });
306
+ ticketChatState.draft = {
307
+ patch: res.patch || "",
308
+ content: res.content || "",
309
+ agentMessage: res.agent_message || "",
310
+ createdAt: res.created_at || "",
311
+ baseHash: res.base_hash || "",
312
+ };
313
+ if (!silent) {
314
+ flash("Loaded pending draft");
315
+ }
316
+ }
317
+ catch (err) {
318
+ const error = err;
319
+ const message = error?.message || "";
320
+ if (message.includes("No pending")) {
321
+ ticketChatState.draft = null;
322
+ if (!silent) {
323
+ flash("No pending draft");
324
+ }
325
+ }
326
+ else if (!silent) {
327
+ flash(message || "Failed to load pending draft", "error");
328
+ }
329
+ }
330
+ finally {
331
+ renderTicketChat();
332
+ }
333
+ }
@@ -0,0 +1,16 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ import { ticketChat } from "./ticketChatActions.js";
3
+ // This module now delegates to docChatCore for rendering and event parsing.
4
+ export function applyTicketEvent(payload) {
5
+ ticketChat.applyAppEvent(payload);
6
+ }
7
+ export function renderTicketEvents() {
8
+ ticketChat.renderEvents();
9
+ }
10
+ export function renderTicketMessages() {
11
+ ticketChat.renderMessages();
12
+ }
13
+ export function initTicketChatEvents() {
14
+ // Toggle already wired in docChatCore constructor.
15
+ return;
16
+ }
@@ -0,0 +1,16 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ import { clearChatHistory, loadChatHistory, saveChatHistory, } from "./docChatStorage.js";
3
+ const STORAGE_CONFIG = {
4
+ keyPrefix: "car-ticket-chat-",
5
+ maxMessages: 50,
6
+ version: 1,
7
+ };
8
+ export function saveTicketChatHistory(ticketIndex, messages) {
9
+ saveChatHistory(STORAGE_CONFIG, String(ticketIndex), messages);
10
+ }
11
+ export function loadTicketChatHistory(ticketIndex) {
12
+ return loadChatHistory(STORAGE_CONFIG, String(ticketIndex));
13
+ }
14
+ export function clearTicketChatHistory(ticketIndex) {
15
+ clearChatHistory(STORAGE_CONFIG, String(ticketIndex));
16
+ }
@@ -0,0 +1,264 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ /**
3
+ * Ticket Chat Stream - handles SSE streaming for ticket chat
4
+ */
5
+ import { resolvePath, getAuthToken } from "./utils.js";
6
+ import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, } from "./ticketChatActions.js";
7
+ import { applyTicketEvent, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
8
+ const decoder = new TextDecoder();
9
+ function parseMaybeJson(data) {
10
+ try {
11
+ return JSON.parse(data);
12
+ }
13
+ catch {
14
+ return data;
15
+ }
16
+ }
17
+ export async function performTicketChatRequest(ticketIndex, message, signal, options = {}) {
18
+ // Clear events from previous request and add user message to history
19
+ clearTicketEvents();
20
+ addUserMessage(message);
21
+ // Render both chat (for container visibility) and messages
22
+ renderTicketChat();
23
+ renderTicketMessages();
24
+ const endpoint = resolvePath(`/api/tickets/${ticketIndex}/chat`);
25
+ const headers = {
26
+ "Content-Type": "application/json",
27
+ };
28
+ const token = getAuthToken();
29
+ if (token) {
30
+ headers.Authorization = `Bearer ${token}`;
31
+ }
32
+ const payload = {
33
+ message,
34
+ stream: true,
35
+ };
36
+ if (options.agent)
37
+ payload.agent = options.agent;
38
+ if (options.model)
39
+ payload.model = options.model;
40
+ if (options.reasoning)
41
+ payload.reasoning = options.reasoning;
42
+ const res = await fetch(endpoint, {
43
+ method: "POST",
44
+ headers,
45
+ body: JSON.stringify(payload),
46
+ signal,
47
+ });
48
+ if (!res.ok) {
49
+ const text = await res.text();
50
+ let detail = text;
51
+ try {
52
+ const parsed = JSON.parse(text);
53
+ detail = parsed.detail || parsed.error || text;
54
+ }
55
+ catch {
56
+ // ignore parse errors
57
+ }
58
+ throw new Error(detail || `Request failed (${res.status})`);
59
+ }
60
+ const contentType = res.headers.get("content-type") || "";
61
+ if (contentType.includes("text/event-stream")) {
62
+ await readTicketChatStream(res);
63
+ }
64
+ else {
65
+ // Non-streaming response
66
+ const responsePayload = contentType.includes("application/json")
67
+ ? await res.json()
68
+ : await res.text();
69
+ applyTicketChatResult(responsePayload);
70
+ }
71
+ }
72
+ async function readTicketChatStream(res) {
73
+ if (!res.body)
74
+ throw new Error("Streaming not supported in this browser");
75
+ const reader = res.body.getReader();
76
+ let buffer = "";
77
+ let escapedNewlines = false;
78
+ for (;;) {
79
+ const { value, done } = await reader.read();
80
+ if (done)
81
+ break;
82
+ const decoded = decoder.decode(value, { stream: true });
83
+ // Handle escaped newlines
84
+ if (!escapedNewlines) {
85
+ const combined = buffer + decoded;
86
+ if (!combined.includes("\n") && combined.includes("\\n")) {
87
+ escapedNewlines = true;
88
+ buffer = buffer.replace(/\\n(?=event:|data:|\\n)/g, "\n");
89
+ }
90
+ }
91
+ buffer += escapedNewlines
92
+ ? decoded.replace(/\\n(?=event:|data:|\\n)/g, "\n")
93
+ : decoded;
94
+ // Split on double newlines (SSE message delimiter)
95
+ const chunks = buffer.split("\n\n");
96
+ buffer = chunks.pop() || "";
97
+ for (const chunk of chunks) {
98
+ if (!chunk.trim())
99
+ continue;
100
+ let event = "message";
101
+ const dataLines = [];
102
+ chunk.split("\n").forEach((line) => {
103
+ if (line.startsWith("event:")) {
104
+ event = line.slice(6).trim();
105
+ }
106
+ else if (line.startsWith("data:")) {
107
+ dataLines.push(line.slice(5).trimStart());
108
+ }
109
+ else if (line.trim()) {
110
+ dataLines.push(line);
111
+ }
112
+ });
113
+ if (dataLines.length === 0)
114
+ continue;
115
+ const data = dataLines.join("\n");
116
+ handleTicketStreamEvent(event, data);
117
+ }
118
+ }
119
+ }
120
+ function handleTicketStreamEvent(event, rawData) {
121
+ const parsed = parseMaybeJson(rawData);
122
+ switch (event) {
123
+ case "status": {
124
+ const status = typeof parsed === "string"
125
+ ? parsed
126
+ : parsed.status || "";
127
+ ticketChatState.statusText = status;
128
+ renderTicketChat();
129
+ renderTicketEvents();
130
+ break;
131
+ }
132
+ case "token": {
133
+ const token = typeof parsed === "string"
134
+ ? parsed
135
+ : parsed.token ||
136
+ parsed.text ||
137
+ rawData ||
138
+ "";
139
+ ticketChatState.streamText = (ticketChatState.streamText || "") + token;
140
+ if (!ticketChatState.statusText || ticketChatState.statusText === "queued") {
141
+ ticketChatState.statusText = "responding";
142
+ }
143
+ renderTicketChat();
144
+ break;
145
+ }
146
+ case "update": {
147
+ applyTicketChatResult(parsed);
148
+ break;
149
+ }
150
+ case "event":
151
+ case "app-server": {
152
+ // App-server events (thinking, tool calls, etc.)
153
+ applyTicketEvent(parsed);
154
+ renderTicketEvents();
155
+ break;
156
+ }
157
+ case "error": {
158
+ const message = typeof parsed === "object" && parsed !== null
159
+ ? parsed.detail ||
160
+ parsed.error ||
161
+ rawData
162
+ : rawData || "Ticket chat failed";
163
+ ticketChatState.status = "error";
164
+ ticketChatState.error = String(message);
165
+ // Add error as assistant message
166
+ addAssistantMessage(`Error: ${message}`, true);
167
+ renderTicketChat();
168
+ renderTicketMessages();
169
+ throw new Error(String(message));
170
+ }
171
+ case "interrupted": {
172
+ const message = typeof parsed === "object" && parsed !== null
173
+ ? parsed.detail || rawData
174
+ : rawData || "Ticket chat interrupted";
175
+ ticketChatState.status = "interrupted";
176
+ ticketChatState.error = "";
177
+ ticketChatState.statusText = String(message);
178
+ // Add interrupted message
179
+ addAssistantMessage("Request interrupted", true);
180
+ renderTicketChat();
181
+ renderTicketMessages();
182
+ break;
183
+ }
184
+ case "done":
185
+ case "finish": {
186
+ ticketChatState.status = "done";
187
+ // Final render to ensure UI is up to date
188
+ renderTicketChat();
189
+ renderTicketMessages();
190
+ renderTicketEvents();
191
+ break;
192
+ }
193
+ default:
194
+ // Unknown event - try to parse as app-server event
195
+ if (typeof parsed === "object" && parsed !== null) {
196
+ const messageObj = parsed;
197
+ if (messageObj.method || messageObj.message) {
198
+ applyTicketEvent(parsed);
199
+ renderTicketEvents();
200
+ }
201
+ }
202
+ break;
203
+ }
204
+ }
205
+ function applyTicketChatResult(payload) {
206
+ if (!payload || typeof payload !== "object")
207
+ return;
208
+ const result = payload;
209
+ if (result.status === "interrupted") {
210
+ ticketChatState.status = "interrupted";
211
+ ticketChatState.error = "";
212
+ addAssistantMessage("Request interrupted", true);
213
+ renderTicketChat();
214
+ renderTicketMessages();
215
+ return;
216
+ }
217
+ if (result.status === "error" || result.error) {
218
+ ticketChatState.status = "error";
219
+ ticketChatState.error =
220
+ result.detail || result.error || "Chat failed";
221
+ addAssistantMessage(`Error: ${ticketChatState.error}`, true);
222
+ renderTicketChat();
223
+ renderTicketMessages();
224
+ return;
225
+ }
226
+ // Success
227
+ ticketChatState.status = "done";
228
+ if (result.message) {
229
+ ticketChatState.streamText = result.message;
230
+ }
231
+ if (result.agent_message || result.agentMessage) {
232
+ ticketChatState.statusText =
233
+ result.agent_message || result.agentMessage || "";
234
+ }
235
+ // Check for draft/patch in response
236
+ const hasDraft = result.has_draft ?? result.hasDraft;
237
+ if (hasDraft === false) {
238
+ ticketChatState.draft = null;
239
+ }
240
+ else if (hasDraft === true || result.draft || result.patch || result.content) {
241
+ ticketChatState.draft = {
242
+ content: result.content || "",
243
+ patch: result.patch || "",
244
+ agentMessage: result.agent_message || result.agentMessage || "",
245
+ createdAt: result.created_at || result.createdAt || "",
246
+ baseHash: result.base_hash || result.baseHash || "",
247
+ };
248
+ }
249
+ // Add assistant message from response
250
+ const responseText = ticketChatState.streamText ||
251
+ ticketChatState.statusText ||
252
+ (ticketChatState.draft ? "Changes ready to apply" : "Done");
253
+ if (responseText && ticketChatState.messages.length > 0) {
254
+ // Only add if we have messages (i.e., a user message was sent)
255
+ const lastMessage = ticketChatState.messages[ticketChatState.messages.length - 1];
256
+ // Avoid duplicate assistant messages
257
+ if (lastMessage.role === "user") {
258
+ addAssistantMessage(responseText, true);
259
+ }
260
+ }
261
+ renderTicketChat();
262
+ renderTicketMessages();
263
+ renderTicketEvents();
264
+ }