@amalgm/agents 0.1.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.
Files changed (103) hide show
  1. package/PURPOSE.md +45 -0
  2. package/README.md +110 -0
  3. package/dist/agent-store.d.ts +17 -0
  4. package/dist/agent-store.js +111 -0
  5. package/dist/agents.d.ts +39 -0
  6. package/dist/agents.js +129 -0
  7. package/dist/bin/agents.d.ts +2 -0
  8. package/dist/bin/agents.js +5 -0
  9. package/dist/bin/fatal.d.ts +1 -0
  10. package/dist/bin/fatal.js +5 -0
  11. package/dist/bin/mcp.d.ts +2 -0
  12. package/dist/bin/mcp.js +19 -0
  13. package/dist/bin/rest.d.ts +2 -0
  14. package/dist/bin/rest.js +21 -0
  15. package/dist/cli/agent-commands.d.ts +3 -0
  16. package/dist/cli/agent-commands.js +23 -0
  17. package/dist/cli/args.d.ts +7 -0
  18. package/dist/cli/args.js +31 -0
  19. package/dist/cli/files.d.ts +2 -0
  20. package/dist/cli/files.js +21 -0
  21. package/dist/cli/help.d.ts +1 -0
  22. package/dist/cli/help.js +24 -0
  23. package/dist/cli/open.d.ts +3 -0
  24. package/dist/cli/open.js +12 -0
  25. package/dist/cli/run.d.ts +1 -0
  26. package/dist/cli/run.js +38 -0
  27. package/dist/cli/session-commands.d.ts +4 -0
  28. package/dist/cli/session-commands.js +54 -0
  29. package/dist/cli.d.ts +2 -0
  30. package/dist/cli.js +2 -0
  31. package/dist/database.d.ts +9 -0
  32. package/dist/database.js +35 -0
  33. package/dist/definition.d.ts +4 -0
  34. package/dist/definition.js +93 -0
  35. package/dist/drivers.d.ts +4 -0
  36. package/dist/drivers.js +25 -0
  37. package/dist/errors.d.ts +9 -0
  38. package/dist/errors.js +28 -0
  39. package/dist/event-store.d.ts +13 -0
  40. package/dist/event-store.js +50 -0
  41. package/dist/http/agent-routes.d.ts +2 -0
  42. package/dist/http/agent-routes.js +40 -0
  43. package/dist/http/request.d.ts +5 -0
  44. package/dist/http/request.js +40 -0
  45. package/dist/http/server.d.ts +2 -0
  46. package/dist/http/server.js +82 -0
  47. package/dist/http/session-routes.d.ts +2 -0
  48. package/dist/http/session-routes.js +67 -0
  49. package/dist/http/stream.d.ts +3 -0
  50. package/dist/http/stream.js +30 -0
  51. package/dist/http-types.d.ts +23 -0
  52. package/dist/http-types.js +1 -0
  53. package/dist/http.d.ts +2 -0
  54. package/dist/http.js +1 -0
  55. package/dist/ids.d.ts +5 -0
  56. package/dist/ids.js +32 -0
  57. package/dist/index.d.ts +6 -0
  58. package/dist/index.js +5 -0
  59. package/dist/json.d.ts +7 -0
  60. package/dist/json.js +41 -0
  61. package/dist/mcp/agent-tools.d.ts +3 -0
  62. package/dist/mcp/agent-tools.js +51 -0
  63. package/dist/mcp/helpers.d.ts +5 -0
  64. package/dist/mcp/helpers.js +27 -0
  65. package/dist/mcp/server.d.ts +6 -0
  66. package/dist/mcp/server.js +73 -0
  67. package/dist/mcp/session-tools.d.ts +3 -0
  68. package/dist/mcp/session-tools.js +81 -0
  69. package/dist/mcp/tools.d.ts +3 -0
  70. package/dist/mcp/tools.js +5 -0
  71. package/dist/mcp/types.d.ts +18 -0
  72. package/dist/mcp/types.js +1 -0
  73. package/dist/mcp.d.ts +3 -0
  74. package/dist/mcp.js +2 -0
  75. package/dist/messages.d.ts +3 -0
  76. package/dist/messages.js +56 -0
  77. package/dist/rows.d.ts +7 -0
  78. package/dist/rows.js +65 -0
  79. package/dist/runtime.d.ts +29 -0
  80. package/dist/runtime.js +176 -0
  81. package/dist/schema.d.ts +2 -0
  82. package/dist/schema.js +127 -0
  83. package/dist/session-store.d.ts +15 -0
  84. package/dist/session-store.js +83 -0
  85. package/dist/turn-store.d.ts +31 -0
  86. package/dist/turn-store.js +164 -0
  87. package/dist/types.d.ts +176 -0
  88. package/dist/types.js +1 -0
  89. package/docs/ARCHITECTURE.md +73 -0
  90. package/docs/CLI.md +61 -0
  91. package/docs/DATA_MODEL.md +56 -0
  92. package/docs/DEFINITIONS.md +65 -0
  93. package/docs/DRIVERS.md +72 -0
  94. package/docs/ENGINE_INTEGRATION.md +74 -0
  95. package/docs/MCP.md +24 -0
  96. package/docs/REST.md +78 -0
  97. package/docs/SDK.md +72 -0
  98. package/docs/SECURITY.md +38 -0
  99. package/examples/basic.ts +31 -0
  100. package/examples/reviewer.json +25 -0
  101. package/package.json +64 -0
  102. package/skills/amalgm-agents/SKILL.md +30 -0
  103. package/skills/amalgm-agents/agents/openai.yaml +4 -0
