@amalgm/tools 0.1.5 → 0.1.6

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.
Files changed (43) hide show
  1. package/PURPOSE.md +58 -26
  2. package/README.md +36 -10
  3. package/dist/apply-definition.d.ts +12 -0
  4. package/dist/apply-definition.js +33 -0
  5. package/dist/artifact-files.d.ts +3 -1
  6. package/dist/artifact-files.js +11 -3
  7. package/dist/artifacts.d.ts +8 -6
  8. package/dist/artifacts.js +19 -6
  9. package/dist/definition.js +10 -0
  10. package/dist/deployment-files.d.ts +10 -0
  11. package/dist/deployment-files.js +38 -0
  12. package/dist/deployment-types.d.ts +32 -0
  13. package/dist/deployment-types.js +1 -0
  14. package/dist/deployments.d.ts +19 -0
  15. package/dist/deployments.js +98 -0
  16. package/dist/host-mcp-tool.d.ts +8 -0
  17. package/dist/host-mcp-tool.js +40 -0
  18. package/dist/http.js +6 -1
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.js +3 -1
  21. package/dist/mcp-server.d.ts +2 -1
  22. package/dist/mcp-server.js +3 -2
  23. package/dist/mcp.d.ts +2 -1
  24. package/dist/mcp.js +33 -21
  25. package/dist/notifications.js +4 -3
  26. package/dist/schema.js +15 -0
  27. package/dist/store.d.ts +17 -4
  28. package/dist/store.js +105 -41
  29. package/dist/toolbox-deployments.d.ts +30 -0
  30. package/dist/toolbox-deployments.js +125 -0
  31. package/dist/toolbox-projection.d.ts +13 -0
  32. package/dist/toolbox-projection.js +41 -0
  33. package/dist/toolbox-view.d.ts +35 -0
  34. package/dist/toolbox-view.js +75 -0
  35. package/dist/toolbox.d.ts +13 -5
  36. package/dist/toolbox.js +90 -114
  37. package/dist/types.d.ts +30 -1
  38. package/dist/updates.d.ts +2 -1
  39. package/dist/updates.js +12 -1
  40. package/docs/ENGINE_INTEGRATION.md +10 -54
  41. package/docs/SHELL_INTEGRATION.md +44 -0
  42. package/package.json +3 -2
  43. package/skills/tools/SKILL.md +185 -0
