AI Rendezvous MVP: core + HTTP API + SQLite, agent Markdown endpoints, web UI, MCP server
This commit is contained in:
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user