loopengine 0.1.41 → 0.1.42

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.
@@ -63,6 +63,14 @@ export interface InstalledAbilityRecord {
63
63
  skills: string[];
64
64
  actauthRules: string[];
65
65
  env: AbilityEnvDecl[];
66
+ /** Real npm package names (dependencies + optionalDependencies) the
67
+ * ability's own tool files import, as of the last install/upgrade —
68
+ * same values AbilityManifest.dependencies carries, persisted here so
69
+ * the Admin UI's "Install dependencies" button can read them back
70
+ * without refetching the ability from its spec. Optional only because
71
+ * an ability installed before this field existed has no recorded
72
+ * value. */
73
+ dependencies?: string[];
66
74
  /** relative path (e.g. "tools/foo.ts") -> sha256 hex, as of the last
67
75
  * install/upgrade — remove-ability's dirty-check compares against
68
76
  * this rather than storing/refetching full content to diff. */
@@ -454,6 +454,7 @@ export async function installAbility(agentName, spec, options = {}) {
454
454
  skills: skillIds,
455
455
  actauthRules: ruleNames,
456
456
  env: manifest.env ?? [],
457
+ dependencies: manifest.dependencies ?? [],
457
458
  contentHashes,
458
459
  };
459
460
  writeProvenance(agentName, provenance);
@@ -633,7 +634,14 @@ export async function upgradeAbility(agentName, abilityName, options = {}) {
633
634
  updateActauthRule(agentName, ruleName, { scope: newRule.scope, tool: newRule.tool, decision: newRule.decision });
634
635
  results.push({ path, status: 'updated' });
635
636
  }
636
- provenance[abilityName] = { ...record, version: newManifest.version, spec, contentHashes: newContentHashes, env: newManifest.env ?? record.env };
637
+ provenance[abilityName] = {
638
+ ...record,
639
+ version: newManifest.version,
640
+ spec,
641
+ contentHashes: newContentHashes,
642
+ env: newManifest.env ?? record.env,
643
+ dependencies: newManifest.dependencies ?? record.dependencies,
644
+ };
637
645
  writeProvenance(agentName, provenance);
638
646
  return { files: results };
639
647
  }
@@ -1297,6 +1297,14 @@ export const agentsConfigPageHtml = `<!doctype html>
1297
1297
  var actionHtml = a.upToDate
1298
1298
  ? '<span class="hint">up to date</span>'
1299
1299
  : '<button type="button" class="ability-upgrade-btn" data-name="' + escapeHtml(a.name) + '">Upgrade</button>';
1300
+ // Same reasoning as upToDate above — missingDependencies (see
1301
+ // hasNodeModule) only ever hides this once the server actually
1302
+ // found every dependency already present in node_modules; anything
1303
+ // it can't confirm still shows the button.
1304
+ var deps = a.missingDependencies || [];
1305
+ if (deps.length) {
1306
+ actionHtml += ' <button type="button" class="ability-install-deps-btn" data-name="' + escapeHtml(a.name) + '" title="npm install ' + escapeHtml(deps.join(' ')) + '">Install deps</button>';
1307
+ }
1300
1308
  return '<tr>' +
1301
1309
  '<td><code>' + escapeHtml(a.name) + '</code></td>' +
1302
1310
  '<td>' + escapeHtml(a.version) + '</td>' +
@@ -1372,8 +1380,10 @@ export const agentsConfigPageHtml = `<!doctype html>
1372
1380
  // has to be installed separately, or the tool fails at runtime
1373
1381
  // with an opaque "Cannot find package" the very next time this
1374
1382
  // agent's config is loaded. Surfaced here, loudly, instead of
1375
- // leaving that as a silent trap discovered later.
1376
- message += ' <strong>This ability also needs: <code>npm install ' + escapeHtml(deps.join(' ')) + '</code></strong> in your own project before it will actually work.';
1383
+ // leaving that as a silent trap discovered later — the
1384
+ // Installed table below now has its own "Install deps" button
1385
+ // for exactly this (see handleAbilityInstallDepsPost).
1386
+ message += ' <strong>This ability also needs: <code>' + escapeHtml(deps.join(', ')) + '</code></strong> — use the "Install deps" button below, or run <code>npm install ' + escapeHtml(deps.join(' ')) + '</code> yourself.';
1377
1387
  }
1378
1388
  statusEl.innerHTML = message;
1379
1389
  loadAbilitiesTab(name);
@@ -1427,6 +1437,39 @@ export const agentsConfigPageHtml = `<!doctype html>
1427
1437
  });
1428
1438
  }
1429
1439
 
1440
+ // Runs npm install for an ability's own missing dependencies
1441
+ // (handleAbilityInstallDepsPost) — the server reads the dependency
1442
+ // list back off this ability's own installed record, not anything
1443
+ // this form could tamper with, so there's nothing to pass except
1444
+ // which ability. Can take a while (a native module like sharp
1445
+ // especially), so the button stays disabled and the status line says
1446
+ // so rather than looking hung.
1447
+ function installAbilityDeps(name, abilityName, statusEl, btn) {
1448
+ if (btn) { btn.disabled = true; }
1449
+ statusEl.className = 'hint';
1450
+ statusEl.textContent = 'Running npm install for ' + abilityName + '’s dependencies… this can take a minute.';
1451
+ fetch('/agents/' + encodeURIComponent(name) + '/abilities/' + encodeURIComponent(abilityName) + '/install-deps', {
1452
+ method: 'POST',
1453
+ headers: { 'content-type': 'application/json' },
1454
+ body: JSON.stringify({}),
1455
+ })
1456
+ .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
1457
+ .then(function (result) {
1458
+ if (!result.ok) throw new Error(result.body.error || 'request failed');
1459
+ var installed = result.body.installed || [];
1460
+ statusEl.className = 'hint';
1461
+ statusEl.textContent = installed.length
1462
+ ? 'Installed: ' + installed.join(', ') + '. Restart the server to pick it up.'
1463
+ : abilityName + ' had no dependencies to install.';
1464
+ loadAbilitiesTab(name);
1465
+ })
1466
+ .catch(function (err) {
1467
+ statusEl.className = 'error';
1468
+ statusEl.textContent = 'Could not install dependencies: ' + err.message;
1469
+ if (btn) { btn.disabled = false; }
1470
+ });
1471
+ }
1472
+
1430
1473
  // Shared by the initial (empty-query) load right after the tab opens
1431
1474
  // and the search form's own submit — the empty-query case is just
1432
1475
  // "list every published ability" (the keyword filter alone, no extra
@@ -1468,6 +1511,14 @@ export const agentsConfigPageHtml = `<!doctype html>
1468
1511
  });
1469
1512
  }
1470
1513
 
1514
+ var installDepsBtns = content.querySelectorAll('.ability-install-deps-btn');
1515
+ for (var d = 0; d < installDepsBtns.length; d++) {
1516
+ installDepsBtns[d].addEventListener('click', function (ev) {
1517
+ var btn = ev.currentTarget;
1518
+ installAbilityDeps(name, btn.dataset.name, upgradeStatusEl, btn);
1519
+ });
1520
+ }
1521
+
1471
1522
  var searchForm = content.querySelector('#abilitySearchForm');
1472
1523
  var resultsEl = content.querySelector('#abilitySearchResults');
1473
1524
  if (searchForm && resultsEl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",