pythonclaw 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {pythonclaw-0.3.0/pythonclaw.egg-info → pythonclaw-0.3.2}/PKG-INFO +3 -8
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pyproject.toml +3 -9
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/__init__.py +1 -1
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/config.py +18 -17
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/agent.py +19 -12
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/compaction.py +9 -3
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/memory/storage.py +4 -1
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/session_store.py +8 -3
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/skillhub.py +2 -1
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/tools.py +2 -1
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/daemon.py +5 -2
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/init.py +9 -4
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/onboard.py +3 -2
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/scheduler/cron.py +18 -8
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/scheduler/heartbeat.py +13 -3
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/server.py +0 -3
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_setting/update_config.py +12 -5
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/onboarding/write_identity.py +4 -2
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/web/app.py +10 -10
- {pythonclaw-0.3.0 → pythonclaw-0.3.2/pythonclaw.egg-info}/PKG-INFO +3 -8
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw.egg-info/requires.txt +2 -10
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/tests/test_persistence.py +5 -5
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/LICENSE +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/MANIFEST.in +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/README.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/__main__.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/channels/discord_bot.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/channels/telegram_bot.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/__init__.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/knowledge/rag.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/llm/anthropic_client.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/llm/base.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/llm/gemini_client.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/llm/openai_compatible.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/llm/response.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/memory/manager.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/persistent_agent.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/__init__.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/chunker.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/dense.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/fusion.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/reranker.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/retriever.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/retrieval/sparse.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/skill_loader.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/core/utils.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/main.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/session_manager.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/persona/demo_persona.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/communication/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/communication/email/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/communication/email/send_email.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/csv_analyzer/analyze.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/finance/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/finance/fetch_quote.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/news/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/news/search_news.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/scraper/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/scraper/scrape.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/weather/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/weather/weather.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/youtube/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/youtube/youtube_info.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/code_runner/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/code_runner/run_code.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/github/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/github/gh.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/http_request/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/http_request/request.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/google/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/google/workspace/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/meta/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_persona/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_setting/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_soul/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/onboarding/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/random/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/random/random_util.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/time/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/time/time_util.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/text/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/text/translator/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/text/translator/translate.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/web/CATEGORY.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/web/tavily/SKILL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/soul/SOUL.md +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/web/__init__.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/web/static/favicon.png +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/web/static/index.html +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/web/static/logo.png +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw.egg-info/SOURCES.txt +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw.egg-info/dependency_links.txt +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw.egg-info/entry_points.txt +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw.egg-info/top_level.txt +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/setup.cfg +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/tests/test_compaction.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/tests/test_rag_hybrid.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/tests/test_skills.py +0 -0
- {pythonclaw-0.3.0 → pythonclaw-0.3.2}/tests/test_soul.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonclaw
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support.
|
|
5
5
|
Author-email: Eric Wang <wangchen2007915@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -23,6 +23,8 @@ Requires-Python: >=3.10
|
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: openai
|
|
26
|
+
Requires-Dist: anthropic
|
|
27
|
+
Requires-Dist: google-generativeai
|
|
26
28
|
Requires-Dist: python-telegram-bot[job-queue]>=20.0
|
|
27
29
|
Requires-Dist: discord.py>=2.3
|
|
28
30
|
Requires-Dist: apscheduler>=3.10
|
|
@@ -35,13 +37,6 @@ Requires-Dist: numpy>=1.24
|
|
|
35
37
|
Requires-Dist: scikit-learn>=1.3
|
|
36
38
|
Requires-Dist: beautifulsoup4
|
|
37
39
|
Requires-Dist: requests
|
|
38
|
-
Provides-Extra: all
|
|
39
|
-
Requires-Dist: anthropic; extra == "all"
|
|
40
|
-
Requires-Dist: google-generativeai; extra == "all"
|
|
41
|
-
Provides-Extra: anthropic
|
|
42
|
-
Requires-Dist: anthropic; extra == "anthropic"
|
|
43
|
-
Provides-Extra: gemini
|
|
44
|
-
Requires-Dist: google-generativeai; extra == "gemini"
|
|
45
40
|
Dynamic: license-file
|
|
46
41
|
|
|
47
42
|
<p align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythonclaw"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -27,6 +27,8 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
29
|
"openai",
|
|
30
|
+
"anthropic",
|
|
31
|
+
"google-generativeai",
|
|
30
32
|
"python-telegram-bot[job-queue]>=20.0",
|
|
31
33
|
"discord.py>=2.3",
|
|
32
34
|
"apscheduler>=3.10",
|
|
@@ -41,14 +43,6 @@ dependencies = [
|
|
|
41
43
|
"requests",
|
|
42
44
|
]
|
|
43
45
|
|
|
44
|
-
[project.optional-dependencies]
|
|
45
|
-
all = [
|
|
46
|
-
"anthropic",
|
|
47
|
-
"google-generativeai",
|
|
48
|
-
]
|
|
49
|
-
anthropic = ["anthropic"]
|
|
50
|
-
gemini = ["google-generativeai"]
|
|
51
|
-
|
|
52
46
|
[project.scripts]
|
|
53
47
|
pythonclaw = "pythonclaw.main:main"
|
|
54
48
|
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Centralised configuration for PythonClaw.
|
|
3
3
|
|
|
4
|
+
All runtime data lives under ``~/.pythonclaw/`` (the *home* directory):
|
|
5
|
+
|
|
6
|
+
~/.pythonclaw/
|
|
7
|
+
pythonclaw.json ← config file
|
|
8
|
+
context/ ← sessions, logs, memory, skills, …
|
|
9
|
+
daemon.log ← daemon output
|
|
10
|
+
pythonclaw.pid ← daemon PID
|
|
11
|
+
|
|
4
12
|
Load order (later sources override earlier ones):
|
|
5
|
-
1. pythonclaw.json
|
|
6
|
-
2.
|
|
7
|
-
3. Environment variables (highest priority)
|
|
8
|
-
|
|
9
|
-
The JSON file supports // line comments and trailing commas for convenience
|
|
10
|
-
(a subset of JSON5 that covers the most common needs).
|
|
11
|
-
|
|
12
|
-
Usage
|
|
13
|
-
-----
|
|
14
|
-
from pythonclaw import config
|
|
15
|
-
|
|
16
|
-
config.load() # call once at startup
|
|
17
|
-
provider = config.get("llm", "provider", env="LLM_PROVIDER", default="deepseek")
|
|
18
|
-
token = config.get("channels", "telegram", "token", env="TELEGRAM_BOT_TOKEN")
|
|
19
|
-
users = config.get_int_list("channels", "telegram", "allowedUsers",
|
|
20
|
-
env="TELEGRAM_ALLOWED_USERS")
|
|
13
|
+
1. ~/.pythonclaw/pythonclaw.json
|
|
14
|
+
2. Environment variables (highest priority)
|
|
21
15
|
"""
|
|
22
16
|
|
|
23
17
|
from __future__ import annotations
|
|
@@ -28,12 +22,19 @@ import re
|
|
|
28
22
|
from pathlib import Path
|
|
29
23
|
from typing import Any
|
|
30
24
|
|
|
25
|
+
PYTHONCLAW_HOME = Path(os.environ.get("PYTHONCLAW_HOME", Path.home() / ".pythonclaw"))
|
|
26
|
+
|
|
31
27
|
_TRAILING_COMMA_RE = re.compile(r",\s*([}\]])")
|
|
32
28
|
|
|
33
29
|
_config: dict | None = None
|
|
34
30
|
_config_path: Path | None = None
|
|
35
31
|
|
|
36
32
|
|
|
33
|
+
def home() -> Path:
|
|
34
|
+
"""Return the PythonClaw home directory (``~/.pythonclaw`` by default)."""
|
|
35
|
+
return PYTHONCLAW_HOME
|
|
36
|
+
|
|
37
|
+
|
|
37
38
|
def _strip_json5(text: str) -> str:
|
|
38
39
|
"""Strip // comments and trailing commas so standard json.loads works.
|
|
39
40
|
|
|
@@ -71,8 +72,8 @@ def _strip_json5(text: str) -> str:
|
|
|
71
72
|
|
|
72
73
|
def _find_config_file() -> Path | None:
|
|
73
74
|
candidates = [
|
|
75
|
+
PYTHONCLAW_HOME / "pythonclaw.json",
|
|
74
76
|
Path.cwd() / "pythonclaw.json",
|
|
75
|
-
Path.home() / ".pythonclaw" / "pythonclaw.json",
|
|
76
77
|
]
|
|
77
78
|
for p in candidates:
|
|
78
79
|
if p.is_file():
|
|
@@ -73,16 +73,22 @@ def _load_text_dir_or_file(path: str | None, label: str = "File") -> str:
|
|
|
73
73
|
return ""
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
def _detail_log_dir() -> str:
|
|
77
|
+
from .. import config as _cfg
|
|
78
|
+
return os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "logs")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _detail_log_file() -> str:
|
|
82
|
+
return os.path.join(_detail_log_dir(), "history_detail.jsonl")
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
def _log_detail(entry: dict) -> None:
|
|
81
86
|
"""Append a JSON line to the detailed interaction log."""
|
|
82
87
|
try:
|
|
83
|
-
|
|
88
|
+
log_dir = _detail_log_dir()
|
|
89
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
84
90
|
entry["ts"] = datetime.now().isoformat(timespec="milliseconds")
|
|
85
|
-
with open(
|
|
91
|
+
with open(_detail_log_file(), "a", encoding="utf-8") as f:
|
|
86
92
|
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
87
93
|
except OSError:
|
|
88
94
|
pass
|
|
@@ -129,18 +135,19 @@ class Agent:
|
|
|
129
135
|
cron_manager=None,
|
|
130
136
|
) -> None:
|
|
131
137
|
if memory_dir is None and skills_dirs is None and knowledge_path is None and persona_path is None:
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
from .. import config as _cfg
|
|
139
|
+
home = str(_cfg.PYTHONCLAW_HOME)
|
|
140
|
+
context_dir = os.path.join(home, "context")
|
|
134
141
|
if not os.path.exists(context_dir):
|
|
135
142
|
if verbose:
|
|
136
143
|
print(f"[Agent] Context not found. Initialising default context in {context_dir}...")
|
|
137
144
|
try:
|
|
138
|
-
from
|
|
139
|
-
init(
|
|
145
|
+
from ..init import init
|
|
146
|
+
init(home)
|
|
140
147
|
except ImportError:
|
|
141
148
|
try:
|
|
142
149
|
from pythonclaw.init import init
|
|
143
|
-
init(
|
|
150
|
+
init(home)
|
|
144
151
|
except ImportError:
|
|
145
152
|
print("[Agent] Warning: Could not auto-initialise context.")
|
|
146
153
|
if verbose:
|
|
@@ -152,9 +159,9 @@ class Agent:
|
|
|
152
159
|
if soul_path is None:
|
|
153
160
|
soul_path = os.path.join(context_dir, "soul")
|
|
154
161
|
|
|
155
|
-
# Sandbox: restrict file-write tools to the
|
|
156
|
-
sandbox_root = os.getcwd()
|
|
157
|
-
set_sandbox([sandbox_root])
|
|
162
|
+
# Sandbox: restrict file-write tools to the home directory
|
|
163
|
+
sandbox_root = str(_cfg.PYTHONCLAW_HOME) if '_cfg' in dir() else os.getcwd()
|
|
164
|
+
set_sandbox([sandbox_root, os.path.expanduser("~")])
|
|
158
165
|
if verbose:
|
|
159
166
|
print(f"[Agent] Sandbox root: {sandbox_root}")
|
|
160
167
|
|
|
@@ -45,7 +45,12 @@ logger = logging.getLogger(__name__)
|
|
|
45
45
|
CHARS_PER_TOKEN = 4
|
|
46
46
|
DEFAULT_AUTO_THRESHOLD_TOKENS = 6000 # trigger auto-compaction at ~6k tokens
|
|
47
47
|
DEFAULT_RECENT_KEEP = 6 # keep last N chat messages verbatim
|
|
48
|
-
|
|
48
|
+
def _compaction_log_file() -> str:
|
|
49
|
+
from .. import config as _cfg
|
|
50
|
+
return os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "compaction", "history.jsonl")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
COMPACTION_LOG_FILE = None # resolved lazily
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
# ── Token estimation ──────────────────────────────────────────────────────────
|
|
@@ -58,8 +63,9 @@ def estimate_tokens(messages: list[dict]) -> int:
|
|
|
58
63
|
|
|
59
64
|
# ── JSONL persistence ─────────────────────────────────────────────────────────
|
|
60
65
|
|
|
61
|
-
def persist_compaction(summary: str, message_count: int, log_path: str =
|
|
66
|
+
def persist_compaction(summary: str, message_count: int, log_path: str | None = None) -> None:
|
|
62
67
|
"""Append one compaction entry to the JSONL audit log."""
|
|
68
|
+
log_path = log_path or _compaction_log_file()
|
|
63
69
|
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
|
64
70
|
entry = {
|
|
65
71
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
@@ -150,7 +156,7 @@ def compact(
|
|
|
150
156
|
memory: "MemoryManager | None" = None,
|
|
151
157
|
recent_keep: int = DEFAULT_RECENT_KEEP,
|
|
152
158
|
instruction: str | None = None,
|
|
153
|
-
log_path: str =
|
|
159
|
+
log_path: str | None = None,
|
|
154
160
|
) -> tuple[list[dict], str]:
|
|
155
161
|
"""
|
|
156
162
|
Compact conversation history.
|
|
@@ -36,7 +36,10 @@ _UPDATED_LINE = re.compile(r"^> Updated: (.+)$", re.MULTILINE)
|
|
|
36
36
|
class MemoryStorage:
|
|
37
37
|
"""Markdown-backed key-value memory with daily logs."""
|
|
38
38
|
|
|
39
|
-
def __init__(self, memory_dir: str =
|
|
39
|
+
def __init__(self, memory_dir: str | None = None) -> None:
|
|
40
|
+
if memory_dir is None:
|
|
41
|
+
from ... import config as _cfg
|
|
42
|
+
memory_dir = os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "memory")
|
|
40
43
|
self.memory_dir = memory_dir
|
|
41
44
|
os.makedirs(memory_dir, exist_ok=True)
|
|
42
45
|
self._memory_file = os.path.join(memory_dir, "MEMORY.md")
|
|
@@ -37,7 +37,12 @@ from datetime import datetime
|
|
|
37
37
|
|
|
38
38
|
logger = logging.getLogger(__name__)
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
def _default_store_dir() -> str:
|
|
41
|
+
from .. import config as _cfg
|
|
42
|
+
return os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "sessions")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
DEFAULT_STORE_DIR = None # resolved lazily
|
|
41
46
|
DEFAULT_MAX_MESSAGES = 200
|
|
42
47
|
|
|
43
48
|
_META_PATTERN = re.compile(r"<!-- msg:(.*?) -->")
|
|
@@ -54,10 +59,10 @@ class SessionStore:
|
|
|
54
59
|
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
|
-
base_dir: str =
|
|
62
|
+
base_dir: str | None = None,
|
|
58
63
|
max_messages: int = DEFAULT_MAX_MESSAGES,
|
|
59
64
|
) -> None:
|
|
60
|
-
self.base_dir = base_dir
|
|
65
|
+
self.base_dir = base_dir or _default_store_dir()
|
|
61
66
|
self.max_messages = max_messages
|
|
62
67
|
os.makedirs(base_dir, exist_ok=True)
|
|
63
68
|
|
|
@@ -213,7 +213,8 @@ def install_skill(
|
|
|
213
213
|
Returns the path to the installed skill directory.
|
|
214
214
|
"""
|
|
215
215
|
if target_dir is None:
|
|
216
|
-
|
|
216
|
+
from .. import config as _cfg
|
|
217
|
+
target_dir = os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "skills")
|
|
217
218
|
|
|
218
219
|
detail = None
|
|
219
220
|
if not skill_md_override:
|
|
@@ -460,7 +460,8 @@ def create_skill(
|
|
|
460
460
|
All paths are validated against the sandbox. Resource filenames are
|
|
461
461
|
sanitized to prevent directory traversal.
|
|
462
462
|
"""
|
|
463
|
-
|
|
463
|
+
from .. import config as _cfg
|
|
464
|
+
skills_dir = os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "skills")
|
|
464
465
|
_resolve_in_sandbox(skills_dir)
|
|
465
466
|
os.makedirs(skills_dir, exist_ok=True)
|
|
466
467
|
|
|
@@ -62,12 +62,15 @@ def start_daemon(
|
|
|
62
62
|
log_handle.write(f"{'='*60}\n")
|
|
63
63
|
log_handle.flush()
|
|
64
64
|
|
|
65
|
+
home = str(config.PYTHONCLAW_HOME)
|
|
66
|
+
os.makedirs(home, exist_ok=True)
|
|
67
|
+
|
|
65
68
|
proc = subprocess.Popen(
|
|
66
69
|
cmd,
|
|
67
70
|
stdout=log_handle,
|
|
68
71
|
stderr=subprocess.STDOUT,
|
|
69
72
|
start_new_session=True,
|
|
70
|
-
cwd=
|
|
73
|
+
cwd=home,
|
|
71
74
|
)
|
|
72
75
|
|
|
73
76
|
_write_pid(proc.pid)
|
|
@@ -76,7 +79,7 @@ def start_daemon(
|
|
|
76
79
|
_write_meta({
|
|
77
80
|
"pid": proc.pid,
|
|
78
81
|
"port": port,
|
|
79
|
-
"cwd":
|
|
82
|
+
"cwd": home,
|
|
80
83
|
"started_at": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
81
84
|
"channels": channels or [],
|
|
82
85
|
"config_path": config_path,
|
|
@@ -19,14 +19,19 @@ import os
|
|
|
19
19
|
import shutil
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def init(project_path: str =
|
|
22
|
+
def init(project_path: str | None = None) -> None:
|
|
23
23
|
"""
|
|
24
|
-
Initialise a new PythonClaw project
|
|
24
|
+
Initialise a new PythonClaw project.
|
|
25
25
|
|
|
26
|
-
Copies template files into
|
|
26
|
+
Copies template files into ``<project_path>/context/`` only for
|
|
27
27
|
directories that do not already exist (safe to re-run).
|
|
28
|
+
Defaults to ``~/.pythonclaw``.
|
|
28
29
|
"""
|
|
29
|
-
|
|
30
|
+
if project_path is None:
|
|
31
|
+
from . import config
|
|
32
|
+
application_dir = str(config.PYTHONCLAW_HOME)
|
|
33
|
+
else:
|
|
34
|
+
application_dir = os.path.abspath(project_path)
|
|
30
35
|
context_dir = os.path.join(application_dir, "context")
|
|
31
36
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
|
32
37
|
templates_dir = os.path.join(pkg_dir, "templates")
|
|
@@ -300,11 +300,12 @@ def _validate_key(cfg: dict, provider: dict) -> None:
|
|
|
300
300
|
|
|
301
301
|
|
|
302
302
|
def _save_config(cfg: dict, config_path: str | None) -> Path:
|
|
303
|
-
"""Write config to disk."""
|
|
303
|
+
"""Write config to disk (defaults to ~/.pythonclaw/pythonclaw.json)."""
|
|
304
304
|
if config_path:
|
|
305
305
|
out = Path(config_path)
|
|
306
306
|
else:
|
|
307
|
-
out =
|
|
307
|
+
out = config.PYTHONCLAW_HOME / "pythonclaw.json"
|
|
308
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
308
309
|
|
|
309
310
|
# Ensure default sections exist
|
|
310
311
|
cfg.setdefault("channels", {"telegram": {"token": "", "allowedUsers": []}, "discord": {"token": "", "allowedUsers": [], "allowedChannels": []}})
|
|
@@ -52,9 +52,17 @@ if TYPE_CHECKING:
|
|
|
52
52
|
|
|
53
53
|
logger = logging.getLogger(__name__)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
def _cron_dir() -> str:
|
|
56
|
+
from .. import config as _cfg
|
|
57
|
+
return os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "cron")
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
|
|
60
|
+
def _dynamic_jobs_file() -> str:
|
|
61
|
+
return os.path.join(_cron_dir(), "dynamic_jobs.json")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _default_jobs_path() -> str:
|
|
65
|
+
return os.path.join(_cron_dir(), "jobs.yaml")
|
|
58
66
|
|
|
59
67
|
|
|
60
68
|
class CronScheduler:
|
|
@@ -68,11 +76,11 @@ class CronScheduler:
|
|
|
68
76
|
def __init__(
|
|
69
77
|
self,
|
|
70
78
|
session_manager: "SessionManager",
|
|
71
|
-
jobs_path: str =
|
|
79
|
+
jobs_path: str | None = None,
|
|
72
80
|
telegram_bot: "TelegramBot | None" = None,
|
|
73
81
|
) -> None:
|
|
74
82
|
self._sm = session_manager
|
|
75
|
-
self._jobs_path = jobs_path
|
|
83
|
+
self._jobs_path = jobs_path or _default_jobs_path()
|
|
76
84
|
self._telegram_bot = telegram_bot
|
|
77
85
|
self._scheduler = AsyncIOScheduler()
|
|
78
86
|
|
|
@@ -186,18 +194,20 @@ class CronScheduler:
|
|
|
186
194
|
|
|
187
195
|
def _load_dynamic_jobs(self) -> dict[str, dict]:
|
|
188
196
|
"""Load persisted dynamic jobs from JSON. Returns {job_id: job_dict}."""
|
|
189
|
-
|
|
197
|
+
djf = _dynamic_jobs_file()
|
|
198
|
+
if not os.path.exists(djf):
|
|
190
199
|
return {}
|
|
191
200
|
try:
|
|
192
|
-
with open(
|
|
201
|
+
with open(djf, "r", encoding="utf-8") as f:
|
|
193
202
|
return json.load(f)
|
|
194
203
|
except (OSError, json.JSONDecodeError) as exc:
|
|
195
204
|
logger.error("[CronScheduler] Failed to load dynamic jobs: %s", exc)
|
|
196
205
|
return {}
|
|
197
206
|
|
|
198
207
|
def _save_dynamic_jobs(self, jobs: dict[str, dict]) -> None:
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
djf = _dynamic_jobs_file()
|
|
209
|
+
os.makedirs(os.path.dirname(djf), exist_ok=True)
|
|
210
|
+
with open(djf, "w", encoding="utf-8") as f:
|
|
201
211
|
json.dump(jobs, f, indent=2, ensure_ascii=False)
|
|
202
212
|
|
|
203
213
|
def _register_dynamic_jobs(self) -> int:
|
|
@@ -33,8 +33,13 @@ if TYPE_CHECKING:
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
|
|
35
35
|
DEFAULT_INTERVAL = 60
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
def _log_dir() -> str:
|
|
37
|
+
from .. import config as _cfg
|
|
38
|
+
return os.path.join(str(_cfg.PYTHONCLAW_HOME), "context", "logs")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _log_file() -> str:
|
|
42
|
+
return os.path.join(_log_dir(), "heartbeat.log")
|
|
38
43
|
|
|
39
44
|
# Minimal probe message sent to the LLM
|
|
40
45
|
_PROBE_MESSAGES = [{"role": "user", "content": "ping"}]
|
|
@@ -49,12 +54,17 @@ class HeartbeatMonitor:
|
|
|
49
54
|
interval_sec: int = DEFAULT_INTERVAL,
|
|
50
55
|
telegram_bot: "TelegramBot | None" = None,
|
|
51
56
|
alert_chat_id: int | None = None,
|
|
52
|
-
log_path: str =
|
|
57
|
+
log_path: str | None = None,
|
|
53
58
|
) -> None:
|
|
54
59
|
self._provider = provider
|
|
55
60
|
self._interval = interval_sec
|
|
56
61
|
self._telegram_bot = telegram_bot
|
|
57
62
|
self._alert_chat_id = alert_chat_id
|
|
63
|
+
if log_path is None:
|
|
64
|
+
try:
|
|
65
|
+
log_path = _log_file()
|
|
66
|
+
except Exception:
|
|
67
|
+
log_path = os.path.join(os.path.expanduser("~/.pythonclaw"), "context", "logs", "heartbeat.log")
|
|
58
68
|
self._log_path = log_path
|
|
59
69
|
self._running = False
|
|
60
70
|
self._task: asyncio.Task | None = None
|
|
@@ -9,7 +9,6 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import logging
|
|
12
|
-
import os
|
|
13
12
|
import signal
|
|
14
13
|
|
|
15
14
|
from .core.llm.base import LLMProvider
|
|
@@ -33,10 +32,8 @@ async def start_channels(
|
|
|
33
32
|
store = SessionStore()
|
|
34
33
|
session_manager = SessionManager(agent_factory=lambda sid: None, store=store)
|
|
35
34
|
|
|
36
|
-
jobs_path = os.path.join("context", "cron", "jobs.yaml")
|
|
37
35
|
scheduler = CronScheduler(
|
|
38
36
|
session_manager=session_manager,
|
|
39
|
-
jobs_path=jobs_path,
|
|
40
37
|
)
|
|
41
38
|
|
|
42
39
|
def agent_factory(session_id: str) -> PersistentAgent:
|
|
@@ -7,7 +7,13 @@ import os
|
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
def _config_file() -> str:
|
|
11
|
+
home = os.path.expanduser("~/.pythonclaw")
|
|
12
|
+
for p in [os.path.join(home, "pythonclaw.json"), "pythonclaw.json"]:
|
|
13
|
+
if os.path.exists(p):
|
|
14
|
+
return p
|
|
15
|
+
return os.path.join(home, "pythonclaw.json")
|
|
16
|
+
|
|
11
17
|
|
|
12
18
|
SENSITIVE_PATTERNS = re.compile(
|
|
13
19
|
r"(apikey|api_key|token|password|secret)", re.IGNORECASE
|
|
@@ -15,15 +21,16 @@ SENSITIVE_PATTERNS = re.compile(
|
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
def _load_config() -> dict:
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
cfg_file = _config_file()
|
|
25
|
+
if not os.path.exists(cfg_file):
|
|
26
|
+
print(f"Error: {cfg_file} not found", file=sys.stderr)
|
|
20
27
|
sys.exit(1)
|
|
21
|
-
with open(
|
|
28
|
+
with open(cfg_file, "r", encoding="utf-8") as f:
|
|
22
29
|
return json.load(f)
|
|
23
30
|
|
|
24
31
|
|
|
25
32
|
def _save_config(cfg: dict) -> None:
|
|
26
|
-
with open(
|
|
33
|
+
with open(_config_file(), "w", encoding="utf-8") as f:
|
|
27
34
|
json.dump(cfg, f, indent=2, ensure_ascii=False)
|
|
28
35
|
f.write("\n")
|
|
29
36
|
|
|
@@ -174,7 +174,8 @@ def write_soul(user_name: str, personality: str, focus: str, language: str) -> s
|
|
|
174
174
|
language=language,
|
|
175
175
|
personality_description=_personality_description(personality),
|
|
176
176
|
)
|
|
177
|
-
|
|
177
|
+
home = os.path.expanduser("~/.pythonclaw")
|
|
178
|
+
path = os.path.join(home, "context", "soul", "SOUL.md")
|
|
178
179
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
179
180
|
with open(path, "w", encoding="utf-8") as f:
|
|
180
181
|
f.write(content.strip() + "\n")
|
|
@@ -190,7 +191,8 @@ def write_persona(user_name: str, personality: str, focus: str, language: str) -
|
|
|
190
191
|
style_notes=_style_notes(personality),
|
|
191
192
|
focus_guidelines=_focus_guidelines(focus),
|
|
192
193
|
)
|
|
193
|
-
|
|
194
|
+
home = os.path.expanduser("~/.pythonclaw")
|
|
195
|
+
path = os.path.join(home, "context", "persona", "persona.md")
|
|
194
196
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
195
197
|
with open(path, "w", encoding="utf-8") as f:
|
|
196
198
|
f.write(content.strip() + "\n")
|
|
@@ -209,7 +209,7 @@ async def _api_config_save(request: Request):
|
|
|
209
209
|
|
|
210
210
|
cfg_path = config.config_path()
|
|
211
211
|
if cfg_path is None:
|
|
212
|
-
cfg_path =
|
|
212
|
+
cfg_path = config.PYTHONCLAW_HOME / "pythonclaw.json"
|
|
213
213
|
|
|
214
214
|
try:
|
|
215
215
|
json_text = json.dumps(new_config, indent=2, ensure_ascii=False)
|
|
@@ -247,7 +247,7 @@ async def _api_skills():
|
|
|
247
247
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
248
248
|
"templates", "skills",
|
|
249
249
|
)
|
|
250
|
-
skills_dirs = [pkg_templates, os.path.join("context", "skills")]
|
|
250
|
+
skills_dirs = [pkg_templates, os.path.join(str(config.PYTHONCLAW_HOME), "context", "skills")]
|
|
251
251
|
skills_dirs = [d for d in skills_dirs if os.path.isdir(d)]
|
|
252
252
|
registry = SkillRegistry(skills_dirs=skills_dirs)
|
|
253
253
|
skills_meta = registry.discover()
|
|
@@ -331,9 +331,9 @@ async def _api_identity():
|
|
|
331
331
|
return f.read_text(encoding="utf-8").strip()
|
|
332
332
|
return None
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
soul = _read_md(str(
|
|
336
|
-
persona = _read_md(str(
|
|
334
|
+
home = config.PYTHONCLAW_HOME
|
|
335
|
+
soul = _read_md(str(home / "context" / "soul"))
|
|
336
|
+
persona = _read_md(str(home / "context" / "persona"))
|
|
337
337
|
|
|
338
338
|
def _tool_info(schema: dict) -> dict:
|
|
339
339
|
fn = schema.get("function", {})
|
|
@@ -373,7 +373,7 @@ async def _api_save_soul(request: Request):
|
|
|
373
373
|
if not content:
|
|
374
374
|
return JSONResponse({"ok": False, "error": "Content cannot be empty."}, status_code=400)
|
|
375
375
|
|
|
376
|
-
soul_dir =
|
|
376
|
+
soul_dir = config.PYTHONCLAW_HOME / "context" / "soul"
|
|
377
377
|
soul_dir.mkdir(parents=True, exist_ok=True)
|
|
378
378
|
soul_file = soul_dir / "SOUL.md"
|
|
379
379
|
soul_file.write_text(content + "\n", encoding="utf-8")
|
|
@@ -393,7 +393,7 @@ async def _api_save_persona(request: Request):
|
|
|
393
393
|
if not content:
|
|
394
394
|
return JSONResponse({"ok": False, "error": "Content cannot be empty."}, status_code=400)
|
|
395
395
|
|
|
396
|
-
persona_dir =
|
|
396
|
+
persona_dir = config.PYTHONCLAW_HOME / "context" / "persona"
|
|
397
397
|
persona_dir.mkdir(parents=True, exist_ok=True)
|
|
398
398
|
persona_file = persona_dir / "persona.md"
|
|
399
399
|
persona_file.write_text(content + "\n", encoding="utf-8")
|
|
@@ -598,12 +598,12 @@ def _reload_agent_identity() -> None:
|
|
|
598
598
|
if _agent is None:
|
|
599
599
|
return
|
|
600
600
|
from ..core.agent import _load_text_dir_or_file
|
|
601
|
-
|
|
601
|
+
home = config.PYTHONCLAW_HOME
|
|
602
602
|
_agent.soul_instruction = _load_text_dir_or_file(
|
|
603
|
-
str(
|
|
603
|
+
str(home / "context" / "soul"), label="Soul"
|
|
604
604
|
)
|
|
605
605
|
_agent.persona_instruction = _load_text_dir_or_file(
|
|
606
|
-
str(
|
|
606
|
+
str(home / "context" / "persona"), label="Persona"
|
|
607
607
|
)
|
|
608
608
|
_agent._needs_onboarding = False
|
|
609
609
|
_agent._init_system_prompt()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonclaw
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support.
|
|
5
5
|
Author-email: Eric Wang <wangchen2007915@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -23,6 +23,8 @@ Requires-Python: >=3.10
|
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: openai
|
|
26
|
+
Requires-Dist: anthropic
|
|
27
|
+
Requires-Dist: google-generativeai
|
|
26
28
|
Requires-Dist: python-telegram-bot[job-queue]>=20.0
|
|
27
29
|
Requires-Dist: discord.py>=2.3
|
|
28
30
|
Requires-Dist: apscheduler>=3.10
|
|
@@ -35,13 +37,6 @@ Requires-Dist: numpy>=1.24
|
|
|
35
37
|
Requires-Dist: scikit-learn>=1.3
|
|
36
38
|
Requires-Dist: beautifulsoup4
|
|
37
39
|
Requires-Dist: requests
|
|
38
|
-
Provides-Extra: all
|
|
39
|
-
Requires-Dist: anthropic; extra == "all"
|
|
40
|
-
Requires-Dist: google-generativeai; extra == "all"
|
|
41
|
-
Provides-Extra: anthropic
|
|
42
|
-
Requires-Dist: anthropic; extra == "anthropic"
|
|
43
|
-
Provides-Extra: gemini
|
|
44
|
-
Requires-Dist: google-generativeai; extra == "gemini"
|
|
45
40
|
Dynamic: license-file
|
|
46
41
|
|
|
47
42
|
<p align="center">
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
openai
|
|
2
|
+
anthropic
|
|
3
|
+
google-generativeai
|
|
2
4
|
python-telegram-bot[job-queue]>=20.0
|
|
3
5
|
discord.py>=2.3
|
|
4
6
|
apscheduler>=3.10
|
|
@@ -11,13 +13,3 @@ numpy>=1.24
|
|
|
11
13
|
scikit-learn>=1.3
|
|
12
14
|
beautifulsoup4
|
|
13
15
|
requests
|
|
14
|
-
|
|
15
|
-
[all]
|
|
16
|
-
anthropic
|
|
17
|
-
google-generativeai
|
|
18
|
-
|
|
19
|
-
[anthropic]
|
|
20
|
-
anthropic
|
|
21
|
-
|
|
22
|
-
[gemini]
|
|
23
|
-
google-generativeai
|
|
@@ -219,7 +219,7 @@ class TestAgentCronTools:
|
|
|
219
219
|
assert "scheduled" in result.lower()
|
|
220
220
|
|
|
221
221
|
def test_cron_add_persisted_to_json(self, tmp_path):
|
|
222
|
-
from pythonclaw.scheduler.cron import DYNAMIC_JOBS_FILE
|
|
222
|
+
from pythonclaw.scheduler.cron import _dynamic_jobs_file as _djf; DYNAMIC_JOBS_FILE = _djf()
|
|
223
223
|
cron_mgr = self._make_cron_manager(tmp_path)
|
|
224
224
|
cron_mgr.add_dynamic_job("persist_job", "0 8 * * *", "Daily check")
|
|
225
225
|
assert os.path.exists(DYNAMIC_JOBS_FILE)
|
|
@@ -235,7 +235,7 @@ class TestAgentCronTools:
|
|
|
235
235
|
assert "removed" in result.lower()
|
|
236
236
|
|
|
237
237
|
# Should no longer be in JSON
|
|
238
|
-
from pythonclaw.scheduler.cron import DYNAMIC_JOBS_FILE
|
|
238
|
+
from pythonclaw.scheduler.cron import _dynamic_jobs_file as _djf; DYNAMIC_JOBS_FILE = _djf()
|
|
239
239
|
with open(DYNAMIC_JOBS_FILE) as f:
|
|
240
240
|
data = json.load(f)
|
|
241
241
|
assert "rm_job" not in data
|
|
@@ -269,7 +269,7 @@ class TestAgentCronTools:
|
|
|
269
269
|
c._scheduler.get_jobs.return_value = [mock_job]
|
|
270
270
|
|
|
271
271
|
# Fake the dynamic_jobs.json
|
|
272
|
-
from pythonclaw.scheduler.cron import DYNAMIC_JOBS_FILE
|
|
272
|
+
from pythonclaw.scheduler.cron import _dynamic_jobs_file as _djf; DYNAMIC_JOBS_FILE = _djf()
|
|
273
273
|
os.makedirs(os.path.dirname(DYNAMIC_JOBS_FILE), exist_ok=True)
|
|
274
274
|
with open(DYNAMIC_JOBS_FILE, "w") as f:
|
|
275
275
|
json.dump({"my_dynamic_job": {"cron": "0 9 * * *", "prompt": "hi"}}, f)
|
|
@@ -279,7 +279,7 @@ class TestAgentCronTools:
|
|
|
279
279
|
assert "[dynamic]" in result
|
|
280
280
|
|
|
281
281
|
def test_load_dynamic_jobs_from_json(self, tmp_path):
|
|
282
|
-
from pythonclaw.scheduler.cron import DYNAMIC_JOBS_FILE
|
|
282
|
+
from pythonclaw.scheduler.cron import _dynamic_jobs_file as _djf; DYNAMIC_JOBS_FILE = _djf()
|
|
283
283
|
os.makedirs(os.path.dirname(DYNAMIC_JOBS_FILE), exist_ok=True)
|
|
284
284
|
with open(DYNAMIC_JOBS_FILE, "w") as f:
|
|
285
285
|
json.dump({
|
|
@@ -292,6 +292,6 @@ class TestAgentCronTools:
|
|
|
292
292
|
assert jobs["pre_existing"]["cron"] == "0 7 * * *"
|
|
293
293
|
|
|
294
294
|
def teardown_method(self, method):
|
|
295
|
-
from pythonclaw.scheduler.cron import DYNAMIC_JOBS_FILE
|
|
295
|
+
from pythonclaw.scheduler.cron import _dynamic_jobs_file as _djf; DYNAMIC_JOBS_FILE = _djf()
|
|
296
296
|
if os.path.exists(DYNAMIC_JOBS_FILE):
|
|
297
297
|
os.remove(DYNAMIC_JOBS_FILE)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/communication/email/SKILL.md
RENAMED
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/communication/email/send_email.py
RENAMED
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md
RENAMED
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/csv_analyzer/analyze.py
RENAMED
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/finance/fetch_quote.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/pdf_reader/read_pdf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/data/youtube/youtube_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/code_runner/run_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/dev/http_request/request.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/meta/skill_creator/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_persona/SKILL.md
RENAMED
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_setting/SKILL.md
RENAMED
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/change_soul/SKILL.md
RENAMED
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/onboarding/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/system/random/random_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pythonclaw-0.3.0 → pythonclaw-0.3.2}/pythonclaw/templates/skills/text/translator/translate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|