codex-autorunner 0.1.2__py3-none-any.whl → 1.0.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 (189) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/opencode/client.py +68 -35
  3. codex_autorunner/agents/opencode/logging.py +21 -5
  4. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  5. codex_autorunner/agents/opencode/runtime.py +118 -30
  6. codex_autorunner/agents/opencode/supervisor.py +36 -48
  7. codex_autorunner/agents/registry.py +136 -8
  8. codex_autorunner/api.py +25 -0
  9. codex_autorunner/bootstrap.py +16 -35
  10. codex_autorunner/cli.py +157 -139
  11. codex_autorunner/core/about_car.py +44 -32
  12. codex_autorunner/core/adapter_utils.py +21 -0
  13. codex_autorunner/core/app_server_logging.py +7 -3
  14. codex_autorunner/core/app_server_prompts.py +27 -260
  15. codex_autorunner/core/app_server_threads.py +15 -26
  16. codex_autorunner/core/codex_runner.py +6 -0
  17. codex_autorunner/core/config.py +390 -100
  18. codex_autorunner/core/docs.py +10 -2
  19. codex_autorunner/core/drafts.py +82 -0
  20. codex_autorunner/core/engine.py +278 -262
  21. codex_autorunner/core/flows/__init__.py +25 -0
  22. codex_autorunner/core/flows/controller.py +178 -0
  23. codex_autorunner/core/flows/definition.py +82 -0
  24. codex_autorunner/core/flows/models.py +75 -0
  25. codex_autorunner/core/flows/runtime.py +351 -0
  26. codex_autorunner/core/flows/store.py +485 -0
  27. codex_autorunner/core/flows/transition.py +133 -0
  28. codex_autorunner/core/flows/worker_process.py +242 -0
  29. codex_autorunner/core/hub.py +15 -9
  30. codex_autorunner/core/locks.py +4 -0
  31. codex_autorunner/core/prompt.py +15 -7
  32. codex_autorunner/core/redaction.py +29 -0
  33. codex_autorunner/core/review_context.py +5 -8
  34. codex_autorunner/core/run_index.py +6 -0
  35. codex_autorunner/core/runner_process.py +5 -2
  36. codex_autorunner/core/state.py +0 -88
  37. codex_autorunner/core/static_assets.py +55 -0
  38. codex_autorunner/core/supervisor_utils.py +67 -0
  39. codex_autorunner/core/update.py +20 -11
  40. codex_autorunner/core/update_runner.py +2 -0
  41. codex_autorunner/core/utils.py +29 -2
  42. codex_autorunner/discovery.py +2 -4
  43. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  44. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  45. codex_autorunner/integrations/agents/__init__.py +27 -0
  46. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  47. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  48. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  49. codex_autorunner/integrations/agents/run_event.py +71 -0
  50. codex_autorunner/integrations/app_server/client.py +576 -92
  51. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  52. codex_autorunner/integrations/telegram/adapter.py +141 -167
  53. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  54. codex_autorunner/integrations/telegram/config.py +175 -0
  55. codex_autorunner/integrations/telegram/constants.py +16 -1
  56. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  57. codex_autorunner/integrations/telegram/doctor.py +47 -0
  58. codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
  59. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  64. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  65. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  66. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
  67. codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
  68. codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
  69. codex_autorunner/integrations/telegram/helpers.py +88 -16
  70. codex_autorunner/integrations/telegram/outbox.py +208 -37
  71. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  72. codex_autorunner/integrations/telegram/service.py +214 -40
  73. codex_autorunner/integrations/telegram/state.py +100 -2
  74. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  75. codex_autorunner/integrations/telegram/transport.py +36 -3
  76. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  77. codex_autorunner/manifest.py +2 -0
  78. codex_autorunner/plugin_api.py +22 -0
  79. codex_autorunner/routes/__init__.py +23 -14
  80. codex_autorunner/routes/analytics.py +239 -0
  81. codex_autorunner/routes/base.py +81 -109
  82. codex_autorunner/routes/file_chat.py +836 -0
  83. codex_autorunner/routes/flows.py +980 -0
  84. codex_autorunner/routes/messages.py +459 -0
  85. codex_autorunner/routes/system.py +6 -1
  86. codex_autorunner/routes/usage.py +87 -0
  87. codex_autorunner/routes/workspace.py +271 -0
  88. codex_autorunner/server.py +2 -1
  89. codex_autorunner/static/agentControls.js +1 -0
  90. codex_autorunner/static/agentEvents.js +248 -0
  91. codex_autorunner/static/app.js +25 -22
  92. codex_autorunner/static/autoRefresh.js +29 -1
  93. codex_autorunner/static/bootstrap.js +1 -0
  94. codex_autorunner/static/bus.js +1 -0
  95. codex_autorunner/static/cache.js +1 -0
  96. codex_autorunner/static/constants.js +20 -4
  97. codex_autorunner/static/dashboard.js +162 -196
  98. codex_autorunner/static/diffRenderer.js +37 -0
  99. codex_autorunner/static/docChatCore.js +324 -0
  100. codex_autorunner/static/docChatStorage.js +65 -0
  101. codex_autorunner/static/docChatVoice.js +65 -0
  102. codex_autorunner/static/docEditor.js +133 -0
  103. codex_autorunner/static/env.js +1 -0
  104. codex_autorunner/static/eventSummarizer.js +166 -0
  105. codex_autorunner/static/fileChat.js +182 -0
  106. codex_autorunner/static/health.js +155 -0
  107. codex_autorunner/static/hub.js +41 -118
  108. codex_autorunner/static/index.html +787 -858
  109. codex_autorunner/static/liveUpdates.js +1 -0
  110. codex_autorunner/static/loader.js +1 -0
  111. codex_autorunner/static/messages.js +470 -0
  112. codex_autorunner/static/mobileCompact.js +2 -1
  113. codex_autorunner/static/settings.js +24 -211
  114. codex_autorunner/static/styles.css +7567 -3865
  115. codex_autorunner/static/tabs.js +28 -5
  116. codex_autorunner/static/terminal.js +14 -0
  117. codex_autorunner/static/terminalManager.js +34 -59
  118. codex_autorunner/static/ticketChatActions.js +333 -0
  119. codex_autorunner/static/ticketChatEvents.js +16 -0
  120. codex_autorunner/static/ticketChatStorage.js +16 -0
  121. codex_autorunner/static/ticketChatStream.js +264 -0
  122. codex_autorunner/static/ticketEditor.js +750 -0
  123. codex_autorunner/static/ticketVoice.js +9 -0
  124. codex_autorunner/static/tickets.js +1315 -0
  125. codex_autorunner/static/utils.js +32 -3
  126. codex_autorunner/static/voice.js +1 -0
  127. codex_autorunner/static/workspace.js +672 -0
  128. codex_autorunner/static/workspaceApi.js +53 -0
  129. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  130. codex_autorunner/tickets/__init__.py +20 -0
  131. codex_autorunner/tickets/agent_pool.py +377 -0
  132. codex_autorunner/tickets/files.py +85 -0
  133. codex_autorunner/tickets/frontmatter.py +55 -0
  134. codex_autorunner/tickets/lint.py +102 -0
  135. codex_autorunner/tickets/models.py +95 -0
  136. codex_autorunner/tickets/outbox.py +232 -0
  137. codex_autorunner/tickets/replies.py +179 -0
  138. codex_autorunner/tickets/runner.py +823 -0
  139. codex_autorunner/tickets/spec_ingest.py +77 -0
  140. codex_autorunner/web/app.py +269 -91
  141. codex_autorunner/web/middleware.py +3 -4
  142. codex_autorunner/web/schemas.py +89 -109
  143. codex_autorunner/web/static_assets.py +1 -44
  144. codex_autorunner/workspace/__init__.py +40 -0
  145. codex_autorunner/workspace/paths.py +319 -0
  146. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
  147. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  148. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  149. codex_autorunner/agents/execution/policy.py +0 -292
  150. codex_autorunner/agents/factory.py +0 -52
  151. codex_autorunner/agents/orchestrator.py +0 -358
  152. codex_autorunner/core/doc_chat.py +0 -1446
  153. codex_autorunner/core/snapshot.py +0 -580
  154. codex_autorunner/integrations/github/chatops.py +0 -268
  155. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  156. codex_autorunner/routes/docs.py +0 -381
  157. codex_autorunner/routes/github.py +0 -327
  158. codex_autorunner/routes/runs.py +0 -250
  159. codex_autorunner/spec_ingest.py +0 -812
  160. codex_autorunner/static/docChatActions.js +0 -287
  161. codex_autorunner/static/docChatEvents.js +0 -300
  162. codex_autorunner/static/docChatRender.js +0 -205
  163. codex_autorunner/static/docChatStream.js +0 -361
  164. codex_autorunner/static/docs.js +0 -20
  165. codex_autorunner/static/docsClipboard.js +0 -69
  166. codex_autorunner/static/docsCrud.js +0 -257
  167. codex_autorunner/static/docsDocUpdates.js +0 -62
  168. codex_autorunner/static/docsDrafts.js +0 -16
  169. codex_autorunner/static/docsElements.js +0 -69
  170. codex_autorunner/static/docsInit.js +0 -285
  171. codex_autorunner/static/docsParse.js +0 -160
  172. codex_autorunner/static/docsSnapshot.js +0 -87
  173. codex_autorunner/static/docsSpecIngest.js +0 -263
  174. codex_autorunner/static/docsState.js +0 -127
  175. codex_autorunner/static/docsThreadRegistry.js +0 -44
  176. codex_autorunner/static/docsUi.js +0 -153
  177. codex_autorunner/static/docsVoice.js +0 -56
  178. codex_autorunner/static/github.js +0 -504
  179. codex_autorunner/static/logs.js +0 -678
  180. codex_autorunner/static/review.js +0 -157
  181. codex_autorunner/static/runs.js +0 -418
  182. codex_autorunner/static/snapshot.js +0 -124
  183. codex_autorunner/static/state.js +0 -94
  184. codex_autorunner/static/todoPreview.js +0 -27
  185. codex_autorunner/workspace.py +0 -16
  186. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  187. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  188. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  189. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,21 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { publish } from "./bus.js";
