Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
1f37fe8
docs: add TaskCast integration design spec
winrey Mar 13, 2026
b2bd696
chore: remove remaining task-tracker references from Dockerfiles
winrey Mar 13, 2026
b82af74
chore: remove task-tracker microservice
winrey Mar 13, 2026
aca29e5
docs: add TaskCast integration implementation plan
winrey Mar 14, 2026
5a7ca7e
docs: update TaskCast integration plan with deterministic ID pattern
winrey Mar 14, 2026
7d9eb08
chore: add @taskcast/server-sdk to gateway and task-worker
winrey Mar 14, 2026
bd21806
feat(tasks): replace TaskCastService TODO stubs with real @taskcast/s…
winrey Mar 14, 2026
49f5a46
feat(task-worker): create TaskCast client and replace UUID placeholde…
winrey Mar 14, 2026
bc02431
feat(tasks): integrate TaskCastService into TaskBotService for event …
winrey Mar 14, 2026
c3bcfbc
fix(webhook): parse executionId from deterministic TaskCast ID prefix
winrey Mar 14, 2026
5198dcc
feat(tasks): integrate TaskCastService into TasksService for pause/re…
winrey Mar 14, 2026
5d4e79b
feat(tasks): add SSE proxy endpoint for TaskCast event streaming
winrey Mar 14, 2026
77ebf10
chore: add @taskcast/client to frontend
winrey Mar 14, 2026
9f259a0
feat(client): add useExecutionStream SSE hook for TaskCast events
winrey Mar 14, 2026
8f9177a
feat(client): add SSE streaming to TaskDetailPanel, reduce polling wh…
winrey Mar 14, 2026
50c121c
feat(client): add SSE streaming to TaskBasicInfoTab
winrey Mar 14, 2026
08e6717
feat(client): add SSE streaming to RunDetailView, reduce polling when…
winrey Mar 14, 2026
762dd8a
test(tasks): add unit tests for TaskCastService
winrey Mar 14, 2026
c97bd0b
test(webhook): add unit tests for WebhookController timeout handler
winrey Mar 14, 2026
7a07a40
test(task-worker): add unit tests for TaskCastClient
winrey Mar 14, 2026
ebc4813
test(tasks): add unit tests for TasksStreamController SSE proxy
winrey Mar 14, 2026
fc73efc
test(tasks): add unit tests for TasksService TaskCast integration
winrey Mar 14, 2026
d14a385
test(tasks): add unit tests for TaskBotService TaskCast integration
winrey Mar 14, 2026
8c51f74
Merge branch 'dev' into feat/tasks-module-preview
ivan2717 Mar 16, 2026
536da0a
feat(tasks): add custom strategy support and fix refetchInterval
ivan2717 Mar 16, 2026
c667b22
feat(tasks): snapshot task version on execution and implement OpenCla…
ivan2717 Mar 16, 2026
8278095
test(tasks): add unit tests for OpenclawStrategy and ExecutorService
ivan2717 Mar 16, 2026
28cc8e7
docs: add bot debugger design spec
winrey Mar 16, 2026
7aa3154
docs: address spec review feedback for bot debugger
winrey Mar 16, 2026
06a362b
docs: add bot debugger implementation plan
winrey Mar 16, 2026
e50eb50
feat(debugger): scaffold Vite + React project
winrey Mar 16, 2026
1196db0
feat(debugger): add event constants and type definitions
winrey Mar 16, 2026
ed3ae6f
feat(debugger): add EventStore with filtering and export
winrey Mar 16, 2026
362b2a8
feat(debugger): add ConnectionStore with profile management
winrey Mar 16, 2026
2a3a41e
feat(debugger): add DebugSocket service with event interception
winrey Mar 16, 2026
68b2191
feat(debugger): add REST API client for messages and bot info
winrey Mar 16, 2026
2720bad
feat(debugger): add three-column layout with TopBar and BottomBar
winrey Mar 16, 2026
e2df0ea
feat(debugger): add ConnectionPanel with profile management
winrey Mar 16, 2026
51e8826
feat(debugger): add ChannelList, BotInfo, and wire left panel
winrey Mar 16, 2026
f53c88a
feat(debugger): add EventFilter bar with direction, category, and search
winrey Mar 16, 2026
55f0e1f
feat(debugger): add semantic event renderers for messages, streaming,…
winrey Mar 16, 2026
f069fe6
feat(debugger): add EventStream with virtual scrolling and EventCard
winrey Mar 16, 2026
414df5a
fix(debugger): handle typing_start/stop, user_status_changed; remove …
winrey Mar 16, 2026
b25562c
feat(debugger): add QuickActions with message send and streaming simu…
winrey Mar 16, 2026
02a519d
feat(debugger): add ActionPanel with QuickActions, JsonEditor, and In…
winrey Mar 16, 2026
2ce56b0
feat(debugger): auto-load channels after WebSocket authentication
winrey Mar 16, 2026
abeab6a
fix(debugger): fix fractionalSecondDigits TS error in formatTimestamp
winrey Mar 16, 2026
8e10637
fix(debugger): fix setInterval leak, bound event array, add REST erro…
winrey Mar 16, 2026
e119dd5
feat(tasks): add hideHeader and readOnly props to ChannelView
ivan2717 Mar 17, 2026
e87d7b5
feat(tasks): add i18n keys for task module UI redesign
ivan2717 Mar 17, 2026
2dd5a05
feat(tasks): create TaskChatArea, TaskRunTab, TaskSettingsTab, TaskHi…
ivan2717 Mar 17, 2026
ee855c7
feat(tasks): rewrite TaskList with three-column run-centric layout
ivan2717 Mar 17, 2026
3387c41
refactor(tasks): remove old TaskDetailPanel, TaskBasicInfoTab, RunDet…
ivan2717 Mar 17, 2026
45a5abe
fix(tasks): remove duplicate page header, TaskList already has its own
ivan2717 Mar 17, 2026
2bd59de
feat(tasks): replace status grouping with filter tabs in task list
ivan2717 Mar 17, 2026
d6a340f
docs: add task module UI redesign spec, plans, and chat placeholder c…
ivan2717 Mar 17, 2026
2c028a0
docs: add task module hierarchy redesign spec
ivan2717 Mar 17, 2026
9940c57
docs: add task hierarchy redesign implementation plan
ivan2717 Mar 17, 2026
f7ea000
feat(tasks): add TaskRunItem and TaskSettingsDialog components
ivan2717 Mar 17, 2026
af77741
feat(tasks): rewrite TaskCard with expandable run list and settings gear
ivan2717 Mar 17, 2026
f51dd49
refactor(tasks): simplify TaskRightPanel to show only run details
ivan2717 Mar 17, 2026
40ee552
feat(tasks): rewrite TaskList with expandable task cards and settings…
ivan2717 Mar 17, 2026
ac12b97
refactor(tasks): remove TaskHistoryTab (absorbed into TaskCard)
ivan2717 Mar 17, 2026
a2e095f
docs: add OpenClaw task execute integration design spec
ivan2717 Mar 17, 2026
414b3ca
docs: add OpenClaw task execute integration implementation plan
ivan2717 Mar 17, 2026
7347367
feat: rewrite OpenclawStrategy with proper body, auth, timeout, and s…
ivan2717 Mar 17, 2026
26ac80e
feat: add stopExecution to ExecutorService, wire stop command in cons…
ivan2717 Mar 17, 2026
9db4ea5
fix(tasks): execution-scoped bot API, CAS task claiming, and foundati…
ivan2717 Mar 19, 2026
81a2c24
fix(tasks): validate task message before sending to OpenClaw, use tit…
ivan2717 Mar 20, 2026
7afd1b0
Merge branch 'feat/tasks-module-preview' of github.com:team9ai/team9 …
ivan2717 Mar 20, 2026
90a93d3
Merge branch 'main' into feat/tasks-module-preview
ivan2717 Mar 21, 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
342 changes: 342 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
# AGENTS.md

