loopengine 0.1.41 → 0.1.43
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 +22 -0
- package/dist/bin/ability-manager.js +42 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/web/agents-config-page.js +70 -8
- 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. */
|
|
@@ -76,6 +84,20 @@ export interface InstalledAbilityRecord {
|
|
|
76
84
|
export declare function listInstalledAbilities(agentName: string): Array<{
|
|
77
85
|
name: string;
|
|
78
86
|
} & InstalledAbilityRecord>;
|
|
87
|
+
/** `record.dependencies` is only ever absent for an ability installed or
|
|
88
|
+
* last upgraded before that field existed (see InstalledAbilityRecord's
|
|
89
|
+
* own doc comment) — this re-derives it once by refetching the ability's
|
|
90
|
+
* own manifest (same technique upgradeAbility already uses for its own
|
|
91
|
+
* three-way merge base) and persists the result into provenance, so this
|
|
92
|
+
* only ever runs once per such ability rather than on every read. A
|
|
93
|
+
* record that already has `dependencies` — even a real empty array,
|
|
94
|
+
* meaning the ability genuinely declares none — is returned unchanged,
|
|
95
|
+
* never refetched. Best-effort: a spec that no longer resolves (private
|
|
96
|
+
* registry auth expired, a git ref that's gone, ...) leaves
|
|
97
|
+
* `dependencies` unset rather than throwing, since the Admin UI's own
|
|
98
|
+
* caller (handleAbilitiesGet) would rather show "can't tell" than fail
|
|
99
|
+
* the whole abilities list over one ability's stale record. */
|
|
100
|
+
export declare function backfillDependencies(agentName: string, abilityName: string, options?: FetchOptions): Promise<string[] | undefined>;
|
|
79
101
|
/** `npm pack <spec>` + tar extract — generalized from
|
|
80
102
|
* create-loopengine's own `fetchPublishedTemplateDir` (that function
|
|
81
103
|
* only ever fetches the hardcoded `create-loopengine@<version>`; this
|
|
@@ -166,6 +166,39 @@ export function listInstalledAbilities(agentName) {
|
|
|
166
166
|
function writeProvenance(agentName, data) {
|
|
167
167
|
writeFileSync(provenancePath(agentName), JSON.stringify(data, null, 2) + '\n');
|
|
168
168
|
}
|
|
169
|
+
/** `record.dependencies` is only ever absent for an ability installed or
|
|
170
|
+
* last upgraded before that field existed (see InstalledAbilityRecord's
|
|
171
|
+
* own doc comment) — this re-derives it once by refetching the ability's
|
|
172
|
+
* own manifest (same technique upgradeAbility already uses for its own
|
|
173
|
+
* three-way merge base) and persists the result into provenance, so this
|
|
174
|
+
* only ever runs once per such ability rather than on every read. A
|
|
175
|
+
* record that already has `dependencies` — even a real empty array,
|
|
176
|
+
* meaning the ability genuinely declares none — is returned unchanged,
|
|
177
|
+
* never refetched. Best-effort: a spec that no longer resolves (private
|
|
178
|
+
* registry auth expired, a git ref that's gone, ...) leaves
|
|
179
|
+
* `dependencies` unset rather than throwing, since the Admin UI's own
|
|
180
|
+
* caller (handleAbilitiesGet) would rather show "can't tell" than fail
|
|
181
|
+
* the whole abilities list over one ability's stale record. */
|
|
182
|
+
export async function backfillDependencies(agentName, abilityName, options = {}) {
|
|
183
|
+
const provenance = readProvenance(agentName);
|
|
184
|
+
const record = provenance[abilityName];
|
|
185
|
+
if (!record)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (record.dependencies !== undefined)
|
|
188
|
+
return record.dependencies;
|
|
189
|
+
const fetch = options.fetchAbilityDir ?? fetchAbilityDir;
|
|
190
|
+
try {
|
|
191
|
+
const dir = fetch(record.spec ?? abilityName);
|
|
192
|
+
const manifest = readManifest(dir);
|
|
193
|
+
record.dependencies = manifest.dependencies ?? [];
|
|
194
|
+
provenance[abilityName] = record;
|
|
195
|
+
writeProvenance(agentName, provenance);
|
|
196
|
+
return record.dependencies;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
169
202
|
function sha256(content) {
|
|
170
203
|
return createHash('sha256').update(content).digest('hex');
|
|
171
204
|
}
|
|
@@ -454,6 +487,7 @@ export async function installAbility(agentName, spec, options = {}) {
|
|
|
454
487
|
skills: skillIds,
|
|
455
488
|
actauthRules: ruleNames,
|
|
456
489
|
env: manifest.env ?? [],
|
|
490
|
+
dependencies: manifest.dependencies ?? [],
|
|
457
491
|
contentHashes,
|
|
458
492
|
};
|
|
459
493
|
writeProvenance(agentName, provenance);
|
|
@@ -633,7 +667,14 @@ export async function upgradeAbility(agentName, abilityName, options = {}) {
|
|
|
633
667
|
updateActauthRule(agentName, ruleName, { scope: newRule.scope, tool: newRule.tool, decision: newRule.decision });
|
|
634
668
|
results.push({ path, status: 'updated' });
|
|
635
669
|
}
|
|
636
|
-
provenance[abilityName] = {
|
|
670
|
+
provenance[abilityName] = {
|
|
671
|
+
...record,
|
|
672
|
+
version: newManifest.version,
|
|
673
|
+
spec,
|
|
674
|
+
contentHashes: newContentHashes,
|
|
675
|
+
env: newManifest.env ?? record.env,
|
|
676
|
+
dependencies: newManifest.dependencies ?? record.dependencies,
|
|
677
|
+
};
|
|
637
678
|
writeProvenance(agentName, provenance);
|
|
638
679
|
return { files: results };
|
|
639
680
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ 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, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
60
|
+
export { installAbility, upgradeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
61
61
|
export type { InstalledAbilityRecord, AbilityEnvDecl, UpgradeFileResult } from './bin/ability-manager.js';
|
|
62
62
|
export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilities-admin.js';
|
|
63
63
|
export type { PublicAbilitySearchResult } from './web/abilities-admin.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, upgradeAbility, listInstalledAbilities, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
54
|
+
export { installAbility, upgradeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
55
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';
|
|
@@ -75,15 +75,18 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
75
75
|
flex-direction: column;
|
|
76
76
|
}
|
|
77
77
|
#agentList { list-style: none; margin: 0; padding: 6px; flex: 1; }
|
|
78
|
-
#agentList li {
|
|
78
|
+
#agentList li { border-radius: 6px; }
|
|
79
|
+
#agentList li a {
|
|
80
|
+
display: block;
|
|
79
81
|
padding: 8px 10px;
|
|
80
82
|
border-radius: 6px;
|
|
81
|
-
cursor: pointer;
|
|
82
83
|
font-size: 13px;
|
|
83
84
|
font-family: ui-monospace, monospace;
|
|
85
|
+
color: inherit;
|
|
86
|
+
text-decoration: none;
|
|
84
87
|
}
|
|
85
|
-
#agentList li:hover { background: light-dark(#eee, #26262b); }
|
|
86
|
-
#agentList li.active { background: light-dark(#dbeafe, #1e3a5f); font-weight: 600; }
|
|
88
|
+
#agentList li a:hover { background: light-dark(#eee, #26262b); }
|
|
89
|
+
#agentList li.active a { background: light-dark(#dbeafe, #1e3a5f); font-weight: 600; }
|
|
87
90
|
main {
|
|
88
91
|
flex: 1;
|
|
89
92
|
overflow-y: auto;
|
|
@@ -1297,6 +1300,14 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1297
1300
|
var actionHtml = a.upToDate
|
|
1298
1301
|
? '<span class="hint">up to date</span>'
|
|
1299
1302
|
: '<button type="button" class="ability-upgrade-btn" data-name="' + escapeHtml(a.name) + '">Upgrade</button>';
|
|
1303
|
+
// Same reasoning as upToDate above — missingDependencies (see
|
|
1304
|
+
// hasNodeModule) only ever hides this once the server actually
|
|
1305
|
+
// found every dependency already present in node_modules; anything
|
|
1306
|
+
// it can't confirm still shows the button.
|
|
1307
|
+
var deps = a.missingDependencies || [];
|
|
1308
|
+
if (deps.length) {
|
|
1309
|
+
actionHtml += ' <button type="button" class="ability-install-deps-btn" data-name="' + escapeHtml(a.name) + '" title="npm install ' + escapeHtml(deps.join(' ')) + '">Install deps</button>';
|
|
1310
|
+
}
|
|
1300
1311
|
return '<tr>' +
|
|
1301
1312
|
'<td><code>' + escapeHtml(a.name) + '</code></td>' +
|
|
1302
1313
|
'<td>' + escapeHtml(a.version) + '</td>' +
|
|
@@ -1372,8 +1383,10 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1372
1383
|
// has to be installed separately, or the tool fails at runtime
|
|
1373
1384
|
// with an opaque "Cannot find package" the very next time this
|
|
1374
1385
|
// agent's config is loaded. Surfaced here, loudly, instead of
|
|
1375
|
-
// leaving that as a silent trap discovered later
|
|
1376
|
-
|
|
1386
|
+
// leaving that as a silent trap discovered later — the
|
|
1387
|
+
// Installed table below now has its own "Install deps" button
|
|
1388
|
+
// for exactly this (see handleAbilityInstallDepsPost).
|
|
1389
|
+
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
1390
|
}
|
|
1378
1391
|
statusEl.innerHTML = message;
|
|
1379
1392
|
loadAbilitiesTab(name);
|
|
@@ -1427,6 +1440,39 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1427
1440
|
});
|
|
1428
1441
|
}
|
|
1429
1442
|
|
|
1443
|
+
// Runs npm install for an ability's own missing dependencies
|
|
1444
|
+
// (handleAbilityInstallDepsPost) — the server reads the dependency
|
|
1445
|
+
// list back off this ability's own installed record, not anything
|
|
1446
|
+
// this form could tamper with, so there's nothing to pass except
|
|
1447
|
+
// which ability. Can take a while (a native module like sharp
|
|
1448
|
+
// especially), so the button stays disabled and the status line says
|
|
1449
|
+
// so rather than looking hung.
|
|
1450
|
+
function installAbilityDeps(name, abilityName, statusEl, btn) {
|
|
1451
|
+
if (btn) { btn.disabled = true; }
|
|
1452
|
+
statusEl.className = 'hint';
|
|
1453
|
+
statusEl.textContent = 'Running npm install for ' + abilityName + '’s dependencies… this can take a minute.';
|
|
1454
|
+
fetch('/agents/' + encodeURIComponent(name) + '/abilities/' + encodeURIComponent(abilityName) + '/install-deps', {
|
|
1455
|
+
method: 'POST',
|
|
1456
|
+
headers: { 'content-type': 'application/json' },
|
|
1457
|
+
body: JSON.stringify({}),
|
|
1458
|
+
})
|
|
1459
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
1460
|
+
.then(function (result) {
|
|
1461
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
1462
|
+
var installed = result.body.installed || [];
|
|
1463
|
+
statusEl.className = 'hint';
|
|
1464
|
+
statusEl.textContent = installed.length
|
|
1465
|
+
? 'Installed: ' + installed.join(', ') + '. Restart the server to pick it up.'
|
|
1466
|
+
: abilityName + ' had no dependencies to install.';
|
|
1467
|
+
loadAbilitiesTab(name);
|
|
1468
|
+
})
|
|
1469
|
+
.catch(function (err) {
|
|
1470
|
+
statusEl.className = 'error';
|
|
1471
|
+
statusEl.textContent = 'Could not install dependencies: ' + err.message;
|
|
1472
|
+
if (btn) { btn.disabled = false; }
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1430
1476
|
// Shared by the initial (empty-query) load right after the tab opens
|
|
1431
1477
|
// and the search form's own submit — the empty-query case is just
|
|
1432
1478
|
// "list every published ability" (the keyword filter alone, no extra
|
|
@@ -1468,6 +1514,14 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1468
1514
|
});
|
|
1469
1515
|
}
|
|
1470
1516
|
|
|
1517
|
+
var installDepsBtns = content.querySelectorAll('.ability-install-deps-btn');
|
|
1518
|
+
for (var d = 0; d < installDepsBtns.length; d++) {
|
|
1519
|
+
installDepsBtns[d].addEventListener('click', function (ev) {
|
|
1520
|
+
var btn = ev.currentTarget;
|
|
1521
|
+
installAbilityDeps(name, btn.dataset.name, upgradeStatusEl, btn);
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1471
1525
|
var searchForm = content.querySelector('#abilitySearchForm');
|
|
1472
1526
|
var resultsEl = content.querySelector('#abilitySearchResults');
|
|
1473
1527
|
if (searchForm && resultsEl) {
|
|
@@ -2461,9 +2515,17 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2461
2515
|
for (var i = 0; i < agents.length; i++) {
|
|
2462
2516
|
(function (name) {
|
|
2463
2517
|
var li = document.createElement('li');
|
|
2464
|
-
li.textContent = name;
|
|
2465
2518
|
li.dataset.name = name;
|
|
2466
|
-
|
|
2519
|
+
// A real <a href>, not a click handler calling selectAgent —
|
|
2520
|
+
// switching agents is a full navigation to this same page with
|
|
2521
|
+
// a different ?agent=, so the URL, browser back/forward, and
|
|
2522
|
+
// bookmarking all reflect which agent is open, instead of the
|
|
2523
|
+
// URL staying frozen at whatever it was on first load while
|
|
2524
|
+
// the panel silently swaps underneath it via fetch.
|
|
2525
|
+
var a = document.createElement('a');
|
|
2526
|
+
a.href = '/agents/config?agent=' + encodeURIComponent(name);
|
|
2527
|
+
a.textContent = name;
|
|
2528
|
+
li.appendChild(a);
|
|
2467
2529
|
agentList.appendChild(li);
|
|
2468
2530
|
})(agents[i].name);
|
|
2469
2531
|
}
|