package/PURPOSE.md ADDED
@@ -0,0 +1,45 @@
1
+ # Amalgm Agents
2
+
3
+ ## Purpose
4
+
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.
14
+
15
+ ## Primitives
16
+
17
+ - An **agent** is a stable identity with a complete current definition.
18
+ - A **session** is a durable conversation with one agent definition.
19
+ - A **turn** is one accepted input and its driver result.
20
+ - A **driver** runs the agent using a host-provided harness.
21
+ - The **Agents service** is the behavior shared by every public surface.
22
+
23
+ ## Axioms
24
+
25
+ 1. A user can create, list, inspect, edit, and delete agents.
26
+ 2. A user or another agent can start, inspect, and continue an agent session
27
+ without creating or depending on a Chat conversation.
28
+ 3. An agent definition can reference its harness, model, instructions, tools,
29
+ skills, files, credentials, and subagents without Agents owning those
30
+ products.
31
+ 4. Any compatible agent harness can be used through the same driver contract.
32
+ 5. Agent definitions and sessions survive restart, while uncertain interrupted
33
+ execution is never silently replayed.
34
+ 6. SDK, CLI, REST, MCP, and skill workflows expose the same Agents behavior;
35
+ none is a second implementation.
36
+ 7. An unchanged current definition is idempotent; every actual change creates
37
+ a new immutable revision, even when its content matches an earlier revision.
38
+
39
+ ## Consequences
40
+
41
+ The package needs no cloud command inbox, scheduler, trigger relay, Chat
42
+ record, or general event-routing system. Engine and other hosts may call the
43
+ SDK whenever they need to route work, but routing is outside the Agents
44
+ product. Internal revisions, ordered events, idempotency, and cancellation
45
+ exist only to make the six behaviors above durable and predictable.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Amalgm Agents
2
+
3
+ `@amalgm/agents` is a local-first TypeScript SDK for persistent agent
4
+ definitions and durable multi-turn sessions. The same service powers its CLI,
5
+ REST API, and MCP server.
6
+
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
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @amalgm/agents
15
+ ```
16
+
17
+ Node 20 or newer is required.
18
+
19
+ ## First agent
20
+
21
+ ```ts
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
+ });
39
+
40
+ const agents = new Agents({ stateDir: './state', drivers: [echo] });
41
+
42
+ agents.createAgent({
43
+ id: 'reviewer',
44
+ name: 'Reviewer',
45
+ driver: { id: 'echo' },
46
+ instructions: 'Review work carefully.',
47
+ toolbox: { toolIds: ['git'], actionIds: ['git.diff'] },
48
+ });
49
+
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();
59
+ ```
60
+
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.
63
+
64
+ ## Product surfaces
65
+
66
+ | Surface | Entry point | Purpose |
67
+ |---|---|---|
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 |
72
+ | Skill | `skills/amalgm-agents` | Minimal workflow over the Agents tools |
73
+
74
+ ## CLI
75
+
76
+ ```bash
77
+ amalgm-agents agent create ./agent.json
78
+ amalgm-agents agent list
79
+ amalgm-agents session start reviewer
80
+ amalgm-agents session send <session-id> "Review the diff"
81
+ amalgm-agents talk reviewer "Review the diff"
82
+ ```
83
+
84
+ The CLI reads the same SQLite database as the SDK. Driver modules are loaded
85
+ with `--drivers ./dist/codex-driver.js` or `AMALGM_AGENT_DRIVERS`.
86
+
87
+ ## Documentation
88
+
89
+ - [Purpose and axioms](./PURPOSE.md)
90
+ - [Architecture and boundaries](./docs/ARCHITECTURE.md)
91
+ - [Agent definitions](./docs/DEFINITIONS.md)
92
+ - [SDK reference](./docs/SDK.md)
93
+ - [Driver contract](./docs/DRIVERS.md)
94
+ - [CLI reference](./docs/CLI.md)
95
+ - [REST API](./docs/REST.md)
96
+ - [MCP server](./docs/MCP.md)
97
+ - [Persistence and recovery](./docs/DATA_MODEL.md)
98
+ - [Security model](./docs/SECURITY.md)
99
+ - [Engine integration](./docs/ENGINE_INTEGRATION.md)
100
+
101
+ ## Development
102
+
103
+ ```bash
104
+ npm ci
105
+ npm run verify
106
+ ```
107
+
108
+ `verify` runs strict typechecking for source, tests, and examples; enforces the
109
+ 220-line source-file limit; builds JavaScript and declarations; and runs the
110
+ real SQLite, process-restart, TCP/SSE, stdio MCP, and CLI tests.
@@ -0,0 +1,17 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { AgentDefinitionInput, AgentPatch, AgentRecord, AgentRevision } from './types.js';
3
+ export declare class AgentStore {
4
+ private readonly database;
5
+ private readonly now;
6
+ constructor(database: Database.Database, now: () => string);
7
+ create(input: AgentDefinitionInput): AgentRecord;
8
+ apply(input: AgentDefinitionInput): AgentRecord;
9
+ update(id: string, patch: AgentPatch): AgentRecord;
10
+ delete(id: string): AgentRecord;
11
+ get(id: string, includeDeleted?: boolean): AgentRecord | null;
12
+ require(id: string, includeDeleted?: boolean): AgentRecord;
13
+ list(includeDeleted?: boolean): AgentRecord[];
14
+ revision(agentId: string, revisionId?: string): AgentRevision;
15
+ private rawAgent;
16
+ private insertRevision;
17
+ }
@@ -0,0 +1,111 @@
1
+ import { definitionHash, normalizeDefinition, patchDefinition } from './definition.js';
2
+ import { AgentError } from './errors.js';
3
+ import { newId, requireId } from './ids.js';
4
+ import { agentFromRow, revisionFromRow } from './rows.js';
5
+ const AGENT_SELECT = `
6
+ SELECT a.*, r.id AS revision_id, r.revision_number, r.definition_hash,
7
+ r.definition_json, r.created_at AS revision_created_at
8
+ FROM agents a
9
+ JOIN agent_revisions r ON r.id = a.current_revision_id
10
+ `;
11
+ export class AgentStore {
12
+ database;
13
+ now;
14
+ constructor(database, now) {
15
+ this.database = database;
16
+ this.now = now;
17
+ }
18
+ create(input) {
19
+ const definition = normalizeDefinition(input);
20
+ if (this.rawAgent(definition.id)) {
21
+ throw new AgentError('conflict', `Agent id already exists: ${definition.id}`);
22
+ }
23
+ const timestamp = this.now();
24
+ const revisionId = newId('revision');
25
+ this.database.transaction(() => {
26
+ this.database.prepare(`
27
+ INSERT INTO agents (id, created_at, updated_at) VALUES (?, ?, ?)
28
+ `).run(definition.id, timestamp, timestamp);
29
+ this.insertRevision(revisionId, definition.id, 1, definition, timestamp);
30
+ this.database.prepare(`
31
+ UPDATE agents SET current_revision_id = ?, current_revision_number = 1 WHERE id = ?
32
+ `).run(revisionId, definition.id);
33
+ })();
34
+ return this.require(definition.id);
35
+ }
36
+ apply(input) {
37
+ const definition = normalizeDefinition(input);
38
+ const existing = this.get(definition.id, true);
39
+ if (!existing)
40
+ return this.create(definition);
41
+ if (existing.deletedAt)
42
+ throw new AgentError('deleted', `Agent is deleted: ${definition.id}`);
43
+ const hash = definitionHash(definition);
44
+ const equal = this.database.prepare(`
45
+ SELECT 1 FROM agent_revisions WHERE id = ? AND definition_hash = ?
46
+ `).get(existing.currentRevisionId, hash);
47
+ if (equal)
48
+ return existing;
49
+ const timestamp = this.now();
50
+ const revisionId = newId('revision');
51
+ const number = existing.currentRevision + 1;
52
+ this.database.transaction(() => {
53
+ this.insertRevision(revisionId, existing.id, number, definition, timestamp);
54
+ this.database.prepare(`
55
+ UPDATE agents SET current_revision_id = ?, current_revision_number = ?, updated_at = ?
56
+ WHERE id = ? AND deleted_at IS NULL
57
+ `).run(revisionId, number, timestamp, existing.id);
58
+ })();
59
+ return this.require(existing.id);
60
+ }
61
+ update(id, patch) {
62
+ const existing = this.require(id);
63
+ return this.apply(patchDefinition(existing.definition, patch));
64
+ }
65
+ delete(id) {
66
+ const existing = this.require(id, true);
67
+ if (existing.deletedAt)
68
+ return existing;
69
+ const timestamp = this.now();
70
+ this.database.prepare(`UPDATE agents SET deleted_at = ?, updated_at = ? WHERE id = ?`)
71
+ .run(timestamp, timestamp, existing.id);
72
+ return this.require(existing.id, true);
73
+ }
74
+ get(id, includeDeleted = false) {
75
+ const clean = requireId(id, 'agent id');
76
+ const deleted = includeDeleted ? '' : ' AND a.deleted_at IS NULL';
77
+ const row = this.database.prepare(`${AGENT_SELECT} WHERE a.id = ?${deleted}`).get(clean);
78
+ return row ? agentFromRow(row) : null;
79
+ }
80
+ require(id, includeDeleted = false) {
81
+ const agent = this.get(id, includeDeleted);
82
+ if (!agent)
83
+ throw new AgentError('not_found', `Agent not found: ${id}`);
84
+ return agent;
85
+ }
86
+ list(includeDeleted = false) {
87
+ const where = includeDeleted ? '' : ' WHERE a.deleted_at IS NULL';
88
+ return this.database.prepare(`${AGENT_SELECT}${where} ORDER BY a.updated_at DESC`).all()
89
+ .map(agentFromRow);
90
+ }
91
+ revision(agentId, revisionId) {
92
+ const agent = this.require(agentId, true);
93
+ const id = revisionId || agent.currentRevisionId;
94
+ const row = this.database.prepare(`
95
+ SELECT * FROM agent_revisions WHERE id = ? AND agent_id = ?
96
+ `).get(id, agent.id);
97
+ if (!row)
98
+ throw new AgentError('not_found', `Agent revision not found: ${id}`);
99
+ return revisionFromRow(row);
100
+ }
101
+ rawAgent(id) {
102
+ return this.database.prepare('SELECT 1 FROM agents WHERE id = ?').get(id);
103
+ }
104
+ insertRevision(id, agentId, number, definition, timestamp) {
105
+ this.database.prepare(`
106
+ INSERT INTO agent_revisions
107
+ (id, agent_id, revision_number, definition_hash, definition_json, created_at)
108
+ VALUES (?, ?, ?, ?, ?, ?)
109
+ `).run(id, agentId, number, definitionHash(definition), JSON.stringify(definition), timestamp);
110
+ }
111
+ }
@@ -0,0 +1,39 @@
1
+ import type { AgentDefinitionInput, AgentDriver, AgentPatch, AgentRecord, AgentSession, AgentsOptions, AgentTurn, EnqueuedTurn, EventListener, JsonObject, SendInput, SessionEvent, StartSessionInput } from './types.js';
2
+ export declare class Agents {
3
+ readonly databasePath: string;
4
+ readonly recoveredTurns: number;
5
+ private readonly opened;
6
+ private readonly agentStore;
7
+ private readonly eventStore;
8
+ private readonly sessionStore;
9
+ private readonly turnStore;
10
+ private readonly runtime;
11
+ private readonly listeners;
12
+ private closed;
13
+ constructor(options?: AgentsOptions);
14
+ createAgent(input: AgentDefinitionInput): AgentRecord;
15
+ updateAgent(id: string, patch: AgentPatch): AgentRecord;
16
+ deleteAgent(id: string): AgentRecord;
17
+ getAgent(id: string, includeDeleted?: boolean): AgentRecord | null;
18
+ listAgents(includeDeleted?: boolean): AgentRecord[];
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;
36
+ health(): JsonObject;
37
+ close(): Promise<void>;
38
+ private publish;
39
+ }
package/dist/agents.js ADDED
@@ -0,0 +1,129 @@
1
+ import path from 'node:path';
2
+ import { AgentStore } from './agent-store.js';
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
+ export class Agents {
10
+ databasePath;
11
+ recoveredTurns;
12
+ opened;
13
+ agentStore;
14
+ eventStore;
15
+ sessionStore;
16
+ turnStore;
17
+ runtime;
18
+ listeners = new Map();
19
+ closed = false;
20
+ constructor(options = {}) {
21
+ this.opened = openDatabase(options);
22
+ this.databasePath = this.opened.databasePath;
23
+ const now = options.now || (() => new Date().toISOString());
24
+ this.eventStore = new EventStore(this.opened.database, now, (event) => this.publish(event));
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);
34
+ }
35
+ createAgent(input) {
36
+ return this.agentStore.create(input);
37
+ }
38
+ updateAgent(id, patch) {
39
+ return this.agentStore.update(id, patch);
40
+ }
41
+ deleteAgent(id) {
42
+ return this.agentStore.delete(id);
43
+ }
44
+ getAgent(id, includeDeleted = false) {
45
+ return this.agentStore.get(id, includeDeleted);
46
+ }
47
+ listAgents(includeDeleted = false) {
48
+ return this.agentStore.list(includeDeleted);
49
+ }
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
+ };
108
+ }
109
+ health() {
110
+ return {
111
+ ok: !this.closed,
112
+ database: path.basename(this.databasePath),
113
+ recoveredTurns: this.recoveredTurns,
114
+ drivers: this.driverIds(),
115
+ };
116
+ }
117
+ async close() {
118
+ if (this.closed)
119
+ return;
120
+ this.closed = true;
121
+ await this.runtime.shutdown();
122
+ this.listeners.clear();
123
+ this.opened.close();
124
+ }
125
+ publish(event) {
126
+ for (const listener of this.listeners.get(event.sessionId) || [])
127
+ listener(event);
128
+ }
129
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../cli/run.js';
3
+ void runCli().then((code) => {
4
+ process.exitCode = code;
5
+ });
@@ -0,0 +1 @@
1
+ export declare function fatal(error: unknown): void;
@@ -0,0 +1,5 @@
1
+ export function fatal(error) {
2
+ const message = error instanceof Error ? error.message : String(error);
3
+ process.stderr.write(`amalgm-agents: ${message}\n`);
4
+ process.exitCode = 1;
5
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from '../cli/args.js';
3
+ import { openAgents } from '../cli/open.js';
4
+ import { createMcpServer } from '../mcp/server.js';
5
+ import { fatal } from './fatal.js';
6
+ async function main() {
7
+ const args = parseArgs(process.argv.slice(2));
8
+ if (args.flags.has('help')) {
9
+ process.stdout.write('Usage: amalgm-agents-mcp [--state-dir <path>] [--drivers <a.js,b.js>]\n');
10
+ return;
11
+ }
12
+ const agents = await openAgents(args);
13
+ const server = createMcpServer({ agents });
14
+ const stop = await server.start();
15
+ const close = () => void stop().then(() => agents.close()).catch(fatal);
16
+ process.once('SIGTERM', close);
17
+ process.once('SIGINT', close);
18
+ }
19
+ void main().catch(fatal);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs, flag } from '../cli/args.js';
3
+ import { openAgents } from '../cli/open.js';
4
+ import { createRestServer } from '../http/server.js';
5
+ import { fatal } from './fatal.js';
6
+ async function main() {
7
+ const args = parseArgs(process.argv.slice(2));
8
+ if (args.flags.has('help')) {
9
+ process.stdout.write('Usage: amalgm-agents-rest [--host 127.0.0.1] [--port 4317] [--token value]\n');
10
+ return;
11
+ }
12
+ const agents = await openAgents(args);
13
+ const token = flag(args, 'token') || process.env.AMALGM_AGENTS_TOKEN;
14
+ const rest = createRestServer({ agents, ...(token ? { token } : {}) });
15
+ const address = await rest.listen(Number(flag(args, 'port') || 4317), flag(args, 'host') || '127.0.0.1');
16
+ process.stderr.write(`amalgm-agents REST listening on http://${address.address}:${address.port}\n`);
17
+ const close = () => void rest.close().then(() => agents.close()).catch(fatal);
18
+ process.once('SIGTERM', close);
19
+ process.once('SIGINT', close);
20
+ }
21
+ void main().catch(fatal);
@@ -0,0 +1,3 @@
1
+ import type { Agents } from '../agents.js';
2
+ import type { ParsedArgs } from './args.js';
3
+ export declare function runAgentCommand(agents: Agents, args: ParsedArgs): unknown;
@@ -0,0 +1,23 @@
1
+ import { AgentError } from '../errors.js';
2
+ import { enabled } from './args.js';
3
+ import { readObjectFile } from './files.js';
4
+ export function runAgentCommand(agents, args) {
5
+ const [, action, first, second] = args.words;
6
+ if (action === 'list')
7
+ return { agents: agents.listAgents(enabled(args, 'include-deleted')) };
8
+ if (action === 'show' && first) {
9
+ const agent = agents.getAgent(first, enabled(args, 'include-deleted'));
10
+ if (!agent)
11
+ throw new AgentError('not_found', `Agent not found: ${first}`);
12
+ return { agent };
13
+ }
14
+ if (action === 'create' && first) {
15
+ return { agent: agents.createAgent(readObjectFile(first)) };
16
+ }
17
+ if (action === 'update' && first && second) {
18
+ return { agent: agents.updateAgent(first, readObjectFile(second)) };
19
+ }
20
+ if (action === 'delete' && first)
21
+ return { agent: agents.deleteAgent(first) };
22
+ throw new AgentError('invalid_input', 'Unknown or incomplete agent command. Run with --help.');
23
+ }
@@ -0,0 +1,7 @@
1
+ export interface ParsedArgs {
2
+ words: string[];
3
+ flags: Map<string, string | boolean>;
4
+ }
5
+ export declare function parseArgs(argv: string[]): ParsedArgs;
6
+ export declare function flag(args: ParsedArgs, name: string): string | undefined;
7
+ export declare function enabled(args: ParsedArgs, name: string): boolean;
@@ -0,0 +1,31 @@
1
+ export function parseArgs(argv) {
2
+ const words = [];
3
+ const flags = new Map();
4
+ for (let index = 0; index < argv.length; index += 1) {
5
+ const item = argv[index];
6
+ if (!item?.startsWith('--')) {
7
+ if (item !== undefined)
8
+ words.push(item);
9
+ continue;
10
+ }
11
+ const [rawName, inline] = item.slice(2).split('=', 2);
12
+ const next = argv[index + 1];
13
+ if (inline !== undefined)
14
+ flags.set(rawName || '', inline);
15
+ else if (next !== undefined && !next.startsWith('--')) {
16
+ flags.set(rawName || '', next);
17
+ index += 1;
18
+ }
19
+ else
20
+ flags.set(rawName || '', true);
21
+ }
22
+ return { words, flags };
23
+ }
24
+ export function flag(args, name) {
25
+ const value = args.flags.get(name);
26
+ return typeof value === 'string' ? value : undefined;
27
+ }
28
+ export function enabled(args, name) {
29
+ const value = args.flags.get(name);
30
+ return value === true || value === 'true';
31
+ }
@@ -0,0 +1,2 @@
1
+ export declare function readObjectFile(file: string): Record<string, unknown>;
2
+ export declare function writeJson(value: unknown): void;
@@ -0,0 +1,21 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { AgentError } from '../errors.js';
4
+ export function readObjectFile(file) {
5
+ const target = path.resolve(file);
6
+ let value;
7
+ try {
8
+ value = JSON.parse(fs.readFileSync(target, 'utf8'));
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ throw new AgentError('invalid_input', `Cannot read JSON file ${target}: ${message}`);
13
+ }
14
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
15
+ throw new AgentError('invalid_input', `JSON file must contain an object: ${target}`);
16
+ }
17
+ return value;
18
+ }
19
+ export function writeJson(value) {
20
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
21
+ }
@@ -0,0 +1 @@
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";
@@ -0,0 +1,24 @@
1
+ export const HELP = `amalgm-agents — local agent definitions and durable sessions
2
+
3
+ Agent definitions:
4
+ amalgm-agents agent list [--include-deleted]
5
+ amalgm-agents agent show <id>
6
+ amalgm-agents agent create <definition.json>
7
+ amalgm-agents agent update <id> <patch.json>
8
+ amalgm-agents agent delete <id>
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
+
20
+ Global options:
21
+ --state-dir <path> Agents state directory
22
+ --drivers <a.js,b.js> Driver modules (or AMALGM_AGENT_DRIVERS)
23
+ --help Show this help
24
+ `;
@@ -0,0 +1,3 @@
1
+ import { Agents } from '../agents.js';
2
+ import type { ParsedArgs } from './args.js';
3
+ export declare function openAgents(args: ParsedArgs): Promise<Agents>;
@@ -0,0 +1,12 @@
1
+ import { Agents } from '../agents.js';
2
+ import { driverSpecifiers, loadDriverModules } from '../drivers.js';
3
+ import { flag } from './args.js';
4
+ export async function openAgents(args) {
5
+ const modules = flag(args, 'drivers');
6
+ const stateDir = flag(args, 'state-dir');
7
+ const drivers = await loadDriverModules(driverSpecifiers(modules));
8
+ return new Agents({
9
+ drivers,
10
+ ...(stateDir ? { stateDir } : {}),
11
+ });
12
+ }