Add read-only observer URL with 'whose turn' indicator
deploy / deploy (push) Canceled after 0s

Creating agents now must return the human both the other participant's
invite URL and the observer link. Fixes: human had no way to watch a room
or see whose move it is without holding a participant token.
This commit is contained in:
2026-09-06 20:20:41 +03:00
parent 61919ab00d
commit 91fc74efc1
13 changed files with 280 additions and 27 deletions
+43 -1
View File
@@ -1,4 +1,46 @@
import type { MessageView, ParticipantView, QuestionView, RoomView } from './types.js';
import type { MessageView, ObserverView, ParticipantView, QuestionView, RoomView } from './types.js';
/** Read-only Markdown state for the human observer of a room. */
export function renderObserverMarkdown(o: ObserverView, baseUrl: string): string {
const parts: string[] = [];
parts.push(`# AI Rendezvous (observer): ${o.room.title}`);
parts.push('');
parts.push(`Read-only observer view — this link cannot post messages or agree. Goal: ${o.room.goal || '(not set)'}.`);
parts.push(`Status: ${o.room_status} · expires ${o.room.expires_at} (all data deleted then).`);
parts.push('');
parts.push(`**Whose turn:** ${o.turn}`);
parts.push('');
parts.push(`Participants: ${o.participants.map((p) => p.role).join(', ')}`);
parts.push('');
parts.push('## Open questions');
if (o.open_questions.length === 0) parts.push('(none)');
else
for (const q of o.open_questions)
parts.push(`- [${q.blocking ? 'BLOCKING' : 'non-blocking'}] (id: ${q.id}) ${q.author_role}${q.addressed_to_role ? `${q.addressed_to_role}` : ''}: ${q.question}`);
parts.push('');
parts.push('## Agreed Contract');
parts.push(o.current_contract?.markdown?.trim() || '(not drafted yet)');
parts.push('');
if (o.resolved_questions.length) {
parts.push('## Resolved questions');
for (const q of o.resolved_questions) {
parts.push(`- ${q.author_role}: ${q.question}`);
parts.push(` - resolution: ${q.resolution ?? ''}`);
}
parts.push('');
}
parts.push('## Conversation');
if (o.conversation.length === 0) parts.push('(no messages yet)');
else
for (const m of o.conversation) {
parts.push(`**${m.role}** (${m.created_at}):`);
parts.push('');
parts.push(m.content);
parts.push('');
}
parts.push(`---\n_Observer link for room ${o.room.id}. Final artifact: ${baseUrl}/api/rooms/${o.room.id}/final.md (requires a participant token)._`);
return parts.join('\n');
}
export interface MarkdownInput {
baseUrl: string;