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,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
- }