@agenticmail/api 0.9.8 → 0.9.10
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.
- package/dist/index.js +13 -1
- package/package.json +1 -1
- package/public/js/activity-badges.js +33 -0
- package/public/js/list-view.js +9 -1
package/dist/index.js
CHANGED
|
@@ -1861,7 +1861,19 @@ function normalizeWakeList(value) {
|
|
|
1861
1861
|
if (value === "all" || value === WAKE_ALL_SENTINEL) return void 0;
|
|
1862
1862
|
const strip = (s) => s.trim().replace(/@localhost$/i, "").toLowerCase();
|
|
1863
1863
|
if (Array.isArray(value)) return value.map((v) => strip(String(v))).filter(Boolean);
|
|
1864
|
-
if (typeof value === "string")
|
|
1864
|
+
if (typeof value === "string") {
|
|
1865
|
+
const trimmed = value.trim();
|
|
1866
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
1867
|
+
try {
|
|
1868
|
+
const parsed = JSON.parse(trimmed);
|
|
1869
|
+
if (Array.isArray(parsed)) {
|
|
1870
|
+
return parsed.map((v) => strip(String(v))).filter(Boolean);
|
|
1871
|
+
}
|
|
1872
|
+
} catch {
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return value.split(",").map(strip).filter(Boolean);
|
|
1876
|
+
}
|
|
1865
1877
|
return void 0;
|
|
1866
1878
|
}
|
|
1867
1879
|
function deriveDefaultWakeList(toField) {
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// reflects what the agent is doing right now.
|
|
13
13
|
|
|
14
14
|
import { onSystemEvent } from './system-stream.js';
|
|
15
|
+
import { state, API_URL } from './state.js';
|
|
15
16
|
|
|
16
17
|
const BADGE_CONTAINER_ID = 'activity-badges';
|
|
17
18
|
|
|
@@ -92,6 +93,32 @@ function handleEvent(event) {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* One-shot backfill: pull the dispatcher's currently-active workers
|
|
98
|
+
* so the badges appear IMMEDIATELY on page load if anything is in
|
|
99
|
+
* flight. Without this, the user only sees badges when the next
|
|
100
|
+
* worker_heartbeat / worker_started event fires — which can be up
|
|
101
|
+
* to 30 s away (heartbeat cadence), or never if the worker happens
|
|
102
|
+
* to finish first.
|
|
103
|
+
*
|
|
104
|
+
* Failures here are silent — the SSE stream is the source of truth
|
|
105
|
+
* for subsequent updates and will paint badges as events arrive.
|
|
106
|
+
*/
|
|
107
|
+
async function backfillActiveWorkers() {
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(`${API_URL}/api/agenticmail/dispatcher/activity`, {
|
|
110
|
+
headers: { Authorization: `Bearer ${state.masterKey}` },
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok) return;
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
const active = Array.isArray(data?.active) ? data.active : [];
|
|
115
|
+
for (const w of active) {
|
|
116
|
+
if (w?.workerId) workers.set(w.workerId, w);
|
|
117
|
+
}
|
|
118
|
+
render();
|
|
119
|
+
} catch { /* silent — SSE will repaint as events come in */ }
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
/**
|
|
96
123
|
* Subscribe to worker_* events on the shared /system/events stream.
|
|
97
124
|
* Idempotent — safe to call after agent-list refresh.
|
|
@@ -103,6 +130,12 @@ export function subscribeToActivity() {
|
|
|
103
130
|
unsubWorkerStarted = onSystemEvent('worker_started', handleEvent);
|
|
104
131
|
unsubWorkerHeartbeat = onSystemEvent('worker_heartbeat', handleEvent);
|
|
105
132
|
unsubWorkerFinished = onSystemEvent('worker_finished', handleEvent);
|
|
133
|
+
// Paint whatever's already running BEFORE the first SSE event
|
|
134
|
+
// arrives. Without this, an in-flight worker stays invisible
|
|
135
|
+
// until its next heartbeat (~30 s) or until it finishes (which
|
|
136
|
+
// then never paints since worker_finished just removes the
|
|
137
|
+
// badge that never showed up).
|
|
138
|
+
backfillActiveWorkers();
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
// Tiny HTML escapers (kept local to avoid an import cycle).
|
package/public/js/list-view.js
CHANGED
|
@@ -107,7 +107,7 @@ export async function loadList(agent, folder) {
|
|
|
107
107
|
</div>
|
|
108
108
|
<div class="list-toolbar-spacer"></div>
|
|
109
109
|
<span class="count-text" id="list-count"></span>
|
|
110
|
-
<button class="icon-btn pager-btn" id="pager-prev" title="Newer"
|
|
110
|
+
<button class="icon-btn pager-btn" id="pager-prev" title="Newer"></button>
|
|
111
111
|
<button class="icon-btn pager-btn" id="pager-next" title="Older"></button>
|
|
112
112
|
</div>
|
|
113
113
|
<div class="list-rows" id="list-rows"><div class="empty">Loading…</div></div>
|
|
@@ -116,6 +116,14 @@ export async function loadList(agent, folder) {
|
|
|
116
116
|
// the head of the inbox); "Older" advances to higher offsets.
|
|
117
117
|
// Each handler clamps to valid bounds and refetches via loadList
|
|
118
118
|
// with the new offset preserved in state.pagination.
|
|
119
|
+
//
|
|
120
|
+
// Both buttons share the same `back` chevron glyph — Prev is the
|
|
121
|
+
// glyph as-is (points left), Next rotates it 180° to point right.
|
|
122
|
+
// Previously Prev had only a `data-icon="back"` attribute (no
|
|
123
|
+
// innerHTML assignment), which left it visually empty — the user
|
|
124
|
+
// could click it but couldn't see it, so on page 2+ the back
|
|
125
|
+
// button looked entirely missing.
|
|
126
|
+
document.getElementById('pager-prev').innerHTML = icon('back', { size: 18 });
|
|
119
127
|
document.getElementById('pager-next').innerHTML = icon('back', { size: 18 });
|
|
120
128
|
document.getElementById('pager-next').style.transform = 'rotate(180deg)';
|
|
121
129
|
document.getElementById('pager-prev')?.addEventListener('click', () => goToPage(agent, folder, -1));
|