2
- import { getUrlParams, updateUrlParams } from "./utils.js";
3
+ import { escapeHtml, getUrlParams, updateUrlParams } from "./utils.js";
3
4
  const tabs = [];
4
- export function registerTab(id, label) {
5
- tabs.push({ id, label });
5
+ export function registerTab(id, label, opts = {}) {
6
+ tabs.push({ id, label, hidden: Boolean(opts.hidden) });
6
7
  }
7
- export function initTabs(defaultTab = "dashboard") {
8
+ let setActivePanelFn = null;
9
+ let pendingActivate = null;
10
+ export function activateTab(id) {
11
+ if (setActivePanelFn) {
12
+ setActivePanelFn(id);
13
+ }
14
+ else {
15
+ pendingActivate = id;
16
+ }
17
+ }
18
+ export function initTabs(defaultTab = "analytics") {
8
19
  const container = document.querySelector(".tabs");
9
20
  if (!container)
10
21
  return;
@@ -17,11 +28,18 @@ export function initTabs(defaultTab = "dashboard") {
17
28
  updateUrlParams({ tab: id });
18
29
  publish("tab:change", id);
19
30
  };
31
+ setActivePanelFn = setActivePanel;
20
32
  tabs.forEach(tab => {
33
+ if (tab.hidden) {
34
+ return;
35
+ }
21
36
  const btn = document.createElement("button");
22
37
  btn.className = "tab";
23
38
  btn.dataset.target = tab.id;
24
- btn.textContent = tab.label;
39
+ btn.innerHTML = `
40
+ <span class="tab-label">${escapeHtml(tab.label)}</span>
41
+ <span class="badge hidden" id="tab-badge-${tab.id}"></span>
42
+ `;
25
43
  btn.addEventListener("click", () => setActivePanel(tab.id));
26
44
  container.appendChild(btn);
27
45
  });
@@ -38,4 +56,9 @@ export function initTabs(defaultTab = "dashboard") {
38
56
  else if (tabs.length > 0) {
39
57
  setActivePanel(tabs[0].id);
40
58
  }
59
+ if (pendingActivate && tabs.some((t) => t.id === pendingActivate)) {
60
+ const id = pendingActivate;
61
+ pendingActivate = null;
62
+ setActivePanel(id);
63
+ }
41
64
  }
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { TerminalManager } from "./terminalManager.js";
2
3
  let terminalManager = null;
3
4
  export function getTerminalManager() {
@@ -5,8 +6,21 @@ export function getTerminalManager() {
5
6
  }
6
7
  export function initTerminal() {
7
8
  if (terminalManager) {
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ if (typeof terminalManager.fit === "function") {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ terminalManager.fit();
13
+ }
8
14
  return;
9
15
  }
10
16
  terminalManager = new TerminalManager();
11
17
  terminalManager.init();
18
+ // Ensure terminal is resized to fit container after initialization
19
+ if (terminalManager) {
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ if (typeof terminalManager.fit === "function") {
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ terminalManager.fit();
24
+ }
25
+ }
12
26
  }
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { api, flash, buildWsUrl, getAuthToken, isMobileViewport } from "./utils.js";
2
3
  import { getSelectedAgent, getSelectedModel, getSelectedReasoning, initAgentControls, } from "./agentControls.js";
3
4
  function base64UrlEncode(value) {
@@ -44,21 +45,7 @@ const XTERM_COLOR_MODE_PALETTE_16 = 0x01000000;
44
45
  const XTERM_COLOR_MODE_PALETTE_256 = 0x02000000;
45
46
  const XTERM_COLOR_MODE_RGB = 0x03000000;
46
47
  const CAR_CONTEXT_HOOK_ID = "car_context";
47
- const GITHUB_CONTEXT_HOOK_ID = "github_context";
48
- const CAR_CONTEXT_KEYWORDS = [
49
- "car",
50
- "codex",
51
- "todo",
52
- "progress",
53
- "opinions",
54
- "spec",
55
- "summary",
56
- "autorunner",
57
- "work docs",
58
- ];
59
- const GITHUB_LINK_RE = /https?:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:issues|pull)\/\d+(?:[/?#][^\s]*)?/i;
60
- const CAR_CONTEXT_HINT_TEXT = "Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules.";
61
- const CAR_CONTEXT_HINT = wrapInjectedContext(CAR_CONTEXT_HINT_TEXT);
48
+ const CAR_CONTEXT_HINT = wrapInjectedContext(CONSTANTS.PROMPTS.CAR_CONTEXT_HINT);
62
49
  const VOICE_TRANSCRIPT_DISCLAIMER_TEXT = CONSTANTS.PROMPTS?.VOICE_TRANSCRIPT_DISCLAIMER ||
63
50
  "Note: transcribed from user voice. If confusing or possibly inaccurate and you cannot infer the intention please clarify before proceeding.";
64
51
  const INJECTED_CONTEXT_TAG_RE = /<injected context>/i;
@@ -360,7 +347,6 @@ export class TerminalManager {
360
347
  this.lastAltScrollbackSize = 0;
361
348
  this.transcriptResetForConnect = false;
362
349
  this._registerTextInputHook(this._buildCarContextHook());
363
- this._registerTextInputHook(this._buildGithubContextHook());
364
350
  // Bind methods that are used as callbacks
365
351
  this._handleResize = this._handleResize.bind(this);
366
352
  this._handleVoiceHotkeyDown = this._handleVoiceHotkeyDown.bind(this);
@@ -444,6 +430,20 @@ export class TerminalManager {
444
430
  this.connect({ mode: "attach" });
445
431
  }
446
432
  }
433
+ /**
434
+ * Force resize terminal to fit container
435
+ */
436
+ fit() {
437
+ if (this.fitAddon && this.term) {
438
+ try {
439
+ this.fitAddon.fit();
440
+ this._handleResize(); // Send resize to server
441
+ }
442
+ catch (e) {
443
+ // ignore
444
+ }
445
+ }
446
+ }
447
447
  /**
448
448
  * Set terminal status message
449
449
  */
@@ -621,12 +621,12 @@ export class TerminalManager {
621
621
  if (manager._hasTextInputHookFired(CAR_CONTEXT_HOOK_ID))
622
622
  return null;
623
623
  const lowered = text.toLowerCase();
624
- const hit = CAR_CONTEXT_KEYWORDS.some((kw) => lowered.includes(kw));
624
+ const hit = CONSTANTS.KEYWORDS.CAR_CONTEXT.some((kw) => lowered.includes(kw));
625
625
  if (!hit)
626
626
  return null;
627
627
  if (lowered.includes("about_car.md"))
628
628
  return null;
629
- if (text.includes(CAR_CONTEXT_HINT_TEXT) ||
629
+ if (text.includes(CONSTANTS.PROMPTS.CAR_CONTEXT_HINT) ||
630
630
  text.includes(CAR_CONTEXT_HINT)) {
631
631
  return null;
632
632
  }
@@ -637,43 +637,8 @@ export class TerminalManager {
637
637
  },
638
638
  };
639
639
  }
640
- _buildGithubContextHook() {
641
- return {
642
- id: GITHUB_CONTEXT_HOOK_ID,
643
- apply: async ({ text }) => {
644
- if (!text || !text.trim())
645
- return null;
646
- const match = text.match(GITHUB_LINK_RE);
647
- if (!match)
648
- return null;
649
- try {
650
- const res = await api("/api/github/context", {
651
- method: "POST",
652
- body: { url: match[0] },
653
- });
654
- if (!res || typeof res !== "object")
655
- return null;
656
- const injection = wrapInjectedContextIfNeeded(res.hint);
657
- const separator = text.endsWith("\n") ? "\n" : "\n\n";
658
- return { text: `${text}${separator}${injection}` };
659
- }
660
- catch (_err) {
661
- return null;
662
- }
663
- },
664
- };
665
- }
666
640
  async _loadTerminalIdleTimeout() {
667
- try {
668
- const data = await api(CONSTANTS.API.STATE_ENDPOINT);
669
- if (data &&
670
- typeof data === "object" && data !== null && "terminal_idle_timeout_seconds" in data) {
671
- this.terminalIdleTimeoutSeconds = data.terminal_idle_timeout_seconds;
672
- }
673
- }
674
- catch (_err) {
675
- // ignore
676
- }
641
+ // State endpoint removed - terminal idle timeout no longer loaded from /api/state
677
642
  }
678
643
  _getSessionStorageKey() {
679
644
  return `${SESSION_STORAGE_PREFIX}${this._getRepoStorageKey()}`;
@@ -1880,7 +1845,8 @@ export class TerminalManager {
1880
1845
  * Ensure xterm terminal is initialized
1881
1846
  */
1882
1847
  _ensureTerminal() {
1883
- if (!window.Terminal || !window.FitAddon) {
1848
+ const win = window;
1849
+ if (!win.Terminal || !win.FitAddon) {
1884
1850
  this._setStatus("xterm assets missing; reload or check /static/vendor");
1885
1851
  flash("xterm assets missing; reload the page", "error");
1886
1852
  return false;
@@ -1899,7 +1865,7 @@ export class TerminalManager {
1899
1865
  const container = document.getElementById("terminal-container");
1900
1866
  if (!container)
1901
1867
  return false;
1902
- this.term = new window.Terminal({
1868
+ this.term = new win.Terminal({
1903
1869
  convertEol: true,
1904
1870
  fontFamily: '"JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace',
1905
1871
  fontSize: this._getFontSize(),
@@ -1911,7 +1877,7 @@ export class TerminalManager {
1911
1877
  scrollback: this.transcriptMaxLines,
1912
1878
  theme: CONSTANTS.THEME.XTERM,
1913
1879
  });
1914
- this.fitAddon = new window.FitAddon.FitAddon();
1880
+ this.fitAddon = new win.FitAddon.FitAddon();
1915
1881
  this.term.loadAddon(this.fitAddon);
1916
1882
  this.term.open(container);
1917
1883
  this.term.write('Press "New" or "Resume" to launch Codex TUI...\r\n');
@@ -1920,6 +1886,13 @@ export class TerminalManager {
1920
1886
  this.term.onScroll(() => this._updateJumpBottomVisibility());
1921
1887
  this.term.onRender(() => this._scheduleMobileViewRender());
1922
1888
  this._updateJumpBottomVisibility();
1889
+ // Initial fit
1890
+ try {
1891
+ this.fitAddon.fit();
1892
+ }
1893
+ catch (e) {
1894
+ // ignore fit errors when not visible
1895
+ }
1923
1896
  if (!this.inputDisposable) {
1924
1897
  this.inputDisposable = this.term.onData((data) => {
1925
1898
  if (!this.socket || this.socket.readyState !== WebSocket.OPEN)
@@ -2004,7 +1977,8 @@ export class TerminalManager {
2004
1977
  if (!viewport)
2005
1978
  return;
2006
1979
  const getLineHeight = () => {
2007
- const dims = this.term?._core?._renderService?.dimensions;
1980
+ const core = this.term?._core;
1981
+ const dims = core?._renderService?.dimensions;
2008
1982
  if (dims && Number.isFinite(dims.actualCellHeight) && dims.actualCellHeight > 0) {
2009
1983
  return dims.actualCellHeight;
2010
1984
  }
@@ -3100,7 +3074,8 @@ export class TerminalManager {
3100
3074
  method: "POST",
3101
3075
  body: formData,
3102
3076
  });
3103
- const imagePath = response?.path || response?.abs_path;
3077
+ const p = response;
3078
+ const imagePath = p.path || p.abs_path;
3104
3079
  if (!imagePath) {
3105
3080
  throw new Error("Upload returned no path");
3106
3081
  }
@@ -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
+ }