Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions .github/workflows/docs-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Deploy Documentation

on:
push:
branches: [main]
paths: ['docs/**', '.github/workflows/docs-deploy.yml']
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'pnpm'

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build documentation
run: |
cd docs
pnpm build

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: './docs/dist'

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v3
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,46 @@ A comprehensive web-based debugging tool for RusticAI's Redis messaging system.
- **Thread View**: Track message threads and conversations
- **Developer Presence**: See which developers are currently monitoring

## Screenshots

<div align="center">

### Dashboard - Guild Overview
![Dashboard View](docs/src/assets/screenshots/dashboard-guilds.png)
*Monitor all active guilds with real-time metrics and message rates*

### Debug Views

<table>
<tr>
<td width="50%">

#### List View
![List View](docs/src/assets/screenshots/debug-list-view.png)
*Chronological message list with filtering*

</td>
<td width="50%">

#### Thread View
![Thread View](docs/src/assets/screenshots/debug-thread-view.png)
*Messages grouped by conversation threads*

</td>
</tr>
<tr>
<td colspan="2">

#### Graph View
![Graph View](docs/src/assets/screenshots/debug-graph-view.png)
*Interactive visualization of message flows between agents*

</td>
</tr>
</table>

</div>

## Architecture

This is a monorepo project using PNPM workspaces:
Expand Down
4 changes: 2 additions & 2 deletions backend/src/api/routes/messages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FastifyPluginAsync } from 'fastify';
import type { ApiResponse, PaginatedResponse, Message, MessageStatus } from '@rustic-debug/types';
import type { ApiResponse, PaginatedResponse, Message, ProcessStatus } from '@rustic-debug/types';
import { MessageHistoryService } from '../../services/messageHistory/index.js';
import { GuildDiscoveryService } from '../../services/guildDiscovery.js';
import { validateMessageId } from '../../utils/gemstoneId.js';
Expand Down Expand Up @@ -86,7 +86,7 @@ export const topicMessageRoutes: FastifyPluginAsync = async (fastify) => {
}

// Parse status array
const statusArray = status ? status.split(',') as MessageStatus[] : undefined;
const statusArray = status ? status.split(',') as ProcessStatus[] : undefined;

