codex-autorunner 1.2.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/core/about_car.py +12 -12
- codex_autorunner/core/config.py +2 -2
- codex_autorunner/core/context_awareness.py +1 -0
- codex_autorunner/core/pma_context.py +188 -1
- codex_autorunner/integrations/telegram/adapter.py +1 -1
- codex_autorunner/integrations/telegram/config.py +1 -1
- codex_autorunner/integrations/telegram/handlers/messages.py +8 -2
- codex_autorunner/static/archive.js +274 -81
- codex_autorunner/static/archiveApi.js +21 -0
- codex_autorunner/static/constants.js +1 -1
- codex_autorunner/static/notifications.js +33 -0
- codex_autorunner/static/styles.css +16 -0
- codex_autorunner/static/terminalManager.js +22 -3
- codex_autorunner/surfaces/web/routes/archive.py +197 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +6 -26
- codex_autorunner/surfaces/web/schemas.py +11 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/METADATA +1 -1
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/RECORD +22 -22
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
2
|
import { subscribe } from "./bus.js";
|
|
3
|
-
import { downloadArchiveFile, fetchArchiveSnapshot, listArchiveSnapshots, listArchiveTree, readArchiveFile, } from "./archiveApi.js";
|
|
3
|
+
import { downloadArchiveFile, downloadLocalArchiveFile, fetchArchiveSnapshot, listArchiveSnapshots, listArchiveTree, listLocalArchiveTree, listLocalRunArchives, readArchiveFile, readLocalArchiveFile, } from "./archiveApi.js";
|
|
4
4
|
import { escapeHtml, flash, statusPill, setButtonLoading } from "./utils.js";
|
|
5
5
|
let initialized = false;
|
|
6
6
|
let snapshots = [];
|
|
7
|
-
let
|
|
8
|
-
let
|
|
7
|
+
let localArchives = [];
|
|
8
|
+
let selectedItem = null;
|
|
9
|
+
let activeSourceKey = "";
|
|
9
10
|
let activeSubTab = "snapshot";
|
|
10
|
-
let
|
|
11
|
-
/** Compute a signature of the
|
|
12
|
-
function
|
|
13
|
-
|
|
11
|
+
let lastArchiveSignature = "";
|
|
12
|
+
/** Compute a signature of the archive lists for change detection. */
|
|
13
|
+
function archiveSignature(snapshotItems, localItems) {
|
|
14
|
+
const snapSig = snapshotItems
|
|
15
|
+
.map((s) => `${s.snapshot_id}:${s.worktree_repo_id}:${s.status || ""}`)
|
|
16
|
+
.join("|");
|
|
17
|
+
const localSig = localItems
|
|
18
|
+
.map((s) => `${s.run_id}:${s.archived_at || ""}:${s.has_tickets ? "t" : "f"}:${s.has_runs ? "t" : "f"}`)
|
|
19
|
+
.join("|");
|
|
20
|
+
return `${snapSig}::${localSig}`;
|
|
14
21
|
}
|
|
15
22
|
const listEl = document.getElementById("archive-snapshot-list");
|
|
16
23
|
const detailEl = document.getElementById("archive-snapshot-detail");
|
|
@@ -23,7 +30,7 @@ let fileEls = null;
|
|
|
23
30
|
let treeRequestToken = 0;
|
|
24
31
|
let fileRequestToken = 0;
|
|
25
32
|
let artifactRequestToken = 0;
|
|
26
|
-
const
|
|
33
|
+
const SNAPSHOT_QUICK_LINKS = [
|
|
27
34
|
{ label: "Active Context", path: "workspace/active_context.md", kind: "file" },
|
|
28
35
|
{ label: "Decisions", path: "workspace/decisions.md", kind: "file" },
|
|
29
36
|
{ label: "Spec", path: "workspace/spec.md", kind: "file" },
|
|
@@ -32,6 +39,10 @@ const QUICK_LINKS = [
|
|
|
32
39
|
{ label: "Flows", path: "flows", kind: "folder" },
|
|
33
40
|
{ label: "Logs", path: "logs", kind: "folder" },
|
|
34
41
|
];
|
|
42
|
+
const LOCAL_QUICK_LINKS = [
|
|
43
|
+
{ label: "Archived tickets", path: "archived_tickets", kind: "folder" },
|
|
44
|
+
{ label: "Archived runs", path: "archived_runs", kind: "folder" },
|
|
45
|
+
];
|
|
35
46
|
function formatTimestamp(ts) {
|
|
36
47
|
if (!ts)
|
|
37
48
|
return "–";
|
|
@@ -54,6 +65,12 @@ function formatBytes(bytes) {
|
|
|
54
65
|
function snapshotKey(snapshot) {
|
|
55
66
|
return `${snapshot.snapshot_id}::${snapshot.worktree_repo_id}`;
|
|
56
67
|
}
|
|
68
|
+
function listItemKey(item) {
|
|
69
|
+
if (item.kind === "snapshot") {
|
|
70
|
+
return `snapshot:${snapshotKey(item.summary)}`;
|
|
71
|
+
}
|
|
72
|
+
return `local:${item.summary.run_id}`;
|
|
73
|
+
}
|
|
57
74
|
function parentPath(path) {
|
|
58
75
|
const parts = path.split("/").filter(Boolean);
|
|
59
76
|
if (parts.length <= 1)
|
|
@@ -67,32 +84,33 @@ function renderEmptyDetail(message) {
|
|
|
67
84
|
detailEl.innerHTML = `
|
|
68
85
|
<div class="archive-empty-state">
|
|
69
86
|
<div class="archive-empty-title">${escapeHtml(message)}</div>
|
|
70
|
-
<div class="archive-empty-hint">Select a snapshot on the left to view
|
|
87
|
+
<div class="archive-empty-hint">Select a snapshot or local run archive on the left to view details.</div>
|
|
71
88
|
</div>
|
|
72
89
|
`;
|
|
73
90
|
}
|
|
74
|
-
function renderList(
|
|
91
|
+
function renderList(snapshotItems, localItems) {
|
|
75
92
|
if (!listEl)
|
|
76
93
|
return;
|
|
77
|
-
|
|
94
|
+
const hasItems = snapshotItems.length > 0 || localItems.length > 0;
|
|
95
|
+
if (!hasItems) {
|
|
78
96
|
listEl.innerHTML = "";
|
|
79
97
|
if (emptyEl)
|
|
80
98
|
emptyEl.classList.remove("hidden");
|
|
81
|
-
renderEmptyDetail("No
|
|
99
|
+
renderEmptyDetail("No archives yet.");
|
|
82
100
|
return;
|
|
83
101
|
}
|
|
84
102
|
if (emptyEl)
|
|
85
103
|
emptyEl.classList.add("hidden");
|
|
86
|
-
const selectedKey =
|
|
87
|
-
|
|
104
|
+
const selectedKey = selectedItem ? listItemKey(selectedItem) : "";
|
|
105
|
+
const snapshotHtml = snapshotItems
|
|
88
106
|
.map((item) => {
|
|
89
|
-
const isActive = selectedKey && selectedKey === snapshotKey(item)
|
|
107
|
+
const isActive = selectedKey && selectedKey === `snapshot:${snapshotKey(item)}`;
|
|
90
108
|
const created = formatTimestamp(item.created_at);
|
|
91
109
|
const branch = item.branch ? `· ${item.branch}` : "";
|
|
92
110
|
const status = item.status ? item.status : "unknown";
|
|
93
111
|
const note = item.note ? ` · ${item.note}` : "";
|
|
94
112
|
return `
|
|
95
|
-
<button class="archive-snapshot${isActive ? " active" : ""}" data-snapshot-id="${escapeHtml(item.snapshot_id)}" data-worktree-id="${escapeHtml(item.worktree_repo_id)}">
|
|
113
|
+
<button class="archive-snapshot${isActive ? " active" : ""}" data-kind="snapshot" data-snapshot-id="${escapeHtml(item.snapshot_id)}" data-worktree-id="${escapeHtml(item.worktree_repo_id)}">
|
|
96
114
|
<div class="archive-snapshot-title">${escapeHtml(item.snapshot_id)}</div>
|
|
97
115
|
<div class="archive-snapshot-meta muted small">${escapeHtml(created)} ${escapeHtml(branch)}</div>
|
|
98
116
|
<div class="archive-snapshot-meta muted small">Status: ${escapeHtml(status)}${escapeHtml(note)}</div>
|
|
@@ -100,6 +118,34 @@ function renderList(items) {
|
|
|
100
118
|
`;
|
|
101
119
|
})
|
|
102
120
|
.join("");
|
|
121
|
+
const localHtml = localItems
|
|
122
|
+
.map((item) => {
|
|
123
|
+
const isActive = selectedKey && selectedKey === `local:${item.run_id}`;
|
|
124
|
+
const created = formatTimestamp(item.archived_at);
|
|
125
|
+
const tickets = item.has_tickets ? "tickets" : "no tickets";
|
|
126
|
+
const runs = item.has_runs ? "runs" : "no runs";
|
|
127
|
+
return `
|
|
128
|
+
<button class="archive-snapshot${isActive ? " active" : ""}" data-kind="local" data-run-id="${escapeHtml(item.run_id)}">
|
|
129
|
+
<div class="archive-snapshot-title">${escapeHtml(item.run_id)}</div>
|
|
130
|
+
<div class="archive-snapshot-meta muted small">${escapeHtml(created)} · Local run archive</div>
|
|
131
|
+
<div class="archive-snapshot-meta muted small">${escapeHtml(tickets)} · ${escapeHtml(runs)}</div>
|
|
132
|
+
</button>
|
|
133
|
+
`;
|
|
134
|
+
})
|
|
135
|
+
.join("");
|
|
136
|
+
const snapshotSection = `
|
|
137
|
+
<div class="archive-list-section">
|
|
138
|
+
<div class="archive-list-header muted small">Worktree snapshots</div>
|
|
139
|
+
${snapshotHtml || `<div class="archive-list-empty muted small">No snapshots.</div>`}
|
|
140
|
+
</div>
|
|
141
|
+
`;
|
|
142
|
+
const localSection = `
|
|
143
|
+
<div class="archive-list-section">
|
|
144
|
+
<div class="archive-list-header muted small">Local run archives</div>
|
|
145
|
+
${localHtml || `<div class="archive-list-empty muted small">No run archives.</div>`}
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
listEl.innerHTML = `${snapshotSection}${localSection}`;
|
|
103
149
|
}
|
|
104
150
|
function renderSummaryGrid(summary, meta) {
|
|
105
151
|
const created = formatTimestamp(summary.created_at);
|
|
@@ -233,10 +279,10 @@ function renderArtifactSection(summary, meta) {
|
|
|
233
279
|
</div>
|
|
234
280
|
`;
|
|
235
281
|
}
|
|
236
|
-
function renderSubTabs() {
|
|
282
|
+
function renderSubTabs(summaryLabel) {
|
|
237
283
|
return `
|
|
238
284
|
<div class="archive-subtabs">
|
|
239
|
-
<button class="archive-subtab${activeSubTab === "snapshot" ? " active" : ""}" data-subtab="snapshot"
|
|
285
|
+
<button class="archive-subtab${activeSubTab === "snapshot" ? " active" : ""}" data-subtab="snapshot">${escapeHtml(summaryLabel)}</button>
|
|
240
286
|
<button class="archive-subtab${activeSubTab === "files" ? " active" : ""}" data-subtab="files">Files</button>
|
|
241
287
|
</div>
|
|
242
288
|
`;
|
|
@@ -272,14 +318,17 @@ function wireSubTabs() {
|
|
|
272
318
|
}
|
|
273
319
|
});
|
|
274
320
|
}
|
|
275
|
-
function renderFileSection() {
|
|
276
|
-
const quickLinks =
|
|
321
|
+
function renderFileSection(quickLinksData, description) {
|
|
322
|
+
const quickLinks = quickLinksData
|
|
323
|
+
.map((item) => `<button class="ghost sm" data-archive-path="${escapeHtml(item.path)}" data-archive-kind="${item.kind}">${escapeHtml(item.label)}</button>`)
|
|
324
|
+
.join("");
|
|
325
|
+
const descriptionText = description || "Browse archive files (read-only).";
|
|
277
326
|
return `
|
|
278
327
|
<div class="archive-file-section">
|
|
279
328
|
<div class="archive-file-header-row">
|
|
280
329
|
<div>
|
|
281
330
|
<div class="archive-section-title">Archive files</div>
|
|
282
|
-
<div class="muted small"
|
|
331
|
+
<div class="muted small">${escapeHtml(descriptionText)}</div>
|
|
283
332
|
</div>
|
|
284
333
|
<div class="archive-quick-links" id="archive-quick-links">
|
|
285
334
|
${quickLinks}
|
|
@@ -311,6 +360,47 @@ function renderFileSection() {
|
|
|
311
360
|
</div>
|
|
312
361
|
`;
|
|
313
362
|
}
|
|
363
|
+
function renderLocalSummary(run) {
|
|
364
|
+
const archivedAt = formatTimestamp(run.archived_at);
|
|
365
|
+
const tickets = run.has_tickets ? "Yes" : "No";
|
|
366
|
+
const runs = run.has_runs ? "Yes" : "No";
|
|
367
|
+
const actionButtons = [
|
|
368
|
+
run.has_tickets
|
|
369
|
+
? `<button class="ghost sm" data-archive-path="archived_tickets" data-archive-kind="folder">Archived tickets</button>`
|
|
370
|
+
: "",
|
|
371
|
+
run.has_runs
|
|
372
|
+
? `<button class="ghost sm" data-archive-path="archived_runs" data-archive-kind="folder">Archived runs</button>`
|
|
373
|
+
: "",
|
|
374
|
+
]
|
|
375
|
+
.filter(Boolean)
|
|
376
|
+
.join("");
|
|
377
|
+
return `
|
|
378
|
+
<div class="archive-meta-grid">
|
|
379
|
+
<div class="archive-meta-row">
|
|
380
|
+
<div class="archive-meta-label muted small">Run ID</div>
|
|
381
|
+
<div class="archive-meta-value">${escapeHtml(run.run_id)}</div>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="archive-meta-row">
|
|
384
|
+
<div class="archive-meta-label muted small">Archived at</div>
|
|
385
|
+
<div class="archive-meta-value">${escapeHtml(archivedAt)}</div>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="archive-meta-row">
|
|
388
|
+
<div class="archive-meta-label muted small">Archived tickets</div>
|
|
389
|
+
<div class="archive-meta-value">${escapeHtml(tickets)}</div>
|
|
390
|
+
</div>
|
|
391
|
+
<div class="archive-meta-row">
|
|
392
|
+
<div class="archive-meta-label muted small">Archived runs</div>
|
|
393
|
+
<div class="archive-meta-value">${escapeHtml(runs)}</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
<div class="archive-summary-block">
|
|
397
|
+
<div class="archive-section-title">Artifacts</div>
|
|
398
|
+
<div class="archive-quick-links archive-artifact-actions" id="archive-local-artifact-actions">
|
|
399
|
+
${actionButtons || `<span class="muted small">No archived folders found.</span>`}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
314
404
|
function collectFileEls() {
|
|
315
405
|
const list = document.getElementById("archive-file-list");
|
|
316
406
|
const breadcrumbs = document.getElementById("archive-breadcrumbs");
|
|
@@ -358,7 +448,7 @@ function renderBreadcrumbs(path) {
|
|
|
358
448
|
nav.className = "workspace-breadcrumbs-inner";
|
|
359
449
|
const rootBtn = document.createElement("button");
|
|
360
450
|
rootBtn.type = "button";
|
|
361
|
-
rootBtn.textContent = "
|
|
451
|
+
rootBtn.textContent = fileState?.rootLabel || "Archive";
|
|
362
452
|
rootBtn.addEventListener("click", () => {
|
|
363
453
|
void navigateTo("");
|
|
364
454
|
});
|
|
@@ -500,14 +590,43 @@ function renderFileList() {
|
|
|
500
590
|
list.appendChild(row);
|
|
501
591
|
});
|
|
502
592
|
}
|
|
593
|
+
async function listTreeForState(state, path) {
|
|
594
|
+
if (state.kind === "snapshot" && state.snapshotId && state.worktreeRepoId) {
|
|
595
|
+
return listArchiveTree(state.snapshotId, state.worktreeRepoId, path);
|
|
596
|
+
}
|
|
597
|
+
if (state.kind === "local" && state.runId) {
|
|
598
|
+
return listLocalArchiveTree(state.runId, path);
|
|
599
|
+
}
|
|
600
|
+
throw new Error("Invalid archive source");
|
|
601
|
+
}
|
|
602
|
+
async function readFileForState(state, path) {
|
|
603
|
+
if (state.kind === "snapshot" && state.snapshotId) {
|
|
604
|
+
return readArchiveFile(state.snapshotId, state.worktreeRepoId ?? null, path);
|
|
605
|
+
}
|
|
606
|
+
if (state.kind === "local" && state.runId) {
|
|
607
|
+
return readLocalArchiveFile(state.runId, path);
|
|
608
|
+
}
|
|
609
|
+
throw new Error("Invalid archive source");
|
|
610
|
+
}
|
|
611
|
+
function downloadFileForState(state, path) {
|
|
612
|
+
if (state.kind === "snapshot" && state.snapshotId) {
|
|
613
|
+
downloadArchiveFile(state.snapshotId, state.worktreeRepoId ?? null, path);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (state.kind === "local" && state.runId) {
|
|
617
|
+
downloadLocalArchiveFile(state.runId, path);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
throw new Error("Invalid archive source");
|
|
621
|
+
}
|
|
503
622
|
async function navigateTo(path) {
|
|
504
623
|
if (!fileState || !fileEls)
|
|
505
624
|
return;
|
|
506
625
|
fileEls.list.innerHTML = "Loading…";
|
|
507
626
|
const requestId = ++treeRequestToken;
|
|
508
627
|
try {
|
|
509
|
-
const res = await
|
|
510
|
-
if (!fileState || fileState.
|
|
628
|
+
const res = await listTreeForState(fileState, path);
|
|
629
|
+
if (!fileState || fileState.sourceKey !== activeSourceKey)
|
|
511
630
|
return;
|
|
512
631
|
if (requestId !== treeRequestToken)
|
|
513
632
|
return;
|
|
@@ -530,14 +649,14 @@ async function openFilePath(path) {
|
|
|
530
649
|
return;
|
|
531
650
|
const folder = parentPath(path);
|
|
532
651
|
await navigateTo(folder);
|
|
533
|
-
if (!fileState || fileState.
|
|
652
|
+
if (!fileState || fileState.sourceKey !== activeSourceKey)
|
|
534
653
|
return;
|
|
535
654
|
const node = fileState.nodes.find((item) => item.path === path && item.type === "file");
|
|
536
655
|
if (node) {
|
|
537
656
|
await selectFile(node);
|
|
538
657
|
}
|
|
539
658
|
else {
|
|
540
|
-
flash("File not found in archive
|
|
659
|
+
flash("File not found in archive.", "error");
|
|
541
660
|
}
|
|
542
661
|
}
|
|
543
662
|
async function selectFile(node, forceLoad = false) {
|
|
@@ -556,7 +675,7 @@ async function selectFile(node, forceLoad = false) {
|
|
|
556
675
|
fileEls.downloadBtn.onclick = () => {
|
|
557
676
|
if (!fileState)
|
|
558
677
|
return;
|
|
559
|
-
|
|
678
|
+
downloadFileForState(fileState, node.path);
|
|
560
679
|
};
|
|
561
680
|
if (node.size_bytes && node.size_bytes > MAX_PREVIEW_BYTES && !forceLoad) {
|
|
562
681
|
fileEls.fileContent.textContent = `Preview disabled for ${formatBytes(node.size_bytes)} file. Use Download or Load anyway.`;
|
|
@@ -572,8 +691,8 @@ async function selectFile(node, forceLoad = false) {
|
|
|
572
691
|
fileEls.fileContent.textContent = "Loading…";
|
|
573
692
|
fileEls.fileContent.classList.remove("hidden");
|
|
574
693
|
try {
|
|
575
|
-
const text = await
|
|
576
|
-
if (!fileState || fileState.
|
|
694
|
+
const text = await readFileForState(fileState, node.path);
|
|
695
|
+
if (!fileState || fileState.sourceKey !== activeSourceKey)
|
|
577
696
|
return;
|
|
578
697
|
if (requestId !== fileRequestToken)
|
|
579
698
|
return;
|
|
@@ -589,20 +708,35 @@ async function selectFile(node, forceLoad = false) {
|
|
|
589
708
|
flash("Failed to load archive file.", "error");
|
|
590
709
|
}
|
|
591
710
|
}
|
|
592
|
-
function initArchiveFileViewer(
|
|
711
|
+
function initArchiveFileViewer(source) {
|
|
593
712
|
fileEls = collectFileEls();
|
|
594
713
|
if (!fileEls)
|
|
595
714
|
return;
|
|
596
|
-
const key =
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
715
|
+
const key = listItemKey(source);
|
|
716
|
+
activeSourceKey = key;
|
|
717
|
+
if (source.kind === "snapshot") {
|
|
718
|
+
fileState = {
|
|
719
|
+
kind: "snapshot",
|
|
720
|
+
snapshotId: source.summary.snapshot_id,
|
|
721
|
+
worktreeRepoId: source.summary.worktree_repo_id,
|
|
722
|
+
sourceKey: key,
|
|
723
|
+
rootLabel: "Snapshot",
|
|
724
|
+
currentPath: "",
|
|
725
|
+
nodes: [],
|
|
726
|
+
selectedFile: null,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
fileState = {
|
|
731
|
+
kind: "local",
|
|
732
|
+
runId: source.summary.run_id,
|
|
733
|
+
sourceKey: key,
|
|
734
|
+
rootLabel: "Run archive",
|
|
735
|
+
currentPath: "",
|
|
736
|
+
nodes: [],
|
|
737
|
+
selectedFile: null,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
606
740
|
resetFileViewer();
|
|
607
741
|
wireArchivePathButtons(fileEls.quickLinks);
|
|
608
742
|
fileEls.refreshBtn?.addEventListener("click", () => {
|
|
@@ -654,7 +788,7 @@ async function loadArtifactListings(summary) {
|
|
|
654
788
|
flowList.textContent = "Loading…";
|
|
655
789
|
try {
|
|
656
790
|
const runs = await listArchiveTree(summary.snapshot_id, summary.worktree_repo_id, "runs");
|
|
657
|
-
if (!fileState || fileState.
|
|
791
|
+
if (!fileState || fileState.sourceKey !== activeSourceKey)
|
|
658
792
|
return;
|
|
659
793
|
if (requestId !== artifactRequestToken)
|
|
660
794
|
return;
|
|
@@ -668,7 +802,7 @@ async function loadArtifactListings(summary) {
|
|
|
668
802
|
}
|
|
669
803
|
try {
|
|
670
804
|
const flows = await listArchiveTree(summary.snapshot_id, summary.worktree_repo_id, "flows");
|
|
671
|
-
if (!fileState || fileState.
|
|
805
|
+
if (!fileState || fileState.sourceKey !== activeSourceKey)
|
|
672
806
|
return;
|
|
673
807
|
if (requestId !== artifactRequestToken)
|
|
674
808
|
return;
|
|
@@ -699,20 +833,20 @@ async function loadSnapshotDetail(target) {
|
|
|
699
833
|
</div>
|
|
700
834
|
<span class="pill pill-idle" id="archive-detail-status">${escapeHtml(summary.status || "unknown")}</span>
|
|
701
835
|
</div>
|
|
702
|
-
${renderSubTabs()}
|
|
836
|
+
${renderSubTabs("Snapshot")}
|
|
703
837
|
<div id="archive-tab-snapshot" class="archive-tab-content archive-tab-snapshot${activeSubTab === "snapshot" ? " active" : ""}">
|
|
704
838
|
${renderSummaryGrid(summary, meta)}
|
|
705
839
|
${renderArtifactSection(summary, meta)}
|
|
706
840
|
</div>
|
|
707
841
|
<div id="archive-tab-files" class="archive-tab-content archive-tab-files${activeSubTab === "files" ? " active" : ""}">
|
|
708
|
-
${renderFileSection()}
|
|
842
|
+
${renderFileSection(SNAPSHOT_QUICK_LINKS, "Browse snapshot files (read-only).")}
|
|
709
843
|
</div>
|
|
710
844
|
`;
|
|
711
845
|
const statusEl = document.getElementById("archive-detail-status");
|
|
712
846
|
if (statusEl)
|
|
713
847
|
statusPill(statusEl, summary.status || "unknown");
|
|
714
848
|
wireSubTabs();
|
|
715
|
-
initArchiveFileViewer(summary);
|
|
849
|
+
initArchiveFileViewer({ kind: "snapshot", summary });
|
|
716
850
|
wireArchivePathButtons(document.getElementById("archive-artifact-actions"));
|
|
717
851
|
void loadArtifactListings(summary);
|
|
718
852
|
}
|
|
@@ -724,15 +858,44 @@ async function loadSnapshotDetail(target) {
|
|
|
724
858
|
flash("Failed to load archive snapshot.", "error");
|
|
725
859
|
}
|
|
726
860
|
}
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
861
|
+
async function loadLocalDetail(target) {
|
|
862
|
+
if (!detailEl)
|
|
863
|
+
return;
|
|
864
|
+
detailEl.innerHTML = `<div class="muted small">Loading run archive…</div>`;
|
|
865
|
+
detailEl.innerHTML = `
|
|
866
|
+
<div class="archive-detail-header">
|
|
867
|
+
<div>
|
|
868
|
+
<div class="archive-detail-title">${escapeHtml(target.run_id)}</div>
|
|
869
|
+
<div class="archive-detail-subtitle muted small">Local run archive</div>
|
|
870
|
+
</div>
|
|
871
|
+
<span class="pill pill-idle" id="archive-detail-status">local</span>
|
|
872
|
+
</div>
|
|
873
|
+
${renderSubTabs("Overview")}
|
|
874
|
+
<div id="archive-tab-snapshot" class="archive-tab-content archive-tab-snapshot${activeSubTab === "snapshot" ? " active" : ""}">
|
|
875
|
+
${renderLocalSummary(target)}
|
|
876
|
+
</div>
|
|
877
|
+
<div id="archive-tab-files" class="archive-tab-content archive-tab-files${activeSubTab === "files" ? " active" : ""}">
|
|
878
|
+
${renderFileSection(LOCAL_QUICK_LINKS, "Browse archived run files (read-only).")}
|
|
879
|
+
</div>
|
|
880
|
+
`;
|
|
881
|
+
wireSubTabs();
|
|
882
|
+
initArchiveFileViewer({ kind: "local", summary: target });
|
|
883
|
+
wireArchivePathButtons(document.getElementById("archive-local-artifact-actions"));
|
|
884
|
+
}
|
|
885
|
+
function selectItem(target) {
|
|
886
|
+
selectedItem = target;
|
|
887
|
+
renderList(snapshots, localArchives);
|
|
888
|
+
if (target.kind === "snapshot") {
|
|
889
|
+
void loadSnapshotDetail(target.summary);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
void loadLocalDetail(target.summary);
|
|
893
|
+
}
|
|
731
894
|
}
|
|
732
|
-
async function
|
|
895
|
+
async function loadArchiveData(forceReload = false) {
|
|
733
896
|
if (!listEl)
|
|
734
897
|
return;
|
|
735
|
-
const isInitialLoad = snapshots.length === 0;
|
|
898
|
+
const isInitialLoad = snapshots.length === 0 && localArchives.length === 0;
|
|
736
899
|
const showRefreshIndicator = !isInitialLoad;
|
|
737
900
|
if (showRefreshIndicator) {
|
|
738
901
|
setButtonLoading(refreshBtn, true);
|
|
@@ -744,47 +907,67 @@ async function loadSnapshots(forceReload = false) {
|
|
|
744
907
|
if (emptyEl)
|
|
745
908
|
emptyEl.classList.add("hidden");
|
|
746
909
|
try {
|
|
747
|
-
const
|
|
748
|
-
const
|
|
910
|
+
const [snapshotItems, localItems] = await Promise.all([listArchiveSnapshots(), listLocalRunArchives()]);
|
|
911
|
+
const sortedSnapshots = snapshotItems.slice().sort((a, b) => {
|
|
749
912
|
const aTime = a.created_at ? new Date(a.created_at).getTime() : 0;
|
|
750
913
|
const bTime = b.created_at ? new Date(b.created_at).getTime() : 0;
|
|
751
914
|
if (aTime !== bTime)
|
|
752
915
|
return bTime - aTime;
|
|
753
916
|
return (b.snapshot_id || "").localeCompare(a.snapshot_id || "");
|
|
754
917
|
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
918
|
+
const sortedLocals = localItems.slice().sort((a, b) => {
|
|
919
|
+
const aTime = a.archived_at ? new Date(a.archived_at).getTime() : 0;
|
|
920
|
+
const bTime = b.archived_at ? new Date(b.archived_at).getTime() : 0;
|
|
921
|
+
if (aTime !== bTime)
|
|
922
|
+
return bTime - aTime;
|
|
923
|
+
return (b.run_id || "").localeCompare(a.run_id || "");
|
|
924
|
+
});
|
|
925
|
+
// Check if archives have changed
|
|
926
|
+
const newSignature = archiveSignature(sortedSnapshots, sortedLocals);
|
|
927
|
+
const hasChanged = newSignature !== lastArchiveSignature;
|
|
758
928
|
// Skip update if nothing changed and not forced
|
|
759
929
|
if (!forceReload && !hasChanged && !isInitialLoad) {
|
|
760
930
|
return;
|
|
761
931
|
}
|
|
762
|
-
|
|
763
|
-
snapshots =
|
|
764
|
-
|
|
765
|
-
|
|
932
|
+
lastArchiveSignature = newSignature;
|
|
933
|
+
snapshots = sortedSnapshots;
|
|
934
|
+
localArchives = sortedLocals;
|
|
935
|
+
renderList(sortedSnapshots, sortedLocals);
|
|
936
|
+
if (!sortedSnapshots.length && !sortedLocals.length)
|
|
766
937
|
return;
|
|
767
|
-
const selectedKey =
|
|
768
|
-
const
|
|
769
|
-
?
|
|
938
|
+
const selectedKey = selectedItem ? listItemKey(selectedItem) : "";
|
|
939
|
+
const matchSnapshot = selectedKey && selectedKey.startsWith("snapshot:")
|
|
940
|
+
? sortedSnapshots.find((item) => `snapshot:${snapshotKey(item)}` === selectedKey)
|
|
941
|
+
: null;
|
|
942
|
+
const matchLocal = selectedKey && selectedKey.startsWith("local:")
|
|
943
|
+
? sortedLocals.find((item) => `local:${item.run_id}` === selectedKey)
|
|
770
944
|
: null;
|
|
771
945
|
// Only reload detail if selection changed or forced
|
|
772
|
-
if (forceReload || !
|
|
773
|
-
const
|
|
774
|
-
|
|
946
|
+
if (forceReload || (!matchSnapshot && !matchLocal) || isInitialLoad) {
|
|
947
|
+
const nextSnapshot = sortedSnapshots[0];
|
|
948
|
+
const nextLocal = sortedLocals[0];
|
|
949
|
+
if (nextSnapshot) {
|
|
950
|
+
selectItem({ kind: "snapshot", summary: nextSnapshot });
|
|
951
|
+
}
|
|
952
|
+
else if (nextLocal) {
|
|
953
|
+
selectItem({ kind: "local", summary: nextLocal });
|
|
954
|
+
}
|
|
775
955
|
}
|
|
776
|
-
else if (
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
956
|
+
else if (matchSnapshot) {
|
|
957
|
+
selectedItem = { kind: "snapshot", summary: matchSnapshot };
|
|
958
|
+
renderList(sortedSnapshots, sortedLocals);
|
|
959
|
+
}
|
|
960
|
+
else if (matchLocal) {
|
|
961
|
+
selectedItem = { kind: "local", summary: matchLocal };
|
|
962
|
+
renderList(sortedSnapshots, sortedLocals);
|
|
780
963
|
}
|
|
781
964
|
}
|
|
782
965
|
catch (err) {
|
|
783
966
|
listEl.innerHTML = "";
|
|
784
|
-
renderEmptyDetail("Unable to load
|
|
967
|
+
renderEmptyDetail("Unable to load archives.");
|
|
785
968
|
if (emptyEl)
|
|
786
969
|
emptyEl.classList.add("hidden");
|
|
787
|
-
flash("Failed to load
|
|
970
|
+
flash("Failed to load archives.", "error");
|
|
788
971
|
}
|
|
789
972
|
finally {
|
|
790
973
|
if (showRefreshIndicator) {
|
|
@@ -799,12 +982,22 @@ function handleListClick(event) {
|
|
|
799
982
|
const btn = target.closest(".archive-snapshot");
|
|
800
983
|
if (!btn)
|
|
801
984
|
return;
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
985
|
+
const kind = btn.dataset.kind;
|
|
986
|
+
if (kind === "snapshot") {
|
|
987
|
+
const snapshotId = btn.dataset.snapshotId;
|
|
988
|
+
const worktreeId = btn.dataset.worktreeId;
|
|
989
|
+
if (!snapshotId || !worktreeId)
|
|
990
|
+
return;
|
|
991
|
+
const match = snapshots.find((item) => item.snapshot_id === snapshotId && item.worktree_repo_id === worktreeId);
|
|
992
|
+
selectItem({ kind: "snapshot", summary: match || { snapshot_id: snapshotId, worktree_repo_id: worktreeId } });
|
|
993
|
+
}
|
|
994
|
+
else if (kind === "local") {
|
|
995
|
+
const runId = btn.dataset.runId;
|
|
996
|
+
if (!runId)
|
|
997
|
+
return;
|
|
998
|
+
const match = localArchives.find((item) => item.run_id === runId);
|
|
999
|
+
selectItem({ kind: "local", summary: match || { run_id: runId, has_tickets: false, has_runs: false } });
|
|
1000
|
+
}
|
|
808
1001
|
}
|
|
809
1002
|
export function initArchive() {
|
|
810
1003
|
if (initialized)
|
|
@@ -814,13 +1007,13 @@ export function initArchive() {
|
|
|
814
1007
|
return;
|
|
815
1008
|
listEl.addEventListener("click", handleListClick);
|
|
816
1009
|
refreshBtn?.addEventListener("click", () => {
|
|
817
|
-
void
|
|
1010
|
+
void loadArchiveData(true); // Force reload on manual refresh
|
|
818
1011
|
});
|
|
819
1012
|
subscribe("repo:health", (payload) => {
|
|
820
1013
|
const status = payload?.status || "";
|
|
821
1014
|
if (status === "ok" || status === "degraded") {
|
|
822
|
-
void
|
|
1015
|
+
void loadArchiveData(); // Non-forced: only updates if data changed
|
|
823
1016
|
}
|
|
824
1017
|
});
|
|
825
|
-
void
|
|
1018
|
+
void loadArchiveData(true); // Initial load
|
|
826
1019
|
}
|
|
@@ -12,6 +12,10 @@ export async function fetchArchiveSnapshot(snapshotId, worktreeRepoId) {
|
|
|
12
12
|
const url = `/api/archive/snapshots/${encodeURIComponent(snapshotId)}${qs ? `?${qs}` : ""}`;
|
|
13
13
|
return (await api(url));
|
|
14
14
|
}
|
|
15
|
+
export async function listLocalRunArchives() {
|
|
16
|
+
const res = (await api("/api/archive/local-runs"));
|
|
17
|
+
return res?.archives ?? [];
|
|
18
|
+
}
|
|
15
19
|
export async function listArchiveTree(snapshotId, worktreeRepoId, path = "") {
|
|
16
20
|
const params = new URLSearchParams({ snapshot_id: snapshotId });
|
|
17
21
|
if (worktreeRepoId)
|
|
@@ -21,6 +25,13 @@ export async function listArchiveTree(snapshotId, worktreeRepoId, path = "") {
|
|
|
21
25
|
const url = `/api/archive/tree?${params.toString()}`;
|
|
22
26
|
return (await api(url));
|
|
23
27
|
}
|
|
28
|
+
export async function listLocalArchiveTree(runId, path = "") {
|
|
29
|
+
const params = new URLSearchParams({ run_id: runId });
|
|
30
|
+
if (path)
|
|
31
|
+
params.set("path", path);
|
|
32
|
+
const url = `/api/archive/local/tree?${params.toString()}`;
|
|
33
|
+
return (await api(url));
|
|
34
|
+
}
|
|
24
35
|
export async function readArchiveFile(snapshotId, worktreeRepoId, path) {
|
|
25
36
|
const params = new URLSearchParams({ snapshot_id: snapshotId, path });
|
|
26
37
|
if (worktreeRepoId)
|
|
@@ -28,6 +39,11 @@ export async function readArchiveFile(snapshotId, worktreeRepoId, path) {
|
|
|
28
39
|
const url = `/api/archive/file?${params.toString()}`;
|
|
29
40
|
return (await api(url));
|
|
30
41
|
}
|
|
42
|
+
export async function readLocalArchiveFile(runId, path) {
|
|
43
|
+
const params = new URLSearchParams({ run_id: runId, path });
|
|
44
|
+
const url = `/api/archive/local/file?${params.toString()}`;
|
|
45
|
+
return (await api(url));
|
|
46
|
+
}
|
|
31
47
|
export function downloadArchiveFile(snapshotId, worktreeRepoId, path) {
|
|
32
48
|
const params = new URLSearchParams({ snapshot_id: snapshotId, path });
|
|
33
49
|
if (worktreeRepoId)
|
|
@@ -35,3 +51,8 @@ export function downloadArchiveFile(snapshotId, worktreeRepoId, path) {
|
|
|
35
51
|
const url = resolvePath(`/api/archive/download?${params.toString()}`);
|
|
36
52
|
window.location.href = url;
|
|
37
53
|
}
|
|
54
|
+
export function downloadLocalArchiveFile(runId, path) {
|
|
55
|
+
const params = new URLSearchParams({ run_id: runId, path });
|
|
56
|
+
const url = resolvePath(`/api/archive/local/download?${params.toString()}`);
|
|
57
|
+
window.location.href = url;
|
|
58
|
+
}
|
|
@@ -37,7 +37,7 @@ export const CONSTANTS = {
|
|
|
37
37
|
},
|
|
38
38
|
PROMPTS: {
|
|
39
39
|
VOICE_TRANSCRIPT_DISCLAIMER: "Note: transcribed from user voice. If confusing or possibly inaccurate and you cannot infer the intention please clarify before proceeding.",
|
|
40
|
-
CAR_CONTEXT_HINT: "Context:
|
|
40
|
+
CAR_CONTEXT_HINT: "Context: This repo is managed by Codex Autorunner (CAR). Read `.codex-autorunner/ABOUT_CAR.md` (tickets, workspace docs, helper commands) before making workflow assumptions.",
|
|
41
41
|
},
|
|
42
42
|
KEYWORDS: {
|
|
43
43
|
CAR_CONTEXT: [
|