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.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
/**
|
|
3
|
+
* Ticket Chat Stream - handles SSE streaming for ticket chat
|
|
4
|
+
*/
|
|
5
|
+
import { resolvePath, getAuthToken } from "./utils.js";
|
|
6
|
+
import { ticketChatState, renderTicketChat, clearTicketEvents, addUserMessage, addAssistantMessage, } from "./ticketChatActions.js";
|
|
7
|
+
import { applyTicketEvent, renderTicketEvents, renderTicketMessages } from "./ticketChatEvents.js";
|
|
8
|
+
const decoder = new TextDecoder();
|
|
9
|
+
function parseMaybeJson(data) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(data);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function performTicketChatRequest(ticketIndex, message, signal, options = {}) {
|
|
18
|
+
// Clear events from previous request and add user message to history
|
|
19
|
+
clearTicketEvents();
|
|
20
|
+
addUserMessage(message);
|
|
21
|
+
// Render both chat (for container visibility) and messages
|
|
22
|
+
renderTicketChat();
|
|
23
|
+
renderTicketMessages();
|
|
24
|
+
const endpoint = resolvePath(`/api/tickets/${ticketIndex}/chat`);
|
|
25
|
+
const headers = {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
};
|
|
28
|
+
const token = getAuthToken();
|
|
29
|
+
if (token) {
|
|
30
|
+
headers.Authorization = `Bearer ${token}`;
|
|
31
|
+
}
|
|
32
|
+
const payload = {
|
|
33
|
+
message,
|
|
34
|
+
stream: true,
|
|
35
|
+
};
|
|
36
|
+
if (options.agent)
|
|
37
|
+
payload.agent = options.agent;
|
|
38
|
+
if (options.model)
|
|
39
|
+
payload.model = options.model;
|
|
40
|
+
if (options.reasoning)
|
|
41
|
+
payload.reasoning = options.reasoning;
|
|
42
|
+
const res = await fetch(endpoint, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers,
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
|
+
signal,
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
let detail = text;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(text);
|
|
53
|
+
detail = parsed.detail || parsed.error || text;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore parse errors
|
|
57
|
+
}
|
|
58
|
+
throw new Error(detail || `Request failed (${res.status})`);
|
|
59
|
+
}
|
|
60
|
+
const contentType = res.headers.get("content-type") || "";
|
|
61
|
+
if (contentType.includes("text/event-stream")) {
|
|
62
|
+
await readTicketChatStream(res);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Non-streaming response
|
|
66
|
+
const responsePayload = contentType.includes("application/json")
|
|
67
|
+
? await res.json()
|
|
68
|
+
: await res.text();
|
|
69
|
+
applyTicketChatResult(responsePayload);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
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
|
+
function handleTicketStreamEvent(event, rawData) {
|
|
121
|
+
const parsed = parseMaybeJson(rawData);
|
|
122
|
+
switch (event) {
|
|
123
|
+
case "status": {
|
|
124
|
+
const status = typeof parsed === "string"
|
|
125
|
+
? parsed
|
|
126
|
+
: parsed.status || "";
|
|
127
|
+
ticketChatState.statusText = status;
|
|
128
|
+
renderTicketChat();
|
|
129
|
+
renderTicketEvents();
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "token": {
|
|
133
|
+
const token = typeof parsed === "string"
|
|
134
|
+
? parsed
|
|
135
|
+
: parsed.token ||
|
|
136
|
+
parsed.text ||
|
|
137
|
+
rawData ||
|
|
138
|
+
"";
|
|
139
|
+
ticketChatState.streamText = (ticketChatState.streamText || "") + token;
|
|
140
|
+
if (!ticketChatState.statusText || ticketChatState.statusText === "queued") {
|
|
141
|
+
ticketChatState.statusText = "responding";
|
|
142
|
+
}
|
|
143
|
+
renderTicketChat();
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "update": {
|
|
147
|
+
applyTicketChatResult(parsed);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "event":
|
|
151
|
+
case "app-server": {
|
|
152
|
+
// App-server events (thinking, tool calls, etc.)
|
|
153
|
+
applyTicketEvent(parsed);
|
|
154
|
+
renderTicketEvents();
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "error": {
|
|
158
|
+
const message = typeof parsed === "object" && parsed !== null
|
|
159
|
+
? parsed.detail ||
|
|
160
|
+
parsed.error ||
|
|
161
|
+
rawData
|
|
162
|
+
: rawData || "Ticket chat failed";
|
|
163
|
+
ticketChatState.status = "error";
|
|
164
|
+
ticketChatState.error = String(message);
|
|
165
|
+
// Add error as assistant message
|
|
166
|
+
addAssistantMessage(`Error: ${message}`, true);
|
|
167
|
+
renderTicketChat();
|
|
168
|
+
renderTicketMessages();
|
|
169
|
+
throw new Error(String(message));
|
|
170
|
+
}
|
|
171
|
+
case "interrupted": {
|
|
172
|
+
const message = typeof parsed === "object" && parsed !== null
|
|
173
|
+
? parsed.detail || rawData
|
|
174
|
+
: rawData || "Ticket chat interrupted";
|
|
175
|
+
ticketChatState.status = "interrupted";
|
|
176
|
+
ticketChatState.error = "";
|
|
177
|
+
ticketChatState.statusText = String(message);
|
|
178
|
+
// Add interrupted message
|
|
179
|
+
addAssistantMessage("Request interrupted", true);
|
|
180
|
+
renderTicketChat();
|
|
181
|
+
renderTicketMessages();
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "done":
|
|
185
|
+
case "finish": {
|
|
186
|
+
ticketChatState.status = "done";
|
|
187
|
+
// Final render to ensure UI is up to date
|
|
188
|
+
renderTicketChat();
|
|
189
|
+
renderTicketMessages();
|
|
190
|
+
renderTicketEvents();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
// Unknown event - try to parse as app-server event
|
|
195
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
196
|
+
const messageObj = parsed;
|
|
197
|
+
if (messageObj.method || messageObj.message) {
|
|
198
|
+
applyTicketEvent(parsed);
|
|
199
|
+
renderTicketEvents();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
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
|
+
}
|