@amalgm/tools 0.1.5 → 0.1.7-main.34175698435
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 +78 -26
- package/README.md +63 -10
- package/dist/apply-definition.d.ts +12 -0
- package/dist/apply-definition.js +33 -0
- package/dist/artifact-files.d.ts +3 -1
- package/dist/artifact-files.js +11 -3
- package/dist/artifacts.d.ts +8 -6
- package/dist/artifacts.js +19 -6
- package/dist/definition.js +10 -0
- package/dist/deployment-files.d.ts +10 -0
- package/dist/deployment-files.js +38 -0
- package/dist/deployment-types.d.ts +32 -0
- package/dist/deployment-types.js +1 -0
- package/dist/deployments.d.ts +19 -0
- package/dist/deployments.js +98 -0
- package/dist/host-mcp-tool.d.ts +8 -0
- package/dist/host-mcp-tool.js +40 -0
- package/dist/http.js +6 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/mcp-server.d.ts +2 -1
- package/dist/mcp-server.js +3 -2
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +33 -21
- package/dist/notifications.js +4 -3
- package/dist/schema.js +15 -0
- package/dist/store.d.ts +17 -4
- package/dist/store.js +105 -41
- package/dist/toolbox-deployments.d.ts +30 -0
- package/dist/toolbox-deployments.js +125 -0
- package/dist/toolbox-projection.d.ts +13 -0
- package/dist/toolbox-projection.js +41 -0
- package/dist/toolbox-view.d.ts +35 -0
- package/dist/toolbox-view.js +75 -0
- package/dist/toolbox.d.ts +13 -5
- package/dist/toolbox.js +90 -114
- package/dist/types.d.ts +30 -1
- package/dist/updates.d.ts +2 -1
- package/dist/updates.js +12 -1
- package/docs/ENGINE_INTEGRATION.md +10 -54
- package/docs/SHELL_INTEGRATION.md +44 -0
- package/package.json +40 -4
- package/skills/tools/SKILL.md +185 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { TOOL_DEPLOYMENT_CONTRACT, assertToolDeploymentActivation, createToolDeployment, } from './deployments.js';
|
|
2
|
+
import { assertMcpNamesUnique, id } from './ids.js';
|
|
3
|
+
/** The Toolbox deployment state machine; adapters only observe its operations. */
|
|
4
|
+
class ToolboxDeployments {
|
|
5
|
+
options;
|
|
6
|
+
observers = new Set();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
adoptLegacy() {
|
|
11
|
+
const { store, systemTools, files } = this.options;
|
|
12
|
+
for (const tool of store.catalog().tools) {
|
|
13
|
+
if (store.currentDeploymentId(tool.id) || systemTools.some((system) => system.id === tool.id))
|
|
14
|
+
continue;
|
|
15
|
+
store.adoptDeployment(createToolDeployment({
|
|
16
|
+
toolId: tool.id,
|
|
17
|
+
previousDeploymentId: null,
|
|
18
|
+
definition: { tool, actions: store.actionsFor(tool.id) },
|
|
19
|
+
createdAt: tool.updatedAt,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
files.materialize(store.allDeployments());
|
|
23
|
+
}
|
|
24
|
+
async activateLocal(definition, deletedToolId) {
|
|
25
|
+
const toolId = definition?.tool.id || deletedToolId;
|
|
26
|
+
if (!toolId)
|
|
27
|
+
throw new Error('Tool deployment requires a stable tool id');
|
|
28
|
+
const activation = createToolDeployment({
|
|
29
|
+
toolId,
|
|
30
|
+
previousDeploymentId: this.options.store.currentDeploymentId(toolId),
|
|
31
|
+
definition,
|
|
32
|
+
createdAt: definition?.tool.updatedAt || new Date().toISOString(),
|
|
33
|
+
});
|
|
34
|
+
const applied = this.options.store.applyDeployment(activation);
|
|
35
|
+
if (!applied.changed)
|
|
36
|
+
return;
|
|
37
|
+
this.options.files.write(activation.deployment);
|
|
38
|
+
await this.options.project(toolId);
|
|
39
|
+
for (const observer of this.observers)
|
|
40
|
+
observer(activation);
|
|
41
|
+
await this.options.onDeployment?.(activation);
|
|
42
|
+
}
|
|
43
|
+
current(toolId) {
|
|
44
|
+
const value = id(toolId, 'tool id');
|
|
45
|
+
const deploymentId = this.options.store.currentDeploymentId(value);
|
|
46
|
+
return deploymentId ? this.options.store.getDeployment(deploymentId) : null;
|
|
47
|
+
}
|
|
48
|
+
get(deploymentId) {
|
|
49
|
+
return this.options.store.getDeployment(id(deploymentId, 'deployment id', 96));
|
|
50
|
+
}
|
|
51
|
+
history(toolId) {
|
|
52
|
+
return this.options.store.deploymentsFor(id(toolId, 'tool id'));
|
|
53
|
+
}
|
|
54
|
+
snapshot() {
|
|
55
|
+
return this.options.store.deploymentSnapshot();
|
|
56
|
+
}
|
|
57
|
+
async activateRemote(activation) {
|
|
58
|
+
this.validateActivation(activation);
|
|
59
|
+
const applied = this.options.store.applyDeployment(activation);
|
|
60
|
+
if (!applied.changed)
|
|
61
|
+
return applied.result;
|
|
62
|
+
this.options.files.write(activation.deployment);
|
|
63
|
+
await this.options.project(activation.deployment.subjectId);
|
|
64
|
+
return applied.result;
|
|
65
|
+
}
|
|
66
|
+
async applySnapshot(snapshot) {
|
|
67
|
+
if (!snapshot || snapshot.version !== 1 || !snapshot.deployments || !snapshot.heads) {
|
|
68
|
+
throw new Error('Invalid tool deployment snapshot');
|
|
69
|
+
}
|
|
70
|
+
for (const [deploymentId, deployment] of Object.entries(snapshot.deployments)) {
|
|
71
|
+
this.validateActivation({ type: 'deployment.activate', deployment }, false);
|
|
72
|
+
if (deploymentId !== deployment.id)
|
|
73
|
+
throw new Error(`Deployment snapshot key mismatch: ${deploymentId}`);
|
|
74
|
+
}
|
|
75
|
+
const currentDefinitions = [];
|
|
76
|
+
for (const [toolId, deploymentId] of Object.entries(snapshot.heads)) {
|
|
77
|
+
const deployment = snapshot.deployments[deploymentId];
|
|
78
|
+
if (!deployment || deployment.subjectId !== toolId) {
|
|
79
|
+
throw new Error(`Deployment snapshot head is invalid: ${toolId}`);
|
|
80
|
+
}
|
|
81
|
+
this.assertNotSystem(toolId);
|
|
82
|
+
if (deployment.definition)
|
|
83
|
+
currentDefinitions.push(deployment.definition);
|
|
84
|
+
}
|
|
85
|
+
assertMcpNamesUnique([
|
|
86
|
+
...this.options.systemActions,
|
|
87
|
+
...currentDefinitions.flatMap((definition) => definition.actions),
|
|
88
|
+
]);
|
|
89
|
+
const previousToolIds = this.options.store.catalog().tools.map((tool) => tool.id);
|
|
90
|
+
this.options.store.replaceDeploymentSnapshot(snapshot);
|
|
91
|
+
this.options.files.materialize(Object.values(snapshot.deployments));
|
|
92
|
+
await this.options.replaceProjection(previousToolIds);
|
|
93
|
+
}
|
|
94
|
+
surface() {
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
contract: TOOL_DEPLOYMENT_CONTRACT,
|
|
97
|
+
observeEdits: (emit) => {
|
|
98
|
+
this.observers.add(emit);
|
|
99
|
+
return () => this.observers.delete(emit);
|
|
100
|
+
},
|
|
101
|
+
applyRemote: async (operation) => {
|
|
102
|
+
await this.activateRemote(operation);
|
|
103
|
+
},
|
|
104
|
+
applySnapshot: (snapshot) => this.applySnapshot(snapshot),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
validateActivation(activation, checkNames = true) {
|
|
108
|
+
assertToolDeploymentActivation(activation);
|
|
109
|
+
const deployment = activation.deployment;
|
|
110
|
+
this.assertNotSystem(deployment.subjectId);
|
|
111
|
+
if (checkNames && deployment.definition) {
|
|
112
|
+
assertMcpNamesUnique([
|
|
113
|
+
...this.options.systemActions,
|
|
114
|
+
...this.options.store.catalog().actions.filter((action) => action.toolId !== deployment.subjectId),
|
|
115
|
+
...deployment.definition.actions,
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
assertNotSystem(toolId) {
|
|
120
|
+
if (this.options.systemTools.some((tool) => tool.id === toolId)) {
|
|
121
|
+
throw new Error(`System tool is immutable: ${toolId}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export { ToolboxDeployments };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ArtifactFiles } from './artifact-files.js';
|
|
2
|
+
import type { Catalog, ToolboxOptions } from './types.js';
|
|
3
|
+
/** Current-catalog compatibility projections; deployments remain authority. */
|
|
4
|
+
declare class ToolboxProjection {
|
|
5
|
+
private readonly artifacts;
|
|
6
|
+
private readonly catalog;
|
|
7
|
+
private readonly onChange?;
|
|
8
|
+
constructor(artifacts: ArtifactFiles, catalog: () => Catalog, onChange?: ToolboxOptions['onChange']);
|
|
9
|
+
boot(): void;
|
|
10
|
+
changed(toolId: string): Promise<void>;
|
|
11
|
+
replaced(previousToolIds: readonly string[]): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export { ToolboxProjection };
|
|
@@ -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
|
|
4
|
+
private readonly deploymentService;
|
|
5
|
+
private readonly projection;
|
|
5
6
|
private readonly system;
|
|
6
7
|
private readonly drivers;
|
|
7
|
-
private readonly
|
|
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
|
|
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 {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
35
|
+
deploymentService;
|
|
36
|
+
projection;
|
|
36
37
|
system;
|
|
37
38
|
drivers = new Map();
|
|
38
|
-
|
|
39
|
+
view;
|
|
39
40
|
constructor(options = {}) {
|
|
40
41
|
const databaseFile = defaultDatabase(options);
|
|
41
42
|
this.store = new Store(databaseFile);
|
|
42
|
-
|
|
43
|
-
|
|
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 = {
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
105
|
+
return this.view.resolve(toolId, actionReference, loadout);
|
|
97
106
|
}
|
|
98
107
|
resolveCallable(toolId, actionReference, loadout, filters = {}) {
|
|
99
|
-
return
|
|
108
|
+
return this.view.resolveCallable(toolId, actionReference, loadout, filters);
|
|
100
109
|
}
|
|
101
110
|
async apply(definition) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
143
|
-
if (
|
|
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
|
-
|
|
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
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
return
|
|
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
|
}
|