codex-autorunner 0.1.1__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 (226) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/__init__.py +20 -0
  3. codex_autorunner/agents/base.py +2 -2
  4. codex_autorunner/agents/codex/harness.py +1 -1
  5. codex_autorunner/agents/opencode/__init__.py +4 -0
  6. codex_autorunner/agents/opencode/agent_config.py +104 -0
  7. codex_autorunner/agents/opencode/client.py +305 -28
  8. codex_autorunner/agents/opencode/harness.py +71 -20
  9. codex_autorunner/agents/opencode/logging.py +225 -0
  10. codex_autorunner/agents/opencode/run_prompt.py +261 -0
  11. codex_autorunner/agents/opencode/runtime.py +1202 -132
  12. codex_autorunner/agents/opencode/supervisor.py +194 -68
  13. codex_autorunner/agents/registry.py +258 -0
  14. codex_autorunner/agents/types.py +2 -2
  15. codex_autorunner/api.py +25 -0
  16. codex_autorunner/bootstrap.py +19 -40
  17. codex_autorunner/cli.py +234 -151
  18. codex_autorunner/core/about_car.py +44 -32
  19. codex_autorunner/core/adapter_utils.py +21 -0
  20. codex_autorunner/core/app_server_events.py +15 -6
  21. codex_autorunner/core/app_server_logging.py +55 -15
  22. codex_autorunner/core/app_server_prompts.py +28 -259
  23. codex_autorunner/core/app_server_threads.py +15 -26
  24. codex_autorunner/core/circuit_breaker.py +183 -0
  25. codex_autorunner/core/codex_runner.py +6 -0
  26. codex_autorunner/core/config.py +555 -133
  27. codex_autorunner/core/docs.py +54 -9
  28. codex_autorunner/core/drafts.py +82 -0
  29. codex_autorunner/core/engine.py +828 -274
  30. codex_autorunner/core/exceptions.py +60 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +178 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +75 -0
  35. codex_autorunner/core/flows/runtime.py +351 -0
  36. codex_autorunner/core/flows/store.py +485 -0
  37. codex_autorunner/core/flows/transition.py +133 -0
  38. codex_autorunner/core/flows/worker_process.py +242 -0
  39. codex_autorunner/core/hub.py +21 -13
  40. codex_autorunner/core/locks.py +118 -1
  41. codex_autorunner/core/logging_utils.py +9 -6
  42. codex_autorunner/core/path_utils.py +123 -0
  43. codex_autorunner/core/prompt.py +15 -7
  44. codex_autorunner/core/redaction.py +29 -0
  45. codex_autorunner/core/retry.py +61 -0
  46. codex_autorunner/core/review.py +888 -0
  47. codex_autorunner/core/review_context.py +161 -0
  48. codex_autorunner/core/run_index.py +223 -0
  49. codex_autorunner/core/runner_controller.py +44 -1
  50. codex_autorunner/core/runner_process.py +30 -1
  51. codex_autorunner/core/sqlite_utils.py +32 -0
  52. codex_autorunner/core/state.py +273 -44
  53. codex_autorunner/core/static_assets.py +55 -0
  54. codex_autorunner/core/supervisor_utils.py +67 -0
  55. codex_autorunner/core/text_delta_coalescer.py +43 -0
  56. codex_autorunner/core/update.py +20 -11
  57. codex_autorunner/core/update_runner.py +2 -0
  58. codex_autorunner/core/usage.py +107 -75
  59. codex_autorunner/core/utils.py +167 -3
  60. codex_autorunner/discovery.py +3 -3
  61. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  62. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  63. codex_autorunner/integrations/agents/__init__.py +27 -0
  64. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  65. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  66. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  67. codex_autorunner/integrations/agents/run_event.py +71 -0
  68. codex_autorunner/integrations/app_server/client.py +708 -153
  69. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  70. codex_autorunner/integrations/telegram/adapter.py +474 -185
  71. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  72. codex_autorunner/integrations/telegram/config.py +239 -1
  73. codex_autorunner/integrations/telegram/constants.py +19 -1
  74. codex_autorunner/integrations/telegram/dispatch.py +44 -8
  75. codex_autorunner/integrations/telegram/doctor.py +47 -0
  76. codex_autorunner/integrations/telegram/handlers/approvals.py +12 -10
  77. codex_autorunner/integrations/telegram/handlers/callbacks.py +15 -1
  78. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +29 -0
  79. codex_autorunner/integrations/telegram/handlers/commands/approvals.py +173 -0
  80. codex_autorunner/integrations/telegram/handlers/commands/execution.py +2595 -0
  81. codex_autorunner/integrations/telegram/handlers/commands/files.py +1408 -0
  82. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  83. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +81 -0
  84. codex_autorunner/integrations/telegram/handlers/commands/github.py +1688 -0
  85. codex_autorunner/integrations/telegram/handlers/commands/shared.py +190 -0
  86. codex_autorunner/integrations/telegram/handlers/commands/voice.py +112 -0
  87. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +2043 -0
  88. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +954 -5689
  89. codex_autorunner/integrations/telegram/handlers/{commands.py → commands_spec.py} +11 -4
  90. codex_autorunner/integrations/telegram/handlers/messages.py +374 -49
  91. codex_autorunner/integrations/telegram/handlers/questions.py +389 -0
  92. codex_autorunner/integrations/telegram/handlers/selections.py +6 -4
  93. codex_autorunner/integrations/telegram/handlers/utils.py +171 -0
  94. codex_autorunner/integrations/telegram/helpers.py +90 -18
  95. codex_autorunner/integrations/telegram/notifications.py +126 -35
  96. codex_autorunner/integrations/telegram/outbox.py +214 -43
  97. codex_autorunner/integrations/telegram/progress_stream.py +42 -19
  98. codex_autorunner/integrations/telegram/runtime.py +24 -13
  99. codex_autorunner/integrations/telegram/service.py +500 -129
  100. codex_autorunner/integrations/telegram/state.py +1278 -330
  101. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  102. codex_autorunner/integrations/telegram/transport.py +37 -4
  103. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  104. codex_autorunner/integrations/telegram/types.py +22 -2
  105. codex_autorunner/integrations/telegram/voice.py +14 -15
  106. codex_autorunner/manifest.py +2 -0
  107. codex_autorunner/plugin_api.py +22 -0
  108. codex_autorunner/routes/__init__.py +25 -14
  109. codex_autorunner/routes/agents.py +18 -78
  110. codex_autorunner/routes/analytics.py +239 -0
  111. codex_autorunner/routes/base.py +142 -113
  112. codex_autorunner/routes/file_chat.py +836 -0
  113. codex_autorunner/routes/flows.py +980 -0
  114. codex_autorunner/routes/messages.py +459 -0
  115. codex_autorunner/routes/repos.py +17 -0
  116. codex_autorunner/routes/review.py +148 -0
  117. codex_autorunner/routes/sessions.py +16 -8
  118. codex_autorunner/routes/settings.py +22 -0
  119. codex_autorunner/routes/shared.py +33 -3
  120. codex_autorunner/routes/system.py +22 -1
  121. codex_autorunner/routes/usage.py +87 -0
  122. codex_autorunner/routes/voice.py +5 -13
  123. codex_autorunner/routes/workspace.py +271 -0
  124. codex_autorunner/server.py +2 -1
  125. codex_autorunner/static/agentControls.js +9 -1
  126. codex_autorunner/static/agentEvents.js +248 -0
  127. codex_autorunner/static/app.js +27 -22
  128. codex_autorunner/static/autoRefresh.js +29 -1
  129. codex_autorunner/static/bootstrap.js +1 -0
  130. codex_autorunner/static/bus.js +1 -0
  131. codex_autorunner/static/cache.js +1 -0
  132. codex_autorunner/static/constants.js +20 -4
  133. codex_autorunner/static/dashboard.js +162 -150
  134. codex_autorunner/static/diffRenderer.js +37 -0
  135. codex_autorunner/static/docChatCore.js +324 -0
  136. codex_autorunner/static/docChatStorage.js +65 -0
  137. codex_autorunner/static/docChatVoice.js +65 -0
  138. codex_autorunner/static/docEditor.js +133 -0
  139. codex_autorunner/static/env.js +1 -0
  140. codex_autorunner/static/eventSummarizer.js +166 -0
  141. codex_autorunner/static/fileChat.js +182 -0
  142. codex_autorunner/static/health.js +155 -0
  143. codex_autorunner/static/hub.js +67 -126
  144. codex_autorunner/static/index.html +788 -807
  145. codex_autorunner/static/liveUpdates.js +59 -0
  146. codex_autorunner/static/loader.js +1 -0
  147. codex_autorunner/static/messages.js +470 -0
  148. codex_autorunner/static/mobileCompact.js +2 -1
  149. codex_autorunner/static/settings.js +24 -205
  150. codex_autorunner/static/styles.css +7577 -3758
  151. codex_autorunner/static/tabs.js +28 -5
  152. codex_autorunner/static/terminal.js +14 -0
  153. codex_autorunner/static/terminalManager.js +53 -59
  154. codex_autorunner/static/ticketChatActions.js +333 -0
  155. codex_autorunner/static/ticketChatEvents.js +16 -0
  156. codex_autorunner/static/ticketChatStorage.js +16 -0
  157. codex_autorunner/static/ticketChatStream.js +264 -0
  158. codex_autorunner/static/ticketEditor.js +750 -0
  159. codex_autorunner/static/ticketVoice.js +9 -0
  160. codex_autorunner/static/tickets.js +1315 -0
  161. codex_autorunner/static/utils.js +32 -3
  162. codex_autorunner/static/voice.js +21 -7
  163. codex_autorunner/static/workspace.js +672 -0
  164. codex_autorunner/static/workspaceApi.js +53 -0
  165. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  166. codex_autorunner/tickets/__init__.py +20 -0
  167. codex_autorunner/tickets/agent_pool.py +377 -0
  168. codex_autorunner/tickets/files.py +85 -0
  169. codex_autorunner/tickets/frontmatter.py +55 -0
  170. codex_autorunner/tickets/lint.py +102 -0
  171. codex_autorunner/tickets/models.py +95 -0
  172. codex_autorunner/tickets/outbox.py +232 -0
  173. codex_autorunner/tickets/replies.py +179 -0
  174. codex_autorunner/tickets/runner.py +823 -0
  175. codex_autorunner/tickets/spec_ingest.py +77 -0
  176. codex_autorunner/voice/capture.py +7 -7
  177. codex_autorunner/voice/service.py +51 -9
  178. codex_autorunner/web/app.py +419 -199
  179. codex_autorunner/web/hub_jobs.py +13 -2
  180. codex_autorunner/web/middleware.py +47 -13
  181. codex_autorunner/web/pty_session.py +26 -13
  182. codex_autorunner/web/schemas.py +114 -109
  183. codex_autorunner/web/static_assets.py +55 -42
  184. codex_autorunner/web/static_refresh.py +86 -0
  185. codex_autorunner/workspace/__init__.py +40 -0
  186. codex_autorunner/workspace/paths.py +319 -0
  187. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +20 -21
  188. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  189. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  190. codex_autorunner/core/doc_chat.py +0 -1415
  191. codex_autorunner/core/snapshot.py +0 -580
  192. codex_autorunner/integrations/github/chatops.py +0 -268
  193. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  194. codex_autorunner/routes/docs.py +0 -381
  195. codex_autorunner/routes/github.py +0 -327
  196. codex_autorunner/routes/runs.py +0 -118
  197. codex_autorunner/spec_ingest.py +0 -788
  198. codex_autorunner/static/docChatActions.js +0 -279
  199. codex_autorunner/static/docChatEvents.js +0 -300
  200. codex_autorunner/static/docChatRender.js +0 -205
  201. codex_autorunner/static/docChatStream.js +0 -361
  202. codex_autorunner/static/docs.js +0 -20
  203. codex_autorunner/static/docsClipboard.js +0 -69
  204. codex_autorunner/static/docsCrud.js +0 -257
  205. codex_autorunner/static/docsDocUpdates.js +0 -62
  206. codex_autorunner/static/docsDrafts.js +0 -16
  207. codex_autorunner/static/docsElements.js +0 -69
  208. codex_autorunner/static/docsInit.js +0 -274
  209. codex_autorunner/static/docsParse.js +0 -160
  210. codex_autorunner/static/docsSnapshot.js +0 -87
  211. codex_autorunner/static/docsSpecIngest.js +0 -263
  212. codex_autorunner/static/docsState.js +0 -127
  213. codex_autorunner/static/docsThreadRegistry.js +0 -44
  214. codex_autorunner/static/docsUi.js +0 -153
  215. codex_autorunner/static/docsVoice.js +0 -56
  216. codex_autorunner/static/github.js +0 -442
  217. codex_autorunner/static/logs.js +0 -640
  218. codex_autorunner/static/runs.js +0 -409
  219. codex_autorunner/static/snapshot.js +0 -124
  220. codex_autorunner/static/state.js +0 -86
  221. codex_autorunner/static/todoPreview.js +0 -27
  222. codex_autorunner/workspace.py +0 -16
  223. codex_autorunner-0.1.1.dist-info/RECORD +0 -191
  224. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  225. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  226. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,205 +0,0 @@
