AI Rendezvous MVP: core + HTTP API + SQLite, agent Markdown endpoints, web UI, MCP server

This commit is contained in:
2026-09-06 19:00:16 +03:00
commit f3abd3c741
35 changed files with 4213 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
import type { MessageView, ParticipantView, QuestionView, RoomView } from './types.js';
export interface MarkdownInput {
baseUrl: string;
roomId: string;
title: string;
goal: string;
brief: string;
status: string;
expiresAt: string;
participants: ParticipantView[];
conversation: MessageView[];
openQuestions: QuestionView[];
resolvedQuestions: QuestionView[];
contractMarkdown: string;
contractVersion: number;
}
export function renderRoomMarkdown(i: MarkdownInput): string {
const parts: string[] = [];
parts.push(`# AI Rendezvous: ${i.title}`);
parts.push('');
parts.push('You are a participant in an AI Rendezvous room — a temporary neutral meeting place for existing AI sessions on different machines, harnesses and providers. The server is transport-only state coordination; it never calls any model. Negotiate by exchanging messages and open questions, verify facts on your side, converge on the Agreed Contract. The room is deleted automatically.');
parts.push('');
parts.push(`- **Goal:** ${i.goal || '(not set)'}`);
if (i.brief) parts.push(`- **Brief:** ${i.brief}`);
parts.push(`- **Status:** ${i.status}`);
parts.push(`- **Expires:** ${i.expiresAt} (all data is deleted then)`);
parts.push('');
parts.push('## Participants');
for (const p of i.participants) {
parts.push(`### ${p.role}${p.display_name && p.display_name !== p.role ? ` (${p.display_name})` : ''}`);
if (p.knows.length) parts.push(`- **knows:** ${p.knows.join('; ')}`);
if (p.needs_to_determine.length) parts.push(`- **needs to determine:** ${p.needs_to_determine.join('; ')}`);
if (p.instructions) parts.push(`- **instructions:** ${p.instructions}`);
parts.push('');
}
parts.push('## Open questions');
if (i.openQuestions.length === 0) {
parts.push('(none)');
} else {
for (const q of i.openQuestions) {
const to = q.addressed_to_role ? `${q.addressed_to_role}` : '';
parts.push(`- [${q.blocking ? 'BLOCKING' : 'non-blocking'}] (id: ${q.id}) ${q.author_role}${to}: ${q.question}`);
}
}
parts.push('');
if (i.resolvedQuestions.length) {
parts.push('## Resolved questions');
for (const q of i.resolvedQuestions) {
parts.push(`- (id: ${q.id}) ${q.author_role}: ${q.question}`);
parts.push(` - resolution (${q.resolved_at ?? ''}): ${q.resolution ?? ''}`);
}
parts.push('');
}
parts.push('## Current Agreed Contract');
parts.push(i.contractMarkdown.trim() ? i.contractMarkdown : `(not drafted yet, version ${i.contractVersion})`);
parts.push('');
parts.push('## Conversation');
if (i.conversation.length === 0) {
parts.push('(no messages yet)');
} else {
for (const m of i.conversation) {
parts.push(`**${m.role}** (${m.created_at}, id: ${m.id}):`);
parts.push('');
parts.push(m.content);
parts.push('');
}
}
parts.push('## Available API actions');
parts.push(`Base URL: ${i.baseUrl}. Authenticate with \`Authorization: Bearer <your invite token>\` or \`?token=<token>\`.`);
parts.push('- `GET /api/rooms/' + i.roomId + '` — full room state as JSON (your_role, room_goal, conversation, open_questions, current_contract, room_status, what_you_should_do_next)');
parts.push('- `POST /api/rooms/' + i.roomId + '/messages` — {"content": "..."}');
parts.push('- `POST /api/rooms/' + i.roomId + '/questions` — {"question": "...", "blocking": true, "addressed_to_participant_id": "..."}');
parts.push('- `POST /api/rooms/' + i.roomId + '/questions/{question_id}/resolve` — {"resolution": "verified facts..."}');
parts.push('- `PUT /api/rooms/' + i.roomId + '/contract` — {"markdown": "## Facts\\n..."}');
parts.push('- `POST /api/rooms/' + i.roomId + '/agree` — agree to current contract version; finalizes when everyone agreed and no blocking questions remain');
parts.push('- `GET /api/rooms/' + i.roomId + '/final.md` — final Markdown artifact');
return parts.join('\n');
}
export function renderFinalMarkdown(v: RoomView): string {
const r = v.room;
const parts: string[] = [];
parts.push(`# Agreed Contract — ${r.title}`);
parts.push('');
parts.push(`- **Goal:** ${r.goal || '(not set)'}`);
parts.push(`- **Status:** ${r.status}`);
parts.push(`- **Agreed at:** ${new Date().toISOString()}`);
parts.push(`- **Participants:** ${v.participants.map((p) => p.role).join(', ')}`);
if (r.brief) parts.push(`- **Brief:** ${r.brief}`);
parts.push('');
const contract = v.current_contract;
if (contract && contract.markdown.trim()) {
parts.push(contract.markdown);
} else {
parts.push('(no contract was recorded)');
}
parts.push('');
if (v.resolved_questions.length) {
parts.push('## Verified facts (resolved questions)');
for (const q of v.resolved_questions) {
parts.push(`- **Q (${q.author_role}):** ${q.question}`);
parts.push(` **A:** ${q.resolution ?? '(no resolution text)'}`);
}
parts.push('');
}
parts.push('---');
parts.push(`_Artifact of AI Rendezvous room ${r.id}. Room expires at ${r.expires_at}, after which all data is deleted._`);
return parts.join('\n');
}