diff --git a/Autotests/test_websearch_smoke.py b/Autotests/test_websearch_smoke.py index 2653bb08..3a655457 100644 --- a/Autotests/test_websearch_smoke.py +++ b/Autotests/test_websearch_smoke.py @@ -12,7 +12,7 @@ def test_websearch_returns_results(): code = ( "import os, sys;" - "sys.path.insert(0, os.path.join(os.environ['OMEGACLAW_DIR'], 'channels'));" + "sys.path.insert(0, os.path.join(os.environ['OMEGACLAW_DIR'], 'src'));" "import websearch;" "print('COUNT', len(websearch.search_('test')))" ) diff --git a/channels/irc.metta b/channels/irc.metta new file mode 100644 index 00000000..e57171bf --- /dev/null +++ b/channels/irc.metta @@ -0,0 +1,19 @@ +!(import! &self (library OmegaClaw-Core ./channels/irc.py)) + +(= (IRC_channel) (empty)) +(= (IRC_server) (empty)) +(= (IRC_port) (empty)) +(= (IRC_user) (empty)) + +(= (commChannelInit) + (progn (configure IRC_channel ##omegaclaw) + (configure IRC_server "irc.quakenet.org") + (configure IRC_port 6667) + (configure IRC_user omegaclaw) + (py-call (irc.start_irc (IRC_channel) (IRC_server) (IRC_port) (IRC_user))))) + +(= (commChannelReceiveMessage) + (py-call (irc.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (irc.send_message $msg))) diff --git a/channels/mattermost.metta b/channels/mattermost.metta new file mode 100644 index 00000000..73636032 --- /dev/null +++ b/channels/mattermost.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/mattermost.py)) + +(= (MM_URL) (empty)) +(= (MM_CHANNEL_ID) (empty)) + +(= (commChannelInit) + (progn (configure MM_URL "https://chat.singularitynet.io") + (configure MM_CHANNEL_ID "8fjrmabjx7gupy7e5kjznpt5qh") + (py-call (mattermost.start_mattermost (MM_URL) (MM_CHANNEL_ID))))) + +(= (commChannelReceiveMessage) + (py-call (mattermost.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (mattermost.send_message $msg))) diff --git a/channels/slack.metta b/channels/slack.metta new file mode 100644 index 00000000..67c3d8b0 --- /dev/null +++ b/channels/slack.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/slack.py)) + +(= (SL_CHANNEL_ID) (empty)) +(= (SL_POLL_INTERVAL) (empty)) + +(= (commChannelInit) + (progn (configure SL_CHANNEL_ID "") + (configure SL_POLL_INTERVAL 60) + (py-call (slack.start_slack (SL_CHANNEL_ID) (SL_POLL_INTERVAL))))) + +(= (commChannelReceiveMessage) + (py-call (slack.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (slack.send_message $msg))) diff --git a/channels/telegram.metta b/channels/telegram.metta new file mode 100644 index 00000000..ae78956f --- /dev/null +++ b/channels/telegram.metta @@ -0,0 +1,15 @@ +!(import! &self (library OmegaClaw-Core ./channels/telegram.py)) + +(= (TG_CHAT_ID) (empty)) +(= (TG_POLL_TIMEOUT) (empty)) + +(= (commChannelInit) + (progn (configure TG_CHAT_ID "") + (configure TG_POLL_TIMEOUT 20) + (py-call (telegram.start_telegram (TG_CHAT_ID) (TG_POLL_TIMEOUT))))) + +(= (commChannelReceiveMessage) + (py-call (telegram.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (telegram.send_message $msg))) diff --git a/channels/test.metta b/channels/test.metta new file mode 100644 index 00000000..9cf115eb --- /dev/null +++ b/channels/test.metta @@ -0,0 +1,10 @@ +!(import! &self (library OmegaClaw-Core ./channels/mock.py)) + +(= (commChannelInit) + (py-call (mock.start_mock))) + +(= (commChannelReceiveMessage) + (py-call (mock.getLastMessage))) + +(= (commChannelSendMessage $msg) + (py-call (mock.send_message $msg))) diff --git a/docs/introduction.md b/docs/introduction.md index 5e38f08a..636a7e2f 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -121,6 +121,7 @@ lib_omegaclaw.metta loads all submodules ├── src/helper.py parenthesis balancing, normalization ├── src/agentverse.py remote-agent bridge ├── src/skills.pl Prolog helpers (shell, first_char) +├── src/websearch.py web search ├── lib_nal.metta NAL truth functions ├── lib_pln.metta PLN rules └── lib_llm_ext.py Claude / GPT / MiniMax / local embeddings @@ -129,7 +130,6 @@ channels/irc.py IRC adapter channels/telegram.py Telegram adapter channels/slack.py Slack adapter channels/mattermost.py Mattermost adapter -channels/websearch.py web search memory/prompt.txt system prompt (agent identity + values) memory/history.metta episodic trace (written at runtime) diff --git a/docs/reference-channels.md b/docs/reference-channels.md index a67e17c0..ad7819c4 100644 --- a/docs/reference-channels.md +++ b/docs/reference-channels.md @@ -59,10 +59,6 @@ Slack adapter using Slack Web API polling. - Adapter respects Slack `Retry-After` backoff on HTTP 429 and enforces a minimum 60s poll interval. - Uses the same one-time `auth ` ownership gate as the other adapters. -## `channels/websearch.py` - -Not a communication channel in the `send`/`receive` sense — this is the backend for the `search` skill. Exposes `search(query)`. - ## Adding a new channel See [tutorial-04-adding-a-channel.md](./tutorial-04-adding-a-channel.md). diff --git a/docs/reference-skills-communication.md b/docs/reference-skills-communication.md index 4f778a21..70205b45 100644 --- a/docs/reference-skills-communication.md +++ b/docs/reference-skills-communication.md @@ -68,7 +68,7 @@ The agent does not normally call `receive` itself; the loop wraps it: ``` ### Purpose -Perform a web search through the `channels/websearch.py` adapter. +Perform a web search through the `src/websearch.py` adapter. ### Parameters - `query` — the search string. diff --git a/lib_omegaclaw.metta b/lib_omegaclaw.metta index 7c5ca790..b935ec43 100644 --- a/lib_omegaclaw.metta +++ b/lib_omegaclaw.metta @@ -8,12 +8,9 @@ !(import! &self (library OmegaClaw-Core ./src/utils)) !(import! &self (library OmegaClaw-Core ./src/helper.py)) !(import! &self (library OmegaClaw-Core ./src/agentverse.py)) -!(import! &self (library OmegaClaw-Core ./channels/irc.py)) -!(import! &self (library OmegaClaw-Core ./channels/mattermost.py)) -!(import! &self (library OmegaClaw-Core ./channels/telegram.py)) -!(import! &self (library OmegaClaw-Core ./channels/slack.py)) -!(import! &self (library OmegaClaw-Core ./channels/websearch.py)) -!(import! &self (library OmegaClaw-Core ./src/channels)) +!(import! &self (library OmegaClaw-Core ./src/websearch.py)) +!(import! &self (library OmegaClaw-Core ./src/ext.py)) +!(import! &self (library OmegaClaw-Core ./src/ext)) !(import! &self (library OmegaClaw-Core ./src/skills)) !(import! &self (library OmegaClaw-Core ./src/memory)) !(import! &self (library OmegaClaw-Core ./src/context)) diff --git a/src/channels.metta b/src/channels.metta deleted file mode 100644 index 95e642e4..00000000 --- a/src/channels.metta +++ /dev/null @@ -1,68 +0,0 @@ -;configured at runtime: -(= (IRC_channel) (empty)) -(= (IRC_server) (empty)) -(= (IRC_port) (empty)) -(= (IRC_user) (empty)) -(= (commchannel) (empty)) -(= (MM_URL) (empty)) -(= (MM_CHANNEL_ID) (empty)) -(= (TG_CHAT_ID) (empty)) -(= (TG_POLL_TIMEOUT) (empty)) -(= (SL_CHANNEL_ID) (empty)) -(= (SL_POLL_INTERVAL) (empty)) - -;Connect all the channels: -(= (initChannels) - (progn (println! "Initializing channels") - (configure commchannel irc) - (if (== (commchannel) irc) - (progn (configure IRC_channel ##omegaclaw) - (configure IRC_server "irc.quakenet.org") - (configure IRC_port 6667) - (configure IRC_user omegaclaw) - (py-call (irc.start_irc (IRC_channel) (IRC_server) (IRC_port) (IRC_user)))) - (if (== (commchannel) telegram) - (progn (configure TG_CHAT_ID "") - (configure TG_POLL_TIMEOUT 20) - (py-call (telegram.start_telegram (TG_CHAT_ID) (TG_POLL_TIMEOUT)))) - (if (== (commchannel) slack) - (progn (configure SL_CHANNEL_ID "") - (configure SL_POLL_INTERVAL 60) - (py-call (slack.start_slack (SL_CHANNEL_ID) (SL_POLL_INTERVAL)))) - (if (== (commchannel) mattermost) - (progn (configure MM_URL "https://chat.singularitynet.io") - (configure MM_CHANNEL_ID "8fjrmabjx7gupy7e5kjznpt5qh") - (py-call (mattermost.start_mattermost (MM_URL) (MM_CHANNEL_ID)))) - (py-call (mock.start_mock)))))))) - -;Receive the latest user message considering all communication channels: -(= (receive) - (if (== (commchannel) irc) - (py-call (irc.getLastMessage)) - (if (== (commchannel) telegram) - (py-call (telegram.getLastMessage)) - (if (== (commchannel) slack) - (py-call (slack.getLastMessage)) - (if (== (commchannel) mattermost) - (py-call (mattermost.getLastMessage)) - (py-call (mock.getLastMessage))))))) - -;Send a message to all communication channels: -!(change-state! &lastsend "") -(= (send $msg) - (if (!= $msg (get-state &lastsend)) - (progn (change-state! &lastsend $msg) - (let $safemsg (string-replace $msg "\n" "\\n") - (if (== (commchannel) irc) - (let $temp (cut) (py-call (irc.send_message $safemsg))) - (if (== (commchannel) telegram) - (let $temp (cut) (py-call (telegram.send_message $safemsg))) - (if (== (commchannel) slack) - (let $temp (cut) (py-call (slack.send_message $safemsg))) - (if (== (commchannel) mattermost) - (let $temp (cut) (py-call (mattermost.send_message $safemsg))) - (let $temp (cut) (py-call (mock.send_message $safemsg))))))))) _)) - -;Search the internet for some information: -(= (search $msg) - (py-call (websearch.search $msg))) diff --git a/src/ext.metta b/src/ext.metta new file mode 100644 index 00000000..38ce4f76 --- /dev/null +++ b/src/ext.metta @@ -0,0 +1,51 @@ +; TODO: the main reason of moving loading the extensions loading out of the +; (init... ) functions is that dependency should be loaded before the dependent +; module in PeTTa otherwise the dependency functions will not be callable from +; the dependent module (see https://github.com/trueagi-io/PeTTa/issues/181). +; This module is loaded before loop.metta and thus the extensions API is +; available before loop.metta which is critical to call (initChannels) and +; similar. If #181 is fixed it will be possible to move code below into +; (initChannels) in channels.metta and similar. + +;configured at runtime: +(= (commchannel) (empty)) +!(configure commchannel irc) + +(= (load-commchannel-py $extname) + (if (== 1 (py-call (ext.commchannel $extname))) + (progn (add-atom &self (= (commChannelReceiveMessage) (py-call (ext.commChannelReceiveMessage)))) + (add-atom &self (= (commChannelSendMessage $msg) (py-call (ext.commChannelSendMessage $msg)))) + True) + False)) + +(= (empty-to-false $x) + (case $x + ((() False) + ($_ True)))) + + +(= (load-commchannel $extname) + (progn (println! (atom_concat "Loading communication channel extension: " $extname)) + (if (empty-to-false (collapse (import! &self (library OmegaClaw-Core (atom_concat "./channels/" $extname))))) True + (if (empty-to-false (collapse (import! &self (library OmegaClaw-Core (atom_concat "./extensions/" $extname))))) True + (if (load-commchannel-py $extname) True + (progn (println! (atom_concat "Could not load communication channel extension: " $extname)) + False)))))) + +!(load-commchannel (commchannel)) + +;Connect all the channels: +(= (initChannels) + (progn (println! "Initializing channels") + (commChannelInit))) + +;Receive the latest user message considering all communication channels: +(= (receive) (commChannelReceiveMessage)) + +;Send a message to all communication channels: +!(change-state! &lastsend "") +(= (send $msg) + (if (!= $msg (get-state &lastsend)) + (progn (change-state! &lastsend $msg) + (let $safemsg (string-replace $msg "\n" "\\n") + (let $temp (cut) (commChannelSendMessage $safemsg)))))) diff --git a/src/ext.py b/src/ext.py new file mode 100644 index 00000000..f1ba96cb --- /dev/null +++ b/src/ext.py @@ -0,0 +1,20 @@ +import importlib + +_mod = None + +def commchannel(extname): + global _mod + try: + _mod = importlib.import_module(extname) + if _mod is not None: + return 1 + except Exception as e: + return 0 + +def commChannelReceiveMessage(): + global _mod + return _mod.commChannelReceiveMessage() + +def commChannelSendMessage(msg): + global _mod + return _mod.commChannelSendMessage(msg) diff --git a/src/skills.metta b/src/skills.metta index 7f751b97..5fdeb73b 100644 --- a/src/skills.metta +++ b/src/skills.metta @@ -62,3 +62,7 @@ (= (pin $x) PIN-SUCCESS) + +;Search the internet for some information: +(= (search $msg) + (py-call (websearch.search $msg))) diff --git a/channels/websearch.py b/src/websearch.py similarity index 100% rename from channels/websearch.py rename to src/websearch.py