1
- import { flash, statusPill, isMobileViewport } from "./utils.js";
2
- import { chatUI } from "./docsElements.js";
3
- import { CHAT_HISTORY_LIMIT, getActiveDoc, getChatState, getDraft, isDraftPreview, setHistoryNavIndex, } from "./docsState.js";
4
- import { autoResizeTextarea, formatDraftTimestamp, renderDiffHtml, updateDocControls, updateDocVisibility, } from "./docsUi.js";
5
- import { renderChatEvents } from "./docChatEvents.js";
6
- export function updatePatchPreviewFromDraft(draft) {
7
- if (!chatUI.patchBody || !draft || !draft.patch)
8
- return;
9
- chatUI.patchBody.innerHTML = renderDiffHtml(draft.patch);
10
- }
11
- export function renderChat() {
12
- const state = getChatState();
13
- const latest = state.history[0];
14
- const isRunning = state.status === "running";
15
- const hasError = !!state.error;
16
- const pillState = isRunning
17
- ? "running"
18
- : state.status === "error"
19
- ? "error"
20
- : state.status === "interrupted"
21
- ? "interrupted"
22
- : "idle";
23
- if (chatUI.status) {
24
- statusPill(chatUI.status, pillState);
25
- }
26
- if (chatUI.send) {
27
- chatUI.send.disabled = isRunning;
28
- }
29
- if (chatUI.input) {
30
- chatUI.input.disabled = isRunning;
31
- }
32
- if (chatUI.cancel) {
33
- chatUI.cancel.classList.toggle("hidden", !isRunning);
34
- }
35
- if (chatUI.voiceBtn) {
36
- chatUI.voiceBtn.disabled =
37
- isRunning && !chatUI.voiceBtn.classList.contains("voice-retry");
38
- chatUI.voiceBtn.classList.toggle("disabled", chatUI.voiceBtn.disabled);
39
- if (typeof chatUI.voiceBtn.setAttribute === "function") {
40
- chatUI.voiceBtn.setAttribute("aria-disabled", chatUI.voiceBtn.disabled ? "true" : "false");
41
- }
42
- }
43
- if (chatUI.newThread) {
44
- chatUI.newThread.disabled = isRunning;
45
- chatUI.newThread.classList.toggle("disabled", isRunning);
46
- }
47
- if (chatUI.hint) {
48
- if (isRunning) {
49
- const statusText = state.statusText || "processing";
50
- chatUI.hint.textContent = statusText;
51
- chatUI.hint.classList.add("loading");
52
- }
53
- else {
54
- const sendHint = isMobileViewport()
55
- ? "Tap Send to send · Enter for newline"
56
- : "Cmd+Enter / Ctrl+Enter to send · Enter for newline";
57
- chatUI.hint.textContent = sendHint;
58
- chatUI.hint.classList.remove("loading");
59
- }
60
- }
61
- if (hasError) {
62
- chatUI.error.textContent = state.error;
63
- chatUI.error.classList.remove("hidden");
64
- }
65
- else {
66
- chatUI.error.textContent = "";
67
- chatUI.error.classList.add("hidden");
68
- }
69
- const activeDoc = getActiveDoc();
70
- const latestDrafts = latest?.drafts;
71
- const draft = getDraft(activeDoc) || (latestDrafts?.[activeDoc] || null);
72
- const hasPatch = !!(draft && (draft.patch || "").trim());
73
- const previewing = hasPatch && isDraftPreview(activeDoc);
74
- if (chatUI.patchMain) {
75
- chatUI.patchMain.classList.toggle("hidden", !hasPatch);
76
- chatUI.patchMain.classList.toggle("previewing", previewing);
77
- chatUI.patchBody.innerHTML = hasPatch
78
- ? renderDiffHtml(draft.patch)
79
- : "(no draft)";
80
- if (hasPatch) {
81
- chatUI.patchSummary.textContent =
82
- draft?.agentMessage ||
83
- latest?.response ||
84
- state.error ||
85
- "Draft ready";
86
- }
87
- else {
88
- chatUI.patchSummary.textContent = "";
89
- }
90
- if (chatUI.patchMeta) {
91
- const metaParts = [];
92
- if (hasPatch && draft?.createdAt) {
93
- metaParts.push(`drafted ${formatDraftTimestamp(draft.createdAt)}`);
94
- }
95
- if (hasPatch && draft?.baseHash) {
96
- metaParts.push(`base ${draft.baseHash.slice(0, 7)}`);
97
- }
98
- chatUI.patchMeta.textContent = metaParts.join(" · ");
99
- }
100
- if (chatUI.patchApply)
101
- chatUI.patchApply.disabled = isRunning || !hasPatch;
102
- if (chatUI.patchDiscard)
103
- chatUI.patchDiscard.disabled = isRunning || !hasPatch;
104
- if (chatUI.patchReload)
105
- chatUI.patchReload.disabled = isRunning;
106
- if (chatUI.patchPreview) {
107
- chatUI.patchPreview.disabled = isRunning || !hasPatch;
108
- chatUI.patchPreview.textContent = previewing
109
- ? "Hide preview"
110
- : "Preview draft";
111
- chatUI.patchPreview.classList.toggle("active", previewing);
112
- chatUI.patchPreview.setAttribute("aria-pressed", previewing ? "true" : "false");
113
- }
114
- }
115
- updateDocVisibility();
116
- updateDocControls(activeDoc);
117
- renderChatEvents(state);
118
- renderChatHistory(state);
119
- }
120
- export function renderChatHistory(state) {
121
- if (!chatUI.history)
122
- return;
123
- const count = state.history.length;
124
- chatUI.historyCount.textContent = String(count);
125
- chatUI.history.innerHTML = "";
126
- if (count === 0) {
127
- const empty = document.createElement("div");
128
- empty.className = "doc-chat-empty";
129
- empty.textContent = "No messages yet.";
130
- chatUI.history.appendChild(empty);
131
- return;
132
- }
133
- state.history.slice(0, CHAT_HISTORY_LIMIT).forEach((entry) => {
134
- const wrapper = document.createElement("div");
135
- wrapper.className = `doc-chat-entry ${entry.status}`;
136
- const header = document.createElement("div");
137
- header.className = "doc-chat-entry-header";
138
- const promptRow = document.createElement("div");
139
- promptRow.className = "prompt-row";
140
- const prompt = document.createElement("div");
141
- prompt.className = "prompt";
142
- prompt.textContent = entry.prompt || "(no prompt)";
143
- prompt.title = entry.prompt;
144
- const copyBtn = document.createElement("button");
145
- copyBtn.className = "copy-prompt-btn";
146
- copyBtn.title = "Copy to input";
147
- copyBtn.innerHTML = "↑";
148
- copyBtn.addEventListener("click", (e) => {
149
- e.stopPropagation();
150
- if (chatUI.input)
151
- chatUI.input.value = entry.prompt;
152
- autoResizeTextarea(chatUI.input);
153
- chatUI.input?.focus();
154
- setHistoryNavIndex(-1);
155
- flash("Prompt restored to input");
156
- });
157
- promptRow.appendChild(prompt);
158
- promptRow.appendChild(copyBtn);
159
- const meta = document.createElement("div");
160
- meta.className = "meta";
161
- const dot = document.createElement("span");
162
- dot.className = "status-dot";
163
- const stamp = document.createElement("span");
164
- stamp.textContent = entry.time
165
- ? new Date(entry.time).toLocaleTimeString([], {
166
- hour: "2-digit",
167
- minute: "2-digit",
168
- })
169
- : entry.status;
170
- meta.appendChild(dot);
171
- meta.appendChild(stamp);
172
- header.appendChild(promptRow);
173
- header.appendChild(meta);
174
- const response = document.createElement("div");
175
- response.className = "doc-chat-entry-response";
176
- const isLatest = entry === state.history[0];
177
- const runningText = (isLatest && state.streamText) ||
178
- entry.response ||
179
- (isLatest && state.statusText) ||
180
- "queued";
181
- const responseText = entry.error ||
182
- (entry.status === "running" ? runningText : entry.response || "(no response)");
183
- response.textContent = responseText;
184
- response.classList.toggle("streaming", entry.status === "running" && !!(state.streamText || entry.response));
185
- wrapper.appendChild(header);
186
- wrapper.appendChild(response);
187
- const tags = [];
188
- if (entry.viewing) {
189
- tags.push(`Viewing: ${entry.viewing.toUpperCase()}`);
190
- }
191
- else if (entry.targets && entry.targets.length) {
192
- tags.push(`Targets: ${entry.targets.map((k) => k.toUpperCase()).join(", ")}`);
193
- }
194
- if (entry.updated && entry.updated.length) {
195
- tags.push(`Drafts: ${entry.updated.map((k) => k.toUpperCase()).join(", ")}`);
196
- }
197
- if (tags.length) {
198
- const tagLine = document.createElement("div");
199
- tagLine.className = "doc-chat-entry-tags";
200
- tagLine.textContent = tags.join(" · ");
201
- wrapper.appendChild(tagLine);
202
- }
203
- chatUI.history.appendChild(wrapper);
204
- });
205
- }
@@ -1,361 +0,0 @@
1
- import { resolvePath, getAuthToken } from "./utils.js";
2
- import { chatDecoder, getActiveDoc, getChatState, resetChatEvents, } from "./docsState.js";
3
- import { parseChatPayload, parseMaybeJson, recoverDraftMap, recoverPatchFromRaw, } from "./docsParse.js";
4
- import { applyDraftUpdates } from "./docsDrafts.js";
5
- import { renderChat, updatePatchPreviewFromDraft } from "./docChatRender.js";
6
- import { applyAppServerEvent, extractOutputDelta, renderChatEvents } from "./docChatEvents.js";
7
- import { getSelectedAgent, getSelectedModel, getSelectedReasoning, } from "./agentControls.js";
8
- export async function performDocChatRequest(entry, state) {
9
- const endpoint = resolvePath("/api/docs/chat");
10
- const headers = {
11
- "Content-Type": "application/json",
12
- };
13
- const token = getAuthToken();
14
- if (token) {
15
- headers.Authorization = `Bearer ${token}`;
16
- }
17
- const payload = { message: entry.prompt, stream: true };
18
- payload.agent = entry.agent || getSelectedAgent();
19
- const selectedModel = entry.model || getSelectedModel(payload.agent);
20
- const selectedReasoning = entry.reasoning || getSelectedReasoning(payload.agent);
21
- if (selectedModel) {
22
- payload.model = selectedModel;
23
- }
24
- if (selectedReasoning) {
25
- payload.reasoning = selectedReasoning;
26
- }
27
- if (entry.viewing) {
28
- payload.context_doc = entry.viewing;
29
- }
30
- const res = await fetch(endpoint, {
31
- method: "POST",
32
- headers,
33
- body: JSON.stringify(payload),
34
- signal: state.controller.signal,
35
- });
36
- if (!res.ok) {
37
- const text = await res.text();
38
- let detail = text;
39
- try {
40
- const parsed = JSON.parse(text);
41
- const parsedObj = parsed;
42
- detail = parsedObj.detail || parsedObj.error || text;
43
- }
44
- catch (err) {
45
- // ignore parse errors
46
- }
47
- throw new Error(detail || `Request failed (${res.status})`);
48
- }
49
- const contentType = res.headers.get("content-type") || "";
50
- if (contentType.includes("text/event-stream")) {
51
- await readChatStream(res, state, entry);
52
- if (entry.status !== "error" &&
53
- entry.status !== "done" &&
54
- entry.status !== "interrupted") {
55
- entry.status = "done";
56
- }
57
- }
58
- else {
59
- const responsePayload = contentType.includes("application/json")
60
- ? await res.json()
61
- : await res.text();
62
- applyChatResult(responsePayload, state, entry);
63
- }
64
- }
65
- export async function startDocChatEventStream(payload) {
66
- const threadId = payload?.thread_id || payload?.threadId;
67
- const turnId = payload?.turn_id || payload?.turnId;
68
- const agent = payload?.agent || getSelectedAgent();
69
- if (!threadId || !turnId)
70
- return;
71
- const state = getChatState();
72
- if (state.eventTurnId === turnId && state.eventThreadId === threadId) {
73
- return;
74
- }
75
- resetChatEvents(state);
76
- state.eventTurnId = turnId;
77
- state.eventThreadId = threadId;
78
- state.eventAgent = agent;
79
- state.eventController = new AbortController();
80
- renderChatEvents(state);
81
- const endpoint = resolvePath(`/api/agents/${encodeURIComponent(agent)}/turns/${encodeURIComponent(turnId)}/events`);
82
- const url = `${endpoint}?thread_id=${encodeURIComponent(threadId)}`;
83
- const headers = {};
84
- const token = getAuthToken();
85
- if (token)
86
- headers.Authorization = `Bearer ${token}`;
87
- try {
88
- const res = await fetch(url, {
89
- method: "GET",
90
- headers,
91
- signal: state.eventController.signal,
92
- });
93
- if (!res.ok) {
94
- const text = await res.text();
95
- throw new Error(text || `Event stream failed (${res.status})`);
96
- }
97
- const contentType = res.headers.get("content-type") || "";
98
- if (!contentType.includes("text/event-stream")) {
99
- throw new Error("Event stream unavailable");
100
- }
101
- await readAppServerEventStream(res, state);
102
- }
103
- catch (err) {
104
- const error = err;
105
- if (error.name === "AbortError")
106
- return;
107
- state.eventError = error.message || "Failed to stream app-server events";
108
- renderChatEvents(state);
109
- }
110
- }
111
- export async function readAppServerEventStream(res, state) {
112
- if (!res.body)
113
- throw new Error("Streaming not supported in this browser");
114
- const reader = res.body.getReader();
115
- let buffer = "";
116
- let escapedNewlines = false;
117
- for (;;) {
118
- const { value, done } = await reader.read();
119
- if (done)
120
- break;
121
- const decoded = chatDecoder.decode(value, { stream: true });
122
- if (!escapedNewlines) {
123
- const combined = buffer + decoded;
124
- if (!combined.includes("\n") && combined.includes("\\n")) {
125
- escapedNewlines = true;
126
- buffer = buffer.replace(/\\n(?=event:|data:|\\n)/g, "\n");
127
- }
128
- }
129
- buffer += escapedNewlines
130
- ? decoded.replace(/\\n(?=event:|data:|\\n)/g, "\n")
131
- : decoded;
132
- const chunks = buffer.split("\n\n");
133
- buffer = chunks.pop() || "";
134
- for (const chunk of chunks) {
135
- if (!chunk.trim())
136
- continue;
137
- let event = "message";
138
- const dataLines = [];
139
- chunk.split("\n").forEach((line) => {
140
- if (line.startsWith("event:")) {
141
- event = line.slice(6).trim();
142
- }
143
- else if (line.startsWith("data:")) {
144
- dataLines.push(line.slice(5).trimStart());
145
- }
146
- else if (line.trim()) {
147
- dataLines.push(line);
148
- }
149
- });
150
- if (dataLines.length === 0)
151
- continue;
152
- const data = dataLines.join("\n");
153
- await handleAppServerStreamEvent(event || "message", data, state);
154
- }
155
- }
156
- }
157
- async function handleAppServerStreamEvent(_event, rawData, state) {
158
- if (!rawData)
159
- return;
160
- const parsed = parseMaybeJson(rawData);
161
- applyAppServerEvent(state, parsed);
162
- const delta = extractOutputDelta(parsed);
163
- if (delta) {
164
- const entry = state.history[0];
165
- if (entry && entry.status === "running") {
166
- entry.response = (entry.response || "") + delta;
167
- state.streamText = entry.response;
168
- renderChat();
169
- }
170
- }
171
- renderChatEvents(state);
172
- }
173
- export async function readChatStream(res, state, entry) {
174
- if (!res.body)
175
- throw new Error("Streaming not supported in this browser");
176
- const reader = res.body.getReader();
177
- let buffer = "";
178
- let escapedNewlines = false;
179
- for (;;) {
180
- const { value, done } = await reader.read();
181
- if (done)
182
- break;
183
- const decoded = chatDecoder.decode(value, { stream: true });
184
- if (!escapedNewlines) {
185
- const combined = buffer + decoded;
186
- if (!combined.includes("\n") && combined.includes("\\n")) {
187
- escapedNewlines = true;
188
- buffer = buffer.replace(/\\n(?=event:|data:|\\n)/g, "\n");
189
- }
190
- }
191
- buffer += escapedNewlines
192
- ? decoded.replace(/\\n(?=event:|data:|\\n)/g, "\n")
193
- : decoded;
194
- const chunks = buffer.split("\n\n");
195
- buffer = chunks.pop() || "";
196
- for (const chunk of chunks) {
197
- if (!chunk.trim())
198
- continue;
199
- let event = "message";
200
- const dataLines = [];
201
- chunk.split("\n").forEach((line) => {
202
- if (line.startsWith("event:")) {
203
- event = line.slice(6).trim();
204
- }
205
- else if (line.startsWith("data:")) {
206
- dataLines.push(line.slice(5).trimStart());
207
- }
208
- else if (line.trim()) {
209
- dataLines.push(line);
210
- }
211
- });
212
- const data = dataLines.join("\n");
213
- const sanitizedData = data.includes("\n")
214
- ? data.replace(/\n/g, "\\n")
215
- : data;
216
- await handleStreamEvent(event || "message", sanitizedData, state, entry);
217
- }
218
- }
219
- }
220
- export async function handleStreamEvent(event, rawData, state, entry) {
221
- const parsed = parseMaybeJson(rawData);
222
- if (event === "turn") {
223
- void startDocChatEventStream(parsed);
224
- return;
225
- }
226
- if (event === "status") {
227
- state.statusText =
228
- typeof parsed === "string" ? parsed : parsed.status || "";
229
- renderChat();
230
- return;
231
- }
232
- if (event === "token") {
233
- const token = typeof parsed === "string"
234
- ? parsed
235
- : parsed.token || parsed.text || rawData || "";
236
- entry.response = (entry.response || "") + token;
237
- state.streamText = entry.response || "";
238
- if (!state.statusText || state.statusText === "queued") {
239
- state.statusText = "responding";
240
- }
241
- renderChat();
242
- return;
243
- }
244
- if (event === "update") {
245
- const payload = parseChatPayload(parsed);
246
- const fallbackPatch = recoverPatchFromRaw(rawData);
247
- if (fallbackPatch) {
248
- updatePatchPreviewFromDraft({
249
- patch: fallbackPatch,
250
- content: "",
251
- agentMessage: "",
252
- createdAt: "",
253
- baseHash: "",
254
- });
255
- }
256
- if (payload.response) {
257
- entry.response = payload.response;
258
- }
259
- state.streamText = entry.response;
260
- let updated = (payload.updated && payload.updated.length
261
- ? payload.updated
262
- : Object.keys(payload.drafts || {})) || [];
263
- if (!updated.length) {
264
- const recoveredDrafts = recoverDraftMap(rawData);
265
- if (recoveredDrafts) {
266
- updated = Object.keys(recoveredDrafts);
267
- entry.updated = updated;
268
- entry.drafts = recoveredDrafts;
269
- applyDraftUpdates(recoveredDrafts);
270
- updatePatchPreviewFromDraft(recoveredDrafts[getActiveDoc()]);
271
- entry.status = "done";
272
- renderChat();
273
- return;
274
- }
275
- const recoveredPatch = recoverPatchFromRaw(rawData);
276
- if (recoveredPatch) {
277
- const recoveredDraft = {
278
- patch: recoveredPatch,
279
- content: "",
280
- agentMessage: "",
281
- createdAt: "",
282
- baseHash: "",
283
- };
284
- entry.updated = [getActiveDoc()];
285
- entry.drafts = { [getActiveDoc()]: recoveredDraft };
286
- applyDraftUpdates(entry.drafts);
287
- updatePatchPreviewFromDraft(recoveredDraft);
288
- entry.status = "done";
289
- renderChat();
290
- return;
291
- }
292
- }
293
- if (updated.length) {
294
- entry.updated = updated;
295
- entry.drafts = payload.drafts || {};
296
- applyDraftUpdates(payload.drafts);
297
- updatePatchPreviewFromDraft(payload.drafts?.[getActiveDoc()]);
298
- entry.status = "done";
299
- }
300
- renderChat();
301
- return;
302
- }
303
- if (event === "error") {
304
- const message = (parsed && parsed.detail) ||
305
- (parsed && parsed.error) ||
306
- rawData ||
307
- "Doc chat failed";
308
- entry.status = "error";
309
- entry.error = String(message);
310
- state.error = String(message);
311
- state.status = "error";
312
- renderChat();
313
- resetChatEvents(state, { preserve: true });
314
- throw new Error(String(message));
315
- }
316
- if (event === "interrupted") {
317
- const message = (parsed && parsed.detail) || rawData || "Doc chat interrupted";
318
- entry.status = "interrupted";
319
- entry.error = String(message);
320
- state.error = "";
321
- state.status = "interrupted";
322
- state.streamText = entry.response || "";
323
- resetChatEvents(state, { preserve: true });
324
- renderChat();
325
- return;
326
- }
327
- if (event === "done" || event === "finish") {
328
- entry.status = "done";
329
- resetChatEvents(state, { preserve: true });
330
- return;
331
- }
332
- }
333
- export function applyChatResult(payload, state, entry) {
334
- const parsed = parseChatPayload(payload);
335
- if (parsed.interrupted) {
336
- entry.status = "interrupted";
337
- entry.error = parsed.detail || "Doc chat interrupted";
338
- state.status = "interrupted";
339
- state.error = "";
340
- return;
341
- }
342
- if (parsed.error) {
343
- entry.status = "error";
344
- entry.error = parsed.error;
345
- state.error = parsed.error;
346
- state.status = "error";
347
- renderChat();
348
- return;
349
- }
350
- entry.status = "done";
351
- entry.response = parsed.response || "(no response)";
352
- state.streamText = parsed.response || "";
353
- const updated = (parsed.updated && parsed.updated.length
354
- ? parsed.updated
355
- : Object.keys(parsed.drafts || {})) || [];
356
- if (updated.length) {
357
- entry.updated = updated;
358
- entry.drafts = parsed.drafts || {};
359
- applyDraftUpdates(parsed.drafts);
360
- }
361
- }
@@ -1,20 +0,0 @@
1
- import { initDocs } from "./docsInit.js";
2
- import { applyDocUpdateFromChat } from "./docsDocUpdates.js";
3
- import { applyPatch, discardPatch, reloadPatch } from "./docChatActions.js";
4
- import { getChatState } from "./docsState.js";
5
- import { handleStreamEvent, performDocChatRequest, applyChatResult } from "./docChatStream.js";
6
- import { renderChat } from "./docChatRender.js";
7
- import { setDoc } from "./docsCrud.js";
8
- export { initDocs };
9
- export const __docChatTest = {
10
- applyChatResult,
11
- applyDocUpdateFromChat,
12
- applyPatch,
13
- reloadPatch,
14
- discardPatch,
15
- getChatState,
16
- handleStreamEvent,
17
- performDocChatRequest,
18
- renderChat,
19
- setDoc,
20
- };
@@ -1,69 +0,0 @@
1
- import { flash } from "./utils.js";
2
- import { PASTEABLE_DOCS, getActiveDoc, hasDraft, isDraftPreview } from "./docsState.js";
3
- import { getDocTextarea, getDocCopyText, updateDocControls } from "./docsUi.js";
4
- export async function copyDocToClipboard(kind = getActiveDoc()) {
5
- const text = getDocCopyText(kind);
6
- if (!text.trim())
7
- return;
8
- try {
9
- if (navigator.clipboard?.writeText) {
10
- await navigator.clipboard.writeText(text);
11
- flash("Copied to clipboard");
12
- return;
13
- }
14
- }
15
- catch {
16
- // fall through
17
- }
18
- let temp = null;
19
- try {
20
- temp = document.createElement("textarea");
21
- temp.value = text;
22
- temp.setAttribute("readonly", "");
23
- temp.style.position = "fixed";
24
- temp.style.top = "-9999px";
25
- temp.style.opacity = "0";
26
- document.body.appendChild(temp);
27
- temp.select();
28
- const ok = document.execCommand("copy");
29
- flash(ok ? "Copied to clipboard" : "Copy failed");
30
- }
31
- catch {
32
- flash("Copy failed");
33
- }
34
- finally {
35
- if (temp && temp.parentNode) {
36
- temp.parentNode.removeChild(temp);
37
- }
38
- }
39
- }
40
- export async function pasteSpecFromClipboard() {
41
- const activeDoc = getActiveDoc();
42
- if (!PASTEABLE_DOCS.includes(activeDoc))
43
- return;
44
- if (hasDraft(activeDoc) && isDraftPreview(activeDoc)) {
45
- flash("Exit draft preview before pasting.", "error");
46
- return;
47
- }
48
- const textarea = getDocTextarea();
49
- if (!textarea)
50
- return;
51
- try {
52
- if (!navigator.clipboard?.readText) {
53
- flash("Paste not supported in this browser", "error");
54
- return;
55
- }
56
- const text = await navigator.clipboard.readText();
57
- if (!text) {
58
- flash("Clipboard is empty", "error");
59
- return;
60
- }
61
- textarea.value = text;
62
- textarea.focus();
63
- updateDocControls("spec");
64
- flash("SPEC replaced from clipboard");
65
- }
66
- catch {
67
- flash("Paste failed", "error");
68
- }
69
- }