@ensoul-network/plugin-elizaos 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 (67) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-lint.log +5 -0
  3. package/.turbo/turbo-test.log +15 -0
  4. package/SECURITY.md +37 -0
  5. package/coverage/actions.ts.html +514 -0
  6. package/coverage/adapter.ts.html +298 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/clover.xml +495 -0
  10. package/coverage/coverage-final.json +9 -0
  11. package/coverage/elizaos-types.ts.html +397 -0
  12. package/coverage/evaluators.ts.html +196 -0
  13. package/coverage/favicon.png +0 -0
  14. package/coverage/handshake.ts.html +940 -0
  15. package/coverage/index.html +221 -0
  16. package/coverage/index.ts.html +208 -0
  17. package/coverage/plugin.ts.html +367 -0
  18. package/coverage/prettify.css +1 -0
  19. package/coverage/prettify.js +2 -0
  20. package/coverage/providers.ts.html +286 -0
  21. package/coverage/sort-arrow-sprite.png +0 -0
  22. package/coverage/sorter.js +210 -0
  23. package/dist/actions.d.ts +24 -0
  24. package/dist/actions.d.ts.map +1 -0
  25. package/dist/actions.js +108 -0
  26. package/dist/actions.js.map +1 -0
  27. package/dist/adapter.d.ts +18 -0
  28. package/dist/adapter.d.ts.map +1 -0
  29. package/dist/adapter.js +55 -0
  30. package/dist/adapter.js.map +1 -0
  31. package/dist/elizaos-types.d.ts +81 -0
  32. package/dist/elizaos-types.d.ts.map +1 -0
  33. package/dist/elizaos-types.js +7 -0
  34. package/dist/elizaos-types.js.map +1 -0
  35. package/dist/evaluators.d.ts +8 -0
  36. package/dist/evaluators.d.ts.map +1 -0
  37. package/dist/evaluators.js +24 -0
  38. package/dist/evaluators.js.map +1 -0
  39. package/dist/handshake.d.ts +78 -0
  40. package/dist/handshake.d.ts.map +1 -0
  41. package/dist/handshake.js +195 -0
  42. package/dist/handshake.js.map +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +7 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/plugin.d.ts +24 -0
  48. package/dist/plugin.d.ts.map +1 -0
  49. package/dist/plugin.js +53 -0
  50. package/dist/plugin.js.map +1 -0
  51. package/dist/providers.d.ts +12 -0
  52. package/dist/providers.d.ts.map +1 -0
  53. package/dist/providers.js +49 -0
  54. package/dist/providers.js.map +1 -0
  55. package/package.json +25 -0
  56. package/src/actions.ts +143 -0
  57. package/src/adapter.ts +71 -0
  58. package/src/elizaos-types.ts +104 -0
  59. package/src/evaluators.ts +37 -0
  60. package/src/handshake.ts +285 -0
  61. package/src/index.ts +41 -0
  62. package/src/plugin.ts +94 -0
  63. package/src/providers.ts +67 -0
  64. package/tests/handshake.test.ts +328 -0
  65. package/tests/plugin.test.ts +419 -0
  66. package/tsconfig.json +8 -0
  67. package/vitest.config.ts +7 -0
