@oddessentials/agent-guild 0.5.0 → 0.6.0
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/README.md +1 -1
- package/package.json +1 -1
- package/src/manager/changelog.mjs +195 -0
- package/src/manager/main.mjs +3 -0
- package/src/manager/news.mjs +5 -5
- package/src/manager/server.mjs +27 -8
- package/web/app.js +353 -8
- package/web/art/art.css +372 -159
- package/web/art/characters/anthropic/idle.avif +0 -0
- package/web/art/characters/anthropic/idle.webp +0 -0
- package/web/art/characters/anthropic/locked.avif +0 -0
- package/web/art/characters/anthropic/locked.webp +0 -0
- package/web/art/characters/anthropic/working.avif +0 -0
- package/web/art/characters/anthropic/working.webp +0 -0
- package/web/art/characters/google/idle.avif +0 -0
- package/web/art/characters/google/idle.webp +0 -0
- package/web/art/characters/google/locked.avif +0 -0
- package/web/art/characters/google/locked.webp +0 -0
- package/web/art/characters/google/working.avif +0 -0
- package/web/art/characters/google/working.webp +0 -0
- package/web/art/characters/openai/idle.avif +0 -0
- package/web/art/characters/openai/idle.webp +0 -0
- package/web/art/characters/openai/locked.avif +0 -0
- package/web/art/characters/openai/locked.webp +0 -0
- package/web/art/characters/openai/working.avif +0 -0
- package/web/art/characters/openai/working.webp +0 -0
- package/web/art/characters/shell/idle.avif +0 -0
- package/web/art/characters/shell/idle.webp +0 -0
- package/web/art/characters/shell/locked.avif +0 -0
- package/web/art/characters/shell/locked.webp +0 -0
- package/web/art/characters/shell/working.avif +0 -0
- package/web/art/characters/shell/working.webp +0 -0
- package/web/art/characters/xai/idle.avif +0 -0
- package/web/art/characters/xai/idle.webp +0 -0
- package/web/art/characters/xai/locked.avif +0 -0
- package/web/art/characters/xai/locked.webp +0 -0
- package/web/art/characters/xai/working.avif +0 -0
- package/web/art/characters/xai/working.webp +0 -0
- package/web/art/icons/anthropic.avif +0 -0
- package/web/art/icons/anthropic.webp +0 -0
- package/web/art/icons/google.avif +0 -0
- package/web/art/icons/google.webp +0 -0
- package/web/art/icons/openai.avif +0 -0
- package/web/art/icons/openai.webp +0 -0
- package/web/art/icons/shell.avif +0 -0
- package/web/art/icons/shell.webp +0 -0
- package/web/art/icons/xai.avif +0 -0
- package/web/art/icons/xai.webp +0 -0
- package/web/art/page-light.avif +0 -0
- package/web/art/page-light.webp +0 -0
- package/web/art/page.avif +0 -0
- package/web/art/page.webp +0 -0
- package/web/art/ui/empty-state.avif +0 -0
- package/web/art/ui/empty-state.webp +0 -0
- package/web/art/ui/favicon.png +0 -0
- package/web/art/ui/frame-ornate.png +0 -0
- package/web/art/ui/guild-crest.png +0 -0
- package/web/fonts/OFL.txt +93 -0
- package/web/fonts/cinzel.woff2 +0 -0
- package/web/index.html +35 -11
- package/web/styles.css +168 -71
- package/web/art/characters/anthropic/idle.png +0 -0
- package/web/art/characters/anthropic/locked.png +0 -0
- package/web/art/characters/anthropic/working.png +0 -0
- package/web/art/characters/google/idle.png +0 -0
- package/web/art/characters/google/locked.png +0 -0
- package/web/art/characters/google/working.png +0 -0
- package/web/art/characters/openai/idle.png +0 -0
- package/web/art/characters/openai/locked.png +0 -0
- package/web/art/characters/openai/working.png +0 -0
- package/web/art/characters/shell/idle.png +0 -0
- package/web/art/characters/shell/locked.png +0 -0
- package/web/art/characters/shell/working.png +0 -0
- package/web/art/characters/xai/idle.png +0 -0
- package/web/art/characters/xai/locked.png +0 -0
- package/web/art/characters/xai/working.png +0 -0
- package/web/art/icons/anthropic.png +0 -0
- package/web/art/icons/google.png +0 -0
- package/web/art/icons/openai.png +0 -0
- package/web/art/icons/shell.png +0 -0
- package/web/art/icons/xai.png +0 -0
- package/web/art/page.png +0 -0
package/web/app.js
CHANGED
|
@@ -7,7 +7,11 @@ const ACCOUNTS_KEY = 'agentGuild.accounts';
|
|
|
7
7
|
const THEME_KEY = 'agentGuild.theme';
|
|
8
8
|
const NEWS_SEEN_KEY = 'agentGuild.newsSeen';
|
|
9
9
|
const NEWS_FILTER_KEY = 'agentGuild.newsFilter';
|
|
10
|
+
const CHANGELOG_SEEN_KEY = 'agentGuild.changelogSeen';
|
|
11
|
+
const RELEASES_URL = 'https://github.com/oddessentials/agent-guild/releases';
|
|
10
12
|
const isMac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
|
13
|
+
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
14
|
+
const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)');
|
|
11
15
|
|
|
12
16
|
const $ = (id) => document.getElementById(id);
|
|
13
17
|
const state = {
|
|
@@ -18,6 +22,7 @@ const state = {
|
|
|
18
22
|
stats: null,
|
|
19
23
|
statsFor: new Map(),
|
|
20
24
|
news: null,
|
|
25
|
+
changelog: null,
|
|
21
26
|
sessions: new Map(),
|
|
22
27
|
views: new Map(),
|
|
23
28
|
activeId: null,
|
|
@@ -67,6 +72,7 @@ function setConnection(kind, label) {
|
|
|
67
72
|
$('stop-manager').hidden = !state.connected;
|
|
68
73
|
$('restart-manager').hidden = !state.connected || !state.restartable;
|
|
69
74
|
renderUpgrade();
|
|
75
|
+
guardLeaving();
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
// ---- the running version --------------------------------------------------
|
|
@@ -82,9 +88,42 @@ function renderVersion() {
|
|
|
82
88
|
badge.hidden = !v;
|
|
83
89
|
if (!v) return;
|
|
84
90
|
const dev = isDevelopmentBuild(v);
|
|
91
|
+
const unread = unreadRelease();
|
|
92
|
+
const notes = unread ? `What’s new in v${unread}` : 'What’s new';
|
|
85
93
|
badge.textContent = dev ? 'dev' : `v${v}`;
|
|
86
|
-
badge.
|
|
87
|
-
badge.
|
|
94
|
+
badge.classList.toggle('unread', Boolean(unread));
|
|
95
|
+
badge.title = [dev ? `Development build (${v})` : `Agent Guild ${v}`, state.pid && `session manager pid ${state.pid}`, notes].filter(Boolean).join(' · ');
|
|
96
|
+
badge.setAttribute('aria-label', `${dev ? `Agent Guild development build ${v}` : `Agent Guild version ${v}`}. ${notes}${unread ? ', not read yet' : ''}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const RELEASE_VERSION = /^\d+\.\d+\.\d+$/;
|
|
100
|
+
|
|
101
|
+
function compareReleases(a, b) {
|
|
102
|
+
const [x, y] = [a, b].map((version) => version.split('.').map(Number));
|
|
103
|
+
return x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function newestRelease(versions) {
|
|
107
|
+
return versions.filter((v) => RELEASE_VERSION.test(v ?? '')).reduce((newest, v) => (newest && compareReleases(newest, v) >= 0 ? newest : v), null);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function seenRelease() {
|
|
111
|
+
const saved = load(CHANGELOG_SEEN_KEY);
|
|
112
|
+
return RELEASE_VERSION.test(saved ?? '') ? saved : changelogView.seen;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function markReleasesSeen(...versions) {
|
|
116
|
+
const newest = newestRelease([seenRelease(), ...versions]);
|
|
117
|
+
if (!newest) return;
|
|
118
|
+
changelogView.seen = newest;
|
|
119
|
+
save(CHANGELOG_SEEN_KEY, newest);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function unreadRelease() {
|
|
123
|
+
if (!RELEASE_VERSION.test(state.version ?? '')) return null;
|
|
124
|
+
if (!seenRelease()) markReleasesSeen(state.version);
|
|
125
|
+
const newest = newestRelease([state.version, state.upgrade?.pendingVersion, state.upgrade?.latestVersion]);
|
|
126
|
+
return compareReleases(newest, seenRelease()) > 0 ? newest : null;
|
|
88
127
|
}
|
|
89
128
|
|
|
90
129
|
// ---- theme ----------------------------------------------------------------
|
|
@@ -94,7 +133,8 @@ function applyTheme(theme) {
|
|
|
94
133
|
document.documentElement.dataset.theme = theme;
|
|
95
134
|
const button = $('theme-toggle');
|
|
96
135
|
const other = theme === 'dark' ? 'light' : 'dark';
|
|
97
|
-
button.textContent = theme === 'dark' ? '
|
|
136
|
+
button.textContent = theme === 'dark' ? 'Light' : 'Dark';
|
|
137
|
+
button.dataset.next = other;
|
|
98
138
|
button.title = `Switch to the ${other} theme`;
|
|
99
139
|
button.setAttribute('aria-label', `Switch to the ${other} theme`);
|
|
100
140
|
}
|
|
@@ -103,10 +143,18 @@ function currentTheme() {
|
|
|
103
143
|
return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
|
|
104
144
|
}
|
|
105
145
|
|
|
106
|
-
function toggleTheme() {
|
|
146
|
+
function toggleTheme(event) {
|
|
107
147
|
const theme = currentTheme() === 'dark' ? 'light' : 'dark';
|
|
108
148
|
save(THEME_KEY, theme);
|
|
109
|
-
applyTheme(theme);
|
|
149
|
+
if (!document.startViewTransition || reducedMotion.matches) return applyTheme(theme);
|
|
150
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
151
|
+
const x = box.left + box.width / 2;
|
|
152
|
+
const y = box.top + box.height / 2;
|
|
153
|
+
const root = document.documentElement.style;
|
|
154
|
+
root.setProperty('--reveal-x', `${Math.round(x)}px`);
|
|
155
|
+
root.setProperty('--reveal-y', `${Math.round(y)}px`);
|
|
156
|
+
root.setProperty('--reveal-r', `${Math.ceil(Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)))}px`);
|
|
157
|
+
document.startViewTransition(() => applyTheme(theme));
|
|
110
158
|
}
|
|
111
159
|
|
|
112
160
|
// ---- upgrading the manager ------------------------------------------------
|
|
@@ -130,6 +178,15 @@ function renderUpgrade() {
|
|
|
130
178
|
restart.title = pending
|
|
131
179
|
? `Agent Guild ${pending} is installed, but this manager is still ${u.version}. Restarting ends every session and starts the new version; this page reconnects by itself.`
|
|
132
180
|
: 'Stop the session manager and start it again. This ends every session; this page reconnects by itself.';
|
|
181
|
+
const panelUpgrade = $('changelog-upgrade');
|
|
182
|
+
panelUpgrade.hidden = button.hidden;
|
|
183
|
+
panelUpgrade.textContent = button.textContent;
|
|
184
|
+
panelUpgrade.title = button.title;
|
|
185
|
+
const panelRestart = $('changelog-restart');
|
|
186
|
+
panelRestart.hidden = restart.hidden || !pending;
|
|
187
|
+
panelRestart.textContent = restart.textContent;
|
|
188
|
+
panelRestart.title = restart.title;
|
|
189
|
+
$('changelog-actions').hidden = panelUpgrade.hidden && panelRestart.hidden;
|
|
133
190
|
let text = '';
|
|
134
191
|
let title = '';
|
|
135
192
|
const last = u?.lastInstall;
|
|
@@ -158,6 +215,8 @@ function setUpgrade(upgrade) {
|
|
|
158
215
|
const before = state.upgrade;
|
|
159
216
|
state.upgrade = upgrade || null;
|
|
160
217
|
renderUpgrade();
|
|
218
|
+
renderVersion();
|
|
219
|
+
if ($('changelog').open) renderChangelog();
|
|
161
220
|
const pending = state.upgrade?.pendingVersion;
|
|
162
221
|
if (pending && pending !== before?.pendingVersion) {
|
|
163
222
|
toast(state.restartable
|
|
@@ -242,9 +301,13 @@ function paintProviderIcon(el, provider) {
|
|
|
242
301
|
const FAMILIARS = ['flame', 'leaf', 'night', 'aether'];
|
|
243
302
|
|
|
244
303
|
function renderAgents(container, agents) {
|
|
304
|
+
const known = container.dataset.rendered ? new Set([...container.children].map((el) => el.dataset.agent)) : null;
|
|
305
|
+
container.dataset.rendered = 'true';
|
|
245
306
|
container.replaceChildren(...agents.map((agent) => {
|
|
246
307
|
const el = document.createElement('span');
|
|
247
308
|
el.className = `agent ${agent.status}`;
|
|
309
|
+
el.dataset.agent = agent.id;
|
|
310
|
+
if (known && !known.has(agent.id)) el.classList.add('summon');
|
|
248
311
|
el.style.setProperty('--c', `hsl(${hueFor(agent.name)} 65% 50%)`);
|
|
249
312
|
el.dataset.familiar = FAMILIARS[hueFor(agent.name) % FAMILIARS.length];
|
|
250
313
|
el.textContent = (agent.name || '?').charAt(0).toUpperCase();
|
|
@@ -327,6 +390,8 @@ function renderAccounts(card, provider) {
|
|
|
327
390
|
});
|
|
328
391
|
}
|
|
329
392
|
|
|
393
|
+
let dealt = false;
|
|
394
|
+
|
|
330
395
|
function renderProviders() {
|
|
331
396
|
const list = $('providers');
|
|
332
397
|
const tpl = $('provider-template');
|
|
@@ -371,6 +436,10 @@ function renderProviders() {
|
|
|
371
436
|
renderModelStats(node, provider);
|
|
372
437
|
return node;
|
|
373
438
|
}));
|
|
439
|
+
if (!dealt && state.providers.length) {
|
|
440
|
+
dealt = true;
|
|
441
|
+
if (!reducedMotion.matches) list.classList.add('deal');
|
|
442
|
+
}
|
|
374
443
|
}
|
|
375
444
|
|
|
376
445
|
const openCopies = new Set();
|
|
@@ -426,7 +495,7 @@ function renderHint(hint, provider) {
|
|
|
426
495
|
link.href = docs;
|
|
427
496
|
link.target = '_blank';
|
|
428
497
|
link.rel = 'noopener noreferrer';
|
|
429
|
-
link.textContent = 'Docs
|
|
498
|
+
link.textContent = 'Docs';
|
|
430
499
|
hint.append(' ', link);
|
|
431
500
|
}
|
|
432
501
|
|
|
@@ -810,7 +879,7 @@ function modelFacts(card) {
|
|
|
810
879
|
link.href = httpsHref(`https://openrouter.ai/${card.id}`) ?? '';
|
|
811
880
|
link.target = '_blank';
|
|
812
881
|
link.rel = 'noopener noreferrer';
|
|
813
|
-
link.textContent = 'OpenRouter
|
|
882
|
+
link.textContent = 'OpenRouter';
|
|
814
883
|
link.setAttribute('aria-label', `${card.name} on OpenRouter (opens in a new tab)`);
|
|
815
884
|
const facts = document.createElement('div');
|
|
816
885
|
facts.className = 'model-more';
|
|
@@ -1229,6 +1298,212 @@ function tickNews() {
|
|
|
1229
1298
|
if ($('news').open) renderNewsStatus();
|
|
1230
1299
|
}
|
|
1231
1300
|
|
|
1301
|
+
const RELEASE_BADGES = {
|
|
1302
|
+
running: ['in-use', 'Running', 'The session manager runs this version'],
|
|
1303
|
+
installed: ['installed', 'Installed', 'Installed. Restart the session manager to use it.'],
|
|
1304
|
+
newer: ['new', 'New', 'Released after the version the session manager runs'],
|
|
1305
|
+
};
|
|
1306
|
+
const changelogView = { opener: null, failed: null, seen: null };
|
|
1307
|
+
let changelogLoading = null;
|
|
1308
|
+
let changelogAgain = false;
|
|
1309
|
+
|
|
1310
|
+
function loadChangelog() {
|
|
1311
|
+
if (changelogLoading) {
|
|
1312
|
+
changelogAgain = true;
|
|
1313
|
+
return changelogLoading;
|
|
1314
|
+
}
|
|
1315
|
+
changelogLoading = api('GET', '/changelog').then((changelog) => {
|
|
1316
|
+
state.changelog = changelog;
|
|
1317
|
+
changelogView.failed = null;
|
|
1318
|
+
}, (err) => {
|
|
1319
|
+
if (err instanceof AuthError) return showAuth(err.message);
|
|
1320
|
+
changelogView.failed = state.connected ? err.message : 'the session manager is not reachable';
|
|
1321
|
+
}).finally(() => {
|
|
1322
|
+
changelogLoading = null;
|
|
1323
|
+
if ($('changelog').open) {
|
|
1324
|
+
markReleasesSeen(state.changelog?.releases[0]?.version);
|
|
1325
|
+
renderVersion();
|
|
1326
|
+
renderChangelog();
|
|
1327
|
+
}
|
|
1328
|
+
if (changelogAgain) {
|
|
1329
|
+
changelogAgain = false;
|
|
1330
|
+
loadChangelog();
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
return changelogLoading;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function openChangelog() {
|
|
1337
|
+
const dialog = $('changelog');
|
|
1338
|
+
renderChangelog();
|
|
1339
|
+
if (!dialog.open) {
|
|
1340
|
+
changelogView.opener = document.activeElement;
|
|
1341
|
+
dialog.showModal();
|
|
1342
|
+
}
|
|
1343
|
+
($('changelog-list').querySelector('a') ?? $('changelog-close')).focus();
|
|
1344
|
+
markReleasesSeen(state.version, state.upgrade?.pendingVersion, state.upgrade?.latestVersion, state.changelog?.releases[0]?.version);
|
|
1345
|
+
renderVersion();
|
|
1346
|
+
loadChangelog();
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function closeChangelog() {
|
|
1350
|
+
if ($('changelog').open) $('changelog').close();
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function releasesLink(label) {
|
|
1354
|
+
const link = document.createElement('a');
|
|
1355
|
+
link.className = 'console-link';
|
|
1356
|
+
link.href = RELEASES_URL;
|
|
1357
|
+
link.target = '_blank';
|
|
1358
|
+
link.rel = 'noopener noreferrer';
|
|
1359
|
+
link.textContent = label;
|
|
1360
|
+
link.setAttribute('aria-description', 'Opens in a new tab');
|
|
1361
|
+
return link;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function releaseStatus(version) {
|
|
1365
|
+
const running = state.version;
|
|
1366
|
+
if (!RELEASE_VERSION.test(running ?? '')) return null;
|
|
1367
|
+
const order = compareReleases(version, running);
|
|
1368
|
+
if (order <= 0) return order === 0 ? 'running' : null;
|
|
1369
|
+
const installed = state.upgrade?.pendingVersion;
|
|
1370
|
+
return RELEASE_VERSION.test(installed ?? '') && compareReleases(version, installed) <= 0 ? 'installed' : 'newer';
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function changeRun(run) {
|
|
1374
|
+
const href = run.url ? webHref(run.url) : null;
|
|
1375
|
+
let node;
|
|
1376
|
+
if (href) {
|
|
1377
|
+
node = document.createElement('a');
|
|
1378
|
+
node.className = 'console-link';
|
|
1379
|
+
node.href = href;
|
|
1380
|
+
node.target = '_blank';
|
|
1381
|
+
node.rel = 'noopener noreferrer';
|
|
1382
|
+
node.textContent = run.text;
|
|
1383
|
+
} else if (run.code) {
|
|
1384
|
+
node = document.createElement('code');
|
|
1385
|
+
node.textContent = run.text;
|
|
1386
|
+
} else {
|
|
1387
|
+
node = document.createTextNode(run.text);
|
|
1388
|
+
}
|
|
1389
|
+
if (!run.strong) return node;
|
|
1390
|
+
const strong = document.createElement('strong');
|
|
1391
|
+
strong.append(node);
|
|
1392
|
+
return strong;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function changeGroup(section) {
|
|
1396
|
+
const group = document.createElement('div');
|
|
1397
|
+
group.className = 'changelog-group';
|
|
1398
|
+
if (section.title) {
|
|
1399
|
+
const title = document.createElement('h4');
|
|
1400
|
+
title.className = 'changelog-type';
|
|
1401
|
+
title.classList.toggle('breaking', /breaking/i.test(section.title));
|
|
1402
|
+
title.textContent = section.title;
|
|
1403
|
+
group.append(title);
|
|
1404
|
+
}
|
|
1405
|
+
const list = document.createElement('ul');
|
|
1406
|
+
list.className = 'changelog-changes';
|
|
1407
|
+
list.append(...section.changes.map((runs) => {
|
|
1408
|
+
const item = document.createElement('li');
|
|
1409
|
+
item.append(...runs.map(changeRun));
|
|
1410
|
+
return item;
|
|
1411
|
+
}));
|
|
1412
|
+
group.append(list);
|
|
1413
|
+
return group;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function releaseEntry(release) {
|
|
1417
|
+
const entry = document.createElement('li');
|
|
1418
|
+
entry.className = 'changelog-release';
|
|
1419
|
+
entry.dataset.version = release.version;
|
|
1420
|
+
const heading = document.createElement('h3');
|
|
1421
|
+
heading.className = 'news-head';
|
|
1422
|
+
const href = webHref(release.url);
|
|
1423
|
+
const title = document.createElement(href ? 'a' : 'span');
|
|
1424
|
+
title.className = 'news-link';
|
|
1425
|
+
title.textContent = `v${release.version}`;
|
|
1426
|
+
if (href) {
|
|
1427
|
+
title.href = href;
|
|
1428
|
+
title.target = '_blank';
|
|
1429
|
+
title.rel = 'noopener noreferrer';
|
|
1430
|
+
title.setAttribute('aria-description', 'Opens the release on GitHub in a new tab');
|
|
1431
|
+
}
|
|
1432
|
+
heading.append(title);
|
|
1433
|
+
const status = releaseStatus(release.version);
|
|
1434
|
+
if (status) heading.append(badge(...RELEASE_BADGES[status]));
|
|
1435
|
+
entry.append(heading);
|
|
1436
|
+
if (release.publishedAt) {
|
|
1437
|
+
const time = document.createElement('time');
|
|
1438
|
+
time.className = 'news-meta';
|
|
1439
|
+
time.dateTime = release.publishedAt;
|
|
1440
|
+
time.textContent = relativeTime(release.publishedAt);
|
|
1441
|
+
time.title = new Date(release.publishedAt).toLocaleString();
|
|
1442
|
+
entry.append(time);
|
|
1443
|
+
}
|
|
1444
|
+
entry.append(...release.sections.map(changeGroup));
|
|
1445
|
+
return entry;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function changelogSummary() {
|
|
1449
|
+
const v = state.version;
|
|
1450
|
+
if (!v) return 'Agent Guild releases, newest first';
|
|
1451
|
+
if (isDevelopmentBuild(v)) return `Development build (${v}) · releases, newest first`;
|
|
1452
|
+
const u = state.upgrade;
|
|
1453
|
+
const parts = [`Running v${v}`];
|
|
1454
|
+
if (u?.pendingVersion) parts.push(`v${u.pendingVersion} installed`);
|
|
1455
|
+
const newer = state.changelog?.releases.some((release) => compareReleases(release.version, v) > 0);
|
|
1456
|
+
if (u?.available && u.latestVersion) parts.push(`v${u.latestVersion} available`);
|
|
1457
|
+
else if (!u?.pendingVersion && u?.latestVersion === v && !newer) parts.push('the latest release');
|
|
1458
|
+
return parts.join(' · ');
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function changelogNote(changelog) {
|
|
1462
|
+
if (changelog?.releases.length) return [];
|
|
1463
|
+
if (changelogView.failed) return [`Release notes could not be loaded: ${changelogView.failed}. `, releasesLink('Read them on GitHub')];
|
|
1464
|
+
if (!changelog || changelog.refreshing) return ['Loading release notes…'];
|
|
1465
|
+
if (changelog.error) return [`Release notes could not be loaded. GitHub Releases: ${changelog.error}. `, releasesLink('Read them on GitHub')];
|
|
1466
|
+
return ['No releases yet.'];
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function renderChangelogStatus(changelog) {
|
|
1470
|
+
const lines = [];
|
|
1471
|
+
if (changelog) {
|
|
1472
|
+
const updated = changelog.refreshing ? 'checking for new releases…' : changelog.okAt ? `updated ${relativeTime(changelog.okAt)}` : '';
|
|
1473
|
+
lines.push(['From GitHub Releases', updated].filter(Boolean).join(' · '));
|
|
1474
|
+
if (changelog.error && changelog.releases.length) lines.push(`The last check failed (${changelog.error}); showing notes fetched ${relativeTime(changelog.okAt)}.`);
|
|
1475
|
+
}
|
|
1476
|
+
lines.push(releasesLink('All releases on GitHub'));
|
|
1477
|
+
$('changelog-status').replaceChildren(...lines.map((content) => {
|
|
1478
|
+
const line = document.createElement('span');
|
|
1479
|
+
line.append(content);
|
|
1480
|
+
return line;
|
|
1481
|
+
}));
|
|
1482
|
+
$('changelog-status').hidden = false;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function renderChangelog() {
|
|
1486
|
+
const changelog = state.changelog;
|
|
1487
|
+
const releases = changelog?.releases ?? [];
|
|
1488
|
+
$('changelog-sub').textContent = changelogSummary();
|
|
1489
|
+
const list = $('changelog-list');
|
|
1490
|
+
const body = list.parentElement;
|
|
1491
|
+
const top = body.scrollTop;
|
|
1492
|
+
const entry = document.activeElement?.closest?.('#changelog-list .changelog-release');
|
|
1493
|
+
const focus = entry && { version: entry.dataset.version, index: [...entry.querySelectorAll('a')].indexOf(document.activeElement) };
|
|
1494
|
+
list.replaceChildren(...releases.map(releaseEntry));
|
|
1495
|
+
list.hidden = releases.length === 0;
|
|
1496
|
+
const note = changelogNote(changelog);
|
|
1497
|
+
$('changelog-note').replaceChildren(...note);
|
|
1498
|
+
$('changelog-note').hidden = note.length === 0;
|
|
1499
|
+
renderChangelogStatus(changelog);
|
|
1500
|
+
body.scrollTop = top;
|
|
1501
|
+
if (focus) {
|
|
1502
|
+
const again = [...list.children].find((el) => el.dataset.version === focus.version);
|
|
1503
|
+
(again?.querySelectorAll('a')[focus.index] ?? again?.querySelector('a') ?? $('changelog-close')).focus({ preventScroll: true });
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1232
1507
|
const CHANNEL_LABELS = {
|
|
1233
1508
|
npm: 'npm', native: 'native', brew: 'Homebrew', winget: 'WinGet', legacy: 'legacy install', unknown: 'unknown install',
|
|
1234
1509
|
};
|
|
@@ -1510,6 +1785,7 @@ function resumeById(event) {
|
|
|
1510
1785
|
// ---- session cards --------------------------------------------------------
|
|
1511
1786
|
|
|
1512
1787
|
const cards = new Map();
|
|
1788
|
+
let sessionsShown = false;
|
|
1513
1789
|
|
|
1514
1790
|
const MODEL_SOURCES = { report: 'reported by the tool', screen: 'seen on the tool\'s screen', args: 'from the --model argument' };
|
|
1515
1791
|
|
|
@@ -1553,6 +1829,10 @@ function buildCard(session) {
|
|
|
1553
1829
|
if (id) copyId(id);
|
|
1554
1830
|
});
|
|
1555
1831
|
node.querySelector('.model-pill').addEventListener('click', () => openSessionModel(session.id));
|
|
1832
|
+
node.addEventListener('animationend', (e) => {
|
|
1833
|
+
if (e.animationName === 'level-up') e.target.classList.remove('level-up');
|
|
1834
|
+
else if (e.animationName === 'card-enter' && e.target === node) node.classList.remove('enter');
|
|
1835
|
+
});
|
|
1556
1836
|
return node;
|
|
1557
1837
|
}
|
|
1558
1838
|
|
|
@@ -1584,6 +1864,7 @@ function updateCard(node, s) {
|
|
|
1584
1864
|
paintProviderIcon(node.querySelector('.provider-icon'), s.provider);
|
|
1585
1865
|
const level = sessionLevel(s);
|
|
1586
1866
|
const badge = node.querySelector('.level-badge');
|
|
1867
|
+
if (badge.textContent && Number(badge.textContent) < level) badge.classList.add('level-up');
|
|
1587
1868
|
badge.textContent = level;
|
|
1588
1869
|
badge.title = `Level ${level}`;
|
|
1589
1870
|
node.querySelector('.name').textContent = s.name;
|
|
@@ -1624,7 +1905,11 @@ function renderSessions() {
|
|
|
1624
1905
|
}
|
|
1625
1906
|
sessions.forEach((s, index) => {
|
|
1626
1907
|
let node = cards.get(s.id);
|
|
1627
|
-
if (!node) {
|
|
1908
|
+
if (!node) {
|
|
1909
|
+
node = buildCard(s);
|
|
1910
|
+
cards.set(s.id, node);
|
|
1911
|
+
if (sessionsShown) node.classList.add('enter');
|
|
1912
|
+
}
|
|
1628
1913
|
updateCard(node, s);
|
|
1629
1914
|
// Move a card only when it is out of place: re-inserting a node drops
|
|
1630
1915
|
// keyboard focus and can swallow a click that is in progress.
|
|
@@ -1633,11 +1918,23 @@ function renderSessions() {
|
|
|
1633
1918
|
const running = sessions.filter((s) => s.status === 'running').length;
|
|
1634
1919
|
$('session-count').textContent = sessions.length ? `· ${running} running` : '';
|
|
1635
1920
|
$('empty').hidden = sessions.length > 0;
|
|
1921
|
+
guardLeaving();
|
|
1636
1922
|
if (state.activeId) updatePanel();
|
|
1637
1923
|
if ($('history').open) renderHistory();
|
|
1638
1924
|
if (state.stats && sessions.some((s) => s.model && state.statsFor.get(s.id) !== modelKey(s))) scheduleStats();
|
|
1639
1925
|
}
|
|
1640
1926
|
|
|
1927
|
+
function confirmLeaving(event) {
|
|
1928
|
+
event.preventDefault();
|
|
1929
|
+
event.returnValue = true;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function guardLeaving() {
|
|
1933
|
+
const running = state.connected && [...state.sessions.values()].some((s) => s.status === 'running');
|
|
1934
|
+
if (running) addEventListener('beforeunload', confirmLeaving);
|
|
1935
|
+
else removeEventListener('beforeunload', confirmLeaving);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1641
1938
|
function upsertSession(session) {
|
|
1642
1939
|
state.sessions.set(session.id, session);
|
|
1643
1940
|
renderSessions();
|
|
@@ -1908,6 +2205,7 @@ function enterStopping(running = 0, restart = false) {
|
|
|
1908
2205
|
closePanel();
|
|
1909
2206
|
closeModels();
|
|
1910
2207
|
closeNews();
|
|
2208
|
+
closeChangelog();
|
|
1911
2209
|
closeHistory();
|
|
1912
2210
|
for (const view of state.views.values()) view.dispose();
|
|
1913
2211
|
state.views.clear();
|
|
@@ -2022,10 +2320,15 @@ function connectEvents() {
|
|
|
2022
2320
|
state.sessions = new Map(msg.sessions.map((s) => [s.id, s]));
|
|
2023
2321
|
for (const id of [...state.views.keys()]) if (!state.sessions.has(id)) dropSession(id);
|
|
2024
2322
|
renderSessions();
|
|
2323
|
+
sessionsShown = true;
|
|
2025
2324
|
setUpgrade(msg.upgrade);
|
|
2026
2325
|
loadNews();
|
|
2326
|
+
// A changelog.updated sent while the socket was down is lost; catch up the open panel.
|
|
2327
|
+
if ($('changelog').open) loadChangelog();
|
|
2027
2328
|
} else if (msg.type === 'news.updated') {
|
|
2028
2329
|
loadNews();
|
|
2330
|
+
} else if (msg.type === 'changelog.updated') {
|
|
2331
|
+
if ($('changelog').open) loadChangelog();
|
|
2029
2332
|
} else if (msg.type === 'manager.upgrade') {
|
|
2030
2333
|
setUpgrade(msg.upgrade);
|
|
2031
2334
|
} else if (msg.type === 'manager.stopping') {
|
|
@@ -2085,6 +2388,7 @@ let newsTimer;
|
|
|
2085
2388
|
function showAuth(message = '') {
|
|
2086
2389
|
closeModels();
|
|
2087
2390
|
closeNews();
|
|
2391
|
+
closeChangelog();
|
|
2088
2392
|
closeHistory();
|
|
2089
2393
|
$('app').hidden = true;
|
|
2090
2394
|
$('terminal-panel').hidden = true;
|
|
@@ -2192,6 +2496,23 @@ $('news').addEventListener('close', () => {
|
|
|
2192
2496
|
document.addEventListener('visibilitychange', () => {
|
|
2193
2497
|
if (document.visibilityState === 'visible' && state.connected && Date.now() - newsLoadedAt > 60000) loadNews();
|
|
2194
2498
|
});
|
|
2499
|
+
$('version').addEventListener('click', openChangelog);
|
|
2500
|
+
$('changelog-close').addEventListener('click', closeChangelog);
|
|
2501
|
+
$('changelog').addEventListener('click', (e) => { if (e.target === $('changelog')) closeChangelog(); });
|
|
2502
|
+
$('changelog').addEventListener('close', () => {
|
|
2503
|
+
const opener = changelogView.opener?.isConnected && !changelogView.opener.closest('[hidden]') ? changelogView.opener : $('version');
|
|
2504
|
+
opener.focus();
|
|
2505
|
+
changelogView.opener = null;
|
|
2506
|
+
});
|
|
2507
|
+
$('changelog-upgrade').addEventListener('click', () => {
|
|
2508
|
+
closeChangelog();
|
|
2509
|
+
upgradeManager();
|
|
2510
|
+
});
|
|
2511
|
+
$('changelog-restart').addEventListener('click', () => {
|
|
2512
|
+
closeChangelog();
|
|
2513
|
+
stopManager({ restart: true });
|
|
2514
|
+
});
|
|
2515
|
+
addEventListener('storage', (e) => { if (e.key === CHANGELOG_SEEN_KEY) renderVersion(); });
|
|
2195
2516
|
$('models-more').addEventListener('click', () => {
|
|
2196
2517
|
const before = $('models-list').childElementCount;
|
|
2197
2518
|
modelsView.all = true;
|
|
@@ -2203,6 +2524,30 @@ $('restart-manager').addEventListener('click', () => stopManager({ restart: true
|
|
|
2203
2524
|
$('copy-command').addEventListener('click', copyCommand);
|
|
2204
2525
|
$('upgrade').addEventListener('click', upgradeManager);
|
|
2205
2526
|
$('theme-toggle').addEventListener('click', toggleTheme);
|
|
2527
|
+
$('providers').addEventListener('animationend', (e) => {
|
|
2528
|
+
if (e.animationName === 'deal' && e.target === e.currentTarget.lastElementChild) e.currentTarget.classList.remove('deal');
|
|
2529
|
+
});
|
|
2530
|
+
let tiltFrame = 0;
|
|
2531
|
+
$('providers').addEventListener('pointermove', (e) => {
|
|
2532
|
+
const card = e.target.closest('.provider');
|
|
2533
|
+
if (!card || !finePointer.matches || reducedMotion.matches) return;
|
|
2534
|
+
const box = card.getBoundingClientRect();
|
|
2535
|
+
const x = ((e.clientX - box.left) / box.width) * 2 - 1;
|
|
2536
|
+
const y = ((e.clientY - box.top) / box.height) * 2 - 1;
|
|
2537
|
+
cancelAnimationFrame(tiltFrame);
|
|
2538
|
+
tiltFrame = requestAnimationFrame(() => {
|
|
2539
|
+
card.style.setProperty('--px', x.toFixed(3));
|
|
2540
|
+
card.style.setProperty('--py', y.toFixed(3));
|
|
2541
|
+
});
|
|
2542
|
+
});
|
|
2543
|
+
$('providers').addEventListener('pointerout', (e) => {
|
|
2544
|
+
const card = e.target.closest('.provider');
|
|
2545
|
+
if (card && !card.contains(e.relatedTarget)) {
|
|
2546
|
+
cancelAnimationFrame(tiltFrame);
|
|
2547
|
+
card.style.removeProperty('--px');
|
|
2548
|
+
card.style.removeProperty('--py');
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2206
2551
|
applyTheme(currentTheme());
|
|
2207
2552
|
// Follow the system setting until the user picks a theme.
|
|
2208
2553
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|