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

Viewing files at commit 5ab277316767Commit detailsView latest

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