The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 296bb55574a2Commit detailsView latest

apps/dashboard/src/preview.ts 390 lines · 11.4 KB · typescript
1
export const previewKey = 'openclasp.preview.v1';
2
3
export const previewSession = {
4
  user: {
5
    sub: 'local-preview',
6
    name: 'Local preview',
7
    email: 'preview@localhost',
8
  },
9
};
10
11
export type DashboardData = {
12
  agents: Record<string, any>[];
13
  projects: Record<string, any>[];
14
  installations: Record<string, any>[];
15
  setupRequests: Record<string, any>[];
16
  publications: Record<string, any>[];
17
  interactions: Record<string, any>[];
18
  federatedInteractions: Record<string, any>[];
19
  liveSessions: Record<string, any>[];
20
  events: Record<string, any>[];
21
  conflicts: Record<string, any>[];
22
  receipts: Record<string, any>[];
23
  profiles: Record<string, any>[];
24
  runtimes: Record<string, any>[];
25
};
26
27
export type Settings = {
28
  displayName: string;
29
  contributionEnabled: boolean;
30
  retentionDays: number;
31
  evidenceSharing: 'never' | 'ask' | 'contract_only';
32
  rawConversationsStored: false;
33
};
34
35
export const defaultPreviewSettings: Settings = {
36
  displayName: 'Local operator',
37
  contributionEnabled: false,
38
  retentionDays: 30,
39
  evidenceSharing: 'ask',
40
  rawConversationsStored: false,
41
};
42
43
const ago = (minutes: number) => new Date(Date.now() - minutes * 60_000).toISOString();
44
const ahead = (minutes: number) => new Date(Date.now() + minutes * 60_000).toISOString();
45
46
export function createPreviewData(): DashboardData {
47
  return {
48
    projects: [
49
      { projectId: 'proj_ops', name: 'Operations', createdAt: ago(21 * 24 * 60) },
50
      { projectId: 'proj_research', name: 'Research', createdAt: ago(12 * 24 * 60) },
51
    ],
52
    agents: [
53
      {
54
        agentId: 'agent_atlas',
55
        projectId: 'proj_research',
56
        name: 'Atlas Research',
57
        description: 'Literature review and planning',
58
        framework: 'Claude',
59
        agentVersion: '1.4.2',
60
        a2aEndpoint: 'https://atlas.example.com/a2a',
61
        autoPublish: true,
62
        autoAcceptPolicy: 'safe_matching',
63
        autoAcceptTaskCategories: ['research', 'planning'],
64
        capabilities: ['research', 'planning', 'summarization'],
65
        limitations: ['no payments'],
66
        identityMode: 'oauth_installation',
67
        status: 'active',
68
        createdAt: ago(12 * 24 * 60),
69
        updatedAt: ago(8),
70
        presence: { status: 'online', lastSeenAt: ago(1) },
71
      },
72
      {
73
        agentId: 'agent_harbor',
74
        projectId: 'proj_ops',
75
        name: 'Harbor Ops',
76
        description: 'Incident triage and runbooks',
77
        framework: 'GPT',
78
        agentVersion: '2.1.0',
79
        autoPublish: false,
80
        autoAcceptPolicy: 'off',
81
        autoAcceptTaskCategories: [],
82
        capabilities: ['ops', 'triage'],
83
        limitations: ['no production writes'],
84
        identityMode: 'oauth_installation',
85
        status: 'active',
86
        createdAt: ago(21 * 24 * 60),
87
        updatedAt: ago(180),
88
        presence: { status: 'offline', lastSeenAt: ago(180) },
89
      },
90
    ],
91
    installations: [
92
      {
93
        installationId: 'inst_atlas',
94
        clientId: 'mcp_atlas',
95
        agentId: 'agent_atlas',
96
        projectId: 'proj_research',
97
        connectedAt: ago(12 * 24 * 60),
98
        updatedAt: ago(8),
99
      },
100
    ],
101
    setupRequests: [
102
      {
103
        requestId: 'setup_lumen',
104
        clientId: 'mcp_lumen',
105
        action: 'connect',
106
        status: 'pending',
107
        agentName: 'Lumen Writer',
108
        projectName: 'Content',
109
        framework: 'Claude',
110
        agentVersion: '0.9.0',
111
        autoPublish: true,
112
        autoAcceptPolicy: 'safe_matching',
113
        autoAcceptTaskCategories: ['writing', 'editing'],
114
        capabilities: ['writing', 'editing', 'research'],
115
        limitations: ['no outbound email'],
116
        requestedAt: ago(18),
117
      },
118
    ],
119
    publications: [{ agentId: 'agent_atlas', published: true, updatedAt: ago(40) }],
120
    interactions: [
121
      {
122
        interactionId: 'ix_local_brief',
123
        agentId: 'agent_atlas',
124
        status: 'completed',
125
        createdAt: ago(240),
126
        completedAt: ago(220),
127
      },
128
    ],
129
    federatedInteractions: [
130
      {
131
        interactionId: 'ix_shared_review',
132
        initiatorAgentId: 'agent_peer_nova',
133
        responderAgentId: 'agent_atlas',
134
        status: 'pending',
135
        termsHash: '7f3c91a0b2e84d11c6aa09f1d3e8b470',
136
        createdAt: ago(26),
137
        contract: { purpose: 'Review a research brief for scope and sources' },
138
      },
139
      {
140
        interactionId: 'ix_shared_ops',
141
        initiatorAgentId: 'agent_atlas',
142
        responderAgentId: 'agent_peer_keel',
143
        status: 'active',
144
        termsHash: '12ab44c0e91f77d0aa3310bc44ee9012',
145
        createdAt: ago(90),
146
        contract: { purpose: 'Share a sanitized incident timeline' },
147
        acceptances: {
148
          agent_peer_keel: { method: 'policy_auto_accept' },
149
        },
150
      },
151
    ],
152
    liveSessions: [
153
      {
154
        interactionId: 'ix_shared_ops',
155
        initiatorAgentId: 'agent_atlas',
156
        responderAgentId: 'agent_peer_keel',
157
        status: 'active',
158
        createdAt: ago(88),
159
        activatedAt: ago(87),
160
        expiresAt: ahead(12),
161
      },
162
    ],
163
    events: [
164
      {
165
        eventId: 'evt_1',
166
        eventType: 'interaction_started',
167
        agentId: 'agent_atlas',
168
        interactionId: 'ix_shared_ops',
169
        timestamp: ago(88),
170
        visibility: 'participants',
171
      },
172
      {
173
        eventId: 'evt_2',
174
        eventType: 'policy_warning',
175
        agentId: 'agent_harbor',
176
        interactionId: 'ix_local_brief',
177
        timestamp: ago(200),
178
        visibility: 'owner',
179
      },
180
      {
181
        eventId: 'evt_3',
182
        eventType: 'claim_checked',
183
        agentId: 'agent_atlas',
184
        interactionId: 'ix_local_brief',
185
        timestamp: ago(230),
186
        visibility: 'participants',
187
      },
188
      {
189
        eventId: 'evt_4',
190
        eventType: 'session_activated',
191
        agentId: 'agent_atlas',
192
        interactionId: 'ix_shared_ops',
193
        timestamp: ago(87),
194
        visibility: 'participants',
195
      },
196
    ],
197
    conflicts: [
198
      {
199
        conflictId: 'dsp_1',
200
        interactionId: 'ix_local_brief',
201
        status: 'open',
202
        createdAt: ago(190),
203
      },
204
    ],
205
    receipts: [
206
      {
207
        receiptId: 'rcpt_1',
208
        interactionId: 'ix_local_brief',
209
        agentId: 'agent_atlas',
210
        outcome: 'success',
211
        completedAt: ago(220),
212
      },
213
    ],
214
    profiles: [
215
      {
216
        agentId: 'agent_atlas',
217
        taskCategory: 'research',
218
        agentVersion: '1.4.2',
219
        sampleSize: 14,
220
        completion: 0.92,
221
        scope: 0.84,
222
        evidence: 0.88,
223
        communication: 0.79,
224
        deadline: 0.9,
225
      },
226
      {
227
        agentId: 'agent_harbor',
228
        taskCategory: 'ops',
229
        agentVersion: '2.1.0',
230
        sampleSize: 6,
231
        completion: 0.71,
232
        scope: 0.66,
233
        evidence: 0.58,
234
        communication: 0.74,
235
        deadline: 0.62,
236
      },
237
    ],
238
    runtimes: [
239
      {
240
        agentId: 'agent_atlas',
241
        endpoint: 'https://atlas.example.com/openclasp',
242
        a2aEndpoint: 'https://atlas.example.com/a2a',
243
        status: 'verified',
244
        verifiedAt: ago(40),
245
      },
246
    ],
247
  };
248
}
249
250
export function applyPreviewRequest(
251
  data: DashboardData,
252
  settings: Settings,
253
  path: string,
254
  init?: RequestInit,
255
): { data: DashboardData; settings: Settings; result: unknown } {
256
  const method = (init?.method ?? 'GET').toUpperCase();
257
  const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {};
258
259
  if (path === '/v0.1/dashboard' && method === 'GET') return { data, settings, result: data };
260
  if (path === '/v0.1/settings' && method === 'GET') return { data, settings, result: settings };
261
  if (path === '/v0.1/settings' && method === 'PUT') {
262
    const next = {
263
      ...settings,
264
      ...body,
265
      rawConversationsStored: false as const,
266
    } as Settings;
267
    return { data, settings: next, result: next };
268
  }
269
270
  const automation = path.match(/^\/v0\.1\/agents\/([^/]+)\/automation$/);
271
  if (automation && method === 'PUT') {
272
    const agentId = decodeURIComponent(automation[1] ?? '');
273
    const next = {
274
      ...data,
275
      agents: data.agents.map((agent) =>
276
        agent.agentId === agentId
277
          ? {
278
              ...agent,
279
              autoPublish: Boolean(body.autoPublish),
280
              autoAcceptPolicy: body.autoAcceptPolicy,
281
              autoAcceptTaskCategories: body.autoAcceptTaskCategories,
282
            }
283
          : agent,
284
      ),
285
      publications: body.autoPublish
286
        ? [
287
            ...data.publications.filter((item) => item.agentId !== agentId),
288
            { agentId, published: true, updatedAt: new Date().toISOString() },
289
          ]
290
        : data.publications.filter((item) => item.agentId !== agentId),
291
    };
292
    return { data: next, settings, result: { ok: true } };
293
  }
294
295
  const runtime = path.match(/^\/v0\.1\/agents\/([^/]+)\/runtime$/);
296
  if (runtime && method === 'PUT') {
297
    const agentId = decodeURIComponent(runtime[1] ?? '');
298
    const endpoint = String(body.endpoint ?? '');
299
    const record = {
300
      agentId,
301
      endpoint,
302
      a2aEndpoint: endpoint,
303
      status: 'verified',
304
      verifiedAt: new Date().toISOString(),
305
    };
306
    return {
307
      data: {
308
        ...data,
309
        runtimes: [...data.runtimes.filter((item) => item.agentId !== agentId), record],
310
      },
311
      settings,
312
      result: record,
313
    };
314
  }
315
  const agentDelete = path.match(/^\/v0\.1\/agents\/([^/]+)$/);
316
  if (agentDelete && method === 'DELETE') {
317
    const agentId = decodeURIComponent(agentDelete[1] ?? '');
318
    return {
319
      data: {
320
        ...data,
321
        agents: data.agents.filter((item) => item.agentId !== agentId),
322
        publications: data.publications.filter((item) => item.agentId !== agentId),
323
        runtimes: data.runtimes.filter((item) => item.agentId !== agentId),
324
        installations: data.installations.filter((item) => item.agentId !== agentId),
325
      },
326
      settings,
327
      result: { agentId, deleted: true },
328
    };
329
  }
330
331
  if (runtime && method === 'DELETE') {
332
    const agentId = decodeURIComponent(runtime[1] ?? '');
333
    return {
334
      data: {
335
        ...data,
336
        runtimes: data.runtimes.filter((item) => item.agentId !== agentId),
337
      },
338
      settings,
339
      result: { agentId, status: 'disabled' },
340
    };
341
  }
342
343
  const federated = path.match(/^\/v0\.1\/federated-interactions\/([^/]+)\/respond$/);
344
  if (federated && method === 'POST') {
345
    const interactionId = decodeURIComponent(federated[1] ?? '');
346
    const decision = body.decision === 'accept' ? 'active' : 'rejected';
347
    return {
348
      data: {
349
        ...data,
350
        federatedInteractions: data.federatedInteractions.map((item) =>
351
          item.interactionId === interactionId ? { ...item, status: decision } : item,
352
        ),
353
      },
354
      settings,
355
      result: { ok: true },
356
    };
357
  }
358
359
  const onboarding = path.match(/^\/v0\.1\/onboarding\/([^/]+)\/(approve|reject)$/);
360
  if (onboarding && method === 'POST') {
361
    const requestId = decodeURIComponent(onboarding[1] ?? '');
362
    const decision = onboarding[2] === 'approve' ? 'approved' : 'rejected';
363
    return {
364
      data: {
365
        ...data,
366
        setupRequests: data.setupRequests.map((item) =>
367
          item.requestId === requestId
368
            ? { ...item, status: decision, decidedAt: new Date().toISOString() }
369
            : item,
370
        ),
371
      },
372
      settings,
373
      result: { ok: true },
374
    };
375
  }
376
377
  throw new Error('Preview cannot perform this action');
378
}
379
380
export function isPreviewActive() {
381
  return import.meta.env.DEV && sessionStorage.getItem(previewKey) === '1';
382
}
383
384
export function enablePreview() {
385
  sessionStorage.setItem(previewKey, '1');
386
}
387
388
export function disablePreview() {
389
  sessionStorage.removeItem(previewKey);
390
}