@datalayer/agent-runtimes 1.3.106 → 1.3.108
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.
- package/dist-embed/datalayer-app-main.js +4 -4
- package/lib/apps/apps/computer.d.ts +55 -2
- package/lib/apps/apps/computer.js +82 -4
- package/lib/apps/plugins/app-computer/AppComputer.d.ts +7 -3
- package/lib/apps/plugins/app-computer/AppComputer.js +91 -4
- package/lib/examples/LoopAppComputerExample.d.ts +4 -3
- package/lib/examples/LoopAppComputerExample.js +11 -6
- package/lib/examples/pap/PapGuidedJourneyView.js +224 -39
- package/lib/specs/actions.js +22 -0
- package/lib/types/agentspecs.d.ts +2 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +6 -0
- package/scripts/validate_pap_examples.py +18 -0
|
@@ -3,10 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { useEffect, useState } from 'react';
|
|
6
|
+
import { useEffect, useMemo, useRef, useState, } from 'react';
|
|
7
7
|
import { Box } from '@datalayer/primer-addons';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { contribution, definePlugin } from '@datalayer/reactor';
|
|
9
|
+
import { Button, Heading, IconButton, Label, Text } from '@primer/react';
|
|
10
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@primer/octicons-react';
|
|
11
|
+
import { ConversationClient, DpopFetchClient, DpopProofProviders, SecretValue, buildPapReactor, buildDirectSignInRequest, buildSessionAssertionClaims, parseDiscoveryDocument, } from '@datalayer/personal-agent-protocol';
|
|
12
|
+
import { ChatMessageList } from '../../chat/messages/ChatMessageList';
|
|
10
13
|
import { PapExamplePage } from './PapExamplePage';
|
|
11
14
|
const conversationStart = [
|
|
12
15
|
{
|
|
@@ -125,18 +128,31 @@ Location: https://atlas.example/oauth/callback
|
|
|
125
128
|
DPoP: [fresh proof]
|
|
126
129
|
Content-Type: application/json
|
|
127
130
|
|
|
128
|
-
{ "message_id": "msg_offer", "text": "The user has signed in." }`,
|
|
129
|
-
response: `200 OK
|
|
130
131
|
{
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
"message": {
|
|
133
|
+
"id": "msg_offer",
|
|
134
|
+
"sender": "agent",
|
|
135
|
+
"text": "The user has signed in. Continue the return request."
|
|
136
|
+
}
|
|
137
|
+
}`,
|
|
138
|
+
response: `202 Accepted
|
|
139
|
+
{
|
|
140
|
+
"conversation_id": "cnv_return",
|
|
141
|
+
"events": [{
|
|
142
|
+
"type": "message",
|
|
143
|
+
"message": {
|
|
144
|
+
"role": "company",
|
|
145
|
+
"text": "Choose an even exchange or refund."
|
|
146
|
+
}
|
|
147
|
+
}],
|
|
148
|
+
"status": "idle",
|
|
149
|
+
"responder": "agent"
|
|
134
150
|
}`,
|
|
135
151
|
},
|
|
136
152
|
{
|
|
137
153
|
title: 'The exchange is set',
|
|
138
154
|
summary: "Atlas passes on the user's exact choice. Northstar confirms the exchange and the conversation closes with a durable result.",
|
|
139
|
-
channel: '
|
|
155
|
+
channel: 'Conversation result',
|
|
140
156
|
messages: [
|
|
141
157
|
...conversationStart,
|
|
142
158
|
{ speaker: 'company', text: 'Please sign in so I can find the order.' },
|
|
@@ -157,18 +173,30 @@ Content-Type: application/json
|
|
|
157
173
|
{ label: 'Conversation', value: 'Closed' },
|
|
158
174
|
],
|
|
159
175
|
method: 'POST',
|
|
160
|
-
target: 'https://api.northstar.example/poppy/
|
|
161
|
-
detailLabel: '
|
|
176
|
+
target: 'https://api.northstar.example/poppy/conversations/cnv_return/messages',
|
|
177
|
+
detailLabel: 'Atlas ↔ company agent',
|
|
162
178
|
request: `Authorization: DPoP [host-only Session Token]
|
|
163
179
|
DPoP: [fresh proof]
|
|
164
|
-
|
|
180
|
+
Content-Type: application/json
|
|
165
181
|
|
|
166
|
-
{ "revision": 1, "choice": "exchange" }`,
|
|
167
|
-
response: `200 OK
|
|
168
182
|
{
|
|
169
|
-
"
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
"message": {
|
|
184
|
+
"id": "msg_exchange_choice",
|
|
185
|
+
"sender": "agent",
|
|
186
|
+
"text": "The user chose the even exchange."
|
|
187
|
+
}
|
|
188
|
+
}`,
|
|
189
|
+
response: `202 Accepted
|
|
190
|
+
{
|
|
191
|
+
"conversation_id": "cnv_return",
|
|
192
|
+
"events": [{
|
|
193
|
+
"type": "message",
|
|
194
|
+
"message": {
|
|
195
|
+
"role": "company",
|
|
196
|
+
"text": "The exchange is arranged."
|
|
197
|
+
}
|
|
198
|
+
}],
|
|
199
|
+
"status": "closed"
|
|
172
200
|
}`,
|
|
173
201
|
},
|
|
174
202
|
];
|
|
@@ -177,7 +205,24 @@ const speakerName = {
|
|
|
177
205
|
atlas: 'Atlas',
|
|
178
206
|
company: 'Northstar',
|
|
179
207
|
};
|
|
180
|
-
const
|
|
208
|
+
const companyReply = (response) => {
|
|
209
|
+
if (!('events' in response)) {
|
|
210
|
+
throw new Error('Northstar returned a receipt without a message');
|
|
211
|
+
}
|
|
212
|
+
const reply = response.events.find(event => event.type === 'message' && event.message?.role === 'company')?.message?.text;
|
|
213
|
+
if (!reply) {
|
|
214
|
+
throw new Error('Northstar returned no company reply');
|
|
215
|
+
}
|
|
216
|
+
return reply;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Run the visible transcript through the actual PAP conversation client.
|
|
220
|
+
*
|
|
221
|
+
* The company side is deliberately an in-page deterministic simulator: this
|
|
222
|
+
* makes the example runnable without an account while ensuring that requests,
|
|
223
|
+
* proof-bound transport, response parsing, and messages all use the real SDK.
|
|
224
|
+
*/
|
|
225
|
+
const runJourney = async () => {
|
|
181
226
|
parseDiscoveryDocument({
|
|
182
227
|
protocol_version: '0.1',
|
|
183
228
|
organization: { name: 'Northstar Outfitters', domain: 'northstar.example' },
|
|
@@ -207,42 +252,182 @@ const validateJourneyFixtures = async () => {
|
|
|
207
252
|
redirectUri: 'https://atlas.example/oauth/callback',
|
|
208
253
|
scopes: ['orders:read', 'orders:return'],
|
|
209
254
|
});
|
|
255
|
+
const replies = [
|
|
256
|
+
'I can help with that. Please sign in so I can find the order.',
|
|
257
|
+
'Order NS-48213 can be exchanged for the warmer Summit jacket at no extra cost, or refunded in full.',
|
|
258
|
+
'The exchange is arranged. The Summit jacket ships tomorrow, and return code NRT-7K4Q is ready.',
|
|
259
|
+
];
|
|
260
|
+
let requestIndex = 0;
|
|
261
|
+
const localCompanyFetch = async (_input, init) => {
|
|
262
|
+
const index = requestIndex++;
|
|
263
|
+
const reply = replies[index];
|
|
264
|
+
if (!reply) {
|
|
265
|
+
return new Response(JSON.stringify({ error: 'unexpected_request' }), {
|
|
266
|
+
status: 400,
|
|
267
|
+
headers: { 'content-type': 'application/json' },
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const incoming = JSON.parse(String(init?.body ?? '{}'));
|
|
271
|
+
return new Response(JSON.stringify({
|
|
272
|
+
conversation_id: 'cnv_return',
|
|
273
|
+
events: [
|
|
274
|
+
{
|
|
275
|
+
id: `evt_${index + 1}`,
|
|
276
|
+
type: 'message',
|
|
277
|
+
created_at: `2026-10-10T09:4${index}:00Z`,
|
|
278
|
+
message: {
|
|
279
|
+
id: `reply_${index + 1}`,
|
|
280
|
+
sender: 'agent',
|
|
281
|
+
role: 'company',
|
|
282
|
+
text: reply,
|
|
283
|
+
context: { in_reply_to: incoming.message?.id ?? 'unknown' },
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
cursor: `cursor_${index + 1}`,
|
|
288
|
+
has_more: false,
|
|
289
|
+
status: index === replies.length - 1 ? 'closed' : 'idle',
|
|
290
|
+
responder: 'agent',
|
|
291
|
+
}), {
|
|
292
|
+
status: index === 0 ? 201 : 202,
|
|
293
|
+
headers: { 'content-type': 'application/json' },
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
const securityPlugin = definePlugin({
|
|
297
|
+
name: '@datalayer/loop-example-pap-guided-security',
|
|
298
|
+
contributes: [
|
|
299
|
+
contribution(DpopProofProviders, {
|
|
300
|
+
async create() {
|
|
301
|
+
return new SecretValue(`host-only-proof-${requestIndex + 1}`);
|
|
302
|
+
},
|
|
303
|
+
}, { id: 'guided-example-dpop' }),
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
const reactor = buildPapReactor({ fetch: localCompanyFetch }, [
|
|
307
|
+
securityPlugin,
|
|
308
|
+
]);
|
|
309
|
+
try {
|
|
310
|
+
const transport = new DpopFetchClient({
|
|
311
|
+
reactor,
|
|
312
|
+
fetch: localCompanyFetch,
|
|
313
|
+
});
|
|
314
|
+
const conversations = new ConversationClient({ reactor, transport });
|
|
315
|
+
const session = (signedIn) => ({
|
|
316
|
+
accessToken: new SecretValue(signedIn ? 'host-only-signed-in-token' : 'host-only-session-token'),
|
|
317
|
+
tokenType: 'DPoP',
|
|
318
|
+
expiresIn: 300,
|
|
319
|
+
scopes: signedIn ? ['orders:read', 'orders:return'] : ['poppy:read'],
|
|
320
|
+
sessionId: 'ses_guided_example',
|
|
321
|
+
signedIn,
|
|
322
|
+
});
|
|
323
|
+
const requestOptions = (token) => ({
|
|
324
|
+
token,
|
|
325
|
+
tenantId: 'guided-example',
|
|
326
|
+
pairwiseUserId: 'usr_host_only_journey',
|
|
327
|
+
issuer: 'https://auth.northstar.example',
|
|
328
|
+
wait: 0,
|
|
329
|
+
});
|
|
330
|
+
const endpoint = 'https://api.northstar.example/poppy/conversations';
|
|
331
|
+
const started = await conversations.start(endpoint, {
|
|
332
|
+
id: conversations.generateMessageId(),
|
|
333
|
+
sender: 'agent',
|
|
334
|
+
text: conversationStart[0]?.text,
|
|
335
|
+
}, requestOptions(session(false)));
|
|
336
|
+
const signInReply = companyReply(started);
|
|
337
|
+
const offer = companyReply(await conversations.send(endpoint, 'cnv_return', {
|
|
338
|
+
id: conversations.generateMessageId(),
|
|
339
|
+
sender: 'agent',
|
|
340
|
+
text: 'The user has signed in. Continue the return request.',
|
|
341
|
+
}, requestOptions(session(true))));
|
|
342
|
+
const confirmation = companyReply(await conversations.send(endpoint, 'cnv_return', {
|
|
343
|
+
id: conversations.generateMessageId(),
|
|
344
|
+
sender: 'agent',
|
|
345
|
+
text: 'The user chose the even exchange.',
|
|
346
|
+
}, requestOptions(session(true))));
|
|
347
|
+
const signedInMessages = [
|
|
348
|
+
...conversationStart,
|
|
349
|
+
{ speaker: 'company', text: signInReply },
|
|
350
|
+
{ speaker: 'user', text: "I'm signed in." },
|
|
351
|
+
{ speaker: 'atlas', text: "Thanks. I'll continue the same request." },
|
|
352
|
+
{ speaker: 'company', text: offer },
|
|
353
|
+
];
|
|
354
|
+
return [
|
|
355
|
+
conversationStart,
|
|
356
|
+
[...conversationStart, { speaker: 'company', text: signInReply }],
|
|
357
|
+
conversationStart,
|
|
358
|
+
signedInMessages,
|
|
359
|
+
[
|
|
360
|
+
...signedInMessages,
|
|
361
|
+
{ speaker: 'user', text: "Let's do the exchange." },
|
|
362
|
+
{ speaker: 'company', text: confirmation },
|
|
363
|
+
],
|
|
364
|
+
];
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
reactor.stop();
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const ChatPhone = ({ step, messages, onAuthorized }) => {
|
|
371
|
+
const messagesEndRef = useRef(null);
|
|
372
|
+
const displayItems = useMemo(() => messages.map((message, index) => ({
|
|
373
|
+
id: `guided-${step.channel}-${index}`,
|
|
374
|
+
role: message.speaker === 'user' ? 'user' : 'assistant',
|
|
375
|
+
content: message.text,
|
|
376
|
+
createdAt: new Date(`2026-10-10T09:4${index}:00Z`),
|
|
377
|
+
speaker: {
|
|
378
|
+
id: message.speaker,
|
|
379
|
+
name: speakerName[message.speaker],
|
|
380
|
+
role: message.speaker === 'atlas'
|
|
381
|
+
? 'personal agent'
|
|
382
|
+
: message.speaker === 'company'
|
|
383
|
+
? 'company agent'
|
|
384
|
+
: 'person',
|
|
385
|
+
tone: message.speaker === 'company' ? 'success' : 'accent',
|
|
386
|
+
initials: message.speaker === 'company'
|
|
387
|
+
? 'N'
|
|
388
|
+
: message.speaker === 'atlas'
|
|
389
|
+
? 'A'
|
|
390
|
+
: 'Y',
|
|
391
|
+
},
|
|
392
|
+
})), [messages, step.channel]);
|
|
393
|
+
return (_jsxs(Box, { width: "100%", maxWidth: 340, height: 560, mx: "auto", border: "7px solid", borderColor: "fg.default", borderRadius: 20, 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", children: _jsx(ChatMessageList, { displayItems: displayItems, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: {
|
|
394
|
+
userAvatar: 'Y',
|
|
395
|
+
assistantAvatar: 'A',
|
|
396
|
+
showAvatars: true,
|
|
397
|
+
avatarSize: 26,
|
|
398
|
+
userAvatarBg: 'accent.muted',
|
|
399
|
+
assistantAvatarBg: 'accent.emphasis',
|
|
400
|
+
}, padding: 2, emptyContent: null, messagesEndRef: messagesEndRef, onRespond: async () => undefined, density: "comfortable" }) })] }))] }));
|
|
210
401
|
};
|
|
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
402
|
/** A five-step user, company, and wire-level PAP walkthrough. */
|
|
223
403
|
const PapGuidedJourneyView = () => {
|
|
224
404
|
const [stepIndex, setStepIndex] = useState(0);
|
|
225
405
|
const [detailsOpen, setDetailsOpen] = useState(true);
|
|
226
|
-
const [
|
|
406
|
+
const [interactionState, setInteractionState] = useState('checking');
|
|
407
|
+
const [liveMessages, setLiveMessages] = useState(null);
|
|
227
408
|
const step = STEPS[stepIndex] ?? STEPS[0];
|
|
409
|
+
const messages = liveMessages?.[stepIndex] ?? step.messages;
|
|
228
410
|
useEffect(() => {
|
|
229
|
-
void
|
|
230
|
-
.then(
|
|
231
|
-
|
|
411
|
+
void runJourney()
|
|
412
|
+
.then(result => {
|
|
413
|
+
setLiveMessages(result);
|
|
414
|
+
setInteractionState('valid');
|
|
415
|
+
})
|
|
416
|
+
.catch(() => setInteractionState('invalid'));
|
|
232
417
|
}, []);
|
|
233
418
|
const go = (next) => {
|
|
234
419
|
setStepIndex(Math.max(0, Math.min(STEPS.length - 1, next)));
|
|
235
420
|
};
|
|
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.
|
|
237
|
-
? '
|
|
238
|
-
:
|
|
239
|
-
? '
|
|
240
|
-
: '
|
|
421
|
+
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. Northstar's replies are parsed from real PAP ConversationClient requests against a deterministic in-page simulator; discovery and sign-in values use the SDK too. No external retailer, account, or language model is implied.", 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: interactionState === 'valid' ? 'success' : 'secondary', children: interactionState === 'valid'
|
|
422
|
+
? 'Live local PAP exchange complete'
|
|
423
|
+
: interactionState === 'invalid'
|
|
424
|
+
? 'Local PAP exchange failed'
|
|
425
|
+
: 'Running local PAP exchange…' })] }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", children: [_jsx(IconButton, { icon: ChevronLeftIcon, "aria-label": "Previous journey tab", disabled: stepIndex === 0, onClick: () => go(stepIndex - 1) }), _jsx(Box, { display: "flex", gap: 2, overflow: "auto", flex: 1, "aria-label": "Journey steps", children: STEPS.map((candidate, index) => (_jsxs(Button, { variant: index === stepIndex ? 'primary' : 'default', onClick: () => go(index), sx: { flexShrink: 0 }, children: [index + 1, ". ", candidate.channel] }, candidate.title))) }), _jsx(IconButton, { icon: ChevronRightIcon, "aria-label": "Next journey tab", disabled: stepIndex === STEPS.length - 1, onClick: () => go(stepIndex + 1) })] }), _jsxs(Box, { display: "grid", sx: {
|
|
241
426
|
gridTemplateColumns: [
|
|
242
427
|
'1fr',
|
|
243
428
|
'minmax(300px, 0.78fr) minmax(460px, 1.4fr)',
|
|
244
429
|
],
|
|
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: {
|
|
430
|
+
}, gap: 4, alignItems: "start", children: [_jsx(ChatPhone, { step: step, messages: messages, 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
431
|
fontFamily: 'mono',
|
|
247
432
|
fontWeight: 700,
|
|
248
433
|
color: '#ff7b72',
|
package/lib/specs/actions.js
CHANGED
|
@@ -42,6 +42,7 @@ export const SERVER_ACTIONS = {
|
|
|
42
42
|
default: [],
|
|
43
43
|
tools: {},
|
|
44
44
|
conditions: {},
|
|
45
|
+
signs: {},
|
|
45
46
|
},
|
|
46
47
|
chart: {
|
|
47
48
|
checked: '2026-10-02',
|
|
@@ -76,6 +77,7 @@ export const SERVER_ACTIONS = {
|
|
|
76
77
|
generate_spreadsheet: ['read'],
|
|
77
78
|
},
|
|
78
79
|
conditions: {},
|
|
80
|
+
signs: {},
|
|
79
81
|
},
|
|
80
82
|
datalayer: {
|
|
81
83
|
checked: '2026-10-05',
|
|
@@ -227,6 +229,7 @@ export const SERVER_ACTIONS = {
|
|
|
227
229
|
mount_local_folder: ['read'],
|
|
228
230
|
},
|
|
229
231
|
conditions: {},
|
|
232
|
+
signs: {},
|
|
230
233
|
},
|
|
231
234
|
earthdata: {
|
|
232
235
|
checked: '2026-10-07',
|
|
@@ -237,11 +240,13 @@ export const SERVER_ACTIONS = {
|
|
|
237
240
|
download_earth_data_granules: ['write'],
|
|
238
241
|
},
|
|
239
242
|
conditions: {},
|
|
243
|
+
signs: {},
|
|
240
244
|
},
|
|
241
245
|
eurus: {
|
|
242
246
|
default: [],
|
|
243
247
|
tools: {},
|
|
244
248
|
conditions: {},
|
|
249
|
+
signs: {},
|
|
245
250
|
},
|
|
246
251
|
filesystem: {
|
|
247
252
|
checked: '2026-10-02',
|
|
@@ -263,11 +268,13 @@ export const SERVER_ACTIONS = {
|
|
|
263
268
|
list_allowed_directories: ['read'],
|
|
264
269
|
},
|
|
265
270
|
conditions: {},
|
|
271
|
+
signs: {},
|
|
266
272
|
},
|
|
267
273
|
github: {
|
|
268
274
|
default: [],
|
|
269
275
|
tools: {},
|
|
270
276
|
conditions: {},
|
|
277
|
+
signs: {},
|
|
271
278
|
},
|
|
272
279
|
'google-workspace': {
|
|
273
280
|
checked: '2026-10-02',
|
|
@@ -536,21 +543,29 @@ export const SERVER_ACTIONS = {
|
|
|
536
543
|
},
|
|
537
544
|
],
|
|
538
545
|
},
|
|
546
|
+
signs: {
|
|
547
|
+
send_gmail_message: 'body',
|
|
548
|
+
draft_gmail_message: 'body',
|
|
549
|
+
send_message: 'message_text',
|
|
550
|
+
},
|
|
539
551
|
},
|
|
540
552
|
huggingface: {
|
|
541
553
|
default: [],
|
|
542
554
|
tools: {},
|
|
543
555
|
conditions: {},
|
|
556
|
+
signs: {},
|
|
544
557
|
},
|
|
545
558
|
kaggle: {
|
|
546
559
|
default: [],
|
|
547
560
|
tools: {},
|
|
548
561
|
conditions: {},
|
|
562
|
+
signs: {},
|
|
549
563
|
},
|
|
550
564
|
odoo: {
|
|
551
565
|
default: [],
|
|
552
566
|
tools: {},
|
|
553
567
|
conditions: {},
|
|
568
|
+
signs: {},
|
|
554
569
|
},
|
|
555
570
|
'odoo-accounting': {
|
|
556
571
|
checked: '2026-10-05',
|
|
@@ -607,11 +622,13 @@ export const SERVER_ACTIONS = {
|
|
|
607
622
|
odoo_accounting_set_tax_grids: ['write'],
|
|
608
623
|
},
|
|
609
624
|
conditions: {},
|
|
625
|
+
signs: {},
|
|
610
626
|
},
|
|
611
627
|
salesforce: {
|
|
612
628
|
default: [],
|
|
613
629
|
tools: {},
|
|
614
630
|
conditions: {},
|
|
631
|
+
signs: {},
|
|
615
632
|
},
|
|
616
633
|
slack: {
|
|
617
634
|
checked: '2026-10-02',
|
|
@@ -627,6 +644,10 @@ export const SERVER_ACTIONS = {
|
|
|
627
644
|
slack_get_user_profile: ['read'],
|
|
628
645
|
},
|
|
629
646
|
conditions: {},
|
|
647
|
+
signs: {
|
|
648
|
+
slack_post_message: 'text',
|
|
649
|
+
slack_reply_to_thread: 'text',
|
|
650
|
+
},
|
|
630
651
|
},
|
|
631
652
|
tavily: {
|
|
632
653
|
checked: '2026-10-02',
|
|
@@ -639,6 +660,7 @@ export const SERVER_ACTIONS = {
|
|
|
639
660
|
tavily_research: ['read'],
|
|
640
661
|
},
|
|
641
662
|
conditions: {},
|
|
663
|
+
signs: {},
|
|
642
664
|
},
|
|
643
665
|
};
|
|
644
666
|
/**
|
|
@@ -372,6 +372,8 @@ export interface ServerActionsSpec {
|
|
|
372
372
|
tools: Record<string, ActionClass[]>;
|
|
373
373
|
/** What an argument makes a tool do besides, by tool. */
|
|
374
374
|
conditions: Record<string, ActionConditionSpec[]>;
|
|
375
|
+
/** The argument carrying what a tool sends, signed with the application's byline (LOOP I-10), by tool. */
|
|
376
|
+
signs?: Record<string, string>;
|
|
375
377
|
}
|
|
376
378
|
/** What kind of application it is: what its user meets. */
|
|
377
379
|
export type AppKind = 'chat' | 'widget' | 'decision' | 'worker';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/agent-runtimes",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.108",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"@datalayer/lexical-loro": "^1.0.6",
|
|
161
161
|
"@datalayer/primer-addons": "^1.0.39",
|
|
162
162
|
"@datalayer/primer-rjsf": "^1.0.3",
|
|
163
|
-
"@datalayer/personal-agent-protocol": "^0.3.
|
|
163
|
+
"@datalayer/personal-agent-protocol": "^0.3.3",
|
|
164
164
|
"@datalayer/reactor": "^1.0.4",
|
|
165
165
|
"@datalayer/reactor-commands": "^1.0.4",
|
|
166
166
|
"@datalayer/reactor-manager": "^1.0.4",
|
|
@@ -171,6 +171,12 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
171
171
|
},
|
|
172
172
|
# What an argument makes it do besides.
|
|
173
173
|
"conditions": {name: found for name, found in conditions.items() if found},
|
|
174
|
+
# The argument carrying what it sends, signed with the byline (I-10).
|
|
175
|
+
"signs": {
|
|
176
|
+
name: signs
|
|
177
|
+
for name in names
|
|
178
|
+
if (signs := actions_module.server_tool_signs(server, name))
|
|
179
|
+
},
|
|
174
180
|
}
|
|
175
181
|
actions = {
|
|
176
182
|
"classes": [item.value for item in actions_module.ActionClass],
|
|
@@ -12,6 +12,17 @@ from agent_runtimes.loop.apps.build import build
|
|
|
12
12
|
|
|
13
13
|
ROOT = Path(__file__).parents[1]
|
|
14
14
|
EXAMPLES = ROOT / "examples" / "personal-agent-protocol"
|
|
15
|
+
HOST_ONLY_TOOL_ARGUMENTS = {
|
|
16
|
+
"access_token",
|
|
17
|
+
"authorization_code",
|
|
18
|
+
"callback_url",
|
|
19
|
+
"client_assertion",
|
|
20
|
+
"code_verifier",
|
|
21
|
+
"local_user_id",
|
|
22
|
+
"pairwise_user_id",
|
|
23
|
+
"redirect_uri",
|
|
24
|
+
"session_token",
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
|
|
17
28
|
def main() -> int:
|
|
@@ -33,6 +44,13 @@ def main() -> int:
|
|
|
33
44
|
if key in tool_names:
|
|
34
45
|
raise RuntimeError(f"Duplicate example tool: {app_id}/{tool.name}")
|
|
35
46
|
tool_names.add(key)
|
|
47
|
+
properties = tool.parameters.get("properties", {})
|
|
48
|
+
unsafe = HOST_ONLY_TOOL_ARGUMENTS.intersection(properties)
|
|
49
|
+
if unsafe:
|
|
50
|
+
names = ", ".join(sorted(unsafe))
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
f"Host-only PAP values exposed by {app_id}/{tool.name}: {names}"
|
|
53
|
+
)
|
|
36
54
|
sys.stdout.write(
|
|
37
55
|
f"{path.relative_to(ROOT)}: {app_id} "
|
|
38
56
|
f"({len(built.application.spec.tools)} tool)\n"
|