codex-autorunner 1.1.0__py3-none-any.whl → 1.2.1__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 (134) 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 +124 -11
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +238 -3
  9. codex_autorunner/core/context_awareness.py +39 -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 +683 -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/adapter.py +1 -1
  58. codex_autorunner/integrations/telegram/config.py +1 -1
  59. codex_autorunner/integrations/telegram/doctor.py +228 -6
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  63. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  64. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  65. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  66. codex_autorunner/integrations/telegram/handlers/messages.py +34 -3
  67. codex_autorunner/integrations/telegram/helpers.py +1 -3
  68. codex_autorunner/integrations/telegram/runtime.py +9 -4
  69. codex_autorunner/integrations/telegram/service.py +30 -0
  70. codex_autorunner/integrations/telegram/state.py +38 -0
  71. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  72. codex_autorunner/integrations/telegram/transport.py +10 -3
  73. codex_autorunner/integrations/templates/__init__.py +27 -0
  74. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  75. codex_autorunner/server.py +2 -2
  76. codex_autorunner/static/agentControls.js +21 -5
  77. codex_autorunner/static/app.js +115 -11
  78. codex_autorunner/static/archive.js +274 -81
  79. codex_autorunner/static/archiveApi.js +21 -0
  80. codex_autorunner/static/chatUploads.js +137 -0
  81. codex_autorunner/static/constants.js +1 -1
  82. codex_autorunner/static/docChatCore.js +185 -13
  83. codex_autorunner/static/fileChat.js +68 -40
  84. codex_autorunner/static/fileboxUi.js +159 -0
  85. codex_autorunner/static/hub.js +46 -81
  86. codex_autorunner/static/index.html +303 -24
  87. codex_autorunner/static/messages.js +82 -4
  88. codex_autorunner/static/notifications.js +288 -0
  89. codex_autorunner/static/pma.js +1167 -0
  90. codex_autorunner/static/settings.js +3 -0
  91. codex_autorunner/static/streamUtils.js +57 -0
  92. codex_autorunner/static/styles.css +9141 -6742
  93. codex_autorunner/static/templateReposSettings.js +225 -0
  94. codex_autorunner/static/terminalManager.js +22 -3
  95. codex_autorunner/static/ticketChatActions.js +165 -3
  96. codex_autorunner/static/ticketChatStream.js +17 -119
  97. codex_autorunner/static/ticketEditor.js +41 -13
  98. codex_autorunner/static/ticketTemplates.js +798 -0
  99. codex_autorunner/static/tickets.js +69 -19
  100. codex_autorunner/static/turnEvents.js +27 -0
  101. codex_autorunner/static/turnResume.js +33 -0
  102. codex_autorunner/static/utils.js +28 -0
  103. codex_autorunner/static/workspace.js +258 -44
  104. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  105. codex_autorunner/surfaces/cli/cli.py +1465 -155
  106. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  107. codex_autorunner/surfaces/web/app.py +253 -49
  108. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  109. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  110. codex_autorunner/surfaces/web/routes/archive.py +197 -0
  111. codex_autorunner/surfaces/web/routes/file_chat.py +297 -36
  112. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  113. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  114. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  115. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  116. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  117. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  118. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  119. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  120. codex_autorunner/surfaces/web/schemas.py +81 -18
  121. codex_autorunner/tickets/agent_pool.py +27 -0
  122. codex_autorunner/tickets/files.py +33 -16
  123. codex_autorunner/tickets/lint.py +50 -0
  124. codex_autorunner/tickets/models.py +3 -0
  125. codex_autorunner/tickets/outbox.py +41 -5
  126. codex_autorunner/tickets/runner.py +350 -69
  127. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/METADATA +15 -19
  128. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/RECORD +132 -101
  129. codex_autorunner/core/adapter_utils.py +0 -21
  130. codex_autorunner/core/engine.py +0 -3302
  131. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/WHEEL +0 -0
  132. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/entry_points.txt +0 -0
  133. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/licenses/LICENSE +0 -0
  134. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/top_level.txt +0 -0
@@ -3,21 +3,14 @@
3
3
  * Ticket Chat Stream - handles SSE streaming for ticket chat
4
4
  */
5
5
  import { resolvePath, getAuthToken } from "./utils.js";
