loopengine 0.1.39 → 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
@@ -59,7 +59,7 @@ export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule
59
59
  export type { ActauthRuleInput, ActauthConfigView } from './web/actauth-admin.js';
60
60
  export { installAbility, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
61
61
  export type { InstalledAbilityRecord, AbilityEnvDecl, UpgradeFileResult } from './bin/ability-manager.js';
62
- export { searchPublicAbilities } from './web/abilities-admin.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
@@ -52,7 +52,7 @@ export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundE
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
54
  export { installAbility, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
55
- export { searchPublicAbilities } from './web/abilities-admin.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,19 +1288,31 @@ 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>' +
1295
- '<td><button type="button" class="ability-upgrade-btn" data-name="' + escapeHtml(a.name) + '">Upgrade</button></td>' +
1304
+ '<td>' + actionHtml + '</td>' +
1296
1305
  '</tr>';
1297
1306
  }
1298
1307
 
1299
- 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>';
1300
1312
  return '<tr>' +
1301
1313
  '<td><code>' + escapeHtml(pkg.name) + '</code> <span class="hint">' + escapeHtml(pkg.version) + '</span></td>' +
1302
1314
  '<td>' + escapeHtml(pkg.description || '') + '</td>' +
1303
- '<td><button type="button" class="ability-install-btn" data-spec="' + escapeHtml(pkg.name) + '">Install</button></td>' +
1315
+ '<td>' + actionHtml + '</td>' +
1304
1316
  '</tr>';
1305
1317
  }
1306
1318
 
@@ -1420,7 +1432,7 @@ export const agentsConfigPageHtml = `<!doctype html>
1420
1432
  // "list every published ability" (the keyword filter alone, no extra
1421
1433
  // search text), so an operator sees what's out there immediately
1422
1434
  // instead of having to search for something before anything shows up.
1423
- function runAbilitySearch(name, q, content, resultsEl) {
1435
+ function runAbilitySearch(name, q, content, resultsEl, installedNames) {
1424
1436
  resultsEl.innerHTML = '<p class="hint">Loading&hellip;</p>';
1425
1437
  fetch('/agents/' + encodeURIComponent(name) + '/abilities/search' + (q ? '?q=' + encodeURIComponent(q) : ''))
1426
1438
  .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
@@ -1428,7 +1440,7 @@ export const agentsConfigPageHtml = `<!doctype html>
1428
1440
  if (!result.ok) throw new Error(result.body.error || 'search failed');
1429
1441
  var results = result.body.results || [];
1430
1442
  resultsEl.innerHTML = results.length
1431
- ? '<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>'
1432
1444
  : '<p class="hint">No public abilities found.</p>';
1433
1445
  var installBtns = resultsEl.querySelectorAll('.ability-install-btn');
1434
1446
  for (var i = 0; i < installBtns.length; i++) {
@@ -1443,7 +1455,7 @@ export const agentsConfigPageHtml = `<!doctype html>
1443
1455
  });
1444
1456
  }
1445
1457
 
1446
- function wireAbilitiesHandlers(name) {
1458
+ function wireAbilitiesHandlers(name, installedNames) {
1447
1459
  var content = abilitiesContentEl();
1448
1460
  if (!content) return;
1449
1461
 
@@ -1462,11 +1474,11 @@ export const agentsConfigPageHtml = `<!doctype html>
1462
1474
  searchForm.addEventListener('submit', function (ev) {
1463
1475
  ev.preventDefault();
1464
1476
  var q = new FormData(searchForm).get('q');
1465
- runAbilitySearch(name, q, content, resultsEl);
1477
+ runAbilitySearch(name, q, content, resultsEl, installedNames);
1466
1478
  });
1467
1479
  // List every published ability immediately, not just after the
1468
1480
  // operator explicitly searches for something.
1469
- runAbilitySearch(name, '', content, resultsEl);
1481
+ runAbilitySearch(name, '', content, resultsEl, installedNames);
1470
1482
  }
1471
1483
 
1472
1484
  var installForm = content.querySelector('#abilityInstallForm');
@@ -1485,7 +1497,8 @@ export const agentsConfigPageHtml = `<!doctype html>
1485
1497
  var content = abilitiesContentEl();
1486
1498
  if (!content) return;
1487
1499
  content.innerHTML = renderAbilitiesConfigHtml(data);
1488
- wireAbilitiesHandlers(name);
1500
+ var installedNames = (data.abilities || []).map(function (a) { return a.name; });
1501
+ wireAbilitiesHandlers(name, installedNames);
1489
1502
  abilitiesLoadedFor = name;
1490
1503
  }
1491
1504
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",