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

Viewing files at commit 00864c3cb723Commit detailsView latest

apps/dashboard/src/preview.ts 492 lines · 15.2 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
  hostedThreads: Record<string, any>[];
21
  events: Record<string, any>[];
22
  conflicts: Record<string, any>[];
23
  receipts: Record<string, any>[];
24
  profiles: Record<string, any>[];
25
  runtimes: Record<string, any>[];
26
  accessTokens: Record<string, any>[];
27
};
28
29
export type Settings = {
30
  displayName: string;
31
  contributionEnabled: boolean;
32
  retentionDays: number;
33
  evidenceSharing: 'never' | 'ask' | 'contract_only';
34
  rawConversationsStored: false;
35
};
36
37
export const defaultPreviewSettings: Settings = {
38
  displayName: 'Local operator',
39
  contributionEnabled: false,
40
  retentionDays: 30,
41
  evidenceSharing: 'ask',
42
  rawConversationsStored: false,
43
};
44
45
const ago = (minutes: number) => new Date(Date.now() - minutes * 60_000).toISOString();
46
const ahead = (minutes: number) => new Date(Date.now() + minutes * 60_000).toISOString();
47
48
export function createPreviewData(): DashboardData {
49
  return {
50
    projects: [
51
      { projectId: 'proj_ops', name: 'Operations', createdAt: ago(21 * 24 * 60) },
52
      { projectId: 'proj_research', name: 'Research', createdAt: ago(12 * 24 * 60) },
53
    ],
54
    agents: [
55
      {
56
        agentId: 'agent_atlas',
57
        projectId: 'proj_research',
58
        name: 'Atlas Research',
59
        description: 'Literature review and planning',
60
        framework: 'Claude',
61
        agentVersion: '1.4.2',
62
        agentMode: 'persistent_runtime',
63
        a2aEndpoint: 'https://atlas.example.com/a2a',
64
        autoPublish: true,
65
        autoAcceptPolicy: 'safe_matching',
66
        autoAcceptTaskCategories: ['research', 'planning'],
67
        capabilities: ['research', 'planning', 'summarization'],
68
        limitations: ['no payments'],
69
        identityMode: 'oauth_installation',
70
        status: 'active',
71
        createdAt: ago(12 * 24 * 60),
72
        updatedAt: ago(8),
73
        presence: { status: 'online', lastSeenAt: ago(1) },
74
      },
75
      {
76
        agentId: 'agent_harbor',
77
        projectId: 'proj_ops',
78
        name: 'Harbor Ops',
79
        description: 'Incident triage and runbooks',
80
        framework: 'GPT',
81
        agentVersion: '2.1.0',
82
        agentMode: 'temporary_chat',
83
        autoPublish: false,
84
        autoAcceptPolicy: 'off',
85
        autoAcceptTaskCategories: [],
86
        capabilities: ['ops', 'triage'],
87
        limitations: ['no production writes'],
88
        identityMode: 'oauth_installation',
89
        status: 'active',
90
        createdAt: ago(21 * 24 * 60),
91
        updatedAt: ago(180),
92
        presence: { status: 'offline', lastSeenAt: ago(180) },
93
      },
94
    ],
95
    installations: [
96
      {
97
        installationId: 'inst_atlas',
98
        clientId: 'mcp_atlas',
99
        agentId: 'agent_atlas',
100
        projectId: 'proj_research',
101
        connectedAt: ago(12 * 24 * 60),
102
        updatedAt: ago(8),
103
      },
104
    ],
105
    setupRequests: [
106
      {
107
        requestId: 'setup_lumen',
108
        clientId: 'mcp_lumen',
109
        action: 'connect',
110
        status: 'pending',
111
        agentName: 'Lumen Writer',
112
        projectName: 'Content',
113
        framework: 'Claude',
114
        agentVersion: '0.9.0',
115
        autoPublish: true,
116
        autoAcceptPolicy: 'safe_matching',
117
        autoAcceptTaskCategories: ['writing', 'editing'],
118
        capabilities: ['writing', 'editing', 'research'],
119
        limitations: ['no outbound email'],
120
        requestedAt: ago(18),
121
      },
122
    ],
123
    publications: [{ agentId: 'agent_atlas', published: true, updatedAt: ago(40) }],
124
    interactions: [
125
      {
126
        interactionId: 'ix_local_brief',
127
        agentId: 'agent_atlas',
128
        status: 'completed',
129
        createdAt: ago(240),
130
        completedAt: ago(220),
131
      },
132
    ],
133
    federatedInteractions: [
134
      {
135
        interactionId: 'ix_shared_review',
136
        initiatorAgentId: 'agent_peer_nova',
137
        responderAgentId: 'agent_atlas',
138
        status: 'pending',
139
        termsHash: '7f3c91a0b2e84d11c6aa09f1d3e8b470',
140
        createdAt: ago(26),
141
        contract: { purpose: 'Review a research brief for scope and sources' },
142
      },
143
      {
144
        interactionId: 'ix_shared_ops',
145
        initiatorAgentId: 'agent_atlas',
146
        responderAgentId: 'agent_peer_keel',
147
        status: 'active',
148
        termsHash: '12ab44c0e91f77d0aa3310bc44ee9012',
149
        createdAt: ago(90),
150
        contract: { purpose: 'Share a sanitized incident timeline' },
151
        acceptances: {
152
          agent_peer_keel: { method: 'policy_auto_accept' },
153
        },
154
      },
155
    ],
156
    liveSessions: [
157
      {
158
        interactionId: 'ix_shared_ops',
159
        initiatorAgentId: 'agent_atlas',
160
        responderAgentId: 'agent_peer_keel',
161
        status: 'active',
162
        createdAt: ago(88),
163
        activatedAt: ago(87),
164
        expiresAt: ahead(12),
165
      },
166
    ],
167
    hostedThreads: [],
168
    events: [
169
      {
170
        eventId: 'evt_1',
171
        eventType: 'interaction_started',
172
        agentId: 'agent_atlas',
173
        interactionId: 'ix_shared_ops',
174
        timestamp: ago(88),
175
        visibility: 'participants',
176
      },
177
      {
178
        eventId: 'evt_2',
179
        eventType: 'policy_warning',
180
        agentId: 'agent_harbor',
181
        interactionId: 'ix_local_brief',
182
        timestamp: ago(200),
183
        visibility: 'owner',
184
      },
185
      {
186
        eventId: 'evt_3',
187
        eventType: 'claim_checked',
188
        agentId: 'agent_atlas',
189
        interactionId: 'ix_local_brief',
190
        timestamp: ago(230),
191
        visibility: 'participants',
192
      },
193
      {
194
        eventId: 'evt_4',
195
        eventType: 'session_activated',
196
        agentId: 'agent_atlas',
197
        interactionId: 'ix_shared_ops',
198
        timestamp: ago(87),
199
        visibility: 'participants',
200
      },
201
    ],
202
    conflicts: [
203
      {
204
        conflictId: 'dsp_1',
205
        interactionId: 'ix_local_brief',
206
        status: 'open',
207
        createdAt: ago(190),
208
      },
209
    ],
210
    receipts: [
211
      {
212
        receiptId: 'rcpt_1',
213
        interactionId: 'ix_local_brief',
214
        agentId: 'agent_atlas',
215
        outcome: 'success',
216
        completedAt: ago(220),
217
      },
218
    ],
219
    profiles: [
220
      {
221
        agentId: 'agent_atlas',
222
        taskCategory: 'research',
223
        agentVersion: '1.4.2',
224
        sampleSize: 14,
225
        completion: 0.92,
226
        scope: 0.84,
227
        evidence: 0.88,
228
        communication: 0.79,
229
        deadline: 0.9,
230
      },
231
      {
232
        agentId: 'agent_harbor',
233
        taskCategory: 'ops',
234
        agentVersion: '2.1.0',
235
        sampleSize: 6,
236
        completion: 0.71,
237
        scope: 0.66,
238
        evidence: 0.58,
239
        communication: 0.74,
240
        deadline: 0.62,
241
      },
242
    ],
243
    runtimes: [
244
      {
245
        agentId: 'agent_atlas',
246
        endpoint: 'https://atlas.example.com/openclasp',
247
        a2aEndpoint: 'https://atlas.example.com/a2a',
248
        status: 'verified',
249
        verifiedAt: ago(40),
250
      },
251
    ],
252
    accessTokens: [],
253
  };
254
}
255
256
export function applyPreviewRequest(
257
  data: DashboardData,
258
  settings: Settings,
259
  path: string,
260
  init?: RequestInit,
261
): { data: DashboardData; settings: Settings; result: unknown } {
262
  const method = (init?.method ?? 'GET').toUpperCase();
263
  const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {};
264
265
  if (path === '/v0.1/dashboard' && method === 'GET') return { data, settings, result: data };
266
  if (path === '/v0.1/settings' && method === 'GET') return { data, settings, result: settings };
267
  if (path === '/v0.1/settings' && method === 'PUT') {
268
    const next = {
269
      ...settings,
270
      ...body,
271
      rawConversationsStored: false as const,
272
    } as Settings;
273
    return { data, settings: next, result: next };
274
  }
275
276
  if (path === '/v0.1/provider-connections' && method === 'POST') {
277
    const suffix = crypto.randomUUID();
278
    const projectName = String(body.projectName ?? 'Hosted agents');
279
    const existingProject = data.projects.find(
280
      (project) => String(project.name).toLowerCase() === projectName.toLowerCase(),
281
    );
282
    const project = existingProject ?? {
283
      projectId: `project_${suffix}`,
284
      name: projectName,
285
      createdAt: new Date().toISOString(),
286
    };
287
    const agentId = `agent_${suffix}`;
288
    const createdAt = new Date();
289
    const tokenId = suffix.replaceAll('-', '').slice(0, 16);
290
    const provider = body.provider === 'custom' ? 'custom' : 'botpress';
291
    const agent = {
292
      agentId,
293
      projectId: project.projectId,
294
      name: String(body.agentName ?? 'Botpress agent'),
295
      description: String(body.description ?? ''),
296
      framework: provider === 'botpress' ? 'Botpress' : 'Custom runtime',
297
      agentVersion: '1.0.0',
298
      agentMode: 'persistent_runtime',
299
      transport: 'direct_a2a',
300
      autoPublish: false,
301
      autoAcceptPolicy: 'off',
302
      autoAcceptTaskCategories: [],
303
      capabilities: body.capabilities ?? [],
304
      limitations: body.limitations ?? [],
305
      identityMode: 'owner_managed',
306
      status: 'active',
307
      createdAt: createdAt.toISOString(),
308
      updatedAt: createdAt.toISOString(),
309
      presence: { status: 'offline' },
310
    };
311
    const accessToken = {
312
      tokenId,
313
      token: `oc_at_${tokenId}.preview_agent_access_token_secret_not_for_production`,
314
      agentId,
315
      name: provider === 'botpress' ? 'Botpress' : 'Custom runtime',
316
      scopes: ['mcp:access', 'runtime:connect'],
317
      createdAt: createdAt.toISOString(),
318
      expiresAt: new Date(
319
        createdAt.getTime() + Number(body.expiresInDays ?? 365) * 86_400_000,
320
      ).toISOString(),
321
    };
322
    return {
323
      data: {
324
        ...data,
325
        projects: existingProject ? data.projects : [...data.projects, project],
326
        agents: [...data.agents, agent],
327
        accessTokens: [accessToken, ...data.accessTokens],
328
      },
329
      settings,
330
      result: { agent, project, provider, accessToken },
331
    };
332
  }
333
334
  const automation = path.match(/^\/v0\.1\/agents\/([^/]+)\/automation$/);
335
  if (automation && method === 'PUT') {
336
    const agentId = decodeURIComponent(automation[1] ?? '');
337
    const next = {
338
      ...data,
339
      agents: data.agents.map((agent) =>
340
        agent.agentId === agentId
341
          ? {
342
              ...agent,
343
              autoPublish: Boolean(body.autoPublish),
344
              autoAcceptPolicy: body.autoAcceptPolicy,
345
              autoAcceptTaskCategories: body.autoAcceptTaskCategories,
346
            }
347
          : agent,
348
      ),
349
      publications: body.autoPublish
350
        ? [
351
            ...data.publications.filter((item) => item.agentId !== agentId),
352
            { agentId, published: true, updatedAt: new Date().toISOString() },
353
          ]
354
        : data.publications.filter((item) => item.agentId !== agentId),
355
    };
356
    return { data: next, settings, result: { ok: true } };
357
  }
358
359
  const runtime = path.match(/^\/v0\.1\/agents\/([^/]+)\/runtime$/);
360
  if (runtime && method === 'PUT') {
361
    const agentId = decodeURIComponent(runtime[1] ?? '');
362
    const endpoint = String(body.endpoint ?? '');
363
    const record = {
364
      agentId,
365
      endpoint,
366
      a2aEndpoint: endpoint,
367
      status: 'verified',
368
      verifiedAt: new Date().toISOString(),
369
    };
370
    return {
371
      data: {
372
        ...data,
373
        runtimes: [...data.runtimes.filter((item) => item.agentId !== agentId), record],
374
      },
375
      settings,
376
      result: record,
377
    };
378
  }
379
  const tokenCollection = path.match(/^\/v0\.1\/agents\/([^/]+)\/access-tokens$/);
380
  if (tokenCollection && method === 'POST') {
381
    const agentId = decodeURIComponent(tokenCollection[1] ?? '');
382
    const tokenId = crypto.randomUUID().replaceAll('-', '').slice(0, 16);
383
    const createdAt = new Date();
384
    const expiresInDays = Number(body.expiresInDays ?? 365);
385
    const record = {
386
      tokenId,
387
      agentId,
388
      name: String(body.name ?? 'Hosted provider'),
389
      scopes: ['mcp:access', 'runtime:connect'],
390
      createdAt: createdAt.toISOString(),
391
      expiresAt: new Date(createdAt.getTime() + expiresInDays * 86_400_000).toISOString(),
392
    };
393
    return {
394
      data: { ...data, accessTokens: [record, ...data.accessTokens] },
395
      settings,
396
      result: {
397
        ...record,
398
        token: `oc_at_${tokenId}.preview_agent_access_token_secret_not_for_production`,
399
      },
400
    };
401
  }
402
  const tokenItem = path.match(/^\/v0\.1\/agents\/([^/]+)\/access-tokens\/([^/]+)$/);
403
  if (tokenItem && method === 'DELETE') {
404
    const tokenId = decodeURIComponent(tokenItem[2] ?? '');
405
    return {
406
      data: {
407
        ...data,
408
        accessTokens: data.accessTokens.map((token) =>
409
          token.tokenId === tokenId ? { ...token, revokedAt: new Date().toISOString() } : token,
410
        ),
411
      },
412
      settings,
413
      result: { tokenId, revokedAt: new Date().toISOString() },
414
    };
415
  }
416
  const agentDelete = path.match(/^\/v0\.1\/agents\/([^/]+)$/);
417
  if (agentDelete && method === 'DELETE') {
418
    const agentId = decodeURIComponent(agentDelete[1] ?? '');
419
    return {
420
      data: {
421
        ...data,
422
        agents: data.agents.filter((item) => item.agentId !== agentId),
423
        publications: data.publications.filter((item) => item.agentId !== agentId),
424
        runtimes: data.runtimes.filter((item) => item.agentId !== agentId),
425
        installations: data.installations.filter((item) => item.agentId !== agentId),
426
        accessTokens: data.accessTokens.filter((item) => item.agentId !== agentId),
427
      },
428
      settings,
429
      result: { agentId, deleted: true },
430
    };
431
  }
432
433
  if (runtime && method === 'DELETE') {
434
    const agentId = decodeURIComponent(runtime[1] ?? '');
435
    return {
436
      data: {
437
        ...data,
438
        runtimes: data.runtimes.filter((item) => item.agentId !== agentId),
439
      },
440
      settings,
441
      result: { agentId, status: 'disabled' },
442
    };
443
  }
444
445
  const federated = path.match(/^\/v0\.1\/federated-interactions\/([^/]+)\/respond$/);
446
  if (federated && method === 'POST') {
447
    const interactionId = decodeURIComponent(federated[1] ?? '');
448
    const decision = body.decision === 'accept' ? 'active' : 'rejected';
449
    return {
450
      data: {
451
        ...data,
452
        federatedInteractions: data.federatedInteractions.map((item) =>
453
          item.interactionId === interactionId ? { ...item, status: decision } : item,
454
        ),
455
      },
456
      settings,
457
      result: { ok: true },
458
    };
459
  }
460
461
  const onboarding = path.match(/^\/v0\.1\/onboarding\/([^/]+)\/(approve|reject)$/);
462
  if (onboarding && method === 'POST') {
463
    const requestId = decodeURIComponent(onboarding[1] ?? '');
464
    const decision = onboarding[2] === 'approve' ? 'approved' : 'rejected';
465
    return {
466
      data: {
467
        ...data,
468
        setupRequests: data.setupRequests.map((item) =>
469
          item.requestId === requestId
470
            ? { ...item, status: decision, decidedAt: new Date().toISOString() }
471
            : item,
472
        ),
473
      },
474
      settings,
475
      result: { ok: true },
476
    };
477
  }
478
479
  throw new Error('Preview cannot perform this action');
480
}
481
482
export function isPreviewActive() {
483
  return import.meta.env.DEV && sessionStorage.getItem(previewKey) === '1';
484
}
485
486
export function enablePreview() {
487
  sessionStorage.setItem(previewKey, '1');
488
}
489
490
export function disablePreview() {
491
  sessionStorage.removeItem(previewKey);
492
}