codex-autorunner 1.1.0__py3-none-any.whl → 1.2.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/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 +114 -1
- codex_autorunner/core/app_server_threads.py +6 -0
- codex_autorunner/core/config.py +236 -1
- codex_autorunner/core/context_awareness.py +38 -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 +496 -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/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 +26 -1
- 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/chatUploads.js +137 -0
- 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 +255 -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 +9125 -6742
- codex_autorunner/static/templateReposSettings.js +225 -0
- 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/file_chat.py +317 -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 +70 -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.0.dist-info}/METADATA +15 -19
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
- 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.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
-
import { api, flash, setButtonLoading } from "./utils.js";
|
|
2
|
+
import { api, confirmModal, flash, setButtonLoading } from "./utils.js";
|
|
3
3
|
import { initAgentControls, getSelectedAgent, getSelectedModel, getSelectedReasoning } from "./agentControls.js";
|
|
4
4
|
import { fetchWorkspace, ingestSpecToTickets, listTickets, fetchWorkspaceTree, uploadWorkspaceFiles, downloadWorkspaceZip, createWorkspaceFolder, writeWorkspace, } from "./workspaceApi.js";
|
|
5
|
-
import { applyDraft, discardDraft, fetchPendingDraft, sendFileChat, interruptFileChat, } from "./fileChat.js";
|
|
5
|
+
import { applyDraft, discardDraft, fetchPendingDraft, sendFileChat, interruptFileChat, newClientTurnId, streamTurnEvents, } from "./fileChat.js";
|
|
6
6
|
import { DocEditor } from "./docEditor.js";
|
|
7
7
|
import { WorkspaceFileBrowser } from "./workspaceFileBrowser.js";
|
|
8
8
|
import { createDocChat } from "./docChatCore.js";
|
|
9
|
+
import { initChatPasteUpload } from "./chatUploads.js";
|
|
9
10
|
import { initDocChatVoice } from "./docChatVoice.js";
|
|
10
11
|
import { renderDiff } from "./diffRenderer.js";
|
|
11
12
|
import { createSmartRefresh } from "./smartRefresh.js";
|
|
12
13
|
import { subscribe } from "./bus.js";
|
|
13
14
|
import { isRepoHealthy } from "./health.js";
|
|
15
|
+
import { loadPendingTurn, savePendingTurn, clearPendingTurn } from "./turnResume.js";
|
|
16
|
+
import { resumeFileChatTurn } from "./turnEvents.js";
|
|
14
17
|
const state = {
|
|
15
18
|
target: null,
|
|
16
19
|
content: "",
|
|
@@ -23,6 +26,7 @@ const state = {
|
|
|
23
26
|
};
|
|
24
27
|
const WORKSPACE_CHAT_EVENT_LIMIT = 8;
|
|
25
28
|
const WORKSPACE_CHAT_EVENT_MAX = 50;
|
|
29
|
+
const WORKSPACE_PENDING_KEY = "car.workspace.pendingTurn";
|
|
26
30
|
const workspaceChat = createDocChat({
|
|
27
31
|
idPrefix: "workspace-chat",
|
|
28
32
|
storage: { keyPrefix: "car-workspace-chat-", maxMessages: 50, version: 1 },
|
|
@@ -48,6 +52,7 @@ const workspaceChat = createDocChat({
|
|
|
48
52
|
const WORKSPACE_DOC_KINDS = new Set(["active_context", "decisions", "spec"]);
|
|
49
53
|
const WORKSPACE_REFRESH_REASONS = ["initial", "background", "manual"];
|
|
50
54
|
let workspaceRefreshCount = 0;
|
|
55
|
+
let currentTurnEventsController = null;
|
|
51
56
|
function hashString(value) {
|
|
52
57
|
let hash = 5381;
|
|
53
58
|
for (let i = 0; i < value.length; i += 1) {
|
|
@@ -82,6 +87,13 @@ function els() {
|
|
|
82
87
|
statusMobile: document.getElementById("workspace-status-mobile"),
|
|
83
88
|
uploadBtn: document.getElementById("workspace-upload"),
|
|
84
89
|
uploadInput: document.getElementById("workspace-upload-input"),
|
|
90
|
+
mobileMenuToggle: document.getElementById("workspace-mobile-menu-toggle"),
|
|
91
|
+
mobileDropdown: document.getElementById("workspace-mobile-dropdown"),
|
|
92
|
+
mobileUpload: document.getElementById("workspace-mobile-upload"),
|
|
93
|
+
mobileNewFolder: document.getElementById("workspace-mobile-new-folder"),
|
|
94
|
+
mobileNewFile: document.getElementById("workspace-mobile-new-file"),
|
|
95
|
+
mobileDownload: document.getElementById("workspace-mobile-download"),
|
|
96
|
+
mobileGenerate: document.getElementById("workspace-mobile-generate"),
|
|
85
97
|
newFolderBtn: document.getElementById("workspace-new-folder"),
|
|
86
98
|
newFileBtn: document.getElementById("workspace-new-file"),
|
|
87
99
|
downloadAllBtn: document.getElementById("workspace-download-all"),
|
|
@@ -224,15 +236,33 @@ function renderPatch() {
|
|
|
224
236
|
function renderChat() {
|
|
225
237
|
workspaceChat.render();
|
|
226
238
|
}
|
|
239
|
+
function closeMobileMenu() {
|
|
240
|
+
const dropdown = els().mobileDropdown;
|
|
241
|
+
if (dropdown)
|
|
242
|
+
dropdown.classList.add("hidden");
|
|
243
|
+
}
|
|
244
|
+
function toggleMobileMenu() {
|
|
245
|
+
const dropdown = els().mobileDropdown;
|
|
246
|
+
if (dropdown)
|
|
247
|
+
dropdown.classList.toggle("hidden");
|
|
248
|
+
}
|
|
227
249
|
function updateDownloadButton() {
|
|
228
|
-
const { downloadAllBtn } = els();
|
|
229
|
-
if (!downloadAllBtn)
|
|
230
|
-
return;
|
|
250
|
+
const { downloadAllBtn, mobileDownload } = els();
|
|
231
251
|
const currentPath = state.browser?.getCurrentPath() || "";
|
|
232
252
|
const isRoot = !currentPath;
|
|
233
253
|
const folderName = currentPath.split("/").pop() || "";
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
const download = () => downloadWorkspaceZip(isRoot ? undefined : currentPath);
|
|
255
|
+
if (downloadAllBtn) {
|
|
256
|
+
downloadAllBtn.title = isRoot ? "Download all as ZIP" : `Download ${folderName}/ as ZIP`;
|
|
257
|
+
downloadAllBtn.onclick = download;
|
|
258
|
+
}
|
|
259
|
+
if (mobileDownload) {
|
|
260
|
+
mobileDownload.textContent = isRoot ? "Download ZIP (all)" : `Download ${folderName || "folder"}`;
|
|
261
|
+
mobileDownload.onclick = () => {
|
|
262
|
+
closeMobileMenu();
|
|
263
|
+
download();
|
|
264
|
+
};
|
|
265
|
+
}
|
|
236
266
|
}
|
|
237
267
|
let createMode = null;
|
|
238
268
|
function listFolderPaths(nodes, base = "") {
|
|
@@ -337,7 +367,7 @@ const workspaceTreeRefresh = createSmartRefresh({
|
|
|
337
367
|
},
|
|
338
368
|
onPathChange: () => updateDownloadButton(),
|
|
339
369
|
onRefresh: () => loadFiles(state.target?.path, "manual"),
|
|
340
|
-
onConfirm: (message) => window.workspaceConfirm?.(message) ??
|
|
370
|
+
onConfirm: (message) => window.workspaceConfirm?.(message) ?? confirmModal(message),
|
|
341
371
|
});
|
|
342
372
|
}
|
|
343
373
|
const defaultPath = payload.defaultPath ?? state.target?.path ?? undefined;
|
|
@@ -431,9 +461,12 @@ async function maybeShowGenerate() {
|
|
|
431
461
|
catch {
|
|
432
462
|
state.hasTickets = true;
|
|
433
463
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
464
|
+
const { generateBtn, mobileGenerate } = els();
|
|
465
|
+
const hidden = state.hasTickets;
|
|
466
|
+
if (generateBtn)
|
|
467
|
+
generateBtn.classList.toggle("hidden", hidden);
|
|
468
|
+
if (mobileGenerate)
|
|
469
|
+
mobileGenerate.classList.toggle("hidden", hidden);
|
|
437
470
|
}
|
|
438
471
|
async function generateTickets() {
|
|
439
472
|
try {
|
|
@@ -451,7 +484,7 @@ async function applyWorkspaceDraft() {
|
|
|
451
484
|
try {
|
|
452
485
|
const isStale = Boolean(state.draft?.is_stale);
|
|
453
486
|
if (isStale) {
|
|
454
|
-
const confirmForce =
|
|
487
|
+
const confirmForce = await confirmModal("This draft is stale because the file changed after it was created. Force apply anyway?");
|
|
455
488
|
if (!confirmForce)
|
|
456
489
|
return;
|
|
457
490
|
}
|
|
@@ -484,6 +517,138 @@ async function discardWorkspaceDraft() {
|
|
|
484
517
|
flash(err.message || "Failed to discard draft", "error");
|
|
485
518
|
}
|
|
486
519
|
}
|
|
520
|
+
function clearTurnEventsStream() {
|
|
521
|
+
if (currentTurnEventsController) {
|
|
522
|
+
try {
|
|
523
|
+
currentTurnEventsController.abort();
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
// ignore
|
|
527
|
+
}
|
|
528
|
+
currentTurnEventsController = null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function clearPendingTurnState() {
|
|
532
|
+
clearTurnEventsStream();
|
|
533
|
+
clearPendingTurn(WORKSPACE_PENDING_KEY);
|
|
534
|
+
}
|
|
535
|
+
function maybeStartTurnEventsFromUpdate(update) {
|
|
536
|
+
const meta = update;
|
|
537
|
+
const threadId = typeof meta.thread_id === "string" ? meta.thread_id : "";
|
|
538
|
+
const turnId = typeof meta.turn_id === "string" ? meta.turn_id : "";
|
|
539
|
+
const agent = typeof meta.agent === "string" ? meta.agent : undefined;
|
|
540
|
+
if (!threadId || !turnId)
|
|
541
|
+
return;
|
|
542
|
+
clearTurnEventsStream();
|
|
543
|
+
currentTurnEventsController = streamTurnEvents({ agent, threadId, turnId }, {
|
|
544
|
+
onEvent: (event) => {
|
|
545
|
+
workspaceChat.applyAppEvent(event);
|
|
546
|
+
workspaceChat.renderEvents();
|
|
547
|
+
workspaceChat.render();
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function applyChatUpdate(update) {
|
|
552
|
+
const hasDraft = update.has_draft ?? update.hasDraft;
|
|
553
|
+
if (hasDraft === false) {
|
|
554
|
+
state.draft = null;
|
|
555
|
+
if (typeof update.content === "string") {
|
|
556
|
+
state.content = update.content;
|
|
557
|
+
const textarea = els().textarea;
|
|
558
|
+
if (textarea)
|
|
559
|
+
textarea.value = state.content;
|
|
560
|
+
}
|
|
561
|
+
renderPatch();
|
|
562
|
+
}
|
|
563
|
+
else if (hasDraft === true || update.patch || update.content) {
|
|
564
|
+
state.draft = {
|
|
565
|
+
target: target(),
|
|
566
|
+
content: update.content || "",
|
|
567
|
+
patch: update.patch || "",
|
|
568
|
+
agent_message: update.agent_message,
|
|
569
|
+
created_at: update.created_at,
|
|
570
|
+
base_hash: update.base_hash,
|
|
571
|
+
current_hash: update.current_hash,
|
|
572
|
+
is_stale: Boolean(update.is_stale),
|
|
573
|
+
};
|
|
574
|
+
renderPatch();
|
|
575
|
+
}
|
|
576
|
+
if (update.message || update.agent_message) {
|
|
577
|
+
const text = update.message || update.agent_message || "";
|
|
578
|
+
if (text)
|
|
579
|
+
workspaceChat.addAssistantMessage(text);
|
|
580
|
+
}
|
|
581
|
+
workspaceChat.render();
|
|
582
|
+
}
|
|
583
|
+
function applyFinalResult(result) {
|
|
584
|
+
const chatState = workspaceChat.state;
|
|
585
|
+
const status = String(result.status || "");
|
|
586
|
+
if (status === "ok") {
|
|
587
|
+
applyChatUpdate(result);
|
|
588
|
+
chatState.status = "done";
|
|
589
|
+
chatState.error = "";
|
|
590
|
+
chatState.streamText = "";
|
|
591
|
+
clearPendingTurnState();
|
|
592
|
+
renderChat();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (status === "error") {
|
|
596
|
+
const detail = String(result.detail || "Chat failed");
|
|
597
|
+
chatState.status = "error";
|
|
598
|
+
chatState.error = detail;
|
|
599
|
+
renderChat();
|
|
600
|
+
flash(detail, "error");
|
|
601
|
+
clearPendingTurnState();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (status === "interrupted") {
|
|
605
|
+
chatState.status = "interrupted";
|
|
606
|
+
chatState.error = "";
|
|
607
|
+
chatState.streamText = "";
|
|
608
|
+
renderChat();
|
|
609
|
+
clearPendingTurnState();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function resumePendingWorkspaceTurn() {
|
|
613
|
+
const pending = loadPendingTurn(WORKSPACE_PENDING_KEY);
|
|
614
|
+
if (!pending)
|
|
615
|
+
return;
|
|
616
|
+
const chatState = workspaceChat.state;
|
|
617
|
+
chatState.status = "running";
|
|
618
|
+
chatState.statusText = "Recovering previous turn…";
|
|
619
|
+
workspaceChat.render();
|
|
620
|
+
workspaceChat.renderMessages();
|
|
621
|
+
try {
|
|
622
|
+
const outcome = await resumeFileChatTurn(pending.clientTurnId, {
|
|
623
|
+
onEvent: (event) => {
|
|
624
|
+
workspaceChat.applyAppEvent(event);
|
|
625
|
+
workspaceChat.renderEvents();
|
|
626
|
+
workspaceChat.render();
|
|
627
|
+
},
|
|
628
|
+
onResult: (result) => applyFinalResult(result),
|
|
629
|
+
onError: (msg) => {
|
|
630
|
+
chatState.statusText = msg;
|
|
631
|
+
renderChat();
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
currentTurnEventsController = outcome.controller;
|
|
635
|
+
if (outcome.lastResult && outcome.lastResult.status) {
|
|
636
|
+
applyFinalResult(outcome.lastResult);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
// If still running but no event stream yet, poll again shortly.
|
|
640
|
+
if (!outcome.controller) {
|
|
641
|
+
window.setTimeout(() => {
|
|
642
|
+
void resumePendingWorkspaceTurn();
|
|
643
|
+
}, 1000);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
const msg = err.message || "Failed to resume turn";
|
|
648
|
+
chatState.statusText = msg;
|
|
649
|
+
renderChat();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
487
652
|
async function sendChat() {
|
|
488
653
|
const { chatInput, chatSend, chatCancel } = els();
|
|
489
654
|
const message = (chatInput?.value || "").trim();
|
|
@@ -498,6 +663,7 @@ async function sendChat() {
|
|
|
498
663
|
chatState.error = "";
|
|
499
664
|
chatState.statusText = "queued";
|
|
500
665
|
chatState.streamText = "";
|
|
666
|
+
chatState.contextUsagePercent = null;
|
|
501
667
|
workspaceChat.clearEvents();
|
|
502
668
|
workspaceChat.addUserMessage(message);
|
|
503
669
|
renderChat();
|
|
@@ -505,6 +671,14 @@ async function sendChat() {
|
|
|
505
671
|
chatInput.value = "";
|
|
506
672
|
chatSend?.setAttribute("disabled", "true");
|
|
507
673
|
chatCancel?.classList.remove("hidden");
|
|
674
|
+
clearTurnEventsStream();
|
|
675
|
+
const clientTurnId = newClientTurnId("workspace");
|
|
676
|
+
savePendingTurn(WORKSPACE_PENDING_KEY, {
|
|
677
|
+
clientTurnId,
|
|
678
|
+
message,
|
|
679
|
+
startedAtMs: Date.now(),
|
|
680
|
+
target: target(),
|
|
681
|
+
});
|
|
508
682
|
const agent = getSelectedAgent();
|
|
509
683
|
const model = getSelectedModel(agent) || undefined;
|
|
510
684
|
const reasoning = getSelectedReasoning(agent) || undefined;
|
|
@@ -523,43 +697,20 @@ async function sendChat() {
|
|
|
523
697
|
workspaceChat.applyAppEvent(event);
|
|
524
698
|
workspaceChat.renderEvents();
|
|
525
699
|
},
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (hasDraft === false) {
|
|
529
|
-
chatState.draft = null;
|
|
530
|
-
if (typeof update.content === "string") {
|
|
531
|
-
state.content = update.content;
|
|
532
|
-
const textarea = els().textarea;
|
|
533
|
-
if (textarea)
|
|
534
|
-
textarea.value = state.content;
|
|
535
|
-
}
|
|
536
|
-
renderPatch();
|
|
537
|
-
}
|
|
538
|
-
else if (hasDraft === true || update.patch || update.content) {
|
|
539
|
-
state.draft = {
|
|
540
|
-
target: target(),
|
|
541
|
-
content: update.content || "",
|
|
542
|
-
patch: update.patch || "",
|
|
543
|
-
agent_message: update.agent_message,
|
|
544
|
-
created_at: update.created_at,
|
|
545
|
-
base_hash: update.base_hash,
|
|
546
|
-
current_hash: update.current_hash,
|
|
547
|
-
is_stale: Boolean(update.is_stale),
|
|
548
|
-
};
|
|
549
|
-
renderPatch();
|
|
550
|
-
}
|
|
551
|
-
if (update.message || update.agent_message) {
|
|
552
|
-
const text = update.message || update.agent_message || "";
|
|
553
|
-
if (text)
|
|
554
|
-
workspaceChat.addAssistantMessage(text);
|
|
555
|
-
}
|
|
700
|
+
onTokenUsage: (percent) => {
|
|
701
|
+
chatState.contextUsagePercent = percent;
|
|
556
702
|
renderChat();
|
|
557
703
|
},
|
|
704
|
+
onUpdate: (update) => {
|
|
705
|
+
applyChatUpdate(update);
|
|
706
|
+
maybeStartTurnEventsFromUpdate(update);
|
|
707
|
+
},
|
|
558
708
|
onError: (msg) => {
|
|
559
709
|
chatState.status = "error";
|
|
560
710
|
chatState.error = msg;
|
|
561
711
|
renderChat();
|
|
562
712
|
flash(msg, "error");
|
|
713
|
+
clearPendingTurnState();
|
|
563
714
|
},
|
|
564
715
|
onInterrupted: (msg) => {
|
|
565
716
|
chatState.status = "interrupted";
|
|
@@ -567,6 +718,7 @@ async function sendChat() {
|
|
|
567
718
|
chatState.streamText = "";
|
|
568
719
|
renderChat();
|
|
569
720
|
flash(msg, "info");
|
|
721
|
+
clearPendingTurnState();
|
|
570
722
|
},
|
|
571
723
|
onDone: () => {
|
|
572
724
|
if (chatState.streamText) {
|
|
@@ -575,8 +727,9 @@ async function sendChat() {
|
|
|
575
727
|
}
|
|
576
728
|
chatState.status = "done";
|
|
577
729
|
renderChat();
|
|
730
|
+
clearPendingTurnState();
|
|
578
731
|
},
|
|
579
|
-
}, { agent, model, reasoning });
|
|
732
|
+
}, { agent, model, reasoning, clientTurnId });
|
|
580
733
|
}
|
|
581
734
|
catch (err) {
|
|
582
735
|
const msg = err.message || "Chat failed";
|
|
@@ -585,6 +738,7 @@ async function sendChat() {
|
|
|
585
738
|
chatStateLocal.error = msg;
|
|
586
739
|
renderChat();
|
|
587
740
|
flash(msg, "error");
|
|
741
|
+
clearPendingTurnState();
|
|
588
742
|
}
|
|
589
743
|
finally {
|
|
590
744
|
chatSend?.removeAttribute("disabled");
|
|
@@ -606,7 +760,9 @@ async function cancelChat() {
|
|
|
606
760
|
}
|
|
607
761
|
chatState.status = "interrupted";
|
|
608
762
|
chatState.streamText = "";
|
|
763
|
+
chatState.contextUsagePercent = null;
|
|
609
764
|
renderChat();
|
|
765
|
+
clearPendingTurnState();
|
|
610
766
|
}
|
|
611
767
|
async function resetThread() {
|
|
612
768
|
if (!state.target)
|
|
@@ -619,7 +775,9 @@ async function resetThread() {
|
|
|
619
775
|
const chatState = workspaceChat.state;
|
|
620
776
|
chatState.messages = [];
|
|
621
777
|
chatState.streamText = "";
|
|
778
|
+
chatState.contextUsagePercent = null;
|
|
622
779
|
workspaceChat.clearEvents();
|
|
780
|
+
clearPendingTurnState();
|
|
623
781
|
renderChat();
|
|
624
782
|
flash("New workspace chat thread", "success");
|
|
625
783
|
}
|
|
@@ -645,7 +803,7 @@ async function loadFiles(defaultPath, reason = "manual") {
|
|
|
645
803
|
}
|
|
646
804
|
}
|
|
647
805
|
export async function initWorkspace() {
|
|
648
|
-
const { generateBtn, uploadBtn, uploadInput, newFolderBtn, saveBtn, saveBtnMobile, reloadBtn, reloadBtnMobile, patchApply, patchDiscard, patchReload, chatSend, chatCancel, chatNewThread, } = els();
|
|
806
|
+
const { generateBtn, uploadBtn, uploadInput, mobileMenuToggle, mobileDropdown, mobileUpload, mobileNewFolder, mobileNewFile, mobileDownload, mobileGenerate, newFolderBtn, saveBtn, saveBtnMobile, reloadBtn, reloadBtnMobile, patchApply, patchDiscard, patchReload, chatSend, chatCancel, chatNewThread, } = els();
|
|
649
807
|
if (!document.getElementById("workspace"))
|
|
650
808
|
return;
|
|
651
809
|
initAgentControls({
|
|
@@ -660,6 +818,7 @@ export async function initWorkspace() {
|
|
|
660
818
|
await maybeShowGenerate();
|
|
661
819
|
await loadFiles(undefined, "initial");
|
|
662
820
|
workspaceChat.setTarget(target());
|
|
821
|
+
void resumePendingWorkspaceTurn();
|
|
663
822
|
const reloadEverything = async () => {
|
|
664
823
|
await loadFiles(state.target?.path, "manual");
|
|
665
824
|
await reloadWorkspace();
|
|
@@ -686,6 +845,54 @@ export async function initWorkspace() {
|
|
|
686
845
|
uploadInput.value = "";
|
|
687
846
|
}
|
|
688
847
|
});
|
|
848
|
+
// Mobile action sheet
|
|
849
|
+
const handleMobileToggle = (evt) => {
|
|
850
|
+
evt.preventDefault();
|
|
851
|
+
evt.stopPropagation();
|
|
852
|
+
toggleMobileMenu();
|
|
853
|
+
};
|
|
854
|
+
mobileMenuToggle?.addEventListener("pointerdown", handleMobileToggle);
|
|
855
|
+
mobileMenuToggle?.addEventListener("click", (evt) => {
|
|
856
|
+
evt.preventDefault(); // swallow synthetic click after pointerdown
|
|
857
|
+
});
|
|
858
|
+
mobileMenuToggle?.addEventListener("keydown", (evt) => {
|
|
859
|
+
if (evt.key === "Enter" || evt.key === " ") {
|
|
860
|
+
handleMobileToggle(evt);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
document.addEventListener("pointerdown", (evt) => {
|
|
864
|
+
if (!mobileDropdown || mobileDropdown.classList.contains("hidden"))
|
|
865
|
+
return;
|
|
866
|
+
if (evt.target instanceof Node && mobileDropdown.contains(evt.target))
|
|
867
|
+
return;
|
|
868
|
+
closeMobileMenu();
|
|
869
|
+
});
|
|
870
|
+
document.addEventListener("keydown", (evt) => {
|
|
871
|
+
if (evt.key === "Escape" && mobileDropdown && !mobileDropdown.classList.contains("hidden")) {
|
|
872
|
+
closeMobileMenu();
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
mobileUpload?.addEventListener("click", () => {
|
|
876
|
+
closeMobileMenu();
|
|
877
|
+
uploadInput?.click();
|
|
878
|
+
});
|
|
879
|
+
mobileNewFolder?.addEventListener("click", () => {
|
|
880
|
+
closeMobileMenu();
|
|
881
|
+
openCreateModal("folder");
|
|
882
|
+
});
|
|
883
|
+
mobileNewFile?.addEventListener("click", () => {
|
|
884
|
+
closeMobileMenu();
|
|
885
|
+
openCreateModal("file");
|
|
886
|
+
});
|
|
887
|
+
mobileDownload?.addEventListener("click", () => {
|
|
888
|
+
closeMobileMenu();
|
|
889
|
+
const currentPath = state.browser?.getCurrentPath() || "";
|
|
890
|
+
downloadWorkspaceZip(currentPath || undefined);
|
|
891
|
+
});
|
|
892
|
+
mobileGenerate?.addEventListener("click", () => {
|
|
893
|
+
closeMobileMenu();
|
|
894
|
+
void generateTickets();
|
|
895
|
+
});
|
|
689
896
|
newFolderBtn?.addEventListener("click", () => openCreateModal("folder"));
|
|
690
897
|
els().newFileBtn?.addEventListener("click", () => openCreateModal("file"));
|
|
691
898
|
generateBtn?.addEventListener("click", () => void generateTickets());
|
|
@@ -703,6 +910,13 @@ export async function initWorkspace() {
|
|
|
703
910
|
void sendChat();
|
|
704
911
|
}
|
|
705
912
|
});
|
|
913
|
+
initChatPasteUpload({
|
|
914
|
+
textarea: chatInput,
|
|
915
|
+
basePath: "/api/filebox",
|
|
916
|
+
box: "inbox",
|
|
917
|
+
insertStyle: "both",
|
|
918
|
+
pathPrefix: ".codex-autorunner/filebox",
|
|
919
|
+
});
|
|
706
920
|
}
|
|
707
921
|
const { createModal, createClose, createCancel, createSubmit } = els();
|
|
708
922
|
createClose?.addEventListener("click", () => closeCreateModal());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
2
|
import { deleteWorkspaceFile, deleteWorkspaceFolder, downloadWorkspaceFile, downloadWorkspaceZip } from "./workspaceApi.js";
|
|
3
|
-
import { flash } from "./utils.js";
|
|
3
|
+
import { confirmModal, flash } from "./utils.js";
|
|
4
4
|
export class WorkspaceFileBrowser {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
this.tree = [];
|
|
@@ -274,7 +274,9 @@ export class WorkspaceFileBrowser {
|
|
|
274
274
|
delBtn.title = "Delete file";
|
|
275
275
|
delBtn.addEventListener("click", async (evt) => {
|
|
276
276
|
evt.stopPropagation();
|
|
277
|
-
const ok = this.onConfirm
|
|
277
|
+
const ok = this.onConfirm
|
|
278
|
+
? await this.onConfirm(`Delete ${node.name}?`)
|
|
279
|
+
: await confirmModal(`Delete ${node.name}?`);
|
|
278
280
|
if (!ok)
|
|
279
281
|
return;
|
|
280
282
|
try {
|
|
@@ -301,7 +303,7 @@ export class WorkspaceFileBrowser {
|
|
|
301
303
|
evt.stopPropagation();
|
|
302
304
|
const ok = this.onConfirm
|
|
303
305
|
? await this.onConfirm(`Delete folder ${node.name}? (must be empty)`)
|
|
304
|
-
:
|
|
306
|
+
: await confirmModal(`Delete folder ${node.name}? (must be empty)`);
|
|
305
307
|
if (!ok)
|
|
306
308
|
return;
|
|
307
309
|
try {
|
|
@@ -418,7 +420,7 @@ export class WorkspaceFileBrowser {
|
|
|
418
420
|
e.stopPropagation();
|
|
419
421
|
const ok = this.onConfirm
|
|
420
422
|
? await this.onConfirm(`Delete ${node.name}${node.type === "folder" ? " (must be empty)" : ""}?`)
|
|
421
|
-
:
|
|
423
|
+
: await confirmModal(`Delete ${node.name}${node.type === "folder" ? " (must be empty)" : ""}?`);
|
|
422
424
|
if (!ok)
|
|
423
425
|
return;
|
|
424
426
|
try {
|