loopengine 0.1.39 → 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.
- package/dist/bin/ability-manager.d.ts +8 -0
- package/dist/bin/ability-manager.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/web/abilities-admin.d.ts +9 -0
- package/dist/web/abilities-admin.js +26 -0
- package/dist/web/agents-config-page.js +75 -11
- package/package.json +1 -1
|
@@ -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] = {
|
|
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
|
}
|
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,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>';
|
|
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
|
+
}
|
|
1291
1308
|
return '<tr>' +
|
|
1292
1309
|
'<td><code>' + escapeHtml(a.name) + '</code></td>' +
|
|
1293
1310
|
'<td>' + escapeHtml(a.version) + '</td>' +
|
|
1294
1311
|
'<td class="hint">' + escapeHtml((a.tools || []).join(', ')) + '</td>' +
|
|
1295
|
-
'<td
|
|
1312
|
+
'<td>' + actionHtml + '</td>' +
|
|
1296
1313
|
'</tr>';
|
|
1297
1314
|
}
|
|
1298
1315
|
|
|
1299
|
-
function renderAbilitySearchResultRow(pkg) {
|
|
1316
|
+
function renderAbilitySearchResultRow(pkg, installedNames) {
|
|
1317
|
+
var actionHtml = installedNames.indexOf(pkg.name) !== -1
|
|
1318
|
+
? '<span class="hint">Installed</span>'
|
|
1319
|
+
: '<button type="button" class="ability-install-btn" data-spec="' + escapeHtml(pkg.name) + '">Install</button>';
|
|
1300
1320
|
return '<tr>' +
|
|
1301
1321
|
'<td><code>' + escapeHtml(pkg.name) + '</code> <span class="hint">' + escapeHtml(pkg.version) + '</span></td>' +
|
|
1302
1322
|
'<td>' + escapeHtml(pkg.description || '') + '</td>' +
|
|
1303
|
-
'<td
|
|
1323
|
+
'<td>' + actionHtml + '</td>' +
|
|
1304
1324
|
'</tr>';
|
|
1305
1325
|
}
|
|
1306
1326
|
|
|
@@ -1360,8 +1380,10 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1360
1380
|
// has to be installed separately, or the tool fails at runtime
|
|
1361
1381
|
// with an opaque "Cannot find package" the very next time this
|
|
1362
1382
|
// agent's config is loaded. Surfaced here, loudly, instead of
|
|
1363
|
-
// leaving that as a silent trap discovered later
|
|
1364
|
-
|
|
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.';
|
|
1365
1387
|
}
|
|
1366
1388
|
statusEl.innerHTML = message;
|
|
1367
1389
|
loadAbilitiesTab(name);
|
|
@@ -1415,12 +1437,45 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1415
1437
|
});
|
|
1416
1438
|
}
|
|
1417
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
|
+
|
|
1418
1473
|
// Shared by the initial (empty-query) load right after the tab opens
|
|
1419
1474
|
// and the search form's own submit — the empty-query case is just
|
|
1420
1475
|
// "list every published ability" (the keyword filter alone, no extra
|
|
1421
1476
|
// search text), so an operator sees what's out there immediately
|
|
1422
1477
|
// instead of having to search for something before anything shows up.
|
|
1423
|
-
function runAbilitySearch(name, q, content, resultsEl) {
|
|
1478
|
+
function runAbilitySearch(name, q, content, resultsEl, installedNames) {
|
|
1424
1479
|
resultsEl.innerHTML = '<p class="hint">Loading…</p>';
|
|
1425
1480
|
fetch('/agents/' + encodeURIComponent(name) + '/abilities/search' + (q ? '?q=' + encodeURIComponent(q) : ''))
|
|
1426
1481
|
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
@@ -1428,7 +1483,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1428
1483
|
if (!result.ok) throw new Error(result.body.error || 'search failed');
|
|
1429
1484
|
var results = result.body.results || [];
|
|
1430
1485
|
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>'
|
|
1486
|
+
? '<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
1487
|
: '<p class="hint">No public abilities found.</p>';
|
|
1433
1488
|
var installBtns = resultsEl.querySelectorAll('.ability-install-btn');
|
|
1434
1489
|
for (var i = 0; i < installBtns.length; i++) {
|
|
@@ -1443,7 +1498,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1443
1498
|
});
|
|
1444
1499
|
}
|
|
1445
1500
|
|
|
1446
|
-
function wireAbilitiesHandlers(name) {
|
|
1501
|
+
function wireAbilitiesHandlers(name, installedNames) {
|
|
1447
1502
|
var content = abilitiesContentEl();
|
|
1448
1503
|
if (!content) return;
|
|
1449
1504
|
|
|
@@ -1456,17 +1511,25 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1456
1511
|
});
|
|
1457
1512
|
}
|
|
1458
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
|
+
|
|
1459
1522
|
var searchForm = content.querySelector('#abilitySearchForm');
|
|
1460
1523
|
var resultsEl = content.querySelector('#abilitySearchResults');
|
|
1461
1524
|
if (searchForm && resultsEl) {
|
|
1462
1525
|
searchForm.addEventListener('submit', function (ev) {
|
|
1463
1526
|
ev.preventDefault();
|
|
1464
1527
|
var q = new FormData(searchForm).get('q');
|
|
1465
|
-
runAbilitySearch(name, q, content, resultsEl);
|
|
1528
|
+
runAbilitySearch(name, q, content, resultsEl, installedNames);
|
|
1466
1529
|
});
|
|
1467
1530
|
// List every published ability immediately, not just after the
|
|
1468
1531
|
// operator explicitly searches for something.
|
|
1469
|
-
runAbilitySearch(name, '', content, resultsEl);
|
|
1532
|
+
runAbilitySearch(name, '', content, resultsEl, installedNames);
|
|
1470
1533
|
}
|
|
1471
1534
|
|
|
1472
1535
|
var installForm = content.querySelector('#abilityInstallForm');
|
|
@@ -1485,7 +1548,8 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1485
1548
|
var content = abilitiesContentEl();
|
|
1486
1549
|
if (!content) return;
|
|
1487
1550
|
content.innerHTML = renderAbilitiesConfigHtml(data);
|
|
1488
|
-
|
|
1551
|
+
var installedNames = (data.abilities || []).map(function (a) { return a.name; });
|
|
1552
|
+
wireAbilitiesHandlers(name, installedNames);
|
|
1489
1553
|
abilitiesLoadedFor = name;
|
|
1490
1554
|
}
|
|
1491
1555
|
|