6
- import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, } from "./ticketChatActions.js";
6
+ import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, applyTicketChatResult, } from "./ticketChatActions.js";
7
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
- }
8
+ import { extractContextRemainingPercent, readEventStream, parseMaybeJson } from "./streamUtils.js";
17
9
  export async function performTicketChatRequest(ticketIndex, message, signal, options = {}) {
18
10
  // Clear events from previous request and add user message to history
19
11
  clearTicketEvents();
20
12
  addUserMessage(message);
13
+ ticketChatState.contextUsagePercent = null;
21
14
  // Render both chat (for container visibility) and messages
22
15
  renderTicketChat();
23
16
  renderTicketMessages();
@@ -39,6 +32,8 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
39
32
  payload.model = options.model;
40
33
  if (options.reasoning)
41
34
  payload.reasoning = options.reasoning;
35
+ if (options.clientTurnId)
36
+ payload.client_turn_id = options.clientTurnId;
42
37
  const res = await fetch(endpoint, {
43
38
  method: "POST",
44
39
  headers,
@@ -59,7 +54,7 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
59
54
  }
60
55
  const contentType = res.headers.get("content-type") || "";
61
56
  if (contentType.includes("text/event-stream")) {
62
- await readTicketChatStream(res);
57
+ await readEventStream(res, (event, data) => handleTicketStreamEvent(event, data));
63
58
  }
64
59
  else {
65
60
  // Non-streaming response
@@ -69,54 +64,6 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
69
64
  applyTicketChatResult(responsePayload);
70
65
  }
71
66
  }
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
67
  function handleTicketStreamEvent(event, rawData) {
121
68
  const parsed = parseMaybeJson(rawData);
122
69
  switch (event) {
@@ -154,6 +101,17 @@ function handleTicketStreamEvent(event, rawData) {
154
101
  renderTicketEvents();
155
102
  break;
156
103
  }
104
+ case "token_usage": {
105
+ // Token usage events - context window usage
106
+ if (typeof parsed === "object" && parsed !== null) {
107
+ const percentRemaining = extractContextRemainingPercent(parsed);
108
+ if (percentRemaining !== null) {
109
+ ticketChatState.contextUsagePercent = percentRemaining;
110
+ renderTicketChat();
111
+ }
112
+ }
113
+ break;
114
+ }
157
115
  case "error": {
158
116
  const message = typeof parsed === "object" && parsed !== null
159
117
  ? parsed.detail ||
@@ -202,63 +160,3 @@ function handleTicketStreamEvent(event, rawData) {
202
160
  break;
203
161
  }
204
162
  }
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
- }
@@ -2,14 +2,16 @@
2
2
  /**
3
3
  * Ticket Editor Modal - handles creating, editing, and deleting tickets
4
4
  */
5
- import { api, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
5
+ import { api, confirmModal, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
6
6
  import { publish } from "./bus.js";
7
7
  import { clearTicketChatHistory } from "./ticketChatStorage.js";
8
- import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, } from "./ticketChatActions.js";
8
+ import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, resumeTicketPendingTurn, } from "./ticketChatActions.js";
9
9
  import { initAgentControls } from "./agentControls.js";
10
10
  import { initTicketVoice } from "./ticketVoice.js";
11
11
  import { initTicketChatEvents, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
12
+ import { initChatPasteUpload } from "./chatUploads.js";
12
13
  import { DocEditor } from "./docEditor.js";
14
+ import { initTicketTemplates } from "./ticketTemplates.js";
13
15
  const DEFAULT_FRONTMATTER = {
14
16
  agent: "codex",
15
17
  done: false,
@@ -88,7 +90,13 @@ async function navigateTicket(delta) {
88
90
  const idx = list.findIndex((ticket) => ticket.index === state.ticketIndex);
89
91
  const target = idx >= 0 ? list[idx + delta] : null;
90
92
  if (target && target.index != null) {
91
- openTicketEditor(target);
93
+ try {
94
+ const data = (await api(`/api/flows/ticket_flow/tickets/${target.index}`));
95
+ openTicketEditor(data);
96
+ }
97
+ catch (err) {
98
+ flash(`Failed to navigate to ticket: ${err.message}`, "error");
99
+ }
92
100
  }
93
101
  void updateTicketNavButtons();
94
102
  }
@@ -298,20 +306,24 @@ function extractFrontmatter(ticket) {
298
306
  /**
299
307
  * Build full markdown content from frontmatter form + body textarea
300
308
  */
309
+ function yamlQuote(value) {
310
+ // Use JSON.stringify for simple, safe double-quoted scalars (handles colons, quotes, newlines).
311
+ return JSON.stringify(value ?? "");
312
+ }
301
313
  function buildTicketContent() {
302
314
  const { content } = els();
303
315
  const fm = getFrontmatterFromForm();
304
316
  const body = content?.value || "";
305
- // Reconstruct frontmatter YAML
317
+ // Reconstruct frontmatter YAML with quoted scalars to tolerate special characters.
306
318
  const lines = ["---"];
307
- lines.push(`agent: ${fm.agent}`);
319
+ lines.push(`agent: ${yamlQuote(fm.agent)}`);
308
320
  lines.push(`done: ${fm.done}`);
309
321
  if (fm.title)
310
- lines.push(`title: ${fm.title}`);
322
+ lines.push(`title: ${yamlQuote(fm.title)}`);
311
323
  if (fm.model)
312
- lines.push(`model: ${fm.model}`);
324
+ lines.push(`model: ${yamlQuote(fm.model)}`);
313
325
  if (fm.reasoning)
314
- lines.push(`reasoning: ${fm.reasoning}`);
326
+ lines.push(`reasoning: ${yamlQuote(fm.reasoning)}`);
315
327
  lines.push("---");
316
328
  lines.push("");
317
329
  lines.push(body);
@@ -478,8 +490,12 @@ async function performAutosave() {
478
490
  // Notify that tickets changed
479
491
  publish("tickets:updated", {});
480
492
  }
481
- catch {
493
+ catch (err) {
494
+ // Surface the failure to the user and let DocEditor keep the "dirty" state
495
+ // so a retry is attempted instead of falsely reporting success.
482
496
  setAutosaveStatus("error");
497
+ flash(err?.message || "Failed to save ticket", "error");
498
+ throw err;
483
499
  }
484
500
  }
485
501
  /**
@@ -590,6 +606,7 @@ export function openTicketEditor(ticket) {
590
606
  renderTicketChat();
591
607
  renderTicketEvents();
592
608
  renderTicketMessages();
609
+ void resumeTicketPendingTurn(ticket?.index ?? null);
593
610
  state.isOpen = true;
594
611
  modal.classList.remove("hidden");
595
612
  // Update URL with ticket index
@@ -614,7 +631,9 @@ export function closeTicketEditor() {
614
631
  return;
615
632
  // Autosave on close if there are changes
616
633
  if (hasUnsavedChanges()) {
617
- void performAutosave();
634
+ // Fire-and-forget: swallow rejection because the error is already flashed
635
+ // inside performAutosave and DocEditor keeps the buffer dirty for retry.
636
+ void performAutosave().catch(() => { });
618
637
  }
619
638
  // Cancel any running chat
620
639
  if (ticketChatState.status === "running") {
@@ -654,7 +673,7 @@ export async function deleteTicket() {
654
673
  flash("Cannot delete: no ticket selected", "error");
655
674
  return;
656
675
  }
657
- const confirmed = window.confirm(`Delete TICKET-${String(state.ticketIndex).padStart(3, "0")}.md? This cannot be undone.`);
676
+ const confirmed = await confirmModal(`Delete TICKET-${String(state.ticketIndex).padStart(3, "0")}.md? This cannot be undone.`);
658
677
  if (!confirmed)
659
678
  return;
660
679
  setButtonsLoading(true);
@@ -703,6 +722,8 @@ export function initTicketEditor() {
703
722
  void initTicketVoice();
704
723
  // Initialize rich chat experience (events toggle, etc.)
705
724
  initTicketChatEvents();
725
+ // Initialize ticket templates picker
726
+ initTicketTemplates();
706
727
  // Button handlers
707
728
  if (deleteBtn)
708
729
  deleteBtn.addEventListener("click", () => void deleteTicket());
@@ -772,6 +793,13 @@ export function initTicketEditor() {
772
793
  chatInput.style.height = "auto";
773
794
  chatInput.style.height = Math.min(chatInput.scrollHeight, 100) + "px";
774
795
  });
796
+ initChatPasteUpload({
797
+ textarea: chatInput,
798
+ basePath: "/api/filebox",
799
+ box: "inbox",
800
+ insertStyle: "both",
801
+ pathPrefix: ".codex-autorunner/filebox",
802
+ });
775
803
  }
776
804
  // Close on backdrop click
777
805
  modal.addEventListener("click", (e) => {
@@ -806,8 +834,8 @@ export function initTicketEditor() {
806
834
  // Don't interfere with typing
807
835
  if (isTypingTarget(e.target))
808
836
  return;
809
- // Only allow Alt or no modifier (no Ctrl/Meta/Shift)
810
- if (e.ctrlKey || e.metaKey || e.shiftKey)
837
+ // Require Alt modifier for navigation (no Ctrl/Meta/Shift)
838
+ if (!e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
811
839
  return;
812
840
  e.preventDefault();
813
841
  void navigateTicket(e.key === "ArrowLeft" ? -1 : 1);