loopengine 0.1.11 → 0.1.12

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/cli.js CHANGED
@@ -320,6 +320,9 @@ async function main() {
320
320
  console.log(`Installed into agents/${agent}/:`);
321
321
  for (const p of installed)
322
322
  console.log(` ${p}`);
323
+ console.log();
324
+ console.log('Already running under `npx loopengine dev`? The new tool becomes active automatically.');
325
+ console.log('Running under `serve` (or nothing yet)? Restart the server to pick it up.');
323
326
  }
324
327
  catch (err) {
325
328
  console.error(err instanceof Error ? err.message : String(err));
@@ -64,12 +64,30 @@ export interface FetchOptions {
64
64
  * createHttpTool does), copies each skill directory verbatim (a raw
65
65
  * cpSync, not skills-admin.ts's writeSkill — that regenerates
66
66
  * frontmatter from scratch and can't carry a package's own
67
- * frontmatter/assets), appends each actauth rule (addActauthRule), then
68
- * splices the newly-imported tools into the live agent registry so
69
- * they're callable immediately, no restart — same updateAgent call
70
- * adapters/http.ts's handleHttpToolPost already makes. All-or-nothing:
71
- * every collision check below runs, and the whole install is refused,
72
- * before a single file is written. */
67
+ * frontmatter/assets), and appends each actauth rule (addActauthRule).
68
+ *
69
+ * Deliberately does *not* attempt a live in-memory registry splice the
70
+ * way adapters/http.ts's own handleHttpToolPost does for an
71
+ * admin-created tool — that works there because the HTTP request
72
+ * creating the tool runs *inside the same process* as the already-running
73
+ * server, so updateAgent's mutation is visible to every later request in
74
+ * that same process immediately. `add-package` is a separate, one-off
75
+ * CLI process with no connection to whatever server might be running
76
+ * elsewhere — even a successful getEntry/updateAgent call here would
77
+ * only mutate *this* CLI invocation's own throwaway registry, then
78
+ * vanish the moment the process exits, having done nothing to the real
79
+ * running server. (Confirmed live: this used to import
80
+ * core/agent-registry.js, whose own discoverAgents does a top-level
81
+ * directory scan relative to *its own compiled file's location* —
82
+ * inside node_modules/loopengine when this runs there, not the
83
+ * consuming project's real agents/ at all, throwing ENOENT immediately.)
84
+ * Same reason add-agent/add-subagent above don't attempt this either —
85
+ * a new tool becomes active the same way a new agent does: the next
86
+ * request under `serve`, or automatically under `npx loopengine dev`'s
87
+ * file watcher once tools/index.ts's own edit is picked up.
88
+ *
89
+ * All-or-nothing: every collision check below runs, and the whole
90
+ * install is refused, before a single file is written. */
73
91
  export declare function installPackage(agentName: string, spec: string, options?: FetchOptions): Promise<{
74
92
  installed: string[];
75
93
  }>;
@@ -21,12 +21,9 @@ import { execFileSync } from 'node:child_process';
21
21
  import { basename, join } from 'node:path';
22
22
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
23
23
  import { tmpdir } from 'node:os';
24
- import { pathToFileURL } from 'node:url';
25
24
  import { parse as parseYaml } from 'yaml';
26
25
  import semver from 'semver';
27
26
  import { agentDir } from '../core/gateway-tools.js';
28
- import { getEntry, updateAgent } from '../core/agent-registry.js';
29
- import { loadDefaultTools } from '../core/run-agent.js';
30
27
  import { addToolToIndex, toCamelCase } from '../web/http-tool-admin.js';
31
28
  import { addActauthRule, updateActauthRule, removeActauthRule, readActauthConfig } from '../web/actauth-admin.js';
32
29
  export class PackageManifestError extends Error {
@@ -137,7 +134,20 @@ function checkLoopengineVersion(manifest, installedRangeOverride) {
137
134
  throw new PackageVersionError(`This project's package.json has no "loopengine" dependency — can't check compatibility.`);
138
135
  }
139
136
  }
140
- const floor = semver.minVersion(installedRange);
137
+ // A non-registry dependency specifier (file:, git:, workspace:, ...) —
138
+ // common for local development against an unpublished loopengine
139
+ // build — isn't a semver range at all, so minVersion throws rather
140
+ // than returning null; nothing meaningful to compare against, so skip
141
+ // the check rather than crash the whole command on it (same "nothing
142
+ // to compare, nothing to refuse" reasoning the this-repo-itself skip
143
+ // above already uses).
144
+ let floor;
145
+ try {
146
+ floor = semver.minVersion(installedRange);
147
+ }
148
+ catch {
149
+ return;
150
+ }
141
151
  if (!floor || !semver.satisfies(floor, manifest.loopengineVersion)) {
142
152
  throw new PackageVersionError(`Package '${manifest.name}' needs loopengine ${manifest.loopengineVersion}, but this project depends on loopengine ${installedRange} — bump the dependency first.`);
143
153
  }
@@ -162,12 +172,30 @@ function parseActauthRules(packageDir, manifest) {
162
172
  * createHttpTool does), copies each skill directory verbatim (a raw
163
173
  * cpSync, not skills-admin.ts's writeSkill — that regenerates
164
174
  * frontmatter from scratch and can't carry a package's own
165
- * frontmatter/assets), appends each actauth rule (addActauthRule), then
166
- * splices the newly-imported tools into the live agent registry so
167
- * they're callable immediately, no restart — same updateAgent call
168
- * adapters/http.ts's handleHttpToolPost already makes. All-or-nothing:
169
- * every collision check below runs, and the whole install is refused,
170
- * before a single file is written. */
175
+ * frontmatter/assets), and appends each actauth rule (addActauthRule).
176
+ *
177
+ * Deliberately does *not* attempt a live in-memory registry splice the
178
+ * way adapters/http.ts's own handleHttpToolPost does for an
179
+ * admin-created tool — that works there because the HTTP request
180
+ * creating the tool runs *inside the same process* as the already-running
181
+ * server, so updateAgent's mutation is visible to every later request in
182
+ * that same process immediately. `add-package` is a separate, one-off
183
+ * CLI process with no connection to whatever server might be running
184
+ * elsewhere — even a successful getEntry/updateAgent call here would
185
+ * only mutate *this* CLI invocation's own throwaway registry, then
186
+ * vanish the moment the process exits, having done nothing to the real
187
+ * running server. (Confirmed live: this used to import
188
+ * core/agent-registry.js, whose own discoverAgents does a top-level
189
+ * directory scan relative to *its own compiled file's location* —
190
+ * inside node_modules/loopengine when this runs there, not the
191
+ * consuming project's real agents/ at all, throwing ENOENT immediately.)
192
+ * Same reason add-agent/add-subagent above don't attempt this either —
193
+ * a new tool becomes active the same way a new agent does: the next
194
+ * request under `serve`, or automatically under `npx loopengine dev`'s
195
+ * file watcher once tools/index.ts's own edit is picked up.
196
+ *
197
+ * All-or-nothing: every collision check below runs, and the whole
198
+ * install is refused, before a single file is written. */
171
199
  export async function installPackage(agentName, spec, options = {}) {
172
200
  const fetch = options.fetchPackageDir ?? fetchPackageDir;
173
201
  const packageDir = fetch(spec);
@@ -215,19 +243,11 @@ export async function installPackage(agentName, spec, options = {}) {
215
243
  }
216
244
  }
217
245
  const contentHashes = {};
218
- // Write tool files, patch tools/index.ts, dynamic-import each fresh
219
- // file. currentTools is captured once, before any write — accumulate
220
- // newly-installed tools locally and splice with one final updateAgent
221
- // call, avoiding the exact double-append hazard handleHttpToolPost's
222
- // own comment already warns about for a folder-form agent with no
223
- // cached config.tools (re-reading tools/index.ts after it's already
224
- // been patched would see the just-added entry a second time).
246
+ // Write tool files and patch tools/index.ts — pure filesystem
247
+ // operations, no dynamic import and no registry interaction (see this
248
+ // function's own doc comment for why: a CLI process can't live-splice
249
+ // into a separate, already-running server).
225
250
  if (toolNames.length > 0) {
226
- const entry = getEntry(agentName);
227
- if (!entry)
228
- throw new Error(`Unknown agent '${agentName}'.`);
229
- const currentTools = entry.config.tools ?? (await loadDefaultTools(entry.config));
230
- const newTools = [];
231
251
  mkdirSync(toolsDir, { recursive: true });
232
252
  const indexPath = join(toolsDir, 'index.ts');
233
253
  for (const toolFile of toolFiles) {
@@ -243,10 +263,7 @@ export async function installPackage(agentName, spec, options = {}) {
243
263
  else {
244
264
  writeFileSync(indexPath, `import type { ToolDefinition } from 'loopengine'\nimport { ${exportName} } from './${toolName}.js'\n\nexport const tools: ToolDefinition[] = [${exportName}]\n`);
245
265
  }
246
- const mod = (await import(pathToFileURL(destPath).href));
247
- newTools.push(mod[exportName]);
248
266
  }
249
- updateAgent(agentName, { config: { tools: [...currentTools, ...newTools] } });
250
267
  }
251
268
  // Copy skill directories verbatim.
252
269
  for (const skillDir of skillDirs) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",