mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 06:04:11 +01:00
Add Muse invitation, connection readiness and scoped operator controls.
Keep connection state tied to the current company, person, agent and binding. Surface failed reads and invalidate stale cleanup observations; require current authority and exact readback. Preserve Dot/Grok setup, source-trust limits and existing design tokens.
Carry actual merged backend e6dcc6b095 through a normal forward merge. Preserve its project-write authority fix, existing migration bytes and upstream idle-serving behavior; no new UI behavior is authored by this source carry. Current-head CI and substantive review remain merge gates; final canonical and deployed-provider qualification remain separate.
Refs #16450.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
34 lines
1.7 KiB
TypeScript
34 lines
1.7 KiB
TypeScript
import { AGENT_ROLES, type AgentRole } from "@paperclipai/shared";
|
|
|
|
export interface MuseInvitationDraft { name: string; role: AgentRole }
|
|
export const emptyMuseInvitationDraft: MuseInvitationDraft = { name: "", role: "general" };
|
|
|
|
function key(companyId: string, operatorId: string) {
|
|
return `paperclip.muse-invitation.${encodeURIComponent(companyId)}.${encodeURIComponent(operatorId)}`;
|
|
}
|
|
|
|
/** Only the name and role survive navigation. Pairing capabilities never enter storage. */
|
|
export function readMuseInvitationDraft(companyId: string, operatorId: string | null): MuseInvitationDraft {
|
|
if (!operatorId) return { ...emptyMuseInvitationDraft };
|
|
try {
|
|
const value: unknown = JSON.parse(localStorage.getItem(key(companyId, operatorId)) ?? "null");
|
|
if (typeof value === "object" && value !== null && "name" in value && "role" in value
|
|
&& typeof value.name === "string" && value.name.length <= 100
|
|
&& AGENT_ROLES.some(role => role === value.role)) {
|
|
return { name: value.name, role: AGENT_ROLES.find(role => role === value.role)! };
|
|
}
|
|
} catch { /* Storage is optional; the mounted draft remains usable. */ }
|
|
return { ...emptyMuseInvitationDraft };
|
|
}
|
|
|
|
export function saveMuseInvitationDraft(companyId: string, operatorId: string | null, draft: MuseInvitationDraft) {
|
|
if (!operatorId) return;
|
|
try { localStorage.setItem(key(companyId, operatorId), JSON.stringify({ name: draft.name, role: draft.role })); }
|
|
catch { /* Storage is optional. */ }
|
|
}
|
|
|
|
export function clearMuseInvitationDraft(companyId: string, operatorId: string | null) {
|
|
if (!operatorId) return;
|
|
try { localStorage.removeItem(key(companyId, operatorId)); } catch { /* Storage is optional. */ }
|
|
}
|