amalgm 0.1.257 → 0.1.258

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.
@@ -12,14 +12,25 @@
12
12
  */
13
13
 
14
14
  const path = require('path');
15
- const { AMALGM_DIR } = require('../config');
16
- const { ensureDir, readJson, writeJsonAtomic } = require('../lib/storage');
15
+ const { readJson } = require('../lib/storage');
17
16
  const { openLocalDb } = require('../state/db');
18
17
  const { insertStateEvent, publishStateEvent } = require('../state/events');
18
+ const {
19
+ TOOLBOX_DIR,
20
+ TOOLBOX_INDEX_FILE,
21
+ isSystemToolId,
22
+ readToolArtifacts,
23
+ removeUserToolArtifact,
24
+ writeToolArtifact,
25
+ writeToolboxIndex,
26
+ } = require('./artifacts');
19
27
 
20
28
  const TOOLBOX_VERSION = 1;
21
- const TOOLBOX_DIR = path.join(AMALGM_DIR, 'toolbox');
22
- const TOOLBOX_FILE = path.join(TOOLBOX_DIR, 'toolbox.json');
29
+ // `toolbox.index.json` is a local cache, deliberately outside the registered
30
+ // toolbox folder. Individual `toolbox/*.json` artifacts are the portable
31
+ // source of truth; `toolbox/toolbox.json` remains only a legacy import path.
32
+ const TOOLBOX_FILE = TOOLBOX_INDEX_FILE;
33
+ const LEGACY_TOOLBOX_FILE = path.join(TOOLBOX_DIR, 'toolbox.json');
23
34
 
24
35
  const TOOL_TYPES = new Set(['mcp', 'api', 'cli']);
25
36
  const TOOL_ORIGINS = new Set(['system', 'user', 'catalog']);
@@ -528,19 +539,32 @@ function writeToolboxToDb(toolbox, options = {}) {
528
539
  }
529
540
 
530
541
  function exportToolboxJson(toolbox) {
531
- ensureDir(TOOLBOX_DIR);
532
- writeJsonAtomic(TOOLBOX_FILE, normalizeToolbox(toolbox));
542
+ writeToolboxIndex(normalizeToolbox(toolbox), { toolboxDir: TOOLBOX_DIR });
533
543
  }
534
544
 
535
545
  function migrateLegacyToolboxFileIfNeeded() {
536
546
  const db = openLocalDb();
537
547
  if (toolboxRowCount(db) > 0) return;
538
548
 
539
- const legacyToolbox = dropInheritedHarnessTools(normalizeToolbox(readJson(TOOLBOX_FILE, emptyToolbox())));
549
+ const legacyToolbox = dropInheritedHarnessTools(normalizeToolbox(readJson(LEGACY_TOOLBOX_FILE, emptyToolbox())));
540
550
  if (Object.keys(legacyToolbox.tools).length === 0 && Object.keys(legacyToolbox.toolActions).length === 0) return;
541
551
  writeToolboxToDb(legacyToolbox, { replace: true });
542
552
  }
543
553
 
554
+ function writeUserToolArtifacts(toolbox) {
555
+ for (const tool of Object.values(toolbox.tools || {})) {
556
+ if (!tool || isSystemToolId(tool.id) || tool.origin === 'system') continue;
557
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
558
+ }
559
+ }
560
+
561
+ function cacheToolboxFromArtifacts(toolbox) {
562
+ const normalized = normalizeToolbox(toolbox);
563
+ writeToolboxToDb(normalized, { replace: true });
564
+ exportToolboxJson(normalized);
565
+ return normalized;
566
+ }
567
+
544
568
  function publishCommittedEvents(events) {
545
569
  for (const event of events) publishStateEvent(event);
546
570
  }
