Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ def _strip_yaml_frontmatter(content: str) -> str:
"- File contents, sizes, line counts → use read_file, search_files, or terminal\n"
"- Git history, branches, diffs → use terminal\n"
"- Current facts (weather, news, versions) → use web_search\n"
"- Image generation requests (draw, generate an image, make a poster/logo/illustration) → use image_generate\n"
"Your memory and user profile describe the USER, not the system you are "
"running on. The execution environment may differ from what the user profile "
"says about their personal setup.\n"
Expand Down Expand Up @@ -799,6 +800,7 @@ def build_skills_system_prompt(
"Skills also encode the user's preferred approach, conventions, and quality standards "
"for tasks like code review, planning, and testing — load them even for tasks you "
"already know how to do, because the skill defines how it should be done here.\n"
"When the user asks to draw, generate, create, render, illustrate, or make an image, poster, logo, icon, wallpaper, cover, or artwork, use the image_generate tool instead of replying with text-only instructions.\n"
"If a skill has issues, fix it with skill_manage(action='patch').\n"
"After difficult/iterative tasks, offer to save as a skill. "
"If a skill you loaded was missing steps, had wrong commands, or needed "
Expand Down
12 changes: 11 additions & 1 deletion cron/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,9 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> Option
# rooms (e.g. Matrix) where the standalone HTTP path cannot encrypt.
runtime_adapter = (adapters or {}).get(platform)
delivered = False
if runtime_adapter is not None and loop is not None and getattr(loop, "is_running", lambda: False)():
loop_running = bool(loop is not None and getattr(loop, "is_running", lambda: False)())
loop_closed = bool(loop is not None and getattr(loop, "is_closed", lambda: False)())
if runtime_adapter is not None and loop_running and not loop_closed:
send_metadata = {"thread_id": thread_id} if thread_id else None
try:
# Send cleaned text (MEDIA tags stripped) — not the raw content
Expand Down Expand Up @@ -420,6 +422,14 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> Option
delivery_errors.append(msg)
continue

if sys.is_finalizing():
msg = (
f"delivery to {platform_name}:{chat_id} skipped during interpreter shutdown"
)
logger.warning("Job '%s': %s", job["id"], msg)
delivery_errors.append(msg)
continue

# Standalone path: run the async send in a fresh event loop (safe from any thread)
coro = _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files)
try:
Expand Down
27 changes: 23 additions & 4 deletions gateway/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,33 @@ def write_pid_file() -> None:
Uses atomic O_CREAT | O_EXCL creation so that concurrent --replace
invocations race: exactly one process wins and the rest get
FileExistsError.

If the file already exists but only contains stale/invalid state, clean it up
and retry once. This closes the gap where a previous crashed gateway left a
dead PID record behind: start_gateway() may decide the old PID is stale, but
another concurrent observer can recreate or preserve the stale file before we
reach the atomic create below.
"""
path = _get_pid_path()
path.parent.mkdir(parents=True, exist_ok=True)
record = json.dumps(_build_pid_record())
try:
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except FileExistsError:
raise # Let caller decide: another gateway is racing us

for _attempt in range(2):
try:
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
break
except FileExistsError:
existing_pid = get_running_pid(path, cleanup_stale=True)
if existing_pid is None:
try:
path.unlink(missing_ok=True)
except OSError:
pass
continue
raise # Let caller decide: another live gateway is racing us
else:
raise FileExistsError(path)

try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(record)
Expand Down
Loading