Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
7cd46f4
aidlc: AI clone — initial spec
choguun Jun 27, 2026
623b3ea
aidlc: AI clone — revise spec to mirror existing codebase
choguun Jun 27, 2026
ed409e1
aidlc: AI clone — implementation plan
choguun Jun 27, 2026
c66484e
implement T-001: backend persona-chat endpoint + capability
choguun Jun 27, 2026
7bd2d34
aidlc: T-001 done, plan updated
choguun Jun 27, 2026
da4c03b
implement T-002: shared persona_client module
choguun Jun 27, 2026
662ead2
aidlc: T-002 done, plan updated
choguun Jun 27, 2026
4e58818
implement T-003: omi-telegram-app skeleton + setup flow
choguun Jun 27, 2026
f91e176
aidlc: T-003 done, plan updated
choguun Jun 27, 2026
50ac740
implement T-004: Telegram auto-reply dispatch + /toggle endpoint
choguun Jun 27, 2026
b601f24
aidlc: T-004 done, plan updated
choguun Jun 27, 2026
f94ea5c
fix: review findings for T-001..T-004
choguun Jun 27, 2026
7d0f6ca
chore: stop tracking .aidlc/ (process artifact)
choguun Jun 27, 2026
f02f3e0
fix: use sys.modules.setdefault in test stubs to avoid polluting sibl…
choguun Jun 27, 2026
56962f8
fix: address cubic review findings from PR #8437
choguun Jun 27, 2026
cf155d6
fix: address second cubic review (auth + error leakage)
choguun Jun 27, 2026
4d9f44f
fix: /toggle returns same 403 for unknown chat_id and wrong token
choguun Jun 27, 2026
59b24ac
fix: address maintainer review on PR #8437 (uid + auth + contract)
choguun Jun 27, 2026
160c03c
fix: address cubic pass-2 (legacy key fallback + dedupe)
choguun Jun 27, 2026
a1f5c87
fix: address maintainer review (PR #8437) — token-leak + dead code
choguun Jun 27, 2026
694ea95
chore(deps): add requirements-dev.txt for plugin and shared tests
choguun Jun 27, 2026
bd192e4
fix(deps): pin shared dev reqs to match consuming plugin runtime (cub…
choguun Jun 27, 2026
0964207
feat(plugins): add WhatsApp AI-clone plugin (v0.2)
choguun Jun 28, 2026
8a86eef
fix(whatsapp): address cubic review (9 issues — 3 P1, 6 P2)
choguun Jun 28, 2026
b903551
fix(whatsapp): validate display_phone_number before persisting pendin…
choguun Jun 28, 2026
3c5bc62
test(whatsapp): unique test module names + fix state-leakage fixture
choguun Jun 28, 2026
db04645
feat(desktop): add AI Clone screen (T-006)
choguun Jun 29, 2026
dc119c4
fix(desktop): address cubic review on AI Clone (PR #8528)
choguun Jun 29, 2026
9c2be94
fix(desktop): bind deep-link host check to the active plugin
choguun Jun 29, 2026
b2528c3
chore(desktop): add unreleased changelog fragment for AI Clone
choguun Jun 29, 2026
e3c57e4
chore(desktop): fix changelog fragment schema
choguun Jun 29, 2026
afe4ab8
fix(desktop): move AI Clone secrets from UserDefaults to Keychain
choguun Jun 29, 2026
cdeba25
fix(desktop): correct keychain isolation claim (cubic P2)
choguun Jun 29, 2026
3890a19
test(desktop): use object(forKey:) not data(forKey:) for UserDefaults…
choguun Jun 29, 2026
f78d05c
feat(desktop): zero-config plugin auto-discovery + improved AI Clone UI
choguun Jun 29, 2026
a44d5b7
fix(desktop): correct AIClonePage footer copy (cubic P2)
choguun Jun 29, 2026
c525362
fix(desktop): mark PluginURLCard.checkHealth as @MainActor (cubic P1)
choguun Jun 29, 2026
c3d9534
fix(desktop): validate plugin_url + surface effectivePublicURL in Inf…
choguun Jun 29, 2026
029a756
fix(desktop): extract applyDiscovery() from init + use local pluginUR…
choguun Jun 29, 2026
b925076
fix(desktop): disable auto-reply toggle in PluginCard (cubic P1)
choguun Jun 29, 2026
35c83d2
fix(telegram): add .dockerignore matching WhatsApp's (maintainer review)
choguun Jun 29, 2026
0b651b6
fix(whatsapp): hoist imports + sanitize HMAC mismatch log (maintainer…
choguun Jun 29, 2026
6eaf4d1
test(desktop): pin applyDiscovery separation from init (cubic P2 foll…
choguun Jun 29, 2026
f346aa4
feat(desktop): add TelegramTokenValidator + tests (Tier 1 UX improvem…
choguun Jun 29, 2026
5252289
feat(desktop): add QRCodeGenerator + tests (Tier 1 UX improvement)
choguun Jun 29, 2026
c4e6369
feat(desktop): add ClipboardWatcher + tests (Tier 1 UX improvement)
choguun Jun 29, 2026
f78b3be
feat(desktop): wire Tier 1 UX improvements into ConnectSheet
choguun Jun 29, 2026
ad82f34
chore(desktop): changelog entry for AI Clone Tier 1 UX improvements
choguun Jun 29, 2026
0c1e8cf
fix(plugins): enforce bearer auth on /setup + /toggle (security blocker)
choguun Jun 29, 2026
9f18893
fix(whatsapp): bump fastapi pin to 0.115.12 to drop vulnerable starlette
choguun Jun 29, 2026
7bfcc5a
fix(telegram): bump fastapi pin to 0.115.12 to drop vulnerable starlette
choguun Jun 29, 2026
c101e03
test(telegram): add conftest defaulting OMI_DEV_MODE=1
choguun Jun 29, 2026
e5eb23d
test(whatsapp): add load_main_module helper to conftest
choguun Jun 29, 2026
2a0e527
test(whatsapp): set placeholder WHATSAPP_APP_SECRET in auth tests
choguun Jun 29, 2026
6f485fb
fix(whatsapp): use WABA id for subscribed_apps (P1 functional bug)
choguun Jun 29, 2026
e811396
fix(desktop): distinct handshake-completed vs timed-out states + QR s…
choguun Jun 29, 2026
aa6802b
fix(desktop): split ClipboardWatcher sources so string read is lazy (P1)
choguun Jun 29, 2026
a8f620f
test(desktop): update ClipboardWatcher tests for split sources
choguun Jun 29, 2026
3e1b21c
fix(telegram): persist auto-generated webhook secret across restarts …
choguun Jun 29, 2026
9eec388
test(telegram): cover webhook-secret persistence (6 cases)
choguun Jun 29, 2026
b452a43
fix(whatsapp): tighten storage file perms + propagate write errors (P1)
choguun Jun 29, 2026
ba07646
fix(whatsapp): use base HTTPError (not HTTPStatusError) for 2xx waba-…
choguun Jun 29, 2026
e01e921
fix(telegram): secure webhook-secret persistence (3 P1s from cubic)
choguun Jun 29, 2026
5e8f424
test(telegram): update webhook-secret tests for hardened resolver
choguun Jun 29, 2026
e64a2d7
fix(whatsapp): unique temp filename per _save (P1 cubic)
choguun Jun 29, 2026
ac05dd5
feat(desktop): zero-config connect flow + plugin status + bug fixes
choguun Jun 29, 2026
f37a672
fix(desktop): security hardening — loopback URL check, remove backend…
choguun Jun 30, 2026
466e1b4
docs(desktop): correct overpromising security comment on persona helpers
choguun Jun 30, 2026
40c3a4e
fix(test): sync CI test fixes from PR #8531
choguun Jun 30, 2026
ac8afb2
fix(plugins): sync all security fixes from PR #8531 to desktop branch
choguun Jun 30, 2026
07885c2
fix(plugins): sync concurrent-safe discovery files from #8531
choguun Jun 30, 2026
3fb5b3d
test(whatsapp): isolate sys.modules via conftest helper to fix runtim…
choguun Jun 28, 2026
32ff3d3
feat(plugins): expose Omi Chat Tools manifest for AI Clone plugins (T…
choguun Jun 29, 2026
537f1e9
fix(telegram): restore send_message call lost in T-007 refactor
choguun Jun 29, 2026
664cdc8
test(telegram): add Layer 1 E2E simulator + runbook
choguun Jun 29, 2026
44a83b1
test(telegram): make sim_e2e actually catch the send_message regression
choguun Jun 29, 2026
b696b84
fix(backend): sanitize persona-chat logs + add is_a_persona + SSE format
choguun Jun 29, 2026
878293e
fix(plugins): wamid dedup + phone normalization + telegram_client JSO…
choguun Jun 29, 2026
3b85621
chore(plugins): add .dockerignore + telegram plugin; fix _shared README
choguun Jun 29, 2026
f0fdf52
fix(plugins): enforce bearer auth on /setup + /toggle (security blocker)
choguun Jun 29, 2026
81df784
fix(persona_client, auth): cubic round-3 fixes
choguun Jun 29, 2026
47821cf
chore(plugins): document .dockerignore build-context requirement
choguun Jun 29, 2026
d5a90d6
fix(whatsapp): hoist in-function imports + tighten dev-mode auth tests
choguun Jun 29, 2026
5cc7089
fix(telegram): redesign chat-tools manifest — drop bot_token parameter
choguun Jun 29, 2026
777b926
fix(whatsapp): redesign chat-tools manifest — drop access_token param…
choguun Jun 29, 2026
df7b7c9
docs(telegram): mark E2E_RUNBOOK.md as a process artifact
choguun Jun 29, 2026
275d63d
fix(backend): persona chat streaming — use astream() instead of agene…
choguun Jun 29, 2026
2d8a0b6
fix(plugins): harden plugin_discovery file/dir perms + require plugin…
choguun Jun 29, 2026
0a57961
fix(backend): restore LangSmith tracer so persona chat run_ids are real
choguun Jun 29, 2026
cb43796
fix(backend): add PERSONA_CHAT permission text to oauth authorize
choguun Jun 29, 2026
98b8987
fix(telegram): document COPY . . rationale alongside .dockerignore
choguun Jun 29, 2026
f0fcb6b
fix(telegram): document /toggle auth + body schema in README
choguun Jun 29, 2026
53b8ff4
fix(whatsapp): normalize tz-aware/naive timestamps in should_nudge
choguun Jun 29, 2026
12b6976
fix(test): assert auto-reply-disabled nudge body mentions 'auto-reply'
choguun Jun 29, 2026
8362e42
fix(sim_e2e): use exported STORAGE_DIR + plugin.log redirect + token …
choguun Jun 29, 2026
0eefbc3
style: apply black --line-length 120 to fixed files
choguun Jun 29, 2026
ff47f07
fix(backend): pass langsmith run_id via RunnableConfig (not just trac…
choguun Jun 29, 2026
1331e92
fix(telegram): correct README /toggle docs + remove unused pytest import
choguun Jun 29, 2026
004546f
fix: address 3 cubic follow-up findings on PR #8531
choguun Jun 29, 2026
4683c4e
chore(telegram): remove stale sim_e2e.py Layer-1 E2E script
choguun Jun 29, 2026
a21627c
chore(telegram): remove stale E2E_RUNBOOK.md + clean Dockerfile comment
choguun Jun 29, 2026
817410a
fix(whatsapp): refresh stale /toggle docs after bearer-token redesign
choguun Jun 29, 2026
030d958
fix(telegram): refresh manifest comments + add bearer conftest
choguun Jun 29, 2026
f20a7c8
fix: auth whitespace token rejection + qos test update for persona_chat
choguun Jun 30, 2026
b7031ea
fix(plugins): concurrent-safe discovery files + unique tmp filenames
choguun Jun 30, 2026
021b441
fix(backend): rewrite persona prompt to stop 'AI clone' leaks (T-019)
choguun Jun 30, 2026
440f89f
feat(plugins): pass sender + recent messages to persona-chat (T-020)
choguun Jun 30, 2026
0ebb57d
feat(backend): memory RAG for persona prompt (T-022)
choguun Jun 30, 2026
6b0a16f
test(backend): fix TestReEnableRouterBehavior __spec__ errors after T…
choguun Jun 30, 2026
64d8725
fix(plugins,backend): address cubic AI review on PR #8682
choguun Jun 30, 2026
11c1aad
fix(desktop,plugins): address remaining cubic AI review on PR #8682
choguun Jun 30, 2026
bb3c8bf
fix(plugins,backend): round 3 cubic AI review on PR #8682
choguun Jun 30, 2026
fd98424
fix(plugins): scope no-fsync optimization to history writes only
choguun Jun 30, 2026
92677d3
fix(plugins): restore durable _save + add parent-dir fsync
choguun Jun 30, 2026
ff283d5
fix(desktop): send tunnel/public URL as webhook target in Connect flow
choguun Jun 30, 2026
e0e5462
fix(desktop): always refresh publicBaseURL from discovery on startup
choguun Jun 30, 2026
8380259
docs(desktop): add E2E stack runner + AI Clone testing guide
choguun Jun 30, 2026
725924b
fix(ai-clone): address PR #8682 reviews — prompt-injection + mixed-co…
choguun Jun 30, 2026
2389bbb
fix(ai-clone): address cubic review #4601469127 — 3 real findings
choguun Jun 30, 2026
b9d5491
fix(ai-clone): address cubic review #4601668066 — 3 real findings
choguun Jun 30, 2026
0ce0c6d
fix(ai-clone): address cubic review #4601825081 — spy patching wrong …
choguun Jun 30, 2026
66117c9
docs(desktop): strip agent-skill frontmatter from ai-clone.md (review…
choguun Jun 30, 2026
f9bc844
style(backend): run black on apps.py post-rebase
choguun Jul 1, 2026
c21c8a2
fix(backend): unstub google.* + utils.llm so rebase works with main's…
choguun Jul 1, 2026
bab955f
fix(test): remove fragile _NullCallback fallback (cubic review #46076…
choguun Jul 1, 2026
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
1 change: 1 addition & 0 deletions backend/models/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ActionType(str, Enum):
READ_MEMORIES = "read_memories"
READ_CONVERSATIONS = "read_conversations"
READ_TASKS = "read_tasks"
PERSONA_CHAT = "persona_chat" # AI Clone plugins (Telegram/WhatsApp/iMessage)


class Action(BaseModel):
Expand Down
94 changes: 93 additions & 1 deletion backend/models/integrations.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List, Dict, Any
from enum import Enum
from datetime import datetime, timezone

from models.memories import MemoryCategory, MemoryDB

# Bounds for PersonaChatRequest.context / PersonaChatRequest.previous_messages.
# These mirror the server-side caps enforced in
# `routers/integration.persona_chat_via_integration` (20 turns, 8192 chars
# per turn, ~500 chars per recognized context key). Putting them at the
# Pydantic layer (P2 from cubic AI review) rejects oversized payloads at
# parse time instead of after a full JSON body has already been read into
# memory — defense against accidental 100MB bodies from a buggy client.
_PERSONA_CONTEXT_MAX_KEYS = 5
_PERSONA_CONTEXT_VALUE_MAX_CHARS = 200
_PERSONA_PREVIOUS_MESSAGES_MAX_ITEMS = 20
_PERSONA_PREVIOUS_MESSAGE_TEXT_MAX_CHARS = 8192


class ConversationTimestampRange(BaseModel):
start: int
Expand Down Expand Up @@ -53,6 +65,86 @@ class EmptyResponse(BaseModel):
pass


class PersonaChatRequest(BaseModel):
"""Single-turn persona chat request from a 3rd-party integration (e.g. AI clone plugins).

The optional `context` and `previous_messages` fields (added in T-020)
let the plugin tell the persona who they're talking to and what was
said in the recent turns. Without them, the LLM treats every inbound
webhook as a fresh conversation and can't answer "who am I?" /
"remind me about X" / "what did I just say?" in a way that's
grounded in the actual chat history. Both fields are optional — the
desktop persona chat (which has its own session continuity) still
works without them, and the regular `text`-only path is unchanged.
"""

# Telegram caps messages at 4096 chars; WhatsApp at ~65536; iMessage at
# ~20000. We pick a conservative 8192 so the cap covers the largest
# platform and the LLM has plenty of room to think.
text: str = Field(
description="The inbound message from the chat platform (1:1 DM, text only)", min_length=1, max_length=8192
)

context: Optional[dict] = Field(
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
default=None,
description=(
"Free-form platform context (sender name, sender username, chat type, "
"platform). Forwarded to the persona prompt as a SystemMessage so the "
"persona knows who they're talking to. Recognized keys: sender_name "
"(str), sender_username (str), chat_type ('private'|'group'), "
"platform ('telegram'|'whatsapp'|'imessage'). Unknown keys are "
"preserved verbatim — the renderer ignores them."
),
max_length=_PERSONA_CONTEXT_MAX_KEYS,
)

previous_messages: Optional[List[dict]] = Field(
default=None,
description=(
"Recent prior turns from the same chat, oldest first. Each entry is "
"{'role': 'human'|'ai', 'text': '<message>'}. Inserted into the "
"persona prompt as HumanMessage / AIMessage before the current "
"'text' HumanMessage. Capped at 20 entries server-side; per-text "
"length capped at 8192 to mirror the inbound text limit."
),
max_length=_PERSONA_PREVIOUS_MESSAGES_MAX_ITEMS,
)

@field_validator('context')
@classmethod
def _cap_context_values(cls, v: Optional[dict]) -> Optional[dict]:
# Pydantic's `max_length` checks the number of keys (Dict allows
# arbitrary types). We additionally cap each value's serialized
# length to keep an oversized sender_name etc. from filling
# memory before the server re-truncates.
if v is None:
return v
capped: dict = {}
for k, val in v.items():
if isinstance(val, str) and len(val) > _PERSONA_CONTEXT_VALUE_MAX_CHARS:
capped[k] = val[:_PERSONA_CONTEXT_VALUE_MAX_CHARS]
else:
capped[k] = val
return capped

@field_validator('previous_messages')
@classmethod
def _cap_previous_message_text(cls, v: Optional[List[dict]]) -> Optional[List[dict]]:
if v is None:
return v
# Mirror the server-side cap (text per turn) so a chatty buffer
# doesn't blow the request body budget.
capped: List[dict] = []
for turn in v:
if not isinstance(turn, dict):
continue
text = turn.get('text')
if isinstance(text, str) and len(text) > _PERSONA_PREVIOUS_MESSAGE_TEXT_MAX_CHARS:
turn = {**turn, 'text': text[:_PERSONA_PREVIOUS_MESSAGE_TEXT_MAX_CHARS]}
capped.append(turn)
return capped


class ConversationCreateResponse(BaseModel):
status: str
conversation_id: str
Expand Down
11 changes: 10 additions & 1 deletion backend/routers/apps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import json

Check warning on line 1 in backend/routers/apps.py

View workflow job for this annotation

GitHub Actions / Lint & Format Check

Large changed file

backend/routers/apps.py is 2056 lines; consider splitting files over 800 lines.
import logging
import os
import asyncio
Expand Down Expand Up @@ -1971,7 +1971,16 @@

key, hashed_key, label = generate_api_key()

data = {'id': str(ULID()), 'hashed': hashed_key, 'label': label, 'created_at': datetime.now(timezone.utc)}
data = {
'id': str(ULID()),
'hashed': hashed_key,
'label': label,
'created_at': datetime.now(timezone.utc),
# Stamp the uid on the key so sensitive endpoints (e.g. persona-chat)
# can verify the key was issued by this exact user, not just by anyone
# who happens to hold an app-level key.
'uid': uid,
}
create_api_key_db(app_id, data)

# Return both the raw key (for one-time display to user) and the stored data
Expand Down
Loading
Loading