| 1 | import { describe, expect, it } from 'vitest';
|
| 2 | import { buildApi } from '../apps/api/src/app.js';
|
| 3 | import { buildPublicAgentCard } from '../packages/persistence/src/hosted.js';
|
| 4 | import type { AgentProfile } from '../packages/persistence/src/onboarding.js';
|
| 5 | import type {
|
| 6 | FeedbackRequest,
|
| 7 | FederatedInteraction,
|
| 8 | PublicAgentCard,
|
| 9 | } from '../packages/protocol/src/index.js';
|
| 10 |
|
| 11 | describe('dashboard quickstart API', () => {
|
| 12 | it('creates an agent, starts an agreement, and records owner outcome and feedback', async () => {
|
| 13 | const rows: { kind: string; recordId: string; payload: any }[] = [];
|
| 14 | const cards = new Map<string, PublicAgentCard>();
|
| 15 | let interaction: FederatedInteraction | undefined;
|
| 16 | let feedbackRequest: FeedbackRequest | undefined;
|
| 17 | const peerProfile: AgentProfile = {
|
| 18 | agentId: 'agent-peer',
|
| 19 | projectId: 'project-peer',
|
| 20 | name: 'Peer runtime',
|
| 21 | description: 'Does product research',
|
| 22 | framework: 'Custom runtime',
|
| 23 | agentVersion: '1.0.0',
|
| 24 | agentMode: 'persistent_runtime',
|
| 25 | a2aEndpoint: 'https://peer.example/a2a',
|
| 26 | transport: 'direct_a2a',
|
| 27 | autoPublish: true,
|
| 28 | autoAcceptPolicy: 'off',
|
| 29 | autoAcceptTaskCategories: [],
|
| 30 | capabilities: ['research'],
|
| 31 | limitations: [],
|
| 32 | identityMode: 'owner_managed',
|
| 33 | status: 'active',
|
| 34 | createdAt: '2026-08-31T10:00:00.000Z',
|
| 35 | updatedAt: '2026-08-31T10:00:00.000Z',
|
| 36 | };
|
| 37 | cards.set('agent-peer', buildPublicAgentCard(peerProfile, 'https://openclasp.example'));
|
| 38 | const repository = {
|
| 39 | list: async () => rows,
|
| 40 | upsert: async (_operatorId: string, kind: string, recordId: string, payload: any) => {
|
| 41 | const current = rows.find((row) => row.kind === kind && row.recordId === recordId);
|
| 42 | if (current) current.payload = payload;
|
| 43 | else rows.push({ kind, recordId, payload });
|
| 44 | },
|
| 45 | publishAgent: async (_operatorId: string, card: PublicAgentCard) => {
|
| 46 | cards.set(card.agentId, card);
|
| 47 | return card;
|
| 48 | },
|
| 49 | getPublishedAgent: async (agentId: string) => cards.get(agentId),
|
| 50 | createFederatedInteraction: async (_operatorId: string, value: FederatedInteraction) => {
|
| 51 | interaction = value;
|
| 52 | return value;
|
| 53 | },
|
| 54 | getFederatedInteraction: async () => interaction,
|
| 55 | getLiveSession: async () => ({ activatedAt: '2026-08-31T10:01:00.000Z' }),
|
| 56 | submitCompletionReport: async (
|
| 57 | _operatorId: string,
|
| 58 | _agentId: string,
|
| 59 | report: any,
|
| 60 | method: string,
|
| 61 | ) => {
|
| 62 | rows.push({ kind: 'completion_report', recordId: report.reportId, payload: report });
|
| 63 | feedbackRequest = {
|
| 64 | requestId: '33333333-3333-4333-8333-333333333333',
|
| 65 | interactionId: report.interactionId,
|
| 66 | reviewerAgentId: report.reportingAgentId,
|
| 67 | subjectAgentId: report.counterpartyAgentId,
|
| 68 | status: 'pending',
|
| 69 | requestedDimensions: ['overall_satisfaction'],
|
| 70 | requestedAt: '2026-08-31T10:02:00.000Z',
|
| 71 | dueAt: '2026-09-01T10:02:00.000Z',
|
| 72 | };
|
| 73 | return { report, method };
|
| 74 | },
|
| 75 | listFeedbackRequests: async () => (feedbackRequest ? [feedbackRequest] : []),
|
| 76 | submitInteractionFeedback: async (
|
| 77 | _operatorId: string,
|
| 78 | _agentId: string,
|
| 79 | feedback: any,
|
| 80 | method: string,
|
| 81 | ) => ({ feedback, method }),
|
| 82 | } as any;
|
| 83 | const app = buildApi(undefined, undefined, repository);
|
| 84 | await app.ready();
|
| 85 | const headers = { 'x-openclasp-operator': 'owner-a' };
|
| 86 |
|
| 87 | const createdResponse = await app.inject({
|
| 88 | method: 'POST',
|
| 89 | url: '/v0.1/quickstart/agent',
|
| 90 | headers,
|
| 91 | payload: {
|
| 92 | agentName: 'Research assistant',
|
| 93 | projectName: 'My agents',
|
| 94 | description: 'Returns sourced comparisons',
|
| 95 | capabilities: ['research'],
|
| 96 | },
|
| 97 | });
|
| 98 | expect(createdResponse.statusCode).toBe(200);
|
| 99 | const created = createdResponse.json();
|
| 100 | expect(created.card).toMatchObject({
|
| 101 | agentMode: 'temporary_chat',
|
| 102 | transports: [{ managedBy: 'openclasp' }],
|
| 103 | });
|
| 104 |
|
| 105 | const startedResponse = await app.inject({
|
| 106 | method: 'POST',
|
| 107 | url: '/v0.1/federated-interactions/start',
|
| 108 | headers,
|
| 109 | payload: {
|
| 110 | initiatorAgentId: created.agent.agentId,
|
| 111 | responderAgentId: 'agent-peer',
|
| 112 | task: 'Compare three customer support platforms',
|
| 113 | requestedOutcome: 'A ranked shortlist',
|
| 114 | successCriterion: 'Three current products with source links',
|
| 115 | },
|
| 116 | });
|
| 117 | expect(startedResponse.statusCode).toBe(200);
|
| 118 | expect(startedResponse.json()).toMatchObject({
|
| 119 | status: 'pending',
|
| 120 | acceptances: { [created.agent.agentId]: { method: 'oauth_account' } },
|
| 121 | });
|
| 122 | interaction = { ...interaction!, status: 'active' };
|
| 123 |
|
| 124 | const completedResponse = await app.inject({
|
| 125 | method: 'POST',
|
| 126 | url: `/v0.1/federated-interactions/${interaction.interactionId}/complete`,
|
| 127 | headers,
|
| 128 | payload: {
|
| 129 | agentId: created.agent.agentId,
|
| 130 | outcome: 'success',
|
| 131 | summary: 'Delivered a ranked, sourced shortlist.',
|
| 132 | },
|
| 133 | });
|
| 134 | expect(completedResponse.statusCode).toBe(200);
|
| 135 | expect(completedResponse.json()).toMatchObject({
|
| 136 | method: 'oauth_account',
|
| 137 | report: { submissionMethod: 'oauth_account' },
|
| 138 | });
|
| 139 |
|
| 140 | const feedbackResponse = await app.inject({
|
| 141 | method: 'POST',
|
| 142 | url: `/v0.1/feedback-requests/${feedbackRequest!.requestId}/respond`,
|
| 143 | headers,
|
| 144 | payload: {
|
| 145 | agentId: created.agent.agentId,
|
| 146 | rating: 5,
|
| 147 | wouldWorkAgain: 'yes',
|
| 148 | },
|
| 149 | });
|
| 150 | expect(feedbackResponse.statusCode).toBe(200);
|
| 151 | expect(feedbackResponse.json()).toMatchObject({
|
| 152 | method: 'oauth_account',
|
| 153 | feedback: { ratings: { overall_satisfaction: 1 } },
|
| 154 | });
|
| 155 | await app.close();
|
| 156 | });
|
| 157 | });
|