Unanswered Messages

v1.0.0

Track and find unanswered messages using a local file-based inbox. No DB required. Use when asked to find unanswered messages, missed messages, people waitin...

0· 11·0 current·0 all-time
byNetanel Abergel@netanel-abergel
MIT-0
Download zip
LicenseMIT-0 · Free to use, modify, and redistribute. No attribution required.
Security Scan
VirusTotalVirusTotal
Benign
View report →
OpenClawOpenClaw
Benign
high confidence
Purpose & Capability
Name/description (track unanswered messages locally) matches the actual requirements and instructions: everything revolves around a single JSON inbox file at /opt/ocana/openclaw/workspace/inbox/pending.json. The requested capabilities and file I/O are proportionate to the stated purpose.
Instruction Scope
Instructions only read/write the specified inbox file and include example Python snippets for logging, marking answered, finding unanswered, cleanup, and a heartbeat check. They're within scope, but the docs assume the file already exists and do not address concurrency/locking, file permissions, or creation/default content; they also instruct adding a heartbeat check and note 'Alert Netanel or handle inline' which implies the operator should review how alerts are delivered.
Install Mechanism
No install spec or code is provided (instruction-only), so nothing is downloaded or written to disk by the skill itself. This is the lowest-risk install model.
Credentials
The skill requests no environment variables, credentials, or config paths beyond the single explicit inbox file path. That is appropriate for a local, file-based inbox implementation.
Persistence & Privilege
always is false and model invocation is normal. The skill only asks to add a heartbeat check and operate on its own inbox file; it does not request system-wide configuration changes or access to other skills' configs.
Assessment
This skill is coherent and lightweight, but review the following before installing: 1) Confirm the inbox path (/opt/ocana/openclaw/workspace/inbox/pending.json) is correct and writable for the agent and that the file is initialized with the expected JSON structure. 2) Add file locking or atomic writes (and backups) to avoid corruption if multiple processes write concurrently. 3) Restrict file permissions to limit access to sensitive message content (PII) and consider encryption at rest if needed. 4) When adding the heartbeat check, verify how alerts are delivered—avoid auto-forwarding message bodies to external endpoints unless explicitly desired and authorized. 5) If you plan to auto-hook into inbound message handlers in the future, audit those integrations and required credentials separately. If any of these points are unclear, ask the maintainer for a small reference implementation (with safe defaults for file creation, locking, and permissions) before deploying.

Like a lobster shell, security has layers — review code before you run it.

latestvk9787qh6egvyej60vveg0ef1j5841zvc

License

MIT-0
Free to use, modify, and redistribute. No attribution required.

SKILL.md

Unanswered Messages (File-Based)

Tracks messages sent to Heleni that haven't been replied to yet. No DB required — uses /opt/ocana/openclaw/workspace/inbox/pending.json.


File Structure

/opt/ocana/openclaw/workspace/inbox/pending.json:

{
  "version": 1,
  "messages": [
    {
      "id": "MSG_ID",
      "ts": "2026-04-01T14:30:00Z",
      "chat_id": "+972XXXXXXXXX",
      "chat_name": "Netanel",
      "chat_type": "direct",
      "sender_name": "Netanel",
      "sender_phone": "+972XXXXXXXXX",
      "body": "message text...",
      "answered": false,
      "answered_at": null
    }
  ]
}

1. Log an Incoming Message

When a message arrives that needs a response, add it to the file:

import json, datetime

INBOX = "/opt/ocana/openclaw/workspace/inbox/pending.json"

with open(INBOX) as f:
    data = json.load(f)

data["messages"].append({
    "id": "<message_id>",
    "ts": datetime.datetime.utcnow().isoformat() + "Z",
    "chat_id": "<chat_id>",
    "chat_name": "<chat_name>",
    "chat_type": "direct",  # or "group"
    "sender_name": "<sender_name>",
    "sender_phone": "<sender_phone>",
    "body": "<message body, max 300 chars>",
    "answered": False,
    "answered_at": None
})

with open(INBOX, "w") as f:
    json.dump(data, f, indent=2)

2. Mark a Message as Answered

After replying, mark it done:

import json, datetime

INBOX = "/opt/ocana/openclaw/workspace/inbox/pending.json"

with open(INBOX) as f:
    data = json.load(f)

for msg in data["messages"]:
    if msg["id"] == "<message_id>":
        msg["answered"] = True
        msg["answered_at"] = datetime.datetime.utcnow().isoformat() + "Z"

with open(INBOX, "w") as f:
    json.dump(data, f, indent=2)

3. Find Unanswered Messages

import json
from datetime import datetime, timedelta, timezone

INBOX = "/opt/ocana/openclaw/workspace/inbox/pending.json"
MAX_AGE_HOURS = 24  # ignore very old messages

with open(INBOX) as f:
    data = json.load(f)

cutoff = datetime.now(timezone.utc) - timedelta(hours=MAX_AGE_HOURS)
unanswered = [
    m for m in data["messages"]
    if not m["answered"]
    and datetime.fromisoformat(m["ts"].replace("Z", "+00:00")) > cutoff
]

for m in sorted(unanswered, key=lambda x: x["ts"]):
    ts = datetime.fromisoformat(m["ts"].replace("Z", "+00:00")).strftime("%d/%m %H:%M")
    print(f"📩 {m['sender_name']} | {m['chat_name']} | {ts}")
    print(f"   > {m['body'][:100]}")
    print(f"   Message ID: {m['id']}")

4. Cleanup — Remove Old Answered Messages

Run periodically (e.g. weekly via heartbeat) to keep the file small:

import json
from datetime import datetime, timedelta, timezone

INBOX = "/opt/ocana/openclaw/workspace/inbox/pending.json"

with open(INBOX) as f:
    data = json.load(f)

cutoff = datetime.now(timezone.utc) - timedelta(days=7)
data["messages"] = [
    m for m in data["messages"]
    if not m["answered"]
    or datetime.fromisoformat(m["ts"].replace("Z", "+00:00")) > cutoff
]

with open(INBOX, "w") as f:
    json.dump(data, f, indent=2)

print(f"Kept {len(data['messages'])} messages")

Heartbeat Integration

During heartbeat, check for unanswered messages from the last 2 hours:

import json
from datetime import datetime, timedelta, timezone

INBOX = "/opt/ocana/openclaw/workspace/inbox/pending.json"

with open(INBOX) as f:
    data = json.load(f)

cutoff = datetime.now(timezone.utc) - timedelta(hours=2)
unanswered = [
    m for m in data["messages"]
    if not m["answered"]
    and datetime.fromisoformat(m["ts"].replace("Z", "+00:00")) > cutoff
]

if unanswered:
    # Alert Netanel or handle inline
    for m in unanswered:
        print(f"⚠️ לא ענינו ל-{m['sender_name']}: {m['body'][:80]}")

Add this check to HEARTBEAT.md under "Heartbeat Checks".


Output Format

Report unanswered messages as:

  • 📩 [sender_name] | [chat_name] | [DD/MM HH:MM]

    [body preview, max 100 chars] Message ID: [message_id]

Group by chat if multiple from same conversation.


Notes

  • ❌ No DB, no tunnel, no psql — pure file I/O
  • ✅ Works offline, works on AWS without Mac tunnel
  • Inbox file: /opt/ocana/openclaw/workspace/inbox/pending.json
  • Currently: messages are logged manually when Heleni decides to track them
  • Future: hook into inbound message handler to auto-log

Files

1 total
Select a file
Select a file to preview.

Comments

Loading comments…