This file provides guidance to AI coding agents when working with code in this repository.

## Project Overview

Team9 is a full-stack instant messaging and team collaboration platform built as a monorepo. The backend uses NestJS with PostgreSQL (Drizzle ORM), while the frontend is a Tauri-based cross-platform desktop app (React + TypeScript) with real-time WebSocket communication via Socket.io.

## Common Commands

### Development

```bash
pnpm dev # Run server (gateway + im-worker) and client concurrently
pnpm dev:client # Web frontend only (Vite dev server)
pnpm dev:desktop # Tauri desktop app (hot reload)
pnpm dev:server # Gateway service only
pnpm dev:im-worker # Background IM worker service only
pnpm dev:server:all # Both gateway and im-worker services
```

### Database Operations

```bash
pnpm db:generate # Generate Drizzle schemas from TypeScript
pnpm db:migrate # Run pending migrations
pnpm db:push # Push schema changes to database (dev only)
pnpm db:studio # Open Drizzle Studio UI for database inspection
```

### Building

```bash
pnpm build # Build both server and client
pnpm build:server # Build NestJS backend
pnpm build:client # Build web client
pnpm build:client:mac # Build macOS Tauri app
pnpm build:client:windows # Build Windows Tauri app
```

### Production

```bash
pnpm start:prod # Start server in production mode
```

