loopengine 0.1.31 → 0.1.33

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
- return { ...declared, name: pkgJson.name, version: pkgJson.version };
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 { installed: [...toolNames.map((n) => `tools/${n}.ts`), ...skillIds.map((id) => `skills/${id}/`), ...ruleNames.map((n) => `actauth:${n}`)] };
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),
@@ -18,9 +18,23 @@ export async function searchPublicAbilities(query) {
18
18
  if (!res.ok)
19
19
  throw new Error(`npm registry search failed (HTTP ${res.status})`);
20
20
  const data = (await res.json());
21
- return (data.objects ?? []).map((o) => ({
21
+ const results = (data.objects ?? []).map((o) => ({
22
22
  name: o.package.name,
23
23
  description: o.package.description ?? '',
24
24
  version: o.package.version,
25
25
  }));
26
+ // npm's own search is a relevance *ranker*, not a filter — a query
27
+ // like "keywords:loopengine-ability docs" boosts a matching package's
28
+ // score but doesn't drop the non-matching ones from the response at
29
+ // all (confirmed live: every ability in this ecosystem still came
30
+ // back regardless of the extra query text, just reordered). With this
31
+ // few, brand-new, all-zero-download packages, that reordering barely
32
+ // moves, so an operator typing a search term would otherwise see the
33
+ // exact same full list every time. Filtering here on name/description
34
+ // gives the strict, expected "search box" behavior npm's own API
35
+ // doesn't provide on its own.
36
+ if (!query)
37
+ return results;
38
+ const needle = query.toLowerCase();
39
+ return results.filter((r) => r.name.toLowerCase().includes(needle) || r.description.toLowerCase().includes(needle));
26
40
  }
@@ -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
- statusEl.textContent = 'Installed: ' + 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.';
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",