Skip to content

Commit 00302cb

Browse files
authored
Merge pull request #69 from ghostwright/release/v0.19.0
chore: v0.19.0 release - chat channel, docs, Docker infrastructure
2 parents 039fe05 + 379297f commit 00302cb

20 files changed

Lines changed: 219 additions & 28 deletions

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ local/
1212
*.db-shm
1313
.DS_Store
1414
bun.lockb
15+
chat-ui/node_modules
16+
chat-ui/dist
17+
chat-ui/.vite

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ ANTHROPIC_API_KEY=
3434
# Owner's Slack user ID (starts with U) - only this user can talk to Phantom
3535
# OWNER_SLACK_USER_ID=
3636

37+
# ========================
38+
# OPTIONAL: Web Chat Login
39+
# ========================
40+
# If Slack is not configured, Phantom can send you a magic link email
41+
# to log into the web chat at /chat. Set your email and a Resend API key.
42+
# Without Resend, a bootstrap token is printed to container logs instead.
43+
44+
# OWNER_EMAIL=
45+
# RESEND_API_KEY is also used for the phantom_email tool (see below).
46+
3747
# ========================
3848
# OPTIONAL: Identity
3949
# ========================

CLAUDE.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Phantom
22

3-
Phantom is an autonomous AI co-worker that runs as a persistent Bun process on a VM. It wraps the Claude Agent SDK as a subprocess (Anthropic by default, swappable via a `provider:` config block to Z.AI/GLM-5.1, OpenRouter, Ollama, vLLM, LiteLLM, or any Anthropic Messages API compatible endpoint). It maintains vector-backed memory across sessions, rewrites its own configuration through a validated self-evolution engine, communicates via Slack/Telegram/Email/Webhook, and exposes all capabilities as an MCP server. 27,000+ lines of TypeScript, 875 tests, v0.18.2. Apache 2.0, repo at ghostwright/phantom.
3+
Phantom is an autonomous AI co-worker that runs as a persistent Bun process on a VM. It wraps the Claude Agent SDK as a subprocess (Anthropic by default, swappable via a `provider:` config block to Z.AI/GLM-5.1, OpenRouter, Ollama, vLLM, LiteLLM, or any Anthropic Messages API compatible endpoint). It maintains vector-backed memory across sessions, rewrites its own configuration through a validated self-evolution engine, communicates via Slack/Web Chat/Telegram/Email/Webhook, and exposes all capabilities as an MCP server. 30,000+ lines of TypeScript, 1,584 tests, v0.19.0. Apache 2.0, repo at ghostwright/phantom.
44

55
## Tech Stack
66

