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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
-
import { api, flash, getUrlParams, resolvePath, statusPill, getAuthToken, openModal, inputModal, setButtonLoading, } from "./utils.js";
|
|
2
|
+
import { api, confirmModal, flash, getUrlParams, resolvePath, statusPill, getAuthToken, openModal, inputModal, setButtonLoading, } from "./utils.js";
|
|
3
3
|
// Note: activateTab removed - header now used for collapse, not inbox navigation
|
|
4
4
|
import { registerAutoRefresh } from "./autoRefresh.js";
|
|
5
5
|
import { CONSTANTS } from "./constants.js";
|
|
@@ -837,6 +837,17 @@ function renderTickets(data) {
|
|
|
837
837
|
if (!tickets)
|
|
838
838
|
return;
|
|
839
839
|
tickets.innerHTML = "";
|
|
840
|
+
// Display lint errors if present
|
|
841
|
+
if (data?.lint_errors && data.lint_errors.length > 0) {
|
|
842
|
+
const lintBanner = document.createElement("div");
|
|
843
|
+
lintBanner.className = "ticket-lint-errors";
|
|
844
|
+
data.lint_errors.forEach((error) => {
|
|
845
|
+
const errorLine = document.createElement("div");
|
|
846
|
+
errorLine.textContent = error;
|
|
847
|
+
lintBanner.appendChild(errorLine);
|
|
848
|
+
});
|
|
849
|
+
tickets.appendChild(lintBanner);
|
|
850
|
+
}
|
|
840
851
|
const list = (data?.tickets || []);
|
|
841
852
|
ticketsExist = list.length > 0;
|
|
842
853
|
// Update progress bar
|
|
@@ -870,9 +881,19 @@ function renderTickets(data) {
|
|
|
870
881
|
item.title = "Click to edit";
|
|
871
882
|
item.setAttribute("data-ticket-path", ticket.path || "");
|
|
872
883
|
// Make ticket item clickable to open editor
|
|
873
|
-
item.addEventListener("click", () => {
|
|
884
|
+
item.addEventListener("click", async () => {
|
|
874
885
|
updateSelectedTicket(ticket.path || null);
|
|
875
|
-
|
|
886
|
+
try {
|
|
887
|
+
if (ticket.index == null) {
|
|
888
|
+
flash("Invalid ticket: missing index", "error");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const data = (await api(`/api/flows/ticket_flow/tickets/${ticket.index}`));
|
|
892
|
+
openTicketEditor(data);
|
|
893
|
+
}
|
|
894
|
+
catch (err) {
|
|
895
|
+
flash(`Failed to load ticket: ${err.message}`, "error");
|
|
896
|
+
}
|
|
876
897
|
});
|
|
877
898
|
const head = document.createElement("div");
|
|
878
899
|
head.className = "ticket-item-head";
|
|
@@ -928,6 +949,17 @@ function renderTickets(data) {
|
|
|
928
949
|
agent.className = "ticket-agent";
|
|
929
950
|
agent.textContent = fm?.agent || "codex";
|
|
930
951
|
badges.appendChild(agent);
|
|
952
|
+
// Cumulative diff stats (from FlowStore DIFF_UPDATED aggregation).
|
|
953
|
+
const diffStats = ticket.diff_stats || null;
|
|
954
|
+
if (diffStats && (diffStats.insertions > 0 || diffStats.deletions > 0)) {
|
|
955
|
+
const statsEl = document.createElement("span");
|
|
956
|
+
statsEl.className = "ticket-diff-stats";
|
|
957
|
+
const ins = diffStats.insertions || 0;
|
|
958
|
+
const del = diffStats.deletions || 0;
|
|
959
|
+
statsEl.innerHTML = `<span class="diff-add">+${formatNumber(ins)}</span><span class="diff-del">-${formatNumber(del)}</span>`;
|
|
960
|
+
statsEl.title = `${ins} insertions, ${del} deletions${diffStats.files_changed ? `, ${diffStats.files_changed} files` : ""}`;
|
|
961
|
+
badges.appendChild(statsEl);
|
|
962
|
+
}
|
|
931
963
|
head.appendChild(badges);
|
|
932
964
|
item.appendChild(head);
|
|
933
965
|
if (ticket.errors && ticket.errors.length) {
|
|
@@ -1051,7 +1083,10 @@ function renderDispatchHistory(runId, data) {
|
|
|
1051
1083
|
contentWrapper.appendChild(header);
|
|
1052
1084
|
container.append(collapseBar, contentWrapper);
|
|
1053
1085
|
// Add diff stats if present (for turn summaries)
|
|
1054
|
-
|
|
1086
|
+
// New path: dispatch.diff_stats (from FlowStore DIFF_UPDATED merge)
|
|
1087
|
+
// Legacy fallback: dispatch.extra.diff_stats (DISPATCH.md frontmatter)
|
|
1088
|
+
const diffStats = (dispatch?.diff_stats ||
|
|
1089
|
+
dispatch?.extra?.diff_stats);
|
|
1055
1090
|
if (diffStats && (diffStats.insertions || diffStats.deletions)) {
|
|
1056
1091
|
const statsEl = document.createElement("span");
|
|
1057
1092
|
statsEl.className = "dispatch-diff-stats";
|
|
@@ -1113,10 +1148,18 @@ function renderDispatchHistory(runId, data) {
|
|
|
1113
1148
|
if (!att.url)
|
|
1114
1149
|
return;
|
|
1115
1150
|
const link = document.createElement("a");
|
|
1116
|
-
|
|
1151
|
+
const resolved = new URL(resolvePath(att.url), window.location.origin);
|
|
1152
|
+
link.href = resolved.toString();
|
|
1117
1153
|
link.textContent = att.name || att.rel_path || "attachment";
|
|
1118
|
-
|
|
1119
|
-
|
|
1154
|
+
// Prefer direct downloads for same-origin attachments.
|
|
1155
|
+
if (resolved.origin === window.location.origin) {
|
|
1156
|
+
link.download = "";
|
|
1157
|
+
link.rel = "noopener";
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
link.target = "_blank";
|
|
1161
|
+
link.rel = "noreferrer noopener";
|
|
1162
|
+
}
|
|
1120
1163
|
link.title = att.path || "";
|
|
1121
1164
|
wrap.appendChild(link);
|
|
1122
1165
|
});
|
|
@@ -1185,6 +1228,7 @@ async function loadTicketFiles(ctx) {
|
|
|
1185
1228
|
return {
|
|
1186
1229
|
ticket_dir: data.ticket_dir,
|
|
1187
1230
|
tickets: data.tickets,
|
|
1231
|
+
lint_errors: data.lint_errors,
|
|
1188
1232
|
activeTicket: currentActiveTicket,
|
|
1189
1233
|
flowStatus: currentFlowStatus,
|
|
1190
1234
|
};
|
|
@@ -1204,10 +1248,9 @@ async function loadTicketFiles(ctx) {
|
|
|
1204
1248
|
*/
|
|
1205
1249
|
async function openTicketByIndex(index) {
|
|
1206
1250
|
try {
|
|
1207
|
-
const data = (await api(
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
openTicketEditor(ticket);
|
|
1251
|
+
const data = (await api(`/api/flows/ticket_flow/tickets/${index}`));
|
|
1252
|
+
if (data) {
|
|
1253
|
+
openTicketEditor(data);
|
|
1211
1254
|
}
|
|
1212
1255
|
else {
|
|
1213
1256
|
flash(`Ticket TICKET-${String(index).padStart(3, "0")} not found`, "error");
|
|
@@ -1714,7 +1757,8 @@ async function restartTicketFlow() {
|
|
|
1714
1757
|
flash("Create a ticket first before restarting the flow.", "error");
|
|
1715
1758
|
return;
|
|
1716
1759
|
}
|
|
1717
|
-
|
|
1760
|
+
const confirmed = await confirmModal("Restart ticket flow? This will stop the current run and start a new one.");
|
|
1761
|
+
if (!confirmed) {
|
|
1718
1762
|
return;
|
|
1719
1763
|
}
|
|
1720
1764
|
setButtonsDisabled(true);
|
|
@@ -1754,7 +1798,8 @@ async function archiveTicketFlow() {
|
|
|
1754
1798
|
flash("No ticket flow run to archive", "info");
|
|
1755
1799
|
return;
|
|
1756
1800
|
}
|
|
1757
|
-
|
|
1801
|
+
const confirmed = await confirmModal("Archive all tickets from this flow? They will be moved to the run's artifact directory.");
|
|
1802
|
+
if (!confirmed) {
|
|
1758
1803
|
return;
|
|
1759
1804
|
}
|
|
1760
1805
|
setButtonsDisabled(true);
|
|
@@ -1869,21 +1914,26 @@ export function initTicketFlow() {
|
|
|
1869
1914
|
const { overflowToggle, overflowDropdown, overflowNew, overflowRestart, overflowArchive } = els();
|
|
1870
1915
|
if (overflowToggle && overflowDropdown) {
|
|
1871
1916
|
const toggleMenu = (e) => {
|
|
1917
|
+
e.preventDefault();
|
|
1872
1918
|
e.stopPropagation();
|
|
1873
1919
|
const isHidden = overflowDropdown.classList.contains("hidden");
|
|
1874
1920
|
overflowDropdown.classList.toggle("hidden", !isHidden);
|
|
1875
1921
|
};
|
|
1876
|
-
|
|
1877
|
-
overflowToggle.addEventListener("
|
|
1878
|
-
|
|
1879
|
-
|
|
1922
|
+
const closeMenu = () => overflowDropdown.classList.add("hidden");
|
|
1923
|
+
overflowToggle.addEventListener("pointerdown", toggleMenu);
|
|
1924
|
+
overflowToggle.addEventListener("click", (e) => {
|
|
1925
|
+
e.preventDefault(); // swallow synthetic click after pointerdown
|
|
1926
|
+
});
|
|
1927
|
+
overflowToggle.addEventListener("keydown", (e) => {
|
|
1928
|
+
if (e.key === "Enter" || e.key === " ")
|
|
1929
|
+
toggleMenu(e);
|
|
1880
1930
|
});
|
|
1881
1931
|
// Close on outside click
|
|
1882
|
-
document.addEventListener("
|
|
1932
|
+
document.addEventListener("pointerdown", (e) => {
|
|
1883
1933
|
if (!overflowDropdown.classList.contains("hidden") &&
|
|
1884
1934
|
!overflowToggle.contains(e.target) &&
|
|
1885
1935
|
!overflowDropdown.contains(e.target)) {
|
|
1886
|
-
|
|
1936
|
+
closeMenu();
|
|
1887
1937
|
}
|
|
1888
1938
|
});
|
|
1889
1939
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { fetchActiveFileChat, streamTurnEvents } from "./fileChat.js";
|
|
3
|
+
export async function resumeFileChatTurn(clientTurnId, opts = {}) {
|
|
4
|
+
const active = await fetchActiveFileChat(clientTurnId, opts.basePath || "/api/file-chat/active");
|
|
5
|
+
const current = (active.current || {});
|
|
6
|
+
const lastResult = (active.last_result || {});
|
|
7
|
+
if (lastResult.status && opts.onResult) {
|
|
8
|
+
opts.onResult(lastResult);
|
|
9
|
+
}
|
|
10
|
+
const threadId = typeof current.thread_id === "string" ? current.thread_id : "";
|
|
11
|
+
const turnId = typeof current.turn_id === "string" ? current.turn_id : "";
|
|
12
|
+
const agent = typeof current.agent === "string" ? current.agent : "codex";
|
|
13
|
+
if (threadId && turnId) {
|
|
14
|
+
const meta = {
|
|
15
|
+
agent,
|
|
16
|
+
threadId,
|
|
17
|
+
turnId,
|
|
18
|
+
basePath: opts.eventsBasePath || "/api/file-chat/turns",
|
|
19
|
+
};
|
|
20
|
+
const controller = streamTurnEvents(meta, {
|
|
21
|
+
onEvent: opts.onEvent,
|
|
22
|
+
onError: opts.onError,
|
|
23
|
+
});
|
|
24
|
+
return { controller, lastResult };
|
|
25
|
+
}
|
|
26
|
+
return { controller: null, lastResult };
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
export function loadPendingTurn(key) {
|
|
3
|
+
try {
|
|
4
|
+
const raw = localStorage.getItem(key);
|
|
5
|
+
if (!raw)
|
|
6
|
+
return null;
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
if (!parsed || typeof parsed !== "object")
|
|
9
|
+
return null;
|
|
10
|
+
if (!parsed.clientTurnId || !parsed.message || !parsed.startedAtMs)
|
|
11
|
+
return null;
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function savePendingTurn(key, turn) {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem(key, JSON.stringify(turn));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// ignore
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function clearPendingTurn(key) {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.removeItem(key);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
}
|
codex_autorunner/static/utils.js
CHANGED
|
@@ -355,6 +355,34 @@ const FOCUSABLE_SELECTOR = [
|
|
|
355
355
|
"[tabindex]:not([tabindex=\"-1\"])",
|
|
356
356
|
].join(",");
|
|
357
357
|
let modalOpenCount = 0;
|
|
358
|
+
export function repairModalBackgroundIfStuck() {
|
|
359
|
+
// Dev reloads / unexpected errors can leave the app background `inert` even when
|
|
360
|
+
// no modal is visible. This makes the whole UI feel "unclickable".
|
|
361
|
+
const openModals = document.querySelectorAll(".modal-overlay:not([hidden])");
|
|
362
|
+
if (openModals.length > 0)
|
|
363
|
+
return false;
|
|
364
|
+
let repaired = false;
|
|
365
|
+
MODAL_BACKGROUND_IDS.forEach((id) => {
|
|
366
|
+
const el = document.getElementById(id);
|
|
367
|
+
if (!el)
|
|
368
|
+
return;
|
|
369
|
+
if (el.hasAttribute("inert") || el.getAttribute("aria-hidden") === "true") {
|
|
370
|
+
repaired = true;
|
|
371
|
+
}
|
|
372
|
+
el.removeAttribute("aria-hidden");
|
|
373
|
+
try {
|
|
374
|
+
el.inert = false;
|
|
375
|
+
}
|
|
376
|
+
catch (_err) {
|
|
377
|
+
// ignore
|
|
378
|
+
}
|
|
379
|
+
el.removeAttribute("inert");
|
|
380
|
+
});
|
|
381
|
+
if (repaired) {
|
|
382
|
+
modalOpenCount = 0;
|
|
383
|
+
}
|
|
384
|
+
return repaired;
|
|
385
|
+
}
|
|
358
386
|
function getFocusableElements(container) {
|
|
359
387
|
if (!container || !container.querySelectorAll)
|
|
360
388
|
return [];
|