pythonclaw 0.6.2__py3-none-any.whl → 0.6.4__py3-none-any.whl
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/channels/telegram_bot.py +18 -76
- pythonclaw/core/agent.py +17 -8
- pythonclaw/core/llm/anthropic_client.py +1 -1
- pythonclaw/core/llm/gemini_client.py +1 -1
- pythonclaw/core/llm/openai_compatible.py +1 -1
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/METADATA +1 -1
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/RECORD +11 -11
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/WHEEL +0 -0
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/entry_points.txt +0 -0
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/licenses/LICENSE +0 -0
- {pythonclaw-0.6.2.dist-info → pythonclaw-0.6.4.dist-info}/top_level.txt +0 -0
|
@@ -244,8 +244,7 @@ class TelegramBot:
|
|
|
244
244
|
except Exception:
|
|
245
245
|
pass
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
_AGENT_TIMEOUT = 180
|
|
247
|
+
_AGENT_TIMEOUT = 600
|
|
249
248
|
|
|
250
249
|
async def _flush_stream(
|
|
251
250
|
self,
|
|
@@ -253,105 +252,47 @@ class TelegramBot:
|
|
|
253
252
|
token_queue: "_queue.Queue[str]",
|
|
254
253
|
future: "asyncio.Future[str]",
|
|
255
254
|
) -> None:
|
|
256
|
-
"""
|
|
255
|
+
"""Collect streamed tokens and deliver as 2-3 large messages.
|
|
257
256
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
Strategy: accumulate all tokens silently. Tool-call markers are
|
|
258
|
+
stripped but do NOT trigger new messages. Content is edit-in-place
|
|
259
|
+
updated into a single live message; only when a message hits the
|
|
260
|
+
Telegram 4096 char limit is a new message started.
|
|
261
261
|
|
|
262
|
-
|
|
263
|
-
- **Heartbeat**: if no tokens arrive for 15 s, sends a "still
|
|
264
|
-
working" notification so the user knows the bot is alive.
|
|
265
|
-
- **Overall timeout**: after ``_AGENT_TIMEOUT`` seconds the
|
|
266
|
-
future is abandoned and a timeout message is sent.
|
|
262
|
+
No heartbeat / "still working" messages are sent.
|
|
267
263
|
"""
|
|
268
264
|
buf: list[str] = []
|
|
269
265
|
live_msg = None
|
|
270
266
|
live_text = ""
|
|
271
267
|
sent_any = False
|
|
272
|
-
THROTTLE =
|
|
273
|
-
HEARTBEAT_INTERVAL = 15.0
|
|
268
|
+
THROTTLE = 2.0
|
|
274
269
|
last_edit = time.monotonic()
|
|
275
|
-
last_token_time = time.monotonic()
|
|
276
270
|
start_time = time.monotonic()
|
|
277
|
-
heartbeat_sent = False
|
|
278
271
|
_MARKER = re.compile(r'`\[calling:\s*([^\]]+)\]`')
|
|
279
272
|
|
|
280
273
|
while not future.done():
|
|
281
|
-
# ── Overall timeout guard ─────────────────────────────────
|
|
282
274
|
if (time.monotonic() - start_time) > self._AGENT_TIMEOUT:
|
|
283
275
|
logger.warning(
|
|
284
276
|
"[Telegram] Agent timeout after %ds", self._AGENT_TIMEOUT,
|
|
285
277
|
)
|
|
286
|
-
|
|
287
|
-
await update.message.reply_text(
|
|
288
|
-
"\u23f0 The operation timed out. "
|
|
289
|
-
"Please try a simpler request."
|
|
290
|
-
)
|
|
291
|
-
except Exception:
|
|
292
|
-
pass
|
|
293
|
-
return
|
|
278
|
+
break
|
|
294
279
|
|
|
295
|
-
# ── Drain token queue ─────────────────────────────────────
|
|
296
280
|
drained = False
|
|
297
281
|
while True:
|
|
298
282
|
try:
|
|
299
283
|
buf.append(token_queue.get_nowait())
|
|
300
284
|
drained = True
|
|
301
|
-
last_token_time = time.monotonic()
|
|
302
|
-
heartbeat_sent = False
|
|
303
285
|
except _queue.Empty:
|
|
304
286
|
break
|
|
305
287
|
|
|
306
|
-
# ── Heartbeat: notify user during long silences ───────────
|
|
307
|
-
if (
|
|
308
|
-
not drained
|
|
309
|
-
and not heartbeat_sent
|
|
310
|
-
and (time.monotonic() - last_token_time) > HEARTBEAT_INTERVAL
|
|
311
|
-
):
|
|
312
|
-
try:
|
|
313
|
-
await update.message.reply_text(
|
|
314
|
-
"\u23f3 Still working\u2026"
|
|
315
|
-
)
|
|
316
|
-
except Exception:
|
|
317
|
-
pass
|
|
318
|
-
heartbeat_sent = True
|
|
319
|
-
|
|
320
288
|
if not drained:
|
|
321
|
-
await asyncio.sleep(0.
|
|
289
|
+
await asyncio.sleep(0.4)
|
|
322
290
|
continue
|
|
323
291
|
|
|
324
|
-
raw = "".join(buf)
|
|
292
|
+
raw = _MARKER.sub("", "".join(buf))
|
|
293
|
+
text = _clean_response(raw)
|
|
325
294
|
now = time.monotonic()
|
|
326
295
|
|
|
327
|
-
# ── Tool-call marker → status line + new message ──────────
|
|
328
|
-
marker = _MARKER.search(raw)
|
|
329
|
-
if marker:
|
|
330
|
-
before = _clean_response(raw[:marker.start()])
|
|
331
|
-
if before and before != live_text:
|
|
332
|
-
try:
|
|
333
|
-
if live_msg:
|
|
334
|
-
await live_msg.edit_text(before[:4096])
|
|
335
|
-
else:
|
|
336
|
-
await update.message.reply_text(before[:4096])
|
|
337
|
-
except Exception:
|
|
338
|
-
pass
|
|
339
|
-
live_msg = None
|
|
340
|
-
live_text = ""
|
|
341
|
-
tools = marker.group(1)
|
|
342
|
-
try:
|
|
343
|
-
await update.message.reply_text(
|
|
344
|
-
f"\U0001f527 {tools}\u2026"
|
|
345
|
-
)
|
|
346
|
-
except Exception:
|
|
347
|
-
pass
|
|
348
|
-
sent_any = True
|
|
349
|
-
buf = [raw[marker.end():].lstrip()]
|
|
350
|
-
last_edit = now
|
|
351
|
-
continue
|
|
352
|
-
|
|
353
|
-
# ── Regular text → edit-in-place ──────────────────────────
|
|
354
|
-
text = _clean_response(raw)
|
|
355
296
|
if text and text != live_text and (now - last_edit) >= THROTTLE:
|
|
356
297
|
try:
|
|
357
298
|
if live_msg is None:
|
|
@@ -363,26 +304,27 @@ class TelegramBot:
|
|
|
363
304
|
await live_msg.edit_text(text)
|
|
364
305
|
live_text = text
|
|
365
306
|
else:
|
|
366
|
-
await live_msg.edit_text(
|
|
307
|
+
await live_msg.edit_text(live_text)
|
|
367
308
|
live_msg = None
|
|
368
309
|
live_text = ""
|
|
369
|
-
buf = [text[
|
|
310
|
+
buf = [text[len(live_text):] if live_text else text]
|
|
370
311
|
sent_any = True
|
|
371
312
|
except Exception:
|
|
372
313
|
pass
|
|
373
314
|
last_edit = now
|
|
374
315
|
|
|
375
|
-
await asyncio.sleep(0.
|
|
316
|
+
await asyncio.sleep(0.4)
|
|
376
317
|
|
|
377
318
|
# ── Final drain ───────────────────────────────────────────────
|
|
378
|
-
response = future.result()
|
|
319
|
+
response = future.result() if future.done() else "(timed out)"
|
|
379
320
|
while True:
|
|
380
321
|
try:
|
|
381
322
|
buf.append(token_queue.get_nowait())
|
|
382
323
|
except _queue.Empty:
|
|
383
324
|
break
|
|
384
325
|
|
|
385
|
-
|
|
326
|
+
raw = _MARKER.sub("", "".join(buf))
|
|
327
|
+
remaining = _clean_response(raw.strip())
|
|
386
328
|
if remaining and remaining != live_text:
|
|
387
329
|
try:
|
|
388
330
|
if live_msg and len(remaining) <= 4096:
|
pythonclaw/core/agent.py
CHANGED
|
@@ -120,9 +120,9 @@ class Agent:
|
|
|
120
120
|
cron_manager : CronScheduler instance (enables cron_add/remove/list tools)
|
|
121
121
|
"""
|
|
122
122
|
|
|
123
|
-
MAX_TOOL_ROUNDS =
|
|
123
|
+
MAX_TOOL_ROUNDS = 12
|
|
124
124
|
MAX_PARALLEL_SKILLS = 5
|
|
125
|
-
TOOL_TIMEOUT =
|
|
125
|
+
TOOL_TIMEOUT = 300
|
|
126
126
|
|
|
127
127
|
def __init__(
|
|
128
128
|
self,
|
|
@@ -323,23 +323,32 @@ class Agent:
|
|
|
323
323
|
- **Memory**: `remember(key,val)`, `recall(query)`, `memory_get(path)`, `memory_list_files()`, `forget(key)`, `update_index(content)`
|
|
324
324
|
- **Skill creation**: `create_skill` — create generic reusable skills when none fit{web_search_section}
|
|
325
325
|
|
|
326
|
+
### Task Execution Modes
|
|
327
|
+
Choose your approach based on task complexity:
|
|
328
|
+
|
|
329
|
+
**ReAct** (simple tasks, 1-2 steps): Act directly — call tools and respond immediately.
|
|
330
|
+
|
|
331
|
+
**Plan & Execute** (complex tasks, 3+ steps, research, multi-source analysis):
|
|
332
|
+
1. Output a short numbered plan (3-6 steps) as your first response
|
|
333
|
+
2. Execute each step using tools — call multiple tools in parallel when steps are independent (up to {self.MAX_PARALLEL_SKILLS} parallel skills)
|
|
334
|
+
3. After each step, briefly summarize what you found before moving on
|
|
335
|
+
4. After all steps, synthesize a concise final answer
|
|
336
|
+
|
|
337
|
+
You decide which mode fits. Don't announce the mode name.
|
|
338
|
+
|
|
326
339
|
### Rules
|
|
327
|
-
- **Parallel skill execution**: For complex tasks, call multiple `use_skill` in ONE response (up to {self.MAX_PARALLEL_SKILLS} skills). They run in parallel for speed. Example: researching a topic? Activate `news`, `web_search`, and `summarize` simultaneously.
|
|
328
340
|
- Batch independent tool calls in one response (parallel execution).
|
|
329
341
|
- Minimize search rounds (1-3 max). Combine queries. Don't repeat.
|
|
330
342
|
- Proactively `remember` user preferences, decisions, key facts.
|
|
331
343
|
- Use `recall` when user references past context.
|
|
332
344
|
- Memory auto-loaded at session start. INDEX.md = curated system info.
|
|
333
|
-
|
|
334
|
-
Always verify command output.
|
|
345
|
+
- NEVER output tool calls as XML or text. Always use the function calling API.
|
|
335
346
|
|
|
336
347
|
### Response Guidelines
|
|
337
348
|
- Answer the user's question directly and concisely.
|
|
338
|
-
-
|
|
339
|
-
- Keep responses focused and concise — under 300 words when possible. Break long answers into short paragraphs.
|
|
349
|
+
- Keep responses focused — under 300 words when possible. Break long answers into short paragraphs.
|
|
340
350
|
- Do NOT mention what skills or tools you have available, unless explicitly asked.
|
|
341
351
|
- Do NOT list other things you can do at the end of your response.
|
|
342
|
-
- NEVER output tool calls as XML or text. Always use the function calling API.
|
|
343
352
|
"""
|
|
344
353
|
# ── Auto-inject memory context ────────────────────────────────────
|
|
345
354
|
boot_mem = self.memory.boot_context(max_chars=3000)
|
|
@@ -29,7 +29,7 @@ class AnthropicProvider(LLMProvider):
|
|
|
29
29
|
def __init__(self, api_key: str, model_name: str = "claude-sonnet-4-20250514"):
|
|
30
30
|
self.client = anthropic.Anthropic(
|
|
31
31
|
api_key=api_key,
|
|
32
|
-
timeout=
|
|
32
|
+
timeout=300.0,
|
|
33
33
|
)
|
|
34
34
|
self.model_name = model_name
|
|
35
35
|
self._auth_type = (
|
|
@@ -104,7 +104,7 @@ class GeminiProvider(LLMProvider):
|
|
|
104
104
|
response = self.model.generate_content(
|
|
105
105
|
contents=gemini_history,
|
|
106
106
|
tools=gemini_tools,
|
|
107
|
-
request_options={"timeout":
|
|
107
|
+
request_options={"timeout": 300},
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
# Convert to OpenAI-compatible format
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonclaw
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
|
@@ -8,10 +8,10 @@ pythonclaw/onboard.py,sha256=X6ViAduToi9P9J_WdWm9mKQtTb48IwGj5DiqS4jjt0Y,13921
|
|
|
8
8
|
pythonclaw/server.py,sha256=zUV09uNTmzK597swGwt45gdmVxuHPsF7ogVV0DHBIhA,4521
|
|
9
9
|
pythonclaw/session_manager.py,sha256=LKRolNa2i3evb6Ps1zRbajlk4AfvujbR1iPlhfAMBj8,5981
|
|
10
10
|
pythonclaw/channels/discord_bot.py,sha256=95IJcBJlcnOSbsg0LILq6uBYfhdpb-iLLlRw_41Xz7U,11744
|
|
11
|
-
pythonclaw/channels/telegram_bot.py,sha256=
|
|
11
|
+
pythonclaw/channels/telegram_bot.py,sha256=QCwGZlTm6bZIr-E557EEwQktFXvmwvJ4DJJviXgfG00,19610
|
|
12
12
|
pythonclaw/channels/whatsapp_bot.py,sha256=60n6W3ONEIKAdpmI6gCS9RWf5KkLtMUU4J5NJH8vQEY,10650
|
|
13
13
|
pythonclaw/core/__init__.py,sha256=G5LCqUcCIcYYbMv6SreqS-kj8T9n-IvBAhHEG7wDF5w,661
|
|
14
|
-
pythonclaw/core/agent.py,sha256=
|
|
14
|
+
pythonclaw/core/agent.py,sha256=kNXHAZRy0NcL3lktI4E3P7dz_d2ubTrp3bE7HaUYFGk,50160
|
|
15
15
|
pythonclaw/core/compaction.py,sha256=b3zrqwBhPmsmQdfRjrvKK6j0hcfNLpiRrZ8qFxY1h1U,8966
|
|
16
16
|
pythonclaw/core/persistent_agent.py,sha256=nnY1vaZFsn0Wd_MQ27wbG7sRjO1v2ZxbwXjnJGKsBZc,4932
|
|
17
17
|
pythonclaw/core/session_store.py,sha256=V44wMBbMpbDCh1aiCa5lb0_iXrKA_7VrR1rOHGGZYhs,9458
|
|
@@ -20,10 +20,10 @@ pythonclaw/core/skillhub.py,sha256=3MGJ81DFcgcVDCD_Wvf5vHJhcDLZf6IKOhzGzaHkPPQ,1
|
|
|
20
20
|
pythonclaw/core/tools.py,sha256=e3ZZnZ5uZt1bj30IBMJP9ZAwhXUEs-3F_q1tmE2Uk90,23205
|
|
21
21
|
pythonclaw/core/utils.py,sha256=Ih_ZYnulGlxctdyVy4oKknjvkwFS6ZHcdrznIFIAwxo,1919
|
|
22
22
|
pythonclaw/core/knowledge/rag.py,sha256=_6GKs8ZFirMQhOeT-CAJBkwLcPkEz7Og-gWKMfUezDw,2895
|
|
23
|
-
pythonclaw/core/llm/anthropic_client.py,sha256=
|
|
23
|
+
pythonclaw/core/llm/anthropic_client.py,sha256=MrzXK79V5brWnfUdl8HPD3sEZ__8iRgILATb4tSRWes,11797
|
|
24
24
|
pythonclaw/core/llm/base.py,sha256=y1muHBuK14rvzWlXmoSf6ahz6Xi0BojpnDUTRhaD3pI,1683
|
|
25
|
-
pythonclaw/core/llm/gemini_client.py,sha256=
|
|
26
|
-
pythonclaw/core/llm/openai_compatible.py,sha256=
|
|
25
|
+
pythonclaw/core/llm/gemini_client.py,sha256=pCFcxBov7Kp9uVZ_TEgo7MRFsqxyd71lX2A9NqAwGSs,7218
|
|
26
|
+
pythonclaw/core/llm/openai_compatible.py,sha256=PeqIaZNyCKEyqTnHpmzmIn7djoGN2HJUU4E4uRP47Ts,3567
|
|
27
27
|
pythonclaw/core/llm/response.py,sha256=hNCsi0aV1ffXsFuDNnBpRp96cFtVDfX_XEC34QZoykc,1223
|
|
28
28
|
pythonclaw/core/memory/manager.py,sha256=JzNT6CGVRmmIqbOflRzF7HxSfPfI5jLu8tmF6-91ZVA,8945
|
|
29
29
|
pythonclaw/core/memory/storage.py,sha256=mHDN8yCVUZ5srOwYWDNjUhbELXka-X8zSexFWEBUB1M,9119
|
|
@@ -112,9 +112,9 @@ pythonclaw/web/app.py,sha256=uudrxieo5oGwhQUBLzkmn6GU6SnR4VKlRYOg1bFAYQg,32208
|
|
|
112
112
|
pythonclaw/web/static/favicon.png,sha256=zJA13uE8mSe6lOlR5NyAhiOmnZkfv7ZlBbSBNCH7iTM,2557
|
|
113
113
|
pythonclaw/web/static/index.html,sha256=wU4Lw0NcenS0i0HsJS6a6ceFefDxpRsUQnCXVUXYWVU,93734
|
|
114
114
|
pythonclaw/web/static/logo.png,sha256=h7v0HHllD23FtmCL2UvjjTDt0UgqKjGy5jOhI3rb7lM,28359
|
|
115
|
-
pythonclaw-0.6.
|
|
116
|
-
pythonclaw-0.6.
|
|
117
|
-
pythonclaw-0.6.
|
|
118
|
-
pythonclaw-0.6.
|
|
119
|
-
pythonclaw-0.6.
|
|
120
|
-
pythonclaw-0.6.
|
|
115
|
+
pythonclaw-0.6.4.dist-info/licenses/LICENSE,sha256=wbYsm5Ofe8cnxHgWSnSG1vUJDNiY1DIeTyxHSbo1HqM,1066
|
|
116
|
+
pythonclaw-0.6.4.dist-info/METADATA,sha256=cQ3xhWP9WNNTkyUJehSl2sxJuuZUwVdCiwifLnRYhmM,14919
|
|
117
|
+
pythonclaw-0.6.4.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
118
|
+
pythonclaw-0.6.4.dist-info/entry_points.txt,sha256=4uGCuBw-id_22IRdkoxPVOOcwgiPX5lNEpD1XKQWE4I,52
|
|
119
|
+
pythonclaw-0.6.4.dist-info/top_level.txt,sha256=S_lM2VH3gP3UeZbSWHXIrBOCNtoqn5pk491IAzgsV7M,11
|
|
120
|
+
pythonclaw-0.6.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|