@@ -569,14 +593,18 @@ function insertToolActionEvent(db, op, action, options = {}) {
569
593
 
570
594
  function readToolbox() {
571
595
  migrateLegacyToolboxFileIfNeeded();
572
- const toolbox = readToolboxFromDb();
573
- const removedLegacyAggregate = dropLegacyAggregateAmalgmTool(toolbox);
574
- if (removedLegacyAggregate) writeToolbox(toolbox);
596
+ // An existing device may still have user records only in its local cache.
597
+ // Give each one a portable artifact before treating artifacts as canonical.
598
+ const cached = readToolboxFromDb();
599
+ dropLegacyAggregateAmalgmTool(cached);
600
+ writeUserToolArtifacts(cached);
601
+ const toolbox = cacheToolboxFromArtifacts(readToolArtifacts({ toolboxDir: TOOLBOX_DIR }));
575
602
  return toolbox;
576
603
  }
577
604
 
578
605
  function writeToolbox(toolbox) {
579
606
  const normalized = writeToolboxToDb(toolbox, { replace: true });
607
+ writeUserToolArtifacts(normalized);
580
608
  exportToolboxJson(normalized);
581
609
  return normalized;
582
610
  }
@@ -614,6 +642,9 @@ function upsertTool(input) {
614
642
  const existing = input?.id ? toolbox.tools[sanitizeId(input.id)] : null;
615
643
  const existingActionIds = new Set(Object.keys(toolbox.toolActions));
616
644
  const tool = normalizeTool(input, existing);
645
+ if (isSystemToolId(tool.id) || tool.origin === 'system') {
646
+ throw new Error('System tools are managed by Amalgm and cannot be created or replaced here');
647
+ }
617
648
  toolbox.tools[tool.id] = tool;
618
649
 
619
650
  const actions = [];
@@ -659,6 +690,8 @@ function upsertTool(input) {
659
690
  }
660
691
  return inserted;
661
692
  })();
693
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
694
+ exportToolboxJson(toolbox);
662
695
  publishCommittedEvents(events);
663
696
  return { tool, actions };
664
697
  }
@@ -735,6 +768,8 @@ function updateTool(input) {
735
768
  source: 'toolbox:update-tool',
736
769
  })];
737
770
  })();
771
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
772
+ exportToolboxJson(toolbox);
738
773
  publishCommittedEvents(events);
739
774
  return { tool, actions: toolActionsForTool(toolbox, tool.id) };
740
775
  }
@@ -744,6 +779,7 @@ function upsertToolAction(input) {
744
779
  const toolId = sanitizeId(input?.toolId || input?.providerId || input?.tool);
745
780
  const tool = toolbox.tools[toolId];
746
781
  if (!tool) throw new Error(`Unknown tool: ${toolId}`);
782
+ if (tool.origin === 'system' || isSystemToolId(tool.id)) throw new Error('System tool actions cannot be modified');
747
783
  const existing = input?.id ? toolbox.toolActions[sanitizeId(input.id)] : null;
748
784
  const action = normalizeAction(input, tool, existing);
749
785
  toolbox.toolActions[action.id] = action;
@@ -755,6 +791,8 @@ function upsertToolAction(input) {
755
791
  source: 'toolbox:upsert-action',
756
792
  })];
757
793
  })();
794
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
795
+ exportToolboxJson(toolbox);
758
796
  publishCommittedEvents(events);
759
797
  return action;
760
798
  }
@@ -805,6 +843,8 @@ function updateToolAction(input) {
805
843
  source: 'toolbox:update-action',
806
844
  })];
807
845
  })();
846
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
847
+ exportToolboxJson(toolbox);
808
848
  publishCommittedEvents(events);
809
849
  return action;
810
850
  }
@@ -830,6 +870,12 @@ function deleteTool(id, options = {}) {
830
870
  }));
831
871
  return inserted;
832
872
  })();
