codex-autorunner 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +17 -7
- codex_autorunner/bootstrap.py +219 -1
- codex_autorunner/core/__init__.py +17 -1
- codex_autorunner/core/about_car.py +124 -11
- codex_autorunner/core/app_server_threads.py +6 -0
- codex_autorunner/core/config.py +238 -3
- codex_autorunner/core/context_awareness.py +39 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +71 -1
- codex_autorunner/core/flows/reconciler.py +4 -1
- codex_autorunner/core/flows/runtime.py +22 -0
- codex_autorunner/core/flows/store.py +61 -9
- codex_autorunner/core/flows/transition.py +23 -16
- codex_autorunner/core/flows/ux_helpers.py +18 -3
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/hub.py +198 -41
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +683 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/agent_backend.py +2 -5
- codex_autorunner/core/ports/run_event.py +1 -4
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +5 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/ticket_linter_cli.py +17 -0
- codex_autorunner/core/ticket_manager_cli.py +154 -92
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/utils.py +34 -6
- codex_autorunner/flows/review/service.py +23 -25
- codex_autorunner/flows/ticket_flow/definition.py +43 -1
- codex_autorunner/integrations/agents/__init__.py +2 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
- codex_autorunner/integrations/agents/codex_backend.py +19 -8
- codex_autorunner/integrations/agents/runner.py +3 -8
- codex_autorunner/integrations/agents/wiring.py +8 -0
- codex_autorunner/integrations/telegram/adapter.py +1 -1
- codex_autorunner/integrations/telegram/config.py +1 -1
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
- codex_autorunner/integrations/telegram/handlers/messages.py +34 -3
- codex_autorunner/integrations/telegram/helpers.py +1 -3
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +30 -0
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
- codex_autorunner/integrations/telegram/transport.py +10 -3
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/server.py +2 -2
- codex_autorunner/static/agentControls.js +21 -5
- codex_autorunner/static/app.js +115 -11
- codex_autorunner/static/archive.js +274 -81
- codex_autorunner/static/archiveApi.js +21 -0
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/constants.js +1 -1
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +46 -81
- codex_autorunner/static/index.html +303 -24
- codex_autorunner/static/messages.js +82 -4
- codex_autorunner/static/notifications.js +288 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/settings.js +3 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9141 -6742
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/terminalManager.js +22 -3
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +41 -13
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +69 -19
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +28 -0
- codex_autorunner/static/workspace.js +258 -44
- codex_autorunner/static/workspaceFileBrowser.js +6 -4
- codex_autorunner/surfaces/cli/cli.py +1465 -155
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/web/app.py +253 -49
- codex_autorunner/surfaces/web/routes/__init__.py +4 -0
- codex_autorunner/surfaces/web/routes/analytics.py +29 -22
- codex_autorunner/surfaces/web/routes/archive.py +197 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +297 -36
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +219 -29
- codex_autorunner/surfaces/web/routes/messages.py +70 -39
- codex_autorunner/surfaces/web/routes/pma.py +1652 -0
- codex_autorunner/surfaces/web/routes/repos.py +1 -1
- codex_autorunner/surfaces/web/routes/shared.py +0 -3
- codex_autorunner/surfaces/web/routes/templates.py +634 -0
- codex_autorunner/surfaces/web/runner_manager.py +2 -2
- codex_autorunner/surfaces/web/schemas.py +81 -18
- codex_autorunner/tickets/agent_pool.py +27 -0
- codex_autorunner/tickets/files.py +33 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +3 -0
- codex_autorunner/tickets/outbox.py +41 -5
- codex_autorunner/tickets/runner.py +350 -69
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/METADATA +15 -19
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/RECORD +132 -101
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -3302
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -3,21 +3,14 @@
|
|
|
3
3
|
* Ticket Chat Stream - handles SSE streaming for ticket chat
|
|
4
4
|
*/
|
|
5
5
|
import { resolvePath, getAuthToken } from "./utils.js";
|
|
6
|
-
import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, } from "./ticketChatActions.js";
|
|
6
|
+
import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, applyTicketChatResult, } from "./ticketChatActions.js";
|
|
7
7
|
import { applyTicketEvent, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
|
|
8
|
-
|
|
9
|
-
function parseMaybeJson(data) {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(data);
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return data;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
8
|
+
import { extractContextRemainingPercent, readEventStream, parseMaybeJson } from "./streamUtils.js";
|
|
17
9
|
export async function performTicketChatRequest(ticketIndex, message, signal, options = {}) {
|
|
18
10
|
// Clear events from previous request and add user message to history
|
|
19
11
|
clearTicketEvents();
|
|
20
12
|
addUserMessage(message);
|
|
13
|
+
ticketChatState.contextUsagePercent = null;
|
|
21
14
|
// Render both chat (for container visibility) and messages
|
|
22
15
|
renderTicketChat();
|
|
23
16
|
renderTicketMessages();
|
|
@@ -39,6 +32,8 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
|
|
|
39
32
|
payload.model = options.model;
|
|
40
33
|
if (options.reasoning)
|
|
41
34
|
payload.reasoning = options.reasoning;
|
|
35
|
+
if (options.clientTurnId)
|
|
36
|
+
payload.client_turn_id = options.clientTurnId;
|
|
42
37
|
const res = await fetch(endpoint, {
|
|
43
38
|
method: "POST",
|
|
44
39
|
headers,
|
|
@@ -59,7 +54,7 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
|
|
|
59
54
|
}
|
|
60
55
|
const contentType = res.headers.get("content-type") || "";
|
|
61
56
|
if (contentType.includes("text/event-stream")) {
|
|
62
|
-
await
|
|
57
|
+
await readEventStream(res, (event, data) => handleTicketStreamEvent(event, data));
|
|
63
58
|
}
|
|
64
59
|
else {
|
|
65
60
|
// Non-streaming response
|
|
@@ -69,54 +64,6 @@ export async function performTicketChatRequest(ticketIndex, message, signal, opt
|
|
|
69
64
|
applyTicketChatResult(responsePayload);
|
|
70
65
|
}
|
|
71
66
|
}
|
|
72
|
-
async function readTicketChatStream(res) {
|
|
73
|
-
if (!res.body)
|
|
74
|
-
throw new Error("Streaming not supported in this browser");
|
|
75
|
-
const reader = res.body.getReader();
|
|
76
|
-
let buffer = "";
|
|
77
|
-
let escapedNewlines = false;
|
|
78
|
-
for (;;) {
|
|
79
|
-
const { value, done } = await reader.read();
|
|
80
|
-
if (done)
|
|
81
|
-
break;
|
|
82
|
-
const decoded = decoder.decode(value, { stream: true });
|
|
83
|
-
// Handle escaped newlines
|
|
84
|
-
if (!escapedNewlines) {
|
|
85
|
-
const combined = buffer + decoded;
|
|
86
|
-
if (!combined.includes("\n") && combined.includes("\\n")) {
|
|
87
|
-
escapedNewlines = true;
|
|
88
|
-
buffer = buffer.replace(/\\n(?=event:|data:|\\n)/g, "\n");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
buffer += escapedNewlines
|
|
92
|
-
? decoded.replace(/\\n(?=event:|data:|\\n)/g, "\n")
|
|
93
|
-
: decoded;
|
|
94
|
-
// Split on double newlines (SSE message delimiter)
|
|
95
|
-
const chunks = buffer.split("\n\n");
|
|
96
|
-
buffer = chunks.pop() || "";
|
|
97
|
-
for (const chunk of chunks) {
|
|
98
|
-
if (!chunk.trim())
|
|
99
|
-
continue;
|
|
100
|
-
let event = "message";
|
|
101
|
-
const dataLines = [];
|
|
102
|
-
chunk.split("\n").forEach((line) => {
|
|
103
|
-
if (line.startsWith("event:")) {
|
|
104
|
-
event = line.slice(6).trim();
|
|
105
|
-
}
|
|
106
|
-
else if (line.startsWith("data:")) {
|
|
107
|
-
dataLines.push(line.slice(5).trimStart());
|
|
108
|
-
}
|
|
109
|
-
else if (line.trim()) {
|
|
110
|
-
dataLines.push(line);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
if (dataLines.length === 0)
|
|
114
|
-
continue;
|
|
115
|
-
const data = dataLines.join("\n");
|
|
116
|
-
handleTicketStreamEvent(event, data);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
67
|
function handleTicketStreamEvent(event, rawData) {
|
|
121
68
|
const parsed = parseMaybeJson(rawData);
|
|
122
69
|
switch (event) {
|
|
@@ -154,6 +101,17 @@ function handleTicketStreamEvent(event, rawData) {
|
|
|
154
101
|
renderTicketEvents();
|
|
155
102
|
break;
|
|
156
103
|
}
|
|
104
|
+
case "token_usage": {
|
|
105
|
+
// Token usage events - context window usage
|
|
106
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
107
|
+
const percentRemaining = extractContextRemainingPercent(parsed);
|
|
108
|
+
if (percentRemaining !== null) {
|
|
109
|
+
ticketChatState.contextUsagePercent = percentRemaining;
|
|
110
|
+
renderTicketChat();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
157
115
|
case "error": {
|
|
158
116
|
const message = typeof parsed === "object" && parsed !== null
|
|
159
117
|
? parsed.detail ||
|
|
@@ -202,63 +160,3 @@ function handleTicketStreamEvent(event, rawData) {
|
|
|
202
160
|
break;
|
|
203
161
|
}
|
|
204
162
|
}
|
|
205
|
-
function applyTicketChatResult(payload) {
|
|
206
|
-
if (!payload || typeof payload !== "object")
|
|
207
|
-
return;
|
|
208
|
-
const result = payload;
|
|
209
|
-
if (result.status === "interrupted") {
|
|
210
|
-
ticketChatState.status = "interrupted";
|
|
211
|
-
ticketChatState.error = "";
|
|
212
|
-
addAssistantMessage("Request interrupted", true);
|
|
213
|
-
renderTicketChat();
|
|
214
|
-
renderTicketMessages();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (result.status === "error" || result.error) {
|
|
218
|
-
ticketChatState.status = "error";
|
|
219
|
-
ticketChatState.error =
|
|
220
|
-
result.detail || result.error || "Chat failed";
|
|
221
|
-
addAssistantMessage(`Error: ${ticketChatState.error}`, true);
|
|
222
|
-
renderTicketChat();
|
|
223
|
-
renderTicketMessages();
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
// Success
|
|
227
|
-
ticketChatState.status = "done";
|
|
228
|
-
if (result.message) {
|
|
229
|
-
ticketChatState.streamText = result.message;
|
|
230
|
-
}
|
|
231
|
-
if (result.agent_message || result.agentMessage) {
|
|
232
|
-
ticketChatState.statusText =
|
|
233
|
-
result.agent_message || result.agentMessage || "";
|
|
234
|
-
}
|
|
235
|
-
// Check for draft/patch in response
|
|
236
|
-
const hasDraft = result.has_draft ?? result.hasDraft;
|
|
237
|
-
if (hasDraft === false) {
|
|
238
|
-
ticketChatState.draft = null;
|
|
239
|
-
}
|
|
240
|
-
else if (hasDraft === true || result.draft || result.patch || result.content) {
|
|
241
|
-
ticketChatState.draft = {
|
|
242
|
-
content: result.content || "",
|
|
243
|
-
patch: result.patch || "",
|
|
244
|
-
agentMessage: result.agent_message || result.agentMessage || "",
|
|
245
|
-
createdAt: result.created_at || result.createdAt || "",
|
|
246
|
-
baseHash: result.base_hash || result.baseHash || "",
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
// Add assistant message from response
|
|
250
|
-
const responseText = ticketChatState.streamText ||
|
|
251
|
-
ticketChatState.statusText ||
|
|
252
|
-
(ticketChatState.draft ? "Changes ready to apply" : "Done");
|
|
253
|
-
if (responseText && ticketChatState.messages.length > 0) {
|
|
254
|
-
// Only add if we have messages (i.e., a user message was sent)
|
|
255
|
-
const lastMessage = ticketChatState.messages[ticketChatState.messages.length - 1];
|
|
256
|
-
// Avoid duplicate assistant messages
|
|
257
|
-
if (lastMessage.role === "user") {
|
|
258
|
-
addAssistantMessage(responseText, true);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
renderTicketChat();
|
|
262
|
-
renderTicketMessages();
|
|
263
|
-
renderTicketEvents();
|
|
264
|
-
}
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Ticket Editor Modal - handles creating, editing, and deleting tickets
|
|
4
4
|
*/
|
|
5
|
-
import { api, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
|
|
5
|
+
import { api, confirmModal, flash, updateUrlParams, splitMarkdownFrontmatter } from "./utils.js";
|
|
6
6
|
import { publish } from "./bus.js";
|
|
7
7
|
import { clearTicketChatHistory } from "./ticketChatStorage.js";
|
|
8
|
-
import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, } from "./ticketChatActions.js";
|
|
8
|
+
import { setTicketIndex, sendTicketChat, cancelTicketChat, applyTicketPatch, discardTicketPatch, loadTicketPending, renderTicketChat, resetTicketChatState, ticketChatState, resumeTicketPendingTurn, } from "./ticketChatActions.js";
|
|
9
9
|
import { initAgentControls } from "./agentControls.js";
|
|
10
10
|
import { initTicketVoice } from "./ticketVoice.js";
|
|
11
11
|
import { initTicketChatEvents, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
|
|
12
|
+
import { initChatPasteUpload } from "./chatUploads.js";
|
|
12
13
|
import { DocEditor } from "./docEditor.js";
|
|
14
|
+
import { initTicketTemplates } from "./ticketTemplates.js";
|
|
13
15
|
const DEFAULT_FRONTMATTER = {
|
|
14
16
|
agent: "codex",
|
|
15
17
|
done: false,
|
|
@@ -88,7 +90,13 @@ async function navigateTicket(delta) {
|
|
|
88
90
|
const idx = list.findIndex((ticket) => ticket.index === state.ticketIndex);
|
|
89
91
|
const target = idx >= 0 ? list[idx + delta] : null;
|
|
90
92
|
if (target && target.index != null) {
|
|
91
|
-
|
|
93
|
+
try {
|
|
94
|
+
const data = (await api(`/api/flows/ticket_flow/tickets/${target.index}`));
|
|
95
|
+
openTicketEditor(data);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
flash(`Failed to navigate to ticket: ${err.message}`, "error");
|
|
99
|
+
}
|
|
92
100
|
}
|
|
93
101
|
void updateTicketNavButtons();
|
|
94
102
|
}
|
|
@@ -298,20 +306,24 @@ function extractFrontmatter(ticket) {
|
|
|
298
306
|
/**
|
|
299
307
|
* Build full markdown content from frontmatter form + body textarea
|
|
300
308
|
*/
|
|
309
|
+
function yamlQuote(value) {
|
|
310
|
+
// Use JSON.stringify for simple, safe double-quoted scalars (handles colons, quotes, newlines).
|
|
311
|
+
return JSON.stringify(value ?? "");
|
|
312
|
+
}
|
|
301
313
|
function buildTicketContent() {
|
|
302
314
|
const { content } = els();
|
|
303
315
|
const fm = getFrontmatterFromForm();
|
|
304
316
|
const body = content?.value || "";
|
|
305
|
-
// Reconstruct frontmatter YAML
|
|
317
|
+
// Reconstruct frontmatter YAML with quoted scalars to tolerate special characters.
|
|
306
318
|
const lines = ["---"];
|
|
307
|
-
lines.push(`agent: ${fm.agent}`);
|
|
319
|
+
lines.push(`agent: ${yamlQuote(fm.agent)}`);
|
|
308
320
|
lines.push(`done: ${fm.done}`);
|
|
309
321
|
if (fm.title)
|
|
310
|
-
lines.push(`title: ${fm.title}`);
|
|
322
|
+
lines.push(`title: ${yamlQuote(fm.title)}`);
|
|
311
323
|
if (fm.model)
|
|
312
|
-
lines.push(`model: ${fm.model}`);
|
|
324
|
+
lines.push(`model: ${yamlQuote(fm.model)}`);
|
|
313
325
|
if (fm.reasoning)
|
|
314
|
-
lines.push(`reasoning: ${fm.reasoning}`);
|
|
326
|
+
lines.push(`reasoning: ${yamlQuote(fm.reasoning)}`);
|
|
315
327
|
lines.push("---");
|
|
316
328
|
lines.push("");
|
|
317
329
|
lines.push(body);
|
|
@@ -478,8 +490,12 @@ async function performAutosave() {
|
|
|
478
490
|
// Notify that tickets changed
|
|
479
491
|
publish("tickets:updated", {});
|
|
480
492
|
}
|
|
481
|
-
catch {
|
|
493
|
+
catch (err) {
|
|
494
|
+
// Surface the failure to the user and let DocEditor keep the "dirty" state
|
|
495
|
+
// so a retry is attempted instead of falsely reporting success.
|
|
482
496
|
setAutosaveStatus("error");
|
|
497
|
+
flash(err?.message || "Failed to save ticket", "error");
|
|
498
|
+
throw err;
|
|
483
499
|
}
|
|
484
500
|
}
|
|
485
501
|
/**
|
|
@@ -590,6 +606,7 @@ export function openTicketEditor(ticket) {
|
|
|
590
606
|
renderTicketChat();
|
|
591
607
|
renderTicketEvents();
|
|
592
608
|
renderTicketMessages();
|
|
609
|
+
void resumeTicketPendingTurn(ticket?.index ?? null);
|
|
593
610
|
state.isOpen = true;
|
|
594
611
|
modal.classList.remove("hidden");
|
|
595
612
|
// Update URL with ticket index
|
|
@@ -614,7 +631,9 @@ export function closeTicketEditor() {
|
|
|
614
631
|
return;
|
|
615
632
|
// Autosave on close if there are changes
|
|
616
633
|
if (hasUnsavedChanges()) {
|
|
617
|
-
|
|
634
|
+
// Fire-and-forget: swallow rejection because the error is already flashed
|
|
635
|
+
// inside performAutosave and DocEditor keeps the buffer dirty for retry.
|
|
636
|
+
void performAutosave().catch(() => { });
|
|
618
637
|
}
|
|
619
638
|
// Cancel any running chat
|
|
620
639
|
if (ticketChatState.status === "running") {
|
|
@@ -654,7 +673,7 @@ export async function deleteTicket() {
|
|
|
654
673
|
flash("Cannot delete: no ticket selected", "error");
|
|
655
674
|
return;
|
|
656
675
|
}
|
|
657
|
-
const confirmed =
|
|
676
|
+
const confirmed = await confirmModal(`Delete TICKET-${String(state.ticketIndex).padStart(3, "0")}.md? This cannot be undone.`);
|
|
658
677
|
if (!confirmed)
|
|
659
678
|
return;
|
|
660
679
|
setButtonsLoading(true);
|
|
@@ -703,6 +722,8 @@ export function initTicketEditor() {
|
|
|
703
722
|
void initTicketVoice();
|
|
704
723
|
// Initialize rich chat experience (events toggle, etc.)
|
|
705
724
|
initTicketChatEvents();
|
|
725
|
+
// Initialize ticket templates picker
|
|
726
|
+
initTicketTemplates();
|
|
706
727
|
// Button handlers
|
|
707
728
|
if (deleteBtn)
|
|
708
729
|
deleteBtn.addEventListener("click", () => void deleteTicket());
|
|
@@ -772,6 +793,13 @@ export function initTicketEditor() {
|
|
|
772
793
|
chatInput.style.height = "auto";
|
|
773
794
|
chatInput.style.height = Math.min(chatInput.scrollHeight, 100) + "px";
|
|
774
795
|
});
|
|
796
|
+
initChatPasteUpload({
|
|
797
|
+
textarea: chatInput,
|
|
798
|
+
basePath: "/api/filebox",
|
|
799
|
+
box: "inbox",
|
|
800
|
+
insertStyle: "both",
|
|
801
|
+
pathPrefix: ".codex-autorunner/filebox",
|
|
802
|
+
});
|
|
775
803
|
}
|
|
776
804
|
// Close on backdrop click
|
|
777
805
|
modal.addEventListener("click", (e) => {
|
|
@@ -806,8 +834,8 @@ export function initTicketEditor() {
|
|
|
806
834
|
// Don't interfere with typing
|
|
807
835
|
if (isTypingTarget(e.target))
|
|
808
836
|
return;
|
|
809
|
-
//
|
|
810
|
-
if (e.ctrlKey || e.metaKey || e.shiftKey)
|
|
837
|
+
// Require Alt modifier for navigation (no Ctrl/Meta/Shift)
|
|
838
|
+
if (!e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
|
|
811
839
|
return;
|
|
812
840
|
e.preventDefault();
|
|
813
841
|
void navigateTicket(e.key === "ArrowLeft" ? -1 : 1);
|