@amalgm/agents 0.1.2 → 0.2.0
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 +29 -22
- package/README.md +36 -46
- package/dist/agents.d.ts +4 -26
- package/dist/agents.js +4 -87
- package/dist/bin/mcp.js +1 -1
- package/dist/bundles/create.d.ts +13 -0
- package/dist/bundles/create.js +125 -0
- package/dist/bundles/graph-records.d.ts +10 -0
- package/dist/bundles/graph-records.js +61 -0
- package/dist/bundles/graph.d.ts +6 -0
- package/dist/bundles/graph.js +121 -0
- package/dist/bundles/install.d.ts +6 -0
- package/dist/bundles/install.js +64 -0
- package/dist/bundles/types.d.ts +97 -0
- package/dist/bundles/types.js +1 -0
- package/dist/bundles/util.d.ts +14 -0
- package/dist/bundles/util.js +56 -0
- package/dist/bundles/validate.d.ts +2 -0
- package/dist/bundles/validate.js +92 -0
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +2 -13
- package/dist/cli/open.d.ts +1 -1
- package/dist/cli/open.js +1 -5
- package/dist/cli/run.js +3 -8
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +0 -2
- package/dist/http/bundle-routes.d.ts +2 -0
- package/dist/http/bundle-routes.js +67 -0
- package/dist/http/server.js +4 -8
- package/dist/http-types.d.ts +3 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +5 -2
- package/dist/mcp/agent-tools.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools.js +1 -2
- package/dist/rows.d.ts +1 -4
- package/dist/rows.js +0 -39
- package/dist/schema.js +53 -101
- package/dist/types.d.ts +0 -106
- package/docs/ARCHITECTURE.md +21 -33
- package/docs/CLI.md +3 -43
- package/docs/DATA_MODEL.md +7 -46
- package/docs/DEFINITIONS.md +4 -4
- package/docs/ENGINE_INTEGRATION.md +22 -67
- package/docs/MCP.md +3 -19
- package/docs/REST.md +14 -85
- package/docs/SDK.md +12 -61
- package/docs/SECURITY.md +6 -35
- package/examples/basic.ts +8 -22
- package/package.json +2 -2
- package/skills/amalgm-agents/SKILL.md +3 -12
- package/skills/amalgm-agents/agents/openai.yaml +2 -2
- package/dist/cli/session-commands.d.ts +0 -4
- package/dist/cli/session-commands.js +0 -54
- package/dist/drivers.d.ts +0 -4
- package/dist/drivers.js +0 -25
- package/dist/event-store.d.ts +0 -13
- package/dist/event-store.js +0 -50
- package/dist/http/session-routes.d.ts +0 -2
- package/dist/http/session-routes.js +0 -67
- package/dist/http/stream.d.ts +0 -3
- package/dist/http/stream.js +0 -30
- package/dist/mcp/session-tools.d.ts +0 -3
- package/dist/mcp/session-tools.js +0 -81
- package/dist/messages.d.ts +0 -3
- package/dist/messages.js +0 -56
- package/dist/runtime.d.ts +0 -29
- package/dist/runtime.js +0 -176
- package/dist/session-store.d.ts +0 -15
- package/dist/session-store.js +0 -83
- package/dist/turn-store.d.ts +0 -31
- package/dist/turn-store.js +0 -164
- package/docs/DRIVERS.md +0 -72
package/PURPOSE.md
CHANGED
|
@@ -2,50 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Amalgm Agents is the small, standalone
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
execution to an injected driver; it does not duplicate their state.
|
|
5
|
+
Amalgm Agents is the small, standalone registry for agent identities,
|
|
6
|
+
installations, and immutable revisions. A user or another agent can create,
|
|
7
|
+
register, install, list, inspect, edit, and delete an available agent, then
|
|
8
|
+
resolve one exact revision for execution by Chat.
|
|
9
|
+
|
|
10
|
+
Shell composes Agents with Chat, harnesses, credentials, Toolbox, files, Live,
|
|
11
|
+
and UI. This package stores references to those products; it does not duplicate
|
|
12
|
+
their state or execute a conversation.
|
|
14
13
|
It also discovers installed agent skills across the canonical project and
|
|
15
14
|
harness roots so every transport presents the same available skill catalog.
|
|
16
15
|
Rich harness configuration is stored inside the same immutable definition and
|
|
17
16
|
projected onto its executable fields; it is not a second agent registry.
|
|
17
|
+
Portable bundle-v2 documents close an agent's subagent, skill, and tool graph;
|
|
18
|
+
hosts supply explicit ports for resources owned by other products.
|
|
18
19
|
|
|
19
20
|
## Primitives
|
|
20
21
|
|
|
21
22
|
- An **agent** is a stable identity with a complete current definition.
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
- A **
|
|
23
|
+
- An **installation** is one locally available agent identity and its Agent
|
|
24
|
+
Home descriptor.
|
|
25
|
+
- A **resolved agent** is one exact immutable revision selected for execution.
|
|
25
26
|
- The **Agents service** is the behavior shared by every public surface.
|
|
26
27
|
|
|
27
28
|
## Axioms
|
|
28
29
|
|
|
29
30
|
1. A user can create, list, inspect, edit, and delete agents.
|
|
30
|
-
2. A user or another agent can
|
|
31
|
-
|
|
31
|
+
2. A user or another agent can resolve an available agent to one exact
|
|
32
|
+
immutable revision without starting a conversation.
|
|
32
33
|
3. An agent definition can reference its harness, model, instructions, tools,
|
|
33
34
|
skills, files, credentials, and subagents without Agents owning those
|
|
34
35
|
products.
|
|
35
|
-
4.
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
4. Harness identity is part of an agent revision; provider execution belongs
|
|
37
|
+
to Chat behind a host runtime port.
|
|
38
|
+
5. Agent definitions, installations, and revisions survive restart.
|
|
38
39
|
6. SDK, CLI, REST, MCP, and skill workflows expose the same Agents behavior;
|
|
39
40
|
none is a second implementation.
|
|
40
41
|
7. An unchanged current definition is idempotent; every actual change creates
|
|
41
42
|
a new immutable revision, even when its content matches an earlier revision.
|
|
42
43
|
8. Agent configuration and its executable projection change atomically in one
|
|
43
44
|
immutable revision; native imports never ingest credentials or MCP config.
|
|
45
|
+
9. A portable bundle is accepted only when its flat graph is derivable from
|
|
46
|
+
its canonical records and every element is reachable from a declared head.
|
|
44
47
|
|
|
45
48
|
## Consequences
|
|
46
49
|
|
|
47
|
-
The package needs no
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
The package needs no conversation, turn, transcript, stream, reconnect,
|
|
51
|
+
interrupt, cloud command inbox, scheduler, trigger relay, or general
|
|
52
|
+
event-routing system. Shell and other hosts resolve Agents revisions while
|
|
53
|
+
preparing a Chat execution. Bundle ports call Apps, Automations, and Toolbox
|
|
54
|
+
public APIs; Agents never imports their stores or duplicates their lifecycle
|
|
55
|
+
behavior.
|
|
56
|
+
|
|
57
|
+
There is no execution subsystem in Agents. Chat is the single owner of every
|
|
58
|
+
conversation and turn.
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Amalgm Agents
|
|
2
2
|
|
|
3
3
|
`@amalgm/agents` is a local-first TypeScript SDK for persistent agent
|
|
4
|
-
|
|
5
|
-
REST API, and MCP server.
|
|
4
|
+
identities, installations, and immutable revisions. The same registry powers
|
|
5
|
+
its CLI, REST API, and MCP server.
|
|
6
6
|
|
|
7
|
-
It is intentionally not Chat.
|
|
8
|
-
owns the
|
|
9
|
-
|
|
7
|
+
It is intentionally not Chat. Agents resolves the exact installed revision;
|
|
8
|
+
`@amalgm/chat` owns the durable conversation, turns, stream, reconnect, and
|
|
9
|
+
interrupt lifecycle that executes it.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -19,25 +19,9 @@ Node 20 or newer is required.
|
|
|
19
19
|
## First agent
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { Agents
|
|
23
|
-
|
|
24
|
-
const echo = defineDriver({
|
|
25
|
-
id: 'echo',
|
|
26
|
-
async run(request) {
|
|
27
|
-
const text = request.input.parts
|
|
28
|
-
.filter((part) => part.type === 'text')
|
|
29
|
-
.map((part) => part.text)
|
|
30
|
-
.join('');
|
|
31
|
-
return {
|
|
32
|
-
message: {
|
|
33
|
-
role: 'assistant',
|
|
34
|
-
parts: [{ type: 'text', text: `Received: ${text}` }],
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
});
|
|
22
|
+
import { Agents } from '@amalgm/agents';
|
|
39
23
|
|
|
40
|
-
const agents = new Agents({ stateDir: './state'
|
|
24
|
+
const agents = new Agents({ stateDir: './state' });
|
|
41
25
|
|
|
42
26
|
agents.createAgent({
|
|
43
27
|
id: 'reviewer',
|
|
@@ -47,28 +31,22 @@ agents.createAgent({
|
|
|
47
31
|
toolbox: { toolIds: ['git'], actionIds: ['git.diff'] },
|
|
48
32
|
});
|
|
49
33
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
idempotencyKey: 'review-001',
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
console.log(turn.status);
|
|
57
|
-
console.log(agents.listEvents(session.id));
|
|
58
|
-
await agents.close();
|
|
34
|
+
const revision = agents.getAgentRevision('reviewer');
|
|
35
|
+
console.log(revision.id, revision.definition.driver.id);
|
|
36
|
+
agents.close();
|
|
59
37
|
```
|
|
60
38
|
|
|
61
|
-
An agent definition is normalized into a complete immutable revision.
|
|
62
|
-
|
|
39
|
+
An agent definition is normalized into a complete immutable revision. Chat
|
|
40
|
+
records the resolved revision id in every prepared execution.
|
|
63
41
|
|
|
64
42
|
## Product surfaces
|
|
65
43
|
|
|
66
44
|
| Surface | Entry point | Purpose |
|
|
67
45
|
|---|---|---|
|
|
68
|
-
| SDK | `@amalgm/agents` | Embed the canonical
|
|
69
|
-
| CLI | `amalgm-agents` | Manage
|
|
70
|
-
| REST | `@amalgm/agents/http`, `amalgm-agents-rest` | Local
|
|
71
|
-
| MCP | `@amalgm/agents/mcp`, `amalgm-agents-mcp` | Agent
|
|
46
|
+
| SDK | `@amalgm/agents` | Embed the canonical registry and resolve revisions |
|
|
47
|
+
| CLI | `amalgm-agents` | Manage agent identities and installations |
|
|
48
|
+
| REST | `@amalgm/agents/http`, `amalgm-agents-rest` | Local registry integration |
|
|
49
|
+
| MCP | `@amalgm/agents/mcp`, `amalgm-agents-mcp` | Agent registry tools |
|
|
72
50
|
| Skill | `skills/amalgm-agents` | Minimal workflow over the Agents tools |
|
|
73
51
|
|
|
74
52
|
Installed skills are available from the REST surface without introducing a
|
|
@@ -100,18 +78,31 @@ The REST adapter also serves `/agent-config`, `/agent-config/get`,
|
|
|
100
78
|
only instructions, skills, command hooks, and agent markdown from an injected
|
|
101
79
|
home; credentials and MCP configuration never enter Agents.
|
|
102
80
|
|
|
81
|
+
Portable bundle-v2 documents use the same SDK law as the REST surface:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const { bundle, preview } = await createAgentBundle(
|
|
85
|
+
agents,
|
|
86
|
+
{ agentIds: ['reviewer'] },
|
|
87
|
+
{ port: hostResourcePort },
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
await installAgentBundle(agents, bundle, { port: hostResourcePort });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Agents closes recursive subagents, skills, tools, and the canonical flat
|
|
94
|
+
graph. The injected port exports and installs Apps, Automations, and Toolbox
|
|
95
|
+
records through their owning SDKs. A missing port or graph mismatch fails
|
|
96
|
+
explicitly; dependencies are never omitted to make a bundle look portable.
|
|
97
|
+
|
|
103
98
|
## CLI
|
|
104
99
|
|
|
105
100
|
```bash
|
|
106
101
|
amalgm-agents agent create ./agent.json
|
|
107
102
|
amalgm-agents agent list
|
|
108
|
-
amalgm-agents session start reviewer
|
|
109
|
-
amalgm-agents session send <session-id> "Review the diff"
|
|
110
|
-
amalgm-agents talk reviewer "Review the diff"
|
|
111
103
|
```
|
|
112
104
|
|
|
113
|
-
The CLI reads the same SQLite
|
|
114
|
-
with `--drivers ./dist/codex-driver.js` or `AMALGM_AGENT_DRIVERS`.
|
|
105
|
+
The CLI reads the same SQLite registry as the SDK.
|
|
115
106
|
|
|
116
107
|
## Documentation
|
|
117
108
|
|
|
@@ -119,11 +110,10 @@ with `--drivers ./dist/codex-driver.js` or `AMALGM_AGENT_DRIVERS`.
|
|
|
119
110
|
- [Architecture and boundaries](./docs/ARCHITECTURE.md)
|
|
120
111
|
- [Agent definitions](./docs/DEFINITIONS.md)
|
|
121
112
|
- [SDK reference](./docs/SDK.md)
|
|
122
|
-
- [Driver contract](./docs/DRIVERS.md)
|
|
123
113
|
- [CLI reference](./docs/CLI.md)
|
|
124
114
|
- [REST API](./docs/REST.md)
|
|
125
115
|
- [MCP server](./docs/MCP.md)
|
|
126
|
-
- [Persistence
|
|
116
|
+
- [Persistence](./docs/DATA_MODEL.md)
|
|
127
117
|
- [Security model](./docs/SECURITY.md)
|
|
128
118
|
- [Engine integration](./docs/ENGINE_INTEGRATION.md)
|
|
129
119
|
|
|
@@ -136,4 +126,4 @@ npm run verify
|
|
|
136
126
|
|
|
137
127
|
`verify` runs strict typechecking for source, tests, and examples; enforces the
|
|
138
128
|
220-line source-file limit; builds JavaScript and declarations; and runs the
|
|
139
|
-
real SQLite,
|
|
129
|
+
real SQLite, TCP, stdio MCP, and CLI tests.
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import type { AgentDefinitionInput,
|
|
1
|
+
import type { AgentDefinitionInput, AgentPatch, AgentRecord, AgentRevision, AgentsOptions, JsonObject } from './types.js';
|
|
2
2
|
export declare class Agents {
|
|
3
3
|
readonly databasePath: string;
|
|
4
|
-
readonly recoveredTurns: number;
|
|
5
4
|
private readonly opened;
|
|
6
5
|
private readonly agentStore;
|
|
7
|
-
private readonly eventStore;
|
|
8
|
-
private readonly sessionStore;
|
|
9
|
-
private readonly turnStore;
|
|
10
|
-
private readonly runtime;
|
|
11
|
-
private readonly listeners;
|
|
12
6
|
private closed;
|
|
13
7
|
constructor(options?: AgentsOptions);
|
|
14
8
|
createAgent(input: AgentDefinitionInput): AgentRecord;
|
|
@@ -16,24 +10,8 @@ export declare class Agents {
|
|
|
16
10
|
deleteAgent(id: string): AgentRecord;
|
|
17
11
|
getAgent(id: string, includeDeleted?: boolean): AgentRecord | null;
|
|
18
12
|
listAgents(includeDeleted?: boolean): AgentRecord[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
listSessions(agentId?: string, includeArchived?: boolean): AgentSession[];
|
|
22
|
-
archiveSession(id: string): AgentSession;
|
|
23
|
-
enqueue(id: string, input: SendInput): EnqueuedTurn;
|
|
24
|
-
send(id: string, input: SendInput): Promise<AgentTurn>;
|
|
25
|
-
talk(agentId: string, input: SendInput & {
|
|
26
|
-
sessionId?: string;
|
|
27
|
-
}): EnqueuedTurn & {
|
|
28
|
-
session: AgentSession;
|
|
29
|
-
};
|
|
30
|
-
cancelSession(id: string): AgentTurn;
|
|
31
|
-
listTurns(sessionId: string): AgentTurn[];
|
|
32
|
-
listEvents(sessionId: string, after?: number, limit?: number): SessionEvent[];
|
|
33
|
-
registerDriver(driver: AgentDriver): void;
|
|
34
|
-
driverIds(): string[];
|
|
35
|
-
subscribe(sessionId: string, listener: EventListener): () => void;
|
|
13
|
+
/** Resolve one exact immutable revision for a host such as Chat. */
|
|
14
|
+
getAgentRevision(agentId: string, revisionId?: string): AgentRevision;
|
|
36
15
|
health(): JsonObject;
|
|
37
|
-
close():
|
|
38
|
-
private publish;
|
|
16
|
+
close(): void;
|
|
39
17
|
}
|
package/dist/agents.js
CHANGED
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { AgentStore } from './agent-store.js';
|
|
3
3
|
import { openDatabase } from './database.js';
|
|
4
|
-
import { EventStore } from './event-store.js';
|
|
5
|
-
import { AgentError } from './errors.js';
|
|
6
|
-
import { AgentRuntime } from './runtime.js';
|
|
7
|
-
import { SessionStore } from './session-store.js';
|
|
8
|
-
import { TurnStore } from './turn-store.js';
|
|
9
4
|
export class Agents {
|
|
10
5
|
databasePath;
|
|
11
|
-
recoveredTurns;
|
|
12
6
|
opened;
|
|
13
7
|
agentStore;
|
|
14
|
-
eventStore;
|
|
15
|
-
sessionStore;
|
|
16
|
-
turnStore;
|
|
17
|
-
runtime;
|
|
18
|
-
listeners = new Map();
|
|
19
8
|
closed = false;
|
|
20
9
|
constructor(options = {}) {
|
|
21
10
|
this.opened = openDatabase(options);
|
|
22
11
|
this.databasePath = this.opened.databasePath;
|
|
23
12
|
const now = options.now || (() => new Date().toISOString());
|
|
24
|
-
this.eventStore = new EventStore(this.opened.database, now, (event) => this.publish(event));
|
|
25
13
|
this.agentStore = new AgentStore(this.opened.database, now);
|
|
26
|
-
this.sessionStore = new SessionStore(this.opened.database, this.eventStore, now);
|
|
27
|
-
this.turnStore = new TurnStore(this.opened.database, this.sessionStore, this.eventStore, now);
|
|
28
|
-
this.recoveredTurns = this.turnStore.recoverInterrupted();
|
|
29
|
-
this.runtime = new AgentRuntime(this.agentStore, this.sessionStore, this.turnStore, this.eventStore, {
|
|
30
|
-
maxInputBytes: options.maxInputBytes || 256_000,
|
|
31
|
-
maxEventBytes: options.maxEventBytes || 256_000,
|
|
32
|
-
turnTimeoutMs: options.turnTimeoutMs ?? 600_000,
|
|
33
|
-
}, options.drivers);
|
|
34
14
|
}
|
|
35
15
|
createAgent(input) {
|
|
36
16
|
return this.agentStore.create(input);
|
|
@@ -47,83 +27,20 @@ export class Agents {
|
|
|
47
27
|
listAgents(includeDeleted = false) {
|
|
48
28
|
return this.agentStore.list(includeDeleted);
|
|
49
29
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return this.sessionStore.create(revision, input.sessionId, input.metadata || {});
|
|
54
|
-
}
|
|
55
|
-
getSession(id) {
|
|
56
|
-
return this.sessionStore.get(id);
|
|
57
|
-
}
|
|
58
|
-
listSessions(agentId, includeArchived = false) {
|
|
59
|
-
return this.sessionStore.list(agentId, includeArchived);
|
|
60
|
-
}
|
|
61
|
-
archiveSession(id) {
|
|
62
|
-
return this.sessionStore.archive(id, Boolean(this.turnStore.active(id)));
|
|
63
|
-
}
|
|
64
|
-
enqueue(id, input) {
|
|
65
|
-
this.sessionStore.require(id);
|
|
66
|
-
return this.runtime.enqueue(id, input);
|
|
67
|
-
}
|
|
68
|
-
send(id, input) {
|
|
69
|
-
this.sessionStore.require(id);
|
|
70
|
-
return this.runtime.send(id, input);
|
|
71
|
-
}
|
|
72
|
-
talk(agentId, input) {
|
|
73
|
-
const session = input.sessionId
|
|
74
|
-
? this.sessionStore.require(input.sessionId)
|
|
75
|
-
: this.startSession({ agentId });
|
|
76
|
-
if (session.agentId !== agentId) {
|
|
77
|
-
throw new AgentError('conflict', `Session ${session.id} belongs to agent ${session.agentId}.`);
|
|
78
|
-
}
|
|
79
|
-
return { session, ...this.enqueue(session.id, input) };
|
|
80
|
-
}
|
|
81
|
-
cancelSession(id) {
|
|
82
|
-
this.sessionStore.require(id);
|
|
83
|
-
return this.runtime.cancel(id);
|
|
84
|
-
}
|
|
85
|
-
listTurns(sessionId) {
|
|
86
|
-
return this.turnStore.list(sessionId);
|
|
87
|
-
}
|
|
88
|
-
listEvents(sessionId, after = 0, limit = 200) {
|
|
89
|
-
this.sessionStore.require(sessionId);
|
|
90
|
-
return this.eventStore.list(sessionId, after, limit);
|
|
91
|
-
}
|
|
92
|
-
registerDriver(driver) {
|
|
93
|
-
this.runtime.register(driver);
|
|
94
|
-
}
|
|
95
|
-
driverIds() {
|
|
96
|
-
return this.runtime.driverIds();
|
|
97
|
-
}
|
|
98
|
-
subscribe(sessionId, listener) {
|
|
99
|
-
this.sessionStore.require(sessionId);
|
|
100
|
-
const listeners = this.listeners.get(sessionId) || new Set();
|
|
101
|
-
listeners.add(listener);
|
|
102
|
-
this.listeners.set(sessionId, listeners);
|
|
103
|
-
return () => {
|
|
104
|
-
listeners.delete(listener);
|
|
105
|
-
if (listeners.size === 0)
|
|
106
|
-
this.listeners.delete(sessionId);
|
|
107
|
-
};
|
|
30
|
+
/** Resolve one exact immutable revision for a host such as Chat. */
|
|
31
|
+
getAgentRevision(agentId, revisionId) {
|
|
32
|
+
return this.agentStore.revision(agentId, revisionId);
|
|
108
33
|
}
|
|
109
34
|
health() {
|
|
110
35
|
return {
|
|
111
36
|
ok: !this.closed,
|
|
112
37
|
database: path.basename(this.databasePath),
|
|
113
|
-
recoveredTurns: this.recoveredTurns,
|
|
114
|
-
drivers: this.driverIds(),
|
|
115
38
|
};
|
|
116
39
|
}
|
|
117
|
-
|
|
40
|
+
close() {
|
|
118
41
|
if (this.closed)
|
|
119
42
|
return;
|
|
120
43
|
this.closed = true;
|
|
121
|
-
await this.runtime.shutdown();
|
|
122
|
-
this.listeners.clear();
|
|
123
44
|
this.opened.close();
|
|
124
45
|
}
|
|
125
|
-
publish(event) {
|
|
126
|
-
for (const listener of this.listeners.get(event.sessionId) || [])
|
|
127
|
-
listener(event);
|
|
128
|
-
}
|
|
129
46
|
}
|
package/dist/bin/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fatal } from './fatal.js';
|
|
|
6
6
|
async function main() {
|
|
7
7
|
const args = parseArgs(process.argv.slice(2));
|
|
8
8
|
if (args.flags.has('help')) {
|
|
9
|
-
process.stdout.write('Usage: amalgm-agents-mcp [--state-dir <path>]
|
|
9
|
+
process.stdout.write('Usage: amalgm-agents-mcp [--state-dir <path>]\n');
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const agents = await openAgents(args);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Agents } from '../agents.js';
|
|
2
|
+
import type { InstalledSkill } from '../skills/scanner.js';
|
|
3
|
+
import type { AgentBundle, AgentBundlePort, BundleCreateInput } from './types.js';
|
|
4
|
+
export interface CreateBundleOptions {
|
|
5
|
+
port?: AgentBundlePort;
|
|
6
|
+
installedSkills?: InstalledSkill[];
|
|
7
|
+
bundleId?: string;
|
|
8
|
+
now?: () => string;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAgentBundle(agents: Agents, input: BundleCreateInput, options?: CreateBundleOptions): Promise<{
|
|
11
|
+
bundle: AgentBundle;
|
|
12
|
+
preview: Record<string, unknown>;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { configFromAgent } from '../config/store.js';
|
|
2
|
+
import { buildBundleGraph } from './graph.js';
|
|
3
|
+
import { collectSkillRecords } from './graph-records.js';
|
|
4
|
+
import { BUNDLE_KIND, BUNDLE_SCHEMA_VERSION, addUnique, bundleId, emptyRequirements, mergeRequirements, scrubSecrets, uniqueStrings, } from './util.js';
|
|
5
|
+
function hydratedSkill(skill, installed) {
|
|
6
|
+
if (skill.content)
|
|
7
|
+
return skill;
|
|
8
|
+
const sourcePath = typeof skill.source.path === 'string' ? skill.source.path : '';
|
|
9
|
+
const match = installed.find((item) => item.id === skill.id || item.name === skill.name
|
|
10
|
+
|| (sourcePath && item.source.path === sourcePath));
|
|
11
|
+
return match ? {
|
|
12
|
+
...skill,
|
|
13
|
+
name: match.name,
|
|
14
|
+
description: skill.description || match.description,
|
|
15
|
+
content: match.content || '',
|
|
16
|
+
source: match.source,
|
|
17
|
+
} : skill;
|
|
18
|
+
}
|
|
19
|
+
function collectAgents(agents, roots, installed) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const entries = [];
|
|
22
|
+
const visit = (id) => {
|
|
23
|
+
if (!id || seen.has(id))
|
|
24
|
+
return;
|
|
25
|
+
seen.add(id);
|
|
26
|
+
const record = agents.getAgent(id);
|
|
27
|
+
if (!record)
|
|
28
|
+
throw new Error(`Agent not found: ${id}`);
|
|
29
|
+
const config = configFromAgent(record);
|
|
30
|
+
config.skills = config.skills.map((skill) => hydratedSkill(skill, installed));
|
|
31
|
+
entries.push({
|
|
32
|
+
sourceAgentId: record.id,
|
|
33
|
+
agent: {
|
|
34
|
+
name: record.definition.name,
|
|
35
|
+
description: record.definition.description,
|
|
36
|
+
driverId: record.definition.driver.id,
|
|
37
|
+
modelId: record.definition.model?.id || '',
|
|
38
|
+
modelSettings: record.definition.model?.settings || null,
|
|
39
|
+
},
|
|
40
|
+
config,
|
|
41
|
+
});
|
|
42
|
+
config.subagents.forEach((subagent) => visit(subagent.agentId));
|
|
43
|
+
};
|
|
44
|
+
roots.forEach(visit);
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
function title(entries, external) {
|
|
48
|
+
const agent = entries[0]?.agent;
|
|
49
|
+
if (agent)
|
|
50
|
+
return { title: agent.name || 'Shared agent', description: agent.description || '' };
|
|
51
|
+
for (const records of [external.automations, external.apps, external.tools]) {
|
|
52
|
+
const first = records?.[0];
|
|
53
|
+
const summary = first && typeof first === 'object'
|
|
54
|
+
? (first.automation || first.app || first) : null;
|
|
55
|
+
if (summary)
|
|
56
|
+
return {
|
|
57
|
+
title: typeof summary.name === 'string' ? summary.name : 'Shared bundle',
|
|
58
|
+
description: typeof summary.description === 'string' ? summary.description : '',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { title: 'Shared bundle', description: '' };
|
|
62
|
+
}
|
|
63
|
+
// Engine reference: amalgm-mcp/agent-bundles/export.js createBundle.
|
|
64
|
+
export async function createAgentBundle(agents, input, options = {}) {
|
|
65
|
+
const agentIds = uniqueStrings(input.agentIds);
|
|
66
|
+
const automationIds = uniqueStrings(input.automationIds);
|
|
67
|
+
const appIds = uniqueStrings(input.appIds);
|
|
68
|
+
const requestedToolIds = uniqueStrings(input.toolIds);
|
|
69
|
+
if (![agentIds, automationIds, appIds, requestedToolIds].some((ids) => ids.length)) {
|
|
70
|
+
throw new Error('At least one agent, automation, app, or tool is required');
|
|
71
|
+
}
|
|
72
|
+
const bundleAgents = collectAgents(agents, agentIds, options.installedSkills || []);
|
|
73
|
+
const agentToolIds = bundleAgents.flatMap((entry) => entry.config.loadout.toolIds);
|
|
74
|
+
const needsPort = automationIds.length || appIds.length || requestedToolIds.length || agentToolIds.length;
|
|
75
|
+
if (needsPort && !options.port)
|
|
76
|
+
throw new Error('A bundle resource port is required for tools, apps, or automations');
|
|
77
|
+
const external = options.port ? await options.port.exportResources({
|
|
78
|
+
automationIds, appIds, toolIds: uniqueStrings([...requestedToolIds, ...agentToolIds]),
|
|
79
|
+
}) : {};
|
|
80
|
+
const requires = mergeRequirements(emptyRequirements(), external.requires);
|
|
81
|
+
for (const entry of bundleAgents) {
|
|
82
|
+
addUnique(requires.runtimes, entry.agent.driverId);
|
|
83
|
+
addUnique(requires.auth, entry.agent.driverId);
|
|
84
|
+
}
|
|
85
|
+
const tools = (external.tools || []).map((tool) => {
|
|
86
|
+
if (tool.origin === 'system')
|
|
87
|
+
addUnique(requires.systemTools, String(tool.id || ''));
|
|
88
|
+
return scrubSecrets(tool, `tools.${String(tool.id || '')}`, requires);
|
|
89
|
+
});
|
|
90
|
+
const toolActions = (external.toolActions || []).map((action) => scrubSecrets(action, `toolActions.${String(action.id || '')}`, requires));
|
|
91
|
+
if (requires.missingTools.length)
|
|
92
|
+
throw new Error(`Tool not found: ${requires.missingTools.join(', ')}`);
|
|
93
|
+
const skills = collectSkillRecords(bundleAgents);
|
|
94
|
+
const hooks = bundleAgents.reduce((count, entry) => count + entry.config.hooks.length, 0);
|
|
95
|
+
const warnings = [
|
|
96
|
+
'Instructions, skill text, tool descriptions, and hook commands are shared as plain text.',
|
|
97
|
+
'Secrets pasted into free-text instructions or files are not automatically detected.',
|
|
98
|
+
...(hooks ? ['This bundle includes executable hook definitions. Review commands before importing.'] : []),
|
|
99
|
+
...(tools.length ? ['Tools can call external services or local commands. Reconnect credentials locally.'] : []),
|
|
100
|
+
...(external.warnings || []),
|
|
101
|
+
];
|
|
102
|
+
const named = title(bundleAgents, external);
|
|
103
|
+
const headToolIds = external.headToolIds || requestedToolIds;
|
|
104
|
+
const heads = [
|
|
105
|
+
...agentIds.map((id) => ({ type: 'agent', id })),
|
|
106
|
+
...automationIds.map((id) => ({ type: 'automation', id })),
|
|
107
|
+
...appIds.map((id) => ({ type: 'app', id })),
|
|
108
|
+
...headToolIds.map((id) => ({ type: 'tool', id })),
|
|
109
|
+
];
|
|
110
|
+
const summary = {
|
|
111
|
+
agents: bundleAgents.length, automations: external.automations?.length || 0,
|
|
112
|
+
apps: external.apps?.length || 0, tools: tools.length, toolActions: toolActions.length,
|
|
113
|
+
skills: skills.length, hooks, assets: 0,
|
|
114
|
+
};
|
|
115
|
+
const bundle = {
|
|
116
|
+
kind: BUNDLE_KIND, schemaVersion: BUNDLE_SCHEMA_VERSION,
|
|
117
|
+
bundleId: options.bundleId || bundleId(), createdAt: options.now?.() || new Date().toISOString(),
|
|
118
|
+
rootAgentId: agentIds[0] || null, heads, headIds: [], ...named,
|
|
119
|
+
agents: bundleAgents, automations: external.automations || [], apps: external.apps || [],
|
|
120
|
+
tools, toolActions, skills, assets: [], requires, warnings, summary, elements: [], edges: [],
|
|
121
|
+
};
|
|
122
|
+
Object.assign(bundle, buildBundleGraph(bundle));
|
|
123
|
+
const bytes = Buffer.byteLength(JSON.stringify(bundle));
|
|
124
|
+
return { bundle, preview: { bundleId: bundle.bundleId, ...named, summary, requires, warnings, bytes } };
|
|
125
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentBundle, BundleAgentEntry } from './types.js';
|
|
2
|
+
export declare function agentEntryId(entry: unknown): string;
|
|
3
|
+
export declare function automationEntryId(entry: unknown): string;
|
|
4
|
+
export declare function appEntryId(entry: unknown): string;
|
|
5
|
+
export declare function toolEntryId(entry: unknown): string;
|
|
6
|
+
export declare function actionEntryId(entry: unknown): string;
|
|
7
|
+
export declare function skillFingerprint(skill: unknown): string;
|
|
8
|
+
export declare function skillElementId(skill: unknown): string;
|
|
9
|
+
export declare function skillLabel(skill: unknown): string;
|
|
10
|
+
export declare function collectSkillRecords(agents: BundleAgentEntry[]): AgentBundle['skills'];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { clean, digest, object } from './util.js';
|
|
2
|
+
export function agentEntryId(entry) {
|
|
3
|
+
const record = object(entry);
|
|
4
|
+
return clean(record?.sourceAgentId) || clean(object(record?.agent)?.sourceAgentId);
|
|
5
|
+
}
|
|
6
|
+
export function automationEntryId(entry) {
|
|
7
|
+
return clean(object(entry)?.sourceAutomationId);
|
|
8
|
+
}
|
|
9
|
+
export function appEntryId(entry) {
|
|
10
|
+
return clean(object(entry)?.sourceAppId);
|
|
11
|
+
}
|
|
12
|
+
export function toolEntryId(entry) {
|
|
13
|
+
return clean(object(entry)?.id);
|
|
14
|
+
}
|
|
15
|
+
export function actionEntryId(entry) {
|
|
16
|
+
return clean(object(entry)?.id);
|
|
17
|
+
}
|
|
18
|
+
export function skillFingerprint(skill) {
|
|
19
|
+
if (typeof skill === 'string')
|
|
20
|
+
return clean(skill) ? `string:${clean(skill)}` : '';
|
|
21
|
+
const record = object(skill);
|
|
22
|
+
if (!record)
|
|
23
|
+
return '';
|
|
24
|
+
if (clean(record.id))
|
|
25
|
+
return `id:${clean(record.id)}`;
|
|
26
|
+
if (clean(record.path))
|
|
27
|
+
return `path:${clean(record.path)}`;
|
|
28
|
+
const name = clean(record.name);
|
|
29
|
+
const content = clean(record.content);
|
|
30
|
+
if (name && content)
|
|
31
|
+
return `name-content:${name}:${content}`;
|
|
32
|
+
if (name)
|
|
33
|
+
return `name:${name}`;
|
|
34
|
+
if (content)
|
|
35
|
+
return `content:${content}`;
|
|
36
|
+
return `json:${JSON.stringify(skill)}`;
|
|
37
|
+
}
|
|
38
|
+
export function skillElementId(skill) {
|
|
39
|
+
const fingerprint = skillFingerprint(skill);
|
|
40
|
+
return fingerprint ? `skill:${digest(fingerprint)}` : '';
|
|
41
|
+
}
|
|
42
|
+
export function skillLabel(skill) {
|
|
43
|
+
if (typeof skill === 'string')
|
|
44
|
+
return clean(skill) || 'Skill';
|
|
45
|
+
const record = object(skill);
|
|
46
|
+
if (!record)
|
|
47
|
+
return 'Skill';
|
|
48
|
+
return clean(record.name) || clean(record.path).split('/').filter(Boolean).pop()
|
|
49
|
+
|| clean(record.id) || clean(record.content).slice(0, 60) || 'Skill';
|
|
50
|
+
}
|
|
51
|
+
export function collectSkillRecords(agents) {
|
|
52
|
+
const records = new Map();
|
|
53
|
+
for (const entry of agents) {
|
|
54
|
+
for (const skill of entry.config.skills) {
|
|
55
|
+
const key = skillFingerprint(skill);
|
|
56
|
+
if (key && !records.has(key))
|
|
57
|
+
records.set(key, JSON.parse(JSON.stringify(skill)));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...records.values()];
|
|
61
|
+
}
|