873
+ removeUserToolArtifact(toolId, { toolboxDir: TOOLBOX_DIR });
874
+ exportToolboxJson({
875
+ ...toolbox,
876
+ tools: Object.fromEntries(Object.entries(toolbox.tools).filter(([id]) => id !== toolId)),
877
+ toolActions: Object.fromEntries(Object.entries(toolbox.toolActions).filter(([, action]) => action.toolId !== toolId)),
878
+ });
833
879
  publishCommittedEvents(events);
834
880
  scrubToolFromApps(toolId);
835
881
  return { ok: true, deleted: true };
@@ -867,6 +913,9 @@ function deleteToolAction(id, options = {}) {
867
913
  source: 'toolbox:delete-action',
868
914
  })];
869
915
  })();
916
+ delete toolbox.toolActions[actionId];
917
+ writeToolArtifact({ tool, actions: toolActionsForTool(toolbox, tool.id) }, { toolboxDir: TOOLBOX_DIR });
918
+ exportToolboxJson(toolbox);
870
919
  publishCommittedEvents(events);
871
920
  return { ok: true, deleted: true };
872
921
  }
@@ -3,9 +3,10 @@
3
3
  /**
4
4
  * Virtual first-party Toolbox catalog entries.
5
5
  *
6
- * These records make Amalgm's default capabilities look like normal Toolbox
7
- * tools/actions to UI and control-plane consumers without changing the legacy
8
- * MCP tool names or writing additional system rows to local storage.
6
+ * These records are the canonical definitions materialized as one exact
7
+ * `toolbox/amalgm-*.json` artifact per built-in tool. The overlay keeps the
8
+ * legacy MCP surface stable and refuses a stale local cache from overriding a
9
+ * product-managed definition.
9
10
  */
10
11
 
11
12
  const { PORT } = require('../config');
@@ -29,6 +30,10 @@ function coreToolGroups() {
29
30
  return require('../server/core-tools').CORE_TOOL_GROUPS || [];
30
31
  }
31
32
 
33
+ function systemToolIds() {
34
+ return new Set(coreToolGroups().map((group) => group.id));
35
+ }
36
+
32
37
  function systemToolForGroup(group) {
33
38
  return {
34
39
  id: group.id,
@@ -95,12 +100,15 @@ function buildSystemToolboxCatalog() {
95
100
  function persistedCatalogWithoutLegacyAggregate(toolbox) {
96
101
  const tools = { ...(toolbox?.tools || {}) };
97
102
  const toolActions = { ...(toolbox?.toolActions || {}) };
103
+ const systemIds = systemToolIds();
98
104
  delete tools.amalgm;
105
+ for (const systemId of systemIds) delete tools[systemId];
99
106
  for (const toolId of LEGACY_FIRST_PARTY_TOOL_IDS) delete tools[toolId];
100
107
  for (const [actionId, action] of Object.entries(toolActions)) {
101
108
  const legacyGroupId = actionId.split('.').slice(0, 2).join('.');
102
109
  if (
103
110
  action?.toolId === 'amalgm'
111
+ || systemIds.has(action?.toolId)
104
112
  || LEGACY_FIRST_PARTY_TOOL_IDS.has(action?.toolId)
105
113
  || LEGACY_FIRST_PARTY_TOOL_IDS.has(legacyGroupId)
106
114
  ) {
@@ -116,18 +124,19 @@ function mergeSystemToolboxCatalog(toolbox) {
116
124
  return {
117
125
  ...toolbox,
118
126
  tools: {
119
- ...catalog.tools,
120
127
  ...persisted.tools,
128
+ ...catalog.tools,
121
129
  },
122
130
  toolActions: {
123
- ...catalog.toolActions,
124
131
  ...persisted.toolActions,
132
+ ...catalog.toolActions,
125
133
  },
126
134
  };
127
135
  }
128
136
 
129
137
  module.exports = {
130
138
  buildSystemToolboxCatalog,
139
+ systemToolIds,
131
140
  mergeSystemToolboxCatalog,
132
141
  persistedCatalogWithoutLegacyAggregate,
133
142
  };