@amalgm/agents 0.2.0 → 0.2.1
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 +27 -25
- package/README.md +46 -19
- package/dist/agents.d.ts +26 -4
- package/dist/agents.js +87 -4
- package/dist/bin/mcp.js +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +13 -2
- package/dist/cli/open.d.ts +1 -1
- package/dist/cli/open.js +5 -1
- package/dist/cli/run.js +8 -3
- package/dist/cli/session-commands.d.ts +4 -0
- package/dist/cli/session-commands.js +54 -0
- package/dist/drivers.d.ts +4 -0
- package/dist/drivers.js +25 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +2 -0
- package/dist/event-store.d.ts +13 -0
- package/dist/event-store.js +50 -0
- package/dist/http/server.js +7 -1
- package/dist/http/session-routes.d.ts +2 -0
- package/dist/http/session-routes.js +67 -0
- package/dist/http/stream.d.ts +3 -0
- package/dist/http/stream.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/agent-tools.js +1 -1
- package/dist/mcp/session-tools.d.ts +3 -0
- package/dist/mcp/session-tools.js +81 -0
- package/dist/mcp/tools.js +2 -1
- package/dist/messages.d.ts +3 -0
- package/dist/messages.js +56 -0
- package/dist/rows.d.ts +4 -1
- package/dist/rows.js +39 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.js +176 -0
- package/dist/schema.js +101 -53
- package/dist/session-store.d.ts +15 -0
- package/dist/session-store.js +83 -0
- package/dist/turn-store.d.ts +31 -0
- package/dist/turn-store.js +164 -0
- package/dist/types.d.ts +106 -0
- package/docs/ARCHITECTURE.md +33 -21
- package/docs/CLI.md +43 -3
- package/docs/DATA_MODEL.md +46 -7
- package/docs/DEFINITIONS.md +4 -4
- package/docs/DRIVERS.md +72 -0
- package/docs/ENGINE_INTEGRATION.md +67 -22
- package/docs/MCP.md +19 -3
- package/docs/REST.md +97 -14
- package/docs/SDK.md +61 -12
- package/docs/SECURITY.md +35 -6
- package/examples/basic.ts +22 -8
- package/package.json +5 -5
- package/skills/amalgm-agents/SKILL.md +12 -3
- package/skills/amalgm-agents/agents/openai.yaml +2 -2
package/PURPOSE.md
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Amalgm Agents is the small, standalone
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Amalgm Agents is the small, standalone product for defining agents and talking
|
|
6
|
+
to them. A user can create, inspect, edit, and delete an agent, then start or
|
|
7
|
+
continue a durable session with it through the SDK, CLI, REST, or MCP. Agents
|
|
8
|
+
are not chats: Chat may present a session, but the agent definition and session
|
|
9
|
+
remain usable without Chat or Amalgm Engine.
|
|
10
|
+
|
|
11
|
+
Engine composes Agents with harnesses, credentials, Toolbox, files, Realtime,
|
|
12
|
+
and UI. This package stores references to those products and delegates
|
|
13
|
+
execution to an injected driver; it does not duplicate their state.
|
|
13
14
|
It also discovers installed agent skills across the canonical project and
|
|
14
15
|
harness roots so every transport presents the same available skill catalog.
|
|
15
16
|
Rich harness configuration is stored inside the same immutable definition and
|
|
@@ -20,22 +21,25 @@ hosts supply explicit ports for resources owned by other products.
|
|
|
20
21
|
## Primitives
|
|
21
22
|
|
|
22
23
|
- An **agent** is a stable identity with a complete current definition.
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
- A **
|
|
24
|
+
- A **session** is a durable conversation with one agent definition.
|
|
25
|
+
- A **turn** is one accepted input and its driver result.
|
|
26
|
+
- A **driver** runs the agent using a host-provided harness.
|
|
26
27
|
- The **Agents service** is the behavior shared by every public surface.
|
|
27
28
|
|
|
28
29
|
## Axioms
|
|
29
30
|
|
|
31
|
+
Managed releases bind to one exact published Core version and build from the
|
|
32
|
+
locked dependency graph on the supported Node toolchain.
|
|
33
|
+
|
|
30
34
|
1. A user can create, list, inspect, edit, and delete agents.
|
|
31
|
-
2. A user or another agent can
|
|
32
|
-
|
|
35
|
+
2. A user or another agent can start, inspect, and continue an agent session
|
|
36
|
+
without creating or depending on a Chat conversation.
|
|
33
37
|
3. An agent definition can reference its harness, model, instructions, tools,
|
|
34
38
|
skills, files, credentials, and subagents without Agents owning those
|
|
35
39
|
products.
|
|
36
|
-
4.
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
4. Any compatible agent harness can be used through the same driver contract.
|
|
41
|
+
5. Agent definitions and sessions survive restart, while uncertain interrupted
|
|
42
|
+
execution is never silently replayed.
|
|
39
43
|
6. SDK, CLI, REST, MCP, and skill workflows expose the same Agents behavior;
|
|
40
44
|
none is a second implementation.
|
|
41
45
|
7. An unchanged current definition is idempotent; every actual change creates
|
|
@@ -47,12 +51,10 @@ hosts supply explicit ports for resources owned by other products.
|
|
|
47
51
|
|
|
48
52
|
## Consequences
|
|
49
53
|
|
|
50
|
-
The package needs no
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
There is no execution subsystem in Agents. Chat is the single owner of every
|
|
58
|
-
conversation and turn.
|
|
54
|
+
The package needs no cloud command inbox, scheduler, trigger relay, Chat
|
|
55
|
+
record, or general event-routing system. Engine and other hosts may call the
|
|
56
|
+
SDK whenever they need to route work, but routing is outside the Agents
|
|
57
|
+
product. Bundle ports call Apps, Automations, and Toolbox public APIs; Agents
|
|
58
|
+
never imports their stores or duplicates their lifecycle behavior. Internal
|
|
59
|
+
revisions, ordered events, idempotency, and cancellation exist only to make
|
|
60
|
+
the behaviors above durable and predictable.
|
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
|
-
|
|
4
|
+
definitions and durable multi-turn sessions. The same service powers its CLI,
|
|
5
|
+
REST API, and MCP server.
|
|
6
6
|
|
|
7
|
-
It is intentionally not Chat.
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
It is intentionally not Chat. Chat may present an agent session, but Agents
|
|
8
|
+
owns the agent definition, the pinned execution revision, turns, and ordered
|
|
9
|
+
events.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -19,9 +19,25 @@ Node 20 or newer is required.
|
|
|
19
19
|
## First agent
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { Agents } from '@amalgm/agents';
|
|
22
|
+
import { Agents, defineDriver } from '@amalgm/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
|
+
});
|
|
23
39
|
|
|
24
|
-
const agents = new Agents({ stateDir: './state' });
|
|
40
|
+
const agents = new Agents({ stateDir: './state', drivers: [echo] });
|
|
25
41
|
|
|
26
42
|
agents.createAgent({
|
|
27
43
|
id: 'reviewer',
|
|
@@ -31,22 +47,28 @@ agents.createAgent({
|
|
|
31
47
|
toolbox: { toolIds: ['git'], actionIds: ['git.diff'] },
|
|
32
48
|
});
|
|
33
49
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
const session = agents.startSession({ agentId: 'reviewer' });
|
|
51
|
+
const turn = await agents.send(session.id, {
|
|
52
|
+
message: 'Review the current diff.',
|
|
53
|
+
idempotencyKey: 'review-001',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(turn.status);
|
|
57
|
+
console.log(agents.listEvents(session.id));
|
|
58
|
+
await agents.close();
|
|
37
59
|
```
|
|
38
60
|
|
|
39
|
-
An agent definition is normalized into a complete immutable revision.
|
|
40
|
-
|
|
61
|
+
An agent definition is normalized into a complete immutable revision. A
|
|
62
|
+
session stays pinned to that revision even after the agent is edited.
|
|
41
63
|
|
|
42
64
|
## Product surfaces
|
|
43
65
|
|
|
44
66
|
| Surface | Entry point | Purpose |
|
|
45
67
|
|---|---|---|
|
|
46
|
-
| SDK | `@amalgm/agents` | Embed the canonical
|
|
47
|
-
| CLI | `amalgm-agents` | Manage
|
|
48
|
-
| REST | `@amalgm/agents/http`, `amalgm-agents-rest` | Local
|
|
49
|
-
| MCP | `@amalgm/agents/mcp`, `amalgm-agents-mcp` | Agent
|
|
68
|
+
| SDK | `@amalgm/agents` | Embed the canonical service and supply drivers |
|
|
69
|
+
| CLI | `amalgm-agents` | Manage definitions and sessions |
|
|
70
|
+
| REST | `@amalgm/agents/http`, `amalgm-agents-rest` | Local HTTP and SSE integration |
|
|
71
|
+
| MCP | `@amalgm/agents/mcp`, `amalgm-agents-mcp` | Agent CRUD and agent-to-agent calls |
|
|
50
72
|
| Skill | `skills/amalgm-agents` | Minimal workflow over the Agents tools |
|
|
51
73
|
|
|
52
74
|
Installed skills are available from the REST surface without introducing a
|
|
@@ -100,9 +122,13 @@ explicitly; dependencies are never omitted to make a bundle look portable.
|
|
|
100
122
|
```bash
|
|
101
123
|
amalgm-agents agent create ./agent.json
|
|
102
124
|
amalgm-agents agent list
|
|
125
|
+
amalgm-agents session start reviewer
|
|
126
|
+
amalgm-agents session send <session-id> "Review the diff"
|
|
127
|
+
amalgm-agents talk reviewer "Review the diff"
|
|
103
128
|
```
|
|
104
129
|
|
|
105
|
-
The CLI reads the same SQLite
|
|
130
|
+
The CLI reads the same SQLite database as the SDK. Driver modules are loaded
|
|
131
|
+
with `--drivers ./dist/codex-driver.js` or `AMALGM_AGENT_DRIVERS`.
|
|
106
132
|
|
|
107
133
|
## Documentation
|
|
108
134
|
|
|
@@ -110,10 +136,11 @@ The CLI reads the same SQLite registry as the SDK.
|
|
|
110
136
|
- [Architecture and boundaries](./docs/ARCHITECTURE.md)
|
|
111
137
|
- [Agent definitions](./docs/DEFINITIONS.md)
|
|
112
138
|
- [SDK reference](./docs/SDK.md)
|
|
139
|
+
- [Driver contract](./docs/DRIVERS.md)
|
|
113
140
|
- [CLI reference](./docs/CLI.md)
|
|
114
141
|
- [REST API](./docs/REST.md)
|
|
115
142
|
- [MCP server](./docs/MCP.md)
|
|
116
|
-
- [Persistence](./docs/DATA_MODEL.md)
|
|
143
|
+
- [Persistence and recovery](./docs/DATA_MODEL.md)
|
|
117
144
|
- [Security model](./docs/SECURITY.md)
|
|
118
145
|
- [Engine integration](./docs/ENGINE_INTEGRATION.md)
|
|
119
146
|
|
|
@@ -126,4 +153,4 @@ npm run verify
|
|
|
126
153
|
|
|
127
154
|
`verify` runs strict typechecking for source, tests, and examples; enforces the
|
|
128
155
|
220-line source-file limit; builds JavaScript and declarations; and runs the
|
|
129
|
-
real SQLite, TCP, stdio MCP, and CLI tests.
|
|
156
|
+
real SQLite, process-restart, TCP/SSE, stdio MCP, and CLI tests.
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type { AgentDefinitionInput, AgentPatch, AgentRecord,
|
|
1
|
+
import type { AgentDefinitionInput, AgentDriver, AgentPatch, AgentRecord, AgentSession, AgentsOptions, AgentTurn, EnqueuedTurn, EventListener, JsonObject, SendInput, SessionEvent, StartSessionInput } from './types.js';
|
|
2
2
|
export declare class Agents {
|
|
3
3
|
readonly databasePath: string;
|
|
4
|
+
readonly recoveredTurns: number;
|
|
4
5
|
private readonly opened;
|
|
5
6
|
private readonly agentStore;
|
|
7
|
+
private readonly eventStore;
|
|
8
|
+
private readonly sessionStore;
|
|
9
|
+
private readonly turnStore;
|
|
10
|
+
private readonly runtime;
|
|
11
|
+
private readonly listeners;
|
|
6
12
|
private closed;
|
|
7
13
|
constructor(options?: AgentsOptions);
|
|
8
14
|
createAgent(input: AgentDefinitionInput): AgentRecord;
|
|
@@ -10,8 +16,24 @@ export declare class Agents {
|
|
|
10
16
|
deleteAgent(id: string): AgentRecord;
|
|
11
17
|
getAgent(id: string, includeDeleted?: boolean): AgentRecord | null;
|
|
12
18
|
listAgents(includeDeleted?: boolean): AgentRecord[];
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
startSession(input: StartSessionInput): AgentSession;
|
|
20
|
+
getSession(id: string): AgentSession | null;
|
|
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;
|
|
15
36
|
health(): JsonObject;
|
|
16
|
-
close(): void
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
private publish;
|
|
17
39
|
}
|
package/dist/agents.js
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
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';
|
|
4
9
|
export class Agents {
|
|
5
10
|
databasePath;
|
|
11
|
+
recoveredTurns;
|
|
6
12
|
opened;
|
|
7
13
|
agentStore;
|
|
14
|
+
eventStore;
|
|
15
|
+
sessionStore;
|
|
16
|
+
turnStore;
|
|
17
|
+
runtime;
|
|
18
|
+
listeners = new Map();
|
|
8
19
|
closed = false;
|
|
9
20
|
constructor(options = {}) {
|
|
10
21
|
this.opened = openDatabase(options);
|
|
11
22
|
this.databasePath = this.opened.databasePath;
|
|
12
23
|
const now = options.now || (() => new Date().toISOString());
|
|
24
|
+
this.eventStore = new EventStore(this.opened.database, now, (event) => this.publish(event));
|
|
13
25
|
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);
|
|
14
34
|
}
|
|
15
35
|
createAgent(input) {
|
|
16
36
|
return this.agentStore.create(input);
|
|
@@ -27,20 +47,83 @@ export class Agents {
|
|
|
27
47
|
listAgents(includeDeleted = false) {
|
|
28
48
|
return this.agentStore.list(includeDeleted);
|
|
29
49
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
startSession(input) {
|
|
51
|
+
const agent = this.agentStore.require(input.agentId);
|
|
52
|
+
const revision = this.agentStore.revision(agent.id, input.revisionId);
|
|
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
|
+
};
|
|
33
108
|
}
|
|
34
109
|
health() {
|
|
35
110
|
return {
|
|
36
111
|
ok: !this.closed,
|
|
37
112
|
database: path.basename(this.databasePath),
|
|
113
|
+
recoveredTurns: this.recoveredTurns,
|
|
114
|
+
drivers: this.driverIds(),
|
|
38
115
|
};
|
|
39
116
|
}
|
|
40
|
-
close() {
|
|
117
|
+
async close() {
|
|
41
118
|
if (this.closed)
|
|
42
119
|
return;
|
|
43
120
|
this.closed = true;
|
|
121
|
+
await this.runtime.shutdown();
|
|
122
|
+
this.listeners.clear();
|
|
44
123
|
this.opened.close();
|
|
45
124
|
}
|
|
125
|
+
publish(event) {
|
|
126
|
+
for (const listener of this.listeners.get(event.sessionId) || [])
|
|
127
|
+
listener(event);
|
|
128
|
+
}
|
|
46
129
|
}
|
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>]\n');
|
|
9
|
+
process.stdout.write('Usage: amalgm-agents-mcp [--state-dir <path>] [--drivers <a.js,b.js>]\n');
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const agents = await openAgents(args);
|
package/dist/cli/help.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const HELP = "amalgm-agents \u2014 local agent
|
|
1
|
+
export declare const HELP = "amalgm-agents \u2014 local agent definitions and durable sessions\n\nAgent definitions:\n amalgm-agents agent list [--include-deleted]\n amalgm-agents agent show <id>\n amalgm-agents agent create <definition.json>\n amalgm-agents agent update <id> <patch.json>\n amalgm-agents agent delete <id>\n\nSessions:\n amalgm-agents session list [--agent <id>] [--include-archived]\n amalgm-agents session start <agent-id> [--id <id>] [--revision <id>]\n amalgm-agents session show <id>\n amalgm-agents session events <id> [--after <sequence>]\n amalgm-agents session send <id> <message> [--key <idempotency-key>]\n amalgm-agents session cancel <id>\n amalgm-agents session archive <id>\n amalgm-agents talk <agent-id> <message> [--session <id>] [--background]\n\nGlobal options:\n --state-dir <path> Agents state directory\n --drivers <a.js,b.js> Driver modules (or AMALGM_AGENT_DRIVERS)\n --help Show this help\n";
|
package/dist/cli/help.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
export const HELP = `amalgm-agents — local agent
|
|
1
|
+
export const HELP = `amalgm-agents — local agent definitions and durable sessions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent definitions:
|
|
4
4
|
amalgm-agents agent list [--include-deleted]
|
|
5
5
|
amalgm-agents agent show <id>
|
|
6
6
|
amalgm-agents agent create <definition.json>
|
|
7
7
|
amalgm-agents agent update <id> <patch.json>
|
|
8
8
|
amalgm-agents agent delete <id>
|
|
9
9
|
|
|
10
|
+
Sessions:
|
|
11
|
+
amalgm-agents session list [--agent <id>] [--include-archived]
|
|
12
|
+
amalgm-agents session start <agent-id> [--id <id>] [--revision <id>]
|
|
13
|
+
amalgm-agents session show <id>
|
|
14
|
+
amalgm-agents session events <id> [--after <sequence>]
|
|
15
|
+
amalgm-agents session send <id> <message> [--key <idempotency-key>]
|
|
16
|
+
amalgm-agents session cancel <id>
|
|
17
|
+
amalgm-agents session archive <id>
|
|
18
|
+
amalgm-agents talk <agent-id> <message> [--session <id>] [--background]
|
|
19
|
+
|
|
10
20
|
Global options:
|
|
11
21
|
--state-dir <path> Agents state directory
|
|
22
|
+
--drivers <a.js,b.js> Driver modules (or AMALGM_AGENT_DRIVERS)
|
|
12
23
|
--help Show this help
|
|
13
24
|
`;
|
package/dist/cli/open.d.ts
CHANGED
package/dist/cli/open.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { Agents } from '../agents.js';
|
|
2
|
+
import { driverSpecifiers, loadDriverModules } from '../drivers.js';
|
|
2
3
|
import { flag } from './args.js';
|
|
3
|
-
export function openAgents(args) {
|
|
4
|
+
export async function openAgents(args) {
|
|
5
|
+
const modules = flag(args, 'drivers');
|
|
4
6
|
const stateDir = flag(args, 'state-dir');
|
|
7
|
+
const drivers = await loadDriverModules(driverSpecifiers(modules));
|
|
5
8
|
return new Agents({
|
|
9
|
+
drivers,
|
|
6
10
|
...(stateDir ? { stateDir } : {}),
|
|
7
11
|
});
|
|
8
12
|
}
|
package/dist/cli/run.js
CHANGED
|
@@ -4,6 +4,7 @@ import { runAgentCommand } from './agent-commands.js';
|
|
|
4
4
|
import { writeJson } from './files.js';
|
|
5
5
|
import { HELP } from './help.js';
|
|
6
6
|
import { openAgents } from './open.js';
|
|
7
|
+
import { runSessionCommand, runTalkCommand } from './session-commands.js';
|
|
7
8
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
8
9
|
const args = parseArgs(argv);
|
|
9
10
|
if (args.flags.has('help') || args.words.length === 0) {
|
|
@@ -12,11 +13,15 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
12
13
|
}
|
|
13
14
|
let agents;
|
|
14
15
|
try {
|
|
15
|
-
agents = openAgents(args);
|
|
16
|
+
agents = await openAgents(args);
|
|
16
17
|
const command = args.words[0];
|
|
17
18
|
const value = command === 'agent'
|
|
18
19
|
? runAgentCommand(agents, args)
|
|
19
|
-
:
|
|
20
|
+
: command === 'session'
|
|
21
|
+
? await runSessionCommand(agents, args)
|
|
22
|
+
: command === 'talk'
|
|
23
|
+
? await runTalkCommand(agents, args)
|
|
24
|
+
: undefined;
|
|
20
25
|
if (value === undefined)
|
|
21
26
|
throw new Error(`Unknown command: ${String(command)}`);
|
|
22
27
|
writeJson(value);
|
|
@@ -28,6 +33,6 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
28
33
|
return 1;
|
|
29
34
|
}
|
|
30
35
|
finally {
|
|
31
|
-
agents?.close();
|
|
36
|
+
await agents?.close();
|
|
32
37
|
}
|
|
33
38
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Agents } from '../agents.js';
|
|
2
|
+
import type { ParsedArgs } from './args.js';
|
|
3
|
+
export declare function runSessionCommand(agents: Agents, args: ParsedArgs): Promise<unknown>;
|
|
4
|
+
export declare function runTalkCommand(agents: Agents, args: ParsedArgs): Promise<unknown>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AgentError } from '../errors.js';
|
|
2
|
+
import { enabled, flag } from './args.js';
|
|
3
|
+
export async function runSessionCommand(agents, args) {
|
|
4
|
+
const [, action, first, second] = args.words;
|
|
5
|
+
if (action === 'list') {
|
|
6
|
+
return { sessions: agents.listSessions(flag(args, 'agent'), enabled(args, 'include-archived')) };
|
|
7
|
+
}
|
|
8
|
+
if (action === 'start' && first) {
|
|
9
|
+
const sessionId = flag(args, 'id');
|
|
10
|
+
const revisionId = flag(args, 'revision');
|
|
11
|
+
return { session: agents.startSession({
|
|
12
|
+
agentId: first,
|
|
13
|
+
...(sessionId ? { sessionId } : {}),
|
|
14
|
+
...(revisionId ? { revisionId } : {}),
|
|
15
|
+
}) };
|
|
16
|
+
}
|
|
17
|
+
if (action === 'show' && first) {
|
|
18
|
+
const session = agents.getSession(first);
|
|
19
|
+
if (!session)
|
|
20
|
+
throw new AgentError('not_found', `Session not found: ${first}`);
|
|
21
|
+
return { session, turns: agents.listTurns(first), events: agents.listEvents(first) };
|
|
22
|
+
}
|
|
23
|
+
if (action === 'events' && first) {
|
|
24
|
+
return { events: agents.listEvents(first, Number(flag(args, 'after') || 0)) };
|
|
25
|
+
}
|
|
26
|
+
if (action === 'send' && first && second) {
|
|
27
|
+
const idempotencyKey = flag(args, 'key');
|
|
28
|
+
return { turn: await agents.send(first, {
|
|
29
|
+
message: second,
|
|
30
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
31
|
+
}) };
|
|
32
|
+
}
|
|
33
|
+
if (action === 'cancel' && first)
|
|
34
|
+
return { turn: agents.cancelSession(first) };
|
|
35
|
+
if (action === 'archive' && first)
|
|
36
|
+
return { session: agents.archiveSession(first) };
|
|
37
|
+
throw new AgentError('invalid_input', 'Unknown or incomplete session command. Run with --help.');
|
|
38
|
+
}
|
|
39
|
+
export async function runTalkCommand(agents, args) {
|
|
40
|
+
const [, agentId, message] = args.words;
|
|
41
|
+
if (!agentId || !message)
|
|
42
|
+
throw new AgentError('invalid_input', 'talk requires agent id and message.');
|
|
43
|
+
const sessionId = flag(args, 'session');
|
|
44
|
+
const idempotencyKey = flag(args, 'key');
|
|
45
|
+
const call = agents.talk(agentId, {
|
|
46
|
+
message,
|
|
47
|
+
...(sessionId ? { sessionId } : {}),
|
|
48
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
49
|
+
});
|
|
50
|
+
if (enabled(args, 'background')) {
|
|
51
|
+
return { session: call.session, turn: call.turn, duplicate: call.duplicate };
|
|
52
|
+
}
|
|
53
|
+
return { session: call.session, turn: await call.completion, duplicate: call.duplicate };
|
|
54
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AgentDriver } from './types.js';
|
|
2
|
+
export declare function defineDriver(driver: AgentDriver): AgentDriver;
|
|
3
|
+
export declare function loadDriverModules(specifiers: string[]): Promise<AgentDriver[]>;
|
|
4
|
+
export declare function driverSpecifiers(value?: string): string[];
|
package/dist/drivers.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { AgentError } from './errors.js';
|
|
4
|
+
export function defineDriver(driver) {
|
|
5
|
+
if (!driver || typeof driver.id !== 'string' || typeof driver.run !== 'function') {
|
|
6
|
+
throw new AgentError('invalid_input', 'Agent drivers require id and run.');
|
|
7
|
+
}
|
|
8
|
+
return driver;
|
|
9
|
+
}
|
|
10
|
+
export async function loadDriverModules(specifiers) {
|
|
11
|
+
const drivers = [];
|
|
12
|
+
for (const specifier of specifiers) {
|
|
13
|
+
const target = specifier.startsWith('.') || specifier.startsWith('/')
|
|
14
|
+
? pathToFileURL(path.resolve(specifier)).href
|
|
15
|
+
: specifier;
|
|
16
|
+
const loaded = await import(target);
|
|
17
|
+
const value = loaded.default ?? loaded.driver ?? loaded;
|
|
18
|
+
const driver = typeof value === 'function' ? await value() : value;
|
|
19
|
+
drivers.push(defineDriver(driver));
|
|
20
|
+
}
|
|
21
|
+
return drivers;
|
|
22
|
+
}
|
|
23
|
+
export function driverSpecifiers(value = process.env.AMALGM_AGENT_DRIVERS || '') {
|
|
24
|
+
return value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
25
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonObject } from './types.js';
|
|
2
|
-
export type AgentErrorCode = 'invalid_input' | 'not_found' | 'conflict' | 'deleted' | 'too_large' | 'internal';
|
|
2
|
+
export type AgentErrorCode = 'invalid_input' | 'not_found' | 'conflict' | 'deleted' | 'driver_unavailable' | 'too_large' | 'cancelled' | 'internal';
|
|
3
3
|
export declare class AgentError extends Error {
|
|
4
4
|
readonly code: AgentErrorCode;
|
|
5
5
|
readonly details: JsonObject;
|
package/dist/errors.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { EventListener, JsonObject, SessionEvent } from './types.js';
|
|
3
|
+
export declare class EventStore {
|
|
4
|
+
private readonly database;
|
|
5
|
+
private readonly now;
|
|
6
|
+
private readonly listener;
|
|
7
|
+
constructor(database: Database.Database, now: () => string, listener: EventListener);
|
|
8
|
+
append(sessionId: string, turnId: string | null, type: string, data: JsonObject): SessionEvent;
|
|
9
|
+
write(sessionId: string, turnId: string | null, type: string, data: JsonObject): SessionEvent;
|
|
10
|
+
announce(event: SessionEvent): void;
|
|
11
|
+
list(sessionId: string, after?: number, limit?: number): SessionEvent[];
|
|
12
|
+
forTurn(turnId: string): SessionEvent[];
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { AgentError } from './errors.js';
|
|
2
|
+
import { newId } from './ids.js';
|
|
3
|
+
import { eventFromRow } from './rows.js';
|
|
4
|
+
export class EventStore {
|
|
5
|
+
database;
|
|
6
|
+
now;
|
|
7
|
+
listener;
|
|
8
|
+
constructor(database, now, listener) {
|
|
9
|
+
this.database = database;
|
|
10
|
+
this.now = now;
|
|
11
|
+
this.listener = listener;
|
|
12
|
+
}
|
|
13
|
+
append(sessionId, turnId, type, data) {
|
|
14
|
+
let event;
|
|
15
|
+
this.database.transaction(() => {
|
|
16
|
+
event = this.write(sessionId, turnId, type, data);
|
|
17
|
+
this.database.prepare('UPDATE sessions SET updated_at = ? WHERE id = ?').run(this.now(), sessionId);
|
|
18
|
+
})();
|
|
19
|
+
if (!event)
|
|
20
|
+
throw new AgentError('internal', 'Event transaction produced no event.');
|
|
21
|
+
this.announce(event);
|
|
22
|
+
return event;
|
|
23
|
+
}
|
|
24
|
+
write(sessionId, turnId, type, data) {
|
|
25
|
+
const sequence = Number(this.database.prepare(`
|
|
26
|
+
SELECT COALESCE(MAX(sequence), 0) + 1 AS value FROM session_events WHERE session_id = ?
|
|
27
|
+
`).get(sessionId).value);
|
|
28
|
+
const id = newId('event');
|
|
29
|
+
const createdAt = this.now();
|
|
30
|
+
this.database.prepare(`
|
|
31
|
+
INSERT INTO session_events (id, session_id, turn_id, sequence, type, data_json, created_at)
|
|
32
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
33
|
+
`).run(id, sessionId, turnId, sequence, type, JSON.stringify(data), createdAt);
|
|
34
|
+
return { id, sessionId, turnId, sequence, type, data, createdAt };
|
|
35
|
+
}
|
|
36
|
+
announce(event) {
|
|
37
|
+
this.listener(event);
|
|
38
|
+
}
|
|
39
|
+
list(sessionId, after = 0, limit = 200) {
|
|
40
|
+
const cursor = Number.isFinite(after) ? Math.max(0, Math.floor(after)) : 0;
|
|
41
|
+
const bounded = Number.isFinite(limit) ? Math.max(1, Math.min(1000, Math.floor(limit))) : 200;
|
|
42
|
+
return this.database.prepare(`
|
|
43
|
+
SELECT * FROM session_events WHERE session_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?
|
|
44
|
+
`).all(sessionId, cursor, bounded).map(eventFromRow);
|
|
45
|
+
}
|
|
46
|
+
forTurn(turnId) {
|
|
47
|
+
return this.database.prepare('SELECT * FROM session_events WHERE turn_id = ? ORDER BY sequence ASC')
|
|
48
|
+
.all(turnId).map(eventFromRow);
|
|
49
|
+
}
|
|
50
|
+
}
|