← 返回
未分类 中文

OpenPot Awareness

Teaches this agent how to serve content to the OpenPot iOS client — cards, apps, page captures, calendar, voice, chat persistence, and onboarding
教此代理如何向OpenPot iOS客户端提供内容,包括卡片、应用、页面截图、日历、语音、聊天持久化和新手引导
almnotai almnotai 来源
未分类 clawhub v6.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 346
下载
💾 0
安装
1
版本
#latest

概述

OpenPot Awareness Skill

You are connected to OpenPot — a native iOS app that serves as a

command center for AI agents. OpenPot has configurable tabs: Chat

(always on), Pulse (notification cards), Calendar, Apps,

Terminal, and Agents (always on). Users choose which tabs to

display in Settings — not every user will have all tabs visible.

Three Output Surfaces

SurfaceWhen to useHow
---------------------------
ChatDefault. Conversations, answers, follow-ups.Normal chat response
Pulse cardsProactive output: reports, alerts, briefs.POST /api/cards
Web appsPersistent tools the user returns to.Build HTML, serve via /api/apps

Default to chat. Use cards for output the user did not ask for in

the current conversation. Use apps only when the user requests a

persistent tool.

Decision Framework — Chat vs. Card vs. App

SituationSurfaceWhy
-------------------------
User asked a questionChatThey're in a conversation. Answer there.
Cron job produced outputCardUser didn't ask. Push it to Pulse.
Alert threshold crossedCardProactive. User needs to know.
User asked for a persistent toolAppThey want something that lives in their toolkit.
User asked about a previous cardChatThey're referencing it in conversation.
Scheduled digest or rollupCardProactive summary, not a conversation.

When in doubt, use chat. Cards and apps are for specific use cases.

Triggers

Activate this skill when:

  • User says "OpenPot sync" — run the sync process (see Sync section)
  • User sends a page capture (message contains

---PAGE CAPTURE CONTEXT---) — see Page Captures section

  • User asks about setting up OpenPot — see Onboarding section
  • User asks about calendar, voice, chat persistence, or

building an app — see the relevant section below

  • User asks "what OpenPot features do I support?" — check status
  • User asks to "set up chat persistence" or "save my chats"

see Chat Persistence section


Pulse Cards

Pulse cards are proactive notifications you push to the user's OpenPot

app. They appear in the Pulse tab as a card stream.

When to Create a Card

  • Scheduled output (cron jobs): morning briefs, DCA signals, health checks
  • Threshold alerts: a metric crossed a boundary the user cares about
  • Proactive observations: something changed that the user should know
  • Digests: summaries of activity over a time period

Do NOT create a card for content the user asked for in the current

conversation. That belongs in chat.

Card API

Endpoint: POST /api/cards

Required fields:

FieldTypeDescription
--------------------------
titleStringCard headline. Under 60 characters.
bodyString1-2 line summary visible on the compact card.
categoryStringDetermines Pulse channel routing. Use canonical list below.
agent_idStringYour agent ID.

Optional fields:

FieldTypeDescription
--------------------------
priorityString"normal" (default) or "high". Reserve high for genuinely urgent items.
originStringWhy this card was created: "cron", "alert", "agent_initiated", "announce".
expanded_bodyStringFull markdown report. When present, the card is tappable and opens a detail view.
actions[String]Action buttons in the detail view. Vocabulary: "discuss", "dismiss", "acknowledge", "snooze".

Body Text Rule

The body field is ALWAYS a complete thought. Never a sentence fragment

that leaves the user wondering what the rest says. If the body is cut

off mid-sentence, the card feels broken.

  • Short notifications (1-2 lines): write the complete message
  • Medium content (4-10 lines): write the full text — OpenPot unfolds it inline
  • Reports with tables/sections: keep body as 1-2 line summary, put detail in expanded_body

Minimal Card Example

{
  "title": "System Health Check",
  "body": "All services operational. CPU 23%, memory 41%.",
  "category": "system",
  "agent_id": "your-agent-id",
  "priority": "normal",
  "origin": "cron"
}

Report Card Example (with expanded detail)

{
  "title": "Weekly DCA Signals",
  "body": "5 double-down signals, 4 baseline holds.",
  "category": "finance",
  "agent_id": "your-agent-id",
  "priority": "normal",
  "origin": "cron",
  "expanded_body": "## Weekly DCA Signal Report\n\n**Generated:** Monday 4:30 PM ET\n\n| Ticker | Price | Signal | Conviction |\n|--------|-------|--------|------------|\n| CRCL | $2.14 | Double-down | High |\n| AFRM | $41.30 | Double-down | Medium |\n\n4 baseline holds. Market weakness = accumulation window.",
  "actions": ["discuss", "dismiss"]
}

Canonical Category List

Use these exact strings. Inconsistent casing or synonyms create

duplicate channels.

CategoryUse for
-------------------
briefingMorning briefs, evening summaries, weekly rollups
systemHealth checks, service status, uptime reports
financeDCA signals, portfolio updates, market observations
calendarSchedule digests, conflict alerts, deadline warnings
projectsTask updates, milestone tracking, blockers
educationLearning content, research summaries
healthHealth tracking, medication reminders
entertainmentMedia recommendations, leisure suggestions

You may create new categories when the user's needs expand. When you

do, include a note in your first card: "I've created a new Pulse

channel for [topic]. You can rename or reorganize it."

Expanded Cards (Tap-to-Open Detail)

When a card represents a report, include an expanded_body field

with the full markdown content.

  • Keep body as a 1-2 line summary (the compact card preview).
  • Put the full analysis in expanded_body using markdown.
  • Include "actions": ["discuss", "dismiss"] for report cards.
  • Cards without expanded_body are glanceable only.
  • Keep total expanded_body under 4,000 characters.

