@datalayer/agent-runtimes 1.5.89 → 1.5.91
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/README.md +29 -297
- package/dist-embed/datalayer-app-main.js +40 -36
- package/lib/apps/embed/element.js +20 -5
- package/lib/apps/embed/visitorApp.d.ts +23 -45
- package/lib/apps/embed/visitorApp.js +65 -13
- package/lib/components/teams/A2ATeamGraph.d.ts +58 -152
- package/lib/components/teams/A2ATeamGraph.js +169 -410
- package/lib/components/teams/SceneTranscript.d.ts +5 -1
- package/lib/components/teams/SceneTranscript.js +2 -2
- package/lib/components/teams/SceneView.d.ts +17 -5
- package/lib/components/teams/SceneView.js +7 -3
- package/lib/components/teams/a2aTeamFlow.d.ts +16 -7
- package/lib/components/teams/a2aTeamFlow.js +17 -14
- package/lib/components/teams/backdrops/catalogue.d.ts +35 -0
- package/lib/components/teams/backdrops/catalogue.js +34 -0
- package/lib/components/teams/backdrops/edges.d.ts +20 -0
- package/lib/components/teams/backdrops/edges.js +29 -0
- package/lib/components/teams/backdrops/faces.d.ts +24 -0
- package/lib/components/teams/backdrops/faces.js +39 -0
- package/lib/components/teams/backdrops/halo.d.ts +14 -0
- package/lib/components/teams/backdrops/halo.js +5 -0
- package/lib/components/teams/backdrops/hero.d.ts +14 -0
- package/lib/components/teams/backdrops/hero.js +178 -0
- package/lib/components/teams/backdrops/index.d.ts +22 -0
- package/lib/components/teams/backdrops/index.js +38 -0
- package/lib/components/teams/backdrops/nodes.d.ts +123 -0
- package/lib/components/teams/backdrops/nodes.js +265 -0
- package/lib/components/teams/backdrops/plain.d.ts +14 -0
- package/lib/components/teams/backdrops/plain.js +116 -0
- package/lib/components/teams/backdrops/studio.d.ts +17 -0
- package/lib/components/teams/backdrops/studio.js +122 -0
- package/lib/components/teams/backdrops/theme.d.ts +14 -0
- package/lib/components/teams/backdrops/theme.js +19 -0
- package/lib/components/teams/backdrops/types.d.ts +214 -0
- package/lib/components/teams/backdrops/types.js +5 -0
- package/lib/components/teams/index.d.ts +2 -0
- package/lib/components/teams/index.js +2 -0
- package/lib/components/teams/sceneTranscriptRules.d.ts +32 -1
- package/lib/components/teams/sceneTranscriptRules.js +104 -2
- package/lib/components/teams/transcript/RecordingLabel.d.ts +17 -0
- package/lib/components/teams/transcript/RecordingLabel.js +7 -0
- package/lib/components/teams/transcript/SequenceDiagram.d.ts +39 -0
- package/lib/components/teams/transcript/SequenceDiagram.js +190 -0
- package/lib/components/teams/transcript/TranscriptDetail.d.ts +27 -0
- package/lib/components/teams/transcript/TranscriptDetail.js +48 -0
- package/lib/components/teams/transcript/TranscriptTable.d.ts +23 -0
- package/lib/components/teams/transcript/TranscriptTable.js +112 -0
- package/lib/components/teams/transcript/TranscriptViews.d.ts +66 -0
- package/lib/components/teams/transcript/TranscriptViews.js +135 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.d.ts +13 -0
- package/lib/components/teams/transcript/TranscriptViewsTypes.js +5 -0
- package/lib/components/teams/transcript/index.d.ts +23 -0
- package/lib/components/teams/transcript/index.js +27 -0
- package/lib/components/teams/transcript/recordingPlayback.d.ts +52 -0
- package/lib/components/teams/transcript/recordingPlayback.js +146 -0
- package/lib/components/teams/transcript/selection.d.ts +59 -0
- package/lib/components/teams/transcript/selection.js +79 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.d.ts +35 -0
- package/lib/components/teams/transcript/sequenceDiagramRules.js +158 -0
- package/lib/components/teams/transcript/transcriptDetailRules.d.ts +80 -0
- package/lib/components/teams/transcript/transcriptDetailRules.js +208 -0
- package/lib/components/teams/transcript/transcriptExport.d.ts +31 -0
- package/lib/components/teams/transcript/transcriptExport.js +82 -0
- package/lib/components/teams/transcript/transcriptRows.d.ts +76 -0
- package/lib/components/teams/transcript/transcriptRows.js +187 -0
- package/lib/components/teams/transcript/useSceneRecording.d.ts +45 -0
- package/lib/components/teams/transcript/useSceneRecording.js +96 -0
- package/lib/examples/ScenesExample.js +16 -3
- package/lib/portable/hosts/agentClient.d.ts +8 -0
- package/lib/portable/hosts/agentClient.js +2 -0
- package/lib/portable/hosts/approvalRenderers.d.ts +10 -2
- package/lib/portable/hosts/approvalRenderers.js +81 -2
- package/lib/portable/hosts/contracts.d.ts +1 -0
- package/lib/portable/hosts/contracts.js +2 -0
- package/lib/portable/hosts/contracts.json +56 -2
- package/lib/specs/actions.js +2 -0
- package/lib/specs/apps.d.ts +1 -0
- package/lib/specs/apps.js +219 -0
- package/lib/specs/sceneRecordings.d.ts +14 -0
- package/lib/specs/sceneRecordings.js +12 -0
- package/lib/specs/sceneSchema.js +11 -0
- package/lib/specs/scenes.d.ts +1 -0
- package/lib/specs/scenes.js +159 -0
- package/lib/types/scenes.d.ts +54 -0
- package/package.json +2 -1
- package/scripts/codegen/generate_mcp_servers.py +5 -16
- package/scripts/codegen/generate_scenes.py +80 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SceneRecordingFile } from '../../../types/scenes';
|
|
2
|
+
import type { SceneTranscriptLine, SceneTranscriptMember } from '../sceneTranscriptRules';
|
|
3
|
+
import { type RecordedStage } from './recordingPlayback';
|
|
4
|
+
/** How the recording is played. */
|
|
5
|
+
export type SceneRecordingOptions = {
|
|
6
|
+
/** The members, by id and name, with their connections: for the graph's flows and calls. */
|
|
7
|
+
members: readonly SceneTranscriptMember[];
|
|
8
|
+
/** The member the audience talks to: the links the graph draws flowing start there. */
|
|
9
|
+
entry: string;
|
|
10
|
+
/** One beat's lines only, by its id; every line when unsaid. */
|
|
11
|
+
beat?: string;
|
|
12
|
+
/** Faster than recorded (2 is twice as fast); 1 when unsaid: the recorded pace. */
|
|
13
|
+
speed?: number;
|
|
14
|
+
/** The longest pause between two lines, in milliseconds; none when unsaid. */
|
|
15
|
+
maxGapMs?: number;
|
|
16
|
+
/** Start playing at once; true when unsaid. */
|
|
17
|
+
autoPlay?: boolean;
|
|
18
|
+
/** Start again from the first line once the last was shown, after `loopAfterMs`. */
|
|
19
|
+
loop?: boolean;
|
|
20
|
+
/** How long the last line stays before a loop starts again; 8 s when unsaid. */
|
|
21
|
+
loopAfterMs?: number;
|
|
22
|
+
/** How often the stage is drawn again while a message flows; 250 ms when unsaid. */
|
|
23
|
+
tickMs?: number;
|
|
24
|
+
};
|
|
25
|
+
/** A recording at a moment of its playback. */
|
|
26
|
+
export type SceneRecordingPlayback = RecordedStage & {
|
|
27
|
+
/** The lines said so far, in order. */
|
|
28
|
+
lines: SceneTranscriptLine[];
|
|
29
|
+
/** Every line of what is played: the beat's, or the whole recording's. */
|
|
30
|
+
all: SceneTranscriptLine[];
|
|
31
|
+
/** *Recording, taken 29 October 2026*. */
|
|
32
|
+
label: string;
|
|
33
|
+
/** When it was taken, as the recording says it. */
|
|
34
|
+
taken: string;
|
|
35
|
+
/** What the audience is told of it, when the recording says. */
|
|
36
|
+
note?: string;
|
|
37
|
+
playing: boolean;
|
|
38
|
+
/** Every line was shown. */
|
|
39
|
+
done: boolean;
|
|
40
|
+
play: () => void;
|
|
41
|
+
pause: () => void;
|
|
42
|
+
restart: () => void;
|
|
43
|
+
};
|
|
44
|
+
/** Plays a scene's recording at its recorded pace; nothing to play when there is none. */
|
|
45
|
+
export declare function useSceneRecording(recording: SceneRecordingFile | undefined, { members, entry, beat, speed, maxGapMs, autoPlay, loop, loopAfterMs, tickMs, }: SceneRecordingOptions): SceneRecordingPlayback;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A scene's recording, played: when a stage cannot play live, its recording
|
|
7
|
+
* (`SceneRecordingFile`, `SCENE_RECORDINGS` in `specs/sceneRecordings`)
|
|
8
|
+
* plays at the pace it was recorded. The hook hands the page what to draw
|
|
9
|
+
* at each moment — the lines said so far for `TranscriptViews`, the
|
|
10
|
+
* `flows` and `calls` for `A2ATeamGraph`, and the label, *Recording, taken
|
|
11
|
+
* <date>* — and the controls: play, pause, restart.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const played = useSceneRecording(getSceneRecording(scene.id), { members, entry });
|
|
15
|
+
* <RecordingLabel taken={played.taken} />
|
|
16
|
+
* <A2ATeamGraph … flows={played.flows} calls={played.calls} />
|
|
17
|
+
* <TranscriptViews lines={played.lines} members={members} />
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* A line of a recording has no span: the drill-down says so.
|
|
21
|
+
*
|
|
22
|
+
* @module components/teams/transcript/useSceneRecording
|
|
23
|
+
*/
|
|
24
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
25
|
+
import { playbackOffsets, recordedLinesOf, recordedMomentAt, recordedStageAt, recordingLabel, } from './recordingPlayback.js';
|
|
26
|
+
const now = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
27
|
+
/** Plays a scene's recording at its recorded pace; nothing to play when there is none. */
|
|
28
|
+
export function useSceneRecording(recording, { members, entry, beat, speed = 1, maxGapMs, autoPlay = true, loop = false, loopAfterMs = 8000, tickMs = 250, }) {
|
|
29
|
+
const all = useMemo(() => (recording ? recordedLinesOf(recording, beat) : []), [recording, beat]);
|
|
30
|
+
const offsets = useMemo(() => playbackOffsets(all, { speed, maxGapMs }), [all, speed, maxGapMs]);
|
|
31
|
+
const [elapsed, setElapsed] = useState(0);
|
|
32
|
+
const [playing, setPlaying] = useState(autoPlay && all.length > 0);
|
|
33
|
+
// Where the clock started, minus what was played before a pause.
|
|
34
|
+
const started = useRef(now());
|
|
35
|
+
const total = offsets.length ? offsets[offsets.length - 1] : 0;
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setElapsed(0);
|
|
38
|
+
started.current = now();
|
|
39
|
+
setPlaying(autoPlay && all.length > 0);
|
|
40
|
+
}, [all, autoPlay]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!playing) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const timer = setInterval(() => {
|
|
46
|
+
const at = now() - started.current;
|
|
47
|
+
if (at >= total + (loop ? loopAfterMs : 0)) {
|
|
48
|
+
if (loop) {
|
|
49
|
+
started.current = now();
|
|
50
|
+
setElapsed(0);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setElapsed(total);
|
|
54
|
+
setPlaying(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
setElapsed(at);
|
|
58
|
+
}, tickMs);
|
|
59
|
+
return () => clearInterval(timer);
|
|
60
|
+
}, [playing, total, loop, loopAfterMs, tickMs]);
|
|
61
|
+
const play = useCallback(() => {
|
|
62
|
+
if (all.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
started.current = now() - (elapsed >= total ? 0 : elapsed);
|
|
66
|
+
if (elapsed >= total) {
|
|
67
|
+
setElapsed(0);
|
|
68
|
+
}
|
|
69
|
+
setPlaying(true);
|
|
70
|
+
}, [all.length, elapsed, total]);
|
|
71
|
+
const pause = useCallback(() => setPlaying(false), []);
|
|
72
|
+
const restart = useCallback(() => {
|
|
73
|
+
started.current = now();
|
|
74
|
+
setElapsed(0);
|
|
75
|
+
setPlaying(all.length > 0);
|
|
76
|
+
}, [all.length]);
|
|
77
|
+
const shown = offsets.filter(offset => offset <= elapsed).length;
|
|
78
|
+
const lines = useMemo(() => all.slice(0, shown), [all, shown]);
|
|
79
|
+
const stage = useMemo(() => all.length
|
|
80
|
+
? recordedStageAt(all, recordedMomentAt(all, offsets, elapsed, speed), members, entry)
|
|
81
|
+
: { flows: {}, calls: [] }, [all, offsets, elapsed, speed, members, entry]);
|
|
82
|
+
const taken = recording?.taken ?? '';
|
|
83
|
+
return {
|
|
84
|
+
...stage,
|
|
85
|
+
lines,
|
|
86
|
+
all,
|
|
87
|
+
label: recordingLabel(taken),
|
|
88
|
+
taken,
|
|
89
|
+
...(recording?.note ? { note: recording.note } : {}),
|
|
90
|
+
playing,
|
|
91
|
+
done: all.length > 0 && shown === all.length && !playing,
|
|
92
|
+
play,
|
|
93
|
+
pause,
|
|
94
|
+
restart,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -37,7 +37,7 @@ import { ThemedProvider } from './utils/themedProvider.js';
|
|
|
37
37
|
import { useBrowserInference } from '../hooks/useBrowserInference.js';
|
|
38
38
|
import { useAnonymousSessionStore } from '../runtimes/browser/anonymousToken.js';
|
|
39
39
|
import { connectA2APeer } from '../runtimes/browser/a2aPeer.js';
|
|
40
|
-
import { A2ATeamGraph, NOTEBOOK_AND_WORDS, PERSON, SceneView, sceneCuesOf, sceneMemberUrl, sceneRuntimePorts, sceneStageOf, teamConnectionsOf, teamToolClassifier, useA2ATeam, } from '../components/teams/index.js';
|
|
40
|
+
import { A2ATeamGraph, NOTEBOOK_AND_WORDS, PERSON, SceneView, sceneCuesOf, sceneMemberUrl, sceneRuntimePorts, sceneStageOf, teamConnectionsOf, teamToolClassifier, useA2ATeam, useSceneTranscript, latestLineOnEdge, selectedEdgeOf, selectionOfLine, stageBackdropOf, } from '../components/teams/index.js';
|
|
41
41
|
import { listSceneSpecs } from '../specs/scenes.js';
|
|
42
42
|
import { classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector/index.js';
|
|
43
43
|
/** Where the members on a runtime are, by application id: the local ports, unless a variable says. */
|
|
@@ -151,6 +151,11 @@ function Scene({ scene }) {
|
|
|
151
151
|
name: member.app.name,
|
|
152
152
|
connections: runtimeMembers.find(one => one.id === member.id)?.connections ?? [],
|
|
153
153
|
})), [stage, runtimeMembers]);
|
|
154
|
+
// The exchange selected in the transcript, as the edge of the graph it is
|
|
155
|
+
// on (S3-02): the graph draws it with a halo; a click on an edge selects
|
|
156
|
+
// its latest exchange.
|
|
157
|
+
const [selectedEdge, setSelectedEdge] = useState(null);
|
|
158
|
+
const lines = useSceneTranscript(inspector, transcriptMembers);
|
|
154
159
|
const whereOf = (member) => member.runsIn === 'browser'
|
|
155
160
|
? 'in your browser'
|
|
156
161
|
: `on a runtime, ${new URL(runtimeMembers.find(one => one.id === member.id)?.url ?? 'http://runtime').host}`;
|
|
@@ -173,7 +178,7 @@ function Scene({ scene }) {
|
|
|
173
178
|
return (_jsxs(Box, { "data-scene-example": scene.id, children: [_jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, "data-scene-assumes": "", children: stage.assumes }), anonymous.status === 'active' && anonymous.expiresAt && (_jsx(Box, { mb: 3, children: _jsx(AnonymousKeyTimer, { expiresAt: anonymous.expiresAt, grantedMs: anonymous.grantedMs, label: `${entry.app.name}'s trial key`, onExpire: () => useAnonymousSessionStore.getState().expire() }) })), anonymous.status === 'expired' && (_jsx(Box, { mb: 3, children: _jsx(AnonymousKeyExpired, { agentName: entry.app.name, onSignedIn: () => useAnonymousSessionStore.getState().clear() }) })), entryInBrowser && needsSignIn && (_jsxs(Flash, { variant: "warning", sx: { mb: 3 }, children: [entry.app.name, " asks its model through the Datalayer inference service: sign in, or wait for a visitor\u2019s trial key."] })), notReached.map(member => (_jsx(Flash, { variant: "danger", sx: { mb: 3 }, "data-scene-needs": member.app.id, children: memberNeeds(member, reachedOf(member.id).error ?? '').map(line => (_jsx(Text, { as: "p", sx: { m: 0 }, children: line }, line))) }, member.id))), runtimeMembers.map(member => (_jsx(ReachMember, { member: member,
|
|
174
179
|
// Its requests are the entry's, or the person's own when the
|
|
175
180
|
// page asks it itself.
|
|
176
|
-
asker: member.entry ? PERSON : entry.app.name, tracer: inspector, onReached: onReached }, member.id))), _jsx(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", px: 2, pt: 2, mb: 3, children: _jsx(SceneView, { members: transcriptMembers, tracer: inspector, emptyText: `Nothing said yet: ask ${entry.app.name} with one of the suggestions.`, children: _jsx(A2ATeamGraph, { entry: {
|
|
181
|
+
asker: member.entry ? PERSON : entry.app.name, tracer: inspector, onReached: onReached }, member.id))), _jsx(Box, { border: "1px solid", borderColor: "border.default", borderRadius: 2, bg: "canvas.subtle", px: 2, pt: 2, mb: 3, children: _jsx(SceneView, { members: transcriptMembers, tracer: inspector, onSelect: selection => setSelectedEdge(selectedEdgeOf(selection, transcriptMembers) ?? null), emptyText: `Nothing said yet: ask ${entry.app.name} with one of the suggestions.`, children: _jsx(A2ATeamGraph, { entry: {
|
|
177
182
|
id: entry.app.id,
|
|
178
183
|
name: entry.app.name,
|
|
179
184
|
emoji: entry.cast.persona.face || entry.app.emoji,
|
|
@@ -228,7 +233,15 @@ function Scene({ scene }) {
|
|
|
228
233
|
];
|
|
229
234
|
})),
|
|
230
235
|
// Room for the balloons: suggestions, *more* and a notebook.
|
|
231
|
-
balloonRoom: 320
|
|
236
|
+
balloonRoom: 320,
|
|
237
|
+
// Drawn as the scene says: studio, hero or plain.
|
|
238
|
+
backdrop: stageBackdropOf(scene), selected: selectedEdge, onSelectEdge: edge => {
|
|
239
|
+
const line = latestLineOnEdge(edge, lines, transcriptMembers);
|
|
240
|
+
setSelectedEdge(line
|
|
241
|
+
? (selectedEdgeOf(selectionOfLine(line), transcriptMembers) ??
|
|
242
|
+
null)
|
|
243
|
+
: null);
|
|
244
|
+
} }) }) }), _jsx(Box, { ref: notebookArea, mb: team.notebook ? 3 : 0, "data-team-notebook-placement": "under-graph" }), _jsxs(Box, { display: "flex", gap: 3, flexWrap: "wrap", children: [_jsxs(Box, { flex: "1 1 420px", minWidth: 0, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: ["Ask ", entry.app.name] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mt: 0 }, children: "Or choose one of its suggestions in its balloon: the script\u2019s cues." }), _jsx(Textarea, { ref: composer, block: true, rows: 3, value: draft, placeholder: suggestions[0]?.prompt ?? 'Ask…', onChange: event => setDraft(event.target.value), onKeyDown: event => {
|
|
232
245
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
233
246
|
event.preventDefault();
|
|
234
247
|
send(draft);
|
|
@@ -20,6 +20,12 @@ export type HostTurnRequest<P> = Readonly<{
|
|
|
20
20
|
* another application): a new session's uid is derived with it.
|
|
21
21
|
*/
|
|
22
22
|
sessionGeneration?: number;
|
|
23
|
+
/**
|
|
24
|
+
* The host user never linked: exchanged as a visitor, which ai-agents
|
|
25
|
+
* answers only when the installation's owner opened it to visitors and the
|
|
26
|
+
* deployment opens to somebody signed out (WHATSAPP WA-03).
|
|
27
|
+
*/
|
|
28
|
+
visitor?: boolean;
|
|
23
29
|
}>;
|
|
24
30
|
export type TurnStream = Readonly<{
|
|
25
31
|
/**
|
|
@@ -111,6 +117,8 @@ export declare class HostKeyCredentials<P> implements HostCredentials<P> {
|
|
|
111
117
|
hostKey: string;
|
|
112
118
|
/** Where the turn is asked from: Datalayer refuses a denied channel at once. */
|
|
113
119
|
place?: Place;
|
|
120
|
+
/** As a visitor: the host user never linked (WHATSAPP WA-03). */
|
|
121
|
+
visitor?: boolean;
|
|
114
122
|
}): Promise<HostExchange>;
|
|
115
123
|
forTurn(request: HostTurnRequest<P>): Promise<HostSession>;
|
|
116
124
|
}
|
|
@@ -44,6 +44,7 @@ export class HostKeyCredentials {
|
|
|
44
44
|
externalUserId: request.externalUserId,
|
|
45
45
|
hostKey,
|
|
46
46
|
place: this.shape.placeOf(request),
|
|
47
|
+
...(request.visitor ? { visitor: true } : {}),
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
/** The exchange for one host user and deployment, with a key in hand (a delivery, an action). */
|
|
@@ -53,6 +54,7 @@ export class HostKeyCredentials {
|
|
|
53
54
|
deployment_uid: input.deploymentUid,
|
|
54
55
|
external_user_id: input.externalUserId,
|
|
55
56
|
...(input.place ? { place: input.place } : {}),
|
|
57
|
+
...(input.visitor ? { visitor: true } : {}),
|
|
56
58
|
}),
|
|
57
59
|
headers: {
|
|
58
60
|
'content-type': 'application/json',
|
|
@@ -6,7 +6,9 @@ import type { ApprovalPresentation } from './contracts';
|
|
|
6
6
|
* presented is named by the renderer, or the card fails closed. An unknown
|
|
7
7
|
* tool, an argument the renderer does not know, an attachment, and every
|
|
8
8
|
* action that is not mail (reads, writes, other sends, destructive, financial,
|
|
9
|
-
* publishing, executing) are reviewed in Datalayer (decided by the user on 16 October 2026: mail only)
|
|
9
|
+
* publishing, executing) are reviewed in Datalayer (decided by the user on 16 October 2026: mail only),
|
|
10
|
+
* but for a company's `pap_confirm`, decided in a host with the operation's
|
|
11
|
+
* exact revision since 28 October 2026 (the user, WHATSAPP WA-43).
|
|
10
12
|
* They render ai-agents' presentation v1, a different input
|
|
11
13
|
* from `../approvals.ts` and `../approvalCard.ts`. Moved from the Slack
|
|
12
14
|
* service (DC-004).
|
|
@@ -39,7 +41,13 @@ type Renderer = Readonly<{
|
|
|
39
41
|
export declare const DATALAYER_ONLY: Set<"unknown" | "read" | "write" | "send" | "delete" | "publish" | "execute" | "financial">;
|
|
40
42
|
/** The words of a tool's name that make a send mail: contracts.json `mail_words`. */
|
|
41
43
|
export declare const MAIL_WORDS: ReadonlySet<string>;
|
|
42
|
-
|
|
44
|
+
export declare const isPapConfirm: (toolName: string) => boolean;
|
|
45
|
+
/** What a `pap_confirm` approval carries: contracts.json `presentation.pap_confirm_keys`. */
|
|
46
|
+
export declare const PAP_CONFIRM_KEYS: readonly ["key", "company", "summary", "terms", "operation_ref", "revision", "digest", "class", "session", "only_the_person", "was"];
|
|
47
|
+
/**
|
|
48
|
+
* The renderers, the first that matches wins: a company's `pap_confirm`
|
|
49
|
+
* (whose name says no class), then Datalayer-only before any other.
|
|
50
|
+
*/
|
|
43
51
|
export declare const RENDERERS: readonly Renderer[];
|
|
44
52
|
/**
|
|
45
53
|
* How a host shows an approval, by the renderer that knows its tool; with
|
|
@@ -98,6 +98,82 @@ const mail = {
|
|
|
98
98
|
return rendered('mail', presentation, fields, MAIL_KEYS, extra);
|
|
99
99
|
},
|
|
100
100
|
};
|
|
101
|
+
/** `pap_confirm`, as the gateway names it with or without a prefix. */
|
|
102
|
+
const PAP_CONFIRM = /(^|[_.:\-/])pap_confirm$/;
|
|
103
|
+
export const isPapConfirm = (toolName) => PAP_CONFIRM.test(toolName);
|
|
104
|
+
/** What a `pap_confirm` approval carries: contracts.json `presentation.pap_confirm_keys`. */
|
|
105
|
+
export const PAP_CONFIRM_KEYS = [
|
|
106
|
+
'key',
|
|
107
|
+
'company',
|
|
108
|
+
'summary',
|
|
109
|
+
'terms',
|
|
110
|
+
'operation_ref',
|
|
111
|
+
'revision',
|
|
112
|
+
'digest',
|
|
113
|
+
'class',
|
|
114
|
+
'session',
|
|
115
|
+
'only_the_person',
|
|
116
|
+
'was',
|
|
117
|
+
];
|
|
118
|
+
/**
|
|
119
|
+
* A company's operation the person confirms (WHATSAPP WA-43, decided by the
|
|
120
|
+
* user 28 October 2026): the company, its own summary, every term, the
|
|
121
|
+
* operation and its **exact revision** — a decision is on that revision and
|
|
122
|
+
* no other; a changed revision is another approval. A missing company,
|
|
123
|
+
* summary, operation, revision or digest fails the card closed.
|
|
124
|
+
*/
|
|
125
|
+
const papConfirm = {
|
|
126
|
+
name: 'pap-confirm',
|
|
127
|
+
matches: presentation => isPapConfirm(presentation.tool_name),
|
|
128
|
+
render(presentation) {
|
|
129
|
+
const args = presentation.arguments;
|
|
130
|
+
const fields = [];
|
|
131
|
+
const missing = [];
|
|
132
|
+
const extra = [];
|
|
133
|
+
const company = typeof args.company === 'string' ? args.company : '';
|
|
134
|
+
const summary = typeof args.summary === 'string' ? args.summary : '';
|
|
135
|
+
const operation = typeof args.operation_ref === 'string' ? args.operation_ref : '';
|
|
136
|
+
const revision = Number.isInteger(args.revision)
|
|
137
|
+
? args.revision
|
|
138
|
+
: undefined;
|
|
139
|
+
if (!company)
|
|
140
|
+
missing.push('the company');
|
|
141
|
+
else
|
|
142
|
+
fields.push({ label: 'Company', value: company });
|
|
143
|
+
if (!summary)
|
|
144
|
+
missing.push('what it does, in its words');
|
|
145
|
+
else
|
|
146
|
+
fields.push({ label: 'Summary', value: summary });
|
|
147
|
+
const termsAreFields = args.terms === undefined ||
|
|
148
|
+
args.terms === null ||
|
|
149
|
+
(typeof args.terms === 'object' && !Array.isArray(args.terms));
|
|
150
|
+
if (!termsAreFields)
|
|
151
|
+
extra.push('Its terms are not named one by one: review it in Datalayer.');
|
|
152
|
+
else
|
|
153
|
+
for (const [name, value] of Object.entries((args.terms ?? {})))
|
|
154
|
+
fields.push({ label: name, value: text(value) });
|
|
155
|
+
if (!operation)
|
|
156
|
+
missing.push('the operation');
|
|
157
|
+
else
|
|
158
|
+
fields.push({ label: 'Operation', value: operation });
|
|
159
|
+
if (revision === undefined)
|
|
160
|
+
missing.push('its exact revision');
|
|
161
|
+
else
|
|
162
|
+
fields.push({ label: 'Revision', value: String(revision) });
|
|
163
|
+
if (typeof args.digest !== 'string' || !args.digest)
|
|
164
|
+
missing.push('the digest of its terms');
|
|
165
|
+
if (typeof args.class === 'string' && args.class)
|
|
166
|
+
fields.push({ label: 'What it does', value: args.class });
|
|
167
|
+
const was = args.was && typeof args.was === 'object'
|
|
168
|
+
? args.was
|
|
169
|
+
: undefined;
|
|
170
|
+
if (was && was.revision !== undefined)
|
|
171
|
+
fields.push({ label: 'Replaces revision', value: text(was.revision) });
|
|
172
|
+
if (missing.length)
|
|
173
|
+
extra.push(`The request does not say ${missing.join(', ')}: review it in Datalayer.`);
|
|
174
|
+
return rendered('pap-confirm', presentation, fields, PAP_CONFIRM_KEYS, extra);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
101
177
|
/** Everything but mail: shown, decided only in Datalayer. */
|
|
102
178
|
const datalayerOnly = {
|
|
103
179
|
name: 'datalayer-only',
|
|
@@ -122,8 +198,11 @@ const datalayerOnly = {
|
|
|
122
198
|
};
|
|
123
199
|
},
|
|
124
200
|
};
|
|
125
|
-
/**
|
|
126
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The renderers, the first that matches wins: a company's `pap_confirm`
|
|
203
|
+
* (whose name says no class), then Datalayer-only before any other.
|
|
204
|
+
*/
|
|
205
|
+
export const RENDERERS = [papConfirm, datalayerOnly, mail];
|
|
127
206
|
/**
|
|
128
207
|
* How a host shows an approval, by the renderer that knows its tool; with
|
|
129
208
|
* none, nothing is decided in the host.
|
|
@@ -104,6 +104,8 @@ export const exchangeSchema = z.object({
|
|
|
104
104
|
expires_in: z.number().int().nonnegative(),
|
|
105
105
|
exp: z.number().int(),
|
|
106
106
|
principal_uid: z.string().min(1),
|
|
107
|
+
/** A person who never linked, answered as a visitor (WHATSAPP WA-03): `principal_uid` is `visitor:<hash>`. */
|
|
108
|
+
visitor: z.boolean().optional(),
|
|
107
109
|
host_uid: z.string().min(1),
|
|
108
110
|
deployment: z.object({
|
|
109
111
|
uid: z.string().min(1),
|
|
@@ -114,6 +114,45 @@
|
|
|
114
114
|
"direct"
|
|
115
115
|
]
|
|
116
116
|
},
|
|
117
|
+
"visitor_exchange": {
|
|
118
|
+
"about": "WHATSAPP WA-03 (decided 28 October 2026): a host's user who never linked, answered as a visitor when the installation's owner turned its visitors switch on and the deployment opens to somebody signed out. sub is visitor:<hash of the host's id with the installation's salt>; no link; a daily cap per visitor at the exchange (429).",
|
|
119
|
+
"request": {
|
|
120
|
+
"external_user_id": "US.13491208655302741918",
|
|
121
|
+
"deployment_uid": "dep-1",
|
|
122
|
+
"visitor": true
|
|
123
|
+
},
|
|
124
|
+
"response_keys": [
|
|
125
|
+
"success",
|
|
126
|
+
"access_token",
|
|
127
|
+
"token_type",
|
|
128
|
+
"expires_in",
|
|
129
|
+
"exp",
|
|
130
|
+
"principal_uid",
|
|
131
|
+
"visitor",
|
|
132
|
+
"host_uid",
|
|
133
|
+
"deployment",
|
|
134
|
+
"session_api"
|
|
135
|
+
],
|
|
136
|
+
"token_claims": [
|
|
137
|
+
"jti",
|
|
138
|
+
"iss",
|
|
139
|
+
"iat",
|
|
140
|
+
"exp",
|
|
141
|
+
"aud",
|
|
142
|
+
"sub",
|
|
143
|
+
"azp",
|
|
144
|
+
"host_kind",
|
|
145
|
+
"deployment_uid",
|
|
146
|
+
"app_uid",
|
|
147
|
+
"visitor",
|
|
148
|
+
"scope"
|
|
149
|
+
],
|
|
150
|
+
"sub_prefix": "visitor:",
|
|
151
|
+
"refusals": {
|
|
152
|
+
"visitors_off": 403,
|
|
153
|
+
"capped": 429
|
|
154
|
+
}
|
|
155
|
+
},
|
|
117
156
|
"whoami": {
|
|
118
157
|
"response_keys": [
|
|
119
158
|
"success",
|
|
@@ -124,7 +163,8 @@
|
|
|
124
163
|
"app_uid",
|
|
125
164
|
"link",
|
|
126
165
|
"scope",
|
|
127
|
-
"expires_in"
|
|
166
|
+
"expires_in",
|
|
167
|
+
"visitor"
|
|
128
168
|
]
|
|
129
169
|
},
|
|
130
170
|
"bind_session": {
|
|
@@ -328,7 +368,21 @@
|
|
|
328
368
|
"host_may_decide": true,
|
|
329
369
|
"reasons": [],
|
|
330
370
|
"digest": "0000000000000000000000000000000000000000000000000000000000000000"
|
|
331
|
-
}
|
|
371
|
+
},
|
|
372
|
+
"pap_confirm_keys": [
|
|
373
|
+
"key",
|
|
374
|
+
"company",
|
|
375
|
+
"summary",
|
|
376
|
+
"terms",
|
|
377
|
+
"operation_ref",
|
|
378
|
+
"revision",
|
|
379
|
+
"digest",
|
|
380
|
+
"class",
|
|
381
|
+
"session",
|
|
382
|
+
"only_the_person",
|
|
383
|
+
"was"
|
|
384
|
+
],
|
|
385
|
+
"pap_confirm_about": "WHATSAPP WA-43 (decided 28 October 2026): a company's pap_confirm (any gateway prefix) is decided in a host, on its exact revision; every other non-mail action stays in Datalayer."
|
|
332
386
|
},
|
|
333
387
|
"decision": {
|
|
334
388
|
"request": {
|
package/lib/specs/actions.js
CHANGED
|
@@ -788,6 +788,7 @@ export const APP_BEHAVIOURS = {
|
|
|
788
788
|
'earthdata.search_earth_datagranules': 'do_it',
|
|
789
789
|
'earthdata.search_earth_datasets': 'do_it',
|
|
790
790
|
},
|
|
791
|
+
'code-explainer': {},
|
|
791
792
|
'crop-monitoring': {
|
|
792
793
|
'earthdata.download_earth_data_granules': 'leave_to_me',
|
|
793
794
|
'earthdata.search_earth_datagranules': 'do_it',
|
|
@@ -1011,6 +1012,7 @@ export const APP_ESCALATIONS = {
|
|
|
1011
1012
|
accounting: {},
|
|
1012
1013
|
'bookkeeping-desk': {},
|
|
1013
1014
|
'change-detection': {},
|
|
1015
|
+
'code-explainer': {},
|
|
1014
1016
|
'crop-monitoring': {},
|
|
1015
1017
|
'customer-interview': {},
|
|
1016
1018
|
'data-quality': {},
|
package/lib/specs/apps.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
|
|
|
11
11
|
export declare const ACCOUNTING_APP_0_0_2: AppSpec;
|
|
12
12
|
export declare const BOOKKEEPING_DESK_APP_0_0_2: AppSpec;
|
|
13
13
|
export declare const CHANGE_DETECTION_APP_0_0_2: AppSpec;
|
|
14
|
+
export declare const CODE_EXPLAINER_APP_0_0_1: AppSpec;
|
|
14
15
|
export declare const CROP_MONITORING_APP_0_0_2: AppSpec;
|
|
15
16
|
export declare const CUSTOMER_INTERVIEW_APP_0_0_3: AppSpec;
|
|
16
17
|
export declare const DATA_QUALITY_APP_0_0_3: AppSpec;
|