loopengine 0.1.38 → 0.1.39

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,8 +57,8 @@ 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';
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
62
  export { searchPublicAbilities } 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';
package/dist/index.js CHANGED
@@ -51,7 +51,7 @@ 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';
54
+ export { installAbility, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
55
55
  export { searchPublicAbilities } 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';
@@ -1292,6 +1292,7 @@ export const agentsConfigPageHtml = `<!doctype html>
1292
1292
  '<td><code>' + escapeHtml(a.name) + '</code></td>' +
1293
1293
  '<td>' + escapeHtml(a.version) + '</td>' +
1294
1294
  '<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>' +
1295
1296
  '</tr>';
1296
1297
  }
1297
1298
 
@@ -1306,7 +1307,8 @@ export const agentsConfigPageHtml = `<!doctype html>
1306
1307
  function renderAbilitiesConfigHtml(data) {
1307
1308
  var installed = data.abilities || [];
1308
1309
  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>'
1310
+ ? '<table><thead><tr><th>Name</th><th>Version</th><th>Tools</th><th></th></tr></thead><tbody>' + installed.map(renderInstalledAbilityRow).join('') + '</tbody></table>' +
1311
+ '<p id="abilityUpgradeStatus" class="hint"></p>'
1310
1312
  : '<p class="hint">No abilities installed for this agent yet.</p>';
1311
1313
 
1312
1314
  return '<h3>Installed</h3>' + installedHtml +
@@ -1371,6 +1373,48 @@ export const agentsConfigPageHtml = `<!doctype html>
1371
1373
  });
1372
1374
  }
1373
1375
 
1376
+ // Upgrades an already-installed ability to whatever it currently
1377
+ // resolves to — same endpoint upgradeAbility (bin/ability-manager.ts)
1378
+ // backs, which three-way merges tool/skill files instead of blindly
1379
+ // overwriting a hand-edited one, so a merge conflict is a real,
1380
+ // expected outcome here, not an error — surfaced distinctly from a
1381
+ // request actually failing.
1382
+ function upgradeAbility(name, abilityName, statusEl, btn) {
1383
+ if (btn) { btn.disabled = true; }
1384
+ statusEl.className = 'hint';
1385
+ statusEl.textContent = 'Upgrading ' + abilityName + '…';
1386
+ fetch('/agents/' + encodeURIComponent(name) + '/abilities/' + encodeURIComponent(abilityName) + '/upgrade', {
1387
+ method: 'POST',
1388
+ headers: { 'content-type': 'application/json' },
1389
+ body: JSON.stringify({}),
1390
+ })
1391
+ .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
1392
+ .then(function (result) {
1393
+ if (!result.ok) throw new Error(result.body.error || 'request failed');
1394
+ var files = result.body.files || [];
1395
+ var updated = files.filter(function (f) { return f.status === 'updated'; });
1396
+ var conflicts = files.filter(function (f) { return f.status === 'conflict'; });
1397
+ var message;
1398
+ if (!files.length || (!updated.length && !conflicts.length)) {
1399
+ message = escapeHtml(abilityName) + ' is already up to date.';
1400
+ } else {
1401
+ message = 'Upgraded ' + escapeHtml(abilityName) + ': ' + escapeHtml(updated.map(function (f) { return f.path; }).join(', ') || 'no files changed') +
1402
+ '. Already running under npx loopengine dev? It becomes active automatically. Running under serve (or nothing yet)? Restart the server to pick it up.';
1403
+ if (conflicts.length) {
1404
+ 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.';
1405
+ }
1406
+ }
1407
+ statusEl.className = conflicts.length ? 'error' : 'hint';
1408
+ statusEl.innerHTML = message;
1409
+ loadAbilitiesTab(name);
1410
+ })
1411
+ .catch(function (err) {
1412
+ statusEl.className = 'error';
1413
+ statusEl.textContent = 'Could not upgrade: ' + err.message;
1414
+ if (btn) { btn.disabled = false; }
1415
+ });
1416
+ }
1417
+
1374
1418
  // Shared by the initial (empty-query) load right after the tab opens
1375
1419
  // and the search form's own submit — the empty-query case is just
1376
1420
  // "list every published ability" (the keyword filter alone, no extra
@@ -1403,6 +1447,15 @@ export const agentsConfigPageHtml = `<!doctype html>
1403
1447
  var content = abilitiesContentEl();
1404
1448
  if (!content) return;
1405
1449
 
1450
+ var upgradeStatusEl = content.querySelector('#abilityUpgradeStatus');
1451
+ var upgradeBtns = content.querySelectorAll('.ability-upgrade-btn');
1452
+ for (var u = 0; u < upgradeBtns.length; u++) {
1453
+ upgradeBtns[u].addEventListener('click', function (ev) {
1454
+ var btn = ev.currentTarget;
1455
+ upgradeAbility(name, btn.dataset.name, upgradeStatusEl, btn);
1456
+ });
1457
+ }
1458
+
1406
1459
  var searchForm = content.querySelector('#abilitySearchForm');
1407
1460
  var resultsEl = content.querySelector('#abilitySearchResults');
1408
1461
  if (searchForm && resultsEl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",