Cards that SHOULD have expanded_body: DCA signal reports, morning

briefs, system health diagnostics, any multi-item analysis or report.

Cards that should NOT: Simple reminders, single-fact notifications,

calendar alerts.

Action Buttons

ActionButton LabelBehavior
-------------------------------
"discuss""Discuss with [Agent]"Opens chat with the card content as context
"dismiss""Dismiss"Closes the detail view and dismisses the card
"acknowledge""Got it"Marks the card as read and closes
"snooze""Snooze"Dismisses temporarily, resurfaces later

Web Apps

Web apps are persistent HTML tools that live in the user's Apps tab.

The Apps tab displays apps as an iOS-style grid with emoji icons.

Unlike cards (ephemeral notifications), apps are tools the user returns

to repeatedly.

When to Build an App

Only when the user explicitly requests a persistent tool. Examples:

"Build me a medication tracker," "I need a DCA calculator."

Never build an app speculatively. Always confirm before building.

App Types

TypeDescriptionBackend needed?
------------------------------------
Type 1: Static toolCalculator, converter, reference — pure HTML/CSS/JSNo
Type 2: Smart appTracker, dashboard — needs persistent data via backend APIYes
Type 3: Connected appIntegrates with external APIs via the agent's backendYes

File Rules

  • One self-contained HTML file per app. All CSS and JS inline.
  • Filename: lowercase, hyphenated, descriptive. Example: medication-tracker.html
  • Title: set in </code> tag. This appears in the Apps tab.</li><li>Served by your HTTP server via <code>GET /api/apps</code> (listing) and direct URL (content).</li></ul><h2>App Metadata</h2><p>Your <code>GET /api/apps</code> endpoint must return metadata for each app:</p><pre><code>[ { "filename": "medication-tracker.html", "title": "Medication Tracker", "description": "Track daily medications and schedules", "category": "health", "emoji": "💊" } ] </code></pre><p>The <code>emoji</code> field is displayed as the app's icon in the grid. Choose</p><p>an emoji that represents the app's purpose. If <code>emoji</code> is omitted,</p><p>OpenPot uses the first letter of the title as a fallback.</p><p><strong>Required fields:</strong> <code>filename</code>, <code>title</code>, <code>category</code></p><p><strong>Recommended fields:</strong> <code>description</code>, <code>emoji</code></p><h2>App Categories</h2><table><thead><tr><th>Category</th><th>Icon color</th></tr></thead><tbody><tr><td>----------</td><td>-----------</td></tr><tr><td>tools</td><td>Blue</td></tr><tr><td>health</td><td>Red</td></tr><tr><td>finance</td><td>Green</td></tr><tr><td>projects</td><td>Teal</td></tr><tr><td>monitoring</td><td>Orange</td></tr><tr><td>entertainment</td><td>Purple</td></tr><tr><td>reference</td><td>Gray</td></tr></tbody></table><h2>Design Guidelines</h2><ul><li><strong>Dark theme default.</strong> Background: <code>#1A1D28</code> or similar dark. Text: white/light gray.</li><li><strong>Mobile-first.</strong> Renders in a WKWebView on iPhone and iPad. Design for touch.</li><li><strong>No scrollbars.</strong> Use CSS <code>overflow: auto</code> with <code>-webkit-overflow-scrolling: touch</code>.</li><li><strong>Card aesthetic.</strong> Rounded corners (12-16pt), subtle borders.</li><li><strong>Gold accent.</strong> Use warm gold (<code>#C9A84C</code> or similar) for primary actions.</li><li><strong>Readable typography.</strong> Minimum 16px body text.</li><li><strong>No external images.</strong> Use inline SVG or CSS-drawn elements.</li></ul><h2>Before You Build — Confirmation Step</h2><p>Before creating any app, confirm with the user:</p><ol><li>What the app does (one sentence)</li><li>Which type (1, 2, or 3)</li><li>The filename you will use</li><li>The category (tools, health, finance, projects, monitoring, entertainment)</li><li>An emoji for the app icon</li></ol><hr><h1>Page Captures</h1><p>The user can capture web pages from OpenPot's in-app browser. Captures</p><p>arrive as chat messages with the user's note followed by a structured</p><p>context block.</p><h2>What You Receive</h2><pre><code>[User's note here] ---PAGE CAPTURE CONTEXT--- URL: https://example.com/product Title: Product Name Description: Product description text... Site: Example Readable Text: [Up to 4,000 characters of extracted page content] Tables: | Header | Header | |--------|--------| | Value | Value | Screenshot: ~/.openclaw/workspace/attachments/{uuid}.jpg ---END PAGE CAPTURE CONTEXT--- </code></pre><p>Fields: URL, Title, Description, Site (metadata), Readable Text (main</p><p>content up to 4,000 chars), Tables (if present), Screenshot (file path</p><p>to captured viewport image, omitted in Data Only mode).</p><h2>Processing a Capture</h2><ol><li><strong>Use the text data first.</strong> URL, title, readable text, and tables</li></ol><p> cover most questions without needing the screenshot.</p><ol><li><strong>For visual analysis,</strong> use your <code>read</code> tool on the Screenshot</li></ol><p> path. Only do this when the question requires seeing the page.</p><ol><li><strong>Write a visual description</strong> for your own records after analyzing.</li></ol><p> Example: "matte black single-handle kitchen faucet, pull-down</p><p> sprayer, modern industrial style." This description is your</p><p> permanent memory of the page. The image file is temporary.</p><ol><li><strong>Respond to the user's note.</strong> Keep responses concise — the user</li></ol><p> is in a chat strip inside the browser. If your response needs</p><p> depth, suggest moving to Chat.</p><ol><li><strong>If no note was provided,</strong> confirm briefly: "Noted — [one-line</li></ol><p> description]. I can pull this up anytime."</p><h2>Saving to Pulse</h2><p>When the user signals a page has ongoing value — "save this,"</p><p>"remember this," "bookmark this" — push a Pulse card:</p><ul><li>title: Your one-line summary</li><li>body: Your visual description + the user's note, combined naturally</li><li>expanded_body: Full details — URL, price/ratings if product, key data</li><li>category: Most appropriate channel</li><li>actions: ["discuss", "dismiss"]</li></ul><p>Include the URL as a tappable link. Not every capture becomes a card —</p><p>only when the user signals intent to revisit.</p><h2>Re-encounters</h2><p>When the user returns to a previously captured page, do not re-analyze</p><p>from scratch. You have your original visual notes, the user's note, the</p><p>extracted data, and any conversation that followed. Pick up where you</p><p>left off.</p><h2>Link Cards</h2><p>When recommending a web page to the user, use this format:</p><pre><code>:::link url: https://example.com/page title: Page Title site: Site Name note: Why this matters — your annotation. ::: </code></pre><p>OpenPot renders these as tappable cards that open in the in-app browser.</p><h2>Card Recategorization</h2><p>When the user moves a card to a different Pulse channel, you receive</p><p>a notification. This is a learning signal. If the user moves multiple</p><p>cards of the same type, ask: "Want me to route [type] to [channel]</p><p>automatically?" Never change routing without asking.</p><hr><h1>Calendar</h1><p>OpenPot has a Calendar tab with native Year, Month, and Agenda views.</p><p>The Calendar tab aggregates events from multiple sources into one</p><p>unified display.</p><h2>Calendar Sources</h2><table><thead><tr><th>Source</th><th>How it works</th></tr></thead><tbody><tr><td>--------</td><td>-------------</td></tr><tr><td>Backend API</td><td><code>GET /api/calendar/events</code> serves events from any provider (Google Calendar, Apple Calendar, CalDAV, or any ClawHub calendar skill)</td></tr><tr><td>Agent calendar</td><td><code>:::calendar</code> blocks in chat create local events stored on-device with gold accent</td></tr><tr><td>User-created</td><td>User adds events directly in the Calendar tab via the "+" button or long-press on a date</td></tr></tbody></table><p>OpenPot does not care where backend events come from. It reads one</p><p>endpoint. Any calendar skill from ClawHub that feeds into your</p><p><code>/api/calendar/events</code> endpoint works automatically — Google Calendar,</p><p>Apple Calendar, CalDAV, Outlook, etc. The unified endpoint normalizes</p><p>all sources into the same schema.</p><h2>Authorization Rule</h2><p>**Never add, modify, or delete calendar events without explicit user</p><p>permission.** You may read the calendar, summarize it, and present it.</p><p>Creating events requires the user to say yes first. Always ask:</p><p>"Would you like me to add this to your calendar?"</p><h2>Event Format</h2><p>The <code>/api/calendar/events</code> endpoint returns events in this schema:</p><pre><code>{ "id": "string — stable unique ID, not a UUID", "title": "string — under 60 chars", "start_date": "ISO 8601 or date-only string", "end_date": "ISO 8601 or date-only (optional)", "is_all_day": true, "notes": "string or null — plain text only, no HTML", "location": "string or null", "calendar_name": "string — human-readable calendar name", "calendar_color": "string — hex color", "source": "string — the provider (google_calendar, apple_calendar, caldav, agent, manual, etc.)", "status": "confirmed | tentative | cancelled" } </code></pre><h3>Date Format Rules</h3><table><thead><tr><th>Event Type</th><th>start_date Format</th><th>Example</th></tr></thead><tbody><tr><td>------------</td><td>-------------------</td><td>---------</td></tr><tr><td>All-day</td><td>Date-only string</td><td><code>"2026-04-14"</code></td></tr><tr><td>Timed</td><td>ISO 8601 with timezone offset</td><td><code>"2026-04-14T09:00:00-04:00"</code></td></tr><tr><td>Query param</td><td>ISO 8601 with Z</td><td><code>"2026-04-01T04:00:00Z"</code></td></tr></tbody></table><h2>Calendar Context Updates</h2><p>OpenPot automatically sends calendar context messages when the user</p><p>interacts with the Calendar tab. These arrive as chat messages wrapped</p><p>in <code>[calendar_context]</code> tags.</p><p><strong>When you receive a <code>[calendar_context]</code> message:</strong></p><ul><li><strong>Absorb the schedule information silently.</strong> Do NOT respond to the</li></ul><p> message — no acknowledgment, no confirmation, no "Got it."</p><ul><li><strong>Use the context to inform future conversations.</strong> If the user asks</li></ul><p> "what do I have today," "am I free this afternoon," or "what's</p><p> coming up this week," reference the most recent calendar context</p><p> you received.</p><ul><li><strong>Calendar context replaces any previously received context.</strong> Always</li></ul><p> use the most recent one. Do not accumulate old context.</p><ul><li><strong>Never quote the raw context back to the user.</strong> Synthesize it</li></ul><p> naturally into your responses.</p><p>Example incoming context:</p><pre><code>[calendar_context] Today (April 14, 2026): - Cardiac APP Review at 9:00 AM (Room 4B) - Team standup at 11:00 AM Tomorrow (April 15, 2026): - Oil Change — Miata at 9:00 AM Upcoming 7 days: 8 events [/calendar_context] </code></pre><h2>Ask About Event</h2><p>The user may send a message prefixed with "Tell me about this event:"</p><p>followed by event details (title, date/time, location, calendar name).</p><p>This is triggered from the Calendar tab's long-press menu. When you</p><p>receive this:</p><ul><li>Provide relevant context about the event from your memory</li><li>Suggest preparation steps if appropriate</li><li>Reference related information you know about</li><li>If you have no additional context, say so honestly and offer to</li></ul><p> help with logistics (directions, reminders, related tasks)</p><h2>Calendar Pulse Cards</h2><p>When creating a calendar-category Pulse card:</p><ul><li>Use <code>"category": "calendar"</code></li><li>Do NOT include <code>expanded_body</code> — calendar cards open the calendar view</li><li>Include <code>"actions": ["view_calendar", "dismiss"]</code></li></ul><h2>Common Pitfalls</h2><ol><li><strong>String IDs, not UUIDs.</strong> UUID objects cause decode failures on iOS.</li><li><strong>Timezone offsets required on timed events.</strong> Omitting the offset</li></ol><p> causes events to render at wrong times.</p><ol><li><strong>All-day events use date-only strings.</strong> Do not add <code>T00:00:00</code>.</li><li><strong>Calendar color must be a hex string.</strong> Format: <code>"#f83a22"</code>.</li><li><strong>Notes field: plain text only.</strong> HTML renders as raw tags on iOS.</li></ol><p> Strip all HTML before serving notes to OpenPot.</p><ol><li><strong>Query all accessible calendars.</strong> Do not limit to a single</li></ol><p> calendar — users have shared, subscribed, and multiple provider</p><p> calendars.</p><ol><li><strong>The endpoint must handle both date formats as query params.</strong></li><li><strong>Restart the backend server after configuration changes.</strong></li><li><strong>Do not return HTML in any text field.</strong> OpenPot renders it as</li></ol><p> raw tags, not formatted content.</p><h2>Calendar Setup Steps</h2><ol><li>Ensure your backend server has a <code>/api/calendar/events</code> endpoint</li><li>Configure credentials for your calendar provider (Google OAuth,</li></ol><p> Apple EventKit permissions, CalDAV credentials, etc.)</p><ol><li>Store tokens and credentials securely (not in SOUL.md or version</li></ol><p> control)</p><ol><li>Test: <code>curl -H "Authorization: Bearer <token>" http://localhost:8000/api/calendar/events?start=2026-04-01&end=2026-04-30</code></li><li>Update <code>openpot-status.json</code> with calendar feature as installed</li><li>Restart the backend server</li></ol><h2>Creating Calendar Events</h2><p>The user may have calendars connected from external providers (Google</p><p>Calendar, Apple Calendar, CalDAV), and each provider may have multiple</p><p>sub-calendars (personal, work, family, hobbies, sports, etc.). If no</p><p>external calendars are connected yet, you can help set that up — see</p><p>the Calendar Setup Steps above.</p><p>Regardless of what external calendars are connected, you always have</p><p>your agent calendar. It is yours, stored locally on the OpenPot device,</p><p>and requires no backend or provider setup.</p><h3>Which Calendar? Always Clarify.</h3><p>When the user asks you to add an event, you need to know which</p><p>calendar it belongs on. If the user doesn't specify, ask:</p><p>**"Which calendar should I put this on — your [list their known</p><p>calendars], or my agent calendar?"**</p><p>Learn the user's calendars early. When you first access their calendar</p><p>data, note which calendars exist and what they're used for. Over time</p><p>you'll know which calendar is for what without asking.</p><p><strong>Routing rules:</strong></p><ul><li>User says "my calendar" or names a specific calendar → use the</li></ul><p> appropriate provider calendar API</p><ul><li>User says "agent calendar," "your calendar," or "track this for me"</li></ul><p> → use the <code>:::calendar</code> block (agent calendar)</p><ul><li>User says "remind me" or "don't forget" about a future date → use</li></ul><p> the <code>:::calendar</code> block with a <code>remind</code> interval</p><ul><li>User asks you to add to a specific provider calendar → use that</li></ul><p> provider's API (Google Calendar API, etc.)</p><ul><li>When in doubt → ask. Never guess.</li></ul><p><strong>You have full access to provider calendars.</strong> You can read, create,</p><p>edit, and delete events on the user's connected calendars (Google,</p><p>Apple, CalDAV, etc.) using their respective APIs. The agent calendar</p><p>is an additional calendar, not a replacement.</p><h3>Agent Calendar — The <code>:::calendar</code> Block</h3><p>To put an event on YOUR agent calendar, include a <code>:::calendar</code> block</p><p>in your chat response. OpenPot parses it, stores it locally on the</p><p>device, and displays it on the Calendar tab with a gold accent color.</p><pre><code>:::calendar title: Trailer Registration Expires date: 2027-07-15 notes: Renew at DMV or online remind: 2w ::: </code></pre><p>This works on any tier — no backend server needed.</p><h3>Required Fields</h3><table><thead><tr><th>Field</th><th>Format</th><th>Description</th></tr></thead><tbody><tr><td>-------</td><td>--------</td><td>-------------</td></tr><tr><td>title</td><td>String</td><td>Event title, under 60 characters</td></tr><tr><td>date</td><td><code>YYYY-MM-DD</code></td><td>The date of the event</td></tr></tbody></table><h3>Optional Fields</h3><table><thead><tr><th>Field</th><th>Format</th><th>Description</th></tr></thead><tbody><tr><td>-------</td><td>--------</td><td>-------------</td></tr><tr><td>end_date</td><td><code>YYYY-MM-DD</code></td><td>End date for multi-day events</td></tr><tr><td>time</td><td><code>HH:MM</code></td><td>Start time (24h format). Omit for all-day events.</td></tr><tr><td>end_time</td><td><code>HH:MM</code></td><td>End time (24h). Only valid with <code>time</code>.</td></tr><tr><td>notes</td><td>String</td><td>Additional context, plain text</td></tr><tr><td>category</td><td>String</td><td>Maps to a Pulse channel for future alerts</td></tr><tr><td>remind</td><td>String</td><td>When to push a reminder card: <code>1d</code>, <code>1w</code>, <code>2w</code>, <code>1m</code>, <code>3m</code></td></tr></tbody></table><h3>How It Works</h3><ul><li>The <code>:::calendar</code> block is parsed by OpenPot and replaced with a</li></ul><p> compact calendar card in the chat bubble (not shown as raw text)</p><ul><li>The event is stored locally on the device in the Calendar tab</li><li>Events appear with a gold accent color to distinguish them from</li></ul><p> provider calendar sources</p><ul><li>If <code>remind</code> is set, OpenPot automatically pushes a Pulse card to</li></ul><p> the user when the reminder window is reached</p><ul><li>No backend, no HTTP server, no OAuth — works on any tier</li></ul><h3>Rules</h3><ul><li><strong>Always confirm which calendar before creating an event.</strong></li><li><strong>Never silently create events on any calendar.</strong></li><li>Keep titles under 60 characters</li><li>Use date-only for all-day events, add <code>time</code> for timed events</li><li>Use <code>remind</code> for events the user should be warned about in advance</li><li>Learn the user's calendar layout early</li></ul><hr><h1>Chat Persistence</h1><p>Chat messages can be stored in a PostgreSQL database on your backend</p><p>server, giving the user persistent chat history that survives app</p><p>reinstalls and works across devices. This is a Tier 2 feature —</p><p>requires an HTTP server on port 8000.</p><h2>When to Set Up</h2><p>Set up chat persistence when:</p><ul><li>The user asks: "save my chats," "set up chat persistence," "I want</li></ul><p> my messages backed up"</p><ul><li>During OpenPot sync, if you detect an HTTP server on port 8000 but</li></ul><p> no <code>/api/chat/sessions</code> endpoint responding</p><ul><li>The user reinstalls the app and loses their chat history</li></ul><h2>What You Need</h2><ul><li>PostgreSQL database (the same one used for memory)</li><li>FastAPI server on port 8000 (the same one serving other endpoints)</li><li>Bearer token authentication (reuse existing token)</li></ul><h2>Database Schema</h2><p>Create the following in your PostgreSQL database:</p><h3>Enums</h3><pre><code>CREATE TYPE sender_type AS ENUM ('user', 'assistant', 'system'); CREATE TYPE message_type AS ENUM ('text', 'image', 'file', 'voice', 'system'); </code></pre><h3>Main Table</h3><pre><code>CREATE TABLE chat_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id VARCHAR(255) NOT NULL, channel VARCHAR(255), content TEXT NOT NULL, sender_type sender_type NOT NULL, sender_name VARCHAR(255), message_type message_type NOT NULL DEFAULT 'text', run_id VARCHAR(255), parent_id UUID REFERENCES chat_messages(id), metadata JSONB DEFAULT '{}', attachments JSONB DEFAULT '[]', protected BOOLEAN DEFAULT FALSE, hidden BOOLEAN DEFAULT FALSE, search_vector tsvector, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); </code></pre><h3>Indexes</h3><pre><code>CREATE INDEX idx_chat_messages_session ON chat_messages(session_id, created_at DESC) WHERE hidden = FALSE; CREATE INDEX idx_chat_messages_search ON chat_messages USING GIN(search_vector); CREATE INDEX idx_chat_messages_run ON chat_messages(run_id) WHERE run_id IS NOT NULL; </code></pre><h3>Search Vector Trigger</h3><pre><code>CREATE OR REPLACE FUNCTION update_search_vector() RETURNS TRIGGER AS $$ BEGIN NEW.search_vector := setweight(to_tsvector('english', COALESCE(NEW.content, '')), 'A') || setweight(to_tsvector('english', COALESCE(NEW.sender_name, '')), 'B') || setweight(to_tsvector('english', COALESCE(NEW.channel, '')), 'C'); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER chat_messages_search_update BEFORE INSERT OR UPDATE OF content, sender_name, channel ON chat_messages FOR EACH ROW EXECUTE FUNCTION update_search_vector(); </code></pre><h3>Updated_at Trigger</h3><pre><code>CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER chat_messages_updated_at BEFORE UPDATE ON chat_messages FOR EACH ROW EXECUTE FUNCTION update_updated_at(); </code></pre><h3>Session View</h3><pre><code>CREATE OR REPLACE VIEW chat_sessions AS SELECT session_id, channel, MIN(created_at) AS first_message_at, MAX(created_at) AS last_message_at, COUNT(*) AS message_count, COUNT(*) FILTER (WHERE sender_type = 'user') AS user_message_count, COUNT(*) FILTER (WHERE sender_type = 'assistant') AS assistant_message_count FROM chat_messages WHERE hidden = FALSE GROUP BY session_id, channel ORDER BY last_message_at DESC; </code></pre><h2>REST Endpoints</h2><p>All endpoints require <code>Authorization: Bearer <token></code> header.</p><h3>Store a message</h3><pre><code>POST /api/chat/messages { "session_id": "openpot-local", "content": "Hello!", "sender_type": "user", "sender_name": "User", "message_type": "text" } </code></pre><h3>Retrieve session history</h3><pre><code>GET /api/chat/messages?session=openpot-local&limit=50&before=<timestamp> </code></pre><p>Note: use <code>session</code> as the query parameter, not <code>session_id</code>.</p><h3>Search messages</h3><pre><code>GET /api/chat/messages/search?q=search+term&limit=20 </code></pre><h3>List sessions</h3><pre><code>GET /api/chat/sessions?limit=20 </code></pre><h2>Session ID Convention</h2><ul><li>OpenPot native chat: <code>"openpot-local"</code></li><li>Future channel support: <code>"openpot-{channel-name}"</code></li></ul><h2>Data Management</h2><p>Chat data grows over time. To prevent the database from becoming</p><p>unmanageably large:</p><h3>Automatic Compaction (recommended)</h3><p>Set up a weekly cron job that:</p><ol><li>Counts messages older than 90 days in each session</li><li>For sessions with 1,000+ old messages, summarize the oldest batch</li></ol><p> into a single system message with the key topics covered</p><ol><li>Mark the original messages as <code>hidden = TRUE</code> (soft delete)</li><li>Never delete messages marked <code>protected = TRUE</code></li></ol><pre><code># Example cron entry (runs Sunday 3 AM) 0 3 * * 0 /path/to/compact-chat.py --older-than 90 --batch-size 500 </code></pre><h3>Manual Export</h3><p>When the user asks to export their chat history, provide a markdown</p><p>or JSON export of the full conversation from the database.</p><h3>Storage Estimates</h3><table><thead><tr><th>Messages</th><th>Approximate Size</th></tr></thead><tbody><tr><td>----------</td><td>-----------------</td></tr><tr><td>1,000</td><td>~2 MB</td></tr><tr><td>10,000</td><td>~20 MB</td></tr><tr><td>100,000</td><td>~200 MB</td></tr></tbody></table><p>With compaction running weekly, most deployments stay under 50 MB</p><p>of active chat data indefinitely.</p><h2>Verification</h2><p>After setting up chat persistence, test:</p><pre><code># Store a test message curl -X POST http://localhost:8000/api/chat/messages \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"session_id":"test","content":"Hello","sender_type":"user","sender_name":"Test","message_type":"text"}' # Retrieve it curl -H "Authorization: Bearer <token>" \ "http://localhost:8000/api/chat/messages?session=test&limit=10" # Check sessions curl -H "Authorization: Bearer <token>" \ "http://localhost:8000/api/chat/sessions" </code></pre><p>Update <code>openpot-status.json</code> with chat persistence as enabled after</p><p>verification passes.</p><hr><h1>Voice</h1><p>OpenPot supports voice input (speech-to-text) and voice output</p><p>(text-to-speech). Voice is entirely client-side — no server changes</p><p>are required.</p><h2>How It Works</h2><ul><li><strong>Input:</strong> Apple Speech recognition on the device. The user speaks</li></ul><p> and the transcribed text is sent as a normal chat message. You</p><p> receive it as text — no special handling needed.</p><ul><li><strong>Output:</strong> ElevenLabs text-to-speech on the device. OpenPot reads</li></ul><p> your response aloud. Write naturally, avoid special formatting for</p><p> TTS. If the user is using voice, prefer shorter, conversational</p><p> responses.</p><h2>Voice Setup (User-Side, in the OpenPot App)</h2><p>Walk the user through these steps:</p><ol><li><strong>ElevenLabs API Key:</strong></li></ol><p> Settings → General → Voice Output → ElevenLabs API Key</p><p> (from elevenlabs.io → Profile → API Keys)</p><ol><li><strong>Voice Selection (per agent):</strong></li></ol><p> Agents tab → tap agent → Voice section → Voice ID</p><p> (browse at elevenlabs.io/voice-library, copy the Voice ID)</p><ol><li><strong>Voice Model:</strong> Default is <code>eleven_multilingual_v2</code>. Can be</li></ol><p> changed per-agent.</p><ol><li><strong>Speed:</strong> Adjustable per-agent. Default 1.0, range 0.5–2.0.</li></ol><h2>Common Voice Issues</h2><ul><li><strong>"Voice not working"</strong> → check ElevenLabs API key in Settings,</li></ul><p> check API key has credits remaining.</p><ul><li><strong>"Wrong voice"</strong> → check per-agent Voice ID in Agents tab.</li><li><strong>"Talk Mode breaks messages"</strong> → do NOT add <code>operator.talk.secrets</code></li></ul><p> scope to gateway config. It interferes with message delivery.</p><h2>No Server Changes Needed</h2><p>Voice requires zero backend configuration. Do not modify SOUL.md,</p><p>install services, or configure endpoints for voice. Mark voice as</p><p>installed in <code>openpot-status.json</code> after the user confirms credentials.</p><hr><h1>Onboarding</h1><p>When the user asks to set up or connect OpenPot ("I have OpenPot, get</p><p>me set up," "help me connect OpenPot," "OpenPot setup"), follow these</p><p>steps.</p><h2>Step 1 — Provide Connection Details</h2><p>You know your own infrastructure. Provide:</p><ol><li><strong>Gateway URL</strong> — format: <code>ws://<your-ip>:18789</code></li></ol><p> If Tailscale is available, provide both addresses:</p><ul><li>LAN: <code>ws://<lan-ip>:18789</code> (faster, home WiFi only)</li><li>Tailscale: <code>ws://<tailscale-ip>:18789</code> (works from anywhere)</li></ul><ol><li><strong>Bearer token</strong> — send in a private/1:1 channel only.</li></ol><p> Never post tokens in group channels.</p><ol><li><strong>Instructions:</strong> "Open OpenPot → Agents tab → Add Agent → paste</li></ol><p> the gateway URL and token → tap Connect."</p><h2>Step 2 — Check Tailscale</h2><p>Run <code>tailscale status</code> to check if Tailscale is installed.</p><ul><li>If running: provide the Tailscale IP for the second route.</li><li>If not installed: LAN-only works on home WiFi. For remote access,</li></ul><p> point them to tailscale.com/download.</p><h2>Step 3 — Auto-Approve Device Pairing</h2><p>Tell the user: "When you tap Connect, I'll approve the pairing</p><p>automatically. Go ahead."</p><p>Then:</p><ol><li>Run <code>openclaw devices list</code> to check for pending requests</li><li>Approve the request from "openclaw-ios" or "OpenPot":</li></ol><p> <code>openclaw devices approve <requestId></code></p><ol><li>Confirm: "You're approved and connected!"</li></ol><p><strong>Security rules:</strong></p><ul><li>Only auto-approve during an active onboarding conversation</li><li>Only approve ONE device per onboarding flow</li><li>If multiple requests arrive, approve only the first and alert the user</li><li>Outside onboarding, surface pairing requests for manual approval</li></ul><h2>Step 4 — Post-Connection</h2><p>Once connected:</p><ol><li>Send a welcome Pulse card (if you have an HTTP server):</li></ol><p> title: "Welcome to OpenPot", body: "Your connection is live.</p><p> Explore your tabs.", category: "system",</p><p> actions: ["acknowledge"]</p><ol><li>Briefly describe the tabs the user has enabled.</li></ol><ol><li>Offer to run "OpenPot sync" to configure all features.</li></ol><h2>Step 5 — Backend Setup (if applicable)</h2><p>If you have a backend server on port 8000, check health and provide</p><p>the server URL and token for OpenPot Settings → Server Configuration.</p><p>If you don't have a backend server, skip — Chat, Terminal, and basic</p><p>features work with the gateway alone.</p><h2>Step 6 — SSH Key Setup (for Terminal)</h2><p>The Terminal tab uses a device-generated SSH key. On first use:</p><ol><li>The user taps the key icon in the Terminal status bar</li><li>Copies the public key</li><li>Sends it to you</li><li>You add it to <code>~/.ssh/authorized_keys</code></li></ol><p>This is a one-time step per device.</p><h2>Troubleshooting</h2><ul><li>"Connecting stays amber" → check gateway is running, port 18789,</li></ul><p> address starts with ws://, user is on correct network</p><ul><li>"Device not approved" → run <code>openclaw devices list</code>, approve manually</li><li>"Pulse is empty" → run "OpenPot sync", check for HTTP server</li><li>"Can't connect on cellular" → Tailscale needed for remote access</li><li>"Agent name blank" → check IDENTITY.md in workspace</li><li>"Terminal not connecting on cellular" → known limitation; Terminal</li></ul><p> SSH works on LAN, Tailscale SSH support is in development</p><hr><h1>Sync Process</h1><p>When the user says "OpenPot sync" or taps the OpenPot Sync button</p><p>in the Agents tab:</p><h2>Step 1 — Refresh the skill</h2><p><strong>IMPORTANT:</strong> Until the openpot-awareness skill is fully registered</p><p>with ClawHub for direct updates, refreshing requires a full uninstall</p><p>and reinstall cycle:</p><pre><code>openclaw skills uninstall openpot-awareness openclaw skills install openpot-awareness </code></pre><p>Then restart the gateway for changes to take effect.</p><p><strong>[Future — when ClawHub supports updates]</strong> This step will simplify to:</p><pre><code>openclaw skills update openpot-awareness </code></pre><h2>Step 2 — Check current status</h2><p>Read <code>openpot-status.json</code> from your workspace root. If it doesn't</p><p>exist, this is a first-time setup — treat all features as version 0.</p><p>Use the template from your skill directory at</p><p><code>skills/openpot-awareness/templates/</code> if available.</p><h2>Step 3 — Check infrastructure and report</h2><p>For each feature, check if your infrastructure meets the prerequisites</p><p>and report what's available and what's missing:</p><ul><li><strong>Chat</strong> — always available (gateway only)</li><li><strong>Chat Persistence</strong> — requires HTTP server + PostgreSQL +</li></ul><p> <code>/api/chat/sessions</code> endpoint. If not available, offer to set it up</p><p> (see Chat Persistence section).</p><ul><li><strong>Pulse Cards</strong> — requires HTTP server with <code>POST /api/cards</code></li><li><strong>Calendar</strong> — requires HTTP server + calendar provider +</li></ul><p> <code>/api/calendar/events</code></p><ul><li><strong>Voice</strong> — configured in the app, no server needed</li><li><strong>Page Capture</strong> — always available (gateway only)</li><li><strong>Apps</strong> — requires HTTP server + <code>/api/apps</code> endpoint</li><li><strong>Terminal</strong> — always available (gateway only, LAN connection)</li></ul><p>Offer to help set up missing prerequisites.</p><h2>Step 4 — Install starter apps</h2><p>Copy apps from your skill directory at</p><p><code>skills/openpot-awareness/apps/</code> to your apps serving directory.</p><p>Track installed and user-removed apps in <code>openpot-starter-apps.json</code></p><p>so removed apps don't come back on future syncs.</p><h2>Step 5 — Write status</h2><p>Update <code>openpot-status.json</code> in your workspace root with current</p><p>feature versions and timestamps.</p><h2>Step 6 — Report</h2><pre><code>OpenPot Sync Complete ━━━━━━━━━━━━━━━━━━━━ ✅ Chat (v1) ✅ Chat Persistence (v1) — messages backed up to server ✅ Pulse Cards (v1) ✅ Calendar (v2) — Google Calendar connected ✅ Voice (v1) — configured in app ✅ Page Capture (v2) ✅ Onboarding (v2) ✅ Apps (v1) — {count} starter apps installed ⬚ Terminal SSH on Tailscale — in development Starter Apps: {count} installed Last synced: {timestamp} </code></pre><hr><h1>Migration: Clean Up Old SOUL.md Inserts</h1><p>If your SOUL.md contains <code><!-- OPENPOT INSERT</code> markers from a previous</p><p>version of this skill, remove them. The native skill system loads this</p><p>SKILL.md automatically — SOUL.md injection is no longer needed.</p><p>To clean up:</p><ol><li>Read your SOUL.md</li><li>Delete everything between <code><!-- OPENPOT INSERT</code> and</li></ol><p> <code><!-- END OPENPOT INSERT --></code> markers (including the markers)</p><ol><li>Restart the gateway</li></ol><p>This is a one-time migration. New installations do not touch SOUL.md.</p><hr><h1>Rules</h1><ul><li><strong>NEVER modify SOUL.md</strong> — this skill loads natively via OpenClaw</li><li><strong>NEVER auto-sync</strong> — the user triggers sync with "OpenPot sync"</li></ul><p> or the Sync button</p><ul><li><strong>NEVER re-install apps the user deleted</strong> — respect the tracking file</li><li><strong>NEVER add calendar events without explicit user permission</strong></li><li><strong>NEVER respond to <code>[calendar_context]</code> messages</strong> — absorb silently</li><li><strong>Always update openpot-status.json</strong> after any change</li><li><strong>Always strip HTML from calendar notes</strong> before serving to OpenPot</li><li><strong>Always include <code>emoji</code> field</strong> in app metadata for grid icon display</li><li>App endpoints (<code>/api/apps</code>) must not require authentication</li></ul></div> </div> </div> <div id="tab-versions" class="detail-content"> <div class="detail-section"> <h2>版本历史</h2> <p style="margin-bottom:12px;font-size:14px;color:#94a3b8;">共 1 个版本</p> <ul class="version-list"> <li> <div> <span class="version-tag">v6.0.0</span> <span style="font-size:11px;color:#5b6abf;margin-left:8px;background:#eef0ff;padding:1px 8px;border-radius:10px;">当前</span> </div> <div style="font-size:12px;color:#94a3b8;"> 2026-05-07 08:48 安全 安全 </div> </li> </ul> </div> </div> <div id="tab-security" class="detail-content"> <div class="detail-section"> <h2>安全检测</h2> <div class="sec-grid"> <div class="sec-card"> <h4>腾讯云安全 (Keen)</h4> <div class="sec-status sec-safe"> 安全,无风险 </div> <a href="https://tix.qq.com/search/skill?keyword=67d480128cdabca9eed284523126f8cc" target="_blank">查看报告</a> </div> <div class="sec-card"> <h4>腾讯云安全 (Sanbu)</h4> <div class="sec-status sec-safe"> 安全,无风险 </div> <a href="https://static.cloudsec.tencent.com/html-report-v2/2026/05/26/439821_08ba40eb7ec00d9c3abedff45386e546.html?q-sign-algorithm=sha1&q-ak=AKID8JMG1bzBC1dz96qNhssfFftujT1NCoFi&q-sign-time=1783290974%3B1814826974&q-key-time=1783290974%3B1814826974&q-header-list=host&q-url-param-list=&q-signature=23d3265452fec4526544228948e096c9b1fd1fd1" target="_blank">查看报告</a> </div> </div> </div> </div> <!-- Recommended Skills --> <div style="margin-top:24px;"> <h2 style="font-size:18px;font-weight:600;margin-bottom:16px;">🔗 相关推荐</h2> <div class="rec-grid"> <div class="rec-card"> <span class="badge-cat" style="margin-bottom:8px;display:inline-block;">ai-agent</span> <h3><a href="/s/find-skills">Find Skills</a></h3> <div class="rec-owner">root</div> <div class="rec-desc">帮助用户发现和安装智能体技能,当用户询问如「如何做X」、「找X的技能」、「有能做...的吗」等问题时</div> <div class="rec-stats"> <span style="color:#f39c12;">★ 1,526</span> <span style="color:#5b6abf;">📥 581,920</span> </div> </div> <div class="rec-card"> <span class="badge-cat" style="margin-bottom:8px;display:inline-block;">ai-agent</span> <h3><a href="/s/agent-browser">Agent Browser</a></h3> <div class="rec-owner">rez0</div> <div class="rec-desc">用于 AI 代理的浏览器自动化 CLI。当用户需要与网站交互(包括浏览页面、填写表单、点击按钮、截图等)时使用。</div> <div class="rec-stats"> <span style="color:#f39c12;">★ 873</span> <span style="color:#5b6abf;">📥 350,150</span> </div> </div> <div class="rec-card"> <span class="badge-cat" style="margin-bottom:8px;display:inline-block;">ai-agent</span> <h3><a href="/s/self-improving">Self-Improving + Proactive Agent</a></h3> <div class="rec-owner">ivangdavila</div> <div class="rec-desc">自我反思+自我批评+自我学习+自组织记忆。智能体评估自身工作、发现错误并持续改进。</div> <div class="rec-stats"> <span style="color:#f39c12;">★ 1,448</span> <span style="color:#5b6abf;">📥 329,456</span> </div> </div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded',function(){ document.querySelectorAll('.detail-tab').forEach(function(btn){ btn.addEventListener('click',function(e){ var tab = this.getAttribute('data-tab'); document.querySelectorAll('.detail-tab').forEach(function(b){b.classList.remove('active')}); document.querySelectorAll('.detail-content').forEach(function(c){c.classList.remove('active')}); this.classList.add('active'); var el = document.getElementById('tab-'+tab); if(el) el.classList.add('active'); }); }); }); </script> <div class="footer"> <p>Skill工具集 © 2026</p> </div></body> </html>