Unified activity timeline in Conversation: joins, questions, resolutions, contract, agreements
deploy / deploy (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
This commit is contained in:
@@ -105,6 +105,7 @@ export function buildRouter(service: RendezvousService, cfg: ServerConfig) {
|
||||
resolvedQuestions: view.resolved_questions,
|
||||
contractMarkdown: view.current_contract?.markdown ?? '',
|
||||
contractVersion: view.current_contract?.version ?? 0,
|
||||
contract: view.current_contract,
|
||||
secretsPolicyShort: secretsPolicyShort(cfg.baseUrl),
|
||||
}), 'text/markdown; charset=utf-8');
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
import type { ObserverView, RoomView } from '@ai-rendezvous/core';
|
||||
import { secretsPolicyShort } from '@ai-rendezvous/core';
|
||||
import { secretsPolicyShort, buildTimeline } from '@ai-rendezvous/core';
|
||||
import { escapeHtml } from './http.js';
|
||||
|
||||
/** Unified activity timeline as HTML: every participant action in chronological order. */
|
||||
function timelineHtml(
|
||||
participants: ObserverView['participants'],
|
||||
conversation: ObserverView['conversation'],
|
||||
openQuestions: ObserverView['open_questions'],
|
||||
resolvedQuestions: ObserverView['resolved_questions'],
|
||||
contract: ObserverView['current_contract'],
|
||||
): string {
|
||||
const events = buildTimeline({ participants, conversation, openQuestions, resolvedQuestions, contract });
|
||||
return (
|
||||
events
|
||||
.map((e) => {
|
||||
const body = e.body
|
||||
? `<pre>${escapeHtml(e.body)}</pre>`
|
||||
: '';
|
||||
return `<div class="msg msg-${e.kind}"><div class="meta"><b>${escapeHtml(e.role)}</b> · ${escapeHtml(e.action)} · ${escapeHtml(e.at)}</div>${body}</div>`;
|
||||
})
|
||||
.join('') || '<p class="hint">(no activity yet)</p>'
|
||||
);
|
||||
}
|
||||
|
||||
/** Machine-readable instruction page served at /create.md — this is what an agent reads first. */
|
||||
export function createMarkdownDoc(baseUrl: string): string {
|
||||
return `# AI Rendezvous — create a room
|
||||
@@ -92,6 +113,9 @@ const PAGE_CSS = `
|
||||
code, pre { font-family: ui-monospace, monospace; font-size: 0.85rem; }
|
||||
pre { background: rgba(127,127,127,.12); padding: .75rem 1rem; border-radius: 6px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
|
||||
.msg { border-left: 3px solid rgba(127,127,127,.4); padding: .25rem 0 .25rem .75rem; margin: .75rem 0; }
|
||||
.msg-joined, .msg-agreed, .msg-contract { border-left-color: rgba(60,140,80,.75); }
|
||||
.msg-asked { border-left-color: rgba(210,130,30,.8); }
|
||||
.msg-resolved { opacity: .75; }
|
||||
.meta { color: rgba(127,127,127,.9); font-size: .8rem; }
|
||||
.q { padding: .5rem .75rem; border-radius: 6px; margin: .5rem 0; }
|
||||
.q.blocking { background: rgba(200,60,60,.14); }
|
||||
@@ -196,12 +220,7 @@ export function observerHtmlPage(o: ObserverView, token: string): string {
|
||||
return `${mark} (${joined})`;
|
||||
})
|
||||
.join(', ');
|
||||
const msgs = o.conversation
|
||||
.map(
|
||||
(m) =>
|
||||
`<div class="msg"><div class="meta"><b>${escapeHtml(m.role)}</b> · ${escapeHtml(m.created_at)}</div><pre>${escapeHtml(m.content)}</pre></div>`,
|
||||
)
|
||||
.join('');
|
||||
const msgs = timelineHtml(o.participants, o.conversation, o.open_questions, o.resolved_questions, o.current_contract);
|
||||
const openQs = o.open_questions
|
||||
.map(
|
||||
(q) =>
|
||||
@@ -225,8 +244,8 @@ export function observerHtmlPage(o: ObserverView, token: string): string {
|
||||
${resolvedQs ? `<h2>Resolved questions</h2>${resolvedQs}` : ''}
|
||||
<h2>Agreed Contract (v${contract ? contract.version : 0})</h2>
|
||||
<pre>${escapeHtml(contract ? contract.markdown : '(not drafted yet)')}</pre>
|
||||
<h2>Conversation</h2>${msgs || '<p class="hint">(no messages yet)</p>'}
|
||||
<p class="hint">Read-only observer view; refreshes every 10s. Also available as Markdown: append <code>.md</code> to this URL.</p>`,
|
||||
<h2>Conversation</h2>${msgs}
|
||||
<p class="hint">Full activity timeline: joins, messages, questions, resolutions, contract proposals and agreements. Read-only observer view; refreshes every 10s. Also available as Markdown: append <code>.md</code> to this URL.</p>`,
|
||||
o.room_status === 'open',
|
||||
);
|
||||
}
|
||||
@@ -351,12 +370,7 @@ If a secret must move between the sides, record SECRET_TRANSFER_REQUIRED plus th
|
||||
|
||||
/** Read-only human view of the negotiation (same token auth as the API). */
|
||||
export function roomHtmlPage(v: RoomView, token: string): string {
|
||||
const msgs = v.conversation
|
||||
.map(
|
||||
(m) =>
|
||||
`<div class="msg"><div class="meta"><b>${escapeHtml(m.role)}</b> · ${escapeHtml(m.created_at)}</div><pre>${escapeHtml(m.content)}</pre></div>`,
|
||||
)
|
||||
.join('');
|
||||
const msgs = timelineHtml(v.participants, v.conversation, v.open_questions, v.resolved_questions, v.current_contract);
|
||||
const openQs = v.open_questions
|
||||
.map(
|
||||
(q) =>
|
||||
@@ -379,7 +393,8 @@ export function roomHtmlPage(v: RoomView, token: string): string {
|
||||
${resolvedQs ? `<h2>Resolved questions</h2>${resolvedQs}` : ''}
|
||||
<h2>Agreed Contract (v${contract ? contract.version : 0})</h2>
|
||||
<pre>${escapeHtml(contract ? contract.markdown : '(not drafted yet)')}</pre>
|
||||
<h2>Conversation</h2>${msgs || '<p class="hint">(no messages yet)</p>'}
|
||||
<h2>Conversation</h2>${msgs}
|
||||
<p class="hint">Full activity timeline: joins, messages, questions, resolutions, contract proposals and agreements.</p>
|
||||
<p class="hint">Agent endpoints: <code>/r/${escapeHtml(v.room.id)}/<token>.md</code> · <code>GET /api/rooms/${escapeHtml(v.room.id)}</code> · <a href="/api/rooms/${escapeHtml(v.room.id)}?token=">JSON</a> · <a href="/api/rooms/${escapeHtml(v.room.id)}/final.md">final.md</a></p>
|
||||
<p class="hint">Read-only view; refreshes every 10s.</p>`,
|
||||
v.room.status === 'open',
|
||||
|
||||
Reference in New Issue
Block a user