amalgm 0.1.154 → 0.1.155

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.154",
3
+ "version": "0.1.155",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -3,14 +3,13 @@
3
3
  // Facade over the agent-bundle modules so existing require paths keep working.
4
4
  // The implementation lives in graph.js (flat share graph), export.js
5
5
  // (createBundle and its collectors), and install.js (validate + install).
6
- const { BUNDLE_KIND, BUNDLE_SCHEMA_VERSION, LEGACY_AGENT_BUNDLE_KIND } = require('./graph');
6
+ const { BUNDLE_KIND, BUNDLE_SCHEMA_VERSION } = require('./graph');
7
7
  const { createAgentBundle, createBundle } = require('./export');
8
8
  const { installBundle, validateBundle } = require('./install');
9
9
 
10
10
  module.exports = {
11
11
  BUNDLE_KIND,
12
12
  BUNDLE_SCHEMA_VERSION,
13
- LEGACY_AGENT_BUNDLE_KIND,
14
13
  createAgentBundle,
15
14
  createBundle,
16
15
  installBundle,
@@ -14,7 +14,6 @@ const { annotateAppBoundPaths } = require('./tool-bindings');
14
14
  const {
15
15
  BUNDLE_KIND,
16
16
  BUNDLE_SCHEMA_VERSION,
17
- LEGACY_AGENT_BUNDLE_KIND,
18
17
  addUnique,
19
18
  cloneJson,
20
19
  collectSkillRecords,
@@ -85,10 +84,7 @@ function collectToolRecords(toolIds, requires) {
85
84
  const selectedAction = catalog.toolActions?.[selectedId];
86
85
  const tool = selectedTool || (selectedAction ? catalog.tools?.[selectedAction.toolId] : null);
87
86
 
88
- if (!tool) {
89
- addUnique(requires.missingTools, selectedId);
90
- continue;
91
- }
87
+ if (!tool) throw new Error(`Tool not found: ${selectedId}`);
92
88
 
93
89
  if (tool.origin === 'system') {
94
90
  addUnique(requires.systemTools, tool.id);
@@ -217,31 +213,46 @@ function createBundle(input = {}, options = {}) {
217
213
  for (const ref of exported.missingRefs) addUnique(requires.missingTools, ref);
218
214
  }
219
215
 
220
- // Tool app closure: an app-owned tool only works next to its app, so any
221
- // requested tool whose record points at an app pulls that app in as a
222
- // dependency (not a head). Stale pointers degrade to a warning.
223
- const appLinkWarnings = [];
224
- const dependencyAppIds = [];
216
+ // Close the tool↔app graph from the two canonical declarations: tool.appId
217
+ // and the app manifest's toolIds. Pack each discovered app once, and keep
218
+ // walking until neither side discovers another record.
225
219
  const catalog = defaultToolboxService.readCatalog();
226
- for (const selectedId of uniqueStrings([...loadoutToolIds, ...toolIds])) {
227
- const selectedAction = catalog.toolActions?.[selectedId];
228
- const tool = catalog.tools?.[selectedId]
229
- || (selectedAction ? catalog.tools?.[selectedAction.toolId] : null);
230
- const owningAppId = typeof tool?.appId === 'string' ? tool.appId.trim() : '';
231
- if (!owningAppId || appIds.includes(owningAppId) || dependencyAppIds.includes(owningAppId)) continue;
232
- if (!getApp(owningAppId)) {
233
- appLinkWarnings.push(`Tool ${tool.id} belongs to app ${owningAppId}, which no longer exists; the app was not bundled.`);
234
- continue;
220
+ const closedToolRefs = uniqueStrings([...loadoutToolIds, ...toolIds]);
221
+ const closedAppIds = [...appIds];
222
+ const exportedAppsById = new Map();
223
+ let toolCursor = 0;
224
+ let appCursor = 0;
225
+ while (toolCursor < closedToolRefs.length || appCursor < closedAppIds.length) {
226
+ if (toolCursor < closedToolRefs.length) {
227
+ const selectedId = closedToolRefs[toolCursor];
228
+ toolCursor += 1;
229
+ const selectedAction = catalog.toolActions?.[selectedId];
230
+ const tool = catalog.tools?.[selectedId]
231
+ || (selectedAction ? catalog.tools?.[selectedAction.toolId] : null);
232
+ if (!tool) throw new Error(`Tool not found: ${selectedId}`);
233
+ const owningAppId = typeof tool.appId === 'string' ? tool.appId.trim() : '';
234
+ if (owningAppId && !closedAppIds.includes(owningAppId)) {
235
+ if (!getApp(owningAppId)) throw new Error(`Tool ${tool.id} references missing app: ${owningAppId}`);
236
+ closedAppIds.push(owningAppId);
237
+ }
238
+ }
239
+ if (appCursor < closedAppIds.length) {
240
+ const appId = closedAppIds[appCursor];
241
+ appCursor += 1;
242
+ const exported = exportAppEntry(appId);
243
+ exportedAppsById.set(appId, exported);
244
+ for (const appToolId of uniqueStrings(exported.entry.toolIds)) {
245
+ if (!closedToolRefs.includes(appToolId)) closedToolRefs.push(appToolId);
246
+ }
235
247
  }
236
- dependencyAppIds.push(owningAppId);
237
248
  }
238
249
 
239
250
  const apps = [];
240
251
  const appCwdBySourceId = new Map();
241
252
  let excludedAppFiles = 0;
242
253
  const appToolIds = [];
243
- for (const appId of [...appIds, ...dependencyAppIds]) {
244
- const exported = exportAppEntry(appId);
254
+ for (const appId of closedAppIds) {
255
+ const exported = exportedAppsById.get(appId);
245
256
  apps.push(exported.entry);
246
257
  appCwdBySourceId.set(exported.entry.sourceAppId, exported.sourceCwd);
247
258
  excludedAppFiles += exported.skipped.length;
@@ -249,7 +260,10 @@ function createBundle(input = {}, options = {}) {
249
260
  appToolIds.push(...(exported.entry.toolIds || []));
250
261
  }
251
262
 
252
- const { tools, toolActions } = collectToolRecords([...loadoutToolIds, ...appToolIds, ...toolIds], requires);
263
+ const { tools, toolActions } = collectToolRecords([...closedToolRefs, ...appToolIds], requires);
264
+ if (requires.missingTools.length > 0) {
265
+ throw new Error(`Tool not found: ${requires.missingTools.join(', ')}`);
266
+ }
253
267
 
254
268
  // Tool files that live inside a shared app travel with that app: record
255
269
  // app-relative bindings so install rewrites them to the recipient's copy.
@@ -260,8 +274,7 @@ function createBundle(input = {}, options = {}) {
260
274
  const { unbound } = annotateAppBoundPaths(record, appCwdBySourceId);
261
275
  for (const label of unbound) addUnique(requires.bindings, `${kind}:${record.id}:${label}`);
262
276
  }
263
- // Standalone tool heads must resolve to a real catalog tool — a dependency
264
- // tool that is missing degrades to a placeholder, but a head cannot.
277
+ // Standalone tool heads must resolve to a real catalog tool.
265
278
  const sharedToolIds = toolIds.filter((toolId) => tools.some((tool) => tool.id === toolId));
266
279
  const missingSharedToolIds = toolIds.filter((toolId) => !sharedToolIds.includes(toolId));
267
280
  if (missingSharedToolIds.length > 0) {
@@ -270,16 +283,13 @@ function createBundle(input = {}, options = {}) {
270
283
  const sharedTools = sharedToolIds.map((toolId) => tools.find((tool) => tool.id === toolId));
271
284
  const skills = collectSkillRecords(agents);
272
285
  const hooks = countHooks(agents);
273
- const warnings = [
274
- ...bundleWarnings({
275
- hooks,
276
- tools: tools.length,
277
- automations: automations.length,
278
- apps: apps.length,
279
- excludedAppFiles,
280
- }),
281
- ...appLinkWarnings,
282
- ];
286
+ const warnings = bundleWarnings({
287
+ hooks,
288
+ tools: tools.length,
289
+ automations: automations.length,
290
+ apps: apps.length,
291
+ excludedAppFiles,
292
+ });
283
293
  // Title comes from what the user deliberately shared: dependency apps
284
294
  // pulled in by an app-owned tool must not name the bundle.
285
295
  const headApps = apps.filter((entry) => appIds.includes(entry.sourceAppId));
@@ -292,9 +302,7 @@ function createBundle(input = {}, options = {}) {
292
302
  ];
293
303
 
294
304
  const bundle = withBundleGraph({
295
- kind: automations.length > 0 || apps.length > 0 || sharedToolIds.length > 0
296
- ? BUNDLE_KIND
297
- : LEGACY_AGENT_BUNDLE_KIND,
305
+ kind: BUNDLE_KIND,
298
306
  schemaVersion: BUNDLE_SCHEMA_VERSION,
299
307
  bundleId: options.bundleId || bundleId(),
300
308
  createdAt: nowIso(),