@@ -10,7 +10,8 @@ Phantom is an autonomous AI co-worker that runs as a persistent Bun process on a
1010
| Agent | Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`) subprocess. Provider is configurable via `src/config/providers.ts`: Anthropic (default), Z.AI, OpenRouter, Ollama, vLLM, LiteLLM, custom. |
1111
| Memory | Qdrant (vector DB, Docker) + Ollama (nomic-embed-text, local embeddings) |
1212
| State | SQLite via Bun (sessions, tasks, metrics, evolution versions, scheduled jobs) |
13-
| Channels | Slack (Socket Mode, primary), Telegram (long polling), Email (IMAP/SMTP), Webhook (HMAC-SHA256), CLI |
13+
| Channels | Slack (Socket Mode), Web Chat (SSE streaming), Telegram (long polling), Email (IMAP/SMTP), Webhook (HMAC-SHA256), CLI |
14+
| Chat Client | React 19 + Vite + shadcn/ui + Tailwind v4 SPA at `/chat` |
1415
| Web UI | Tailwind v4 Browser CDN + DaisyUI v5, static files from public/ |
1516
| MCP | Streamable HTTP on /mcp, bearer token auth, 17+ tools |
1617
| Infrastructure | Docker (compose), Specter VMs (Hetzner), systemd (bare metal) |
@@ -41,13 +42,22 @@ If you find yourself writing a function that does something the agent can do bet
4142

4243
```bash
4344
bun install # Install dependencies
44-
bun test # Run 875 tests
45+
bun test # Run 1,584 tests
4546
bun run src/index.ts # Start the server
4647
bun run src/cli/main.ts init --yes # Initialize config (reads env vars)
4748
bun run src/cli/main.ts doctor # Check all subsystems
4849
bun run src/cli/main.ts status # Quick one-liner status
4950
bun run lint # Biome check
5051
bun run typecheck # tsc --noEmit
52+
53+
# Chat UI (separate build)
54+
cd chat-ui && bun install # Install chat-ui dependencies
55+
cd chat-ui && bun run build # Build production SPA to chat-ui/dist/
56+
cd chat-ui && bun run typecheck # Type-check the chat client
57+
cd chat-ui && bun run dev # Dev server on :5173 (proxy to :3100)
58+
59+
# Chat-UI dev loop: run Phantom on :3100 in one terminal, Vite on :5173 in another.
60+
# Vite proxies /chat/* API calls to :3100. Open http://localhost:5173/chat.
5161
```
5262

5363
## Project Structure
@@ -60,6 +70,19 @@ src/
6070
prompt-assembler.ts # System prompt: base + role + evolved + memory context
6171
hooks.ts # Safety hooks (dangerous command blocker, file tracker)
6272
in-process-tools.ts # In-process MCP tool servers (dynamic, scheduler, web UI)
73+
chat/
74+
http.ts # SSE endpoint, session management, message routing
75+
http-handlers.ts # Request handlers for chat API routes
76+
types.ts # 32-event SSE wire format (discriminated union)
77+
sdk-to-wire.ts # Translates Agent SDK events to chat wire frames
78+
session-store.ts # In-memory chat session state
79+
message-store.ts # Persistent message history (SQLite)
80+
stream-bus.ts # Fan-out SSE event bus (multi-tab support)
81+
upload.ts # File attachment upload handler
82+
validators.ts # Type allowlist and size limits for attachments
83+
first-run.ts # Email-based first login (Resend)
84+
email-login.ts # Magic link email delivery
85+
notifications/ # Web Push (VAPID keys, subscriptions, triggers)
6386
channels/
6487
slack.ts # Slack Socket Mode (primary channel, owner access control)
6588
telegram.ts # Telegram via Telegraf
@@ -116,6 +139,7 @@ src/
116139
config/ # YAML configs (phantom.yaml, channels.yaml, mcp.yaml, roles/)
117140
phantom-config/ # Evolved agent config (constitution, persona, domain knowledge)
118141
public/ # Web UI files (_base.html template, index.html)
142+
chat-ui/ # React 19 SPA (Vite + shadcn + Tailwind v4). Built to public/chat/
119143
scripts/
120144
install.sh # Standalone install script for Ubuntu/Debian
121145
docker-entrypoint.sh # Docker bootstrap (wait for deps, model pull, init)
@@ -124,7 +148,7 @@ docs/ # Documentation (architecture, channels, mcp, security,
124148

125149
## Architecture Overview
126150

127-
Message flow: Slack message -> SlackChannel adapter -> ChannelRouter -> SessionManager (find/create session) -> PromptAssembler (base + role + evolved config + memory context) -> AgentRuntime.query() (Opus 4.6 with full tools) -> response -> ChannelRouter -> Slack thread reply.
151+
Message flow: Slack message -> SlackChannel adapter -> ChannelRouter -> SessionManager (find/create session) -> PromptAssembler (base + role + evolved config + memory context) -> AgentRuntime.query() (Opus 4.6 with full tools) -> response -> ChannelRouter -> Slack thread reply. Web chat uses the same flow: POST /chat/sessions/:id/message -> SSE stream of wire frames -> React client renders in real time. Two separate transcripts (wire-format message store for the client, SDK conversation for the agent) are kept in sync.
128152

129153
After each session: EvolutionEngine runs 6-step reflection pipeline -> 5-gate validation -> approved changes applied to phantom-config/ -> version bumped.
130154

@@ -193,6 +217,7 @@ Production deployments are managed internally. Do NOT modify production deployme
193217
| `src/channels/slack.ts` | Primary channel. Owner access control, threading, reactions. |
194218
| `src/mcp/server.ts` | MCP server setup. Tool registration, auth integration. |
195219
| `src/memory/system.ts` | Memory coordinator. How the three tiers connect. |
220+
| `src/chat/http.ts` | Web chat backend. SSE streaming, session routing, API handlers. |
196221
| `src/core/server.ts` | HTTP server. Routes, health endpoint, version. |
197222
| `config/roles/swe.yaml` | SWE role template. Onboarding questions, tools, evolution focus. |
198223
| `phantom-config/constitution.md` | Immutable principles the evolution engine cannot modify. |

CONTRIBUTING.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ If you are unsure whether something belongs in TypeScript or in a prompt, open a
8787
## Running Tests
8888

8989
```bash
90-
# Run the full suite (770 tests)
90+
# Run the full suite (1,584 tests)
9191
bun test
9292

