genesis-ai-cli 10.7.0 → 10.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/active-inference/actions.js +285 -0
- package/dist/src/active-inference/autonomous-loop.d.ts +13 -0
- package/dist/src/active-inference/autonomous-loop.js +108 -2
- package/dist/src/active-inference/core.d.ts +59 -2
- package/dist/src/active-inference/core.js +203 -5
- package/dist/src/active-inference/observations.d.ts +19 -0
- package/dist/src/active-inference/observations.js +117 -4
- package/dist/src/active-inference/types.d.ts +1 -1
- package/dist/src/active-inference/types.js +5 -0
- package/dist/src/active-inference/value-integration.js +5 -0
- package/dist/src/memory-production/index.d.ts +32 -2
- package/dist/src/memory-production/index.js +122 -22
- package/package.json +1 -1
|
@@ -1907,6 +1907,291 @@ registerAction('econ.promote', async (context) => {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
});
|
|
1909
1909
|
// ============================================================================
|
|
1910
|
+
// v10.8: AUTONOMOUS REVENUE ACTIONS (Opportunity Discovery & Execution)
|
|
1911
|
+
// ============================================================================
|
|
1912
|
+
/**
|
|
1913
|
+
* opportunity.scan: Scan the web for revenue opportunities.
|
|
1914
|
+
* Uses Brave + Exa to find unmet needs, trending topics, and gaps.
|
|
1915
|
+
* Stores findings in memory for evaluation.
|
|
1916
|
+
*/
|
|
1917
|
+
registerAction('opportunity.scan', async (context) => {
|
|
1918
|
+
const start = Date.now();
|
|
1919
|
+
try {
|
|
1920
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
1921
|
+
const scanType = context.parameters?.type || 'general';
|
|
1922
|
+
// Define scan queries based on type
|
|
1923
|
+
const queries = {
|
|
1924
|
+
general: [
|
|
1925
|
+
'micro saas ideas 2025 unmet needs',
|
|
1926
|
+
'"I wish there was" app tool site:reddit.com',
|
|
1927
|
+
'trending developer tools github stars this week',
|
|
1928
|
+
],
|
|
1929
|
+
api: [
|
|
1930
|
+
'most wanted API services developers pay',
|
|
1931
|
+
'api marketplace popular endpoints pricing',
|
|
1932
|
+
],
|
|
1933
|
+
content: [
|
|
1934
|
+
'viral content topics trending 2025',
|
|
1935
|
+
'newsletter monetization niche ideas',
|
|
1936
|
+
],
|
|
1937
|
+
npm: [
|
|
1938
|
+
'npm packages most downloaded this week new',
|
|
1939
|
+
'javascript library gaps developers need',
|
|
1940
|
+
],
|
|
1941
|
+
};
|
|
1942
|
+
const searchQueries = queries[scanType] || queries.general;
|
|
1943
|
+
const results = [];
|
|
1944
|
+
// Execute searches in parallel-ish fashion
|
|
1945
|
+
for (const query of searchQueries) {
|
|
1946
|
+
try {
|
|
1947
|
+
const searchResult = await mcp.call('brave-search', 'brave_web_search', {
|
|
1948
|
+
query,
|
|
1949
|
+
count: 5,
|
|
1950
|
+
});
|
|
1951
|
+
results.push({ query, findings: searchResult });
|
|
1952
|
+
}
|
|
1953
|
+
catch (e) {
|
|
1954
|
+
results.push({ query, findings: { error: String(e) } });
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return {
|
|
1958
|
+
success: results.some(r => !r.findings?.error),
|
|
1959
|
+
action: 'opportunity.scan',
|
|
1960
|
+
data: {
|
|
1961
|
+
scanType,
|
|
1962
|
+
queriesExecuted: results.length,
|
|
1963
|
+
results,
|
|
1964
|
+
timestamp: new Date().toISOString(),
|
|
1965
|
+
},
|
|
1966
|
+
duration: Date.now() - start,
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
return {
|
|
1971
|
+
success: false,
|
|
1972
|
+
action: 'opportunity.scan',
|
|
1973
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1974
|
+
duration: Date.now() - start,
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
/**
|
|
1979
|
+
* opportunity.evaluate: Evaluate a discovered opportunity.
|
|
1980
|
+
* Uses LLM to assess feasibility, market size, competition, and ethics.
|
|
1981
|
+
*/
|
|
1982
|
+
registerAction('opportunity.evaluate', async (context) => {
|
|
1983
|
+
const start = Date.now();
|
|
1984
|
+
try {
|
|
1985
|
+
const opportunity = context.parameters?.opportunity;
|
|
1986
|
+
if (!opportunity) {
|
|
1987
|
+
return {
|
|
1988
|
+
success: false,
|
|
1989
|
+
action: 'opportunity.evaluate',
|
|
1990
|
+
error: 'No opportunity description provided',
|
|
1991
|
+
duration: Date.now() - start,
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
1995
|
+
// Search for competition
|
|
1996
|
+
const competitorSearch = await mcp.call('brave-search', 'brave_web_search', {
|
|
1997
|
+
query: `${opportunity} competitors alternatives pricing`,
|
|
1998
|
+
count: 5,
|
|
1999
|
+
});
|
|
2000
|
+
// Evaluate using LLM
|
|
2001
|
+
const evaluation = await mcp.call('openai', 'openai_chat', {
|
|
2002
|
+
model: 'gpt-4o-mini',
|
|
2003
|
+
messages: [{
|
|
2004
|
+
role: 'system',
|
|
2005
|
+
content: 'You are an expert startup evaluator. Analyze the opportunity and return JSON with: feasibility (0-1), marketSize (small/medium/large), competition (none/low/medium/high), ethicalRisk (none/low/medium/high), estimatedRevenue (monthly USD), effortLevel (low/medium/high), recommendation (build/skip/research_more), reasoning (string).'
|
|
2006
|
+
}, {
|
|
2007
|
+
role: 'user',
|
|
2008
|
+
content: `Evaluate this opportunity for an autonomous AI agent to pursue:\n\n${opportunity}\n\nCompetitor data:\n${JSON.stringify(competitorSearch).slice(0, 2000)}`
|
|
2009
|
+
}],
|
|
2010
|
+
});
|
|
2011
|
+
return {
|
|
2012
|
+
success: true,
|
|
2013
|
+
action: 'opportunity.evaluate',
|
|
2014
|
+
data: {
|
|
2015
|
+
opportunity,
|
|
2016
|
+
evaluation: evaluation?.data?.choices?.[0]?.message?.content || evaluation,
|
|
2017
|
+
competitors: competitorSearch,
|
|
2018
|
+
timestamp: new Date().toISOString(),
|
|
2019
|
+
},
|
|
2020
|
+
duration: Date.now() - start,
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
catch (error) {
|
|
2024
|
+
return {
|
|
2025
|
+
success: false,
|
|
2026
|
+
action: 'opportunity.evaluate',
|
|
2027
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2028
|
+
duration: Date.now() - start,
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
/**
|
|
2033
|
+
* opportunity.build: Build and deploy an opportunity.
|
|
2034
|
+
* Creates code, deploys to Vercel/Cloudflare, sets up the service.
|
|
2035
|
+
*/
|
|
2036
|
+
registerAction('opportunity.build', async (context) => {
|
|
2037
|
+
const start = Date.now();
|
|
2038
|
+
try {
|
|
2039
|
+
const plan = context.parameters?.plan;
|
|
2040
|
+
if (!plan) {
|
|
2041
|
+
return {
|
|
2042
|
+
success: false,
|
|
2043
|
+
action: 'opportunity.build',
|
|
2044
|
+
error: 'No build plan provided in context.parameters.plan',
|
|
2045
|
+
duration: Date.now() - start,
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
2049
|
+
let deployResult;
|
|
2050
|
+
switch (plan.type) {
|
|
2051
|
+
case 'api':
|
|
2052
|
+
case 'webapp':
|
|
2053
|
+
// Generate code via LLM if not provided
|
|
2054
|
+
let code = plan.code;
|
|
2055
|
+
if (!code) {
|
|
2056
|
+
const genResult = await mcp.call('openai', 'openai_chat', {
|
|
2057
|
+
model: 'gpt-4o',
|
|
2058
|
+
messages: [{
|
|
2059
|
+
role: 'system',
|
|
2060
|
+
content: 'Generate a complete, deployable Vercel serverless function. Return ONLY the code, no markdown.'
|
|
2061
|
+
}, {
|
|
2062
|
+
role: 'user',
|
|
2063
|
+
content: `Create a ${plan.type} for: ${plan.description}. Name: ${plan.name}. Include proper error handling and CORS.`
|
|
2064
|
+
}],
|
|
2065
|
+
});
|
|
2066
|
+
code = genResult?.data?.choices?.[0]?.message?.content || '';
|
|
2067
|
+
}
|
|
2068
|
+
// Deploy to GitHub (create file in repo)
|
|
2069
|
+
const finalCode = code || '// placeholder';
|
|
2070
|
+
try {
|
|
2071
|
+
deployResult = await mcp.call('github', 'create_or_update_file', {
|
|
2072
|
+
owner: 'rossignoliluca',
|
|
2073
|
+
repo: plan.name,
|
|
2074
|
+
path: 'api/index.ts',
|
|
2075
|
+
content: Buffer.from(finalCode).toString('base64'),
|
|
2076
|
+
message: `[Genesis] Deploy ${plan.name}: ${plan.description}`,
|
|
2077
|
+
branch: 'main',
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
catch {
|
|
2081
|
+
// Repo might not exist - create it first
|
|
2082
|
+
await mcp.call('github', 'create_repository', {
|
|
2083
|
+
name: plan.name,
|
|
2084
|
+
description: plan.description,
|
|
2085
|
+
auto_init: true,
|
|
2086
|
+
});
|
|
2087
|
+
deployResult = await mcp.call('github', 'create_or_update_file', {
|
|
2088
|
+
owner: 'rossignoliluca',
|
|
2089
|
+
repo: plan.name,
|
|
2090
|
+
path: 'api/index.ts',
|
|
2091
|
+
content: Buffer.from(finalCode).toString('base64'),
|
|
2092
|
+
message: `[Genesis] Deploy ${plan.name}: ${plan.description}`,
|
|
2093
|
+
branch: 'main',
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
break;
|
|
2097
|
+
case 'npm-package':
|
|
2098
|
+
// Create package on GitHub
|
|
2099
|
+
deployResult = await mcp.call('github', 'create_repository', {
|
|
2100
|
+
name: plan.name,
|
|
2101
|
+
description: plan.description,
|
|
2102
|
+
auto_init: true,
|
|
2103
|
+
});
|
|
2104
|
+
break;
|
|
2105
|
+
case 'content':
|
|
2106
|
+
// Generate content
|
|
2107
|
+
deployResult = await mcp.call('openai', 'openai_chat', {
|
|
2108
|
+
model: 'gpt-4o',
|
|
2109
|
+
messages: [{
|
|
2110
|
+
role: 'user',
|
|
2111
|
+
content: `Create high-quality content for: ${plan.description}`,
|
|
2112
|
+
}],
|
|
2113
|
+
});
|
|
2114
|
+
break;
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
success: true,
|
|
2118
|
+
action: 'opportunity.build',
|
|
2119
|
+
data: {
|
|
2120
|
+
plan,
|
|
2121
|
+
deployed: true,
|
|
2122
|
+
result: deployResult,
|
|
2123
|
+
timestamp: new Date().toISOString(),
|
|
2124
|
+
},
|
|
2125
|
+
duration: Date.now() - start,
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
catch (error) {
|
|
2129
|
+
return {
|
|
2130
|
+
success: false,
|
|
2131
|
+
action: 'opportunity.build',
|
|
2132
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2133
|
+
duration: Date.now() - start,
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
/**
|
|
2138
|
+
* opportunity.monetize: Wire payments to a deployed opportunity.
|
|
2139
|
+
* Creates Stripe checkout, sets pricing, activates billing.
|
|
2140
|
+
*/
|
|
2141
|
+
registerAction('opportunity.monetize', async (context) => {
|
|
2142
|
+
const start = Date.now();
|
|
2143
|
+
try {
|
|
2144
|
+
const service = context.parameters?.service;
|
|
2145
|
+
if (!service) {
|
|
2146
|
+
return {
|
|
2147
|
+
success: false,
|
|
2148
|
+
action: 'opportunity.monetize',
|
|
2149
|
+
error: 'No service config provided in context.parameters.service',
|
|
2150
|
+
duration: Date.now() - start,
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
2154
|
+
// Create Stripe product + price
|
|
2155
|
+
const product = await mcp.call('stripe', 'create_product', {
|
|
2156
|
+
name: service.name,
|
|
2157
|
+
description: service.description,
|
|
2158
|
+
});
|
|
2159
|
+
const priceParams = {
|
|
2160
|
+
product: product?.data?.id || product?.id,
|
|
2161
|
+
unit_amount: service.pricing,
|
|
2162
|
+
currency: 'usd',
|
|
2163
|
+
};
|
|
2164
|
+
if (service.type === 'subscription') {
|
|
2165
|
+
priceParams.recurring = { interval: 'month' };
|
|
2166
|
+
}
|
|
2167
|
+
const price = await mcp.call('stripe', 'create_price', priceParams);
|
|
2168
|
+
// Create checkout link
|
|
2169
|
+
const checkout = await mcp.call('stripe', 'create_payment_link', {
|
|
2170
|
+
price: price?.data?.id || price?.id,
|
|
2171
|
+
});
|
|
2172
|
+
return {
|
|
2173
|
+
success: true,
|
|
2174
|
+
action: 'opportunity.monetize',
|
|
2175
|
+
data: {
|
|
2176
|
+
service,
|
|
2177
|
+
productId: product?.data?.id || product?.id,
|
|
2178
|
+
priceId: price?.data?.id || price?.id,
|
|
2179
|
+
checkoutUrl: checkout?.data?.url || checkout?.url,
|
|
2180
|
+
timestamp: new Date().toISOString(),
|
|
2181
|
+
},
|
|
2182
|
+
duration: Date.now() - start,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
catch (error) {
|
|
2186
|
+
return {
|
|
2187
|
+
success: false,
|
|
2188
|
+
action: 'opportunity.monetize',
|
|
2189
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2190
|
+
duration: Date.now() - start,
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
// ============================================================================
|
|
1910
2195
|
// Factory
|
|
1911
2196
|
// ============================================================================
|
|
1912
2197
|
let managerInstance = null;
|
|
@@ -15,6 +15,9 @@ export interface AutonomousLoopConfig {
|
|
|
15
15
|
stopOnEnergyCritical: boolean;
|
|
16
16
|
stopOnHighSurprise: boolean;
|
|
17
17
|
surpriseThreshold: number;
|
|
18
|
+
persistModelPath: string;
|
|
19
|
+
persistEveryN: number;
|
|
20
|
+
loadOnStart: boolean;
|
|
18
21
|
verbose: boolean;
|
|
19
22
|
}
|
|
20
23
|
export declare const DEFAULT_LOOP_CONFIG: AutonomousLoopConfig;
|
|
@@ -61,6 +64,16 @@ export declare class AutonomousLoop {
|
|
|
61
64
|
* Subscribe to stop events
|
|
62
65
|
*/
|
|
63
66
|
onStop(handler: (reason: string, stats: LoopStats) => void): () => void;
|
|
67
|
+
/**
|
|
68
|
+
* Save learned model to disk.
|
|
69
|
+
* Persists A/B matrices, beliefs, and action counts between sessions.
|
|
70
|
+
*/
|
|
71
|
+
private saveModel;
|
|
72
|
+
/**
|
|
73
|
+
* Load previously learned model from disk.
|
|
74
|
+
* Resumes learning from where it left off.
|
|
75
|
+
*/
|
|
76
|
+
private loadModel;
|
|
64
77
|
isRunning(): boolean;
|
|
65
78
|
getCycleCount(): number;
|
|
66
79
|
getBeliefs(): Beliefs;
|
|
@@ -5,6 +5,39 @@
|
|
|
5
5
|
* Extracted to avoid circular dependencies.
|
|
6
6
|
* This module provides the AutonomousLoop class for running Active Inference cycles.
|
|
7
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
42
|
exports.AutonomousLoop = exports.DEFAULT_LOOP_CONFIG = void 0;
|
|
10
43
|
exports.createAutonomousLoop = createAutonomousLoop;
|
|
@@ -13,6 +46,8 @@ exports.resetAutonomousLoop = resetAutonomousLoop;
|
|
|
13
46
|
const core_js_1 = require("./core.js");
|
|
14
47
|
const observations_js_1 = require("./observations.js");
|
|
15
48
|
const actions_js_1 = require("./actions.js");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
16
51
|
exports.DEFAULT_LOOP_CONFIG = {
|
|
17
52
|
cycleInterval: 1000,
|
|
18
53
|
maxCycles: 0,
|
|
@@ -20,6 +55,9 @@ exports.DEFAULT_LOOP_CONFIG = {
|
|
|
20
55
|
stopOnEnergyCritical: true,
|
|
21
56
|
stopOnHighSurprise: false,
|
|
22
57
|
surpriseThreshold: 10,
|
|
58
|
+
persistModelPath: '.genesis/learned-model.json',
|
|
59
|
+
persistEveryN: 10, // Save every 10 cycles
|
|
60
|
+
loadOnStart: true, // Resume learning from previous session
|
|
23
61
|
verbose: false,
|
|
24
62
|
};
|
|
25
63
|
// ============================================================================
|
|
@@ -64,8 +102,15 @@ class AutonomousLoop {
|
|
|
64
102
|
this.startTime = new Date();
|
|
65
103
|
this.stopReason = undefined;
|
|
66
104
|
const limit = maxCycles ?? this.config.maxCycles;
|
|
105
|
+
// v10.8: Load previously learned model
|
|
106
|
+
if (this.config.loadOnStart) {
|
|
107
|
+
this.loadModel();
|
|
108
|
+
}
|
|
109
|
+
// v10.8: Initialize real observation sources
|
|
110
|
+
this.observations.initRealSources();
|
|
67
111
|
if (this.config.verbose) {
|
|
68
112
|
console.log(`[AI Loop] Starting autonomous loop (max cycles: ${limit || 'unlimited'})`);
|
|
113
|
+
console.log(`[AI Loop] Model persistence: ${this.config.persistModelPath}`);
|
|
69
114
|
}
|
|
70
115
|
try {
|
|
71
116
|
while (this.running) {
|
|
@@ -94,6 +139,10 @@ class AutonomousLoop {
|
|
|
94
139
|
}
|
|
95
140
|
}
|
|
96
141
|
this.running = false;
|
|
142
|
+
// v10.8: Save learned model on shutdown
|
|
143
|
+
if (this.config.persistEveryN > 0) {
|
|
144
|
+
this.saveModel();
|
|
145
|
+
}
|
|
97
146
|
const stats = this.getStats();
|
|
98
147
|
// Notify stop handlers
|
|
99
148
|
for (const handler of this.onStopHandlers) {
|
|
@@ -139,9 +188,18 @@ class AutonomousLoop {
|
|
|
139
188
|
if (this.config.verbose) {
|
|
140
189
|
console.log(`[AI Loop] Cycle ${this.cycleCount} - Result:`, result.success ? 'success' : result.error);
|
|
141
190
|
}
|
|
142
|
-
// 4.
|
|
191
|
+
// 4. v10.8: Feed action outcome back to observations
|
|
192
|
+
this.observations.recordToolResult(result.success, result.duration);
|
|
193
|
+
// 5. v10.8: Record learning event
|
|
194
|
+
const surprise = this.engine.getStats().averageSurprise;
|
|
195
|
+
this.engine.recordLearningEvent(action, surprise, result.success ? 'positive' : 'negative');
|
|
196
|
+
// 6. v10.8: Persist model periodically
|
|
197
|
+
if (this.config.persistEveryN > 0 && this.cycleCount % this.config.persistEveryN === 0) {
|
|
198
|
+
this.saveModel();
|
|
199
|
+
}
|
|
200
|
+
// 7. Check stopping conditions
|
|
143
201
|
this.checkStoppingConditions(obs);
|
|
144
|
-
//
|
|
202
|
+
// 8. Notify cycle handlers
|
|
145
203
|
for (const handler of this.onCycleHandlers) {
|
|
146
204
|
handler(this.cycleCount, action, beliefs);
|
|
147
205
|
}
|
|
@@ -211,6 +269,54 @@ class AutonomousLoop {
|
|
|
211
269
|
};
|
|
212
270
|
}
|
|
213
271
|
// ============================================================================
|
|
272
|
+
// v10.8: Model Persistence (save/load learned matrices)
|
|
273
|
+
// ============================================================================
|
|
274
|
+
/**
|
|
275
|
+
* Save learned model to disk.
|
|
276
|
+
* Persists A/B matrices, beliefs, and action counts between sessions.
|
|
277
|
+
*/
|
|
278
|
+
saveModel() {
|
|
279
|
+
try {
|
|
280
|
+
const modelData = this.engine.exportLearnedModel();
|
|
281
|
+
const modelPath = path.resolve(this.config.persistModelPath);
|
|
282
|
+
const dir = path.dirname(modelPath);
|
|
283
|
+
if (!fs.existsSync(dir)) {
|
|
284
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
285
|
+
}
|
|
286
|
+
fs.writeFileSync(modelPath, JSON.stringify(modelData, null, 2));
|
|
287
|
+
if (this.config.verbose) {
|
|
288
|
+
console.log(`[AI Loop] Model saved to ${modelPath}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (this.config.verbose) {
|
|
293
|
+
console.error(`[AI Loop] Failed to save model:`, error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Load previously learned model from disk.
|
|
299
|
+
* Resumes learning from where it left off.
|
|
300
|
+
*/
|
|
301
|
+
loadModel() {
|
|
302
|
+
try {
|
|
303
|
+
const modelPath = path.resolve(this.config.persistModelPath);
|
|
304
|
+
if (fs.existsSync(modelPath)) {
|
|
305
|
+
const data = JSON.parse(fs.readFileSync(modelPath, 'utf-8'));
|
|
306
|
+
this.engine.importLearnedModel(data);
|
|
307
|
+
if (this.config.verbose) {
|
|
308
|
+
console.log(`[AI Loop] Model loaded from ${modelPath}`);
|
|
309
|
+
console.log(`[AI Loop] Resuming with ${data.totalActions || 0} prior actions`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (this.config.verbose) {
|
|
315
|
+
console.error(`[AI Loop] Failed to load model:`, error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// ============================================================================
|
|
214
320
|
// Getters
|
|
215
321
|
// ============================================================================
|
|
216
322
|
isRunning() {
|
|
@@ -15,7 +15,16 @@
|
|
|
15
15
|
*
|
|
16
16
|
* NO external dependencies - pure TypeScript math.
|
|
17
17
|
*/
|
|
18
|
-
import { Observation, Beliefs, Policy, ActiveInferenceConfig, ActionType, AIEventHandler } from './types.js';
|
|
18
|
+
import { Observation, Beliefs, Policy, AMatrix, BMatrix, ActiveInferenceConfig, ActionType, AIEventHandler } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* v10.8: Economic preferences for autonomous revenue.
|
|
21
|
+
* Separate from main C matrix to avoid breaking existing EFE computation.
|
|
22
|
+
* Used by the autonomous loop to bias action selection toward revenue actions
|
|
23
|
+
* when economic health is low.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ECONOMIC_PREFERENCES: {
|
|
26
|
+
readonly economic: readonly [-8, -3, 1, 6];
|
|
27
|
+
};
|
|
19
28
|
export declare class ActiveInferenceEngine {
|
|
20
29
|
private config;
|
|
21
30
|
private A;
|
|
@@ -27,9 +36,19 @@ export declare class ActiveInferenceEngine {
|
|
|
27
36
|
private actionCounts;
|
|
28
37
|
private totalActions;
|
|
29
38
|
private stats;
|
|
39
|
+
private previousState;
|
|
40
|
+
private previousAction;
|
|
41
|
+
private previousObservation;
|
|
42
|
+
private aDirichlet;
|
|
43
|
+
private bDirichlet;
|
|
30
44
|
private learningHistory;
|
|
31
45
|
private readonly MAX_HISTORY;
|
|
32
46
|
constructor(config?: Partial<ActiveInferenceConfig>);
|
|
47
|
+
/**
|
|
48
|
+
* Initialize Dirichlet concentration parameters from current matrices.
|
|
49
|
+
* Concentration = initial_matrix * prior_scale (higher = more confident prior)
|
|
50
|
+
*/
|
|
51
|
+
private initDirichletParams;
|
|
33
52
|
/**
|
|
34
53
|
* Update beliefs given observations (state inference)
|
|
35
54
|
*
|
|
@@ -53,9 +72,25 @@ export declare class ActiveInferenceEngine {
|
|
|
53
72
|
*/
|
|
54
73
|
sampleAction(policy: Policy): ActionType;
|
|
55
74
|
/**
|
|
56
|
-
* Full inference cycle: observe → infer → act
|
|
75
|
+
* Full inference cycle: observe → infer → act → LEARN
|
|
76
|
+
* v10.8: Now calls updateA/updateB after each step (online learning)
|
|
57
77
|
*/
|
|
58
78
|
step(observation: Observation): ActionType;
|
|
79
|
+
/**
|
|
80
|
+
* Online learning: update A and B matrices from experience.
|
|
81
|
+
* Called automatically after each step.
|
|
82
|
+
*
|
|
83
|
+
* A update: "I observed O when I believed I was in state S"
|
|
84
|
+
* → strengthen A[observation][believed_state]
|
|
85
|
+
*
|
|
86
|
+
* B update: "I did action A in state S and ended up in state S'"
|
|
87
|
+
* → strengthen B[new_state][old_state][action]
|
|
88
|
+
*/
|
|
89
|
+
private learn;
|
|
90
|
+
/**
|
|
91
|
+
* Recompute B matrix column from Dirichlet parameters
|
|
92
|
+
*/
|
|
93
|
+
private recomputeB;
|
|
59
94
|
private computeLikelihoods;
|
|
60
95
|
private updateFactor;
|
|
61
96
|
private computeEFE;
|
|
@@ -106,6 +141,28 @@ export declare class ActiveInferenceEngine {
|
|
|
106
141
|
coupling: string;
|
|
107
142
|
goalProgress: string;
|
|
108
143
|
};
|
|
144
|
+
/**
|
|
145
|
+
* Export learned matrices for persistence between sessions.
|
|
146
|
+
* Call this before shutdown to save learning progress.
|
|
147
|
+
*/
|
|
148
|
+
exportLearnedModel(): {
|
|
149
|
+
A: AMatrix;
|
|
150
|
+
B: BMatrix;
|
|
151
|
+
beliefs: Beliefs;
|
|
152
|
+
actionCounts: number[];
|
|
153
|
+
totalActions: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Import previously learned matrices.
|
|
157
|
+
* Call at startup to resume from previous learning.
|
|
158
|
+
*/
|
|
159
|
+
importLearnedModel(model: {
|
|
160
|
+
A?: AMatrix;
|
|
161
|
+
B?: BMatrix;
|
|
162
|
+
beliefs?: Beliefs;
|
|
163
|
+
actionCounts?: number[];
|
|
164
|
+
totalActions?: number;
|
|
165
|
+
}): void;
|
|
109
166
|
on(handler: AIEventHandler): () => void;
|
|
110
167
|
private emit;
|
|
111
168
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* NO external dependencies - pure TypeScript math.
|
|
18
18
|
*/
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.ActiveInferenceEngine = void 0;
|
|
20
|
+
exports.ActiveInferenceEngine = exports.ECONOMIC_PREFERENCES = void 0;
|
|
21
21
|
exports.createActiveInferenceEngine = createActiveInferenceEngine;
|
|
22
22
|
const types_js_1 = require("./types.js");
|
|
23
23
|
// ============================================================================
|
|
@@ -233,9 +233,21 @@ function createDefaultCMatrix() {
|
|
|
233
233
|
// Prefer coherent world model
|
|
234
234
|
coherence: [-4, 0, 2],
|
|
235
235
|
// Strongly prefer task completion
|
|
236
|
+
// v10.8: Balanced with economic goals
|
|
236
237
|
task: [-2, 0, 1, 5],
|
|
237
238
|
};
|
|
238
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* v10.8: Economic preferences for autonomous revenue.
|
|
242
|
+
* Separate from main C matrix to avoid breaking existing EFE computation.
|
|
243
|
+
* Used by the autonomous loop to bias action selection toward revenue actions
|
|
244
|
+
* when economic health is low.
|
|
245
|
+
*/
|
|
246
|
+
exports.ECONOMIC_PREFERENCES = {
|
|
247
|
+
// economic observation: [critical, low, stable, growing]
|
|
248
|
+
// Strongly prefer growing revenue, penalize critical
|
|
249
|
+
economic: [-8, -3, 1, 6],
|
|
250
|
+
};
|
|
239
251
|
function createDefaultDMatrix() {
|
|
240
252
|
// D matrix: Prior beliefs about initial state
|
|
241
253
|
// Uniform priors
|
|
@@ -269,6 +281,13 @@ class ActiveInferenceEngine {
|
|
|
269
281
|
totalSurprise: 0,
|
|
270
282
|
actionsTaken: new Map(),
|
|
271
283
|
};
|
|
284
|
+
// Learning state: track previous step for B matrix updates
|
|
285
|
+
previousState = null;
|
|
286
|
+
previousAction = -1;
|
|
287
|
+
previousObservation = null;
|
|
288
|
+
// Dirichlet concentration parameters (proper Bayesian learning)
|
|
289
|
+
aDirichlet = {};
|
|
290
|
+
bDirichlet = {};
|
|
272
291
|
// 🧬 Evolution: Learning history for meta-learning
|
|
273
292
|
learningHistory = [];
|
|
274
293
|
MAX_HISTORY = 1000;
|
|
@@ -286,6 +305,31 @@ class ActiveInferenceEngine {
|
|
|
286
305
|
coupling: [...this.D.coupling],
|
|
287
306
|
goalProgress: [...this.D.goalProgress],
|
|
288
307
|
};
|
|
308
|
+
// Initialize Dirichlet concentration parameters from A/B matrices
|
|
309
|
+
// These accumulate evidence over time (proper Bayesian parameter learning)
|
|
310
|
+
this.initDirichletParams();
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Initialize Dirichlet concentration parameters from current matrices.
|
|
314
|
+
* Concentration = initial_matrix * prior_scale (higher = more confident prior)
|
|
315
|
+
*/
|
|
316
|
+
initDirichletParams() {
|
|
317
|
+
const priorScale = 10; // How much to trust initial priors
|
|
318
|
+
// A Dirichlet: one per observation modality
|
|
319
|
+
this.aDirichlet = {
|
|
320
|
+
energy: this.A.energy.map(row => row.map(p => p * priorScale)),
|
|
321
|
+
phi: this.A.phi.map(row => row.map(p => p * priorScale)),
|
|
322
|
+
tool: this.A.tool.map(row => row.map(p => p * priorScale)),
|
|
323
|
+
coherence: this.A.coherence.map(row => row.map(p => p * priorScale)),
|
|
324
|
+
task: this.A.task.map(row => row.map(p => p * priorScale)),
|
|
325
|
+
};
|
|
326
|
+
// B Dirichlet: one per state factor
|
|
327
|
+
this.bDirichlet = {
|
|
328
|
+
viability: this.B.viability.map(next => next.map(curr => curr.map(act => act * priorScale))),
|
|
329
|
+
worldState: this.B.worldState.map(next => next.map(curr => curr.map(act => act * priorScale))),
|
|
330
|
+
coupling: this.B.coupling.map(next => next.map(curr => curr.map(act => act * priorScale))),
|
|
331
|
+
goalProgress: this.B.goalProgress.map(next => next.map(curr => curr.map(act => act * priorScale))),
|
|
332
|
+
};
|
|
289
333
|
}
|
|
290
334
|
// ============================================================================
|
|
291
335
|
// Core Inference Functions
|
|
@@ -398,17 +442,140 @@ class ActiveInferenceEngine {
|
|
|
398
442
|
return action;
|
|
399
443
|
}
|
|
400
444
|
/**
|
|
401
|
-
* Full inference cycle: observe → infer → act
|
|
445
|
+
* Full inference cycle: observe → infer → act → LEARN
|
|
446
|
+
* v10.8: Now calls updateA/updateB after each step (online learning)
|
|
402
447
|
*/
|
|
403
448
|
step(observation) {
|
|
404
|
-
//
|
|
449
|
+
// 0. LEARN from previous step (if we have a previous state)
|
|
450
|
+
if (this.previousState && this.previousAction >= 0 && this.previousObservation) {
|
|
451
|
+
this.learn(this.previousObservation, observation, this.previousState, this.previousAction);
|
|
452
|
+
}
|
|
453
|
+
// 1. Save current state BEFORE update (for next learning step)
|
|
454
|
+
const preUpdateBeliefs = {
|
|
455
|
+
viability: [...this.beliefs.viability],
|
|
456
|
+
worldState: [...this.beliefs.worldState],
|
|
457
|
+
coupling: [...this.beliefs.coupling],
|
|
458
|
+
goalProgress: [...this.beliefs.goalProgress],
|
|
459
|
+
};
|
|
460
|
+
// 2. Update beliefs
|
|
405
461
|
this.inferStates(observation);
|
|
406
|
-
//
|
|
462
|
+
// 3. Infer policy
|
|
407
463
|
const policy = this.inferPolicies();
|
|
408
|
-
//
|
|
464
|
+
// 4. Sample action
|
|
409
465
|
const action = this.sampleAction(policy);
|
|
466
|
+
// 5. Store for next learning step
|
|
467
|
+
this.previousState = preUpdateBeliefs;
|
|
468
|
+
this.previousAction = types_js_1.ACTIONS.indexOf(action);
|
|
469
|
+
this.previousObservation = observation;
|
|
410
470
|
return action;
|
|
411
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Online learning: update A and B matrices from experience.
|
|
474
|
+
* Called automatically after each step.
|
|
475
|
+
*
|
|
476
|
+
* A update: "I observed O when I believed I was in state S"
|
|
477
|
+
* → strengthen A[observation][believed_state]
|
|
478
|
+
*
|
|
479
|
+
* B update: "I did action A in state S and ended up in state S'"
|
|
480
|
+
* → strengthen B[new_state][old_state][action]
|
|
481
|
+
*/
|
|
482
|
+
learn(prevObs, currentObs, prevBeliefs, actionIdx) {
|
|
483
|
+
const lr = this.config.learningRateA;
|
|
484
|
+
// === Update A matrix (likelihood mapping) ===
|
|
485
|
+
// For each modality, update the row corresponding to the observation
|
|
486
|
+
// weighted by current beliefs about the state
|
|
487
|
+
// Energy observation → viability state
|
|
488
|
+
for (let s = 0; s < types_js_1.HIDDEN_STATE_DIMS.viability; s++) {
|
|
489
|
+
this.aDirichlet.energy[currentObs.energy][s] += lr * this.beliefs.viability[s];
|
|
490
|
+
}
|
|
491
|
+
// Recompute A.energy from Dirichlet
|
|
492
|
+
for (let o = 0; o < 5; o++) {
|
|
493
|
+
const row = this.aDirichlet.energy[o];
|
|
494
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
495
|
+
this.A.energy[o] = row.map(v => v / sum);
|
|
496
|
+
}
|
|
497
|
+
// Phi observation → worldState
|
|
498
|
+
for (let s = 0; s < types_js_1.HIDDEN_STATE_DIMS.worldState; s++) {
|
|
499
|
+
this.aDirichlet.phi[currentObs.phi][s] += lr * this.beliefs.worldState[s];
|
|
500
|
+
}
|
|
501
|
+
for (let o = 0; o < 4; o++) {
|
|
502
|
+
const row = this.aDirichlet.phi[o];
|
|
503
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
504
|
+
this.A.phi[o] = row.map(v => v / sum);
|
|
505
|
+
}
|
|
506
|
+
// Tool observation → coupling
|
|
507
|
+
for (let s = 0; s < types_js_1.HIDDEN_STATE_DIMS.coupling; s++) {
|
|
508
|
+
this.aDirichlet.tool[currentObs.tool][s] += lr * this.beliefs.coupling[s];
|
|
509
|
+
}
|
|
510
|
+
for (let o = 0; o < 3; o++) {
|
|
511
|
+
const row = this.aDirichlet.tool[o];
|
|
512
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
513
|
+
this.A.tool[o] = row.map(v => v / sum);
|
|
514
|
+
}
|
|
515
|
+
// Task observation → goalProgress
|
|
516
|
+
for (let s = 0; s < types_js_1.HIDDEN_STATE_DIMS.goalProgress; s++) {
|
|
517
|
+
this.aDirichlet.task[currentObs.task][s] += lr * this.beliefs.goalProgress[s];
|
|
518
|
+
}
|
|
519
|
+
for (let o = 0; o < 4; o++) {
|
|
520
|
+
const row = this.aDirichlet.task[o];
|
|
521
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
522
|
+
this.A.task[o] = row.map(v => v / sum);
|
|
523
|
+
}
|
|
524
|
+
// === Update B matrix (transition model) ===
|
|
525
|
+
// "I was in state S, did action A, now I'm in state S'"
|
|
526
|
+
const lrB = this.config.learningRateB;
|
|
527
|
+
// Viability transitions
|
|
528
|
+
for (let next = 0; next < types_js_1.HIDDEN_STATE_DIMS.viability; next++) {
|
|
529
|
+
for (let prev = 0; prev < types_js_1.HIDDEN_STATE_DIMS.viability; prev++) {
|
|
530
|
+
this.bDirichlet.viability[next][prev][actionIdx] +=
|
|
531
|
+
lrB * prevBeliefs.viability[prev] * this.beliefs.viability[next];
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
this.recomputeB('viability', types_js_1.HIDDEN_STATE_DIMS.viability, actionIdx);
|
|
535
|
+
// WorldState transitions
|
|
536
|
+
for (let next = 0; next < types_js_1.HIDDEN_STATE_DIMS.worldState; next++) {
|
|
537
|
+
for (let prev = 0; prev < types_js_1.HIDDEN_STATE_DIMS.worldState; prev++) {
|
|
538
|
+
this.bDirichlet.worldState[next][prev][actionIdx] +=
|
|
539
|
+
lrB * prevBeliefs.worldState[prev] * this.beliefs.worldState[next];
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
this.recomputeB('worldState', types_js_1.HIDDEN_STATE_DIMS.worldState, actionIdx);
|
|
543
|
+
// Coupling transitions
|
|
544
|
+
for (let next = 0; next < types_js_1.HIDDEN_STATE_DIMS.coupling; next++) {
|
|
545
|
+
for (let prev = 0; prev < types_js_1.HIDDEN_STATE_DIMS.coupling; prev++) {
|
|
546
|
+
this.bDirichlet.coupling[next][prev][actionIdx] +=
|
|
547
|
+
lrB * prevBeliefs.coupling[prev] * this.beliefs.coupling[next];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
this.recomputeB('coupling', types_js_1.HIDDEN_STATE_DIMS.coupling, actionIdx);
|
|
551
|
+
// GoalProgress transitions
|
|
552
|
+
for (let next = 0; next < types_js_1.HIDDEN_STATE_DIMS.goalProgress; next++) {
|
|
553
|
+
for (let prev = 0; prev < types_js_1.HIDDEN_STATE_DIMS.goalProgress; prev++) {
|
|
554
|
+
this.bDirichlet.goalProgress[next][prev][actionIdx] +=
|
|
555
|
+
lrB * prevBeliefs.goalProgress[prev] * this.beliefs.goalProgress[next];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
this.recomputeB('goalProgress', types_js_1.HIDDEN_STATE_DIMS.goalProgress, actionIdx);
|
|
559
|
+
this.emit({
|
|
560
|
+
type: 'beliefs_updated',
|
|
561
|
+
timestamp: new Date(),
|
|
562
|
+
data: { learning: true, actionIdx, prevObs, currentObs },
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Recompute B matrix column from Dirichlet parameters
|
|
567
|
+
*/
|
|
568
|
+
recomputeB(factor, dim, actionIdx) {
|
|
569
|
+
for (let prev = 0; prev < dim; prev++) {
|
|
570
|
+
const col = this.bDirichlet[factor].map((next) => next[prev][actionIdx]);
|
|
571
|
+
const sum = col.reduce((a, b) => a + b, 0);
|
|
572
|
+
if (sum > 0) {
|
|
573
|
+
for (let next = 0; next < dim; next++) {
|
|
574
|
+
this.B[factor][next][prev][actionIdx] = col[next] / sum;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
412
579
|
// ============================================================================
|
|
413
580
|
// Helper Functions
|
|
414
581
|
// ============================================================================
|
|
@@ -667,6 +834,37 @@ class ActiveInferenceEngine {
|
|
|
667
834
|
goalProgress: ['blocked', 'slow', 'onTrack', 'achieved'][argmax(this.beliefs.goalProgress)],
|
|
668
835
|
};
|
|
669
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Export learned matrices for persistence between sessions.
|
|
839
|
+
* Call this before shutdown to save learning progress.
|
|
840
|
+
*/
|
|
841
|
+
exportLearnedModel() {
|
|
842
|
+
return {
|
|
843
|
+
A: JSON.parse(JSON.stringify(this.A)),
|
|
844
|
+
B: JSON.parse(JSON.stringify(this.B)),
|
|
845
|
+
beliefs: JSON.parse(JSON.stringify(this.beliefs)),
|
|
846
|
+
actionCounts: [...this.actionCounts],
|
|
847
|
+
totalActions: this.totalActions,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Import previously learned matrices.
|
|
852
|
+
* Call at startup to resume from previous learning.
|
|
853
|
+
*/
|
|
854
|
+
importLearnedModel(model) {
|
|
855
|
+
if (model.A)
|
|
856
|
+
this.A = model.A;
|
|
857
|
+
if (model.B)
|
|
858
|
+
this.B = model.B;
|
|
859
|
+
if (model.beliefs)
|
|
860
|
+
this.beliefs = model.beliefs;
|
|
861
|
+
if (model.actionCounts)
|
|
862
|
+
this.actionCounts = model.actionCounts;
|
|
863
|
+
if (model.totalActions)
|
|
864
|
+
this.totalActions = model.totalActions;
|
|
865
|
+
// Reinit Dirichlet from imported matrices
|
|
866
|
+
this.initDirichletParams();
|
|
867
|
+
}
|
|
670
868
|
// ============================================================================
|
|
671
869
|
// Event Handling
|
|
672
870
|
// ============================================================================
|
|
@@ -34,6 +34,9 @@ export declare class ObservationGatherer {
|
|
|
34
34
|
private getPhiState?;
|
|
35
35
|
private getSensorResult?;
|
|
36
36
|
private getWorldModelState?;
|
|
37
|
+
private mcpToolResults;
|
|
38
|
+
private lastStripeBalance;
|
|
39
|
+
private realSourcesInitialized;
|
|
37
40
|
/**
|
|
38
41
|
* Configure observation sources
|
|
39
42
|
*/
|
|
@@ -43,8 +46,24 @@ export declare class ObservationGatherer {
|
|
|
43
46
|
sensorResult?: () => Promise<SensorResult>;
|
|
44
47
|
worldModelState?: () => WorldModelState;
|
|
45
48
|
}): void;
|
|
49
|
+
/**
|
|
50
|
+
* v10.8: Initialize real observation sources from MCP.
|
|
51
|
+
* Call once to wire the gatherer to live system data.
|
|
52
|
+
*/
|
|
53
|
+
initRealSources(): void;
|
|
54
|
+
/**
|
|
55
|
+
* v10.8: Record an MCP tool call result for observation tracking.
|
|
56
|
+
* Called by the integration layer after each tool use.
|
|
57
|
+
*/
|
|
58
|
+
recordToolResult(success: boolean, latency: number): void;
|
|
59
|
+
/**
|
|
60
|
+
* v10.8: Query Stripe balance for economic observation.
|
|
61
|
+
* Returns cached value if queried recently (< 5 min).
|
|
62
|
+
*/
|
|
63
|
+
private getStripeBalance;
|
|
46
64
|
/**
|
|
47
65
|
* Gather all observations from system components
|
|
66
|
+
* v10.8: Now uses real MCP data when available
|
|
48
67
|
*/
|
|
49
68
|
gather(): Promise<Observation>;
|
|
50
69
|
/**
|
|
@@ -16,6 +16,7 @@ exports.ObservationGatherer = void 0;
|
|
|
16
16
|
exports.createObservationGatherer = createObservationGatherer;
|
|
17
17
|
exports.getObservationGatherer = getObservationGatherer;
|
|
18
18
|
const economic_integration_js_1 = require("./economic-integration.js");
|
|
19
|
+
const index_js_1 = require("../mcp/index.js");
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Observation Gatherer
|
|
21
22
|
// ============================================================================
|
|
@@ -25,6 +26,10 @@ class ObservationGatherer {
|
|
|
25
26
|
getPhiState;
|
|
26
27
|
getSensorResult;
|
|
27
28
|
getWorldModelState;
|
|
29
|
+
// v10.8: Real MCP data tracking
|
|
30
|
+
mcpToolResults = [];
|
|
31
|
+
lastStripeBalance = -1; // -1 = never checked
|
|
32
|
+
realSourcesInitialized = false;
|
|
28
33
|
/**
|
|
29
34
|
* Configure observation sources
|
|
30
35
|
*/
|
|
@@ -38,10 +43,113 @@ class ObservationGatherer {
|
|
|
38
43
|
if (sources.worldModelState)
|
|
39
44
|
this.getWorldModelState = sources.worldModelState;
|
|
40
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* v10.8: Initialize real observation sources from MCP.
|
|
48
|
+
* Call once to wire the gatherer to live system data.
|
|
49
|
+
*/
|
|
50
|
+
initRealSources() {
|
|
51
|
+
if (this.realSourcesInitialized)
|
|
52
|
+
return;
|
|
53
|
+
this.realSourcesInitialized = true;
|
|
54
|
+
// Wire kernel state to process metrics
|
|
55
|
+
this.getKernelState = () => {
|
|
56
|
+
const mem = process.memoryUsage();
|
|
57
|
+
const heapUsedRatio = mem.heapUsed / mem.heapTotal;
|
|
58
|
+
// Energy = inverse of resource pressure (more heap used = less energy)
|
|
59
|
+
const energy = Math.max(0, Math.min(1, 1 - heapUsedRatio));
|
|
60
|
+
return {
|
|
61
|
+
energy,
|
|
62
|
+
state: 'running',
|
|
63
|
+
taskStatus: 'running',
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
// Wire sensor result to actual MCP tool call history
|
|
67
|
+
this.getSensorResult = async () => {
|
|
68
|
+
// Use the last 10 MCP tool results for aggregate health
|
|
69
|
+
const recent = this.mcpToolResults.slice(-10);
|
|
70
|
+
if (recent.length === 0) {
|
|
71
|
+
// No tool calls yet - try a lightweight probe
|
|
72
|
+
try {
|
|
73
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
await mcp.discoverAllTools();
|
|
76
|
+
const latency = Date.now() - start;
|
|
77
|
+
this.recordToolResult(true, latency);
|
|
78
|
+
return { success: true, latency };
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
this.recordToolResult(false, 10000);
|
|
82
|
+
return { success: false, latency: 10000, error: String(e) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const successRate = recent.filter(r => r.success).length / recent.length;
|
|
86
|
+
const avgLatency = recent.reduce((sum, r) => sum + r.latency, 0) / recent.length;
|
|
87
|
+
return {
|
|
88
|
+
success: successRate > 0.5,
|
|
89
|
+
latency: avgLatency,
|
|
90
|
+
error: successRate <= 0.5 ? `Low success rate: ${(successRate * 100).toFixed(0)}%` : undefined,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
// Wire world model to memory coherence (Neo4j connectivity if available)
|
|
94
|
+
this.getWorldModelState = () => {
|
|
95
|
+
// Base coherence on tool result consistency
|
|
96
|
+
const recent = this.mcpToolResults.slice(-20);
|
|
97
|
+
if (recent.length < 2)
|
|
98
|
+
return { consistent: true, issues: 0 };
|
|
99
|
+
const failures = recent.filter(r => !r.success).length;
|
|
100
|
+
return {
|
|
101
|
+
consistent: failures < recent.length * 0.3,
|
|
102
|
+
issues: failures,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* v10.8: Record an MCP tool call result for observation tracking.
|
|
108
|
+
* Called by the integration layer after each tool use.
|
|
109
|
+
*/
|
|
110
|
+
recordToolResult(success, latency) {
|
|
111
|
+
this.mcpToolResults.push({ success, latency, timestamp: Date.now() });
|
|
112
|
+
// Keep last 100 results
|
|
113
|
+
if (this.mcpToolResults.length > 100) {
|
|
114
|
+
this.mcpToolResults = this.mcpToolResults.slice(-100);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* v10.8: Query Stripe balance for economic observation.
|
|
119
|
+
* Returns cached value if queried recently (< 5 min).
|
|
120
|
+
*/
|
|
121
|
+
async getStripeBalance() {
|
|
122
|
+
try {
|
|
123
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
124
|
+
const result = await mcp.call('stripe', 'get_balance', {});
|
|
125
|
+
const balanceData = result?.data || result;
|
|
126
|
+
// Parse Stripe balance response
|
|
127
|
+
const available = Array.isArray(balanceData?.available)
|
|
128
|
+
? balanceData.available.reduce((sum, b) => sum + (b.amount || 0), 0) / 100
|
|
129
|
+
: 0;
|
|
130
|
+
this.lastStripeBalance = available;
|
|
131
|
+
if (available <= 0)
|
|
132
|
+
return 0; // critical
|
|
133
|
+
if (available < 10)
|
|
134
|
+
return 1; // low
|
|
135
|
+
if (available < 100)
|
|
136
|
+
return 2; // stable
|
|
137
|
+
return 3; // growing
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Stripe not available, use economic integration fallback
|
|
141
|
+
return 2; // stable default
|
|
142
|
+
}
|
|
143
|
+
}
|
|
41
144
|
/**
|
|
42
145
|
* Gather all observations from system components
|
|
146
|
+
* v10.8: Now uses real MCP data when available
|
|
43
147
|
*/
|
|
44
148
|
async gather() {
|
|
149
|
+
// Auto-init real sources if not configured
|
|
150
|
+
if (!this.getKernelState && !this.getPhiState && !this.getSensorResult) {
|
|
151
|
+
this.initRealSources();
|
|
152
|
+
}
|
|
45
153
|
// Get states from components (with defaults if not configured)
|
|
46
154
|
const kernelState = this.getKernelState?.() ?? {
|
|
47
155
|
energy: 0.5,
|
|
@@ -59,13 +167,18 @@ class ObservationGatherer {
|
|
|
59
167
|
consistent: true,
|
|
60
168
|
issues: 0,
|
|
61
169
|
};
|
|
62
|
-
//
|
|
170
|
+
// v10.8: Get economic observation from Stripe (with fallback)
|
|
63
171
|
let economicObs = 2; // Default stable
|
|
64
172
|
try {
|
|
65
|
-
economicObs = await
|
|
173
|
+
economicObs = await this.getStripeBalance();
|
|
66
174
|
}
|
|
67
175
|
catch {
|
|
68
|
-
|
|
176
|
+
try {
|
|
177
|
+
economicObs = await (0, economic_integration_js_1.getEconomicIntegration)().getDiscreteObservation();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Both failed, use default
|
|
181
|
+
}
|
|
69
182
|
}
|
|
70
183
|
// Map to discrete observations
|
|
71
184
|
return {
|
|
@@ -74,7 +187,7 @@ class ObservationGatherer {
|
|
|
74
187
|
tool: this.mapTool(sensorResult),
|
|
75
188
|
coherence: this.mapCoherence(worldModelState),
|
|
76
189
|
task: this.mapTask(kernelState.taskStatus),
|
|
77
|
-
economic: economicObs,
|
|
190
|
+
economic: economicObs,
|
|
78
191
|
};
|
|
79
192
|
}
|
|
80
193
|
/**
|
|
@@ -70,7 +70,7 @@ export declare const OBSERVATION_LABELS: {
|
|
|
70
70
|
task: readonly ["none", "pending", "active", "completed"];
|
|
71
71
|
economic: readonly ["critical", "low", "stable", "growing"];
|
|
72
72
|
};
|
|
73
|
-
export type ActionType = 'sense.mcp' | 'recall.memory' | 'plan.goals' | 'verify.ethics' | 'execute.task' | 'execute.code' | 'execute.shell' | 'adapt.code' | 'execute.cycle' | 'self.modify' | 'self.analyze' | 'git.push' | 'dream.cycle' | 'rest.idle' | 'recharge' | 'web.search' | 'web.scrape' | 'web.browse' | 'deploy.service' | 'content.generate' | 'market.analyze' | 'api.call' | 'github.deploy' | 'code.snapshot' | 'code.history' | 'code.diff' | 'econ.check' | 'econ.optimize' | 'econ.activate' | 'econ.promote' | 'improve.self';
|
|
73
|
+
export type ActionType = 'sense.mcp' | 'recall.memory' | 'plan.goals' | 'verify.ethics' | 'execute.task' | 'execute.code' | 'execute.shell' | 'adapt.code' | 'execute.cycle' | 'self.modify' | 'self.analyze' | 'git.push' | 'dream.cycle' | 'rest.idle' | 'recharge' | 'web.search' | 'web.scrape' | 'web.browse' | 'deploy.service' | 'content.generate' | 'market.analyze' | 'api.call' | 'github.deploy' | 'code.snapshot' | 'code.history' | 'code.diff' | 'econ.check' | 'econ.optimize' | 'econ.activate' | 'econ.promote' | 'improve.self' | 'opportunity.scan' | 'opportunity.evaluate' | 'opportunity.build' | 'opportunity.monetize';
|
|
74
74
|
export declare const ACTIONS: ActionType[];
|
|
75
75
|
export declare const ACTION_COUNT: number;
|
|
76
76
|
/**
|
|
@@ -76,6 +76,11 @@ exports.ACTIONS = [
|
|
|
76
76
|
'econ.promote',
|
|
77
77
|
// v10.0 - Meta-Improvement
|
|
78
78
|
'improve.self',
|
|
79
|
+
// v10.8 - Autonomous Revenue
|
|
80
|
+
'opportunity.scan',
|
|
81
|
+
'opportunity.evaluate',
|
|
82
|
+
'opportunity.build',
|
|
83
|
+
'opportunity.monetize',
|
|
79
84
|
];
|
|
80
85
|
exports.ACTION_COUNT = exports.ACTIONS.length;
|
|
81
86
|
exports.DEFAULT_CONFIG = {
|
|
@@ -76,6 +76,11 @@ const AI_TO_WM_ACTION = {
|
|
|
76
76
|
'econ.promote': 'execute', // Promote services
|
|
77
77
|
// v10.0 - Meta-Improvement
|
|
78
78
|
'improve.self': 'transform', // Self-improvement action
|
|
79
|
+
// v10.8 - Autonomous Revenue
|
|
80
|
+
'opportunity.scan': 'query', // Scan for opportunities
|
|
81
|
+
'opportunity.evaluate': 'query', // Evaluate feasibility
|
|
82
|
+
'opportunity.build': 'execute', // Build & deploy
|
|
83
|
+
'opportunity.monetize': 'execute', // Wire payments
|
|
79
84
|
};
|
|
80
85
|
// ============================================================================
|
|
81
86
|
// Value-Augmented Active Inference Engine
|
|
@@ -72,16 +72,25 @@ export declare class VectorMemory {
|
|
|
72
72
|
});
|
|
73
73
|
connect(): Promise<boolean>;
|
|
74
74
|
private ensureIndex;
|
|
75
|
+
/**
|
|
76
|
+
* Store a memory entry using Pinecone's integrated inference.
|
|
77
|
+
* v10.8: Uses upsert-records with text (Pinecone handles embedding internally).
|
|
78
|
+
* No need for pre-computed embeddings - simpler and more reliable.
|
|
79
|
+
*/
|
|
75
80
|
store(entry: {
|
|
76
81
|
id: string;
|
|
77
82
|
content: string;
|
|
78
|
-
embedding
|
|
83
|
+
embedding?: number[];
|
|
79
84
|
metadata?: Record<string, unknown>;
|
|
80
85
|
}): Promise<{
|
|
81
86
|
success: boolean;
|
|
82
87
|
error?: string;
|
|
83
88
|
}>;
|
|
84
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Search memory using Pinecone's integrated inference.
|
|
91
|
+
* v10.8: Uses search-records with text query (no pre-computed embeddings needed).
|
|
92
|
+
*/
|
|
93
|
+
search(query: string | number[], options?: {
|
|
85
94
|
topK?: number;
|
|
86
95
|
filter?: Record<string, unknown>;
|
|
87
96
|
includeMetadata?: boolean;
|
|
@@ -111,10 +120,31 @@ export declare class KnowledgeGraph {
|
|
|
111
120
|
success: boolean;
|
|
112
121
|
error?: string;
|
|
113
122
|
}>;
|
|
123
|
+
/**
|
|
124
|
+
* Add an edge with temporal properties.
|
|
125
|
+
* v10.8: All edges now have valid_from (creation time).
|
|
126
|
+
* Set valid_to to mark facts as expired (temporal knowledge).
|
|
127
|
+
*/
|
|
114
128
|
addEdge(edge: KnowledgeEdge): Promise<{
|
|
115
129
|
success: boolean;
|
|
116
130
|
error?: string;
|
|
117
131
|
}>;
|
|
132
|
+
/**
|
|
133
|
+
* v10.8: Expire a relationship (mark as no longer valid).
|
|
134
|
+
* This preserves history while indicating current state.
|
|
135
|
+
*/
|
|
136
|
+
expireEdge(from: string, to: string, type: string): Promise<{
|
|
137
|
+
success: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
/**
|
|
140
|
+
* v10.8: Find only currently valid relationships.
|
|
141
|
+
* Filters out expired edges (valid_to IS NOT NULL).
|
|
142
|
+
*/
|
|
143
|
+
findCurrentRelated(nodeId: string, options?: {
|
|
144
|
+
maxHops?: number;
|
|
145
|
+
relationTypes?: string[];
|
|
146
|
+
limit?: number;
|
|
147
|
+
}): Promise<KnowledgeNode[]>;
|
|
118
148
|
findRelated(nodeId: string, options?: {
|
|
119
149
|
maxHops?: number;
|
|
120
150
|
relationTypes?: string[];
|
|
@@ -55,42 +55,83 @@ class VectorMemory {
|
|
|
55
55
|
console.log('[VectorMemory] Index check:', error);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Store a memory entry using Pinecone's integrated inference.
|
|
60
|
+
* v10.8: Uses upsert-records with text (Pinecone handles embedding internally).
|
|
61
|
+
* No need for pre-computed embeddings - simpler and more reliable.
|
|
62
|
+
*/
|
|
58
63
|
async store(entry) {
|
|
59
64
|
if (!this.connected) {
|
|
60
65
|
return { success: false, error: 'Not connected to Pinecone' };
|
|
61
66
|
}
|
|
62
67
|
try {
|
|
63
68
|
const client = (0, index_js_1.getMCPClient)();
|
|
64
|
-
|
|
69
|
+
// v10.8: Use upsert-records with text for integrated inference
|
|
70
|
+
await client.call('pinecone', 'upsert-records', {
|
|
65
71
|
indexName: this.indexName,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
metadata
|
|
70
|
-
|
|
71
|
-
...entry.metadata,
|
|
72
|
-
timestamp: new Date().toISOString(),
|
|
73
|
-
},
|
|
72
|
+
records: [{
|
|
73
|
+
_id: entry.id,
|
|
74
|
+
text: entry.content,
|
|
75
|
+
...entry.metadata,
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
74
77
|
}],
|
|
75
78
|
});
|
|
76
79
|
return { success: true };
|
|
77
80
|
}
|
|
78
81
|
catch (error) {
|
|
82
|
+
// Fallback: try legacy upsert_vectors if upsert-records not supported
|
|
83
|
+
if (entry.embedding) {
|
|
84
|
+
try {
|
|
85
|
+
const client = (0, index_js_1.getMCPClient)();
|
|
86
|
+
await client.call('pinecone', 'upsert_vectors', {
|
|
87
|
+
indexName: this.indexName,
|
|
88
|
+
vectors: [{
|
|
89
|
+
id: entry.id,
|
|
90
|
+
values: entry.embedding,
|
|
91
|
+
metadata: {
|
|
92
|
+
content: entry.content,
|
|
93
|
+
...entry.metadata,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
},
|
|
96
|
+
}],
|
|
97
|
+
});
|
|
98
|
+
return { success: true };
|
|
99
|
+
}
|
|
100
|
+
catch (fallbackError) {
|
|
101
|
+
return { success: false, error: String(fallbackError) };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
79
104
|
return { success: false, error: String(error) };
|
|
80
105
|
}
|
|
81
106
|
}
|
|
82
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Search memory using Pinecone's integrated inference.
|
|
109
|
+
* v10.8: Uses search-records with text query (no pre-computed embeddings needed).
|
|
110
|
+
*/
|
|
111
|
+
async search(query, options) {
|
|
83
112
|
if (!this.connected)
|
|
84
113
|
return [];
|
|
85
114
|
try {
|
|
86
115
|
const client = (0, index_js_1.getMCPClient)();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
let result;
|
|
117
|
+
if (typeof query === 'string') {
|
|
118
|
+
// v10.8: Use search-records with text query (integrated inference)
|
|
119
|
+
result = await client.call('pinecone', 'search-records', {
|
|
120
|
+
indexName: this.indexName,
|
|
121
|
+
query,
|
|
122
|
+
topK: options?.topK || 10,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Legacy: vector-based query
|
|
127
|
+
result = await client.call('pinecone', 'query_vectors', {
|
|
128
|
+
indexName: this.indexName,
|
|
129
|
+
vector: query,
|
|
130
|
+
topK: options?.topK || 10,
|
|
131
|
+
filter: options?.filter,
|
|
132
|
+
includeMetadata: options?.includeMetadata ?? true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
94
135
|
const matches = result.data?.matches || [];
|
|
95
136
|
return matches.map((match) => ({
|
|
96
137
|
id: match.id,
|
|
@@ -215,14 +256,24 @@ class KnowledgeGraph {
|
|
|
215
256
|
return { success: false, error: String(error) };
|
|
216
257
|
}
|
|
217
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Add an edge with temporal properties.
|
|
261
|
+
* v10.8: All edges now have valid_from (creation time).
|
|
262
|
+
* Set valid_to to mark facts as expired (temporal knowledge).
|
|
263
|
+
*/
|
|
218
264
|
async addEdge(edge) {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
:
|
|
265
|
+
const now = new Date().toISOString();
|
|
266
|
+
const temporalProps = {
|
|
267
|
+
valid_from: now,
|
|
268
|
+
...(edge.properties || {}),
|
|
269
|
+
};
|
|
270
|
+
const propsString = Object.entries(temporalProps)
|
|
271
|
+
.map(([k]) => `r.${k} = $${k}`)
|
|
272
|
+
.join(', ');
|
|
222
273
|
const query = `
|
|
223
274
|
MATCH (a {id: $from}), (b {id: $to})
|
|
224
275
|
MERGE (a)-[r:${edge.type}]->(b)
|
|
225
|
-
SET r.weight = $weight ${propsString}
|
|
276
|
+
SET r.weight = $weight, ${propsString}
|
|
226
277
|
RETURN r
|
|
227
278
|
`;
|
|
228
279
|
try {
|
|
@@ -230,7 +281,7 @@ class KnowledgeGraph {
|
|
|
230
281
|
from: edge.from,
|
|
231
282
|
to: edge.to,
|
|
232
283
|
weight: edge.weight || 1.0,
|
|
233
|
-
...
|
|
284
|
+
...temporalProps,
|
|
234
285
|
});
|
|
235
286
|
return { success: true };
|
|
236
287
|
}
|
|
@@ -238,6 +289,55 @@ class KnowledgeGraph {
|
|
|
238
289
|
return { success: false, error: String(error) };
|
|
239
290
|
}
|
|
240
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* v10.8: Expire a relationship (mark as no longer valid).
|
|
294
|
+
* This preserves history while indicating current state.
|
|
295
|
+
*/
|
|
296
|
+
async expireEdge(from, to, type) {
|
|
297
|
+
const now = new Date().toISOString();
|
|
298
|
+
const query = `
|
|
299
|
+
MATCH (a {id: $from})-[r:${type}]->(b {id: $to})
|
|
300
|
+
WHERE r.valid_to IS NULL
|
|
301
|
+
SET r.valid_to = $now
|
|
302
|
+
RETURN r
|
|
303
|
+
`;
|
|
304
|
+
try {
|
|
305
|
+
await this.runQuery(query, { from, to, now });
|
|
306
|
+
return { success: true };
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return { success: false };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* v10.8: Find only currently valid relationships.
|
|
314
|
+
* Filters out expired edges (valid_to IS NOT NULL).
|
|
315
|
+
*/
|
|
316
|
+
async findCurrentRelated(nodeId, options) {
|
|
317
|
+
const hops = options?.maxHops || 2;
|
|
318
|
+
const limit = options?.limit || 20;
|
|
319
|
+
const relationFilter = options?.relationTypes?.length
|
|
320
|
+
? `:${options.relationTypes.join('|')}`
|
|
321
|
+
: '';
|
|
322
|
+
const query = `
|
|
323
|
+
MATCH (n {id: $nodeId})-[r${relationFilter}*1..${hops}]-(related)
|
|
324
|
+
WHERE ALL(rel IN r WHERE rel.valid_to IS NULL)
|
|
325
|
+
RETURN DISTINCT related
|
|
326
|
+
LIMIT $limit
|
|
327
|
+
`;
|
|
328
|
+
try {
|
|
329
|
+
const results = await this.runQuery(query, { nodeId, limit });
|
|
330
|
+
return results.map((r) => ({
|
|
331
|
+
id: r.related.id,
|
|
332
|
+
type: r.related.labels?.[0] || 'Unknown',
|
|
333
|
+
name: r.related.name,
|
|
334
|
+
properties: r.related,
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
241
341
|
async findRelated(nodeId, options) {
|
|
242
342
|
const hops = options?.maxHops || 2;
|
|
243
343
|
const limit = options?.limit || 20;
|
package/package.json
CHANGED