loopengine 0.1.32 → 0.1.34
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.
|
@@ -27,6 +27,17 @@ export interface AbilityManifest {
|
|
|
27
27
|
skills?: string[];
|
|
28
28
|
actauth?: string;
|
|
29
29
|
env?: AbilityEnvDecl[];
|
|
30
|
+
/** Real npm package names (dependencies + optionalDependencies) the
|
|
31
|
+
* ability's own tool files import — read off package.json, not
|
|
32
|
+
* declared in loopengine.ability.json itself. installAbility only
|
|
33
|
+
* ever copies the ability's *files* into the consuming project, never
|
|
34
|
+
* touches its package.json/node_modules (see installAbility's own
|
|
35
|
+
* doc comment for why), so a tool that imports a real package like
|
|
36
|
+
* `sharp` will fail at runtime with a bare "Cannot find package"
|
|
37
|
+
* error unless the operator separately runs `npm install` for it —
|
|
38
|
+
* this is what lets a caller (the Admin UI's Abilities tab) warn
|
|
39
|
+
* about that up front instead of leaving it as a silent trap. */
|
|
40
|
+
dependencies?: string[];
|
|
30
41
|
}
|
|
31
42
|
/** One agent's record of one installed ability — the merge base a
|
|
32
43
|
* future upgrade needs, what remove needs to know is safe to delete,
|
|
@@ -112,6 +123,7 @@ export interface FetchOptions {
|
|
|
112
123
|
* install is refused, before a single file is written. */
|
|
113
124
|
export declare function installAbility(agentName: string, spec: string, options?: FetchOptions): Promise<{
|
|
114
125
|
installed: string[];
|
|
126
|
+
dependencies: string[];
|
|
115
127
|
}>;
|
|
116
128
|
export interface UpgradeFileResult {
|
|
117
129
|
path: string;
|
|
@@ -212,7 +212,8 @@ function readManifest(abilityDir) {
|
|
|
212
212
|
if (!pkgJson.name || !pkgJson.version) {
|
|
213
213
|
throw new AbilityManifestError(`${pkgJsonPath} must have "name" and "version".`);
|
|
214
214
|
}
|
|
215
|
-
|
|
215
|
+
const dependencies = [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.optionalDependencies ?? {})];
|
|
216
|
+
return { ...declared, name: pkgJson.name, version: pkgJson.version, dependencies };
|
|
216
217
|
}
|
|
217
218
|
// The installing project's own dependencies.loopengine is itself a
|
|
218
219
|
// range (e.g. "^0.1.10"), not a concrete installed version — checking
|
|
@@ -456,7 +457,10 @@ export async function installAbility(agentName, spec, options = {}) {
|
|
|
456
457
|
contentHashes,
|
|
457
458
|
};
|
|
458
459
|
writeProvenance(agentName, provenance);
|
|
459
|
-
return {
|
|
460
|
+
return {
|
|
461
|
+
installed: [...toolNames.map((n) => `tools/${n}.ts`), ...skillIds.map((id) => `skills/${id}/`), ...ruleNames.map((n) => `actauth:${n}`)],
|
|
462
|
+
dependencies: manifest.dependencies ?? [],
|
|
463
|
+
};
|
|
460
464
|
}
|
|
461
465
|
// ---- Upgrade ----
|
|
462
466
|
// git merge-file's own exit code *is* its conflict count (0 = clean),
|
|
@@ -1285,7 +1285,19 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1285
1285
|
// doesn't splice into an already-running server's in-memory
|
|
1286
1286
|
// registry any more than the CLI does (see installAbility's own
|
|
1287
1287
|
// doc comment on why), so the operator needs the same nudge here.
|
|
1288
|
-
|
|
1288
|
+
var message = 'Installed: ' + escapeHtml(result.body.installed.join(', ')) + '. Already running under npx loopengine dev? It becomes active automatically. Running under serve (or nothing yet)? Restart the server to pick it up.';
|
|
1289
|
+
var deps = result.body.dependencies || [];
|
|
1290
|
+
if (deps.length) {
|
|
1291
|
+
// installAbility only ever copies the ability's own files —
|
|
1292
|
+
// never touches package.json/node_modules — so a real npm
|
|
1293
|
+
// dependency the ability's own tool code imports (e.g. sharp)
|
|
1294
|
+
// has to be installed separately, or the tool fails at runtime
|
|
1295
|
+
// with an opaque "Cannot find package" the very next time this
|
|
1296
|
+
// agent's config is loaded. Surfaced here, loudly, instead of
|
|
1297
|
+
// leaving that as a silent trap discovered later.
|
|
1298
|
+
message += ' <strong>This ability also needs: <code>npm install ' + escapeHtml(deps.join(' ')) + '</code></strong> in your own project before it will actually work.';
|
|
1299
|
+
}
|
|
1300
|
+
statusEl.innerHTML = message;
|
|
1289
1301
|
loadAbilitiesTab(name);
|
|
1290
1302
|
})
|
|
1291
1303
|
.catch(function (err) {
|
|
@@ -2175,9 +2187,14 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2175
2187
|
// so only Overview is left needing a re-fetch.
|
|
2176
2188
|
function refreshOverviewPanel(name) {
|
|
2177
2189
|
fetch('/agents/' + encodeURIComponent(name) + '/config')
|
|
2178
|
-
.then(function (r) { return r.json(); })
|
|
2179
|
-
.then(function (
|
|
2190
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
2191
|
+
.then(function (result) {
|
|
2180
2192
|
if (currentName !== name) return;
|
|
2193
|
+
// A non-ok body is {error: "..."}, not a real config — bail out
|
|
2194
|
+
// to the catch below rather than rendering it as one (same risk
|
|
2195
|
+
// selectAgent's own /config fetch has — see its own comment).
|
|
2196
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
2197
|
+
var cfg = result.body;
|
|
2181
2198
|
currentCfg = cfg;
|
|
2182
2199
|
var overviewPanel = detail.querySelector('[data-tab-panel="overview"]');
|
|
2183
2200
|
if (overviewPanel) {
|
|
@@ -2213,9 +2230,13 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2213
2230
|
// re-rendering both it and Overview from a fresh /config fetch.
|
|
2214
2231
|
function refreshSkillsDependentPanels(name) {
|
|
2215
2232
|
fetch('/agents/' + encodeURIComponent(name) + '/config')
|
|
2216
|
-
.then(function (r) { return r.json(); })
|
|
2217
|
-
.then(function (
|
|
2233
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
2234
|
+
.then(function (result) {
|
|
2218
2235
|
if (currentName !== name) return;
|
|
2236
|
+
// Same "don't render an {error: ...} body as if it were a real
|
|
2237
|
+
// config" guard as refreshOverviewPanel/selectAgent above.
|
|
2238
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
2239
|
+
var cfg = result.body;
|
|
2219
2240
|
currentCfg = cfg;
|
|
2220
2241
|
var overviewPanel = detail.querySelector('[data-tab-panel="overview"]');
|
|
2221
2242
|
var skillsPanel = skillsPanelEl();
|
|
@@ -2283,10 +2304,18 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2283
2304
|
empty.style.display = 'none';
|
|
2284
2305
|
detail.style.display = 'block';
|
|
2285
2306
|
fetch('/agents/' + encodeURIComponent(name) + '/config')
|
|
2286
|
-
.then(function (r) { return r.json(); })
|
|
2287
|
-
.then(function (
|
|
2307
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
2308
|
+
.then(function (result) {
|
|
2288
2309
|
if (currentName !== name) return;
|
|
2289
|
-
|
|
2310
|
+
// A non-ok response's body is {error: "..."}, not a real config
|
|
2311
|
+
// — rendering it as one would crash somewhere deep in
|
|
2312
|
+
// renderDetail (e.g. reading .model.provider off an object with
|
|
2313
|
+
// no .model at all) with a confusing, unrelated-looking message
|
|
2314
|
+
// instead of the actual server-side error this route already
|
|
2315
|
+
// went out of its way to surface clearly (see handleAgentConfig
|
|
2316
|
+
// Get's own doc comment).
|
|
2317
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
2318
|
+
renderDetail(result.body);
|
|
2290
2319
|
})
|
|
2291
2320
|
.catch(function (err) {
|
|
2292
2321
|
if (currentName !== name) return;
|