9393
# Run a single test file
@@ -101,6 +101,11 @@ bun run lint
101101

102102
# Typecheck
103103
bun run typecheck
104+
105+
# Chat UI (separate build, separate package.json)
106+
cd chat-ui && bun install # Install chat-ui dependencies
107+
cd chat-ui && bun run build # Build production SPA
108+
cd chat-ui && bun run typecheck # Type-check the chat client
104109
```
105110

106111
All three must pass before submitting a PR: tests, lint, and typecheck.

Dockerfile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ RUN DPKG_ARCH=$(dpkg --print-architecture) && \
7070
# Claude Code CLI refuses --dangerously-skip-permissions when running as root,
7171
# so the container MUST run as a non-root user. Docker socket access is granted
7272
# via group_add in docker-compose.yaml (matching the host's docker GID).
73-
RUN groupadd --system phantom && \
74-
useradd --system --gid phantom --create-home --home-dir /home/phantom phantom && \
73+
RUN groupadd --system --gid 999 phantom && \
74+
useradd --system --uid 999 --gid phantom --create-home --home-dir /home/phantom phantom && \
7575
mkdir -p /home/phantom/.claude && \
7676
chown -R phantom:phantom /home/phantom
7777

@@ -124,6 +124,9 @@ RUN mkdir -p /app/data /app/repos && \
124124
# Backup phantom-config defaults so they survive empty volume mount
125125
RUN cp -r /app/phantom-config /app/phantom-config-defaults
126126

127+
# Backup image-bundled public assets for entrypoint seeding
128+
RUN cp -r /app/public /app/public-defaults
129+
127130
# Make entrypoint executable
128131
RUN chmod +x /app/scripts/docker-entrypoint.sh
129132

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
<p align="center">
99
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
10-
<img src="https://img.shields.io/badge/tests-875%20passed-brightgreen.svg" alt="Tests">
10+
<img src="https://img.shields.io/badge/tests-1584%20passed-brightgreen.svg" alt="Tests">
1111
<a href="https://hub.docker.com/r/ghostwright/phantom"><img src="https://img.shields.io/docker/pulls/ghostwright/phantom.svg" alt="Docker Pulls"></a>
12-
<img src="https://img.shields.io/badge/version-0.18.2-orange.svg" alt="Version">
12+
<img src="https://img.shields.io/badge/version-0.19.0-orange.svg" alt="Version">
1313
</p>
1414

1515
<p align="center">
@@ -27,7 +27,7 @@ AI agents today are disposable. You open a chat, get an answer, close the tab, a
2727

2828
Phantom takes a different approach: **give the AI its own computer.** A dedicated machine where it installs software, spins up databases, builds dashboards, remembers what you told it last week, and gets measurably better at your job every day. Your laptop stays yours. The agent's workspace is its own.
2929

30-
This is not a chatbot. It is a co-worker that runs on Slack, has its own email address, creates its own tools, and builds infrastructure without asking for permission. Don't take our word for it - scroll down to see what production Phantoms have actually built.
30+
This is not a chatbot. It is a co-worker that runs on Slack, has a web chat interface at `/chat`, has its own email address, creates its own tools, and builds infrastructure without asking for permission. Don't take our word for it - scroll down to see what production Phantoms have actually built.
3131

3232
## What This Actually Looks Like
3333

@@ -220,6 +220,7 @@ Because the agent that can only use pre-built tools hits a ceiling. Phantom buil
220220
| **Dynamic tools** | Creates and registers its own MCP tools at runtime. Tools survive restarts and work across sessions. |
221221
| **Encrypted secrets** | AES-256-GCM encrypted forms with magic-link auth. No plain-text credentials in config files. |
222222
| **Email identity** | Every Phantom has its own email address. Send reports to people outside your Slack workspace. |
223+
| **Web chat** | A full browser-based chat client at `/chat` with SSE streaming, file attachments, and Web Push notifications. No Slack required. |
223224
| **Shareable pages** | Generates dashboards and tools on a public URL with auth. Share a link, anyone can see it. |
224225
| **MCP server** | Claude Code connects to your Phantom. Other Phantoms connect to your Phantom. It is an API, not a dead end. |
225226

@@ -238,10 +239,10 @@ Because the agent that can only use pre-built tools hits a ceiling. Phantom buil
238239
| |
239240
| Channels Agent Runtime |
240241
| Slack query() + hooks |
241-
| Telegram Prompt Assembler |
242-
| Email base + role + evolved |
243-
| Webhook + memory context |
244-
| CLI |
242+
| Web Chat Prompt Assembler |
243+
| Telegram base + role + evolved |
244+
| Email + memory context |
245+
| Webhook / CLI |
245246
| |
246247
| Memory System Self-Evolution Engine |
247248
| Qdrant 6-step pipeline |
@@ -379,7 +380,7 @@ bun run phantom start
379380
```
380381

