loopengine 0.1.38 → 0.1.41
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.d.ts
CHANGED
|
@@ -57,9 +57,9 @@ export { listDeclaredEnvVars, setEnvVar, EnvVarNameError } from './web/env-admin
|
|
|
57
57
|
export type { DeclaredEnvVar } from './web/env-admin.js';
|
|
58
58
|
export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
|
|
59
59
|
export type { ActauthRuleInput, ActauthConfigView } from './web/actauth-admin.js';
|
|
60
|
-
export { installAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, } from './bin/ability-manager.js';
|
|
61
|
-
export type { InstalledAbilityRecord, AbilityEnvDecl } from './bin/ability-manager.js';
|
|
62
|
-
export { searchPublicAbilities } from './web/abilities-admin.js';
|
|
60
|
+
export { installAbility, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
61
|
+
export type { InstalledAbilityRecord, AbilityEnvDecl, UpgradeFileResult } from './bin/ability-manager.js';
|
|
62
|
+
export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilities-admin.js';
|
|
63
63
|
export type { PublicAbilitySearchResult } from './web/abilities-admin.js';
|
|
64
64
|
export { describeModelProviders, describeGateways } from './web/global-config.js';
|
|
65
65
|
export type { ModelProviderInfo, AgentModelUsage, ModelsView, GatewayProviderInfo, GatewaysView } from './web/global-config.js';
|
package/dist/index.js
CHANGED
|
@@ -51,8 +51,8 @@ export { playgroundHtml } from './web/playground.js';
|
|
|
51
51
|
export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundError } from './web/skills-admin.js';
|
|
52
52
|
export { listDeclaredEnvVars, setEnvVar, EnvVarNameError } from './web/env-admin.js';
|
|
53
53
|
export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
|
|
54
|
-
export { installAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, } from './bin/ability-manager.js';
|
|
55
|
-
export { searchPublicAbilities } from './web/abilities-admin.js';
|
|
54
|
+
export { installAbility, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
55
|
+
export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilities-admin.js';
|
|
56
56
|
export { describeModelProviders, describeGateways } from './web/global-config.js';
|
|
57
57
|
export { createTrackedApprover, listApprovals, decideApproval, findApproval } from './web/web-approver.js';
|
|
58
58
|
export { scaffoldAgent, scaffoldSubagent, AgentNameError, AgentExistsError, AgentNotFoundError, AgentModelError } from './bin/cli.js';
|
|
@@ -4,3 +4,12 @@ export interface PublicAbilitySearchResult {
|
|
|
4
4
|
version: string;
|
|
5
5
|
}
|
|
6
6
|
export declare function searchPublicAbilities(query?: string): Promise<PublicAbilitySearchResult[]>;
|
|
7
|
+
/** The latest version currently published on the public npm registry for
|
|
8
|
+
* `packageName`, or null if it can't be determined — not published
|
|
9
|
+
* there at all (a private/file:/git-installed ability, the common case
|
|
10
|
+
* during local development), a network hiccup, or a timeout. Best-effort
|
|
11
|
+
* only: the Abilities tab uses this to decide whether an installed
|
|
12
|
+
* ability's own "Upgrade" button is worth showing, not to block
|
|
13
|
+
* anything, so a failure here surfaces as "can't tell, show the button
|
|
14
|
+
* anyway" rather than an error the operator has to deal with. */
|
|
15
|
+
export declare function fetchLatestAbilityVersion(packageName: string): Promise<string | null>;
|
|
@@ -38,3 +38,29 @@ export async function searchPublicAbilities(query) {
|
|
|
38
38
|
const needle = query.toLowerCase();
|
|
39
39
|
return results.filter((r) => r.name.toLowerCase().includes(needle) || r.description.toLowerCase().includes(needle));
|
|
40
40
|
}
|
|
41
|
+
const NPM_LATEST_VERSION_TIMEOUT_MS = 5000;
|
|
42
|
+
/** The latest version currently published on the public npm registry for
|
|
43
|
+
* `packageName`, or null if it can't be determined — not published
|
|
44
|
+
* there at all (a private/file:/git-installed ability, the common case
|
|
45
|
+
* during local development), a network hiccup, or a timeout. Best-effort
|
|
46
|
+
* only: the Abilities tab uses this to decide whether an installed
|
|
47
|
+
* ability's own "Upgrade" button is worth showing, not to block
|
|
48
|
+
* anything, so a failure here surfaces as "can't tell, show the button
|
|
49
|
+
* anyway" rather than an error the operator has to deal with. */
|
|
50
|
+
export async function fetchLatestAbilityVersion(packageName) {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), NPM_LATEST_VERSION_TIMEOUT_MS);
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, { signal: controller.signal });
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
return null;
|
|
57
|
+
const data = (await res.json());
|
|
58
|
+
return typeof data.version === 'string' ? data.version : null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1288,25 +1288,39 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1288
1288
|
}
|
|
1289
1289
|
|
|
1290
1290
|
function renderInstalledAbilityRow(a) {
|
|
1291
|
+
// a.upToDate only ever comes back true when the npm registry lookup
|
|
1292
|
+
// (handleAbilitiesGet's own fetchLatestAbilityVersion) actually
|
|
1293
|
+
// succeeded and matched — a private/file:/git-installed ability, or
|
|
1294
|
+
// any registry hiccup, leaves it false, so the button still shows
|
|
1295
|
+
// rather than silently hiding on an unknown instead of a genuine
|
|
1296
|
+
// "nothing to upgrade".
|
|
1297
|
+
var actionHtml = a.upToDate
|
|
1298
|
+
? '<span class="hint">up to date</span>'
|
|
1299
|
+
: '<button type="button" class="ability-upgrade-btn" data-name="' + escapeHtml(a.name) + '">Upgrade</button>';
|
|
1291
1300
|
return '<tr>' +
|
|
1292
1301
|
'<td><code>' + escapeHtml(a.name) + '</code></td>' +
|
|
1293
1302
|
'<td>' + escapeHtml(a.version) + '</td>' +
|
|
1294
1303
|
'<td class="hint">' + escapeHtml((a.tools || []).join(', ')) + '</td>' +
|
|
1304
|
+
'<td>' + actionHtml + '</td>' +
|
|
1295
1305
|
'</tr>';
|
|
1296
1306
|
}
|
|
1297
1307
|
|
|
1298
|
-
function renderAbilitySearchResultRow(pkg) {
|
|
1308
|
+
function renderAbilitySearchResultRow(pkg, installedNames) {
|
|
1309
|
+
var actionHtml = installedNames.indexOf(pkg.name) !== -1
|
|
1310
|
+
? '<span class="hint">Installed</span>'
|
|
1311
|
+
: '<button type="button" class="ability-install-btn" data-spec="' + escapeHtml(pkg.name) + '">Install</button>';
|
|
1299
1312
|
return '<tr>' +
|
|
1300
1313
|
'<td><code>' + escapeHtml(pkg.name) + '</code> <span class="hint">' + escapeHtml(pkg.version) + '</span></td>' +
|
|
1301
1314
|
'<td>' + escapeHtml(pkg.description || '') + '</td>' +
|
|
1302
|
-
'<td
|
|
1315
|
+
'<td>' + actionHtml + '</td>' +
|
|
1303
1316
|
'</tr>';
|
|
1304
1317
|
}
|
|
1305
1318
|
|
|
1306
1319
|
function renderAbilitiesConfigHtml(data) {
|
|
1307
1320
|
var installed = data.abilities || [];
|
|
1308
1321
|
var installedHtml = installed.length
|
|
1309
|
-
? '<table><thead><tr><th>Name</th><th>Version</th><th>Tools</th></tr></thead><tbody>' + installed.map(renderInstalledAbilityRow).join('') + '</tbody></table>'
|
|
1322
|
+
? '<table><thead><tr><th>Name</th><th>Version</th><th>Tools</th><th></th></tr></thead><tbody>' + installed.map(renderInstalledAbilityRow).join('') + '</tbody></table>' +
|
|
1323
|
+
'<p id="abilityUpgradeStatus" class="hint"></p>'
|
|
1310
1324
|
: '<p class="hint">No abilities installed for this agent yet.</p>';
|
|
1311
1325
|
|
|
1312
1326
|
return '<h3>Installed</h3>' + installedHtml +
|
|
@@ -1371,12 +1385,54 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1371
1385
|
});
|
|
1372
1386
|
}
|
|
1373
1387
|
|
|
1388
|
+
// Upgrades an already-installed ability to whatever it currently
|
|
1389
|
+
// resolves to — same endpoint upgradeAbility (bin/ability-manager.ts)
|
|
1390
|
+
// backs, which three-way merges tool/skill files instead of blindly
|
|
1391
|
+
// overwriting a hand-edited one, so a merge conflict is a real,
|
|
1392
|
+
// expected outcome here, not an error — surfaced distinctly from a
|
|
1393
|
+
// request actually failing.
|
|
1394
|
+
function upgradeAbility(name, abilityName, statusEl, btn) {
|
|
1395
|
+
if (btn) { btn.disabled = true; }
|
|
1396
|
+
statusEl.className = 'hint';
|
|
1397
|
+
statusEl.textContent = 'Upgrading ' + abilityName + '…';
|
|
1398
|
+
fetch('/agents/' + encodeURIComponent(name) + '/abilities/' + encodeURIComponent(abilityName) + '/upgrade', {
|
|
1399
|
+
method: 'POST',
|
|
1400
|
+
headers: { 'content-type': 'application/json' },
|
|
1401
|
+
body: JSON.stringify({}),
|
|
1402
|
+
})
|
|
1403
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
1404
|
+
.then(function (result) {
|
|
1405
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
1406
|
+
var files = result.body.files || [];
|
|
1407
|
+
var updated = files.filter(function (f) { return f.status === 'updated'; });
|
|
1408
|
+
var conflicts = files.filter(function (f) { return f.status === 'conflict'; });
|
|
1409
|
+
var message;
|
|
1410
|
+
if (!files.length || (!updated.length && !conflicts.length)) {
|
|
1411
|
+
message = escapeHtml(abilityName) + ' is already up to date.';
|
|
1412
|
+
} else {
|
|
1413
|
+
message = 'Upgraded ' + escapeHtml(abilityName) + ': ' + escapeHtml(updated.map(function (f) { return f.path; }).join(', ') || 'no files changed') +
|
|
1414
|
+
'. Already running under npx loopengine dev? It becomes active automatically. Running under serve (or nothing yet)? Restart the server to pick it up.';
|
|
1415
|
+
if (conflicts.length) {
|
|
1416
|
+
message += ' <strong>' + conflicts.length + ' file(s) need resolving by hand: ' + escapeHtml(conflicts.map(function (f) { return f.path; }).join(', ')) + '</strong> — a hand-edited file with upstream changes too, left alone rather than overwritten.';
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
statusEl.className = conflicts.length ? 'error' : 'hint';
|
|
1420
|
+
statusEl.innerHTML = message;
|
|
1421
|
+
loadAbilitiesTab(name);
|
|
1422
|
+
})
|
|
1423
|
+
.catch(function (err) {
|
|
1424
|
+
statusEl.className = 'error';
|
|
1425
|
+
statusEl.textContent = 'Could not upgrade: ' + err.message;
|
|
1426
|
+
if (btn) { btn.disabled = false; }
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1374
1430
|
// Shared by the initial (empty-query) load right after the tab opens
|
|
1375
1431
|
// and the search form's own submit — the empty-query case is just
|
|
1376
1432
|
// "list every published ability" (the keyword filter alone, no extra
|
|
1377
1433
|
// search text), so an operator sees what's out there immediately
|
|
1378
1434
|
// instead of having to search for something before anything shows up.
|
|
1379
|
-
function runAbilitySearch(name, q, content, resultsEl) {
|
|
1435
|
+
function runAbilitySearch(name, q, content, resultsEl, installedNames) {
|
|
1380
1436
|
resultsEl.innerHTML = '<p class="hint">Loading…</p>';
|
|
1381
1437
|
fetch('/agents/' + encodeURIComponent(name) + '/abilities/search' + (q ? '?q=' + encodeURIComponent(q) : ''))
|
|
1382
1438
|
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
@@ -1384,7 +1440,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1384
1440
|
if (!result.ok) throw new Error(result.body.error || 'search failed');
|
|
1385
1441
|
var results = result.body.results || [];
|
|
1386
1442
|
resultsEl.innerHTML = results.length
|
|
1387
|
-
? '<table><thead><tr><th>Package</th><th>Description</th><th></th></tr></thead><tbody>' + results.map(renderAbilitySearchResultRow).join('') + '</tbody></table>'
|
|
1443
|
+
? '<table><thead><tr><th>Package</th><th>Description</th><th></th></tr></thead><tbody>' + results.map(function (pkg) { return renderAbilitySearchResultRow(pkg, installedNames); }).join('') + '</tbody></table>'
|
|
1388
1444
|
: '<p class="hint">No public abilities found.</p>';
|
|
1389
1445
|
var installBtns = resultsEl.querySelectorAll('.ability-install-btn');
|
|
1390
1446
|
for (var i = 0; i < installBtns.length; i++) {
|
|
@@ -1399,21 +1455,30 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1399
1455
|
});
|
|
1400
1456
|
}
|
|
1401
1457
|
|
|
1402
|
-
function wireAbilitiesHandlers(name) {
|
|
1458
|
+
function wireAbilitiesHandlers(name, installedNames) {
|
|
1403
1459
|
var content = abilitiesContentEl();
|
|
1404
1460
|
if (!content) return;
|
|
1405
1461
|
|
|
1462
|
+
var upgradeStatusEl = content.querySelector('#abilityUpgradeStatus');
|
|
1463
|
+
var upgradeBtns = content.querySelectorAll('.ability-upgrade-btn');
|
|
1464
|
+
for (var u = 0; u < upgradeBtns.length; u++) {
|
|
1465
|
+
upgradeBtns[u].addEventListener('click', function (ev) {
|
|
1466
|
+
var btn = ev.currentTarget;
|
|
1467
|
+
upgradeAbility(name, btn.dataset.name, upgradeStatusEl, btn);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1406
1471
|
var searchForm = content.querySelector('#abilitySearchForm');
|
|
1407
1472
|
var resultsEl = content.querySelector('#abilitySearchResults');
|
|
1408
1473
|
if (searchForm && resultsEl) {
|
|
1409
1474
|
searchForm.addEventListener('submit', function (ev) {
|
|
1410
1475
|
ev.preventDefault();
|
|
1411
1476
|
var q = new FormData(searchForm).get('q');
|
|
1412
|
-
runAbilitySearch(name, q, content, resultsEl);
|
|
1477
|
+
runAbilitySearch(name, q, content, resultsEl, installedNames);
|
|
1413
1478
|
});
|
|
1414
1479
|
// List every published ability immediately, not just after the
|
|
1415
1480
|
// operator explicitly searches for something.
|
|
1416
|
-
runAbilitySearch(name, '', content, resultsEl);
|
|
1481
|
+
runAbilitySearch(name, '', content, resultsEl, installedNames);
|
|
1417
1482
|
}
|
|
1418
1483
|
|
|
1419
1484
|
var installForm = content.querySelector('#abilityInstallForm');
|
|
@@ -1432,7 +1497,8 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1432
1497
|
var content = abilitiesContentEl();
|
|
1433
1498
|
if (!content) return;
|
|
1434
1499
|
content.innerHTML = renderAbilitiesConfigHtml(data);
|
|
1435
|
-
|
|
1500
|
+
var installedNames = (data.abilities || []).map(function (a) { return a.name; });
|
|
1501
|
+
wireAbilitiesHandlers(name, installedNames);
|
|
1436
1502
|
abilitiesLoadedFor = name;
|
|
1437
1503
|
}
|
|
1438
1504
|
|