@@ -0,0 +1,41 @@
1
+ /** Current-catalog compatibility projections; deployments remain authority. */
2
+ class ToolboxProjection {
3
+ artifacts;
4
+ catalog;
5
+ onChange;
6
+ constructor(artifacts, catalog, onChange) {
7
+ this.artifacts = artifacts;
8
+ this.catalog = catalog;
9
+ this.onChange = onChange;
10
+ }
11
+ boot() {
12
+ this.artifacts.migrateLegacy();
13
+ this.artifacts.materialize(this.catalog());
14
+ }
15
+ async changed(toolId) {
16
+ const catalog = this.catalog();
17
+ const tool = catalog.tools.find((candidate) => candidate.id === toolId);
18
+ if (tool && tool.origin !== 'system') {
19
+ this.artifacts.writeTool({
20
+ tool,
21
+ actions: catalog.actions.filter((action) => action.toolId === toolId),
22
+ });
23
+ }
24
+ else if (!tool) {
25
+ this.artifacts.removeTool(toolId);
26
+ }
27
+ this.artifacts.writeIndex(catalog);
28
+ await this.onChange?.(catalog);
29
+ }
30
+ async replaced(previousToolIds) {
31
+ const catalog = this.catalog();
32
+ const currentToolIds = new Set(catalog.tools.map((tool) => tool.id));
33
+ for (const toolId of previousToolIds) {
34
+ if (!currentToolIds.has(toolId))
35
+ this.artifacts.removeTool(toolId);
36
+ }
37
+ this.artifacts.materialize(catalog);
38
+ await this.onChange?.(catalog);
39
+ }
40
+ }
41
+ export { ToolboxProjection };
@@ -0,0 +1,35 @@
1
+ import type { ActionRecord, ApplyResult, CallOptions, Catalog, LoadoutInput, ToolDriver, ToolQuery, ToolRecord, ToolResult } from './types.js';
2
+ declare class ToolboxView {
3
+ private readonly readCatalog;
4
+ private readonly drivers;
5
+ constructor(readCatalog: () => Catalog, drivers: ReadonlyMap<string, ToolDriver>);
6
+ catalog(): Catalog;
7
+ list(loadout?: LoadoutInput): Array<{
8
+ tool: ToolRecord;
9
+ actions: ActionRecord[];
10
+ }>;
11
+ query(filters?: ToolQuery): {
12
+ tool: ToolRecord;
13
+ actions: ActionRecord[];
14
+ }[];
15
+ callable(loadout?: LoadoutInput, filters?: ToolQuery): {
16
+ tool: ToolRecord;
17
+ action: ActionRecord;
18
+ }[];
19
+ get(toolId: string): ApplyResult | null;
20
+ action(actionId: string): ActionRecord | null;
21
+ resolve(toolId: string, actionReference: string, loadout?: LoadoutInput): {
22
+ tool: ToolRecord;
23
+ action: ActionRecord;
24
+ } | null;
25
+ resolveCallable(toolId: string, actionReference: string, loadout?: LoadoutInput, filters?: ToolQuery): {
26
+ tool: ToolRecord;
27
+ action: ActionRecord;
28
+ } | null;
29
+ connections(loadout?: LoadoutInput): ToolRecord[];
30
+ call(actionId: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
31
+ hasDriver(tool: ToolRecord): boolean;
32
+ callMcp(name: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
33
+ private driverFor;
34
+ }
35
+ export { ToolboxView };
@@ -0,0 +1,75 @@
1
+ import { id, mcpName } from './ids.js';
2
+ import { validateInput } from './input.js';
3
+ import { callableActions, queryTools, resolveAction, resolveCallableAction } from './query.js';
4
+ import { findSelected, selected } from './selection.js';
5
+ class ToolboxView {
6
+ readCatalog;
7
+ drivers;
8
+ constructor(readCatalog, drivers) {
9
+ this.readCatalog = readCatalog;
10
+ this.drivers = drivers;
11
+ }
12
+ catalog() { return this.readCatalog(); }
13
+ list(loadout) {
14
+ const catalog = this.catalog();
15
+ const runnable = selected(catalog, loadout);
16
+ return catalog.tools.flatMap((tool) => {
17
+ const actions = runnable.filter((item) => item.tool.id === tool.id).map((item) => item.action);
18
+ if (loadout !== undefined && actions.length === 0)
19
+ return [];
20
+ return [{ tool, actions }];
21
+ });
22
+ }
23
+ query(filters = {}) { return queryTools(this, filters); }
24
+ callable(loadout, filters = {}) {
25
+ return callableActions(this, loadout, filters);
26
+ }
27
+ get(toolId) {
28
+ const value = id(toolId, 'tool id');
29
+ const catalog = this.catalog();
30
+ const tool = catalog.tools.find((candidate) => candidate.id === value);
31
+ return tool ? { tool, actions: catalog.actions.filter((action) => action.toolId === value) } : null;
32
+ }
33
+ action(actionId) {
34
+ const value = id(actionId, 'action id');
35
+ return this.catalog().actions.find((action) => action.id === value) || null;
36
+ }
37
+ resolve(toolId, actionReference, loadout) {
38
+ return resolveAction(this, toolId, actionReference, loadout);
39
+ }
40
+ resolveCallable(toolId, actionReference, loadout, filters = {}) {
41
+ return resolveCallableAction(this, toolId, actionReference, loadout, filters);
42
+ }
43
+ connections(loadout) {
44
+ const catalog = this.catalog();
45
+ const ids = loadout === undefined ? null : new Set(Array.isArray(loadout) ? loadout : loadout.toolIds);
46
+ return catalog.tools.filter((tool) => {
47
+ if (tool.status !== 'enabled' || tool.source.type !== 'mcp' || tool.origin === 'system')
48
+ return false;
49
+ if (!ids)
50
+ return true;
51
+ return ids.has(tool.id) || catalog.actions.some((action) => action.toolId === tool.id && ids.has(action.id));
52
+ });
53
+ }
54
+ async call(actionId, input = {}, options = {}) {
55
+ const resolved = findSelected(this.catalog(), id(actionId, 'action id'), options.loadout);
56
+ if (!resolved)
57
+ throw new Error(`Action is unavailable: ${actionId}`);
58
+ const driver = this.driverFor(resolved.tool);
59
+ if (!driver)
60
+ throw new Error(`No ${resolved.tool.source.type} driver is configured for ${resolved.tool.id}`);
61
+ return driver.call({ ...resolved, input: validateInput(resolved.action.inputSchema, input), options });
62
+ }
63
+ hasDriver(tool) { return this.driverFor(tool) !== null; }
64
+ async callMcp(name, input = {}, options = {}) {
65
+ const resolved = selected(this.catalog(), options.loadout).find(({ action }) => mcpName(action) === name);
66
+ if (!resolved)
67
+ throw new Error(`Unknown MCP tool: ${name}`);
68
+ return this.call(resolved.action.id, input, options);
69
+ }
70
+ driverFor(tool) {
71
+ const driver = this.drivers.get(tool.source.type);
72
+ return driver && (driver.supports?.(tool) ?? true) ? driver : null;
73
+ }
74
+ }
75
+ export { ToolboxView };
package/dist/toolbox.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { ActionDefinition, ActionPatch, ActionRecord, ApplyResult, CallOptions, Catalog, LoadoutInput, ToolDefinition, ToolboxOptions, ToolPatch, ToolQuery, ToolRecord, ToolResult } from './types.js';
1
+ import type { ActionDefinition, ActionPatch, ActionRecord, ApplyResult, CallOptions, Catalog, LoadoutInput, ToolDefinition, ToolboxOptions, ToolDeployment, ToolDeploymentActivation, ToolDeploymentSnapshot, ToolDeploymentSurface, ToolPatch, ToolQuery, ToolRecord, ToolResult } from './types.js';
2
2
  declare class Toolbox {
3
3
  private readonly store;
4
- private readonly artifacts;
4
+ private readonly deploymentService;
5
+ private readonly projection;
5
6
  private readonly system;
6
7
  private readonly drivers;
7
- private readonly onChange?;
8
+ private readonly view;
8
9
  constructor(options?: ToolboxOptions);
9
10
  catalog(): Catalog;
10
11
  list(loadout?: LoadoutInput): Array<{
@@ -35,12 +36,19 @@ declare class Toolbox {
35
36
  updateAction(actionId: string, patch: ActionPatch): Promise<ActionRecord>;
36
37
  setStatus(recordId: string, status: 'enabled' | 'disabled'): Promise<ToolRecord | ActionRecord>;
37
38
  remove(recordId: string): Promise<ToolRecord | ActionRecord>;
39
+ currentDeployment(toolId: string): ToolDeployment | null;
40
+ deployment(deploymentId: string): ToolDeployment | null;
41
+ deploymentHistory(toolId: string): ToolDeployment[];
42
+ deploymentSnapshot(): ToolDeploymentSnapshot;
43
+ /** Apply an officially ordered remote activation without emitting it again. */
44
+ activateDeployment(activation: ToolDeploymentActivation): Promise<ApplyResult | null>;
45
+ /** Replace local materialization during Live hydration or gap recovery. */
46
+ applyDeploymentSnapshot(snapshot: ToolDeploymentSnapshot): Promise<void>;
47
+ deploymentSurface(): ToolDeploymentSurface;
38
48
  connections(loadout?: LoadoutInput): ToolRecord[];
39
49
  call(actionId: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
40
50
  hasDriver(tool: ToolRecord): boolean;
41
51
  callMcp(name: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
42
- private changed;
43
- private driverFor;
44
52
  close(): void;
45
53
  }
46
54
  export { Toolbox };
package/dist/toolbox.js CHANGED
@@ -2,19 +2,19 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { resolveProductStateDir } from '@amalgm/core/identity';
4
4
  import { apiDriver } from './api-driver.js';
5
+ import { applyDefinition } from './apply-definition.js';
5
6
  import { ArtifactFiles } from './artifact-files.js';
7
+ import { preserveProjectionTime } from './artifacts.js';
6
8
  import { cliDriver } from './cli-driver.js';
9
+ import { DeploymentFiles } from './deployment-files.js';
10
+ import { deploymentRevisionId } from './deployments.js';
7
11
  import { normalizeDefinition } from './definition.js';
8
- import { assertMcpNamesUnique, id, mcpName } from './ids.js';
9
- import { validateInput } from './input.js';
10
- import { callableActions, queryTools, resolveAction, resolveCallableAction } from './query.js';
11
- import { findSelected, selected } from './selection.js';
12
+ import { assertMcpNamesUnique, id } from './ids.js';
12
13
  import { Store } from './store.js';
13
- import { updateAction, updateTool, upsertAction } from './updates.js';
14
- function semantic(value) {
15
- const { createdAt, updatedAt, ...record } = value;
16
- return JSON.stringify(record);
17
- }
14
+ import { ToolboxDeployments } from './toolbox-deployments.js';
15
+ import { ToolboxProjection } from './toolbox-projection.js';
16
+ import { ToolboxView } from './toolbox-view.js';
17
+ import { removeAction, updateAction, updateTool, upsertAction } from './updates.js';
18
18
  function defaultDatabase(options) {
19
19
  if (options.databaseFile)
20
20
  return path.resolve(options.databaseFile);
@@ -32,15 +32,17 @@ function defaultDatabase(options) {
32
32
  }
33
33
  class Toolbox {
34
34
  store;
35
- artifacts;
35
+ deploymentService;
36
+ projection;
36
37
  system;
37
38
  drivers = new Map();
38
- onChange;
39
+ view;
39
40
  constructor(options = {}) {
40
41
  const databaseFile = defaultDatabase(options);
41
42
  this.store = new Store(databaseFile);
42
- this.artifacts = new ArtifactFiles(path.dirname(databaseFile));
43
- this.onChange = options.onChange;
43
+ const artifacts = new ArtifactFiles(path.dirname(databaseFile));
44
+ const deploymentFiles = new DeploymentFiles(path.resolve(options.deploymentDir || options.stateDir || path.dirname(databaseFile)));
45
+ const priorIndex = artifacts.readIndex();
44
46
  for (const driver of [cliDriver, apiDriver, ...(options.drivers || [])])
45
47
  this.drivers.set(driver.type, driver);
46
48
  const tools = [];
@@ -49,13 +51,31 @@ class Toolbox {
49
51
  const normalized = normalizeDefinition({ ...definition, origin: 'system' });
50
52
  if (tools.some((tool) => tool.id === normalized.tool.id))
51
53
  throw new Error(`Duplicate system tool: ${normalized.tool.id}`);
52
- tools.push(normalized.tool);
53
- actions.push(...normalized.actions);
54
+ tools.push(preserveProjectionTime(normalized.tool, priorIndex?.tools[normalized.tool.id]));
55
+ actions.push(...normalized.actions.map((action) => preserveProjectionTime(action, priorIndex?.toolActions[action.id])));
54
56
  }
55
57
  assertMcpNamesUnique(actions);
56
- this.system = { version: 1, revision: 0, tools, actions };
57
- this.artifacts.migrateLegacy();
58
- this.artifacts.materialize(this.catalog());
58
+ this.system = {
59
+ version: 1,
60
+ revision: 0,
61
+ revisionId: deploymentRevisionId({ heads: {}, systemTools: tools, systemActions: actions }),
62
+ deployments: {},
63
+ tools,
64
+ actions,
65
+ };
66
+ this.projection = new ToolboxProjection(artifacts, () => this.catalog(), options.onChange);
67
+ this.deploymentService = new ToolboxDeployments({
68
+ store: this.store,
69
+ files: deploymentFiles,
70
+ systemTools: this.system.tools,
71
+ systemActions: this.system.actions,
72
+ project: (toolId) => this.projection.changed(toolId),
73
+ replaceProjection: (previousToolIds) => this.projection.replaced(previousToolIds),
74
+ ...(options.onDeployment ? { onDeployment: options.onDeployment } : {}),
75
+ });
76
+ this.deploymentService.adoptLegacy();
77
+ this.view = new ToolboxView(() => this.catalog(), this.drivers);
78
+ this.projection.boot();
59
79
  }
60
80
  catalog() {
61
81
  const persisted = this.store.catalog();
@@ -64,66 +84,37 @@ class Toolbox {
64
84
  const userActions = persisted.actions.filter((action) => !systemIds.has(action.toolId));
65
85
  const catalog = {
66
86
  version: 1, revision: persisted.revision,
87
+ revisionId: deploymentRevisionId({
88
+ heads: persisted.deployments,
89
+ systemTools: this.system.tools,
90
+ systemActions: this.system.actions,
91
+ }),
92
+ deployments: persisted.deployments,
67
93
  tools: [...this.system.tools, ...userTools].sort((a, b) => a.id.localeCompare(b.id)),
68
94
  actions: [...this.system.actions, ...userActions].sort((a, b) => a.id.localeCompare(b.id)),
69
95
  };
70
96
  assertMcpNamesUnique(catalog.actions);
71
97
  return catalog;
72
98
  }
73
- list(loadout) {
74
- const catalog = this.catalog();
75
- const runnable = selected(catalog, loadout);
76
- return catalog.tools.flatMap((tool) => {
77
- const actions = runnable.filter((item) => item.tool.id === tool.id).map((item) => item.action);
78
- if (loadout !== undefined && actions.length === 0)
79
- return [];
80
- return [{ tool, actions }];
81
- });
82
- }
83
- query(filters = {}) { return queryTools(this, filters); }
84
- callable(loadout, filters = {}) { return callableActions(this, loadout, filters); }
85
- get(toolId) {
86
- const value = id(toolId, 'tool id');
87
- const catalog = this.catalog();
88
- const tool = catalog.tools.find((candidate) => candidate.id === value);
89
- return tool ? { tool, actions: catalog.actions.filter((action) => action.toolId === value) } : null;
90
- }
91
- action(actionId) {
92
- const value = id(actionId, 'action id');
93
- return this.catalog().actions.find((action) => action.id === value) || null;
94
- }
99
+ list(loadout) { return this.view.list(loadout); }
100
+ query(filters = {}) { return this.view.query(filters); }
101
+ callable(loadout, filters = {}) { return this.view.callable(loadout, filters); }
102
+ get(toolId) { return this.view.get(toolId); }
103
+ action(actionId) { return this.view.action(actionId); }
95
104
  resolve(toolId, actionReference, loadout) {
96
- return resolveAction(this, toolId, actionReference, loadout);
105
+ return this.view.resolve(toolId, actionReference, loadout);
97
106
  }
98
107
  resolveCallable(toolId, actionReference, loadout, filters = {}) {
99
- return resolveCallableAction(this, toolId, actionReference, loadout, filters);
108
+ return this.view.resolveCallable(toolId, actionReference, loadout, filters);
100
109
  }
101
110
  async apply(definition) {
102
- const normalizedId = id(definition.id, 'tool.id', 64);
103
- if (definition.origin === 'system')
104
- throw new Error('System tools can only be supplied by the embedder');
105
- if (this.system.tools.some((tool) => tool.id === normalizedId))
106
- throw new Error(`System tool is immutable: ${normalizedId}`);
107
- const existing = this.store.getTool(normalizedId);
108
- const oldActions = new Map(this.store.actionsFor(normalizedId).map((action) => [action.id, action]));
109
- const normalized = normalizeDefinition(definition, existing || undefined);
110
- normalized.actions = normalized.actions.map((action) => ({
111
- ...action, createdAt: oldActions.get(action.id)?.createdAt || action.createdAt,
112
- }));
113
- const candidateActions = [
114
- ...this.system.actions,
115
- ...this.store.catalog().actions.filter((action) => action.toolId !== normalized.tool.id),
116
- ...normalized.actions,
117
- ];
118
- assertMcpNamesUnique(candidateActions);
119
- const unchanged = existing && semantic(existing) === semantic(normalized.tool)
120
- && normalized.actions.length === oldActions.size
121
- && normalized.actions.every((action) => semantic(action) === semantic(oldActions.get(action.id)));
122
- if (unchanged)
123
- return { tool: existing, actions: [...oldActions.values()].sort((a, b) => a.id.localeCompare(b.id)) };
124
- const result = this.store.apply(normalized);
125
- await this.changed(normalized.tool.id);
126
- return result;
111
+ return applyDefinition({
112
+ definition,
113
+ store: this.store,
114
+ deployments: this.deploymentService,
115
+ systemTools: this.system.tools,
116
+ systemActions: this.system.actions,
117
+ });
127
118
  }
128
119
  async update(toolId, patch) {
129
120
  return updateTool(this, toolId, patch);
@@ -139,65 +130,50 @@ class Toolbox {
139
130
  if (this.system.tools.some((tool) => tool.id === value)
140
131
  || this.system.actions.some((action) => action.id === value))
141
132
  throw new Error(`System record is immutable: ${value}`);
142
- const updated = this.store.setStatus(value, status, new Date().toISOString());
143
- if (!updated)
133
+ const tool = this.get(value);
134
+ if (tool)
135
+ return tool.tool.status === status ? tool.tool : (await this.update(value, { status })).tool;
136
+ const action = this.action(value);
137
+ if (!action)
144
138
  throw new Error(`Unknown tool or action: ${value}`);
145
- await this.changed('toolId' in updated ? updated.toolId : updated.id);
146
- return updated;
139
+ return action.status === status ? action : this.updateAction(value, { status });
147
140
  }
148
141
  async remove(recordId) {
149
142
  const value = id(recordId, 'record id');
150
143
  if (this.system.tools.some((tool) => tool.id === value)
151
144
  || this.system.actions.some((action) => action.id === value))
152
145
  throw new Error(`System record is immutable: ${value}`);
153
- const removed = this.store.remove(value);
154
- if (!removed)
155
- throw new Error(`Unknown tool or action: ${value}`);
156
- await this.changed('toolId' in removed ? removed.toolId : removed.id);
157
- return removed;
146
+ const tool = this.get(value);
147
+ if (tool) {
148
+ await this.deploymentService.activateLocal(null, value);
149
+ return tool.tool;
150
+ }
151
+ return removeAction(this, value);
152
+ }
153
+ currentDeployment(toolId) { return this.deploymentService.current(toolId); }
154
+ deployment(deploymentId) { return this.deploymentService.get(deploymentId); }
155
+ deploymentHistory(toolId) { return this.deploymentService.history(toolId); }
156
+ deploymentSnapshot() { return this.deploymentService.snapshot(); }
157
+ /** Apply an officially ordered remote activation without emitting it again. */
158
+ async activateDeployment(activation) {
159
+ return this.deploymentService.activateRemote(activation);
160
+ }
161
+ /** Replace local materialization during Live hydration or gap recovery. */
162
+ async applyDeploymentSnapshot(snapshot) {
163
+ return this.deploymentService.applySnapshot(snapshot);
164
+ }
165
+ deploymentSurface() {
166
+ return this.deploymentService.surface();
158
167
  }
159
168
  connections(loadout) {
160
- const catalog = this.catalog();
161
- const ids = loadout === undefined ? null : new Set(Array.isArray(loadout) ? loadout : loadout.toolIds);
162
- return catalog.tools.filter((tool) => {
163
- if (tool.status !== 'enabled' || tool.source.type !== 'mcp' || tool.origin === 'system')
164
- return false;
165
- if (!ids)
166
- return true;
167
- return ids.has(tool.id) || catalog.actions.some((action) => action.toolId === tool.id && ids.has(action.id));
168
- });
169
+ return this.view.connections(loadout);
169
170
  }
170
171
  async call(actionId, input = {}, options = {}) {
171
- const resolved = findSelected(this.catalog(), id(actionId, 'action id'), options.loadout);
172
- if (!resolved)
173
- throw new Error(`Action is unavailable: ${actionId}`);
174
- const driver = this.driverFor(resolved.tool);
175
- if (!driver)
176
- throw new Error(`No ${resolved.tool.source.type} driver is configured for ${resolved.tool.id}`);
177
- return driver.call({ ...resolved, input: validateInput(resolved.action.inputSchema, input), options });
178
- }
179
- hasDriver(tool) { return this.driverFor(tool) !== null; }
180
- async callMcp(name, input = {}, options = {}) {
181
- const resolved = selected(this.catalog(), options.loadout).find(({ action }) => mcpName(action) === name);
182
- if (!resolved)
183
- throw new Error(`Unknown MCP tool: ${name}`);
184
- return this.call(resolved.action.id, input, options);
185
- }
186
- async changed(toolId) {
187
- const catalog = this.catalog();
188
- const tool = catalog.tools.find((candidate) => candidate.id === toolId);
189
- if (tool && tool.origin !== 'system') {
190
- this.artifacts.writeTool({ tool, actions: catalog.actions.filter((action) => action.toolId === toolId) });
191
- }
192
- else if (!tool) {
193
- this.artifacts.removeTool(toolId);
194
- }
195
- this.artifacts.writeIndex(catalog);
196
- await this.onChange?.(catalog);
172
+ return this.view.call(actionId, input, options);
197
173
  }
198
- driverFor(tool) {
199
- const driver = this.drivers.get(tool.source.type);
200
- return driver && (driver.supports?.(tool) ?? true) ? driver : null;
174
+ hasDriver(tool) { return this.view.hasDriver(tool); }
175
+ async callMcp(name, input = {}, options = {}) {
176
+ return this.view.callMcp(name, input, options);
201
177
  }
202
178
  close() { this.store.close(); }
203
179
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Writable } from 'node:stream';
2
+ import type { ToolDeploymentActivation } from './deployment-types.js';
2
3
  type JsonSchema = {
3
4
  type: 'object';
4
5
  properties?: Record<string, {
@@ -41,6 +42,8 @@ interface McpSource extends BaseSource {
41
42
  args?: string[];
42
43
  cwd?: string;
43
44
  serverName?: string;
45
+ /** Absolute path mounted by the composing host for `transport: host`. */
46
+ route?: string;
44
47
  secretHeaders?: SecretReferences;
45
48
  secretEnv?: SecretReferences;
46
49
  }
@@ -109,7 +112,12 @@ interface ActionRecord extends Omit<ActionDefinition, 'id' | 'status' | 'inputSc
109
112
  }
110
113
  interface Catalog {
111
114
  version: 1;
115
+ /** Local materialization counter. Never used as immutable authority. */
112
116
  revision: number;
117
+ /** Exact digest of current user deployments and system projections. */
118
+ revisionId: string;
119
+ /** Current deployment id per stable user tool, including tombstones. */
120
+ deployments: Record<string, string>;
113
121
  tools: ToolRecord[];
114
122
  actions: ActionRecord[];
115
123
  }
@@ -151,9 +159,13 @@ interface ToolDriver {
151
159
  interface ToolboxOptions {
152
160
  stateDir?: string;
153
161
  databaseFile?: string;
162
+ /** Portable immutable deployment documents; may differ from databaseFile. */
163
+ deploymentDir?: string;
154
164
  systemTools?: ToolDefinition[];
155
165
  drivers?: ToolDriver[];
156
166
  onChange?: (catalog: Catalog) => void | Promise<void>;
167
+ /** Outbound local Live operation. Remote application never calls it. */
168
+ onDeployment?: (activation: ToolDeploymentActivation) => void | Promise<void>;
157
169
  }
158
170
  interface McpTool {
159
171
  name: string;
@@ -164,9 +176,26 @@ interface McpTool {
164
176
  interface McpOptions extends CallOptions {
165
177
  includeManagement?: boolean;
166
178
  }
179
+ interface HostMcpToolDescriptor {
180
+ name: string;
181
+ description?: string;
182
+ inputSchema?: JsonSchema;
183
+ }
184
+ interface HostMcpToolDefinitionInput {
185
+ id: string;
186
+ name: string;
187
+ description?: string;
188
+ serverName: string;
189
+ route: string;
190
+ tools: readonly HostMcpToolDescriptor[];
191
+ owner?: string;
192
+ display?: Record<string, unknown>;
193
+ metadata?: Record<string, unknown>;
194
+ }
167
195
  interface CliIo {
168
196
  stdout?: Writable;
169
197
  stderr?: Writable;
170
198
  toolbox?: import('./toolbox.js').Toolbox;
171
199
  }
172
- export type { ActionDefinition, ActionRecord, ActionTarget, ApiSource, ApiTarget, ApplyResult, CallOptions, Catalog, CliIo, CliSource, CliTarget, DriverCall, JsonSchema, Loadout, LoadoutInput, McpOptions, McpSource, McpTarget, McpTool, Origin, SecretResolver, Status, ToolDefinition, ToolDriver, ToolboxOptions, ToolRecord, ToolResult, ToolPatch, ActionPatch, ToolQuery, ToolSource, ToolType, };
200
+ export type { ActionDefinition, ActionRecord, ActionTarget, ApiSource, ApiTarget, ApplyResult, CallOptions, Catalog, CliIo, CliSource, CliTarget, DriverCall, HostMcpToolDefinitionInput, HostMcpToolDescriptor, JsonSchema, Loadout, LoadoutInput, McpOptions, McpSource, McpTarget, McpTool, Origin, SecretResolver, Status, ToolDefinition, ToolDriver, ToolboxOptions, ToolRecord, ToolResult, ToolPatch, ActionPatch, ToolQuery, ToolSource, ToolType, };
201
+ export type { ToolDeployment, ToolDeploymentActivation, ToolDeploymentSnapshot, ToolDeploymentSurface, } from './deployment-types.js';
package/dist/updates.d.ts CHANGED
@@ -4,4 +4,5 @@ type UpdatePort = Pick<Toolbox, 'action' | 'apply' | 'get'>;
4
4
  declare function updateTool(toolbox: UpdatePort, toolId: string, patch: ToolPatch): Promise<ApplyResult>;
5
5
  declare function upsertAction(toolbox: UpdatePort, toolId: string, definition: ActionDefinition): Promise<ActionRecord>;
6
6
  declare function updateAction(toolbox: UpdatePort, actionId: string, patch: ActionPatch): Promise<ActionRecord>;
7
- export { updateAction, updateTool, upsertAction };
7
+ declare function removeAction(toolbox: UpdatePort, actionId: string): Promise<ActionRecord>;
8
+ export { removeAction, updateAction, updateTool, upsertAction };
package/dist/updates.js CHANGED
@@ -54,4 +54,15 @@ async function updateAction(toolbox, actionId, patch) {
54
54
  metadata: mergedObject(definition.metadata, patch.metadata),
55
55
  });
56
56
  }
57
- export { updateAction, updateTool, upsertAction };
57
+ async function removeAction(toolbox, actionId) {
58
+ const action = toolbox.action(actionId);
59
+ if (!action)
60
+ throw new Error(`Unknown action: ${actionId}`);
61
+ const current = toolbox.get(action.toolId);
62
+ await toolbox.apply({
63
+ ...toolDefinition(current),
64
+ actions: current.actions.filter((candidate) => candidate.id !== actionId).map(actionDefinition),
65
+ });
66
+ return action;
67
+ }
68
+ export { removeAction, updateAction, updateTool, upsertAction };
@@ -1,57 +1,13 @@
1
- # Engine integration
1
+ # Historical Engine integration
2
2
 
3
- `@amalgm/tools` is the canonical Tools and Toolbox product. Amalgm Engine is a
4
- composition root and must adapt this public service rather than maintain a
5
- second registry.
3
+ Amalgm Engine is deprecated. Its Toolbox tables, MCP-connection registry,
4
+ notification adapter, and copied product output are read-only migration and
5
+ parity evidence. They are not active persistence, a source-flow destination,
6
+ or a runtime fallback.
6
7
 
7
- ## Tools owns
8
+ The old workflow copied generated Tools output into Engine and imported legacy
9
+ records. Active composition now installs the published package in
10
+ `@amalgm/shell`, which supplies host effects and projects other products
11
+ through their official MCP descriptors.
8
12
 
9
- - tool and action definition, validation, identity, and persistence;
10
- - atomic apply, enable, disable, and removal behavior;
11
- - loadout selection semantics and deterministic MCP action names;
12
- - CLI and HTTP API execution drivers and output bounds;
13
- - SDK, CLI, Toolbox management MCP tools, and action MCP projection;
14
- - the connection projection consumed by an MCP session host.
15
-
16
- ## Engine owns
17
-
18
- - first-party action implementations supplied as `systemTools` and drivers;
19
- - authenticated capability context and the execution-time secret resolver;
20
- - the long-lived MCP connection/session host;
21
- - state-event projection, REST shape translation, UI composition, and one-time
22
- import from Engine's superseded Toolbox tables or JSON file.
23
-
24
- Agents owns agent records and their loadout ids. Tools treats those ids as
25
- input to product-owned selection. Neither product is allowed to rewrite the
26
- other through a Core adapter.
27
-
28
- Engine adapters call one `Toolbox` instance. They do not write its SQLite
29
- database or independently normalize, select, name, or execute tool actions.
30
- An absent loadout must be passed as `undefined`; an explicitly empty loadout
31
- must be passed as `[]`.
32
-
33
- For external MCP tools, Engine supplies a `ToolDriver` with type `mcp`. That
34
- driver delegates to Engine's existing MCP session owner. The Toolbox exposes
35
- connection definitions through `connections(loadout)` but never starts a
36
- parallel client or owns the same remote session twice.
37
-
38
- System tools are injected at construction and remain outside user storage.
39
- Engine can therefore update its built-in catalog with its own release while
40
- all standalone Toolbox behavior remains canonical in this package.
41
-
42
- ## Source flow and completed cutover
43
-
44
- Engine now consumes Tools as follows:
45
-
46
- 1. Build this repository.
47
- 2. Copy generated `dist/`, `PURPOSE.md`, and package metadata into
48
- `runtime/products/tools/` with a deterministic source manifest.
49
- 3. Mount the package CLI and MCP definitions over Engine's single Toolbox
50
- service.
51
- 4. Import existing records once, preserving canonical ids and status.
52
- 5. Remove Engine's superseded Toolbox tables after successful import.
53
-
54
- Product changes begin here. Generated Engine copies are never edited by hand.
55
- Engine runtime readers and writers use only this product database. The importer
56
- is the only module allowed to understand the superseded storage shapes, and
57
- malformed source data blocks destructive cleanup.
13
+ See [SHELL_INTEGRATION.md](./SHELL_INTEGRATION.md) for the current boundary.