@@ -0,0 +1,285 @@
1
+ import type { AgentIdentity } from "@ensoul/identity";
2
+ import type { ConsciousnessTree } from "@ensoul/state-tree";
3
+
4
+ /**
5
+ * The three Ensoul Handshake headers.
6
+ */
7
+ export interface HandshakeHeaders {
8
+ "X-Ensoul-Identity": string;
9
+ "X-Ensoul-Proof": string;
10
+ "X-Ensoul-Since": string;
11
+ }
12
+
13
+ /**
14
+ * Result of verifying a handshake.
15
+ */
16
+ export interface HandshakeVerification {
17
+ valid: boolean;
18
+ agentDid: string;
19
+ consciousnessAge: number;
20
+ consciousnessVersion: number;
21
+ error?: string;
22
+ }
23
+
24
+ /** Handshake cache entry. */
25
+ interface CachedHandshake {
26
+ headers: HandshakeHeaders;
27
+ generatedAt: number;
28
+ stateRoot: string;
29
+ }
30
+
31
+ /** Max age of a cached handshake in ms (5 minutes). */
32
+ const CACHE_TTL_MS = 5 * 60 * 1000;
33
+ /** Max age of a received proof before it's considered stale (10 minutes). */
34
+ const FRESHNESS_WINDOW_MS = 10 * 60 * 1000;
35
+
36
+ /**
37
+ * HandshakeProvider: generates and caches the Ensoul Handshake headers.
38
+ * Refreshes every 5 minutes or on state change.
39
+ */
40
+ export class HandshakeProvider {
41
+ private identity: AgentIdentity;
42
+ private tree: ConsciousnessTree;
43
+ private ensoulmentDate: Date;
44
+ private cache: CachedHandshake | null = null;
45
+
46
+ constructor(
47
+ identity: AgentIdentity,
48
+ tree: ConsciousnessTree,
49
+ ensoulmentDate?: Date,
50
+ ) {
51
+ this.identity = identity;
52
+ this.tree = tree;
53
+ this.ensoulmentDate = ensoulmentDate ?? new Date();
54
+ }
55
+
56
+ /**
57
+ * Generate fresh handshake headers, or return cached if still valid.
58
+ */
59
+ async generateHandshake(): Promise<HandshakeHeaders> {
60
+ const now = Date.now();
61
+ const currentRoot = this.tree.rootHash;
62
+
63
+ // Return cached if fresh and state hasn't changed
64
+ if (
65
+ this.cache &&
66
+ now - this.cache.generatedAt < CACHE_TTL_MS &&
67
+ this.cache.stateRoot === currentRoot
68
+ ) {
69
+ return this.cache.headers;
70
+ }
71
+
72
+ // Generate fresh proof
73
+ const version = this.tree.version;
74
+ const timestamp = now;
75
+ const proofPayload = `${currentRoot}:${version}:${timestamp}`;
76
+ const signature = await this.identity.sign(
77
+ new TextEncoder().encode(proofPayload),
78
+ );
79
+ const sigHex = Array.from(signature)
80
+ .map((b) => b.toString(16).padStart(2, "0"))
81
+ .join("");
82
+
83
+ const headers: HandshakeHeaders = {
84
+ "X-Ensoul-Identity": `did:ensoul:${this.identity.did}`,
85
+ "X-Ensoul-Proof": `${sigHex}:${currentRoot}:${version}:${timestamp}`,
86
+ "X-Ensoul-Since": this.ensoulmentDate.toISOString(),
87
+ };
88
+
89
+ this.cache = {
90
+ headers,
91
+ generatedAt: now,
92
+ stateRoot: currentRoot,
93
+ };
94
+
95
+ return headers;
96
+ }
97
+
98
+ /**
99
+ * Force cache invalidation (call when state changes).
100
+ */
101
+ invalidateCache(): void {
102
+ this.cache = null;
103
+ }
104
+
105
+ /**
106
+ * Get the ensoulment date.
107
+ */
108
+ getEnsoulmentDate(): Date {
109
+ return this.ensoulmentDate;
110
+ }
111
+
112
+ /**
113
+ * Get consciousness age in days.
114
+ */
115
+ getConsciousnessAgeDays(): number {
116
+ const ms = Date.now() - this.ensoulmentDate.getTime();
117
+ return Math.floor(ms / (1000 * 60 * 60 * 24));
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Known DID entry for local verification cache.
123
+ */
124
+ export interface KnownIdentity {
125
+ did: string;
126
+ publicKey: Uint8Array;
127
+ verify: (data: Uint8Array, signature: Uint8Array) => Promise<boolean>;
128
+ }
129
+
130
+ /**
131
+ * HandshakeVerifier: verifies incoming Ensoul Handshake headers.
132
+ */
133
+ export class HandshakeVerifier {
134
+ private knownIdentities: Map<string, KnownIdentity> = new Map();
135
+
136
+ /**
137
+ * Register a known identity for local verification.
138
+ */
139
+ registerIdentity(identity: KnownIdentity): void {
140
+ this.knownIdentities.set(identity.did, identity);
141
+ }
142
+
143
+ /**
144
+ * Verify handshake headers.
145
+ */
146
+ async verifyHandshake(headers: {
147
+ "X-Ensoul-Identity"?: string;
148
+ "X-Ensoul-Proof"?: string;
149
+ "X-Ensoul-Since"?: string;
150
+ }): Promise<HandshakeVerification> {
151
+ const identityHeader = headers["X-Ensoul-Identity"];
152
+ const proofHeader = headers["X-Ensoul-Proof"];
153
+ const sinceHeader = headers["X-Ensoul-Since"];
154
+
155
+ if (!identityHeader || !proofHeader || !sinceHeader) {
156
+ return {
157
+ valid: false,
158
+ agentDid: "",
159
+ consciousnessAge: 0,
160
+ consciousnessVersion: 0,
161
+ error: "Missing handshake headers",
162
+ };
163
+ }
164
+
165
+ // Parse identity
166
+ const agentDid = identityHeader.replace("did:ensoul:", "");
167
+
168
+ // Parse proof: signature:stateRoot:version:timestamp
169
+ const proofParts = proofHeader.split(":");
170
+ if (proofParts.length < 4) {
171
+ return {
172
+ valid: false,
173
+ agentDid,
174
+ consciousnessAge: 0,
175
+ consciousnessVersion: 0,
176
+ error: "Malformed proof header",
177
+ };
178
+ }
179
+
180
+ const sigHex = proofParts[0]!;
181
+ const stateRoot = proofParts[1]!;
182
+ const version = Number(proofParts[2]);
183
+ const timestamp = Number(proofParts[3]);
184
+
185
+ // Check timestamp freshness
186
+ const now = Date.now();
187
+ if (now - timestamp > FRESHNESS_WINDOW_MS) {
188
+ return {
189
+ valid: false,
190
+ agentDid,
191
+ consciousnessAge: 0,
192
+ consciousnessVersion: version,
193
+ error: "Proof expired (timestamp too old)",
194
+ };
195
+ }
196
+
197
+ if (timestamp > now + 60000) {
198
+ return {
199
+ valid: false,
200
+ agentDid,
201
+ consciousnessAge: 0,
202
+ consciousnessVersion: version,
203
+ error: "Proof timestamp in the future",
204
+ };
205
+ }
206
+
207
+ // Look up identity
208
+ const known = this.knownIdentities.get(agentDid);
209
+ if (!known) {
210
+ return {
211
+ valid: false,
212
+ agentDid,
213
+ consciousnessAge: 0,
214
+ consciousnessVersion: version,
215
+ error: "Unknown identity (not in local cache)",
216
+ };
217
+ }
218
+
219
+ // Verify signature
220
+ const proofPayload = `${stateRoot}:${version}:${timestamp}`;
221
+ const signature = hexToBytes(sigHex);
222
+
223
+ try {
224
+ const valid = await known.verify(
225
+ new TextEncoder().encode(proofPayload),
226
+ signature,
227
+ );
228
+
229
+ if (!valid) {
230
+ return {
231
+ valid: false,
232
+ agentDid,
233
+ consciousnessAge: 0,
234
+ consciousnessVersion: version,
235
+ error: "Invalid signature",
236
+ };
237
+ }
238
+ } catch {
239
+ return {
240
+ valid: false,
241
+ agentDid,
242
+ consciousnessAge: 0,
243
+ consciousnessVersion: version,
244
+ error: "Signature verification failed",
245
+ };
246
+ }
247
+
248
+ // Compute consciousness age
249
+ const sinceDate = new Date(sinceHeader);
250
+ const ageDays = Math.floor(
251
+ (now - sinceDate.getTime()) / (1000 * 60 * 60 * 24),
252
+ );
253
+
254
+ return {
255
+ valid: true,
256
+ agentDid,
257
+ consciousnessAge: ageDays,
258
+ consciousnessVersion: version,
259
+ };
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Standalone handshake generator for non-ElizaOS frameworks.
265
+ */
266
+ export async function generateStandaloneHandshake(
267
+ identity: AgentIdentity,
268
+ tree: ConsciousnessTree,
269
+ ensoulmentDate?: Date,
270
+ ): Promise<HandshakeHeaders> {
271
+ const provider = new HandshakeProvider(
272
+ identity,
273
+ tree,
274
+ ensoulmentDate,
275
+ );
276
+ return provider.generateHandshake();
277
+ }
278
+
279
+ function hexToBytes(hex: string): Uint8Array {
280
+ const bytes = new Uint8Array(hex.length / 2);
281
+ for (let i = 0; i < hex.length; i += 2) {
282
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
283
+ }
284
+ return bytes;
285
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ export { createConsciousnessPlugin } from "./plugin.js";
2
+ export type { EnsoulPluginConfig } from "./plugin.js";
3
+
4
+ export { ConsciousnessAdapter } from "./adapter.js";
5
+
6
+ export {
7
+ createPersistMemoryAction,
8
+ createRecallFromNetworkAction,
9
+ createCheckPersistenceAction,
10
+ createRunNodeAction,
11
+ } from "./actions.js";
12
+
13
+ export {
14
+ createConsciousnessStatusProvider,
15
+ createNetworkStatsProvider,
16
+ } from "./providers.js";
17
+
18
+ export { createShouldPersistEvaluator } from "./evaluators.js";
19
+
20
+ export {
21
+ HandshakeProvider,
22
+ HandshakeVerifier,
23
+ generateStandaloneHandshake,
24
+ } from "./handshake.js";
25
+
26
+ export type {
27
+ HandshakeHeaders,
28
+ HandshakeVerification,
29
+ KnownIdentity,
30
+ } from "./handshake.js";
31
+
32
+ export type {
33
+ ElizaPlugin,
34
+ ElizaAction,
35
+ ElizaProvider,
36
+ ElizaEvaluator,
37
+ ElizaDatabaseAdapter,
38
+ ElizaRuntime,
39
+ ElizaMemory,
40
+ ElizaMessage,
41
+ } from "./elizaos-types.js";
package/src/plugin.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { createIdentity } from "@ensoul/identity";
2
+ import type { AgentIdentity } from "@ensoul/identity";
3
+ import { createMemoryManager } from "@ensoul/memory";
4
+ import type { MemoryManager } from "@ensoul/memory";
5
+ import { NetworkClientImpl } from "@ensoul/network-client";
6
+ import type { NetworkClient } from "@ensoul/network-client";
7
+ import { createTree } from "@ensoul/state-tree";
8
+ import type { ElizaPlugin } from "./elizaos-types.js";
9
+ import { ConsciousnessAdapter } from "./adapter.js";
10
+ import {
11
+ createPersistMemoryAction,
12
+ createRecallFromNetworkAction,
13
+ createCheckPersistenceAction,
14
+ createRunNodeAction,
15
+ } from "./actions.js";
16
+ import {
17
+ createConsciousnessStatusProvider,
18
+ createNetworkStatsProvider,
19
+ } from "./providers.js";
20
+ import { createShouldPersistEvaluator } from "./evaluators.js";
21
+
22
+ /**
23
+ * Configuration for the Ensoul consciousness plugin.
24
+ */
25
+ export interface EnsoulPluginConfig {
26
+ /** Bootstrap peer multiaddr(s) to connect to. */
27
+ bootstrapPeers?: string[];
28
+ /** Optional pre-created identity (otherwise auto-generated). */
29
+ identity?: AgentIdentity;
30
+ /** Optional pre-created memory manager. */
31
+ memoryManager?: MemoryManager;
32
+ /** Optional pre-created network client. */
33
+ networkClient?: NetworkClient;
34
+ }
35
+
36
+ /**
37
+ * Create the Ensoul consciousness plugin for ElizaOS.
38
+ * Minimal setup: just provide bootstrap peers.
39
+ * Identity, storage, and node participation are automatic.
40
+ */
41
+ export async function createConsciousnessPlugin(
42
+ config?: EnsoulPluginConfig,
43
+ ): Promise<ElizaPlugin> {
44
+ // Auto-generate identity if not provided
45
+ const identity = config?.identity ?? (await createIdentity());
46
+
47
+ // Create state tree
48
+ const tree = await createTree(identity);
49
+
50
+ // Create network client
51
+ const networkClient =
52
+ config?.networkClient ??
53
+ new NetworkClientImpl(identity);
54
+
55
+ // Connect to network if bootstrap peers provided
56
+ const peers = config?.bootstrapPeers ?? [];
57
+ if (peers.length > 0) {
58
+ await networkClient.connect(peers);
59
+ }
60
+
61
+ // Create memory manager
62
+ const manager =
63
+ config?.memoryManager ??
64
+ (await createMemoryManager({
65
+ identity,
66
+ tree,
67
+ networkClient,
68
+ }));
69
+
70
+ // Build the plugin
71
+ const adapter = new ConsciousnessAdapter(manager);
72
+
73
+ return {
74
+ name: "ensoul-consciousness",
75
+ description:
76
+ "Decentralized, indestructible memory persistence for autonomous agents. Powered by the Ensoul network.",
77
+
78
+ databaseAdapter: adapter,
79
+
80
+ actions: [
81
+ createPersistMemoryAction(manager),
82
+ createRecallFromNetworkAction(manager),
83
+ createCheckPersistenceAction(manager),
84
+ createRunNodeAction(networkClient),
85
+ ],
86
+
87
+ providers: [
88
+ createConsciousnessStatusProvider(manager),
89
+ createNetworkStatsProvider(networkClient),
90
+ ],
91
+
92
+ evaluators: [createShouldPersistEvaluator(manager)],
93
+ };
94
+ }
@@ -0,0 +1,67 @@
1
+ import type { MemoryManager } from "@ensoul/memory";
2
+ import type { NetworkClient } from "@ensoul/network-client";
3
+ import type { ElizaProvider, ElizaRuntime, ElizaMessage } from "./elizaos-types.js";
4
+
5
+ /**
6
+ * Provider that injects consciousness persistence status into the agent's context.
7
+ */
8
+ export function createConsciousnessStatusProvider(
9
+ manager: MemoryManager,
10
+ ): ElizaProvider {
11
+ return {
12
+ name: "consciousnessStatus",
13
+ description:
14
+ "Provides the agent with its current consciousness persistence status.",
15
+ async get(
16
+ _runtime: ElizaRuntime,
17
+ _message: ElizaMessage,
18
+ ): Promise<string> {
19
+ const all = await manager.getAll();
20
+ const core = await manager.getAll({ tier: "core" });
21
+ const longterm = await manager.getAll({ tier: "longterm" });
22
+
23
+ return [
24
+ "[Consciousness Status]",
25
+ `Total memories: ${all.length}`,
26
+ `Core identity memories: ${core.length}`,
27
+ `Long-term knowledge: ${longterm.length}`,
28
+ all.length === 0
29
+ ? "Warning: No memories stored yet. Consider persisting important information."
30
+ : `Latest memory: "${all[all.length - 1]!.content.slice(0, 50)}..."`,
31
+ ].join("\n");
32
+ },
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Provider that injects network health stats into the agent's context.
38
+ */
39
+ export function createNetworkStatsProvider(
40
+ networkClient: NetworkClient,
41
+ ): ElizaProvider {
42
+ return {
43
+ name: "networkStats",
44
+ description:
45
+ "Provides the agent with current Ensoul network health statistics.",
46
+ async get(
47
+ _runtime: ElizaRuntime,
48
+ _message: ElizaMessage,
49
+ ): Promise<string> {
50
+ const connected = networkClient.isConnected();
51
+ const peerCount = networkClient.getPeerCount();
52
+ const balance = await networkClient.getBalance();
53
+
54
+ return [
55
+ "[Network Status]",
56
+ `Connected: ${connected ? "yes" : "no"}`,
57
+ `Peers: ${peerCount}`,
58
+ `Credit balance: ${balance}`,
59
+ balance < 10
60
+ ? "Warning: Low credit balance. Consider running a node to earn credits."
61
+ : "",
62
+ ]
63
+ .filter(Boolean)
64
+ .join("\n");
65
+ },
66
+ };
67
+ }