loopengine 0.1.14 → 0.1.15
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.
|
@@ -24,7 +24,7 @@ import { tmpdir } from 'node:os';
|
|
|
24
24
|
import { parse as parseYaml } from 'yaml';
|
|
25
25
|
import semver from 'semver';
|
|
26
26
|
import { agentDir } from '../core/gateway-tools.js';
|
|
27
|
-
import { addToolToIndex, toCamelCase } from '../web/http-tool-admin.js';
|
|
27
|
+
import { addToolToIndex, removeToolFromIndex, toCamelCase } from '../web/http-tool-admin.js';
|
|
28
28
|
import { addActauthRule, updateActauthRule, removeActauthRule, readActauthConfig } from '../web/actauth-admin.js';
|
|
29
29
|
export class PackageManifestError extends Error {
|
|
30
30
|
}
|
|
@@ -484,6 +484,7 @@ export function removePackage(agentName, packageName, force = false) {
|
|
|
484
484
|
const refused = [];
|
|
485
485
|
const remainingTools = [];
|
|
486
486
|
const remainingSkills = [];
|
|
487
|
+
const toolsIndexPath = join(agentDir(agentName), 'tools', 'index.ts');
|
|
487
488
|
for (const toolName of record.tools) {
|
|
488
489
|
const relPath = `tools/${toolName}.ts`;
|
|
489
490
|
const fullPath = join(agentDir(agentName), relPath);
|
|
@@ -493,6 +494,14 @@ export function removePackage(agentName, packageName, force = false) {
|
|
|
493
494
|
continue;
|
|
494
495
|
}
|
|
495
496
|
rmSync(fullPath, { force: true });
|
|
497
|
+
// Undoes installPackage's own addToolToIndex call — without this, a
|
|
498
|
+
// deleted tool's import survives in tools/index.ts, and either fails
|
|
499
|
+
// the next build (a dangling import to a file that no longer exists)
|
|
500
|
+
// or, worse, silently duplicates on a later add-package of the same
|
|
501
|
+
// tool (addToolToIndex used to have no idempotence check at all —
|
|
502
|
+
// now it does, but this is the actual fix: the stale entry shouldn't
|
|
503
|
+
// be there to begin with).
|
|
504
|
+
removeToolFromIndex(toolsIndexPath, toolName, toCamelCase(toolName));
|
|
496
505
|
removed.push(relPath);
|
|
497
506
|
}
|
|
498
507
|
for (const skillId of record.skills) {
|
|
@@ -38,6 +38,17 @@ export interface HttpToolSpec {
|
|
|
38
38
|
}
|
|
39
39
|
export declare function toCamelCase(snakeCase: string): string;
|
|
40
40
|
export declare function addToolToIndex(toolsIndexPath: string, toolFileName: string, exportName: string): void;
|
|
41
|
+
/** addToolToIndex's own undo — removes the named tool's import line and
|
|
42
|
+
* array entry. Best-effort, not "refuse rather than guess" like
|
|
43
|
+
* addToolToIndex: this only ever runs against a tool package-manager.ts
|
|
44
|
+
* itself installed (so the exact shape addToolToIndex wrote is known),
|
|
45
|
+
* and the caller (removePackage) has already deleted the tool's .ts
|
|
46
|
+
* file — leaving a stale entry behind, the previous behavior, means a
|
|
47
|
+
* dangling import that fails the next build, which is strictly worse
|
|
48
|
+
* than a no-op here if the file turns out not to match (hand-edited
|
|
49
|
+
* since). No-ops (rather than throwing) when the file is missing
|
|
50
|
+
* entirely, or the expected import/array-entry shape isn't found. */
|
|
51
|
+
export declare function removeToolFromIndex(toolsIndexPath: string, toolFileName: string, exportName: string): void;
|
|
41
52
|
/** Generates agents/<agentName>/tools/<spec.name>.ts, registers it in
|
|
42
53
|
* that agent's tools/index.ts, then imports the file it just wrote and
|
|
43
54
|
* returns the real, callable ToolDefinition object — so the route
|
|
@@ -191,15 +191,52 @@ export function addToolToIndex(toolsIndexPath, toolFileName, exportName) {
|
|
|
191
191
|
if (!arrayMatch) {
|
|
192
192
|
throw new HttpToolIndexShapeError(`${toolsIndexPath} doesn't match the expected "export const tools: ToolDefinition[] = [...]" shape — add this tool to it by hand instead.`);
|
|
193
193
|
}
|
|
194
|
+
// Idempotent — a caller that adds the same tool twice (e.g. package-
|
|
195
|
+
// manager.ts's removePackage not having cleaned up this same file
|
|
196
|
+
// before a re-add, or simply calling this twice by mistake) gets a
|
|
197
|
+
// no-op the second time instead of a duplicate `import { X }`
|
|
198
|
+
// declaration, which is a TypeScript syntax error ("Duplicate
|
|
199
|
+
// identifier"), not just a harmless extra line.
|
|
200
|
+
const arrayEntries = arrayMatch[1]
|
|
201
|
+
.split(',')
|
|
202
|
+
.map((e) => e.trim())
|
|
203
|
+
.filter(Boolean);
|
|
204
|
+
if (arrayEntries.includes(exportName))
|
|
205
|
+
return;
|
|
194
206
|
const importLine = `import { ${exportName} } from './${toolFileName}.js'\n`;
|
|
195
207
|
const lastImportMatch = [...source.matchAll(/^import .+\n/gm)].pop();
|
|
196
208
|
const insertAt = lastImportMatch ? lastImportMatch.index + lastImportMatch[0].length : 0;
|
|
197
209
|
const withImport = source.slice(0, insertAt) + importLine + source.slice(insertAt);
|
|
198
|
-
const
|
|
199
|
-
const newEntries = existingEntries ? `${existingEntries}, ${exportName}` : exportName;
|
|
210
|
+
const newEntries = [...arrayEntries, exportName].join(', ');
|
|
200
211
|
const withEntry = withImport.replace(/export const tools: ToolDefinition\[\] = \[([^\]]*)\]/, `export const tools: ToolDefinition[] = [${newEntries}]`);
|
|
201
212
|
writeFileSync(toolsIndexPath, withEntry);
|
|
202
213
|
}
|
|
214
|
+
/** addToolToIndex's own undo — removes the named tool's import line and
|
|
215
|
+
* array entry. Best-effort, not "refuse rather than guess" like
|
|
216
|
+
* addToolToIndex: this only ever runs against a tool package-manager.ts
|
|
217
|
+
* itself installed (so the exact shape addToolToIndex wrote is known),
|
|
218
|
+
* and the caller (removePackage) has already deleted the tool's .ts
|
|
219
|
+
* file — leaving a stale entry behind, the previous behavior, means a
|
|
220
|
+
* dangling import that fails the next build, which is strictly worse
|
|
221
|
+
* than a no-op here if the file turns out not to match (hand-edited
|
|
222
|
+
* since). No-ops (rather than throwing) when the file is missing
|
|
223
|
+
* entirely, or the expected import/array-entry shape isn't found. */
|
|
224
|
+
export function removeToolFromIndex(toolsIndexPath, toolFileName, exportName) {
|
|
225
|
+
if (!existsSync(toolsIndexPath))
|
|
226
|
+
return;
|
|
227
|
+
const source = readFileSync(toolsIndexPath, 'utf8');
|
|
228
|
+
const importLineRe = new RegExp(`^import \\{ ${exportName} \\} from '\\./${toolFileName}\\.js'\\n`, 'm');
|
|
229
|
+
const withoutImport = source.replace(importLineRe, '');
|
|
230
|
+
const arrayMatch = withoutImport.match(/export const tools: ToolDefinition\[\] = \[([^\]]*)\]/);
|
|
231
|
+
if (!arrayMatch)
|
|
232
|
+
return;
|
|
233
|
+
const remainingEntries = arrayMatch[1]
|
|
234
|
+
.split(',')
|
|
235
|
+
.map((e) => e.trim())
|
|
236
|
+
.filter((e) => e && e !== exportName);
|
|
237
|
+
const withEntry = withoutImport.replace(/export const tools: ToolDefinition\[\] = \[([^\]]*)\]/, `export const tools: ToolDefinition[] = [${remainingEntries.join(', ')}]`);
|
|
238
|
+
writeFileSync(toolsIndexPath, withEntry);
|
|
239
|
+
}
|
|
203
240
|
// The spec is persisted alongside the generated .ts file, under the same
|
|
204
241
|
// name — this is what lets the Tools tab's Edit button repopulate the
|
|
205
242
|
// form later without reverse-parsing generated code back into a spec,
|