try {
const result = await messageService.getTopicMessages(guildId, topicName, {
Expand Down
5 changes: 4 additions & 1 deletion backend/src/models/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,12 @@ export class MessageModel {
const min = timeRange?.start?.getTime() || '-inf';
const max = timeRange?.end?.getTime() || '+inf';

// Build the full topic key (guildId:topicName in RusticAI)
const topicKey = guildId ? `${guildId}:${topicName}` : topicName;

// The sorted set contains full JSON messages
const rawMessages = await redis.zrangebyscore(
topicName,
topicKey,
min,
max
);
Expand Down
63 changes: 36 additions & 27 deletions backend/src/services/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,34 @@ export class ExportService {
): Promise<Buffer> {
const exportData = messages.map(message => {
const base: any = {
id: message.id.id,
guildId: message.guildId,
topicName: message.topicName,
timestamp: message.metadata.timestamp.toISOString(),
status: message.status.current,
id: message.id,
timestamp: new Date(message.timestamp).toISOString(),
format: message.format,
topics: message.topics,
sender: message.sender,
payload: message.payload,
is_error_message: message.is_error_message,
process_status: message.process_status,
};

if (options.includeMetadata) {
base.metadata = message.metadata;
base.priority = message.priority;
base.recipient_list = message.recipient_list;
base.thread = message.thread;
base.in_response_to = message.in_response_to;
base.conversation_id = message.conversation_id;
base.ttl = message.ttl;
}

if (options.includeRouting) {
base.routing = message.routing;
base.routing_slip = message.routing_slip;
base.forward_header = message.forward_header;
base.message_history = message.message_history;
}

return base;
});

return Buffer.from(JSON.stringify(exportData, null, 2));
}

Expand Down Expand Up @@ -120,31 +129,31 @@ export class ExportService {
const rows = [headers.join(',')];

for (const message of messages) {
const payloadStr = JSON.stringify(message.payload.content);
const payloadStr = JSON.stringify(message.payload);
const row = [
message.id.id,
message.guildId,
message.topicName,
message.metadata.timestamp.toISOString(),
message.status.current,
message.metadata.sourceAgent,
message.metadata.targetAgent || '',
message.payload.type,
message.id.toString(),
typeof message.topics === 'string' ? message.topics : message.topics.join(';'),
message.topic_published_to || '',
new Date(message.timestamp).toISOString(),
message.process_status || (message.is_error_message ? 'error' : 'completed'),
message.sender.name || message.sender.id || 'unknown',
message.recipient_list?.map(r => r.name || r.id).join(';') || '',
message.format,
payloadStr.length.toString(),
];

if (options.includeMetadata) {
row.push(
message.metadata.priority.toString(),
message.metadata.retryCount.toString(),
message.metadata.ttl?.toString() || ''
message.priority.toString(),
message.thread?.length.toString() || '0',
message.ttl?.toString() || ''
);
}

if (options.includeRouting) {
row.push(
message.routing.hops.length.toString(),
message.routing.destination || ''
message.message_history?.length.toString() || '0',
message.forward_header?.on_behalf_of?.name || ''
);
}

Expand Down
30 changes: 15 additions & 15 deletions backend/src/services/messageHistory/ordering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ export class MessageOrdering {
orderMessages(messages: Message[]): Message[] {
return messages.sort((a, b) => {
// First sort by timestamp
const timeDiff = a.metadata.timestamp.getTime() - b.metadata.timestamp.getTime();
const timeDiff = a.timestamp - b.timestamp;
if (timeDiff !== 0) {
return timeDiff;
}

// Same timestamp - sort by priority (higher priority first)
const priorityDiff = b.metadata.priority - a.metadata.priority;
const priorityDiff = b.priority - a.priority;
if (priorityDiff !== 0) {
return priorityDiff;
}
// Same timestamp and priority - use GemstoneID counter for stable ordering
return a.id.counter - b.id.counter;

// Same timestamp and priority - use message ID for stable ordering
return a.id - b.id;
});
}

Expand All @@ -34,19 +34,19 @@ export class MessageOrdering {
const rootMessages: Message[] = [];

for (const message of messages) {
if (message.parentMessageId) {
const siblings = childrenMap.get(message.parentMessageId) || [];
// Use in_response_to field to determine parent
if (message.in_response_to) {
const parentId = message.in_response_to.toString();
const siblings = childrenMap.get(parentId) || [];
siblings.push(message);
childrenMap.set(message.parentMessageId, siblings);
childrenMap.set(parentId, siblings);
} else {
rootMessages.push(message);
}
}

// Sort root messages by timestamp
rootMessages.sort((a, b) =>
a.metadata.timestamp.getTime() - b.metadata.timestamp.getTime()
);
rootMessages.sort((a, b) => a.timestamp - b.timestamp);

// Build ordered list with depth-first traversal
const ordered: Message[] = [];
Expand All @@ -57,9 +57,9 @@ export class MessageOrdering {
ordered.push(message);

// Add children recursively
const children = childrenMap.get(message.id.id) || [];
const children = childrenMap.get(message.id.toString()) || [];
children
.sort((a, b) => a.metadata.timestamp.getTime() - b.metadata.timestamp.getTime())
.sort((a, b) => a.timestamp - b.timestamp)
.forEach(child => addMessageAndChildren(child, depth + 1));
};

Expand All @@ -79,7 +79,7 @@ export class MessageOrdering {
const buckets = new Map<number, Message[]>();

for (const message of messages) {
const timestamp = message.metadata.timestamp.getTime();
const timestamp = message.timestamp;
const bucket = Math.floor(timestamp / bucketSizeMs) * bucketSizeMs;

const bucketMessages = buckets.get(bucket) || [];
Expand Down
Loading
Loading