## Architecture

### Monorepo Structure

```
apps/
├── client/ # Tauri + React frontend
├── server/
│ ├── apps/
│ │ ├── gateway/ # Main API gateway (port 3000)
│ │ ├── im-worker/ # Background IM worker service (port 3001)
│ │ └── task-worker/ # Task execution worker service
│ └── libs/ # Shared libraries
│ ├── database/ # Drizzle schemas and DB module
│ ├── auth/ # Shared authentication
│ ├── ai-client/
│ ├── agent-framework/
│ ├── redis/
│ ├── rabbitmq/
│ └── shared/ # Common types, constants
└── debugger/ # Debug tool
```

### Backend Architecture (NestJS)

Entry point: `apps/server/apps/gateway/src/main.ts`

The backend follows a modular NestJS architecture with two main applications:

- **Gateway:** Main API service with REST endpoints and WebSocket gateway
- **IM Worker:** Background service for async processing (message persistence, routing, offline delivery)

Key Modules:

- **Auth Module** (`apps/server/apps/gateway/src/auth`): JWT-based authentication with Passport strategy, 7-day token expiry
- **IM Module** (`apps/server/apps/gateway/src/im`): Instant messaging functionality
- Channels: direct, public, private types
- Messages: text, file, image, system types with threading support (parentId)
- Users: profile management, status tracking
- WebSocket: Socket.io gateway for real-time events
- **Workspace Module** (`apps/server/apps/gateway/src/workspace`): Multi-tenant workspace management
- **Edition Module** (`apps/server/apps/gateway/src/edition`): Dynamic feature loading for Community vs Enterprise editions

Edition System:
The codebase supports Community and Enterprise editions via environment variable `EDITION=community|enterprise`. The Edition module conditionally loads features (e.g., TenantModule only in enterprise). Enterprise code lives in a separate git submodule at `enterprise/`.

Database Layer:

