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,160 +0,0 @@
1
- export function parseChatPayload(payload) {
2
- if (!payload)
3
- return { response: "", content: "", patch: "", drafts: {}, updated: [], createdAt: "", baseHash: "", agentMessage: "" };
4
- if (typeof payload === "string")
5
- return { response: payload, content: "", patch: "", drafts: {}, updated: [], createdAt: "", baseHash: "", agentMessage: "" };
6
- const p = payload;
7
- if (p.status && p.status !== "ok") {
8
- if (p.status === "interrupted") {
9
- return {
10
- interrupted: true,
11
- detail: p.detail || "Doc chat interrupted",
12
- response: "", content: "", patch: "", drafts: {}, updated: [], createdAt: "", baseHash: "", agentMessage: ""
13
- };
14
- }
15
- return { error: p.detail || "Doc chat failed", response: "", content: "", patch: "", drafts: {}, updated: [], createdAt: "", baseHash: "", agentMessage: "" };
16
- }
17
- return {
18
- response: p.response ||
19
- p.message ||
20
- p.agent_message ||
21
- p.agentMessage ||
22
- p.content ||
23
- "",
24
- content: p.content || "",
25
- patch: p.patch || "",
26
- drafts: normalizeDraftMap(p.drafts || p.draft),
27
- updated: Array.isArray(p.updated)
28
- ? p.updated.filter((entry) => typeof entry === "string")
29
- : [],
30
- createdAt: p.created_at || p.createdAt || "",
31
- baseHash: p.base_hash || p.baseHash || "",
32
- agentMessage: p.agent_message || p.agentMessage || "",
33
- };
34
- }
35
- export function parseSpecIngestPayload(payload) {
36
- if (!payload || typeof payload !== "object") {
37
- return { error: "Spec ingest failed", todo: "", progress: "", opinions: "", spec: "", summary: "", patch: "", agentMessage: "" };
38
- }
39
- const p = payload;
40
- if (p.status && p.status !== "ok") {
41
- if (p.status === "interrupted") {
42
- return {
43
- interrupted: true,
44
- todo: p.todo || "",
45
- progress: p.progress || "",
46
- opinions: p.opinions || "",
47
- spec: p.spec || "",
48
- summary: p.summary || "",
49
- patch: p.patch || "",
50
- agentMessage: p.agent_message || p.agentMessage || "",
51
- };
52
- }
53
- return { error: p.detail || "Spec ingest failed", todo: "", progress: "", opinions: "", spec: "", summary: "", patch: "", agentMessage: "" };
54
- }
55
- return {
56
- todo: p.todo || "",
57
- progress: p.progress || "",
58
- opinions: p.opinions || "",
59
- spec: p.spec || "",
60
- summary: p.summary || "",
61
- patch: p.patch || "",
62
- agentMessage: p.agent_message || p.agentMessage || "",
63
- };
64
- }
65
- export function parseMaybeJson(raw) {
66
- try {
67
- return JSON.parse(raw);
68
- }
69
- catch (err) {
70
- if (typeof raw === "string" && raw.includes("\n")) {
71
- try {
72
- return JSON.parse(raw.replace(/\n/g, "\\n"));
73
- }
74
- catch (_retryErr) {
75
- // fall through
76
- }
77
- }
78
- if (typeof raw === "string" && raw.includes("\\n")) {
79
- try {
80
- return JSON.parse(raw.replace(/\\n/g, "\\\\n"));
81
- }
82
- catch (_retryErr) {
83
- // fall through
84
- }
85
- }
86
- return raw;
87
- }
88
- }
89
- export function recoverDraftMap(raw) {
90
- if (typeof raw !== "string" || !raw.includes("\"drafts\""))
91
- return null;
92
- const candidates = [
93
- raw,
94
- raw.replace(/\n/g, "\\n"),
95
- raw.replace(/\\n/g, "\n"),
96
- raw.replace(/\\n/g, "\\\\n"),
97
- ];
98
- for (const candidate of candidates) {
99
- try {
100
- const parsed = JSON.parse(candidate);
101
- const drafts = normalizeDraftMap(parsed.drafts || parsed.draft);
102
- if (Object.keys(drafts).length)
103
- return drafts;
104
- }
105
- catch (_err) {
106
- // try next candidate
107
- }
108
- }
109
- return null;
110
- }
111
- export function recoverPatchFromRaw(raw) {
112
- if (typeof raw !== "string" || !raw.includes("\"patch\""))
113
- return null;
114
- const match = raw.match(/"patch"\s*:\s*"([\s\S]*?)"(?:,|\})/);
115
- if (!match)
116
- return null;
117
- return match[1]
118
- .replace(/\\\\n/g, "\n")
119
- .replace(/\\"/g, "\"")
120
- .replace(/\\\\/g, "\\");
121
- }
122
- export function normalizeDraftPayload(payload) {
123
- if (!payload || typeof payload !== "object")
124
- return null;
125
- const p = payload;
126
- const content = typeof p.content === "string" ? p.content : "";
127
- const patch = typeof p.patch === "string" ? p.patch : "";
128
- if (!content && !patch)
129
- return null;
130
- return {
131
- content,
132
- patch,
133
- agentMessage: typeof p.agent_message === "string"
134
- ? p.agent_message
135
- : typeof p.agentMessage === "string"
136
- ? p.agentMessage
137
- : "",
138
- createdAt: typeof p.created_at === "string"
139
- ? p.created_at
140
- : typeof p.createdAt === "string"
141
- ? p.createdAt
142
- : "",
143
- baseHash: typeof p.base_hash === "string"
144
- ? p.base_hash
145
- : typeof p.baseHash === "string"
146
- ? p.baseHash
147
- : "",
148
- };
149
- }
150
- export function normalizeDraftMap(raw) {
151
- if (!raw || typeof raw !== "object")
152
- return {};
153
- const drafts = {};
154
- Object.entries(raw).forEach(([kind, entry]) => {
155
- const normalized = normalizeDraftPayload(entry);
156
- if (normalized)
157
- drafts[kind] = normalized;
158
- });
159
- return drafts;
160
- }
@@ -1,87 +0,0 @@
1
- import { api, flash } from "./utils.js";
2
- import { snapshotUI } from "./docsElements.js";
3
- import { docsState, getActiveDoc } from "./docsState.js";
4
- import { getDocTextarea, updateCopyButton, getDocCopyText } from "./docsUi.js";
5
- export function setSnapshotBusy(on) {
6
- docsState.snapshotBusy = on;
7
- const disabled = !!on;
8
- for (const btn of [
9
- snapshotUI.generate,
10
- snapshotUI.update,
11
- snapshotUI.regenerate,
12
- snapshotUI.refresh,
13
- ]) {
14
- if (btn)
15
- btn.disabled = disabled;
16
- }
17
- updateCopyButton(snapshotUI.copy, getDocCopyText("snapshot"), disabled);
18
- const statusEl = document.getElementById("doc-status");
19
- if (statusEl && getActiveDoc() === "snapshot") {
20
- statusEl.textContent = on ? "Working…" : "Viewing SNAPSHOT";
21
- }
22
- }
23
- export function renderSnapshotButtons() {
24
- if (snapshotUI.generate)
25
- snapshotUI.generate.classList.toggle("hidden", false);
26
- if (snapshotUI.update)
27
- snapshotUI.update.classList.toggle("hidden", true);
28
- if (snapshotUI.regenerate)
29
- snapshotUI.regenerate.classList.toggle("hidden", true);
30
- updateCopyButton(snapshotUI.copy, getDocCopyText("snapshot"), docsState.snapshotBusy);
31
- }
32
- export async function loadSnapshot({ notify = false } = {}) {
33
- if (docsState.snapshotBusy)
34
- return;
35
- try {
36
- setSnapshotBusy(true);
37
- const data = await api("/api/snapshot");
38
- docsState.snapshotCache = {
39
- exists: !!data?.exists,
40
- content: data?.content || "",
41
- state: data?.state || {},
42
- };
43
- if (getActiveDoc() === "snapshot") {
44
- const textarea = getDocTextarea();
45
- if (textarea)
46
- textarea.value = docsState.snapshotCache.content || "";
47
- }
48
- renderSnapshotButtons();
49
- if (notify)
50
- flash(docsState.snapshotCache.exists ? "Snapshot loaded" : "No snapshot yet");
51
- }
52
- catch (err) {
53
- flash(err?.message || "Failed to load snapshot");
54
- }
55
- finally {
56
- setSnapshotBusy(false);
57
- }
58
- }
59
- export async function runSnapshot() {
60
- if (docsState.snapshotBusy)
61
- return;
62
- try {
63
- setSnapshotBusy(true);
64
- const data = await api("/api/snapshot", {
65
- method: "POST",
66
- body: {},
67
- });
68
- docsState.snapshotCache = {
69
- exists: true,
70
- content: data?.content || "",
71
- state: data?.state || {},
72
- };
73
- if (getActiveDoc() === "snapshot") {
74
- const textarea = getDocTextarea();
75
- if (textarea)
76
- textarea.value = docsState.snapshotCache.content || "";
77
- }
78
- renderSnapshotButtons();
79
- flash("Snapshot generated");
80
- }
81
- catch (err) {
82
- flash(err?.message || "Snapshot generation failed");
83
- }
84
- finally {
85
- setSnapshotBusy(false);
86
- }
87
- }
@@ -1,263 +0,0 @@
1
- import { api, confirmModal, flash } from "./utils.js";
2
- import { applySpecIngestDocs } from "./docsDocUpdates.js";
3
- import { parseSpecIngestPayload } from "./docsParse.js";
4
- import { getActiveDoc, docsState } from "./docsState.js";
5
- import { specIngestUI } from "./docsElements.js";
6
- import { renderDiffHtml, updateDocVisibility, autoResizeTextarea } from "./docsUi.js";
7
- import { getSelectedAgent, getSelectedModel, getSelectedReasoning, } from "./agentControls.js";
8
- export function renderSpecIngestPatch() {
9
- if (!specIngestUI.patchMain)
10
- return;
11
- const isSpec = getActiveDoc() === "spec";
12
- const hasPatch = !!(docsState.specIngestState.patch || "").trim();
13
- if (specIngestUI.continueBtn)
14
- specIngestUI.continueBtn.disabled = docsState.specIngestState.busy;
15
- if (specIngestUI.cancelBtn) {
16
- specIngestUI.cancelBtn.disabled = !docsState.specIngestState.busy;
17
- specIngestUI.cancelBtn.classList.toggle("hidden", !docsState.specIngestState.busy);
18
- }
19
- specIngestUI.patchMain.classList.toggle("hidden", !isSpec || !hasPatch);
20
- if (!isSpec || !hasPatch) {
21
- updateDocVisibility();
22
- return;
23
- }
24
- specIngestUI.patchBody.innerHTML = renderDiffHtml(docsState.specIngestState.patch);
25
- specIngestUI.patchSummary.textContent =
26
- docsState.specIngestState.agentMessage || "Spec ingest patch ready";
27
- if (specIngestUI.patchApply)
28
- specIngestUI.patchApply.disabled =
29
- docsState.specIngestState.busy || !hasPatch;
30
- if (specIngestUI.patchDiscard)
31
- specIngestUI.patchDiscard.disabled =
32
- docsState.specIngestState.busy || !hasPatch;
33
- if (specIngestUI.patchReload)
34
- specIngestUI.patchReload.disabled = docsState.specIngestState.busy;
35
- updateDocVisibility();
36
- }
37
- async function interruptSpecIngest() {
38
- try {
39
- await api("/api/ingest-spec/interrupt", { method: "POST" });
40
- }
41
- catch (err) {
42
- flash(err.message || "Failed to interrupt spec ingest", "error");
43
- }
44
- }
45
- export async function ingestSpec() {
46
- if (docsState.specIngestState.busy)
47
- return;
48
- const needsForce = ["todo", "progress", "opinions"].some((k) => (docsState.docsCache[k] || "").trim().length > 0);
49
- if (needsForce) {
50
- const ok = await confirmModal("Overwrite TODO, PROGRESS, and OPINIONS from SPEC? Existing content will be replaced.");
51
- if (!ok)
52
- return;
53
- }
54
- const button = document.getElementById("ingest-spec");
55
- if (button)
56
- button.disabled = true;
57
- button?.classList.add("loading");
58
- if (button)
59
- button.disabled = true;
60
- docsState.specIngestState.busy = true;
61
- docsState.specIngestState.controller = new AbortController();
62
- renderSpecIngestPatch();
63
- try {
64
- const data = await api("/api/ingest-spec", {
65
- method: "POST",
66
- body: {
67
- force: needsForce,
68
- agent: getSelectedAgent(),
69
- model: getSelectedModel(),
70
- reasoning: getSelectedReasoning(),
71
- },
72
- signal: docsState.specIngestState.controller.signal,
73
- });
74
- const parsed = parseSpecIngestPayload(data);
75
- if (parsed.error)
76
- throw new Error(parsed.error);
77
- if (parsed.interrupted) {
78
- docsState.specIngestState.patch = "";
79
- docsState.specIngestState.agentMessage = parsed.agentMessage || "";
80
- applySpecIngestDocs(parsed);
81
- renderSpecIngestPatch();
82
- flash("Spec ingest interrupted");
83
- return;
84
- }
85
- docsState.specIngestState.patch = parsed.patch || "";
86
- docsState.specIngestState.agentMessage = parsed.agentMessage || "";
87
- applySpecIngestDocs(parsed);
88
- renderSpecIngestPatch();
89
- flash(parsed.patch ? "Spec ingest patch ready" : "Ingested SPEC into docs");
90
- }
91
- catch (err) {
92
- if (err.name === "AbortError") {
93
- return;
94
- }
95
- else {
96
- flash(err.message, "error");
97
- }
98
- }
99
- finally {
100
- button?.classList.remove("loading");
101
- if (button)
102
- button.disabled = false;
103
- docsState.specIngestState.busy = false;
104
- docsState.specIngestState.controller = null;
105
- renderSpecIngestPatch();
106
- }
107
- }
108
- export function cancelSpecIngest() {
109
- if (!docsState.specIngestState.busy)
110
- return;
111
- interruptSpecIngest();
112
- if (docsState.specIngestState.controller)
113
- docsState.specIngestState.controller.abort();
114
- docsState.specIngestState.busy = false;
115
- docsState.specIngestState.controller = null;
116
- if (specIngestUI.continueBtn)
117
- specIngestUI.continueBtn.disabled = false;
118
- flash("Spec ingest interrupted");
119
- renderSpecIngestPatch();
120
- }
121
- export async function continueSpecIngest() {
122
- if (docsState.specIngestState.busy)
123
- return;
124
- if (!specIngestUI.input)
125
- return;
126
- const message = (specIngestUI.input.value || "").trim();
127
- if (!message) {
128
- flash("Enter a follow-up prompt to continue", "error");
129
- return;
130
- }
131
- const needsForce = ["todo", "progress", "opinions"].some((k) => (docsState.docsCache[k] || "").trim().length > 0);
132
- docsState.specIngestState.busy = true;
133
- if (specIngestUI.continueBtn)
134
- specIngestUI.continueBtn.disabled = true;
135
- docsState.specIngestState.controller = new AbortController();
136
- renderSpecIngestPatch();
137
- try {
138
- const data = await api("/api/ingest-spec", {
139
- method: "POST",
140
- body: {
141
- force: needsForce,
142
- message,
143
- agent: getSelectedAgent(),
144
- model: getSelectedModel(),
145
- reasoning: getSelectedReasoning(),
146
- },
147
- signal: docsState.specIngestState.controller.signal,
148
- });
149
- const parsed = parseSpecIngestPayload(data);
150
- if (parsed.error)
151
- throw new Error(parsed.error);
152
- if (parsed.interrupted) {
153
- docsState.specIngestState.patch = "";
154
- docsState.specIngestState.agentMessage = parsed.agentMessage || "";
155
- applySpecIngestDocs(parsed);
156
- renderSpecIngestPatch();
157
- flash("Spec ingest interrupted");
158
- return;
159
- }
160
- docsState.specIngestState.patch = parsed.patch || "";
161
- docsState.specIngestState.agentMessage = parsed.agentMessage || "";
162
- applySpecIngestDocs(parsed);
163
- renderSpecIngestPatch();
164
- specIngestUI.input.value = "";
165
- autoResizeTextarea(specIngestUI.input);
166
- flash(parsed.patch ? "Spec ingest patch updated" : "Spec ingest updated docs");
167
- }
168
- catch (err) {
169
- if (err.name === "AbortError") {
170
- return;
171
- }
172
- else {
173
- flash(err.message, "error");
174
- }
175
- }
176
- finally {
177
- docsState.specIngestState.busy = false;
178
- if (specIngestUI.continueBtn)
179
- specIngestUI.continueBtn.disabled = false;
180
- docsState.specIngestState.controller = null;
181
- renderSpecIngestPatch();
182
- }
183
- }
184
- export async function applySpecIngestPatch() {
185
- if (!docsState.specIngestState.patch) {
186
- flash("No spec ingest patch to apply", "error");
187
- return;
188
- }
189
- docsState.specIngestState.busy = true;
190
- renderSpecIngestPatch();
191
- try {
192
- const res = await api("/api/ingest-spec/apply", { method: "POST" });
193
- const parsed = parseSpecIngestPayload(res);
194
- if (parsed.error)
195
- throw new Error(parsed.error);
196
- docsState.specIngestState.patch = "";
197
- docsState.specIngestState.agentMessage = "";
198
- applySpecIngestDocs(parsed);
199
- flash("Spec ingest patch applied");
200
- }
201
- catch (err) {
202
- flash(err.message || "Failed to apply spec ingest patch", "error");
203
- }
204
- finally {
205
- docsState.specIngestState.busy = false;
206
- renderSpecIngestPatch();
207
- }
208
- }
209
- export async function discardSpecIngestPatch() {
210
- if (!docsState.specIngestState.patch)
211
- return;
212
- docsState.specIngestState.busy = true;
213
- renderSpecIngestPatch();
214
- try {
215
- const res = await api("/api/ingest-spec/discard", { method: "POST" });
216
- const parsed = parseSpecIngestPayload(res);
217
- if (parsed.error)
218
- throw new Error(parsed.error);
219
- docsState.specIngestState.patch = "";
220
- docsState.specIngestState.agentMessage = "";
221
- applySpecIngestDocs(parsed);
222
- flash("Spec ingest patch discarded");
223
- }
224
- catch (err) {
225
- flash(err.message || "Failed to discard spec ingest patch", "error");
226
- }
227
- finally {
228
- docsState.specIngestState.busy = false;
229
- renderSpecIngestPatch();
230
- }
231
- }
232
- export async function reloadSpecIngestPatch(silent = false) {
233
- try {
234
- const res = await api("/api/ingest-spec/pending", { method: "GET" });
235
- const parsed = parseSpecIngestPayload(res);
236
- if (parsed.error)
237
- throw new Error(parsed.error);
238
- if (parsed.patch) {
239
- docsState.specIngestState.patch = parsed.patch;
240
- docsState.specIngestState.agentMessage = parsed.agentMessage || "";
241
- applySpecIngestDocs(parsed);
242
- renderSpecIngestPatch();
243
- if (!silent)
244
- flash("Loaded spec ingest patch");
245
- return;
246
- }
247
- }
248
- catch (err) {
249
- const message = err?.message || "";
250
- if (message.includes("No pending spec ingest patch")) {
251
- docsState.specIngestState.patch = "";
252
- docsState.specIngestState.agentMessage = "";
253
- renderSpecIngestPatch();
254
- return;
255
- }
256
- if (!silent) {
257
- flash(message || "Failed to load spec ingest patch", "error");
258
- }
259
- }
260
- if (!docsState.specIngestState.patch) {
261
- renderSpecIngestPatch();
262
- }
263
- }
@@ -1,127 +0,0 @@
1
- import { CONSTANTS } from "./constants.js";
2
- import { docButtons } from "./docsElements.js";
3
- export const DOC_TYPES = ["todo", "progress", "opinions", "spec", "summary"];
4
- export const CLEARABLE_DOCS = ["todo", "progress", "opinions"];
5
- export const COPYABLE_DOCS = ["spec", "summary"];
6
- export const PASTEABLE_DOCS = ["spec"];
7
- export const CHAT_HISTORY_LIMIT = 8;
8
- export const CHAT_EVENT_LIMIT = CONSTANTS.UI?.DOC_CHAT_EVENT_LIMIT || 12;
9
- export const CHAT_EVENT_MAX = Math.max(60, CHAT_EVENT_LIMIT * 8);
10
- export const chatDecoder = new TextDecoder();
11
- export const docsState = {
12
- docsCache: { todo: "", progress: "", opinions: "", spec: "", summary: "" },
13
- snapshotCache: { exists: false, content: "", state: {} },
14
- snapshotBusy: false,
15
- activeDoc: "todo",
16
- chatState: createChatState(),
17
- draftState: {
18
- data: {},
19
- preview: {},
20
- },
21
- specIngestState: {
22
- status: "idle",
23
- patch: "",
24
- agentMessage: "",
25
- error: "",
26
- busy: false,
27
- controller: null,
28
- },
29
- historyNavIndex: -1,
30
- };
31
- export const VOICE_TRANSCRIPT_DISCLAIMER_TEXT = CONSTANTS.PROMPTS?.VOICE_TRANSCRIPT_DISCLAIMER ||
32
- "Note: transcribed from user voice. If confusing or possibly inaccurate and you cannot infer the intention please clarify before proceeding.";
33
- export function createChatState() {
34
- return {
35
- history: [],
36
- status: "idle",
37
- statusText: "",
38
- error: "",
39
- streamText: "",
40
- controller: null,
41
- events: [],
42
- eventsExpanded: false,
43
- eventController: null,
44
- eventTurnId: null,
45
- eventThreadId: null,
46
- eventAgent: null,
47
- eventItemIndex: {},
48
- eventError: "",
49
- };
50
- }
51
- export function getChatState() {
52
- return docsState.chatState;
53
- }
54
- export function getActiveDoc() {
55
- return docsState.activeDoc;
56
- }
57
- export function setActiveDoc(kind) {
58
- docsState.activeDoc = kind;
59
- }
60
- export function getHistoryNavIndex() {
61
- return docsState.historyNavIndex;
62
- }
63
- export function setHistoryNavIndex(value) {
64
- docsState.historyNavIndex = value;
65
- }
66
- export function setDraft(kind, draft) {
67
- if (!DOC_TYPES.includes(kind))
68
- return;
69
- if (!draft) {
70
- delete docsState.draftState.data[kind];
71
- delete docsState.draftState.preview[kind];
72
- }
73
- else {
74
- docsState.draftState.data[kind] = draft;
75
- }
76
- updateDocDraftIndicators();
77
- }
78
- export function getDraft(kind) {
79
- return docsState.draftState.data[kind] || null;
80
- }
81
- export function hasDraft(kind) {
82
- return !!getDraft(kind);
83
- }
84
- export function isDraftPreview(kind) {
85
- return !!docsState.draftState.preview[kind];
86
- }
87
- export function setDraftPreview(kind, value) {
88
- if (!DOC_TYPES.includes(kind))
89
- return;
90
- if (value) {
91
- docsState.draftState.preview[kind] = true;
92
- }
93
- else {
94
- delete docsState.draftState.preview[kind];
95
- }
96
- updateDocDraftIndicators();
97
- }
98
- export function updateDocDraftIndicators() {
99
- docButtons.forEach((btn) => {
100
- const htmlBtn = btn;
101
- const kind = htmlBtn.dataset.doc;
102
- if (!DOC_TYPES.includes(kind))
103
- return;
104
- htmlBtn.classList.toggle("has-draft", hasDraft(kind));
105
- htmlBtn.classList.toggle("previewing", hasDraft(kind) && isDraftPreview(kind));
106
- });
107
- }
108
- export function resetChatEvents(state, { preserve = false } = {}) {
109
- if (state.eventController) {
110
- state.eventController.abort();
111
- }
112
- state.eventController = null;
113
- state.eventTurnId = null;
114
- state.eventThreadId = null;
115
- state.eventAgent = null;
116
- state.eventItemIndex = {};
117
- state.eventError = "";
118
- if (!preserve) {
119
- state.events = [];
120
- state.eventsExpanded = false;
121
- }
122
- }
123
- export function getDocChatViewing() {
124
- if (!DOC_TYPES.includes(docsState.activeDoc))
125
- return "todo";
126
- return docsState.activeDoc;
127
- }
@@ -1,44 +0,0 @@
1
- import { api, flash, resolvePath } from "./utils.js";
2
- import { threadRegistryUI } from "./docsElements.js";
3
- export function renderThreadRegistryBanner(notice) {
4
- if (!threadRegistryUI.banner)
5
- return;
6
- const active = notice && notice.status === "corrupt";
7
- threadRegistryUI.banner.classList.toggle("hidden", !active);
8
- if (!active)
9
- return;
10
- const backupPath = notice && typeof notice.backup_path === "string" ? notice.backup_path : "";
11
- if (threadRegistryUI.detail) {
12
- threadRegistryUI.detail.textContent = backupPath
13
- ? `Backup: ${backupPath}`
14
- : "Backup unavailable";
15
- threadRegistryUI.detail.title = backupPath || "";
16
- }
17
- if (threadRegistryUI.download) {
18
- threadRegistryUI.download.classList.toggle("hidden", !backupPath);
19
- }
20
- }
21
- export async function loadThreadRegistryStatus() {
22
- if (!threadRegistryUI.banner)
23
- return;
24
- try {
25
- const data = await api("/api/app-server/threads");
26
- renderThreadRegistryBanner(data?.corruption);
27
- }
28
- catch (err) {
29
- console.error("Failed to load thread registry status", err);
30
- }
31
- }
32
- export async function resetThreadRegistry() {
33
- try {
34
- await api("/api/app-server/threads/reset-all", { method: "POST" });
35
- renderThreadRegistryBanner(null);
36
- flash("Conversations reset");
37
- }
38
- catch (err) {
39
- flash(err.message || "Failed to reset conversations", "error");
40
- }
41
- }
42
- export function downloadThreadRegistryBackup() {
43
- window.location.href = resolvePath("/api/app-server/threads/backup");
44
- }