codex-autorunner 1.2.0__py3-none-any.whl → 1.3.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/bootstrap.py +26 -5
- codex_autorunner/core/about_car.py +12 -12
- codex_autorunner/core/config.py +178 -61
- codex_autorunner/core/context_awareness.py +1 -0
- codex_autorunner/core/filesystem.py +24 -0
- codex_autorunner/core/flows/controller.py +50 -12
- codex_autorunner/core/flows/runtime.py +8 -3
- codex_autorunner/core/hub.py +293 -16
- codex_autorunner/core/lifecycle_events.py +44 -5
- codex_autorunner/core/pma_context.py +188 -1
- codex_autorunner/core/pma_delivery.py +81 -0
- codex_autorunner/core/pma_dispatches.py +224 -0
- codex_autorunner/core/pma_lane_worker.py +122 -0
- codex_autorunner/core/pma_queue.py +167 -18
- codex_autorunner/core/pma_reactive.py +91 -0
- codex_autorunner/core/pma_safety.py +58 -0
- codex_autorunner/core/pma_sink.py +104 -0
- codex_autorunner/core/pma_transcripts.py +183 -0
- codex_autorunner/core/safe_paths.py +117 -0
- codex_autorunner/housekeeping.py +77 -23
- codex_autorunner/integrations/agents/codex_backend.py +18 -12
- codex_autorunner/integrations/agents/wiring.py +2 -0
- codex_autorunner/integrations/app_server/client.py +31 -0
- codex_autorunner/integrations/app_server/supervisor.py +3 -0
- codex_autorunner/integrations/telegram/adapter.py +1 -1
- codex_autorunner/integrations/telegram/config.py +1 -1
- codex_autorunner/integrations/telegram/constants.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +16 -15
- codex_autorunner/integrations/telegram/handlers/commands/files.py +5 -8
- codex_autorunner/integrations/telegram/handlers/commands/github.py +10 -6
- codex_autorunner/integrations/telegram/handlers/commands/shared.py +9 -8
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +85 -2
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +29 -8
- codex_autorunner/integrations/telegram/handlers/messages.py +8 -2
- codex_autorunner/integrations/telegram/helpers.py +30 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +54 -3
- codex_autorunner/static/archive.js +274 -81
- codex_autorunner/static/archiveApi.js +21 -0
- codex_autorunner/static/constants.js +1 -1
- codex_autorunner/static/docChatCore.js +2 -0
- codex_autorunner/static/hub.js +59 -0
- codex_autorunner/static/index.html +70 -54
- codex_autorunner/static/notificationBell.js +173 -0
- codex_autorunner/static/notifications.js +187 -36
- codex_autorunner/static/pma.js +96 -35
- codex_autorunner/static/styles.css +431 -4
- codex_autorunner/static/terminalManager.js +22 -3
- codex_autorunner/static/utils.js +5 -1
- codex_autorunner/surfaces/cli/cli.py +206 -129
- codex_autorunner/surfaces/cli/template_repos.py +157 -0
- codex_autorunner/surfaces/web/app.py +193 -5
- codex_autorunner/surfaces/web/routes/archive.py +197 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +115 -87
- codex_autorunner/surfaces/web/routes/flows.py +125 -67
- codex_autorunner/surfaces/web/routes/pma.py +638 -57
- codex_autorunner/surfaces/web/schemas.py +11 -0
- codex_autorunner/tickets/agent_pool.py +6 -1
- codex_autorunner/tickets/outbox.py +27 -14
- codex_autorunner/tickets/replies.py +4 -10
- codex_autorunner/tickets/runner.py +1 -0
- codex_autorunner/workspace/paths.py +8 -3
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/METADATA +1 -1
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/RECORD +67 -57
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.2.0.dist-info → codex_autorunner-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
-
import { api, escapeHtml, openModal, resolvePath } from "./utils.js";
|
|
2
|
+
import { api, confirmModal, escapeHtml, flash, inputModal, openModal, resolvePath } from "./utils.js";
|
|
3
3
|
import { registerAutoRefresh } from "./autoRefresh.js";
|
|
4
4
|
let notificationsInitialized = false;
|
|
5
|
-
|
|
5
|
+
const notificationItemsByRoot = {};
|
|
6
6
|
let activeRoot = null;
|
|
7
7
|
let closeModalFn = null;
|
|
8
8
|
let documentListenerInstalled = false;
|
|
9
9
|
let modalElements = null;
|
|
10
10
|
let isRefreshing = false;
|
|
11
|
+
const DROPDOWN_MARGIN = 8;
|
|
12
|
+
const DROPDOWN_OFFSET = 6;
|
|
11
13
|
const NOTIFICATIONS_REFRESH_ID = "notifications";
|
|
12
14
|
const NOTIFICATIONS_REFRESH_MS = 15000;
|
|
13
15
|
function getModalElements() {
|
|
@@ -27,10 +29,11 @@ function getRootElements(root) {
|
|
|
27
29
|
const dropdown = root.querySelector("[data-notifications-dropdown]");
|
|
28
30
|
if (!trigger || !badge || !dropdown)
|
|
29
31
|
return null;
|
|
30
|
-
|
|
32
|
+
const key = root.getAttribute("data-notifications-root") || "hub";
|
|
33
|
+
return { root, trigger, badge, dropdown, key };
|
|
31
34
|
}
|
|
32
|
-
function setBadgeCount(count) {
|
|
33
|
-
const roots = document.querySelectorAll(
|
|
35
|
+
function setBadgeCount(rootKey, count) {
|
|
36
|
+
const roots = document.querySelectorAll(`[data-notifications-root="${rootKey}"]`);
|
|
34
37
|
roots.forEach((root) => {
|
|
35
38
|
const elements = getRootElements(root);
|
|
36
39
|
if (!elements)
|
|
@@ -40,7 +43,7 @@ function setBadgeCount(count) {
|
|
|
40
43
|
elements.trigger.setAttribute("aria-label", count > 0 ? `Notifications (${count})` : "Notifications");
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
|
-
function
|
|
46
|
+
function normalizeHubItem(item) {
|
|
44
47
|
const repoId = String(item.repo_id || "");
|
|
45
48
|
const repoDisplay = item.repo_display_name || repoId;
|
|
46
49
|
const mode = item.dispatch?.mode || "";
|
|
@@ -51,6 +54,7 @@ function normalizeItem(item) {
|
|
|
51
54
|
const runId = String(item.run_id || "");
|
|
52
55
|
const openUrl = item.open_url || `/repos/${repoId}/?tab=inbox&run_id=${runId}`;
|
|
53
56
|
return {
|
|
57
|
+
kind: "hub",
|
|
54
58
|
repoId,
|
|
55
59
|
repoDisplay,
|
|
56
60
|
runId,
|
|
@@ -61,21 +65,42 @@ function normalizeItem(item) {
|
|
|
61
65
|
body,
|
|
62
66
|
isHandoff,
|
|
63
67
|
openUrl,
|
|
68
|
+
pillLabel: isHandoff ? "handoff" : "paused",
|
|
64
69
|
};
|
|
65
70
|
}
|
|
71
|
+
function normalizePmaItem(item) {
|
|
72
|
+
const title = (item.title || "PMA dispatch").trim();
|
|
73
|
+
const body = item.body || "";
|
|
74
|
+
const priority = (item.priority || "info").toLowerCase();
|
|
75
|
+
const isHandoff = priority === "action";
|
|
76
|
+
return {
|
|
77
|
+
kind: "pma",
|
|
78
|
+
title,
|
|
79
|
+
body,
|
|
80
|
+
isHandoff,
|
|
81
|
+
openUrl: "/?view=pma",
|
|
82
|
+
pillLabel: priority,
|
|
83
|
+
priority,
|
|
84
|
+
links: item.links || [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function getItemsForRoot(rootKey) {
|
|
88
|
+
return notificationItemsByRoot[rootKey] || [];
|
|
89
|
+
}
|
|
66
90
|
function renderDropdown(root) {
|
|
67
91
|
if (!root)
|
|
68
92
|
return;
|
|
69
|
-
|
|
93
|
+
const items = getItemsForRoot(root.key);
|
|
94
|
+
if (!items.length) {
|
|
70
95
|
root.dropdown.innerHTML = '<div class="notifications-empty muted small">No pending dispatches</div>';
|
|
71
96
|
return;
|
|
72
97
|
}
|
|
73
|
-
const html =
|
|
98
|
+
const html = items
|
|
74
99
|
.map((item, index) => {
|
|
75
|
-
const pill = item.isHandoff ? "handoff" : "paused";
|
|
100
|
+
const pill = item.pillLabel || (item.isHandoff ? "handoff" : "paused");
|
|
76
101
|
return `
|
|
77
102
|
<button class="notifications-item" type="button" data-index="${index}">
|
|
78
|
-
<span class="notifications-item-repo">${escapeHtml(item.repoDisplay)}</span>
|
|
103
|
+
<span class="notifications-item-repo">${escapeHtml(item.repoDisplay || "PMA")}</span>
|
|
79
104
|
<span class="notifications-item-title">${escapeHtml(item.title)}</span>
|
|
80
105
|
<span class="pill pill-small pill-warn notifications-item-pill">${escapeHtml(pill)}</span>
|
|
81
106
|
</button>
|
|
@@ -93,10 +118,40 @@ function closeDropdown() {
|
|
|
93
118
|
if (!activeRoot)
|
|
94
119
|
return;
|
|
95
120
|
activeRoot.dropdown.classList.add("hidden");
|
|
121
|
+
activeRoot.dropdown.style.position = "";
|
|
122
|
+
activeRoot.dropdown.style.left = "";
|
|
123
|
+
activeRoot.dropdown.style.right = "";
|
|
124
|
+
activeRoot.dropdown.style.top = "";
|
|
125
|
+
activeRoot.dropdown.style.visibility = "";
|
|
96
126
|
activeRoot.trigger.setAttribute("aria-expanded", "false");
|
|
97
127
|
activeRoot = null;
|
|
98
128
|
removeDocumentListener();
|
|
99
129
|
}
|
|
130
|
+
function positionDropdown(root) {
|
|
131
|
+
const { trigger, dropdown } = root;
|
|
132
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
133
|
+
dropdown.style.position = "fixed";
|
|
134
|
+
dropdown.style.left = "0";
|
|
135
|
+
dropdown.style.right = "auto";
|
|
136
|
+
dropdown.style.top = "0";
|
|
137
|
+
dropdown.style.visibility = "hidden";
|
|
138
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
139
|
+
const width = dropdownRect.width || 240;
|
|
140
|
+
const height = dropdownRect.height || 0;
|
|
141
|
+
const viewportWidth = window.innerWidth;
|
|
142
|
+
const viewportHeight = window.innerHeight;
|
|
143
|
+
let left = triggerRect.right - width;
|
|
144
|
+
left = Math.min(Math.max(left, DROPDOWN_MARGIN), viewportWidth - width - DROPDOWN_MARGIN);
|
|
145
|
+
const preferredTop = triggerRect.bottom + DROPDOWN_OFFSET;
|
|
146
|
+
const fallbackTop = triggerRect.top - DROPDOWN_OFFSET - height;
|
|
147
|
+
let top = preferredTop;
|
|
148
|
+
if (preferredTop + height > viewportHeight - DROPDOWN_MARGIN) {
|
|
149
|
+
top = Math.max(DROPDOWN_MARGIN, fallbackTop);
|
|
150
|
+
}
|
|
151
|
+
dropdown.style.left = `${Math.max(DROPDOWN_MARGIN, left)}px`;
|
|
152
|
+
dropdown.style.top = `${Math.max(DROPDOWN_MARGIN, top)}px`;
|
|
153
|
+
dropdown.style.visibility = "";
|
|
154
|
+
}
|
|
100
155
|
function openDropdown(root) {
|
|
101
156
|
if (activeRoot && activeRoot !== root) {
|
|
102
157
|
activeRoot.dropdown.classList.add("hidden");
|
|
@@ -105,6 +160,7 @@ function openDropdown(root) {
|
|
|
105
160
|
activeRoot = root;
|
|
106
161
|
renderDropdown(root);
|
|
107
162
|
root.dropdown.classList.remove("hidden");
|
|
163
|
+
positionDropdown(root);
|
|
108
164
|
root.trigger.setAttribute("aria-expanded", "true");
|
|
109
165
|
installDocumentListener();
|
|
110
166
|
}
|
|
@@ -141,35 +197,104 @@ function closeNotificationsModal() {
|
|
|
141
197
|
closeModalFn();
|
|
142
198
|
closeModalFn = null;
|
|
143
199
|
}
|
|
200
|
+
function isSameNotification(a, b) {
|
|
201
|
+
return (a.kind === b.kind &&
|
|
202
|
+
a.repoId === b.repoId &&
|
|
203
|
+
a.runId === b.runId &&
|
|
204
|
+
a.seq === b.seq);
|
|
205
|
+
}
|
|
144
206
|
function openNotificationsModal(item, returnFocusTo) {
|
|
145
207
|
const modal = getModalElements();
|
|
146
208
|
if (!modal)
|
|
147
209
|
return;
|
|
148
210
|
closeNotificationsModal();
|
|
149
|
-
const runLabel = item.seq ? `${item.runId.slice(0, 8)} (#${item.seq})` : item.runId.slice(0, 8);
|
|
150
|
-
const modeLabel = item.mode ? ` (${item.mode})` : "";
|
|
151
211
|
const body = item.body?.trim() ? escapeHtml(item.body) : '<span class="muted">No message body.</span>';
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
212
|
+
if (item.kind === "pma") {
|
|
213
|
+
const priority = item.priority || "info";
|
|
214
|
+
const links = (item.links || [])
|
|
215
|
+
.map((link) => `<a href="${escapeHtml(link.href)}" target="_blank" rel="noopener">${escapeHtml(link.label)}</a>`)
|
|
216
|
+
.join("");
|
|
217
|
+
const linkBlock = links ? `<div class="notifications-modal-links">${links}</div>` : "";
|
|
218
|
+
modal.body.innerHTML = `
|
|
219
|
+
<div class="notifications-modal-meta">
|
|
220
|
+
<div class="notifications-modal-row">
|
|
221
|
+
<span class="notifications-modal-label">Dispatch</span>
|
|
222
|
+
<span class="notifications-modal-value">${escapeHtml(item.title)}</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="notifications-modal-row">
|
|
225
|
+
<span class="notifications-modal-label">Priority</span>
|
|
226
|
+
<span class="notifications-modal-value">${escapeHtml(priority)}</span>
|
|
227
|
+
</div>
|
|
157
228
|
</div>
|
|
158
|
-
<div class="notifications-modal-
|
|
159
|
-
|
|
160
|
-
|
|
229
|
+
<div class="notifications-modal-body">${body}</div>
|
|
230
|
+
${linkBlock}
|
|
231
|
+
<div class="notifications-modal-actions">
|
|
232
|
+
<a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open PMA</a>
|
|
233
|
+
</div>
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const runId = item.runId || "";
|
|
238
|
+
const runLabel = item.seq ? `${runId.slice(0, 8)} (#${item.seq})` : runId.slice(0, 8);
|
|
239
|
+
const modeLabel = item.mode ? ` (${item.mode})` : "";
|
|
240
|
+
modal.body.innerHTML = `
|
|
241
|
+
<div class="notifications-modal-meta">
|
|
242
|
+
<div class="notifications-modal-row">
|
|
243
|
+
<span class="notifications-modal-label">Repo</span>
|
|
244
|
+
<span class="notifications-modal-value">${escapeHtml(item.repoDisplay || "")}</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="notifications-modal-row">
|
|
247
|
+
<span class="notifications-modal-label">Run</span>
|
|
248
|
+
<span class="notifications-modal-value mono">${escapeHtml(runLabel)}</span>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="notifications-modal-row">
|
|
251
|
+
<span class="notifications-modal-label">Dispatch</span>
|
|
252
|
+
<span class="notifications-modal-value">${escapeHtml(item.title)}${escapeHtml(modeLabel)}</span>
|
|
253
|
+
</div>
|
|
161
254
|
</div>
|
|
162
|
-
<div class="notifications-modal-
|
|
163
|
-
|
|
164
|
-
<
|
|
255
|
+
<div class="notifications-modal-body">${body}</div>
|
|
256
|
+
<div class="notifications-modal-actions">
|
|
257
|
+
<a class="primary sm notifications-open-run" href="${escapeHtml(resolvePath(item.openUrl))}">Open run</a>
|
|
258
|
+
${item.seq ? '<button class="ghost sm notifications-dismiss" type="button">Dismiss</button>' : ""}
|
|
165
259
|
</div>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
260
|
+
<div class="notifications-modal-placeholder">Reply here (coming soon).</div>
|
|
261
|
+
`;
|
|
262
|
+
const dismissBtn = modal.body.querySelector(".notifications-dismiss");
|
|
263
|
+
if (dismissBtn && item.seq) {
|
|
264
|
+
dismissBtn.addEventListener("click", async () => {
|
|
265
|
+
const confirmed = await confirmModal("Dismiss this inbox item?", {
|
|
266
|
+
confirmText: "Dismiss",
|
|
267
|
+
danger: false,
|
|
268
|
+
});
|
|
269
|
+
if (!confirmed)
|
|
270
|
+
return;
|
|
271
|
+
const reason = await inputModal("Dismiss reason (optional)", {
|
|
272
|
+
placeholder: "obsolete, resolved elsewhere, ...",
|
|
273
|
+
confirmText: "Dismiss",
|
|
274
|
+
allowEmpty: true,
|
|
275
|
+
});
|
|
276
|
+
if (reason === null)
|
|
277
|
+
return;
|
|
278
|
+
await api("/hub/messages/dismiss", {
|
|
279
|
+
method: "POST",
|
|
280
|
+
body: {
|
|
281
|
+
repo_id: item.repoId,
|
|
282
|
+
run_id: item.runId,
|
|
283
|
+
seq: item.seq,
|
|
284
|
+
reason,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
const hubItems = getItemsForRoot("hub").filter((entry) => !isSameNotification(entry, item));
|
|
288
|
+
notificationItemsByRoot.hub = hubItems;
|
|
289
|
+
setBadgeCount("hub", hubItems.length);
|
|
290
|
+
if (activeRoot && activeRoot.key === "hub") {
|
|
291
|
+
renderDropdown(activeRoot);
|
|
292
|
+
}
|
|
293
|
+
closeNotificationsModal();
|
|
294
|
+
flash("Dispatch dismissed");
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
173
298
|
closeModalFn = openModal(modal.overlay, {
|
|
174
299
|
closeOnEscape: true,
|
|
175
300
|
closeOnOverlay: true,
|
|
@@ -182,10 +307,28 @@ async function refreshNotifications(_ctx) {
|
|
|
182
307
|
return;
|
|
183
308
|
isRefreshing = true;
|
|
184
309
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
310
|
+
let hubPayload = null;
|
|
311
|
+
let pmaPayload = null;
|
|
312
|
+
try {
|
|
313
|
+
hubPayload = (await api("/hub/messages", { method: "GET" }));
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
hubPayload = null;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
pmaPayload = (await api("/hub/pma/dispatches?include_resolved=false", {
|
|
320
|
+
method: "GET",
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
pmaPayload = { items: [] };
|
|
325
|
+
}
|
|
326
|
+
const hubItems = (hubPayload?.items || []).map(normalizeHubItem);
|
|
327
|
+
const pmaItems = (pmaPayload?.items || []).map(normalizePmaItem);
|
|
328
|
+
notificationItemsByRoot.hub = hubItems;
|
|
329
|
+
notificationItemsByRoot.pma = pmaItems;
|
|
330
|
+
setBadgeCount("hub", hubItems.length);
|
|
331
|
+
setBadgeCount("pma", pmaItems.length);
|
|
189
332
|
if (activeRoot) {
|
|
190
333
|
renderDropdown(activeRoot);
|
|
191
334
|
}
|
|
@@ -215,12 +358,20 @@ function attachRoot(root) {
|
|
|
215
358
|
const target = event.target?.closest(".notifications-item");
|
|
216
359
|
if (!target)
|
|
217
360
|
return;
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
event.stopPropagation();
|
|
218
363
|
const index = Number(target.dataset.index || "-1");
|
|
219
|
-
const
|
|
364
|
+
const items = getItemsForRoot(root.key);
|
|
365
|
+
const item = items[index];
|
|
220
366
|
if (!item)
|
|
221
367
|
return;
|
|
222
368
|
closeDropdown();
|
|
223
|
-
|
|
369
|
+
const mouseEvent = event;
|
|
370
|
+
if (mouseEvent.shiftKey) {
|
|
371
|
+
openNotificationsModal(item, root.trigger);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
window.location.href = resolvePath(item.openUrl);
|
|
224
375
|
});
|
|
225
376
|
}
|
|
226
377
|
function attachModalHandlers() {
|
codex_autorunner/static/pma.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* PMA (Project Management Agent) - Hub-level chat interface
|
|
4
4
|
*/
|
|
5
|
-
import { api,
|
|
5
|
+
import { api, resolvePath, getAuthToken, flash } from "./utils.js";
|
|
6
6
|
import { createDocChat, } from "./docChatCore.js";
|
|
7
7
|
import { initChatPasteUpload } from "./chatUploads.js";
|
|
8
8
|
import { clearAgentSelectionStorage, getSelectedAgent, getSelectedModel, getSelectedReasoning, initAgentControls, refreshAgentControls, } from "./agentControls.js";
|
|
9
9
|
import { createFileBoxWidget } from "./fileboxUi.js";
|
|
10
10
|
import { extractContextRemainingPercent } from "./streamUtils.js";
|
|
11
|
+
import { initNotificationBell } from "./notificationBell.js";
|
|
11
12
|
const pmaStyling = {
|
|
12
13
|
eventClass: "chat-event",
|
|
13
14
|
eventTitleClass: "chat-event-title",
|
|
@@ -44,6 +45,7 @@ let isUnloading = false;
|
|
|
44
45
|
let unloadHandlerInstalled = false;
|
|
45
46
|
let currentEventsController = null;
|
|
46
47
|
const PMA_PENDING_TURN_KEY = "car.pma.pendingTurn";
|
|
48
|
+
const PMA_VIEW_KEY = "car.pma.view";
|
|
47
49
|
const DEFAULT_PMA_LANE_ID = "pma:default";
|
|
48
50
|
let fileBoxCtrl = null;
|
|
49
51
|
let pendingUploadNames = [];
|
|
@@ -94,6 +96,29 @@ function clearPendingTurn() {
|
|
|
94
96
|
// ignore
|
|
95
97
|
}
|
|
96
98
|
}
|
|
99
|
+
function loadPMAView() {
|
|
100
|
+
const raw = localStorage.getItem(PMA_VIEW_KEY);
|
|
101
|
+
if (raw === "memory")
|
|
102
|
+
return "memory";
|
|
103
|
+
return "chat";
|
|
104
|
+
}
|
|
105
|
+
function setPMAView(view, options = {}) {
|
|
106
|
+
const elements = getElements();
|
|
107
|
+
const { persist = true } = options;
|
|
108
|
+
if (persist) {
|
|
109
|
+
localStorage.setItem(PMA_VIEW_KEY, view);
|
|
110
|
+
}
|
|
111
|
+
if (elements.shell) {
|
|
112
|
+
elements.shell.setAttribute("data-pma-view", view);
|
|
113
|
+
}
|
|
114
|
+
document.querySelectorAll(".pma-view-btn").forEach((btn) => {
|
|
115
|
+
const isActive = btn.dataset.view === view;
|
|
116
|
+
btn.classList.toggle("active", isActive);
|
|
117
|
+
btn.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
118
|
+
});
|
|
119
|
+
elements.chatSection?.classList.toggle("hidden", view !== "chat");
|
|
120
|
+
elements.docsSection?.classList.toggle("hidden", view !== "memory");
|
|
121
|
+
}
|
|
97
122
|
async function initFileBoxUI() {
|
|
98
123
|
const elements = getElements();
|
|
99
124
|
if (!elements.inboxFiles || !elements.outboxFiles)
|
|
@@ -124,6 +149,7 @@ async function initFileBoxUI() {
|
|
|
124
149
|
}
|
|
125
150
|
pendingUploadNames = [];
|
|
126
151
|
}
|
|
152
|
+
updateClearButtons(listing);
|
|
127
153
|
},
|
|
128
154
|
onUpload: (names) => {
|
|
129
155
|
pendingUploadNames = names;
|
|
@@ -172,11 +198,15 @@ async function loadPMADocContent(name) {
|
|
|
172
198
|
return payload?.content || "";
|
|
173
199
|
}
|
|
174
200
|
catch (err) {
|
|
201
|
+
const content = await bootstrapPMADoc(name);
|
|
202
|
+
if (content) {
|
|
203
|
+
return content;
|
|
204
|
+
}
|
|
175
205
|
flash(`Failed to load ${name}`, "error");
|
|
176
206
|
return "";
|
|
177
207
|
}
|
|
178
208
|
}
|
|
179
|
-
async function loadPMADocDefaultContent(name) {
|
|
209
|
+
async function loadPMADocDefaultContent(name, options = {}) {
|
|
180
210
|
try {
|
|
181
211
|
const payload = (await api(`/hub/pma/docs/default/${encodeURIComponent(name)}`, {
|
|
182
212
|
method: "GET",
|
|
@@ -184,10 +214,28 @@ async function loadPMADocDefaultContent(name) {
|
|
|
184
214
|
return payload?.content || "";
|
|
185
215
|
}
|
|
186
216
|
catch (err) {
|
|
187
|
-
|
|
217
|
+
if (!options.silent) {
|
|
218
|
+
flash(`Failed to load default ${name}`, "error");
|
|
219
|
+
}
|
|
188
220
|
return "";
|
|
189
221
|
}
|
|
190
222
|
}
|
|
223
|
+
async function bootstrapPMADoc(name) {
|
|
224
|
+
const content = await loadPMADocDefaultContent(name, { silent: true });
|
|
225
|
+
if (!content)
|
|
226
|
+
return "";
|
|
227
|
+
try {
|
|
228
|
+
await api(`/hub/pma/docs/${encodeURIComponent(name)}`, {
|
|
229
|
+
method: "PUT",
|
|
230
|
+
body: { content },
|
|
231
|
+
});
|
|
232
|
+
await loadPMADocs();
|
|
233
|
+
return content;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return content;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
191
239
|
async function savePMADoc(name, content) {
|
|
192
240
|
if (isSavingDoc)
|
|
193
241
|
return;
|
|
@@ -282,9 +330,8 @@ async function snapshotActiveContext() {
|
|
|
282
330
|
flash("Failed to snapshot active context", "error");
|
|
283
331
|
}
|
|
284
332
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (!confirmed)
|
|
333
|
+
function resetActiveContext() {
|
|
334
|
+
if (!confirm("Reset active context to default?"))
|
|
288
335
|
return;
|
|
289
336
|
const editor = document.getElementById("pma-docs-editor");
|
|
290
337
|
if (!editor)
|
|
@@ -356,6 +403,7 @@ async function pollForTurnMeta(clientTurnId, options = {}) {
|
|
|
356
403
|
function getElements() {
|
|
357
404
|
return {
|
|
358
405
|
shell: document.getElementById("pma-shell"),
|
|
406
|
+
chatSection: document.getElementById("pma-chat-section"),
|
|
359
407
|
input: document.getElementById("pma-chat-input"),
|
|
360
408
|
sendBtn: document.getElementById("pma-chat-send"),
|
|
361
409
|
cancelBtn: document.getElementById("pma-chat-cancel"),
|
|
@@ -376,6 +424,8 @@ function getElements() {
|
|
|
376
424
|
inboxFiles: document.getElementById("pma-inbox-files"),
|
|
377
425
|
outboxFiles: document.getElementById("pma-outbox-files"),
|
|
378
426
|
outboxRefresh: document.getElementById("pma-outbox-refresh"),
|
|
427
|
+
inboxClear: document.getElementById("pma-inbox-clear"),
|
|
428
|
+
outboxClear: document.getElementById("pma-outbox-clear"),
|
|
379
429
|
threadInfo: document.getElementById("pma-thread-info"),
|
|
380
430
|
threadInfoAgent: document.getElementById("pma-thread-info-agent"),
|
|
381
431
|
threadInfoThreadId: document.getElementById("pma-thread-info-thread-id"),
|
|
@@ -471,6 +521,8 @@ async function initPMA() {
|
|
|
471
521
|
await initFileBoxUI();
|
|
472
522
|
await loadPMADocs();
|
|
473
523
|
attachHandlers();
|
|
524
|
+
setPMAView(loadPMAView(), { persist: false });
|
|
525
|
+
initNotificationBell();
|
|
474
526
|
// If we refreshed mid-turn, recover the final output from the server.
|
|
475
527
|
await resumePendingTurn();
|
|
476
528
|
// If the page refreshes/navigates while a turn is running, avoid showing a noisy
|
|
@@ -543,6 +595,23 @@ async function loadPMAThreadInfo() {
|
|
|
543
595
|
elements.threadInfo?.classList.add("hidden");
|
|
544
596
|
}
|
|
545
597
|
}
|
|
598
|
+
function updateClearButtons(listing) {
|
|
599
|
+
const elements = getElements();
|
|
600
|
+
if (!elements.inboxClear || !elements.outboxClear)
|
|
601
|
+
return;
|
|
602
|
+
const inboxCount = listing?.inbox?.length ?? 0;
|
|
603
|
+
const outboxCount = listing?.outbox?.length ?? 0;
|
|
604
|
+
elements.inboxClear.classList.toggle("hidden", inboxCount <= 1);
|
|
605
|
+
elements.outboxClear.classList.toggle("hidden", outboxCount <= 1);
|
|
606
|
+
}
|
|
607
|
+
async function clearPMABox(box) {
|
|
608
|
+
const confirmed = window.confirm(`Clear ${box}? This will delete all files.`);
|
|
609
|
+
if (!confirmed)
|
|
610
|
+
return;
|
|
611
|
+
await api(`/hub/pma/files/${box}`, { method: "DELETE" });
|
|
612
|
+
flash(`Cleared ${box}`, "info");
|
|
613
|
+
await fileBoxCtrl?.refresh();
|
|
614
|
+
}
|
|
546
615
|
async function sendMessage() {
|
|
547
616
|
const elements = getElements();
|
|
548
617
|
if (!elements.input || !pmaChat)
|
|
@@ -1006,6 +1075,14 @@ async function startNewThreadOnServer() {
|
|
|
1006
1075
|
}
|
|
1007
1076
|
function attachHandlers() {
|
|
1008
1077
|
const elements = getElements();
|
|
1078
|
+
document.addEventListener("click", (event) => {
|
|
1079
|
+
const target = event.target;
|
|
1080
|
+
const btn = target?.closest?.(".pma-view-btn");
|
|
1081
|
+
if (!btn)
|
|
1082
|
+
return;
|
|
1083
|
+
const value = (btn.dataset.view || "chat");
|
|
1084
|
+
setPMAView(value);
|
|
1085
|
+
});
|
|
1009
1086
|
if (elements.sendBtn) {
|
|
1010
1087
|
elements.sendBtn.addEventListener("click", () => {
|
|
1011
1088
|
void sendMessage();
|
|
@@ -1066,12 +1143,22 @@ function attachHandlers() {
|
|
|
1066
1143
|
void fileBoxCtrl?.refresh();
|
|
1067
1144
|
});
|
|
1068
1145
|
}
|
|
1146
|
+
if (elements.inboxClear) {
|
|
1147
|
+
elements.inboxClear.addEventListener("click", () => {
|
|
1148
|
+
void clearPMABox("inbox");
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
if (elements.outboxClear) {
|
|
1152
|
+
elements.outboxClear.addEventListener("click", () => {
|
|
1153
|
+
void clearPMABox("outbox");
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1069
1156
|
if (elements.scanReposBtn) {
|
|
1070
1157
|
elements.scanReposBtn.addEventListener("click", async () => {
|
|
1158
|
+
const btn = elements.scanReposBtn;
|
|
1159
|
+
const originalText = btn.textContent || "";
|
|
1071
1160
|
try {
|
|
1072
|
-
const btn = elements.scanReposBtn;
|
|
1073
1161
|
btn.disabled = true;
|
|
1074
|
-
btn.textContent = "Scanning…";
|
|
1075
1162
|
await api("/hub/repos/scan", { method: "POST" });
|
|
1076
1163
|
flash("Repositories scanned", "info");
|
|
1077
1164
|
}
|
|
@@ -1079,9 +1166,8 @@ function attachHandlers() {
|
|
|
1079
1166
|
flash("Failed to scan repos", "error");
|
|
1080
1167
|
}
|
|
1081
1168
|
finally {
|
|
1082
|
-
const btn = elements.scanReposBtn;
|
|
1083
1169
|
btn.disabled = false;
|
|
1084
|
-
btn.textContent =
|
|
1170
|
+
btn.textContent = btn.textContent || originalText;
|
|
1085
1171
|
}
|
|
1086
1172
|
});
|
|
1087
1173
|
}
|
|
@@ -1132,31 +1218,6 @@ function attachHandlers() {
|
|
|
1132
1218
|
void snapshotActiveContext();
|
|
1133
1219
|
});
|
|
1134
1220
|
}
|
|
1135
|
-
const pmaModeManual = document.getElementById("pma-mode-manual");
|
|
1136
|
-
const pmaModePma = document.getElementById("pma-mode-pma");
|
|
1137
|
-
const docsSection = document.getElementById("pma-docs-section");
|
|
1138
|
-
if (pmaModeManual && pmaModePma) {
|
|
1139
|
-
const handleModeChange = (mode) => {
|
|
1140
|
-
if (!docsSection)
|
|
1141
|
-
return;
|
|
1142
|
-
if (mode === "manual") {
|
|
1143
|
-
docsSection.classList.remove("hidden");
|
|
1144
|
-
}
|
|
1145
|
-
else {
|
|
1146
|
-
docsSection.classList.add("hidden");
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
pmaModeManual.addEventListener("click", () => {
|
|
1150
|
-
if (pmaModeManual.dataset.hubMode === "manual") {
|
|
1151
|
-
handleModeChange("manual");
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
pmaModePma.addEventListener("click", () => {
|
|
1155
|
-
if (pmaModePma.dataset.hubMode === "pma") {
|
|
1156
|
-
handleModeChange("pma");
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
1221
|
if (elements.docsEditor) {
|
|
1161
1222
|
elements.docsEditor.addEventListener("input", () => {
|
|
1162
1223
|
elements.docsEditor.style.height = "auto";
|