@amalgm/tools 0.1.4 → 0.1.5
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/PURPOSE.md +12 -38
- package/README.md +0 -22
- package/dist/artifact-files.d.ts +1 -3
- package/dist/artifact-files.js +3 -11
- package/dist/artifacts.d.ts +6 -8
- package/dist/artifacts.js +6 -19
- package/dist/definition.js +0 -10
- package/dist/http.js +1 -6
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/mcp-server.d.ts +1 -2
- package/dist/mcp-server.js +2 -3
- package/dist/mcp.d.ts +1 -2
- package/dist/mcp.js +21 -33
- package/dist/notifications.js +3 -4
- package/dist/schema.js +0 -15
- package/dist/store.d.ts +4 -17
- package/dist/store.js +41 -105
- package/dist/toolbox.d.ts +5 -13
- package/dist/toolbox.js +114 -90
- package/dist/types.d.ts +1 -30
- package/dist/updates.d.ts +1 -2
- package/dist/updates.js +1 -12
- package/docs/ENGINE_INTEGRATION.md +2 -5
- package/package.json +4 -4
- package/dist/apply-definition.d.ts +0 -12
- package/dist/apply-definition.js +0 -33
- package/dist/deployment-files.d.ts +0 -10
- package/dist/deployment-files.js +0 -38
- package/dist/deployment-types.d.ts +0 -32
- package/dist/deployment-types.js +0 -1
- package/dist/deployments.d.ts +0 -19
- package/dist/deployments.js +0 -98
- package/dist/host-mcp-tool.d.ts +0 -8
- package/dist/host-mcp-tool.js +0 -40
- package/dist/toolbox-deployments.d.ts +0 -30
- package/dist/toolbox-deployments.js +0 -125
- package/dist/toolbox-projection.d.ts +0 -13
- package/dist/toolbox-projection.js +0 -41
- package/dist/toolbox-view.d.ts +0 -35
- package/dist/toolbox-view.js +0 -75
package/dist/store.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
|
-
import { canonicalJson, deploymentRevisionId } from './deployments.js';
|
|
5
4
|
import { migrate } from './schema.js';
|
|
6
5
|
function parse(row) {
|
|
7
6
|
return row ? JSON.parse(row.record_json) : null;
|
|
@@ -33,128 +32,65 @@ class Store {
|
|
|
33
32
|
return this.db.prepare('SELECT record_json FROM actions WHERE tool_id = ? ORDER BY id')
|
|
34
33
|
.all(toolId).map((row) => parse(row)).filter(Boolean);
|
|
35
34
|
}
|
|
36
|
-
heads() {
|
|
37
|
-
return Object.fromEntries(this.db.prepare('SELECT tool_id, deployment_id FROM tool_heads ORDER BY tool_id').all()
|
|
38
|
-
.map((row) => [row.tool_id, row.deployment_id]));
|
|
39
|
-
}
|
|
40
35
|
catalog() {
|
|
41
36
|
const tools = this.db.prepare('SELECT record_json FROM tools ORDER BY id')
|
|
42
37
|
.all().map((row) => parse(row)).filter(Boolean);
|
|
43
38
|
const actions = this.db.prepare('SELECT record_json FROM actions ORDER BY id')
|
|
44
39
|
.all().map((row) => parse(row)).filter(Boolean);
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
version: 1,
|
|
48
|
-
revision: this.revision(),
|
|
49
|
-
revisionId: deploymentRevisionId({ heads: deployments }),
|
|
50
|
-
deployments,
|
|
51
|
-
tools,
|
|
52
|
-
actions,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
currentDeploymentId(toolId) {
|
|
56
|
-
const row = this.db.prepare('SELECT deployment_id FROM tool_heads WHERE tool_id = ?').get(toolId);
|
|
57
|
-
return row?.deployment_id || null;
|
|
58
|
-
}
|
|
59
|
-
getDeployment(id) {
|
|
60
|
-
return parse(this.db.prepare('SELECT record_json FROM tool_deployments WHERE id = ?').get(id));
|
|
61
|
-
}
|
|
62
|
-
deploymentsFor(toolId) {
|
|
63
|
-
return this.db.prepare(`
|
|
64
|
-
SELECT record_json FROM tool_deployments
|
|
65
|
-
WHERE tool_id = ? ORDER BY created_at, id
|
|
66
|
-
`).all(toolId).map((row) => parse(row)).filter(Boolean);
|
|
67
|
-
}
|
|
68
|
-
allDeployments() {
|
|
69
|
-
return this.db.prepare(`
|
|
70
|
-
SELECT record_json FROM tool_deployments ORDER BY created_at, id
|
|
71
|
-
`).all().map((row) => parse(row)).filter(Boolean);
|
|
72
|
-
}
|
|
73
|
-
deploymentSnapshot() {
|
|
74
|
-
return {
|
|
75
|
-
version: 1,
|
|
76
|
-
deployments: Object.fromEntries(this.allDeployments().map((deployment) => [deployment.id, deployment])),
|
|
77
|
-
heads: this.heads(),
|
|
78
|
-
};
|
|
40
|
+
return { version: 1, revision: this.revision(), tools, actions };
|
|
79
41
|
}
|
|
80
|
-
|
|
81
|
-
adoptDeployment(activation) {
|
|
82
|
-
const deployment = activation.deployment;
|
|
42
|
+
apply(result) {
|
|
83
43
|
this.db.transaction(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
44
|
+
this.db.prepare(`
|
|
45
|
+
INSERT INTO tools (id, origin, status, record_json)
|
|
46
|
+
VALUES (@id, @origin, @status, @json)
|
|
47
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
48
|
+
origin = excluded.origin, status = excluded.status, record_json = excluded.record_json
|
|
49
|
+
`).run({ id: result.tool.id, origin: result.tool.origin, status: result.tool.status, json: JSON.stringify(result.tool) });
|
|
50
|
+
this.db.prepare('DELETE FROM actions WHERE tool_id = ?').run(result.tool.id);
|
|
51
|
+
const insert = this.db.prepare('INSERT INTO actions (id, tool_id, status, record_json) VALUES (?, ?, ?, ?)');
|
|
52
|
+
for (const action of result.actions)
|
|
53
|
+
insert.run(action.id, action.toolId, action.status, JSON.stringify(action));
|
|
54
|
+
this.bump();
|
|
88
55
|
})();
|
|
56
|
+
return result;
|
|
89
57
|
}
|
|
90
|
-
|
|
91
|
-
const deployment = activation.deployment;
|
|
58
|
+
setStatus(id, status, now) {
|
|
92
59
|
return this.db.transaction(() => {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
60
|
+
const tool = this.getTool(id);
|
|
61
|
+
if (tool) {
|
|
62
|
+
const updated = { ...tool, status, updatedAt: now };
|
|
63
|
+
this.db.prepare('UPDATE tools SET status = ?, record_json = ? WHERE id = ?')
|
|
64
|
+
.run(status, JSON.stringify(updated), id);
|
|
65
|
+
this.bump();
|
|
66
|
+
return updated;
|
|
99
67
|
}
|
|
100
|
-
this.
|
|
101
|
-
|
|
102
|
-
|
|
68
|
+
const action = this.getAction(id);
|
|
69
|
+
if (!action)
|
|
70
|
+
return null;
|
|
71
|
+
const updated = { ...action, status, updatedAt: now };
|
|
72
|
+
this.db.prepare('UPDATE actions SET status = ?, record_json = ? WHERE id = ?')
|
|
73
|
+
.run(status, JSON.stringify(updated), id);
|
|
103
74
|
this.bump();
|
|
104
|
-
return
|
|
75
|
+
return updated;
|
|
105
76
|
})();
|
|
106
77
|
}
|
|
107
|
-
|
|
108
|
-
this.db.transaction(() => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.insertDeployment(deployment);
|
|
115
|
-
for (const [toolId, deploymentId] of Object.entries(snapshot.heads)) {
|
|
116
|
-
const deployment = snapshot.deployments[deploymentId];
|
|
117
|
-
this.materializeDefinition(toolId, deployment.definition);
|
|
118
|
-
this.setHead(toolId, deploymentId);
|
|
78
|
+
remove(id) {
|
|
79
|
+
return this.db.transaction(() => {
|
|
80
|
+
const tool = this.getTool(id);
|
|
81
|
+
if (tool) {
|
|
82
|
+
this.db.prepare('DELETE FROM tools WHERE id = ?').run(id);
|
|
83
|
+
this.bump();
|
|
84
|
+
return tool;
|
|
119
85
|
}
|
|
86
|
+
const action = this.getAction(id);
|
|
87
|
+
if (!action)
|
|
88
|
+
return null;
|
|
89
|
+
this.db.prepare('DELETE FROM actions WHERE id = ?').run(id);
|
|
120
90
|
this.bump();
|
|
91
|
+
return action;
|
|
121
92
|
})();
|
|
122
93
|
}
|
|
123
|
-
insertDeployment(deployment) {
|
|
124
|
-
this.db.prepare(`
|
|
125
|
-
INSERT INTO tool_deployments
|
|
126
|
-
(id, tool_id, previous_deployment_id, created_at, record_json)
|
|
127
|
-
VALUES (?, ?, ?, ?, ?)
|
|
128
|
-
`).run(deployment.id, deployment.subjectId, deployment.previousDeploymentId, deployment.createdAt, JSON.stringify(deployment));
|
|
129
|
-
}
|
|
130
|
-
setHead(toolId, deploymentId) {
|
|
131
|
-
this.db.prepare(`
|
|
132
|
-
INSERT INTO tool_heads (tool_id, deployment_id) VALUES (?, ?)
|
|
133
|
-
ON CONFLICT(tool_id) DO UPDATE SET deployment_id = excluded.deployment_id
|
|
134
|
-
`).run(toolId, deploymentId);
|
|
135
|
-
}
|
|
136
|
-
materializeDefinition(toolId, result) {
|
|
137
|
-
if (result === null) {
|
|
138
|
-
this.db.prepare('DELETE FROM tools WHERE id = ?').run(toolId);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
this.db.prepare(`
|
|
142
|
-
INSERT INTO tools (id, origin, status, record_json)
|
|
143
|
-
VALUES (@id, @origin, @status, @json)
|
|
144
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
145
|
-
origin = excluded.origin, status = excluded.status, record_json = excluded.record_json
|
|
146
|
-
`).run({
|
|
147
|
-
id: result.tool.id,
|
|
148
|
-
origin: result.tool.origin,
|
|
149
|
-
status: result.tool.status,
|
|
150
|
-
json: JSON.stringify(result.tool),
|
|
151
|
-
});
|
|
152
|
-
this.db.prepare('DELETE FROM actions WHERE tool_id = ?').run(result.tool.id);
|
|
153
|
-
const insert = this.db.prepare('INSERT INTO actions (id, tool_id, status, record_json) VALUES (?, ?, ?, ?)');
|
|
154
|
-
for (const action of result.actions) {
|
|
155
|
-
insert.run(action.id, action.toolId, action.status, JSON.stringify(action));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
94
|
close() { this.db.close(); }
|
|
159
95
|
}
|
|
160
96
|
export { Store };
|
package/dist/toolbox.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { ActionDefinition, ActionPatch, ActionRecord, ApplyResult, CallOptions, Catalog, LoadoutInput, ToolDefinition, ToolboxOptions,
|
|
1
|
+
import type { ActionDefinition, ActionPatch, ActionRecord, ApplyResult, CallOptions, Catalog, LoadoutInput, ToolDefinition, ToolboxOptions, ToolPatch, ToolQuery, ToolRecord, ToolResult } from './types.js';
|
|
2
2
|
declare class Toolbox {
|
|
3
3
|
private readonly store;
|
|
4
|
-
private readonly
|
|
5
|
-
private readonly projection;
|
|
4
|
+
private readonly artifacts;
|
|
6
5
|
private readonly system;
|
|
7
6
|
private readonly drivers;
|
|
8
|
-
private readonly
|
|
7
|
+
private readonly onChange?;
|
|
9
8
|
constructor(options?: ToolboxOptions);
|
|
10
9
|
catalog(): Catalog;
|
|
11
10
|
list(loadout?: LoadoutInput): Array<{
|
|
@@ -36,19 +35,12 @@ declare class Toolbox {
|
|
|
36
35
|
updateAction(actionId: string, patch: ActionPatch): Promise<ActionRecord>;
|
|
37
36
|
setStatus(recordId: string, status: 'enabled' | 'disabled'): Promise<ToolRecord | ActionRecord>;
|
|
38
37
|
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;
|
|
48
38
|
connections(loadout?: LoadoutInput): ToolRecord[];
|
|
49
39
|
call(actionId: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
|
|
50
40
|
hasDriver(tool: ToolRecord): boolean;
|
|
51
41
|
callMcp(name: string, input?: unknown, options?: CallOptions): Promise<ToolResult>;
|
|
42
|
+
private changed;
|
|
43
|
+
private driverFor;
|
|
52
44
|
close(): void;
|
|
53
45
|
}
|
|
54
46
|
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';
|
|
6
5
|
import { ArtifactFiles } from './artifact-files.js';
|
|
7
|
-
import { preserveProjectionTime } from './artifacts.js';
|
|
8
6
|
import { cliDriver } from './cli-driver.js';
|
|
9
|
-
import { DeploymentFiles } from './deployment-files.js';
|
|
10
|
-
import { deploymentRevisionId } from './deployments.js';
|
|
11
7
|
import { normalizeDefinition } from './definition.js';
|
|
12
|
-
import { assertMcpNamesUnique, id } from './ids.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';
|
|
13
12
|
import { Store } from './store.js';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import { updateAction, updateTool, upsertAction } from './updates.js';
|
|
14
|
+
function semantic(value) {
|
|
15
|
+
const { createdAt, updatedAt, ...record } = value;
|
|
16
|
+
return JSON.stringify(record);
|
|
17
|
+
}
|
|
18
18
|
function defaultDatabase(options) {
|
|
19
19
|
if (options.databaseFile)
|
|
20
20
|
return path.resolve(options.databaseFile);
|
|
@@ -32,17 +32,15 @@ function defaultDatabase(options) {
|
|
|
32
32
|
}
|
|
33
33
|
class Toolbox {
|
|
34
34
|
store;
|
|
35
|
-
|
|
36
|
-
projection;
|
|
35
|
+
artifacts;
|
|
37
36
|
system;
|
|
38
37
|
drivers = new Map();
|
|
39
|
-
|
|
38
|
+
onChange;
|
|
40
39
|
constructor(options = {}) {
|
|
41
40
|
const databaseFile = defaultDatabase(options);
|
|
42
41
|
this.store = new Store(databaseFile);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const priorIndex = artifacts.readIndex();
|
|
42
|
+
this.artifacts = new ArtifactFiles(path.dirname(databaseFile));
|
|
43
|
+
this.onChange = options.onChange;
|
|
46
44
|
for (const driver of [cliDriver, apiDriver, ...(options.drivers || [])])
|
|
47
45
|
this.drivers.set(driver.type, driver);
|
|
48
46
|
const tools = [];
|
|
@@ -51,31 +49,13 @@ class Toolbox {
|
|
|
51
49
|
const normalized = normalizeDefinition({ ...definition, origin: 'system' });
|
|
52
50
|
if (tools.some((tool) => tool.id === normalized.tool.id))
|
|
53
51
|
throw new Error(`Duplicate system tool: ${normalized.tool.id}`);
|
|
54
|
-
tools.push(
|
|
55
|
-
actions.push(...normalized.actions
|
|
52
|
+
tools.push(normalized.tool);
|
|
53
|
+
actions.push(...normalized.actions);
|
|
56
54
|
}
|
|
57
55
|
assertMcpNamesUnique(actions);
|
|
58
|
-
this.system = {
|
|
59
|
-
|
|
60
|
-
|
|
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();
|
|
56
|
+
this.system = { version: 1, revision: 0, tools, actions };
|
|
57
|
+
this.artifacts.migrateLegacy();
|
|
58
|
+
this.artifacts.materialize(this.catalog());
|
|
79
59
|
}
|
|
80
60
|
catalog() {
|
|
81
61
|
const persisted = this.store.catalog();
|
|
@@ -84,37 +64,66 @@ class Toolbox {
|
|
|
84
64
|
const userActions = persisted.actions.filter((action) => !systemIds.has(action.toolId));
|
|
85
65
|
const catalog = {
|
|
86
66
|
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,
|
|
93
67
|
tools: [...this.system.tools, ...userTools].sort((a, b) => a.id.localeCompare(b.id)),
|
|
94
68
|
actions: [...this.system.actions, ...userActions].sort((a, b) => a.id.localeCompare(b.id)),
|
|
95
69
|
};
|
|
96
70
|
assertMcpNamesUnique(catalog.actions);
|
|
97
71
|
return catalog;
|
|
98
72
|
}
|
|
99
|
-
list(loadout) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
}
|
|
104
95
|
resolve(toolId, actionReference, loadout) {
|
|
105
|
-
return this
|
|
96
|
+
return resolveAction(this, toolId, actionReference, loadout);
|
|
106
97
|
}
|
|
107
98
|
resolveCallable(toolId, actionReference, loadout, filters = {}) {
|
|
108
|
-
return this
|
|
99
|
+
return resolveCallableAction(this, toolId, actionReference, loadout, filters);
|
|
109
100
|
}
|
|
110
101
|
async apply(definition) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
118
127
|
}
|
|
119
128
|
async update(toolId, patch) {
|
|
120
129
|
return updateTool(this, toolId, patch);
|
|
@@ -130,50 +139,65 @@ class Toolbox {
|
|
|
130
139
|
if (this.system.tools.some((tool) => tool.id === value)
|
|
131
140
|
|| this.system.actions.some((action) => action.id === value))
|
|
132
141
|
throw new Error(`System record is immutable: ${value}`);
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
return tool.tool.status === status ? tool.tool : (await this.update(value, { status })).tool;
|
|
136
|
-
const action = this.action(value);
|
|
137
|
-
if (!action)
|
|
142
|
+
const updated = this.store.setStatus(value, status, new Date().toISOString());
|
|
143
|
+
if (!updated)
|
|
138
144
|
throw new Error(`Unknown tool or action: ${value}`);
|
|
139
|
-
|
|
145
|
+
await this.changed('toolId' in updated ? updated.toolId : updated.id);
|
|
146
|
+
return updated;
|
|
140
147
|
}
|
|
141
148
|
async remove(recordId) {
|
|
142
149
|
const value = id(recordId, 'record id');
|
|
143
150
|
if (this.system.tools.some((tool) => tool.id === value)
|
|
144
151
|
|| this.system.actions.some((action) => action.id === value))
|
|
145
152
|
throw new Error(`System record is immutable: ${value}`);
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
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();
|
|
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;
|
|
167
158
|
}
|
|
168
159
|
connections(loadout) {
|
|
169
|
-
|
|
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
|
+
});
|
|
170
169
|
}
|
|
171
170
|
async call(actionId, input = {}, options = {}) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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; }
|
|
175
180
|
async callMcp(name, input = {}, options = {}) {
|
|
176
|
-
|
|
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);
|
|
197
|
+
}
|
|
198
|
+
driverFor(tool) {
|
|
199
|
+
const driver = this.drivers.get(tool.source.type);
|
|
200
|
+
return driver && (driver.supports?.(tool) ?? true) ? driver : null;
|
|
177
201
|
}
|
|
178
202
|
close() { this.store.close(); }
|
|
179
203
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Writable } from 'node:stream';
|
|
2
|
-
import type { ToolDeploymentActivation } from './deployment-types.js';
|
|
3
2
|
type JsonSchema = {
|
|
4
3
|
type: 'object';
|
|
5
4
|
properties?: Record<string, {
|
|
@@ -42,8 +41,6 @@ interface McpSource extends BaseSource {
|
|
|
42
41
|
args?: string[];
|
|
43
42
|
cwd?: string;
|
|
44
43
|
serverName?: string;
|
|
45
|
-
/** Absolute path mounted by the composing host for `transport: host`. */
|
|
46
|
-
route?: string;
|
|
47
44
|
secretHeaders?: SecretReferences;
|
|
48
45
|
secretEnv?: SecretReferences;
|
|
49
46
|
}
|
|
@@ -112,12 +109,7 @@ interface ActionRecord extends Omit<ActionDefinition, 'id' | 'status' | 'inputSc
|
|
|
112
109
|
}
|
|
113
110
|
interface Catalog {
|
|
114
111
|
version: 1;
|
|
115
|
-
/** Local materialization counter. Never used as immutable authority. */
|
|
116
112
|
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>;
|
|
121
113
|
tools: ToolRecord[];
|
|
122
114
|
actions: ActionRecord[];
|
|
123
115
|
}
|
|
@@ -159,13 +151,9 @@ interface ToolDriver {
|
|
|
159
151
|
interface ToolboxOptions {
|
|
160
152
|
stateDir?: string;
|
|
161
153
|
databaseFile?: string;
|
|
162
|
-
/** Portable immutable deployment documents; may differ from databaseFile. */
|
|
163
|
-
deploymentDir?: string;
|
|
164
154
|
systemTools?: ToolDefinition[];
|
|
165
155
|
drivers?: ToolDriver[];
|
|
166
156
|
onChange?: (catalog: Catalog) => void | Promise<void>;
|
|
167
|
-
/** Outbound local Live operation. Remote application never calls it. */
|
|
168
|
-
onDeployment?: (activation: ToolDeploymentActivation) => void | Promise<void>;
|
|
169
157
|
}
|
|
170
158
|
interface McpTool {
|
|
171
159
|
name: string;
|
|
@@ -176,26 +164,9 @@ interface McpTool {
|
|
|
176
164
|
interface McpOptions extends CallOptions {
|
|
177
165
|
includeManagement?: boolean;
|
|
178
166
|
}
|
|
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
|
-
}
|
|
195
167
|
interface CliIo {
|
|
196
168
|
stdout?: Writable;
|
|
197
169
|
stderr?: Writable;
|
|
198
170
|
toolbox?: import('./toolbox.js').Toolbox;
|
|
199
171
|
}
|
|
200
|
-
export type { ActionDefinition, ActionRecord, ActionTarget, ApiSource, ApiTarget, ApplyResult, CallOptions, Catalog, CliIo, CliSource, CliTarget, DriverCall,
|
|
201
|
-
export type { ToolDeployment, ToolDeploymentActivation, ToolDeploymentSnapshot, ToolDeploymentSurface, } from './deployment-types.js';
|
|
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, };
|
package/dist/updates.d.ts
CHANGED
|
@@ -4,5 +4,4 @@ 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
|
-
|
|
8
|
-
export { removeAction, updateAction, updateTool, upsertAction };
|
|
7
|
+
export { updateAction, updateTool, upsertAction };
|
package/dist/updates.js
CHANGED
|
@@ -54,15 +54,4 @@ async function updateAction(toolbox, actionId, patch) {
|
|
|
54
54
|
metadata: mergedObject(definition.metadata, patch.metadata),
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
-
|
|
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 };
|
|
57
|
+
export { updateAction, updateTool, upsertAction };
|
|
@@ -8,7 +8,6 @@ second registry.
|
|
|
8
8
|
|
|
9
9
|
- tool and action definition, validation, identity, and persistence;
|
|
10
10
|
- atomic apply, enable, disable, and removal behavior;
|
|
11
|
-
- immutable deployment history, current heads, and the Tools Live surface;
|
|
12
11
|
- loadout selection semantics and deterministic MCP action names;
|
|
13
12
|
- CLI and HTTP API execution drivers and output bounds;
|
|
14
13
|
- SDK, CLI, Toolbox management MCP tools, and action MCP projection;
|
|
@@ -19,8 +18,7 @@ second registry.
|
|
|
19
18
|
- first-party action implementations supplied as `systemTools` and drivers;
|
|
20
19
|
- authenticated capability context and the execution-time secret resolver;
|
|
21
20
|
- the long-lived MCP connection/session host;
|
|
22
|
-
-
|
|
23
|
-
state-event projection, REST shape translation, UI composition, and one-time
|
|
21
|
+
- state-event projection, REST shape translation, UI composition, and one-time
|
|
24
22
|
import from Engine's superseded Toolbox tables or JSON file.
|
|
25
23
|
|
|
26
24
|
Agents owns agent records and their loadout ids. Tools treats those ids as
|
|
@@ -28,8 +26,7 @@ input to product-owned selection. Neither product is allowed to rewrite the
|
|
|
28
26
|
other through a Core adapter.
|
|
29
27
|
|
|
30
28
|
Engine adapters call one `Toolbox` instance. They do not write its SQLite
|
|
31
|
-
database
|
|
32
|
-
name, deploy, or execute tool actions.
|
|
29
|
+
database or independently normalize, select, name, or execute tool actions.
|
|
33
30
|
An absent loadout must be passed as `undefined`; an explicitly empty loadout
|
|
34
31
|
must be passed as `[]`.
|
|
35
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Local-first tool definitions, Toolbox registry, and agent execution surfaces.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "
|
|
42
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsx scripts/mark-executables.ts",
|
|
43
43
|
"check": "tsx scripts/check-tree.ts && tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
44
44
|
"test": "tsx --test --test-concurrency=1 --test-timeout=30000 test/*.test.ts",
|
|
45
45
|
"verify": "npm run check && npm run build && npm test",
|
|
46
46
|
"prepack": "npm run build"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=24"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@amalgm/core": "0.
|
|
52
|
+
"@amalgm/core": "0.4.7",
|
|
53
53
|
"better-sqlite3": "^12.6.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Store } from './store.js';
|
|
2
|
-
import { ToolboxDeployments } from './toolbox-deployments.js';
|
|
3
|
-
import type { ActionRecord, ApplyResult, ToolDefinition, ToolRecord } from './types.js';
|
|
4
|
-
/** Normalize and deploy one authoritative user definition. */
|
|
5
|
-
declare function applyDefinition(options: {
|
|
6
|
-
definition: ToolDefinition;
|
|
7
|
-
store: Store;
|
|
8
|
-
deployments: ToolboxDeployments;
|
|
9
|
-
systemTools: readonly ToolRecord[];
|
|
10
|
-
systemActions: readonly ActionRecord[];
|
|
11
|
-
}): Promise<ApplyResult>;
|
|
12
|
-
export { applyDefinition };
|