feat(p8): /v1/calendar REST + kernel-client methods + @insignia/iios-meeting-web

Task 8.6: CalendarController exposes schedule/request(+fromCallback/fromClaim)/
list/get/consent/transcript/summarize/action-items/providers(+sync) (session-auth).
kernel-client RestClient gains scheduleMeeting/requestMeeting/listMeetings/
getMeeting/setConsent/generateTranscript/summarizeMeeting/listActionItems/
connect+syncCalendarProvider + Meeting/MeetingParticipant/MeetingActionItem types.
New @insignia/iios-meeting-web (MeetingProvider + useMeetings/useMeeting/useSchedule/
useConsent/useSummarize/useActionItems). Boundary: meeting-web -> contracts +
kernel-client (fails on meeting-web->service).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:09:18 +05:30
parent 83369f2055
commit ca9498ce83
12 changed files with 366 additions and 1 deletions
+50 -1
View File
@@ -1,5 +1,5 @@
import type { IngestInteractionRequest } from '@insignia/iios-contracts';
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult } from './types';
import type { Message, InboxItem, InboxState, Ticket, TicketState, CallbackRequest, RouteBinding, RouteDecision, AiArtifact, AiJobResult, Meeting, MeetingActionItem } from './types';
export interface RestConfig {
serviceUrl: string;
@@ -149,6 +149,55 @@ export class RestClient {
return this.post<AiArtifact>(`/v1/ai/artifacts/${id}/reject`, {});
}
// ─── Calendar & Meeting (P8) ──────────────────────────────────
async scheduleMeeting(input: {
meetingType: string;
title: string;
startAt: string;
endAt?: string;
timezone?: string;
attendees?: { userId: string; displayName?: string; role?: string; visibility?: string }[];
}): Promise<Meeting> {
return this.post<Meeting>('/v1/calendar/meetings', input);
}
async requestMeeting(input: { requestedWindow: Record<string, unknown>; meetingType?: string }, opts?: { fromCallback?: string; fromClaim?: string }): Promise<unknown> {
const q = new URLSearchParams();
if (opts?.fromCallback) q.set('fromCallback', opts.fromCallback);
if (opts?.fromClaim) q.set('fromClaim', opts.fromClaim);
const qs = q.toString();
return this.post(`/v1/calendar/requests${qs ? `?${qs}` : ''}`, input);
}
async listMeetings(): Promise<Meeting[]> {
const r = await fetch(this.url('/v1/calendar/meetings'), { headers: this.headers() });
if (!r.ok) throw new Error(`listMeetings ${r.status}`);
return (await r.json()) as Meeting[];
}
async getMeeting(id: string): Promise<Meeting> {
const r = await fetch(this.url(`/v1/calendar/meetings/${id}`), { headers: this.headers() });
if (!r.ok) throw new Error(`getMeeting ${r.status}`);
return (await r.json()) as Meeting;
}
async setConsent(meetingId: string, actorRefId: string, status: string): Promise<unknown> {
return this.post(`/v1/calendar/meetings/${meetingId}/consent`, { actorRefId, status });
}
async generateTranscript(meetingId: string): Promise<unknown> {
return this.post(`/v1/calendar/meetings/${meetingId}/transcript`, {});
}
async summarizeMeeting(meetingId: string): Promise<Meeting> {
return this.post<Meeting>(`/v1/calendar/meetings/${meetingId}/summarize`, {});
}
async listActionItems(meetingId: string): Promise<MeetingActionItem[]> {
const r = await fetch(this.url(`/v1/calendar/meetings/${meetingId}/action-items`), { headers: this.headers() });
if (!r.ok) throw new Error(`listActionItems ${r.status}`);
return (await r.json()) as MeetingActionItem[];
}
async connectCalendarProvider(): Promise<{ id: string }> {
return this.post<{ id: string }>('/v1/calendar/providers', {});
}
async syncCalendarProvider(id: string): Promise<{ pulled: number; cursor: string }> {
return this.post(`/v1/calendar/providers/${id}/sync`, {});
}
private async post<T>(path: string, body: unknown): Promise<T> {
const r = await fetch(this.url(path), { method: 'POST', headers: this.headers(), body: JSON.stringify(body) });
if (!r.ok) throw new Error(`POST ${path} ${r.status}`);
+30
View File
@@ -150,6 +150,36 @@ export interface AiJobResult {
artifact: AiArtifact | null;
}
export interface MeetingParticipant {
id: string;
actorRefId?: string | null;
role: string;
attendanceStatus: string;
recordingConsent: string;
visibility: string;
}
export interface Meeting {
id: string;
meetingType: string;
status: string;
title: string;
organizerActorId: string;
startAt?: string | null;
endAt?: string | null;
timezone: string;
calendarEventId?: string | null;
participants?: MeetingParticipant[];
}
export interface MeetingActionItem {
id: string;
meetingId: string;
actionItemId: string;
status: string;
actionItem?: { id: string; title: string; status: string; ownerActorId?: string | null };
}
/** Minimal socket surface so the facade can be unit-tested with a fake. */
export interface SocketLike {
on(event: string, handler: (...args: unknown[]) => void): unknown;