@datalayer/agent-runtimes 1.3.104 → 1.3.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist-embed/datalayer-app-main.js +82 -2
  2. package/lib/apps/apps/appspec.js +17 -0
  3. package/lib/apps/apps/checks.js +4 -0
  4. package/lib/apps/plugins/pap-example/index.d.ts +23 -0
  5. package/lib/apps/plugins/pap-example/index.js +25 -0
  6. package/lib/components/inspector/a2aSpans.js +8 -1
  7. package/lib/components/teams/SceneTranscript.js +8 -3
  8. package/lib/examples/PapAgentIdentityExample.d.ts +4 -0
  9. package/lib/examples/PapAgentIdentityExample.js +23 -0
  10. package/lib/examples/PapAuthorizationBoundaryExample.d.ts +4 -0
  11. package/lib/examples/PapAuthorizationBoundaryExample.js +23 -0
  12. package/lib/examples/PapCompanyDiscoveryExample.d.ts +4 -0
  13. package/lib/examples/PapCompanyDiscoveryExample.js +23 -0
  14. package/lib/examples/PapDpopProofExample.d.ts +4 -0
  15. package/lib/examples/PapDpopProofExample.js +23 -0
  16. package/lib/examples/PapGuidedJourneyExample.d.ts +4 -0
  17. package/lib/examples/PapGuidedJourneyExample.js +23 -0
  18. package/lib/examples/PapSessionLifecycleExample.d.ts +4 -0
  19. package/lib/examples/PapSessionLifecycleExample.js +23 -0
  20. package/lib/examples/example-selector.js +9 -0
  21. package/lib/examples/exampleGroups.d.ts +1 -1
  22. package/lib/examples/exampleGroups.js +3 -0
  23. package/lib/examples/index.d.ts +6 -0
  24. package/lib/examples/index.js +6 -0
  25. package/lib/examples/main.js +9 -0
  26. package/lib/examples/pap/PapAgentIdentityView.d.ts +4 -0
  27. package/lib/examples/pap/PapAgentIdentityView.js +55 -0
  28. package/lib/examples/pap/PapAuthorizationBoundaryView.d.ts +4 -0
  29. package/lib/examples/pap/PapAuthorizationBoundaryView.js +59 -0
  30. package/lib/examples/pap/PapCompanyDiscoveryView.d.ts +4 -0
  31. package/lib/examples/pap/PapCompanyDiscoveryView.js +62 -0
  32. package/lib/examples/pap/PapDpopProofView.d.ts +4 -0
  33. package/lib/examples/pap/PapDpopProofView.js +57 -0
  34. package/lib/examples/pap/PapExamplePage.d.ts +14 -0
  35. package/lib/examples/pap/PapExamplePage.js +6 -0
  36. package/lib/examples/pap/PapGuidedJourneyView.d.ts +4 -0
  37. package/lib/examples/pap/PapGuidedJourneyView.js +263 -0
  38. package/lib/examples/pap/PapSessionLifecycleView.d.ts +4 -0
  39. package/lib/examples/pap/PapSessionLifecycleView.js +103 -0
  40. package/lib/index.d.ts +1 -0
  41. package/lib/index.js +1 -0
  42. package/lib/pap/index.d.ts +42 -0
  43. package/lib/pap/index.js +57 -0
  44. package/lib/specs/apps.js +76 -15
  45. package/lib/specs/appspecSchema.js +27 -0
  46. package/lib/types/agentspecs.d.ts +12 -0
  47. package/package.json +2 -1
  48. package/scripts/codegen/generate_apps.py +3 -0
  49. package/scripts/validate_pap_examples.py +44 -0
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ /** Create real device-held proofs while retaining only non-secret evidence. */
3
+ declare const PapDpopProofView: React.FC;
4
+ export default PapDpopProofView;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useState } from 'react';
7
+ import { Box } from '@datalayer/primer-addons';
8
+ import { Button, Text } from '@primer/react';
9
+ import { JoseDpopProofProvider, SecretValue, } from '@datalayer/personal-agent-protocol';
10
+ import { PapExamplePage, PapResult } from './PapExamplePage';
11
+ /** Create real device-held proofs while retaining only non-secret evidence. */
12
+ const PapDpopProofView = () => {
13
+ const [summary, setSummary] = useState(null);
14
+ const [error, setError] = useState(null);
15
+ const createProofs = async () => {
16
+ setError(null);
17
+ try {
18
+ const keyPair = (await globalThis.crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']));
19
+ const publicJwk = await globalThis.crypto.subtle.exportKey('jwk', keyPair.publicKey);
20
+ const provider = new JoseDpopProofProvider({
21
+ resolveKey: () => ({
22
+ privateKey: keyPair.privateKey,
23
+ publicJwk,
24
+ algorithm: 'ES256',
25
+ }),
26
+ });
27
+ const request = {
28
+ method: 'POST',
29
+ url: 'https://api.company.example/orders?draft=true#editor',
30
+ tenantId: 'example-tenant',
31
+ subject: 'host-only-pairwise-subject',
32
+ issuer: 'https://company.example',
33
+ accessToken: new SecretValue('host-only-example-token'),
34
+ nonce: 'host-only-company-nonce',
35
+ };
36
+ const first = await provider.create(request);
37
+ const second = await provider.create(request);
38
+ setSummary({
39
+ algorithm: 'ES256',
40
+ freshProofPerRequest: first.reveal() !== second.reveal(),
41
+ methodBound: true,
42
+ urlBoundWithoutQueryOrFragment: true,
43
+ accessTokenBound: true,
44
+ nonceIncluded: true,
45
+ proofRedactedByDefault: first.toJSON() === '[REDACTED]',
46
+ proofWithheldFromUiState: true,
47
+ keyMaterialWithheldFromUiState: true,
48
+ });
49
+ }
50
+ catch (cause) {
51
+ setSummary(null);
52
+ setError(cause instanceof Error ? cause.message : String(cause));
53
+ }
54
+ };
55
+ return (_jsxs(PapExamplePage, { eyebrow: "PAP \u00B7 DPoP", title: "Create a fresh proof for every protected request", description: "This Reactor view creates non-exportable P-256 key material and real DPoP proofs in the browser host. Only policy evidence enters React state; proofs, tokens, nonces, identifiers and key material stay outside the agent and rendered UI. Server-side verification adds signature, request, token and replay checks.", children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 3, p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Generate two proofs for the same request without retaining either." }), _jsx(Button, { variant: "primary", onClick: () => void createProofs(), children: "Create safely" })] }), error ? _jsx(Text, { sx: { color: 'danger.fg' }, children: error }) : null, summary ? (_jsx(PapResult, { title: "Safe DPoP evidence", value: summary })) : null] }));
56
+ };
57
+ export default PapDpopProofView;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface PapExamplePageProps {
3
+ title: string;
4
+ eyebrow: string;
5
+ description: string;
6
+ children: React.ReactNode;
7
+ maxWidth?: number | string;
8
+ }
9
+ /** Shared visual frame for the protocol examples. */
10
+ export declare const PapExamplePage: React.FC<PapExamplePageProps>;
11
+ export declare const PapResult: React.FC<{
12
+ title: string;
13
+ value: unknown;
14
+ }>;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from '@datalayer/primer-addons';
3
+ import { Heading, Label, Text } from '@primer/react';
4
+ /** Shared visual frame for the protocol examples. */
5
+ export const PapExamplePage = ({ title, eyebrow, description, children, maxWidth = 960, }) => (_jsx(Box, { height: "100%", overflow: "auto", bg: "canvas.subtle", p: 4, children: _jsxs(Box, { maxWidth: maxWidth, mx: "auto", display: "flex", flexDirection: "column", gap: 3, children: [_jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, children: [_jsx(Box, { children: _jsx(Label, { variant: "accent", children: eyebrow }) }), _jsx(Heading, { as: "h1", sx: { fontSize: 4 }, children: title }), _jsx(Text, { sx: { color: 'fg.muted', maxWidth: 760 }, children: description })] }), children] }) }));
6
+ export const PapResult = ({ title, value }) => (_jsxs(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", p: 3, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 2 }, children: title }), _jsx(Box, { as: "pre", m: 0, p: 3, overflow: "auto", borderRadius: 2, bg: "canvas.inset", sx: { fontSize: 1, lineHeight: 1.5 }, children: JSON.stringify(value, null, 2) })] }));
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ /** A five-step user, company, and wire-level PAP walkthrough. */
3
+ declare const PapGuidedJourneyView: React.FC;
4
+ export default PapGuidedJourneyView;
@@ -0,0 +1,263 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useEffect, useState } from 'react';
7
+ import { Box } from '@datalayer/primer-addons';
8
+ import { Button, Heading, Label, Text } from '@primer/react';
9
+ import { buildDirectSignInRequest, buildSessionAssertionClaims, parseDiscoveryDocument, } from '@datalayer/personal-agent-protocol';
10
+ import { PapExamplePage } from './PapExamplePage';
11
+ const conversationStart = [
12
+ {
13
+ speaker: 'user',
14
+ text: "My Trailhead jacket keeps the rain out, but it isn't warm enough. Can I return it?",
15
+ },
16
+ {
17
+ speaker: 'atlas',
18
+ text: "I'll ask Northstar Outfitters about a return and whether they have something warmer.",
19
+ },
20
+ ];
21
+ const STEPS = [
22
+ {
23
+ title: 'Finding Northstar',
24
+ summary: "Atlas reads Northstar's public PAP document to learn how returns, authorization, and the company agent are exposed.",
25
+ channel: 'Discovery',
26
+ messages: conversationStart,
27
+ facts: [
28
+ { label: 'Publishes', value: 'poppy.json · draft 0.1' },
29
+ { label: 'Returns', value: 'Company conversation agent' },
30
+ { label: 'Sign-in', value: "Northstar's own page" },
31
+ ],
32
+ method: 'GET',
33
+ target: 'https://northstar.example/.well-known/poppy.json',
34
+ detailLabel: 'Atlas → northstar.example',
35
+ request: 'Accept: application/json',
36
+ response: `200 OK
37
+ {
38
+ "protocol_version": "0.1",
39
+ "organization": { "name": "Northstar Outfitters" },
40
+ "auth": { "issuer": "https://auth.northstar.example" },
41
+ "agent": { "protocols": [{ "type": "poppy" }] }
42
+ }`,
43
+ },
44
+ {
45
+ title: 'Starting signed out',
46
+ summary: 'Atlas opens a pairwise Session and can ask about the return before knowing who the user is. Northstar replies that order access needs sign-in.',
47
+ channel: 'Session + conversation',
48
+ messages: [
49
+ ...conversationStart,
50
+ {
51
+ speaker: 'company',
52
+ text: 'I can help with that. Please sign in so I can find the order.',
53
+ },
54
+ ],
55
+ facts: [
56
+ { label: 'Personal agent', value: 'Atlas' },
57
+ { label: 'Session', value: 'Signed out · pairwise' },
58
+ { label: 'Conversation', value: 'Waiting for sign-in' },
59
+ ],
60
+ method: 'POST',
61
+ target: 'https://auth.northstar.example/oauth/token',
62
+ detailLabel: 'Atlas → authorization server',
63
+ request: `Content-Type: application/x-www-form-urlencoded
64
+ DPoP: [fresh host-only proof]
65
+
66
+ grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
67
+ assertion=[host-only session assertion]
68
+ client_assertion=[host-only client assertion]`,
69
+ response: `200 OK
70
+ {
71
+ "access_token": "[stored by host]",
72
+ "token_type": "DPoP",
73
+ "session_id": "[opaque reference]",
74
+ "signed_in": false
75
+ }`,
76
+ },
77
+ {
78
+ title: 'The user signs in',
79
+ summary: "Atlas opens Northstar's authorization page. The user signs in there and grants order access. Atlas never sees the password.",
80
+ channel: 'Direct Sign-In',
81
+ phoneMode: 'signin',
82
+ messages: conversationStart,
83
+ facts: [
84
+ { label: 'User presence', value: 'Required' },
85
+ { label: 'Requested access', value: 'Orders: read and return' },
86
+ { label: 'Credentials', value: 'Visible only to Northstar' },
87
+ ],
88
+ method: 'GET',
89
+ target: 'https://auth.northstar.example/oauth/authorize',
90
+ detailLabel: 'Trusted browser handoff',
91
+ request: `response_type=code
92
+ client_id=https://atlas.example/agent.json
93
+ redirect_uri=https://atlas.example/oauth/callback
94
+ scope=orders:read orders:return
95
+ state=[single-use, host-only]
96
+ code_challenge=[S256, host-only verifier]`,
97
+ response: `302 Found
98
+ Location: https://atlas.example/oauth/callback
99
+ ?code=[single-use authorization code]
100
+ &state=[returned unchanged]`,
101
+ },
102
+ {
103
+ title: 'An offer comes back',
104
+ summary: 'The signed-in Session continues the same conversation. Northstar finds the order and offers an even exchange or a refund.',
105
+ channel: 'Conversation',
106
+ messages: [
107
+ ...conversationStart,
108
+ { speaker: 'company', text: 'Please sign in so I can find the order.' },
109
+ { speaker: 'user', text: "I'm signed in." },
110
+ { speaker: 'atlas', text: "Thanks. I'll continue the same request." },
111
+ {
112
+ speaker: 'company',
113
+ text: 'I can exchange it for the warmer Summit jacket at no extra cost, or issue a full refund.',
114
+ },
115
+ ],
116
+ facts: [
117
+ { label: 'Order', value: 'NS-48213' },
118
+ { label: 'Options', value: 'Even exchange · Full refund' },
119
+ { label: 'Status', value: "Waiting for the user's choice" },
120
+ ],
121
+ method: 'POST',
122
+ target: 'https://api.northstar.example/poppy/conversations/cnv_return/messages',
123
+ detailLabel: 'Atlas ↔ company agent',
124
+ request: `Authorization: DPoP [host-only Session Token]
125
+ DPoP: [fresh proof]
126
+ Content-Type: application/json
127
+
128
+ { "message_id": "msg_offer", "text": "The user has signed in." }`,
129
+ response: `200 OK
130
+ {
131
+ "text": "Choose an even exchange or refund.",
132
+ "data": { "options": ["exchange", "refund"] },
133
+ "responder": { "type": "ai", "name": "Northstar" }
134
+ }`,
135
+ },
136
+ {
137
+ title: 'The exchange is set',
138
+ summary: "Atlas passes on the user's exact choice. Northstar confirms the exchange and the conversation closes with a durable result.",
139
+ channel: 'Operation + result',
140
+ messages: [
141
+ ...conversationStart,
142
+ { speaker: 'company', text: 'Please sign in so I can find the order.' },
143
+ { speaker: 'user', text: "I'm signed in." },
144
+ {
145
+ speaker: 'company',
146
+ text: 'I can offer the warmer Summit jacket or a full refund.',
147
+ },
148
+ { speaker: 'user', text: "Let's do the exchange." },
149
+ {
150
+ speaker: 'atlas',
151
+ text: 'Done. The Summit jacket ships tomorrow. A prepaid return label is in your email.',
152
+ },
153
+ ],
154
+ facts: [
155
+ { label: 'Result', value: 'Exchange arranged' },
156
+ { label: 'Return code', value: 'NRT-7K4Q' },
157
+ { label: 'Conversation', value: 'Closed' },
158
+ ],
159
+ method: 'POST',
160
+ target: 'https://api.northstar.example/poppy/operations/op_exchange/confirm',
161
+ detailLabel: 'Exact user-authorized action',
162
+ request: `Authorization: DPoP [host-only Session Token]
163
+ DPoP: [fresh proof]
164
+ Idempotency-Key: msg_exchange_choice
165
+
166
+ { "revision": 1, "choice": "exchange" }`,
167
+ response: `200 OK
168
+ {
169
+ "status": "succeeded",
170
+ "result": { "return_code": "NRT-7K4Q" },
171
+ "conversation_status": "closed"
172
+ }`,
173
+ },
174
+ ];
175
+ const speakerName = {
176
+ user: 'You',
177
+ atlas: 'Atlas',
178
+ company: 'Northstar',
179
+ };
180
+ const validateJourneyFixtures = async () => {
181
+ parseDiscoveryDocument({
182
+ protocol_version: '0.1',
183
+ organization: { name: 'Northstar Outfitters', domain: 'northstar.example' },
184
+ auth: {
185
+ issuer: 'https://auth.northstar.example',
186
+ direct: { scopes: ['orders:read', 'orders:return'] },
187
+ custom_scopes: {},
188
+ },
189
+ agent: {
190
+ protocols: [
191
+ {
192
+ type: 'poppy',
193
+ endpoint: 'https://api.northstar.example/poppy/conversations',
194
+ },
195
+ ],
196
+ },
197
+ });
198
+ buildSessionAssertionClaims({
199
+ clientId: 'https://atlas.example/agent.json',
200
+ pairwiseUserId: 'usr_host_only_journey',
201
+ tokenEndpoint: 'https://auth.northstar.example/oauth/token',
202
+ });
203
+ await buildDirectSignInRequest({
204
+ authorizationEndpoint: 'https://auth.northstar.example/oauth/authorize',
205
+ issuer: 'https://auth.northstar.example',
206
+ clientId: 'https://atlas.example/agent.json',
207
+ redirectUri: 'https://atlas.example/oauth/callback',
208
+ scopes: ['orders:read', 'orders:return'],
209
+ });
210
+ };
211
+ const ChatPhone = ({ step, onAuthorized }) => (_jsxs(Box, { width: "100%", maxWidth: 340, height: 560, mx: "auto", border: "10px solid", borderColor: "fg.default", borderRadius: 36, bg: "canvas.default", overflow: "hidden", boxShadow: "shadow.large", display: "flex", flexDirection: "column", children: [_jsxs(Box, { px: 3, py: 2, display: "flex", justifyContent: "space-between", borderBottom: "1px solid", borderColor: "border.default", children: [_jsx(Text, { sx: { fontSize: 0, fontWeight: 600 }, children: "9:41" }), _jsx(Text, { sx: { fontSize: 0, fontWeight: 600 }, children: "5G \u00B7 92%" })] }), step.phoneMode === 'signin' ? (_jsxs(Box, { flex: 1, display: "flex", flexDirection: "column", children: [_jsx(Box, { bg: "success.emphasis", color: "fg.onEmphasis", p: 3, children: _jsx(Text, { sx: { fontWeight: 700 }, children: "N \u00B7 Northstar Outfitters" }) }), _jsxs(Box, { p: 3, display: "flex", flexDirection: "column", gap: 3, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "auth.northstar.example" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3 }, children: "Let Atlas access your order?" }), _jsx(Text, { children: "Atlas can read order NS-48213 and arrange the return you requested." }), _jsxs(Box, { p: 3, bg: "canvas.subtle", borderRadius: 2, children: [_jsx(Text, { sx: { fontWeight: 600 }, children: "Requested access" }), _jsx(Text, { as: "p", sx: { mt: 1, color: 'fg.muted' }, children: "Read this order \u00B7 Arrange one return" })] }), _jsx(Button, { variant: "primary", block: true, onClick: onAuthorized, children: "Authorize Atlas" }), _jsx(Text, { textAlign: "center", sx: { fontSize: 0, color: 'fg.muted' }, children: "Passwords and account credentials stay with Northstar." })] })] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { px: 3, py: 2, display: "flex", gap: 2, alignItems: "center", children: [_jsx(Box, { width: 30, height: 30, borderRadius: "50%", bg: "accent.emphasis", color: "fg.onEmphasis", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 700, children: "A" }), _jsxs(Box, { children: [_jsx(Text, { as: "div", sx: { fontWeight: 700 }, children: "Atlas" }), _jsx(Text, { as: "div", sx: { fontSize: 0, color: 'fg.muted' }, children: "Your personal agent" })] })] }), _jsx(Box, { flex: 1, overflow: "auto", bg: "canvas.subtle", p: 2, display: "flex", flexDirection: "column", gap: 2, children: step.messages.map((message, index) => {
212
+ const fromUser = message.speaker === 'user';
213
+ return (_jsxs(Box, { alignSelf: fromUser ? 'flex-end' : 'flex-start', maxWidth: "88%", px: 2, py: 2, borderRadius: 3, color: fromUser ? 'fg.onEmphasis' : 'fg.default', bg: fromUser ? 'accent.emphasis' : 'canvas.default', border: fromUser ? 'none' : '1px solid', borderColor: "border.default", children: [!fromUser ? (_jsx(Text, { as: "div", sx: {
214
+ mb: 1,
215
+ fontSize: 0,
216
+ fontWeight: 700,
217
+ color: message.speaker === 'company'
218
+ ? 'success.fg'
219
+ : 'accent.fg',
220
+ }, children: speakerName[message.speaker] })) : null, _jsx(Text, { sx: { fontSize: 1 }, children: message.text })] }, `${message.speaker}-${index}`));
221
+ }) })] }))] }));
222
+ /** A five-step user, company, and wire-level PAP walkthrough. */
223
+ const PapGuidedJourneyView = () => {
224
+ const [stepIndex, setStepIndex] = useState(0);
225
+ const [detailsOpen, setDetailsOpen] = useState(true);
226
+ const [fixtureState, setFixtureState] = useState('checking');
227
+ const step = STEPS[stepIndex] ?? STEPS[0];
228
+ useEffect(() => {
229
+ void validateJourneyFixtures()
230
+ .then(() => setFixtureState('valid'))
231
+ .catch(() => setFixtureState('invalid'));
232
+ }, []);
233
+ const go = (next) => {
234
+ setStepIndex(Math.max(0, Math.min(STEPS.length - 1, next)));
235
+ };
236
+ return (_jsxs(PapExamplePage, { eyebrow: "PAP \u00B7 Guided interaction", title: "A return, from first request to confirmed exchange", description: "Follow what the user experiences, what the company is allowed to see, and which PAP exchange moves the work forward. The story uses SDK-validated discovery, Session, and Direct Sign-In fixtures; protocol credentials are always represented as host-only placeholders.", maxWidth: 1180, children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, flexWrap: "wrap", p: 3, bg: "canvas.default", border: "1px solid", borderColor: "border.default", borderRadius: 2, children: [_jsxs(Box, { children: [_jsx(Label, { variant: "accent", children: "Company agent \u00B7 Direct Sign-In" }), _jsx(Heading, { as: "h2", sx: { mt: 1, fontSize: 2 }, children: "The jacket is not warm enough" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Atlas finds Northstar, keeps one Session, and asks before acting." })] }), _jsx(Label, { variant: fixtureState === 'valid' ? 'success' : 'secondary', children: fixtureState === 'valid'
237
+ ? 'SDK fixtures validated'
238
+ : fixtureState === 'invalid'
239
+ ? 'Fixture validation failed'
240
+ : 'Validating SDK fixtures…' })] }), _jsx(Box, { display: "flex", gap: 2, flexWrap: "wrap", "aria-label": "Journey steps", children: STEPS.map((candidate, index) => (_jsxs(Button, { variant: index === stepIndex ? 'primary' : 'default', onClick: () => go(index), children: [index + 1, ". ", candidate.channel] }, candidate.title))) }), _jsxs(Box, { display: "grid", sx: {
241
+ gridTemplateColumns: [
242
+ '1fr',
243
+ 'minmax(300px, 0.78fr) minmax(460px, 1.4fr)',
244
+ ],
245
+ }, gap: 4, alignItems: "start", children: [_jsx(ChatPhone, { step: step, onAuthorized: () => go(stepIndex + 1) }), _jsxs(Box, { minHeight: 560, bg: "canvas.default", border: "1px solid", borderColor: "border.default", borderRadius: 2, overflow: "hidden", display: "flex", flexDirection: "column", children: [_jsxs(Box, { p: 3, borderBottom: "1px solid", borderColor: "border.default", children: [_jsxs(Text, { as: "div", sx: { fontSize: 0, color: 'fg.muted' }, children: ["Step ", stepIndex + 1, " of ", STEPS.length, " \u00B7 ", step.channel] }), _jsx(Heading, { as: "h2", sx: { mt: 1, fontSize: 3 }, children: step.title }), _jsx(Text, { as: "p", sx: { mt: 2, color: 'fg.muted' }, children: step.summary })] }), _jsxs(Box, { p: 3, display: "flex", flexDirection: "column", gap: 3, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsx(Box, { width: 24, height: 24, borderRadius: 1, bg: "success.emphasis", color: "fg.onEmphasis", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: 700, children: "N" }), _jsx(Text, { sx: { fontWeight: 700 }, children: "What Northstar sees" })] }), _jsx(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", px: 3, children: step.facts.map(fact => (_jsxs(Box, { display: "flex", justifyContent: "space-between", gap: 3, py: 2, borderBottom: "1px solid", borderColor: "border.muted", sx: { '&:last-child': { borderBottom: 0 } }, children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: fact.label }), _jsx(Text, { textAlign: "right", sx: { fontWeight: 600 }, children: fact.value })] }, fact.label))) }), _jsxs(Button, { variant: "invisible", sx: { alignSelf: 'flex-start', px: 0 }, onClick: () => setDetailsOpen(open => !open), children: ['{ }', " ", detailsOpen ? 'Hide' : 'Show', " protocol details"] }), detailsOpen ? (_jsxs(Box, { bg: "#0d1117", color: "#c9d1d9", borderRadius: 2, overflow: "hidden", children: [_jsxs(Box, { px: 3, py: 2, display: "flex", justifyContent: "space-between", gap: 2, borderBottom: "1px solid", borderColor: "#30363d", children: [_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: step.detailLabel }), _jsx(Label, { variant: "secondary", children: step.channel })] }), _jsxs(Box, { p: 3, maxHeight: 270, overflow: "auto", children: [_jsxs(Text, { as: "div", sx: {
246
+ fontFamily: 'mono',
247
+ fontWeight: 700,
248
+ color: '#ff7b72',
249
+ }, children: [step.method, " ", step.target] }), _jsx(Box, { as: "pre", mt: 2, mb: 3, sx: {
250
+ fontFamily: 'mono',
251
+ fontSize: 0,
252
+ whiteSpace: 'pre-wrap',
253
+ }, children: step.request }), _jsx(Text, { as: "div", sx: {
254
+ fontFamily: 'mono',
255
+ fontWeight: 700,
256
+ color: '#7ee787',
257
+ }, children: "Response" }), _jsx(Box, { as: "pre", mt: 2, mb: 0, sx: {
258
+ fontFamily: 'mono',
259
+ fontSize: 0,
260
+ whiteSpace: 'pre-wrap',
261
+ }, children: step.response })] })] })) : null] })] })] }), _jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "center", gap: 3, children: [_jsx(Button, { disabled: stepIndex === 0, onClick: () => go(stepIndex - 1), children: "Previous" }), _jsx(Box, { display: "flex", gap: 2, "aria-label": "Current step", children: STEPS.map((candidate, index) => (_jsx(Box, { as: "button", "aria-label": `Go to step ${index + 1}: ${candidate.title}`, width: 10, height: 10, p: 0, border: 0, borderRadius: "50%", bg: index === stepIndex ? 'fg.default' : 'border.default', sx: { cursor: 'pointer' }, onClick: () => go(index) }, candidate.title))) }), _jsx(Button, { variant: stepIndex === STEPS.length - 1 ? 'default' : 'primary', disabled: stepIndex === STEPS.length - 1, onClick: () => go(stepIndex + 1), children: "Next step" })] })] }));
262
+ };
263
+ export default PapGuidedJourneyView;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ /** Exercise the real Session client and retain only safe lifecycle evidence. */
3
+ declare const PapSessionLifecycleView: React.FC;
4
+ export default PapSessionLifecycleView;
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useState } from 'react';
7
+ import { Box } from '@datalayer/primer-addons';
8
+ import { contribution, definePlugin } from '@datalayer/reactor';
9
+ import { Button, Text } from '@primer/react';
10
+ import { DpopProofProviders, SecretValue, SessionClient, Signers, buildPapReactor, } from '@datalayer/personal-agent-protocol';
11
+ import { PapExamplePage, PapResult } from './PapExamplePage';
12
+ /** Exercise the real Session client and retain only safe lifecycle evidence. */
13
+ const PapSessionLifecycleView = () => {
14
+ const [summary, setSummary] = useState(null);
15
+ const [error, setError] = useState(null);
16
+ const runLifecycle = async () => {
17
+ setError(null);
18
+ let calls = 0;
19
+ const forms = [];
20
+ const purposes = [];
21
+ const proofRequests = [];
22
+ const localFetch = async (_input, init) => {
23
+ calls += 1;
24
+ forms.push(new URLSearchParams(String(init?.body ?? '')));
25
+ if (calls === 1) {
26
+ return new Response(JSON.stringify({ error: 'use_dpop_nonce' }), {
27
+ status: 400,
28
+ headers: {
29
+ 'content-type': 'application/json',
30
+ 'DPoP-Nonce': 'host-only-company-nonce',
31
+ },
32
+ });
33
+ }
34
+ return new Response(JSON.stringify({
35
+ access_token: `host-only-session-token-${calls}`,
36
+ token_type: 'DPoP',
37
+ expires_in: 300,
38
+ scope: 'poppy:read',
39
+ session_id: 'ses_example',
40
+ signed_in: false,
41
+ }), { status: 200, headers: { 'content-type': 'application/json' } });
42
+ };
43
+ const securityPlugin = definePlugin({
44
+ name: '@datalayer/loop-example-pap-session-security',
45
+ contributes: [
46
+ contribution(Signers, {
47
+ async sign(_claims, options) {
48
+ purposes.push(options.purpose);
49
+ return new SecretValue(`host-only-${options.purpose}-${purposes.length}`);
50
+ },
51
+ }, { id: 'example-signer' }),
52
+ contribution(DpopProofProviders, {
53
+ async create(request) {
54
+ proofRequests.push(request);
55
+ return new SecretValue(`host-only-proof-${proofRequests.length}`);
56
+ },
57
+ }, { id: 'example-dpop' }),
58
+ ],
59
+ });
60
+ const reactor = buildPapReactor({ fetch: localFetch }, [securityPlugin]);
61
+ try {
62
+ const client = new SessionClient({ reactor, fetch: localFetch });
63
+ const options = {
64
+ clientId: 'https://assistant.example/agent.json',
65
+ pairwiseUserId: 'usr_host_only_example',
66
+ issuer: 'https://company.example',
67
+ tokenEndpoint: 'https://company.example/oauth/token',
68
+ tenantId: 'example-tenant',
69
+ };
70
+ const started = await client.start(options);
71
+ const renewed = await client.renew({
72
+ ...options,
73
+ sessionId: started.sessionId,
74
+ });
75
+ setSummary({
76
+ startStatus: 'created',
77
+ renewStatus: 'renewed',
78
+ signedIn: false,
79
+ tokenType: 'DPoP',
80
+ scopes: renewed.scopes,
81
+ sameSession: started.sessionId === renewed.sessionId,
82
+ nonceRetry: proofRequests.length === 3 &&
83
+ proofRequests[0]?.nonce === undefined &&
84
+ proofRequests[1]?.nonce === 'host-only-company-nonce' &&
85
+ proofRequests[2]?.nonce === undefined,
86
+ freshAssertions: purposes.length === 6,
87
+ freshProofs: proofRequests.length === 3,
88
+ renewalBoundToSession: forms.at(-1)?.has('session_id') === true,
89
+ sessionIdWithheld: true,
90
+ credentialsWithheld: true,
91
+ });
92
+ }
93
+ catch (cause) {
94
+ setSummary(null);
95
+ setError(cause instanceof Error ? cause.message : String(cause));
96
+ }
97
+ finally {
98
+ reactor.stop();
99
+ }
100
+ };
101
+ return (_jsxs(PapExamplePage, { eyebrow: "PAP \u00B7 Signed-out Session", title: "Start and renew without giving credentials to the agent", description: "This Reactor view drives the real PAP SessionClient against a deterministic company transport. It handles a DPoP nonce challenge, generates fresh assertions and proofs, and renews the same Session. Only lifecycle evidence enters React state.", children: [_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 3, p: 3, border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.default", children: [_jsx(Text, { sx: { color: 'fg.muted' }, children: "Exercise start, nonce retry, and renewal entirely inside this host." }), _jsx(Button, { variant: "primary", onClick: () => void runLifecycle(), children: "Run lifecycle" })] }), error ? _jsx(Text, { sx: { color: 'danger.fg' }, children: error }) : null, summary ? (_jsx(PapResult, { title: "Safe Session evidence", value: summary })) : null] }));
102
+ };
103
+ export default PapSessionLifecycleView;
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './client';
3
3
  export * from './collaboration';
4
4
  export * from './components';
5
5
  export * from './mcp';
6
+ export * from './pap';
6
7
  export * from './stores';
7
8
  export * from './identity';
8
9
  export * from './config';
package/lib/index.js CHANGED
@@ -7,6 +7,7 @@ export * from './client';
7
7
  export * from './collaboration';
8
8
  export * from './components';
9
9
  export * from './mcp';
10
+ export * from './pap';
10
11
  export * from './stores';
11
12
  export * from './identity';
12
13
  export * from './config';
@@ -0,0 +1,42 @@
1
+ import { type DiscoveredCompany, type DiscoveryOptions, type JsonWebKeySet, type ClientMetadata, type IdentityRequestOptions } from '@datalayer/personal-agent-protocol';
2
+ /** Verified public company capabilities safe for model and UI surfaces. */
3
+ export interface PapCompanySummary {
4
+ readonly organization: string;
5
+ readonly domain: string;
6
+ readonly protocolVersion: string;
7
+ readonly issuer: string | null;
8
+ readonly signIn: readonly string[];
9
+ readonly interfaces: readonly string[];
10
+ readonly extensions: Readonly<Record<string, string>>;
11
+ }
12
+ /** Verified public personal-agent identity safe for model and UI surfaces. */
13
+ export interface PapAgentIdentitySummary {
14
+ readonly clientId: string;
15
+ readonly clientName: string;
16
+ readonly redirectCount: number;
17
+ readonly signingAlgorithms: readonly string[];
18
+ readonly extensions: Readonly<Record<string, string>>;
19
+ }
20
+ type DiscoverCompany = (domain: string, options?: DiscoveryOptions) => Promise<DiscoveredCompany>;
21
+ interface IdentityClient {
22
+ fetchMetadata(clientId: string, options?: IdentityRequestOptions): Promise<ClientMetadata>;
23
+ fetchJwks(clientId: string, options?: IdentityRequestOptions): Promise<JsonWebKeySet>;
24
+ }
25
+ export interface PapPersonalAgentCapabilityOptions {
26
+ readonly discover?: DiscoverCompany;
27
+ readonly identity?: IdentityClient;
28
+ }
29
+ /**
30
+ * Use PAP clients while enforcing Agent Runtimes' public-summary boundary.
31
+ *
32
+ * Authorization URLs, callback state, assertions, proofs, and tokens are not
33
+ * represented by this API. Those values remain in deterministic host
34
+ * services and cross runtime boundaries only as opaque references.
35
+ */
36
+ export declare class PapPersonalAgentCapability {
37
+ #private;
38
+ constructor(options?: PapPersonalAgentCapabilityOptions);
39
+ discoverCompany(domain: string, options?: DiscoveryOptions): Promise<PapCompanySummary>;
40
+ inspectAgent(clientId: string, options?: IdentityRequestOptions): Promise<PapAgentIdentitySummary>;
41
+ }
42
+ export {};
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { AgentIdentityClient, discoverCompany, } from '@datalayer/personal-agent-protocol';
6
+ function extensionVersions(extensions) {
7
+ return Object.freeze(Object.fromEntries(Object.entries(extensions).map(([name, value]) => [name, value.version])));
8
+ }
9
+ /**
10
+ * Use PAP clients while enforcing Agent Runtimes' public-summary boundary.
11
+ *
12
+ * Authorization URLs, callback state, assertions, proofs, and tokens are not
13
+ * represented by this API. Those values remain in deterministic host
14
+ * services and cross runtime boundaries only as opaque references.
15
+ */
16
+ export class PapPersonalAgentCapability {
17
+ #discover;
18
+ #identity;
19
+ constructor(options = {}) {
20
+ this.#discover = options.discover ?? discoverCompany;
21
+ this.#identity = options.identity ?? new AgentIdentityClient();
22
+ }
23
+ async discoverCompany(domain, options = {}) {
24
+ const company = await this.#discover(domain, options);
25
+ const { document } = company;
26
+ const signIn = ['direct', 'device', 'mediated'].filter(method => document.auth?.[method] !== undefined);
27
+ const interfaces = [
28
+ ...(document.agent?.protocols.map(protocol => `agent:${protocol.type}`) ??
29
+ []),
30
+ ...document.apis.map(api => `api:${api.type}`),
31
+ ...(document.web === undefined ? [] : ['web']),
32
+ ];
33
+ return Object.freeze({
34
+ organization: document.organization.name,
35
+ domain: document.organization.domain,
36
+ protocolVersion: document.protocol_version,
37
+ issuer: document.auth?.issuer ?? null,
38
+ signIn: Object.freeze(signIn),
39
+ interfaces: Object.freeze(interfaces),
40
+ extensions: extensionVersions(document.extensions),
41
+ });
42
+ }
43
+ async inspectAgent(clientId, options = {}) {
44
+ const metadata = await this.#identity.fetchMetadata(clientId, options);
45
+ const keys = await this.#identity.fetchJwks(clientId, options);
46
+ const signingAlgorithms = [
47
+ ...new Set(keys.keys.map(key => key.alg ?? (key.kty === 'EC' ? 'ES256' : 'RS256'))),
48
+ ].sort();
49
+ return Object.freeze({
50
+ clientId: metadata.client_id,
51
+ clientName: metadata.client_name,
52
+ redirectCount: metadata.redirect_uris.length,
53
+ signingAlgorithms: Object.freeze(signingAlgorithms),
54
+ extensions: extensionVersions(metadata.extensions),
55
+ });
56
+ }
57
+ }