381382
```bash
382-
bun test # 770 tests
383+
bun test # 1584 tests
383384
bun run lint # Biome
384385
bun run typecheck # tsc --noEmit
385386
```

docker-compose.user.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ services:
6060
ollama:
6161
condition: service_started
6262
restart: unless-stopped
63+
oom_score_adj: -500
64+
cpu_shares: 2048
6365
deploy:
6466
resources:
6567
limits:
@@ -69,6 +71,7 @@ services:
6971
# consolidation) spawned via runJudgeQuery. The prior 2 GiB cap
7072
# cgroup-OOM-killed judge subprocesses under evolution load.
7173
memory: 8G
74+
pids: 256
7275
reservations:
7376
memory: 512M
7477
networks:

docs/architecture.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,26 @@ Phantom is a single Bun process that runs on a VM. It combines an agent runtime,
5858

5959
### HTTP Server
6060

61-
`src/core/server.ts` - Bun.serve() on port 3100. Three routes:
61+
`src/core/server.ts` - Bun.serve() on port 3100. Key routes:
6262
- `/health` - JSON health status (status, uptime, version, channels, memory, evolution)
6363
- `/mcp` - MCP Streamable HTTP endpoint
6464
- `/webhook` - Inbound webhook receiver
65+
- `/chat/*` - Web chat API (SSE streaming, sessions, attachments, push subscriptions)
66+
- `/ui/*` - Static pages and login (magic link auth)
6567

6668
### Channel Router
6769

6870
`src/channels/router.ts` - Multiplexes messages from all connected channels. Each channel implements the `Channel` interface: `connect()`, `disconnect()`, `send()`, `onMessage()`.
6971

70-
Channels: Slack (Socket Mode), Telegram (long polling), Email (IMAP/SMTP), Webhook (HTTP), CLI (readline).
72+
Channels: Slack (Socket Mode), Web Chat (SSE streaming at `/chat`), Telegram (long polling), Email (IMAP/SMTP), Webhook (HTTP), CLI (readline).
73+
74+
### Web Chat Channel
75+
76+
`src/chat/` - A full browser-based chat channel with a React 19 SPA at `/chat`. The backend uses Server-Sent Events (SSE) to stream a 32-event wire format from the Agent SDK to the client in real time. Two independent transcripts are maintained: the wire-format message store (what the client sees) and the SDK conversation (what the agent sees). This two-transcript invariant means the client can render markdown, tool calls, thinking blocks, and subagent progress without coupling to SDK internals.
77+
78+
Auth uses cookie-based sessions with magic link login. On first run without Slack, a login email is sent via Resend (or a bootstrap token is printed to stdout). Web Push notifications (VAPID) alert users when the agent responds while the tab is in the background.
79+
80+
File attachments (images, PDFs, text files) are uploaded via multipart POST and passed to the agent as context. Type allowlist and size limits are enforced server-side.
7181

7282
### Agent Runtime
7383

@@ -114,7 +124,7 @@ Embeddings via Ollama (nomic-embed-text, 768d vectors). Hybrid search using dens
114124

115125
## Data Flow
116126

117-
1. Message arrives via channel (Slack mention, webhook POST, etc.)
127+
1. Message arrives via channel (Slack mention, webhook POST, web chat, etc.)
118128
2. Channel router normalizes to `InboundMessage`
119129
3. Session manager finds or creates a session
120130
4. Prompt assembler builds the full system prompt
@@ -123,6 +133,8 @@ Embeddings via Ollama (nomic-embed-text, 768d vectors). Hybrid search using dens
123133
7. Memory consolidation runs (non-blocking)
124134
8. Evolution pipeline runs (non-blocking)
125135

