mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Wave 2 features: A2A live view (CEO chime-in + reply budget) and prompter memory (#297)
* feat(a2a): live view — watch fleet conversations, CEO chime-in, reply budget A2A_MESSAGE_SENT published from A2AService.send (excerpt-capped) and fanned through the existing /ws/system bridge; CEO-only admin REST for conversations/messages + a reply route on the publish-bearing send path; panel /a2a page with live transcript and a composer gated on task-linked conversations. The matrix gains its one asymmetric rule: CEO may message anyone, nobody may target the CEO — and agent replies inside a CEO-opened conversation are hard-budgeted to one per CEO message (per conversation, per agent), rejected with wait-don't-retry guidance. Built subagent-driven (Sonnet 5), reviewed; v1 seams documented in the map delta. * feat(prompter): intake remembers the task history Intake spawns now carry a per-project chronological digest of recent tasks (capped: 15 lines/project, 4000 chars total — ~300-1000 tokens) merged into the ambient layer, and the interviewer gets a bounded search_past_tasks tool (one shared implementation behind the grok MCP tool and the Claude SDK in-process tool) to check precedent mid-conversation. Informational memory only — the sequencing analyzer keeps ownership of ordering. Built subagent-driven (Sonnet 5), reviewed; pre-existing conventions-ambient MegaTask-scope gap flagged, untouched. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -58,6 +58,57 @@ export interface A2AStreamChunk {
|
||||
is_final: boolean;
|
||||
}
|
||||
|
||||
/** Summary row for the CEO's admin view of an agent-to-agent conversation. */
|
||||
export interface AdminConversationSummary {
|
||||
id: string;
|
||||
agent_a: string;
|
||||
agent_b: string;
|
||||
topic: string | null;
|
||||
task_id: string | null;
|
||||
status: string;
|
||||
message_count: number;
|
||||
last_message_at: string | null;
|
||||
last_message_preview: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** One persisted A2A chat message (full body — WS frames only carry excerpts). */
|
||||
export interface A2AChatMessage {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
from_agent: string;
|
||||
content: string;
|
||||
message_kind: string;
|
||||
response_to_id: string | null;
|
||||
requires_response: boolean;
|
||||
read_at: string | null;
|
||||
created_at: string;
|
||||
edited_at: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CEO reply payload. The backend sends a DIRECT CEO -> to_agent message (it
|
||||
* lands in the CEO<->to_agent pairwise conversation), not an injection into
|
||||
* the watched transcript.
|
||||
*/
|
||||
export interface AdminReplyRequest {
|
||||
to_agent: string;
|
||||
content: string;
|
||||
skill?: string | null;
|
||||
}
|
||||
|
||||
export interface AdminConversationListResponse {
|
||||
items: AdminConversationSummary[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface AdminMessageListResponse {
|
||||
items: A2AChatMessage[];
|
||||
total: number;
|
||||
has_more: boolean;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// API Client
|
||||
// =============================================================================
|
||||
@@ -197,4 +248,106 @@ export const a2aApi = {
|
||||
const { data } = await api.get<A2AAgentCard>(`/a2a/agents/${agentId}/card`);
|
||||
return data;
|
||||
},
|
||||
|
||||
// ===========================================================================
|
||||
// ADMIN (CEO) ENDPOINTS — A2A live view
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* List agent<->agent conversations, most-recent-first (CEO-only).
|
||||
*/
|
||||
listAdminConversations: async (
|
||||
limit: number = 50,
|
||||
): Promise<AdminConversationListResponse> => {
|
||||
if (isMockMode()) {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
id: "mock-conversation-1",
|
||||
agent_a: "be-dev-1",
|
||||
agent_b: "be-qa",
|
||||
topic: "QA handoff",
|
||||
task_id: null,
|
||||
status: "active",
|
||||
message_count: 3,
|
||||
last_message_at: now,
|
||||
last_message_preview: "Tests are green on the branch.",
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
};
|
||||
}
|
||||
const { data } = await api.get<AdminConversationListResponse>(
|
||||
"/a2a/chat/admin/conversations",
|
||||
{ params: { limit } },
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* List a conversation's messages, chronological oldest-first (CEO-only).
|
||||
*/
|
||||
listAdminMessages: async (
|
||||
conversationId: string,
|
||||
limit: number = 100,
|
||||
): Promise<AdminMessageListResponse> => {
|
||||
if (isMockMode()) {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
id: "mock-a2a-message-1",
|
||||
conversation_id: conversationId,
|
||||
from_agent: "be-dev-1",
|
||||
content: "Branch is ready for QA.",
|
||||
message_kind: "text",
|
||||
response_to_id: null,
|
||||
requires_response: false,
|
||||
read_at: null,
|
||||
created_at: now,
|
||||
edited_at: null,
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
const { data } = await api.get<AdminMessageListResponse>(
|
||||
`/a2a/chat/admin/conversations/${conversationId}/messages`,
|
||||
{ params: { limit } },
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a CEO reply. Lands in the CEO<->to_agent pairwise conversation (the
|
||||
* A2A model is strictly pairwise), NOT inside the watched transcript.
|
||||
*/
|
||||
replyAsCeo: async (
|
||||
conversationId: string,
|
||||
request: AdminReplyRequest,
|
||||
): Promise<A2AChatMessage> => {
|
||||
if (isMockMode()) {
|
||||
return {
|
||||
id: `mock-reply-${Date.now()}`,
|
||||
conversation_id: conversationId,
|
||||
from_agent: "ceo",
|
||||
content: request.content,
|
||||
message_kind: "text",
|
||||
response_to_id: null,
|
||||
requires_response: false,
|
||||
read_at: null,
|
||||
created_at: new Date().toISOString(),
|
||||
edited_at: null,
|
||||
};
|
||||
}
|
||||
const { data } = await api.post<A2AChatMessage>(
|
||||
`/a2a/chat/admin/conversations/${conversationId}/reply`,
|
||||
request,
|
||||
);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user