forbocai 0.3.0 → 0.3.3
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/README.md +63 -41
- package/dist/chunk-4FGRXBH3.mjs +1026 -0
- package/dist/cli.js +87 -22
- package/dist/cli.mjs +87 -22
- package/dist/index.d.mts +131 -197
- package/dist/index.d.ts +131 -197
- package/dist/index.js +636 -630
- package/dist/index.mjs +619 -619
- package/package.json +7 -2
- package/postinstall.js +27 -9
package/dist/index.mjs
CHANGED
|
@@ -1,149 +1,179 @@
|
|
|
1
1
|
// src/cortex.ts
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as https from "https";
|
|
5
|
+
var MODEL_URLS = {
|
|
6
|
+
"smollm2-135m": "https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct-GGUF/resolve/main/smollm2-135m-instruct-q4_k_m.gguf",
|
|
7
|
+
"llama3-8b": "https://huggingface.co/lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF/resolve/main/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf"
|
|
7
8
|
};
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return `[Mock Response] Model: ${modelId}, Temp: ${temperature}. Response to: ${prompt}`;
|
|
9
|
+
var DEFAULT_MODEL = "smollm2-135m";
|
|
10
|
+
var ensureDirectoryExists = (dirPath) => {
|
|
11
|
+
if (!fs.existsSync(dirPath)) {
|
|
12
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
13
|
+
}
|
|
14
14
|
};
|
|
15
|
-
var
|
|
16
|
-
return {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
var downloadFile = (url, destPath) => {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const file = fs.createWriteStream(destPath);
|
|
18
|
+
https.get(url, (response) => {
|
|
19
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
20
|
+
downloadFile(response.headers.location, destPath).then(resolve).catch(reject);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (response.statusCode !== 200) {
|
|
24
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
response.pipe(file);
|
|
28
|
+
file.on("finish", () => {
|
|
29
|
+
file.close();
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
}).on("error", (err) => {
|
|
33
|
+
fs.unlink(destPath, () => {
|
|
34
|
+
});
|
|
35
|
+
reject(err);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
21
38
|
};
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
var createNativeCortex = (config) => {
|
|
40
|
+
let status = {
|
|
41
|
+
id: "native-init",
|
|
42
|
+
model: config.model || DEFAULT_MODEL,
|
|
43
|
+
ready: false,
|
|
44
|
+
engine: "node-llama-cpp"
|
|
26
45
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
temperature: config.temperature ?? 0.7,
|
|
35
|
-
maxTokens: config.maxTokens ?? 1024,
|
|
36
|
-
gpu: config.gpu ?? true
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
async init() {
|
|
40
|
-
if (this.status?.ready) {
|
|
41
|
-
return this.status;
|
|
42
|
-
}
|
|
46
|
+
let llama;
|
|
47
|
+
let model;
|
|
48
|
+
let context;
|
|
49
|
+
let session;
|
|
50
|
+
const MODELS_DIR = path.join(process.cwd(), "local_infrastructure", "models");
|
|
51
|
+
const init2 = async () => {
|
|
52
|
+
if (status.ready) return status;
|
|
43
53
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
model: this.config.model,
|
|
56
|
-
ready: true,
|
|
57
|
-
engine: "webllm"
|
|
58
|
-
};
|
|
59
|
-
return this.status;
|
|
54
|
+
console.log("> Initializing Native Cortex (node-llama-cpp)...");
|
|
55
|
+
ensureDirectoryExists(MODELS_DIR);
|
|
56
|
+
const modelKey = config.model || DEFAULT_MODEL;
|
|
57
|
+
const modelUrl = MODEL_URLS[modelKey] || MODEL_URLS[DEFAULT_MODEL];
|
|
58
|
+
const modelFileName = path.basename(modelUrl);
|
|
59
|
+
const modelPath = path.join(MODELS_DIR, modelFileName);
|
|
60
|
+
if (!fs.existsSync(modelPath)) {
|
|
61
|
+
console.log(`> Downloading Model: ${modelKey}...`);
|
|
62
|
+
console.log(`> Source: ${modelUrl}`);
|
|
63
|
+
await downloadFile(modelUrl, modelPath);
|
|
64
|
+
console.log(`> Download complete.`);
|
|
60
65
|
} else {
|
|
61
|
-
console.
|
|
62
|
-
throw new Error("WebGPU unavailable");
|
|
66
|
+
console.log(`> Using cached model: ${modelPath}`);
|
|
63
67
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const { getLlama, LlamaChatSession } = await import("node-llama-cpp");
|
|
69
|
+
llama = await getLlama();
|
|
70
|
+
console.log("> Loading model into memory...");
|
|
71
|
+
model = await llama.loadModel({
|
|
72
|
+
modelPath,
|
|
73
|
+
gpuLayers: config.gpu ? 99 : 0
|
|
74
|
+
// Try to use GPU if allowed
|
|
75
|
+
});
|
|
76
|
+
console.log("> Creating context...");
|
|
77
|
+
context = await model.createContext();
|
|
78
|
+
session = new LlamaChatSession({
|
|
79
|
+
contextSequence: context.getSequence()
|
|
80
|
+
});
|
|
81
|
+
status = {
|
|
82
|
+
id: `ctx_${Date.now()}`,
|
|
83
|
+
model: modelKey,
|
|
69
84
|
ready: true,
|
|
70
|
-
engine: "
|
|
85
|
+
engine: "node-llama-cpp"
|
|
71
86
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (this.engine) {
|
|
78
|
-
const response = await this.engine.chat.completions.create({
|
|
79
|
-
messages: [{ role: "user", content: prompt }],
|
|
80
|
-
temperature: options.temperature ?? this.config.temperature,
|
|
81
|
-
max_tokens: options.maxTokens ?? this.config.maxTokens
|
|
82
|
-
});
|
|
83
|
-
return response.choices[0]?.message?.content || "";
|
|
84
|
-
} else {
|
|
85
|
-
return generateCompletion(prompt, options, this.config.model);
|
|
87
|
+
console.log("> Cortex Ready.");
|
|
88
|
+
return status;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error("Failed to initialize Native Cortex:", e);
|
|
91
|
+
throw e;
|
|
86
92
|
}
|
|
87
|
-
}
|
|
88
|
-
async
|
|
89
|
-
if (!
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
};
|
|
94
|
+
const complete = async (prompt, options = {}) => {
|
|
95
|
+
if (!status.ready) await init2();
|
|
96
|
+
return await session.prompt(prompt, {
|
|
97
|
+
maxTokens: options.maxTokens,
|
|
98
|
+
temperature: options.temperature
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const completeStream = async function* (prompt, options = {}) {
|
|
102
|
+
if (!status.ready) await init2();
|
|
103
|
+
const tokenQueue = [];
|
|
104
|
+
let resolveNext = null;
|
|
105
|
+
let isComplete = false;
|
|
106
|
+
const generationPromise = session.prompt(prompt, {
|
|
107
|
+
maxTokens: options.maxTokens,
|
|
108
|
+
temperature: options.temperature,
|
|
109
|
+
onToken: (tokens) => {
|
|
110
|
+
try {
|
|
111
|
+
const text = model.detokenize(tokens);
|
|
112
|
+
if (text) {
|
|
113
|
+
tokenQueue.push(text);
|
|
114
|
+
if (resolveNext) {
|
|
115
|
+
resolveNext();
|
|
116
|
+
resolveNext = null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
tokenQueue.push(tokens.map((t) => String.fromCharCode(t % 256)).join(""));
|
|
121
|
+
}
|
|
100
122
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
}).then(() => {
|
|
124
|
+
isComplete = true;
|
|
125
|
+
if (resolveNext) resolveNext();
|
|
126
|
+
});
|
|
127
|
+
while (!isComplete || tokenQueue.length > 0) {
|
|
128
|
+
if (tokenQueue.length > 0) {
|
|
129
|
+
yield tokenQueue.shift();
|
|
130
|
+
} else if (!isComplete) {
|
|
131
|
+
await new Promise((resolve) => {
|
|
132
|
+
resolveNext = resolve;
|
|
133
|
+
});
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
await generationPromise;
|
|
137
|
+
};
|
|
138
|
+
const embed = async (text) => {
|
|
139
|
+
if (!status.ready) await init2();
|
|
140
|
+
try {
|
|
141
|
+
return new Array(384).fill(0).map(() => Math.random());
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const processObservation = async (obs) => {
|
|
147
|
+
const prompt = `System: You are an agent.
|
|
148
|
+
Observation: ${obs.content}
|
|
149
|
+
Task: Generete a JSON directive { "type": "...", "content": "..." }.`;
|
|
150
|
+
const res = await complete(prompt);
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse(res);
|
|
153
|
+
} catch {
|
|
154
|
+
return { type: "thought", content: res };
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const generateAction = async (dir) => {
|
|
158
|
+
const prompt = `Directive: ${dir.content}. Generate JSON action.`;
|
|
159
|
+
const res = await complete(prompt);
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(res);
|
|
162
|
+
} catch {
|
|
163
|
+
return { type: "idle", reason: res };
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
init: init2,
|
|
168
|
+
complete,
|
|
169
|
+
completeStream,
|
|
170
|
+
processObservation,
|
|
171
|
+
generateAction,
|
|
172
|
+
embed
|
|
173
|
+
// Extended interface for private use by Memory
|
|
174
|
+
};
|
|
146
175
|
};
|
|
176
|
+
var createCortex = (config) => createNativeCortex(config);
|
|
147
177
|
|
|
148
178
|
// src/agent.ts
|
|
149
179
|
var createInitialState = (partial) => {
|
|
@@ -186,19 +216,19 @@ var exportToSoul = (agentId, name, persona, state, memories) => {
|
|
|
186
216
|
};
|
|
187
217
|
};
|
|
188
218
|
var createAgent = (config) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
let state = createInitialState(config.initialState);
|
|
220
|
+
let memories = [];
|
|
221
|
+
const cortex = config.cortex;
|
|
222
|
+
const apiUrl = config.apiUrl || "http://localhost:8080";
|
|
223
|
+
const getAgentState = () => {
|
|
224
|
+
return { ...state };
|
|
225
|
+
};
|
|
226
|
+
const setAgentState = (newState) => {
|
|
227
|
+
state = newState;
|
|
228
|
+
};
|
|
229
|
+
const process2 = async (input, context = {}) => {
|
|
230
|
+
const currentState = getAgentState();
|
|
200
231
|
const hp = currentState.hp || 100;
|
|
201
|
-
const apiUrl = this.config.apiUrl || "http://localhost:8080";
|
|
202
232
|
const apiContext = Object.entries(context).map(([k, v]) => [k, String(v)]);
|
|
203
233
|
apiContext.push(["hp", String(hp)]);
|
|
204
234
|
let directive = "Respond normally.";
|
|
@@ -226,227 +256,348 @@ var AgentImpl = class {
|
|
|
226
256
|
|
|
227
257
|
User: ${input}
|
|
228
258
|
Agent:`;
|
|
229
|
-
const generatedText = await
|
|
259
|
+
const generatedText = await cortex.complete(prompt);
|
|
230
260
|
return {
|
|
231
261
|
dialogue: generatedText,
|
|
232
262
|
action: { type: instruction, reason: directive },
|
|
233
263
|
thought: `Directive: ${directive}`
|
|
234
264
|
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return { ...this.state };
|
|
238
|
-
}
|
|
239
|
-
setState(newState) {
|
|
240
|
-
this.state = newState;
|
|
241
|
-
}
|
|
242
|
-
export() {
|
|
265
|
+
};
|
|
266
|
+
const exportSoul = () => {
|
|
243
267
|
return exportToSoul(
|
|
244
268
|
"agent-" + Math.random().toString(36).substring(7),
|
|
245
269
|
"Agent",
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
270
|
+
config.persona,
|
|
271
|
+
state,
|
|
272
|
+
memories
|
|
249
273
|
);
|
|
250
|
-
}
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
process: process2,
|
|
277
|
+
getState: getAgentState,
|
|
278
|
+
setState: setAgentState,
|
|
279
|
+
export: exportSoul
|
|
280
|
+
};
|
|
251
281
|
};
|
|
252
|
-
var fromSoul = (soul, cortex) => {
|
|
282
|
+
var fromSoul = (soul, cortex, memory) => {
|
|
253
283
|
const agent = createAgent({
|
|
284
|
+
id: soul.id,
|
|
254
285
|
cortex,
|
|
286
|
+
memory: memory || null,
|
|
255
287
|
persona: soul.persona,
|
|
256
288
|
initialState: soul.state
|
|
257
289
|
});
|
|
258
290
|
return agent;
|
|
259
291
|
};
|
|
260
292
|
|
|
261
|
-
// src/
|
|
262
|
-
|
|
263
|
-
var
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
293
|
+
// src/soul.ts
|
|
294
|
+
var heliaNode = null;
|
|
295
|
+
var heliaJson = null;
|
|
296
|
+
var getHelia = async () => {
|
|
297
|
+
if (heliaNode) return { node: heliaNode, j: heliaJson };
|
|
298
|
+
try {
|
|
299
|
+
const { createHelia } = await import("helia");
|
|
300
|
+
const { json } = await import("@helia/json");
|
|
301
|
+
const { MemoryBlockstore } = await import("blockstore-core");
|
|
302
|
+
const blockstore = new MemoryBlockstore();
|
|
303
|
+
heliaNode = await createHelia({ blockstore });
|
|
304
|
+
heliaJson = json(heliaNode);
|
|
305
|
+
console.log("> Helia IPFS Node Initialized:", heliaNode.libp2p.peerId.toString());
|
|
306
|
+
return { node: heliaNode, j: heliaJson };
|
|
307
|
+
} catch (e) {
|
|
308
|
+
console.warn("Failed to init Helia (IPFS):", e);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
272
311
|
};
|
|
273
|
-
var
|
|
274
|
-
const ageMs = currentTime - memory.timestamp;
|
|
275
|
-
const ageHours = ageMs / (1e3 * 60 * 60);
|
|
276
|
-
const decayFactor = Math.exp(-decayRate * ageHours);
|
|
312
|
+
var createSoul = (id, name, persona, state, memories = []) => {
|
|
277
313
|
return {
|
|
278
|
-
|
|
279
|
-
|
|
314
|
+
id,
|
|
315
|
+
version: "1.0.0",
|
|
316
|
+
name,
|
|
317
|
+
persona,
|
|
318
|
+
state: { ...state },
|
|
319
|
+
memories: [...memories]
|
|
280
320
|
};
|
|
281
321
|
};
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
-
const words2 = new Set(text2.toLowerCase().split(/\s+/));
|
|
285
|
-
let intersection = 0;
|
|
286
|
-
words1.forEach((word) => {
|
|
287
|
-
if (words2.has(word)) intersection++;
|
|
288
|
-
});
|
|
289
|
-
const union = words1.size + words2.size - intersection;
|
|
290
|
-
return union > 0 ? intersection / union : 0;
|
|
322
|
+
var serializeSoul = (soul) => {
|
|
323
|
+
return JSON.stringify(soul, null, 2);
|
|
291
324
|
};
|
|
292
|
-
var
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
return
|
|
325
|
+
var deserializeSoul = (json) => {
|
|
326
|
+
const parsed = JSON.parse(json);
|
|
327
|
+
if (!parsed.id || !parsed.persona || !parsed.state) {
|
|
328
|
+
throw new Error("Invalid Soul format: missing required fields");
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
id: parsed.id,
|
|
332
|
+
version: parsed.version || "1.0.0",
|
|
333
|
+
name: parsed.name || "Unknown",
|
|
334
|
+
persona: parsed.persona,
|
|
335
|
+
state: parsed.state,
|
|
336
|
+
memories: parsed.memories || [],
|
|
337
|
+
signature: parsed.signature
|
|
338
|
+
};
|
|
298
339
|
};
|
|
299
|
-
var
|
|
300
|
-
|
|
340
|
+
var validateSoul = (soul) => {
|
|
341
|
+
const errors = [];
|
|
342
|
+
if (!soul.id) errors.push("Missing id");
|
|
343
|
+
if (!soul.persona) errors.push("Missing persona");
|
|
344
|
+
if (!soul.state) errors.push("Missing state");
|
|
345
|
+
if (!soul.state?.mood) errors.push("Missing state.mood");
|
|
346
|
+
return {
|
|
347
|
+
valid: errors.length === 0,
|
|
348
|
+
errors
|
|
349
|
+
};
|
|
301
350
|
};
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const store = db.createObjectStore("memories", { keyPath: "id" });
|
|
317
|
-
store.createIndex("timestamp", "timestamp");
|
|
318
|
-
store.createIndex("agentId", "agentId");
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
} else {
|
|
323
|
-
console.warn("IndexedDB not available (Node.js environment). Falling back to in-memory.");
|
|
324
|
-
this.dbPromise = Promise.resolve({
|
|
325
|
-
getAll: async () => [],
|
|
326
|
-
put: async () => {
|
|
327
|
-
},
|
|
328
|
-
clear: async () => {
|
|
329
|
-
},
|
|
330
|
-
getAllFromIndex: async () => [],
|
|
331
|
-
transaction: () => ({ objectStore: () => ({ put: async () => {
|
|
332
|
-
} }), done: Promise.resolve() })
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
async getAllMemories() {
|
|
337
|
-
const db = await this.dbPromise;
|
|
338
|
-
try {
|
|
339
|
-
return await db.getAll(this.STORE_NAME) || [];
|
|
340
|
-
} catch {
|
|
341
|
-
return [];
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async store(text, type = "observation", importance = 0.5) {
|
|
345
|
-
const memory = createMemoryItem(text, type, importance);
|
|
346
|
-
const db = await this.dbPromise;
|
|
347
|
-
try {
|
|
348
|
-
await db.put(this.STORE_NAME, memory);
|
|
349
|
-
} catch (e) {
|
|
350
|
-
}
|
|
351
|
-
if (this.config.apiUrl && this.config.agentId) {
|
|
352
|
-
this.syncToApi(text, importance).catch(() => {
|
|
353
|
-
});
|
|
351
|
+
var exportSoulToIPFS = async (agentId, soul, config = {}) => {
|
|
352
|
+
if (config.useLocalNode !== false) {
|
|
353
|
+
const helia = await getHelia();
|
|
354
|
+
if (helia) {
|
|
355
|
+
try {
|
|
356
|
+
const cid = await helia.j.add(soul);
|
|
357
|
+
return {
|
|
358
|
+
cid: cid.toString(),
|
|
359
|
+
ipfsUrl: `ipfs://${cid.toString()}`,
|
|
360
|
+
soul
|
|
361
|
+
};
|
|
362
|
+
} catch (e) {
|
|
363
|
+
console.warn("Helia add failed, falling back to API", e);
|
|
364
|
+
}
|
|
354
365
|
}
|
|
355
|
-
return memory;
|
|
356
366
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
367
|
+
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
368
|
+
try {
|
|
369
|
+
const response = await fetch(`${apiUrl}/agents/${agentId}/soul/export`, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
headers: { "Content-Type": "application/json" },
|
|
372
|
+
body: JSON.stringify({ agentIdRef: agentId })
|
|
373
|
+
});
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
throw new Error(`Export failed: ${response.statusText}`);
|
|
365
376
|
}
|
|
377
|
+
const data = await response.json();
|
|
378
|
+
return {
|
|
379
|
+
cid: data.cid,
|
|
380
|
+
ipfsUrl: data.ipfsUrl,
|
|
381
|
+
signature: data.signature,
|
|
382
|
+
soul
|
|
383
|
+
};
|
|
384
|
+
} catch (e) {
|
|
385
|
+
const mockCid = `Qm${Buffer.from(soul.id).toString("base64").substring(0, 44)}`;
|
|
386
|
+
return {
|
|
387
|
+
cid: mockCid,
|
|
388
|
+
ipfsUrl: `ipfs://${mockCid}`,
|
|
389
|
+
soul
|
|
390
|
+
};
|
|
366
391
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
const results = rankMemoriesByRelevance(memories, query, limit);
|
|
374
|
-
if (this.config.apiUrl && this.config.agentId) {
|
|
392
|
+
};
|
|
393
|
+
var importSoulFromIPFS = async (cid, config = {}) => {
|
|
394
|
+
if (config.useLocalNode !== false) {
|
|
395
|
+
const helia = await getHelia();
|
|
396
|
+
if (helia) {
|
|
375
397
|
try {
|
|
376
|
-
const apiResults = await this.recallFromApi(query);
|
|
377
|
-
return [...results, ...apiResults.slice(0, limit - results.length)];
|
|
378
398
|
} catch (e) {
|
|
379
399
|
}
|
|
380
400
|
}
|
|
381
|
-
return results;
|
|
382
401
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
);
|
|
392
|
-
if (response.ok) return await response.json();
|
|
393
|
-
return [];
|
|
394
|
-
}
|
|
395
|
-
async list(limit = 50, offset = 0) {
|
|
396
|
-
const db = await this.dbPromise;
|
|
397
|
-
try {
|
|
398
|
-
const all = await db.getAllFromIndex(this.STORE_NAME, "timestamp");
|
|
399
|
-
return all.reverse().slice(offset, offset + limit);
|
|
400
|
-
} catch {
|
|
401
|
-
return [];
|
|
402
|
+
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
403
|
+
try {
|
|
404
|
+
const response = await fetch(`${apiUrl}/souls/${cid}`, {
|
|
405
|
+
method: "GET",
|
|
406
|
+
headers: { "Content-Type": "application/json" }
|
|
407
|
+
});
|
|
408
|
+
if (!response.ok) {
|
|
409
|
+
throw new Error(`Import failed: ${response.statusText}`);
|
|
402
410
|
}
|
|
411
|
+
const data = await response.json();
|
|
412
|
+
return {
|
|
413
|
+
id: data.soulId,
|
|
414
|
+
version: "1.0.0",
|
|
415
|
+
name: data.soulName,
|
|
416
|
+
persona: data.dna,
|
|
417
|
+
state: { mood: "neutral", inventory: [], skills: {}, relationships: {} },
|
|
418
|
+
memories: []
|
|
419
|
+
};
|
|
420
|
+
} catch (e) {
|
|
421
|
+
throw new Error(`Failed to import Soul from CID ${cid}: ${e}`);
|
|
403
422
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
423
|
+
};
|
|
424
|
+
var getSoulList = async (limit = 50, apiUrl) => {
|
|
425
|
+
const url = apiUrl || "https://forbocai-api.onrender.com";
|
|
426
|
+
try {
|
|
427
|
+
const response = await fetch(`${url}/souls?limit=${limit}`, {
|
|
428
|
+
method: "GET",
|
|
429
|
+
headers: { "Content-Type": "application/json" }
|
|
430
|
+
});
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
throw new Error(`Failed to get Soul list: ${response.statusText}`);
|
|
409
433
|
}
|
|
434
|
+
const data = await response.json();
|
|
435
|
+
return data.souls.map((s) => ({
|
|
436
|
+
cid: s.cid || s.soulId,
|
|
437
|
+
name: s.name || s.soulName,
|
|
438
|
+
agentId: s.agentId,
|
|
439
|
+
exportedAt: s.exportedAt || s.createdAt,
|
|
440
|
+
ipfsUrl: s.ipfsUrl || `ipfs://${s.cid || s.soulId}`
|
|
441
|
+
}));
|
|
442
|
+
} catch (e) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var createAgentFromSoul = async (cid, cortexId, config = {}) => {
|
|
447
|
+
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
448
|
+
const response = await fetch(`${apiUrl}/agents/import`, {
|
|
449
|
+
method: "POST",
|
|
450
|
+
headers: { "Content-Type": "application/json" },
|
|
451
|
+
body: JSON.stringify({ cidRef: cid })
|
|
452
|
+
});
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
throw new Error(`Failed to create agent from Soul: ${response.statusText}`);
|
|
455
|
+
}
|
|
456
|
+
const data = await response.json();
|
|
457
|
+
return {
|
|
458
|
+
agentId: data.agentId,
|
|
459
|
+
persona: data.persona
|
|
460
|
+
};
|
|
461
|
+
};
|
|
462
|
+
var createSoulInstance = (id, name, persona, state, memories = [], initialApiUrl) => {
|
|
463
|
+
const soulData = createSoul(id, name, persona, state, memories);
|
|
464
|
+
const defaultApiUrl = initialApiUrl || "https://forbocai-api.onrender.com";
|
|
465
|
+
const exportSoul = async (config) => {
|
|
466
|
+
return exportSoulToIPFS(soulData.id, soulData, {
|
|
467
|
+
...config,
|
|
468
|
+
apiUrl: config?.apiUrl || defaultApiUrl
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
const toJSON = () => {
|
|
472
|
+
return { ...soulData };
|
|
473
|
+
};
|
|
474
|
+
return {
|
|
475
|
+
export: exportSoul,
|
|
476
|
+
toJSON
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/memory.ts
|
|
481
|
+
import * as path3 from "path";
|
|
482
|
+
|
|
483
|
+
// src/vector.ts
|
|
484
|
+
import * as path2 from "path";
|
|
485
|
+
import * as fs2 from "fs";
|
|
486
|
+
var pipeline;
|
|
487
|
+
var lancedb;
|
|
488
|
+
var initVectorEngine = async () => {
|
|
489
|
+
if (pipeline) return;
|
|
490
|
+
try {
|
|
491
|
+
console.log("> Initializing Vector Engine (Transformers)...");
|
|
492
|
+
const transformers = await import("@xenova/transformers");
|
|
493
|
+
transformers.env.cacheDir = path2.join(process.env.HOME || ".", ".forbocai", "cache");
|
|
494
|
+
pipeline = await transformers.pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
|
|
495
|
+
console.log("> Initializing LanceDB...");
|
|
496
|
+
lancedb = await import("@lancedb/lancedb");
|
|
497
|
+
} catch (e) {
|
|
498
|
+
console.error("Failed to init Vector Engine:", e);
|
|
410
499
|
}
|
|
411
|
-
|
|
412
|
-
|
|
500
|
+
};
|
|
501
|
+
var generateEmbedding = async (text) => {
|
|
502
|
+
if (!pipeline) await initVectorEngine();
|
|
503
|
+
const output = await pipeline(text, { pooling: "mean", normalize: true });
|
|
504
|
+
return Array.from(output.data);
|
|
505
|
+
};
|
|
506
|
+
var getVectorTable = async (dbPath, tableName = "memories") => {
|
|
507
|
+
if (!lancedb) await initVectorEngine();
|
|
508
|
+
if (!fs2.existsSync(dbPath)) fs2.mkdirSync(dbPath, { recursive: true });
|
|
509
|
+
const db = await lancedb.connect(dbPath);
|
|
510
|
+
const tables = await db.tableNames();
|
|
511
|
+
if (tables.includes(tableName)) {
|
|
512
|
+
return await db.openTable(tableName);
|
|
513
|
+
} else {
|
|
514
|
+
return null;
|
|
413
515
|
}
|
|
414
|
-
|
|
415
|
-
|
|
516
|
+
};
|
|
517
|
+
var createTable = async (dbPath, tableName, data) => {
|
|
518
|
+
if (!lancedb) await initVectorEngine();
|
|
519
|
+
const db = await lancedb.connect(dbPath);
|
|
520
|
+
return await db.createTable(tableName, data);
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/memory.ts
|
|
524
|
+
var createMemoryItem = (text, type = "observation", importance = 0.5) => ({
|
|
525
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
526
|
+
text,
|
|
527
|
+
timestamp: Date.now(),
|
|
528
|
+
type,
|
|
529
|
+
importance: Math.min(1, Math.max(0, importance))
|
|
530
|
+
});
|
|
531
|
+
var createLanceDBMemory = (config) => {
|
|
532
|
+
const dbName = config.storageKey || "forbocai_vectors";
|
|
533
|
+
const dbPath = path3.join(process.cwd(), "local_infrastructure", "vectors", dbName + ".lance");
|
|
534
|
+
const tableName = "memories";
|
|
535
|
+
let _table = null;
|
|
536
|
+
const getTable = async () => {
|
|
537
|
+
if (_table) return _table;
|
|
538
|
+
_table = await getVectorTable(dbPath, tableName);
|
|
539
|
+
return _table;
|
|
540
|
+
};
|
|
541
|
+
const store = async (text, type = "observation", importance = 0.5) => {
|
|
542
|
+
const item = createMemoryItem(text, type, importance);
|
|
543
|
+
const vector = await generateEmbedding(text);
|
|
544
|
+
const record = {
|
|
545
|
+
...item,
|
|
546
|
+
vector
|
|
547
|
+
};
|
|
548
|
+
const existingTable = await getTable();
|
|
549
|
+
if (existingTable) {
|
|
550
|
+
await existingTable.add([record]);
|
|
551
|
+
} else {
|
|
552
|
+
_table = await createTable(dbPath, tableName, [record]);
|
|
553
|
+
}
|
|
554
|
+
return item;
|
|
555
|
+
};
|
|
556
|
+
const recall = async (query, limit = 5) => {
|
|
557
|
+
const table = await getTable();
|
|
558
|
+
if (!table) return [];
|
|
559
|
+
const queryVec = await generateEmbedding(query);
|
|
560
|
+
const results = await table.search(queryVec).limit(limit).execute();
|
|
561
|
+
return results.map((r) => ({
|
|
562
|
+
id: r.id,
|
|
563
|
+
text: r.text,
|
|
564
|
+
timestamp: r.timestamp,
|
|
565
|
+
type: r.type,
|
|
566
|
+
importance: r.importance
|
|
567
|
+
}));
|
|
568
|
+
};
|
|
569
|
+
const list = async (limit = 50, offset = 0) => {
|
|
570
|
+
const table = await getTable();
|
|
571
|
+
if (!table) return [];
|
|
572
|
+
const results = await table.query().limit(limit + offset).execute();
|
|
573
|
+
return results.slice(offset).map((r) => ({
|
|
574
|
+
id: r.id,
|
|
575
|
+
text: r.text,
|
|
576
|
+
timestamp: r.timestamp,
|
|
577
|
+
type: r.type,
|
|
578
|
+
importance: r.importance
|
|
579
|
+
}));
|
|
580
|
+
};
|
|
581
|
+
const clear = async () => {
|
|
582
|
+
const tablePath = path3.join(dbPath, tableName + ".lance");
|
|
416
583
|
try {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
584
|
+
const fs3 = await import("fs");
|
|
585
|
+
if (fs3.existsSync(tablePath)) {
|
|
586
|
+
fs3.rmSync(tablePath, { recursive: true, force: true });
|
|
587
|
+
}
|
|
588
|
+
if (fs3.existsSync(dbPath)) {
|
|
589
|
+
fs3.rmSync(dbPath, { recursive: true, force: true });
|
|
590
|
+
}
|
|
591
|
+
_table = null;
|
|
592
|
+
console.log("> Memory cleared successfully");
|
|
593
|
+
} catch (e) {
|
|
594
|
+
console.error("Failed to clear memory:", e);
|
|
595
|
+
throw e;
|
|
422
596
|
}
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
var MockMemory = class {
|
|
426
|
-
constructor() {
|
|
427
|
-
this.memories = [];
|
|
428
|
-
}
|
|
429
|
-
async store(text, type, importance) {
|
|
430
|
-
const memory = createMemoryItem(text, type, importance);
|
|
431
|
-
this.memories.push(memory);
|
|
432
|
-
return memory;
|
|
433
|
-
}
|
|
434
|
-
async recall(query, limit) {
|
|
435
|
-
return rankMemoriesByRelevance(this.memories, query, limit);
|
|
436
|
-
}
|
|
437
|
-
async list(limit, offset) {
|
|
438
|
-
return this.memories.slice(offset ?? 0, (offset ?? 0) + (limit ?? 50));
|
|
439
|
-
}
|
|
440
|
-
async clear() {
|
|
441
|
-
this.memories = [];
|
|
442
|
-
}
|
|
443
|
-
async export() {
|
|
444
|
-
return [...this.memories];
|
|
445
|
-
}
|
|
446
|
-
async import(memories) {
|
|
447
|
-
this.memories = [...this.memories, ...memories];
|
|
448
|
-
}
|
|
597
|
+
};
|
|
598
|
+
return { store, recall, list, clear };
|
|
449
599
|
};
|
|
600
|
+
var createMemory = (config = {}) => createLanceDBMemory(config);
|
|
450
601
|
|
|
451
602
|
// src/bridge.ts
|
|
452
603
|
var movementRule = {
|
|
@@ -617,30 +768,40 @@ var DEFAULT_RULES = [
|
|
|
617
768
|
resourceRule
|
|
618
769
|
];
|
|
619
770
|
var createBridge = (config = {}) => {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.rules = /* @__PURE__ */ new Map();
|
|
629
|
-
DEFAULT_RULES.forEach((rule) => this.rules.set(rule.id, rule));
|
|
630
|
-
if (config.customRules) {
|
|
631
|
-
config.customRules.forEach((rule) => this.rules.set(rule.id, rule));
|
|
632
|
-
}
|
|
771
|
+
const effectiveConfig = {
|
|
772
|
+
strictMode: config.strictMode ?? false,
|
|
773
|
+
...config
|
|
774
|
+
};
|
|
775
|
+
const rules = /* @__PURE__ */ new Map();
|
|
776
|
+
DEFAULT_RULES.forEach((rule) => rules.set(rule.id, rule));
|
|
777
|
+
if (config.customRules) {
|
|
778
|
+
config.customRules.forEach((rule) => rules.set(rule.id, rule));
|
|
633
779
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
780
|
+
const validateRemote = async (action) => {
|
|
781
|
+
const response = await fetch(`${effectiveConfig.apiUrl}/bridge/validate`, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
headers: { "Content-Type": "application/json" },
|
|
784
|
+
body: JSON.stringify({
|
|
785
|
+
actionType: action.type,
|
|
786
|
+
payload: JSON.stringify(action.payload || {})
|
|
787
|
+
})
|
|
788
|
+
});
|
|
789
|
+
if (!response.ok) {
|
|
790
|
+
throw new Error(`API validation failed: ${response.statusText}`);
|
|
791
|
+
}
|
|
792
|
+
const data = await response.json();
|
|
793
|
+
return {
|
|
794
|
+
valid: data.valid,
|
|
795
|
+
reason: data.reason
|
|
796
|
+
};
|
|
797
|
+
};
|
|
798
|
+
const validate = async (action, context = {}) => {
|
|
799
|
+
const applicableRules = Array.from(rules.values()).filter(
|
|
639
800
|
(rule) => rule.actionTypes.some(
|
|
640
801
|
(t) => t.toLowerCase() === action.type.toLowerCase()
|
|
641
802
|
)
|
|
642
803
|
);
|
|
643
|
-
if (
|
|
804
|
+
if (effectiveConfig.strictMode && applicableRules.length === 0) {
|
|
644
805
|
const safeTypes = ["IDLE", "idle", "WAIT", "wait"];
|
|
645
806
|
if (!safeTypes.includes(action.type)) {
|
|
646
807
|
return {
|
|
@@ -654,9 +815,9 @@ var BridgeImpl = class {
|
|
|
654
815
|
for (const rule of applicableRules) {
|
|
655
816
|
const result = rule.validate(currentAction, context);
|
|
656
817
|
if (!result.valid) {
|
|
657
|
-
if (
|
|
818
|
+
if (effectiveConfig.apiUrl) {
|
|
658
819
|
try {
|
|
659
|
-
const apiResult = await
|
|
820
|
+
const apiResult = await validateRemote(action);
|
|
660
821
|
console.debug("API validation result:", apiResult);
|
|
661
822
|
} catch (e) {
|
|
662
823
|
console.warn("Failed to validate via API:", e);
|
|
@@ -672,59 +833,22 @@ var BridgeImpl = class {
|
|
|
672
833
|
valid: true,
|
|
673
834
|
correctedAction: currentAction !== action ? currentAction : void 0
|
|
674
835
|
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const data = await response.json();
|
|
692
|
-
return {
|
|
693
|
-
valid: data.valid,
|
|
694
|
-
reason: data.reason
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Register a custom validation rule
|
|
699
|
-
*/
|
|
700
|
-
registerRule(rule) {
|
|
701
|
-
this.rules.set(rule.id, rule);
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* List all registered rules
|
|
705
|
-
*/
|
|
706
|
-
listRules() {
|
|
707
|
-
return Array.from(this.rules.values());
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Remove a rule by ID
|
|
711
|
-
*/
|
|
712
|
-
removeRule(ruleId) {
|
|
713
|
-
return this.rules.delete(ruleId);
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
var MockBridge = class {
|
|
717
|
-
async validate(action) {
|
|
718
|
-
return { valid: true };
|
|
719
|
-
}
|
|
720
|
-
registerRule(_rule) {
|
|
721
|
-
}
|
|
722
|
-
listRules() {
|
|
723
|
-
return [];
|
|
724
|
-
}
|
|
725
|
-
removeRule(_ruleId) {
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
836
|
+
};
|
|
837
|
+
const registerRule = (rule) => {
|
|
838
|
+
rules.set(rule.id, rule);
|
|
839
|
+
};
|
|
840
|
+
const listRules = () => {
|
|
841
|
+
return Array.from(rules.values());
|
|
842
|
+
};
|
|
843
|
+
const removeRule = (ruleId) => {
|
|
844
|
+
return rules.delete(ruleId);
|
|
845
|
+
};
|
|
846
|
+
return {
|
|
847
|
+
validate,
|
|
848
|
+
registerRule,
|
|
849
|
+
listRules,
|
|
850
|
+
removeRule
|
|
851
|
+
};
|
|
728
852
|
};
|
|
729
853
|
var validateAction = (action, rules, context = {}) => {
|
|
730
854
|
for (const rule of rules) {
|
|
@@ -738,172 +862,6 @@ var validateAction = (action, rules, context = {}) => {
|
|
|
738
862
|
return { valid: true };
|
|
739
863
|
};
|
|
740
864
|
|
|
741
|
-
// src/soul.ts
|
|
742
|
-
import { createHelia } from "helia";
|
|
743
|
-
import { json } from "@helia/json";
|
|
744
|
-
import { MemoryBlockstore } from "blockstore-core";
|
|
745
|
-
var heliaNode = null;
|
|
746
|
-
var heliaJson = null;
|
|
747
|
-
var getHelia = async () => {
|
|
748
|
-
if (heliaNode) return { node: heliaNode, j: heliaJson };
|
|
749
|
-
try {
|
|
750
|
-
const blockstore = new MemoryBlockstore();
|
|
751
|
-
heliaNode = await createHelia({ blockstore });
|
|
752
|
-
heliaJson = json(heliaNode);
|
|
753
|
-
console.log("> Helia IPFS Node Initialized:", heliaNode.libp2p.peerId.toString());
|
|
754
|
-
return { node: heliaNode, j: heliaJson };
|
|
755
|
-
} catch (e) {
|
|
756
|
-
console.warn("Failed to init Helia (IPFS):", e);
|
|
757
|
-
return null;
|
|
758
|
-
}
|
|
759
|
-
};
|
|
760
|
-
var createSoul = (id, name, persona, state, memories = []) => {
|
|
761
|
-
return {
|
|
762
|
-
id,
|
|
763
|
-
version: "1.0.0",
|
|
764
|
-
name,
|
|
765
|
-
persona,
|
|
766
|
-
state: { ...state },
|
|
767
|
-
memories: [...memories]
|
|
768
|
-
};
|
|
769
|
-
};
|
|
770
|
-
var serializeSoul = (soul) => {
|
|
771
|
-
return JSON.stringify(soul, null, 2);
|
|
772
|
-
};
|
|
773
|
-
var deserializeSoul = (json2) => {
|
|
774
|
-
const parsed = JSON.parse(json2);
|
|
775
|
-
if (!parsed.id || !parsed.persona || !parsed.state) {
|
|
776
|
-
throw new Error("Invalid Soul format: missing required fields");
|
|
777
|
-
}
|
|
778
|
-
return {
|
|
779
|
-
id: parsed.id,
|
|
780
|
-
version: parsed.version || "1.0.0",
|
|
781
|
-
name: parsed.name || "Unknown",
|
|
782
|
-
persona: parsed.persona,
|
|
783
|
-
state: parsed.state,
|
|
784
|
-
memories: parsed.memories || [],
|
|
785
|
-
signature: parsed.signature
|
|
786
|
-
};
|
|
787
|
-
};
|
|
788
|
-
var validateSoul = (soul) => {
|
|
789
|
-
const errors = [];
|
|
790
|
-
if (!soul.id) errors.push("Missing id");
|
|
791
|
-
if (!soul.persona) errors.push("Missing persona");
|
|
792
|
-
if (!soul.state) errors.push("Missing state");
|
|
793
|
-
if (!soul.state?.mood) errors.push("Missing state.mood");
|
|
794
|
-
return {
|
|
795
|
-
valid: errors.length === 0,
|
|
796
|
-
errors
|
|
797
|
-
};
|
|
798
|
-
};
|
|
799
|
-
var exportSoulToIPFS = async (agentId, soul, config = {}) => {
|
|
800
|
-
if (config.useLocalNode !== false) {
|
|
801
|
-
const helia = await getHelia();
|
|
802
|
-
if (helia) {
|
|
803
|
-
try {
|
|
804
|
-
const cid = await helia.j.add(soul);
|
|
805
|
-
return {
|
|
806
|
-
cid: cid.toString(),
|
|
807
|
-
ipfsUrl: `ipfs://${cid.toString()}`,
|
|
808
|
-
soul
|
|
809
|
-
};
|
|
810
|
-
} catch (e) {
|
|
811
|
-
console.warn("Helia add failed, falling back to API", e);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
816
|
-
try {
|
|
817
|
-
const response = await fetch(`${apiUrl}/agents/${agentId}/soul/export`, {
|
|
818
|
-
method: "POST",
|
|
819
|
-
headers: { "Content-Type": "application/json" },
|
|
820
|
-
body: JSON.stringify({ agentIdRef: agentId })
|
|
821
|
-
});
|
|
822
|
-
if (!response.ok) {
|
|
823
|
-
throw new Error(`Export failed: ${response.statusText}`);
|
|
824
|
-
}
|
|
825
|
-
const data = await response.json();
|
|
826
|
-
return {
|
|
827
|
-
cid: data.cid,
|
|
828
|
-
ipfsUrl: data.ipfsUrl,
|
|
829
|
-
signature: data.signature,
|
|
830
|
-
soul
|
|
831
|
-
};
|
|
832
|
-
} catch (e) {
|
|
833
|
-
const mockCid = `Qm${Buffer.from(soul.id).toString("base64").substring(0, 44)}`;
|
|
834
|
-
return {
|
|
835
|
-
cid: mockCid,
|
|
836
|
-
ipfsUrl: `ipfs://${mockCid}`,
|
|
837
|
-
soul
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
var importSoulFromIPFS = async (cid, config = {}) => {
|
|
842
|
-
if (config.useLocalNode !== false) {
|
|
843
|
-
const helia = await getHelia();
|
|
844
|
-
if (helia) {
|
|
845
|
-
try {
|
|
846
|
-
} catch (e) {
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
851
|
-
try {
|
|
852
|
-
const response = await fetch(`${apiUrl}/souls/${cid}`, {
|
|
853
|
-
method: "GET",
|
|
854
|
-
headers: { "Content-Type": "application/json" }
|
|
855
|
-
});
|
|
856
|
-
if (!response.ok) {
|
|
857
|
-
throw new Error(`Import failed: ${response.statusText}`);
|
|
858
|
-
}
|
|
859
|
-
const data = await response.json();
|
|
860
|
-
return {
|
|
861
|
-
id: data.soulId,
|
|
862
|
-
version: "1.0.0",
|
|
863
|
-
name: data.soulName,
|
|
864
|
-
persona: data.dna,
|
|
865
|
-
state: { mood: "neutral", inventory: [], skills: {}, relationships: {} },
|
|
866
|
-
memories: []
|
|
867
|
-
};
|
|
868
|
-
} catch (e) {
|
|
869
|
-
throw new Error(`Failed to import Soul from CID ${cid}: ${e}`);
|
|
870
|
-
}
|
|
871
|
-
};
|
|
872
|
-
var createAgentFromSoul = async (cid, cortexId, config = {}) => {
|
|
873
|
-
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
874
|
-
const response = await fetch(`${apiUrl}/agents/import`, {
|
|
875
|
-
method: "POST",
|
|
876
|
-
headers: { "Content-Type": "application/json" },
|
|
877
|
-
body: JSON.stringify({ cidRef: cid })
|
|
878
|
-
});
|
|
879
|
-
if (!response.ok) {
|
|
880
|
-
throw new Error(`Failed to create agent from Soul: ${response.statusText}`);
|
|
881
|
-
}
|
|
882
|
-
const data = await response.json();
|
|
883
|
-
return {
|
|
884
|
-
agentId: data.agentId,
|
|
885
|
-
persona: data.persona
|
|
886
|
-
};
|
|
887
|
-
};
|
|
888
|
-
var SoulImpl = class {
|
|
889
|
-
constructor(id, name, persona, state, memories = [], apiUrl) {
|
|
890
|
-
this.soul = createSoul(id, name, persona, state, memories);
|
|
891
|
-
this.apiUrl = apiUrl || "https://forbocai-api.onrender.com";
|
|
892
|
-
}
|
|
893
|
-
async export(config) {
|
|
894
|
-
return exportSoulToIPFS(this.soul.id, this.soul, {
|
|
895
|
-
...config,
|
|
896
|
-
apiUrl: config?.apiUrl || this.apiUrl
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
toJSON() {
|
|
900
|
-
return { ...this.soul };
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
var createSoulInstance = (id, name, persona, state, memories, apiUrl) => {
|
|
904
|
-
return new SoulImpl(id, name, persona, state, memories, apiUrl);
|
|
905
|
-
};
|
|
906
|
-
|
|
907
865
|
// src/ghost.ts
|
|
908
866
|
var startGhostSession = async (config) => {
|
|
909
867
|
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
@@ -1029,79 +987,118 @@ var waitForGhostCompletion = async (sessionId, pollIntervalMs = 5e3, timeoutMs =
|
|
|
1029
987
|
}
|
|
1030
988
|
throw new Error(`Ghost session timed out after ${timeoutMs}ms`);
|
|
1031
989
|
};
|
|
1032
|
-
var
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
990
|
+
var stopGhostSession = async (sessionId, apiUrl) => {
|
|
991
|
+
const url = apiUrl || "https://forbocai-api.onrender.com";
|
|
992
|
+
try {
|
|
993
|
+
const response = await fetch(`${url}/ghost/${sessionId}/stop`, {
|
|
994
|
+
method: "POST",
|
|
995
|
+
headers: { "Content-Type": "application/json" }
|
|
996
|
+
});
|
|
997
|
+
if (!response.ok) {
|
|
998
|
+
throw new Error(`Failed to stop Ghost session: ${response.statusText}`);
|
|
999
|
+
}
|
|
1000
|
+
const data = await response.json();
|
|
1001
|
+
return {
|
|
1002
|
+
stopped: true,
|
|
1003
|
+
status: data.status || "stopped"
|
|
1004
|
+
};
|
|
1005
|
+
} catch (e) {
|
|
1006
|
+
return {
|
|
1007
|
+
stopped: true,
|
|
1008
|
+
status: "stopped"
|
|
1009
|
+
};
|
|
1037
1010
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1011
|
+
};
|
|
1012
|
+
var getGhostHistory = async (limit = 10, apiUrl) => {
|
|
1013
|
+
const url = apiUrl || "https://forbocai-api.onrender.com";
|
|
1014
|
+
try {
|
|
1015
|
+
const response = await fetch(`${url}/ghost/history?limit=${limit}`, {
|
|
1016
|
+
method: "GET",
|
|
1017
|
+
headers: { "Content-Type": "application/json" }
|
|
1018
|
+
});
|
|
1019
|
+
if (!response.ok) {
|
|
1020
|
+
throw new Error(`Failed to get Ghost history: ${response.statusText}`);
|
|
1021
|
+
}
|
|
1022
|
+
const data = await response.json();
|
|
1023
|
+
return data.sessions.map((s) => ({
|
|
1024
|
+
sessionId: s.sessionId,
|
|
1025
|
+
testSuite: s.testSuite,
|
|
1026
|
+
startedAt: s.startedAt,
|
|
1027
|
+
completedAt: s.completedAt,
|
|
1028
|
+
status: s.status,
|
|
1029
|
+
passRate: s.passRate
|
|
1030
|
+
}));
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
return [];
|
|
1042
1033
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1034
|
+
};
|
|
1035
|
+
var createGhost = (config) => {
|
|
1036
|
+
let sessionId = null;
|
|
1037
|
+
const apiUrl = config.apiUrl || "https://forbocai-api.onrender.com";
|
|
1038
|
+
const run = async () => {
|
|
1039
|
+
const result = await startGhostSession(config);
|
|
1040
|
+
sessionId = result.sessionId;
|
|
1041
|
+
return sessionId;
|
|
1042
|
+
};
|
|
1043
|
+
const status = async () => {
|
|
1044
|
+
if (!sessionId) {
|
|
1045
1045
|
throw new Error("Ghost session not started");
|
|
1046
1046
|
}
|
|
1047
|
-
return getGhostStatus(
|
|
1048
|
-
}
|
|
1049
|
-
async
|
|
1050
|
-
if (!
|
|
1047
|
+
return getGhostStatus(sessionId, apiUrl);
|
|
1048
|
+
};
|
|
1049
|
+
const results = async () => {
|
|
1050
|
+
if (!sessionId) {
|
|
1051
1051
|
throw new Error("Ghost session not started");
|
|
1052
1052
|
}
|
|
1053
|
-
return getGhostResults(
|
|
1054
|
-
}
|
|
1055
|
-
async
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
async
|
|
1059
|
-
if (!
|
|
1053
|
+
return getGhostResults(sessionId, apiUrl);
|
|
1054
|
+
};
|
|
1055
|
+
const stop = async () => {
|
|
1056
|
+
sessionId = null;
|
|
1057
|
+
};
|
|
1058
|
+
const waitForCompletion = async (pollIntervalMs, timeoutMs, onProgress) => {
|
|
1059
|
+
if (!sessionId) {
|
|
1060
1060
|
throw new Error("Ghost session not started");
|
|
1061
1061
|
}
|
|
1062
1062
|
return waitForGhostCompletion(
|
|
1063
|
-
|
|
1063
|
+
sessionId,
|
|
1064
1064
|
pollIntervalMs,
|
|
1065
1065
|
timeoutMs,
|
|
1066
|
-
|
|
1066
|
+
apiUrl,
|
|
1067
1067
|
onProgress
|
|
1068
1068
|
);
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1069
|
+
};
|
|
1070
|
+
return {
|
|
1071
|
+
run,
|
|
1072
|
+
status,
|
|
1073
|
+
results,
|
|
1074
|
+
stop
|
|
1075
|
+
// Helper method attached to the instance object, though not part of IGhost strictly in the original interface,
|
|
1076
|
+
// it was on the class. We should probably expand IGhost if this is needed, or just rely on the standalone function.
|
|
1077
|
+
// For now, I'll return it as an extra property or strictly adhere to IGhost.
|
|
1078
|
+
// The original Class had it. I'll omit it from the return to match IGhost exactly,
|
|
1079
|
+
// or check IGhost definition. IGhost definition (lines 62-67) does NOT have waitForCompletion.
|
|
1080
|
+
// So I will omit it to match the Interface.
|
|
1081
|
+
};
|
|
1073
1082
|
};
|
|
1074
1083
|
|
|
1075
1084
|
// src/index.ts
|
|
1076
1085
|
var init = () => {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
${dim}> Connecting into the Neuro-Symbolic Grid...${reset}
|
|
1091
|
-
${dim}> Status: ${cyan}ONLINE${reset}
|
|
1092
|
-
|
|
1093
|
-
${cyan}Welcome to the Future of NPC Intelligence.${reset}
|
|
1094
|
-
`);
|
|
1086
|
+
console.log(`
|
|
1087
|
+
\x1B[36m _ _ _ ___ _ _
|
|
1088
|
+
| \\| |___ _ _ _ __ ___ / \\ |_ _|_ _ | |_ (_|
|
|
1089
|
+
| . / -_) || | '_ / _ \\/ _ \\ | || ' \\| _| | |
|
|
1090
|
+
|_|\\_\\___|\\_,_| ._\\___/_/ \\_\\ |___|_||_|\\__|_|_|
|
|
1091
|
+
|_| \x1B[0m
|
|
1092
|
+
Neuro-Symbolic Grid SDK v0.3.3
|
|
1093
|
+
---------------------------------
|
|
1094
|
+
Vessel: ACTIVE
|
|
1095
|
+
Memory: LOCAL (LanceDB)
|
|
1096
|
+
Smart: LOCAL (GGUF)
|
|
1097
|
+
Grid: CONNECTED
|
|
1098
|
+
`);
|
|
1095
1099
|
};
|
|
1096
1100
|
export {
|
|
1097
|
-
GhostSession,
|
|
1098
|
-
MockBridge,
|
|
1099
|
-
MockCortex,
|
|
1100
|
-
MockMemory,
|
|
1101
|
-
SoulImpl,
|
|
1102
|
-
applyTemporalDecay,
|
|
1103
1101
|
attackRule,
|
|
1104
|
-
computeSimilarity,
|
|
1105
1102
|
createAgent,
|
|
1106
1103
|
createAgentFromSoul,
|
|
1107
1104
|
createBridge,
|
|
@@ -1112,25 +1109,28 @@ export {
|
|
|
1112
1109
|
createMemoryItem,
|
|
1113
1110
|
createSoul,
|
|
1114
1111
|
createSoulInstance,
|
|
1112
|
+
createTable,
|
|
1115
1113
|
deserializeSoul,
|
|
1116
1114
|
exportSoulToIPFS,
|
|
1117
1115
|
exportToSoul,
|
|
1118
1116
|
fromSoul,
|
|
1119
|
-
|
|
1120
|
-
|
|
1117
|
+
generateEmbedding,
|
|
1118
|
+
getGhostHistory,
|
|
1121
1119
|
getGhostResults,
|
|
1122
1120
|
getGhostStatus,
|
|
1121
|
+
getSoulList,
|
|
1122
|
+
getVectorTable,
|
|
1123
1123
|
importSoulFromIPFS,
|
|
1124
1124
|
init,
|
|
1125
|
+
initVectorEngine,
|
|
1125
1126
|
interactRule,
|
|
1126
1127
|
movementRule,
|
|
1127
1128
|
processAgentInput,
|
|
1128
|
-
processObservationToDirective,
|
|
1129
|
-
rankMemoriesByRelevance,
|
|
1130
1129
|
resourceRule,
|
|
1131
1130
|
serializeSoul,
|
|
1132
1131
|
speakRule,
|
|
1133
1132
|
startGhostSession,
|
|
1133
|
+
stopGhostSession,
|
|
1134
1134
|
updateAgentState,
|
|
1135
1135
|
validateAction,
|
|
1136
1136
|
validateSoul,
|