136+
For web chat specifically: the client sends `POST /chat/sessions/:id/message`, the server starts an Agent SDK `query()`, and SDK events are translated to wire frames and pushed to all connected SSE streams for that session (supporting multi-tab). The wire format includes session lifecycle, text streaming, thinking blocks, tool calls with input streaming, and subagent progress.
137+
126138
## Technology Stack
127139

128140
| Component | Technology |
@@ -132,7 +144,8 @@ Embeddings via Ollama (nomic-embed-text, 768d vectors). Hybrid search using dens
132144
| Vector DB | Qdrant (Docker) |
133145
| Embeddings | Ollama (nomic-embed-text) |
134146
| State DB | SQLite (Bun built-in) |
135-
| Channels | Slack Bolt, Telegraf, ImapFlow, Nodemailer |
147+
| Channels | Slack Bolt, Web Chat (SSE), Telegraf, ImapFlow, Nodemailer |
148+
| Chat Client | React 19, Vite, shadcn/ui, Tailwind v4 |
136149
| Config | YAML + Zod validation |
137150
| Process | systemd (on Specter VMs) |
138151

@@ -141,6 +154,7 @@ Embeddings via Ollama (nomic-embed-text, 768d vectors). Hybrid search using dens
141154
```
142155
src/
143156
agent/ - Runtime, prompt assembler, hooks, cost tracking
157+
chat/ - Web chat backend (SSE streaming, sessions, attachments, push notifications)
144158
channels/ - Slack, Telegram, Email, Webhook, CLI, status reactions
145159
cli/ - CLI commands (init, start, doctor, token, status)
146160
config/ - YAML config loaders, Zod schemas

docs/channels.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,53 @@
22

33
Phantom communicates through pluggable channel adapters. Each channel implements a standard interface, and the agent does not care where messages originate.
44

5+
## Web Chat
6+
7+
A browser-based chat interface at `/chat`. No Slack required. This is the simplest way to talk to a Phantom - open the URL in a browser and start typing.
8+
9+
### Access
10+
11+
Navigate to `https://your-phantom-host/chat`. On first visit, you will be prompted to log in.
12+
13+
### Authentication
14+
15+
Cookie-based sessions with magic link login:
16+
17+
1. Enter your email address on the login page
18+
2. Phantom sends a magic link via Resend (requires `RESEND_API_KEY` in `.env`)
19+
3. Click the link to authenticate. The session cookie lasts 30 days.
20+
21+
On first run without Slack configured, Phantom sends a login email to `OWNER_EMAIL` automatically. If Resend is not configured, a bootstrap token is printed to stdout instead.
22+
23+
### Configuration
24+
25+
Set these in `.env`:
26+
27+
```
28+
OWNER_EMAIL=you@example.com # Required for email-based login
29+
RESEND_API_KEY=re_... # Required for magic link emails
30+
```
31+
32+
No channel YAML configuration is needed. The chat channel is always available when the HTTP server is running.
33+
34+
### Features
35+
36+
- **SSE streaming** - responses stream token-by-token via Server-Sent Events
37+
- **32-event wire format** - session lifecycle, text, thinking blocks, tool calls with input streaming, subagent progress
38+
- **Multi-tab support** - open the same session in multiple tabs, all stay in sync
39+
- **File attachments** - upload images (JPEG, PNG, GIF, WebP up to 10 MB), PDFs (up to 32 MB), and text/code files (up to 1 MB). Up to 10 files per message.
40+
- **Web Push notifications** - get notified when the agent responds while the tab is in the background. Uses VAPID keys stored in SQLite.
41+
- **Session management** - create, rename, archive, and delete sessions from the sidebar
42+
- **Markdown rendering** - full markdown with code syntax highlighting, tables, and lists
43+
- **Auto-rename** - sessions are automatically titled based on the first exchange
44+
45+
### Tech Stack
46+
47+
The chat client is a React 19 SPA built with Vite, shadcn/ui, and Tailwind v4. The production build lives at `public/chat/` and is served as static files. The Dockerfile includes a dedicated build stage for the chat client.
48+
549
## Slack
650

7-
The primary channel. Uses Socket Mode (no public URL required).
51+
Uses Socket Mode (no public URL required).
852

953
### Setup (App Manifest)
1054

0 commit comments

Comments
 (0)