- Uses Drizzle ORM with PostgreSQL
- Schemas organized by domain in `apps/server/libs/database/schemas`:
- **im/**: users, channels, messages, channel_members, message_attachments, message_reactions, message_acks, mentions, user_channel_read_status
- **tenant/**: tenants, tenant_members, workspace_invitations
- All migrations managed via `pnpm db:migrate`
- Schema changes pushed via `pnpm db:push` (dev) or `pnpm db:generate` + `pnpm db:migrate` (prod)

### Frontend Architecture (Tauri + React)

Entry point: `apps/client/src/main.tsx`

State Management:

- **Zustand** for UI state: theme, user profile, loading states
- App store: `apps/client/src/stores/app.ts`
- Workspace store: `apps/client/src/stores/workspace.ts`
- Home store: `apps/client/src/stores/home.ts`
- **TanStack React Query** for server state: messages, channels, users (caching, invalidation)
- Local component state for UI-only interactions

Routing:

- **TanStack Router** with file-based routing in `apps/client/src/routes`
- Protected routes via `_authenticated` layout
- Automatic route generation from directory structure

HTTP Client:

- Custom HttpClient class at `apps/client/src/services/http.ts`
- Request/response interceptors for auth tokens and error handling
- Centralized API client at `apps/client/src/services/api.ts`

WebSocket Service:

- Singleton pattern at `apps/client/src/services/websocket.ts`
- Auto-reconnection with exponential backoff
- Event queuing for offline operations
- Type-safe event emitters
- Channel join/leave lifecycle management

### Real-Time Communication

WebSocket Events (Socket.io):

Message Operations:

- `new_message`: Server broadcasts new messages to channel members
- `mark_as_read` → `read_status_updated`: Read receipt tracking
- `add_reaction` → `reaction_added`, `reaction_removed`: Message reactions

User Presence:

- `user_online`, `user_offline`: Connection status
- `user_status_changed`: Status updates (online/offline/away/busy)
- `typing_start`, `typing_stop` → `user_typing`: Typing indicators

Channel Management:

- `join_channel`, `leave_channel`: Channel subscription lifecycle

Message Features:

- Threading via `parentId` field
- Mentions: @user, @channel, @everyone (parsed server-side)
- Attachments: file, image types
- Reactions: emoji-based reactions per message
- Read status: per-user, per-channel tracking via `user_channel_read_status` table

### Key Development Patterns

Adding a New Database Table:

1. Define schema in `apps/server/libs/database/schemas` using Drizzle syntax
2. Export from the appropriate index file (im/index.ts or tenant/index.ts)
3. Run `pnpm db:generate` to generate migration
4. Run `pnpm db:migrate` to apply migration
5. Update database module to inject the new table

Adding a New API Endpoint:

1. Create controller method in appropriate module (auth, im, workspace)
2. Implement business logic in service layer
3. Use Drizzle to query database via injected DatabaseService
4. Add DTO classes for request/response validation
5. Apply appropriate guards (JwtAuthGuard for protected routes)

Adding a New WebSocket Event:

1. Define event handler in `apps/server/apps/gateway/src/im/websocket/websocket.gateway.ts`
2. Emit response events via `this.server.to(channelId).emit(event, data)`
3. Add client-side listener in `apps/client/src/services/websocket.ts`
4. Update React Query cache or Zustand store based on event data

Adding a New Frontend Route:

1. Create file in `apps/client/src/routes` following TanStack Router conventions
2. Use `_authenticated` layout for protected routes
3. Define loader functions for data fetching
4. Implement component with hooks for state/query management

## External Dependencies

### OpenClaw Hive

Team9 acts as a **client** of the OpenClaw Hive Control Plane API. The integration module lives at `apps/server/apps/gateway/src/openclaw/`:

- **OpenclawService** (`openclaw.service.ts`): HTTP client that calls the Control Plane API to manage instances
- **OpenclawModule** (`openclaw.module.ts`): Global NestJS module exporting the service

Data flow:

1. When a bot is created in Team9, `OpenclawService` calls the Control Plane API (`POST /api/instances`) to provision an OpenClaw instance
2. Team9 passes `TEAM9_TOKEN` and `TEAM9_BASE_URL` as env vars so the OpenClaw instance can call back to Team9's IM APIs
3. The OpenClaw instance connects to Team9 via REST API and WebSocket (Socket.io) to send/receive messages

Environment variables (Team9 side):

- `OPENCLAW_API_URL`: Control Plane base URL (e.g., `https://plane.claw.team9.ai`)
- `OPENCLAW_AUTH_TOKEN`: Bearer token for authenticating with the Control Plane API

Control Plane API endpoints (called by Team9's OpenclawService):

| Method | Endpoint | Description |
| ------ | -------------------------- | ---------------------------------------------- |
| GET | `/api/instances` | List all instances |
| GET | `/api/instances/:id` | Get instance by ID |
| POST | `/api/instances` | Create new instance (`{id, subdomain?, env?}`) |
| DELETE | `/api/instances/:id` | Delete instance |
| POST | `/api/instances/:id/start` | Start instance |
| POST | `/api/instances/:id/stop` | Stop instance |

Authentication model:

- Team9 → Control Plane: `Authorization: Bearer <OPENCLAW_AUTH_TOKEN>`
- OpenClaw instance → Team9: `Authorization: Bearer <TEAM9_TOKEN>` (JWT generated per bot)

Team9 REST API endpoints called by the OpenClaw plugin:

| Method | Endpoint | Description |
| ------ | ------------------------------------------ | ---------------------------------------------- |
| GET | `/api/v1/users/me` | Get bot's own user profile |
| GET | `/api/v1/users/:userId` | Get user by ID |
| GET | `/api/v1/im/channels` | List channels |
| GET | `/api/v1/im/channels/:id` | Get channel by ID |
| POST | `/api/v1/im/channels/dm/:targetUserId` | Get or create DM channel |
| GET | `/api/v1/im/channels/:id/messages` | Get channel messages |
| POST | `/api/v1/im/channels/:id/messages` | Send message (supports `parentId` for threads) |
| PATCH | `/api/v1/im/messages/:id` | Update message |
| DELETE | `/api/v1/im/messages/:id` | Delete message |
| POST | `/api/v1/im/messages/:id/reactions` | Add reaction |
| DELETE | `/api/v1/im/messages/:id/reactions/:emoji` | Remove reaction |
| POST | `/api/v1/im/channels/:id/read` | Mark channel as read |

> **Important:** When modifying Team9's IM APIs or WebSocket events, changes must stay compatible with the OpenClaw plugin.

### TaskCast

Team9 acts as a **client** of a TaskCast server instance, using `@taskcast/server-sdk` to create tasks, transition statuses, and publish events. The frontend subscribes to task progress via SSE (Server-Sent Events) proxied through the Team9 gateway.

Integration points in Team9:

| Component | Path | Role |
| ----------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `TaskCastService` | `apps/server/apps/gateway/src/tasks/taskcast.service.ts` | Creates TaskCast tasks, transitions status, publishes events |
| `TasksStreamController` | `apps/server/apps/gateway/src/tasks/tasks-stream.controller.ts` | SSE proxy — authenticates user, verifies access, proxies upstream TaskCast SSE |
| `TaskCastClient` | `apps/server/apps/task-worker/src/taskcast/taskcast.client.ts` | Creates TaskCast tasks from the worker service |
| `WebhookController` | `apps/server/apps/task-worker/src/webhook/webhook.controller.ts` | Receives TaskCast timeout webhooks, updates execution/task status |
| `useExecutionStream` | `apps/client/src/hooks/useExecutionStream.ts` | React hook — opens SSE to stream execution events |

Data flow:

1. When a task execution starts, `TaskCastService` creates a TaskCast task with deterministic ID `agent_task_exec_${executionId}`
2. During execution, events are published to TaskCast via `publishEvent()`
3. The frontend opens an SSE connection through the gateway's proxy endpoint (`GET /api/v1/tasks/:taskId/executions/:execId/stream`)
4. The gateway authenticates the user, verifies workspace membership, then proxies the SSE stream from TaskCast
5. On task timeout, TaskCast calls the webhook endpoint (`POST /webhooks/taskcast/timeout`), which updates the DB status

Environment variables (Team9 side):

- `TASKCAST_URL`: TaskCast server base URL (default: `http://localhost:3721`)
- `TASKCAST_WEBHOOK_SECRET`: Shared secret for validating incoming TaskCast webhooks

Key concepts:

- **Task lifecycle:** `pending → running → completed|failed|timeout|cancelled` (no backward transitions)
- **Deterministic IDs:** Team9 uses `agent_task_exec_${executionId}` pattern — no DB lookup needed for TaskCast task ID

### aHand

Team9 integrates with **aHand** — a local execution gateway for cloud AI that lets cloud-side orchestrators run tools on local machines behind NAT/firewalls via WebSocket.

Architecture:

```
Cloud (WS server) ←── WebSocket (protobuf) ──→ Local daemon (WS client)
│ │
@ahand/sdk ahandd
(control plane) (job executor)
├─ shell / tools
├─ browser automation
└─ policy enforcement
```

- **SDK** (`@ahand/sdk`): TypeScript cloud control plane SDK
- **Daemon** (`ahandd`): Rust binary enforcing local security policy before executing any job
- **Protocol:** Protocol Buffers over WebSocket

Session modes enforced by the daemon:

| Mode | Behavior |
| --------------- | ----------------------------------------------------- |
| **Inactive** | Default — rejects all jobs until activated |
| **Strict** | Every command requires manual approval |
| **Trust** | Auto-approve with inactivity timeout (default 60 min) |
| **Auto-Accept** | Auto-approve, no timeout |

## Technology Stack

Frontend:

- React 19, TypeScript 5.8+, Tauri 2
- TanStack Router 1.141, TanStack React Query 5.90
- Zustand 5.0, Socket.io-client
- Radix UI, Tailwind CSS 4.1, Lucide icons
- Vite 7

Backend:

- NestJS 11, TypeScript 5.8+
- PostgreSQL + Drizzle ORM
- Socket.io, JWT + Passport
- Redis, RabbitMQ
- Anthropic AI SDK, Google Generative AI

Tooling:

- pnpm workspaces, ESLint, Prettier
- Jest (testing), SWC (compilation)
- Husky + lint-staged (pre-commit hooks)

## Prerequisites

- Node.js >= 18.0.0
- pnpm >= 8.0.0
- Rust toolchain (for Tauri builds)
- PostgreSQL (local or remote)
- Redis (for caching/sessions)
- RabbitMQ (for message queuing)
1 change: 1 addition & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@tanstack/react-router": "^1.141.6",
"@tanstack/router-devtools": "^1.141.6",
"@tanstack/router-plugin": "^1.141.7",
"@taskcast/client": "^1.1.0",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-deep-link": "^2.4.7",
"@tauri-apps/plugin-opener": "^2",
Expand Down
16 changes: 14 additions & 2 deletions apps/client/src/components/channel/ChannelView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ interface ChannelViewProps {
initialDraft?: string;
// Preview channel data for non-members (public channel preview mode)
previewChannel?: PublicChannelPreview;
// Hide the built-in ChannelHeader (e.g. when a parent component provides its own header)
hideHeader?: boolean;
// Show a read-only bar instead of the message input
readOnly?: boolean;
}

/**
Expand All @@ -60,6 +64,8 @@ export function ChannelView({
initialMessageId,
initialDraft,
previewChannel,
hideHeader,
readOnly,
}: ChannelViewProps) {
const isPreviewMode = !!previewChannel;
const { data: memberChannel, isLoading: channelLoading } = useChannel(
Expand Down Expand Up @@ -329,7 +335,9 @@ export function ChannelView({
<div
className={`flex-1 flex flex-col min-w-0 ${isSnapped ? "hidden" : ""}`}
>
<ChannelHeader channel={channel} currentUserRole={currentUserRole} />
{!hideHeader && (
<ChannelHeader channel={channel} currentUserRole={currentUserRole} />
)}

{showOverlay ? (
<BotStartupOverlay
Expand Down Expand Up @@ -381,7 +389,11 @@ export function ChannelView({
/>
)}

{isPreviewMode ? (
{readOnly ? (
<div className="px-4 py-3 border-t border-border bg-muted/30 text-center">
<span className="text-sm text-muted-foreground">Read-only</span>
</div>
) : isPreviewMode ? (
<JoinChannelPrompt
channelId={channelId}
channelName={channel.name || ""}
Expand Down
Loading