@datalayer/agent-runtimes 1.3.81 → 1.3.82
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/lib/chat/assistant/SpeechBalloon.js +16 -1
- package/lib/chat/base/ChatBase.js +40 -4
- package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/McpStatusIndicator.js +13 -6
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/SkillsStatusIndicator.js +7 -3
- package/lib/chat/prompt/InputPrompt.d.ts +4 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptBase.d.ts +4 -1
- package/lib/chat/prompt/InputPromptBase.js +2 -2
- package/lib/chat/prompt/InputPromptLexical.d.ts +7 -1
- package/lib/chat/prompt/InputPromptLexical.js +5 -3
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/usePeerSandbox.d.ts +13 -0
- package/lib/components/teams/usePeerSandbox.js +59 -0
- package/lib/examples/AgentA2ATeamExample.js +7 -2
- package/lib/loop/apps/AppComposer.d.ts +31 -0
- package/lib/loop/apps/AppComposer.js +67 -0
- package/lib/loop/apps/AppRenderer.d.ts +16 -5
- package/lib/loop/apps/AppRenderer.js +49 -16
- package/lib/loop/apps/appspec.d.ts +8 -1
- package/lib/loop/apps/appspec.js +87 -0
- package/lib/loop/apps/checks.js +108 -1
- package/lib/loop/apps/composer.d.ts +37 -0
- package/lib/loop/apps/composer.js +61 -0
- package/lib/loop/apps/index.d.ts +2 -0
- package/lib/loop/apps/index.js +2 -0
- package/lib/loop/core/index.d.ts +31 -0
- package/lib/loop/core/index.js +22 -0
- package/lib/loop/embed/AppEmbed.d.ts +46 -7
- package/lib/loop/embed/AppEmbed.js +108 -26
- package/lib/loop/embed/LoopEmbed.js +2 -0
- package/lib/loop/embed/element.js +19 -5
- package/lib/loop/embed/embedConfig.d.ts +10 -1
- package/lib/loop/embed/embedConfig.js +12 -1
- package/lib/loop/embed/embedSession.d.ts +79 -0
- package/lib/loop/embed/embedSession.js +136 -0
- package/lib/loop/embed/embedTheme.d.ts +12 -9
- package/lib/loop/embed/embedTheme.js +32 -22
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/agent-capacity/index.d.ts +3 -1
- package/lib/loop/plugins/agent-capacity/index.js +2 -1
- package/lib/loop/plugins/chat/ChatView.js +31 -4
- package/lib/loop/plugins/chat/index.d.ts +6 -1
- package/lib/loop/presets.d.ts +3 -1
- package/lib/loop/presets.js +2 -1
- package/lib/loop/shell/LoopWorkspace.js +3 -0
- package/lib/protocols/BrowserAgentAdapter.d.ts +18 -0
- package/lib/protocols/BrowserAgentAdapter.js +35 -2
- package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +8 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +11 -0
- package/lib/runtimes/browser/a2aPeer.js +15 -1
- package/lib/runtimes/browser/protocol.d.ts +3 -0
- package/lib/runtimes/browser/protocol.js +2 -1
- package/lib/specs/apps.js +28 -0
- package/lib/specs/appspecSchema.js +191 -0
- package/lib/types/agentspecs.d.ts +58 -0
- package/lib/types/chat.d.ts +29 -1
- package/package.json +1 -1
- package/scripts/codegen/generate_agents.py +4 -2
- package/scripts/codegen/generate_backend_tools.py +4 -12
- package/scripts/codegen/generate_cogs.py +3 -1
- package/scripts/codegen/generate_ui_plugins.py +22 -9
- package/scripts/voice/transcribe.mjs +29 -21
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ComponentType, ReactNode } from 'react';
|
|
|
3
3
|
import type { ExecutionState } from '@datalayer/jupyter-react/kernel-indicator';
|
|
4
4
|
import type { ToolbarItem } from '@datalayer/primer-addons';
|
|
5
5
|
import type { FrontendToolDefinition } from '../../types/tools';
|
|
6
|
+
import type { AppModeEffect } from '../../types/agentspecs';
|
|
6
7
|
import type { ContextSnapshotData } from '../../types/context';
|
|
7
8
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
8
9
|
import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
|
|
@@ -331,8 +332,25 @@ export type CommandContribution = {
|
|
|
331
332
|
*/
|
|
332
333
|
keybinding?: string;
|
|
333
334
|
args?: readonly CommandArgSpec[];
|
|
335
|
+
/**
|
|
336
|
+
* Listed in the composer's `/` menu, as an application's commands are
|
|
337
|
+
* (LOOP P-19). Most commands are not: the menu is for what a person sends,
|
|
338
|
+
* the palette and `/help` for the rest.
|
|
339
|
+
*/
|
|
340
|
+
composer?: boolean;
|
|
334
341
|
run: (ctx: LoopCommandContext) => Promise<CommandResult | void>;
|
|
335
342
|
};
|
|
343
|
+
/**
|
|
344
|
+
* What goes with every run besides the message, as AG-UI's `forwardedProps`:
|
|
345
|
+
* an application's modes say there which option the person is in (LOOP
|
|
346
|
+
* P-19). Read when a message is sent, so it says what is true then; what a
|
|
347
|
+
* page says with its own message (its action, its files) is merged over it,
|
|
348
|
+
* the `loop` part key by key.
|
|
349
|
+
*/
|
|
350
|
+
export type RunPropsContribution = {
|
|
351
|
+
id: string;
|
|
352
|
+
props: () => Record<string, unknown>;
|
|
353
|
+
};
|
|
336
354
|
/** A candidate offered by an `@` namespace. */
|
|
337
355
|
export type MentionCandidate = {
|
|
338
356
|
id: string;
|
|
@@ -496,6 +514,12 @@ export type AgentBlueprintContribution = {
|
|
|
496
514
|
instructions?: string;
|
|
497
515
|
/** The model it runs on in the page, in place of its spec's. */
|
|
498
516
|
model?: string;
|
|
517
|
+
/**
|
|
518
|
+
* What the modes chosen in the composer (`forwardedProps.loop.modes`, LOOP
|
|
519
|
+
* P-19) tell a run turned in the page, and the model it runs on — as the
|
|
520
|
+
* runtime's session API applies them. Unsaid for an agent with no modes.
|
|
521
|
+
*/
|
|
522
|
+
modeEffect?: (chosen: Record<string, string>) => AppModeEffect;
|
|
499
523
|
};
|
|
500
524
|
export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
|
|
501
525
|
/** One conversation opener a plugin offers on the empty chat. */
|
|
@@ -781,6 +805,13 @@ export { onSurfaceRequest, requestSurface, type SurfaceRequestListener, } from '
|
|
|
781
805
|
export { focusPrompt, onPromptFocusRequest } from './focusRequests';
|
|
782
806
|
/** Slash commands, shared in shape with the CLI. */
|
|
783
807
|
export declare const LoopCommand: import("@datalayer/reactor").ContributionPoint<CommandContribution>;
|
|
808
|
+
/** What goes with every run besides the message (`forwardedProps`). */
|
|
809
|
+
export declare const LoopRunProps: import("@datalayer/reactor").ContributionPoint<RunPropsContribution>;
|
|
810
|
+
/**
|
|
811
|
+
* The `forwardedProps` of a run: what the contributions say, then what the
|
|
812
|
+
* sender gave, the `loop` part merged key by key.
|
|
813
|
+
*/
|
|
814
|
+
export declare function runForwardedProps(contributed: readonly RunPropsContribution[], given?: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
784
815
|
/** `@` namespaces. */
|
|
785
816
|
export declare const LoopMention: import("@datalayer/reactor").ContributionPoint<MentionContribution>;
|
|
786
817
|
/** Slot names the shell renders. Slots render everything; points choose one. */
|
package/lib/loop/core/index.js
CHANGED
|
@@ -189,6 +189,28 @@ export { onSurfaceRequest, requestSurface, } from './surfaceRequests';
|
|
|
189
189
|
export { focusPrompt, onPromptFocusRequest } from './focusRequests';
|
|
190
190
|
/** Slash commands, shared in shape with the CLI. */
|
|
191
191
|
export const LoopCommand = defineContributionPoint('loop.command');
|
|
192
|
+
/** What goes with every run besides the message (`forwardedProps`). */
|
|
193
|
+
export const LoopRunProps = defineContributionPoint('loop.runProps');
|
|
194
|
+
/**
|
|
195
|
+
* The `forwardedProps` of a run: what the contributions say, then what the
|
|
196
|
+
* sender gave, the `loop` part merged key by key.
|
|
197
|
+
*/
|
|
198
|
+
export function runForwardedProps(contributed, given) {
|
|
199
|
+
let merged;
|
|
200
|
+
for (const part of [...contributed.map(entry => entry.props()), given]) {
|
|
201
|
+
if (!part)
|
|
202
|
+
continue;
|
|
203
|
+
const loop = {
|
|
204
|
+
...(merged?.loop ?? {}),
|
|
205
|
+
...(part.loop ?? {}),
|
|
206
|
+
};
|
|
207
|
+
merged = { ...merged, ...part };
|
|
208
|
+
if (Object.keys(loop).length > 0) {
|
|
209
|
+
merged.loop = loop;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return merged;
|
|
213
|
+
}
|
|
192
214
|
/** `@` namespaces. */
|
|
193
215
|
export const LoopMention = defineContributionPoint('loop.mention');
|
|
194
216
|
/** Slot names the shell renders. Slots render everything; points choose one. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
2
|
import type { PluginRef } from '@datalayer/reactor';
|
|
3
|
-
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
4
|
-
import type { ThemeOverrides } from '../../types/chat';
|
|
3
|
+
import type { AppAccent, AppEmbedMode, AppSpec, AppThemeVariant } from '../../types/agentspecs';
|
|
4
|
+
import type { ResumedThread, ThemeOverrides } from '../../types/chat';
|
|
5
5
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
6
6
|
import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
|
|
7
7
|
import type { PresenceState } from '../../chat/presence/presenceStatus';
|
|
@@ -17,7 +17,10 @@ export type AppEmbedProps = {
|
|
|
17
17
|
mode?: AppEmbedMode;
|
|
18
18
|
/** The host's accent; the application's own by default. */
|
|
19
19
|
accent?: AppAccent;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Light, dark, or the visitor's system: the host's, else the one the
|
|
22
|
+
* Appspec names (`interface.theme.mode`, T-30), else the system's.
|
|
23
|
+
*/
|
|
21
24
|
colorMode?: EmbedColorMode;
|
|
22
25
|
/** The host's face, a CSS font family; the theme's by default. */
|
|
23
26
|
font?: string;
|
|
@@ -56,7 +59,38 @@ export type AppEmbedProps = {
|
|
|
56
59
|
* gives under `deployment.embedded.host`.
|
|
57
60
|
*/
|
|
58
61
|
host?: AppEmbedHost;
|
|
62
|
+
/**
|
|
63
|
+
* Keep the visit's session in the host's storage, and pick it up again
|
|
64
|
+
* after the page reloads (LOOP D-13); on by default. Off, the session
|
|
65
|
+
* lasts as long as the page.
|
|
66
|
+
*/
|
|
67
|
+
resume?: boolean;
|
|
68
|
+
};
|
|
69
|
+
/** What `useEmbedSession` gives the conversation. */
|
|
70
|
+
export type EmbedSession = {
|
|
71
|
+
/** Whether the kept session was asked for: nothing is drawn before. */
|
|
72
|
+
ready: boolean;
|
|
73
|
+
/** The thread the chat goes on with; none where no session is kept. */
|
|
74
|
+
thread?: ResumedThread;
|
|
75
|
+
/** Said once, when the session kept is gone: a new one started. */
|
|
76
|
+
said?: string;
|
|
59
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* The visit's session, kept and picked up again (LOOP D-13): on the host's
|
|
80
|
+
* server with an embed token that names its visit, the session the chat
|
|
81
|
+
* opens is kept under the application and the visit — in the host's
|
|
82
|
+
* storage unless `resume` is off, in memory always — and, when the
|
|
83
|
+
* application is drawn again (the page reloaded, or a token renewed for the
|
|
84
|
+
* visit), its conversation is asked for and drawn again. One that is gone
|
|
85
|
+
* or refused is forgotten, said once, and a new one starts. Elsewhere —
|
|
86
|
+
* on a Datalayer runtime, without a token — nothing is kept.
|
|
87
|
+
*/
|
|
88
|
+
export declare function useEmbedSession({ app, serverUrl, embedToken, resume, }: {
|
|
89
|
+
app: Pick<AppSpec, 'id' | 'name'>;
|
|
90
|
+
serverUrl?: string;
|
|
91
|
+
embedToken?: string;
|
|
92
|
+
resume?: boolean;
|
|
93
|
+
}): EmbedSession;
|
|
60
94
|
/**
|
|
61
95
|
* The page and the application talking (LOOP D-10): the tools of the host
|
|
62
96
|
* the Appspec names, given to its agent through a chat-extras plugin made
|
|
@@ -81,6 +115,8 @@ export type AppFloatingProps = {
|
|
|
81
115
|
app: AppSpec;
|
|
82
116
|
view: 'floating-small' | 'panel' | 'assistant';
|
|
83
117
|
colorMode: 'light' | 'dark';
|
|
118
|
+
/** The theme it is drawn in (T-30); the page's when unsaid. */
|
|
119
|
+
themeVariant?: AppThemeVariant;
|
|
84
120
|
/** The accent and face inside the conversation. */
|
|
85
121
|
themeOverrides: ThemeOverrides;
|
|
86
122
|
/** The assistant's character, in the assistant mode. */
|
|
@@ -98,6 +134,8 @@ export type AppFloatingProps = {
|
|
|
98
134
|
renderer?: Partial<Pick<AppRendererProps, 'pluginsOff' | 'datalayerVisitors' | 'datalayerKept'>>;
|
|
99
135
|
/** What the page gives the application (D-10). */
|
|
100
136
|
host?: AppEmbedHost;
|
|
137
|
+
/** The visit's session, picked up again after a reload (D-13). */
|
|
138
|
+
session?: EmbedSession;
|
|
101
139
|
};
|
|
102
140
|
/**
|
|
103
141
|
* The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
|
|
@@ -108,14 +146,15 @@ export type AppFloatingProps = {
|
|
|
108
146
|
* The workspace tells the chrome what the application is doing and what it
|
|
109
147
|
* last said (`onPresence`, `onSaying`).
|
|
110
148
|
*/
|
|
111
|
-
export declare function AppFloating({ app, view, colorMode, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, }: AppFloatingProps): JSX.Element;
|
|
112
|
-
/** The `loop`
|
|
113
|
-
export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
|
|
149
|
+
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }: AppFloatingProps): JSX.Element;
|
|
150
|
+
/** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
|
|
151
|
+
export declare function EmbedThemed({ accent, colorMode, font, variant, children, style, }: {
|
|
114
152
|
accent: AppAccent;
|
|
115
153
|
colorMode: 'light' | 'dark';
|
|
116
154
|
font?: string;
|
|
155
|
+
variant: AppThemeVariant;
|
|
117
156
|
children: ReactNode;
|
|
118
157
|
style?: CSSProperties;
|
|
119
158
|
}): JSX.Element;
|
|
120
|
-
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, }: AppEmbedProps): JSX.Element;
|
|
159
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
121
160
|
export default AppEmbed;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
@@ -33,13 +33,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
33
33
|
* host may override with the face and the mode (D-11) — around the
|
|
34
34
|
* conversation and inside it, where the chat sets its theme again.
|
|
35
35
|
*
|
|
36
|
+
* On the host's server with the visit's embed token, its session is kept
|
|
37
|
+
* in the host's storage and picked up again after the page reloads (D-13,
|
|
38
|
+
* `useEmbedSession`): its conversation drawn again, and gone on with.
|
|
39
|
+
*
|
|
36
40
|
* @module loop/embed/AppEmbed
|
|
37
41
|
*/
|
|
38
42
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
39
43
|
import { Text, registerPortalRoot } from '@primer/react';
|
|
40
44
|
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
41
45
|
import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
|
|
42
|
-
import { DatalayerThemeProvider,
|
|
46
|
+
import { DatalayerThemeProvider, getThemeConfig, } from '@datalayer/primer-addons';
|
|
47
|
+
import { generateMessageId } from '../../types/messages';
|
|
43
48
|
import { ChatFloating } from '../../chat/ChatFloating';
|
|
44
49
|
import { AppRenderer, } from '../apps/AppRenderer';
|
|
45
50
|
import { AssistantCharactersPlugin, assistantCharacterFor, assistantCharactersFrom, } from '../plugins/assistant-characters';
|
|
@@ -47,6 +52,78 @@ import { floatingViewOf } from './embedConfig';
|
|
|
47
52
|
import { createChatExtrasPlugin } from '../plugins/chat-extras';
|
|
48
53
|
import { hostFrontendTools } from './hostBridge';
|
|
49
54
|
import { embedThemeOverrides, embedThemeStyles } from './embedTheme';
|
|
55
|
+
import { embedSessionKey, reattachSession, sessionGoneSentence, sessionKeeper, visitOfToken, } from './embedSession';
|
|
56
|
+
/**
|
|
57
|
+
* The visit's session, kept and picked up again (LOOP D-13): on the host's
|
|
58
|
+
* server with an embed token that names its visit, the session the chat
|
|
59
|
+
* opens is kept under the application and the visit — in the host's
|
|
60
|
+
* storage unless `resume` is off, in memory always — and, when the
|
|
61
|
+
* application is drawn again (the page reloaded, or a token renewed for the
|
|
62
|
+
* visit), its conversation is asked for and drawn again. One that is gone
|
|
63
|
+
* or refused is forgotten, said once, and a new one starts. Elsewhere —
|
|
64
|
+
* on a Datalayer runtime, without a token — nothing is kept.
|
|
65
|
+
*/
|
|
66
|
+
export function useEmbedSession({ app, serverUrl, embedToken, resume = true, }) {
|
|
67
|
+
const visit = serverUrl && embedToken ? visitOfToken(embedToken) : undefined;
|
|
68
|
+
const key = visit ? embedSessionKey(app.id, visit) : undefined;
|
|
69
|
+
const keeper = useMemo(() => sessionKeeper({ persist: resume }), [resume]);
|
|
70
|
+
// What was found, for the token it was found with: a renewed token asks again.
|
|
71
|
+
const [found, setFound] = useState();
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!key || !serverUrl || !embedToken) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
let cancelled = false;
|
|
77
|
+
const onStarted = (uid) => {
|
|
78
|
+
keeper.keep(key, uid);
|
|
79
|
+
setFound(before => before?.said ? { ...before, said: undefined } : before);
|
|
80
|
+
};
|
|
81
|
+
const fresh = (said) => setFound({
|
|
82
|
+
token: embedToken,
|
|
83
|
+
thread: { id: generateMessageId(), messages: [], onStarted },
|
|
84
|
+
...(said ? { said } : {}),
|
|
85
|
+
});
|
|
86
|
+
const kept = keeper.read(key);
|
|
87
|
+
if (!kept) {
|
|
88
|
+
fresh();
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
void reattachSession({ serverUrl, uid: kept, token: embedToken }).then(reattached => {
|
|
92
|
+
if (cancelled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (reattached.kind === 'resumed') {
|
|
96
|
+
setFound({
|
|
97
|
+
token: embedToken,
|
|
98
|
+
thread: { id: kept, messages: reattached.messages, onStarted },
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
keeper.forget(key);
|
|
103
|
+
fresh(sessionGoneSentence(app.name));
|
|
104
|
+
});
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
};
|
|
108
|
+
// The application's name is only said.
|
|
109
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
110
|
+
}, [key, serverUrl, embedToken, keeper]);
|
|
111
|
+
if (!key) {
|
|
112
|
+
return { ready: true };
|
|
113
|
+
}
|
|
114
|
+
if (!found || found.token !== embedToken) {
|
|
115
|
+
return { ready: false };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
ready: true,
|
|
119
|
+
thread: found.thread,
|
|
120
|
+
...(found.said ? { said: found.said } : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** What the embed says once of its session, above the conversation. */
|
|
124
|
+
function SessionSaid({ said }) {
|
|
125
|
+
return said ? (_jsx(Text, { as: "p", role: "status", sx: { px: 3, py: 2, m: 0, fontSize: 1 }, children: said })) : null;
|
|
126
|
+
}
|
|
50
127
|
/** No plugins for the renderer: one array, so that it never reads as a change. */
|
|
51
128
|
const NO_HOST_PLUGINS = [];
|
|
52
129
|
/**
|
|
@@ -122,7 +199,7 @@ function Face({ emoji }) {
|
|
|
122
199
|
* The workspace tells the chrome what the application is doing and what it
|
|
123
200
|
* last said (`onPresence`, `onSaying`).
|
|
124
201
|
*/
|
|
125
|
-
export function AppFloating({ app, view, colorMode, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, }) {
|
|
202
|
+
export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }) {
|
|
126
203
|
const [presence, setPresence] = useState('idle');
|
|
127
204
|
const [said, setSaid] = useState({ answering: false });
|
|
128
205
|
const bridge = useHostBridge(app, host);
|
|
@@ -143,14 +220,14 @@ export function AppFloating({ app, view, colorMode, themeOverrides, character, s
|
|
|
143
220
|
return (_jsx(ChatFloating, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), ...(app.interface.balloon
|
|
144
221
|
? { balloonDisplay: app.interface.balloon }
|
|
145
222
|
: {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: `Talk to ${app.name}`, colorMode: colorMode, useStore: false, conversation: {
|
|
146
|
-
body: signedOut ? (_jsx(Text, { as: "p", role: "status", sx: { p: 3, m: 0, fontSize: 1 }, children: signedOutSentence(app) })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
223
|
+
body: signedOut ? (_jsx(Text, { as: "p", role: "status", sx: { p: 3, m: 0, fontSize: 1 }, children: signedOutSentence(app) })) : session && !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session?.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
224
|
+
// The host's accent and face over the application's own, inside
|
|
225
|
+
// its conversation too, in the embed's mode.
|
|
226
|
+
themeOverrides: themeOverrides, colorMode: colorMode, ...(themeVariant ? { themeVariant } : {}),
|
|
227
|
+
// The window draws its face, its name and close.
|
|
228
|
+
hideChatHeader: true,
|
|
229
|
+
// Mounted closed: the caret goes in when the window opens.
|
|
230
|
+
autoFocusPrompt: false, onPresence: onPresence, onSaying: onSaying, plugins: bridge.plugins, ...(session?.thread ? { thread: session.thread } : {}), ...renderer })] })),
|
|
154
231
|
presence,
|
|
155
232
|
saying: said.saying,
|
|
156
233
|
answering: said.answering,
|
|
@@ -187,9 +264,9 @@ function useSystemMode() {
|
|
|
187
264
|
}, [query]);
|
|
188
265
|
return dark ? 'dark' : 'light';
|
|
189
266
|
}
|
|
190
|
-
/** The `loop`
|
|
191
|
-
export function EmbedThemed({ accent, colorMode, font, children, style, }) {
|
|
192
|
-
const themeStyles = useMemo(() => embedThemeStyles({ accent, font }), [accent, font]);
|
|
267
|
+
/** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
|
|
268
|
+
export function EmbedThemed({ accent, colorMode, font, variant, children, style, }) {
|
|
269
|
+
const themeStyles = useMemo(() => embedThemeStyles({ accent, font, variant }), [accent, font, variant]);
|
|
193
270
|
return (
|
|
194
271
|
/*
|
|
195
272
|
* Marked as themed above the provider, so that the provider counts
|
|
@@ -197,16 +274,21 @@ export function EmbedThemed({ accent, colorMode, font, children, style, }) {
|
|
|
197
274
|
* alone: it would otherwise write the theme's tokens, colour and face on
|
|
198
275
|
* the host's page (T-13). A wrapper of no box.
|
|
199
276
|
*/
|
|
200
|
-
_jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme:
|
|
277
|
+
_jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: getThemeConfig(variant).primerTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
|
|
201
278
|
}
|
|
202
|
-
export function AppEmbed({ app, mode, accent, colorMode
|
|
279
|
+
export function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, host, resume = true, }) {
|
|
203
280
|
const bridge = useHostBridge(app, host);
|
|
281
|
+
const session = useEmbedSession({ app, serverUrl, embedToken, resume });
|
|
204
282
|
const system = useSystemMode();
|
|
205
|
-
|
|
283
|
+
// The theme the application names (T-30), `loop` when none; its mode
|
|
284
|
+
// the host's, else the application's, else the visitor's system's.
|
|
285
|
+
const variant = app.interface.theme?.variant ?? 'loop';
|
|
286
|
+
const asked = colorMode ?? app.interface.theme?.mode ?? 'auto';
|
|
287
|
+
const resolvedMode = asked === 'auto' ? system : asked;
|
|
206
288
|
const shownAs = mode ?? app.deployment.embedded?.mode ?? 'inline';
|
|
207
289
|
const view = floatingViewOf(shownAs);
|
|
208
290
|
const worn = accent ?? app.interface.accent;
|
|
209
|
-
const themeOverrides = useMemo(() => embedThemeOverrides({ accent: worn, font }), [worn, font]);
|
|
291
|
+
const themeOverrides = useMemo(() => embedThemeOverrides({ accent: worn, font, variant }), [worn, font, variant]);
|
|
210
292
|
const named = app.interface.assistant;
|
|
211
293
|
const assistant = useMemo(() => view === 'assistant' ? embedAssistantCharacter(app, plugins) : undefined,
|
|
212
294
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -215,15 +297,15 @@ export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUr
|
|
|
215
297
|
return (_jsx("p", { role: "status", className: "datalayer-app-said", children: assistant.problem }));
|
|
216
298
|
}
|
|
217
299
|
const character = assistant?.character;
|
|
218
|
-
return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, style: view
|
|
300
|
+
return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, variant: variant, style: view
|
|
219
301
|
? // Floating: nothing in the page's flow, nothing painted behind.
|
|
220
302
|
{ backgroundColor: 'transparent' }
|
|
221
|
-
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(AppFloating, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character, host: host })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
303
|
+
: { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(AppFloating, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeVariant: variant, themeOverrides: themeOverrides, character: character, host: host, session: session })) : !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
|
|
304
|
+
// The host's accent and face over the application's own, inside
|
|
305
|
+
// its conversation too, in the embed's mode: the host's or its
|
|
306
|
+
// visitor's system's, not a Datalayer setting.
|
|
307
|
+
themeOverrides: themeOverrides, colorMode: resolvedMode, themeVariant: variant,
|
|
308
|
+
// What the page gives it, and what it tells the page (D-10).
|
|
309
|
+
plugins: bridge.plugins, onPresence: bridge.onPresence, onSaying: bridge.onSaying, ...(session.thread ? { thread: session.thread } : {}) })] }))] }));
|
|
228
310
|
}
|
|
229
311
|
export default AppEmbed;
|
|
@@ -74,6 +74,8 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
|
|
|
74
74
|
preset.presence?.welcome,
|
|
75
75
|
preset.presence?.paused,
|
|
76
76
|
preset.showTokenUsage,
|
|
77
|
+
// The thread a reloaded embed goes on with, by its id (D-13).
|
|
78
|
+
preset.thread?.id,
|
|
77
79
|
// By its content: an application's spec written inline is a new object each render.
|
|
78
80
|
JSON.stringify(preset.datalayerCreatePayload ?? null),
|
|
79
81
|
JSON.stringify(preset.datalayerVisitors ?? null),
|
|
@@ -38,16 +38,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
38
38
|
* when it called a function of the page, beside `decision` and
|
|
39
39
|
* `token-expired`.
|
|
40
40
|
*
|
|
41
|
+
* On the host's server (`server`) with the visit's embed token, the
|
|
42
|
+
* session the application opens is kept in the page's `localStorage`, under
|
|
43
|
+
* the application and the visit its token names, and picked up again after
|
|
44
|
+
* the page reloads — its conversation drawn again (LOOP D-13); a token
|
|
45
|
+
* renewed for the visit redraws the application on the same session.
|
|
46
|
+
* `resume="false"` keeps nothing in the page's storage.
|
|
47
|
+
*
|
|
41
48
|
* @module loop/embed/element
|
|
42
49
|
*/
|
|
43
50
|
import { createRoot } from 'react-dom/client';
|
|
44
51
|
import { StyleSheetManager } from 'styled-components';
|
|
45
|
-
import {
|
|
52
|
+
import { getThemeConfig } from '@datalayer/primer-addons';
|
|
46
53
|
import { coreStore } from '@datalayer/core/lib/state/substates/CoreState';
|
|
47
54
|
import { parseAppspec } from '../apps/appspec';
|
|
48
55
|
import { readAppspecYaml } from '../apps/yaml';
|
|
49
56
|
import { AppEmbed, embedAssistantCharacter } from './AppEmbed';
|
|
50
|
-
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, } from './embedConfig';
|
|
57
|
+
import { EMBED_HOST_VARIABLES, EMBED_OBSERVED_ATTRIBUTES, EMBED_TAG, EmbedAttributeError, embedLookOf, inlineHeightOf, resumeOf, } from './embedConfig';
|
|
51
58
|
import { embedShadowCss } from './embedTheme';
|
|
52
59
|
/** What the framed run page and the element say to each other. */
|
|
53
60
|
export const EMBED_MESSAGES = {
|
|
@@ -249,7 +256,7 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
249
256
|
return;
|
|
250
257
|
}
|
|
251
258
|
// How it looks is drawn again; what it is, read again.
|
|
252
|
-
if (['mode', 'accent', 'theme', 'font', 'height'].includes(name)) {
|
|
259
|
+
if (['mode', 'accent', 'theme', 'font', 'height', 'resume'].includes(name)) {
|
|
253
260
|
this.draw();
|
|
254
261
|
return;
|
|
255
262
|
}
|
|
@@ -394,8 +401,10 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
394
401
|
return;
|
|
395
402
|
}
|
|
396
403
|
let look;
|
|
404
|
+
let resume;
|
|
397
405
|
try {
|
|
398
406
|
look = embedLookOf(this.inputs(), source.app);
|
|
407
|
+
resume = resumeOf(this.getAttribute('resume'));
|
|
399
408
|
}
|
|
400
409
|
catch (error) {
|
|
401
410
|
this.say(error.message);
|
|
@@ -411,13 +420,18 @@ export function defineDatalayerAppElement(options = {}) {
|
|
|
411
420
|
}
|
|
412
421
|
}
|
|
413
422
|
this.setAttribute('data-embed-mode', look.mode);
|
|
414
|
-
|
|
423
|
+
// The theme's own stylesheet: `loop`'s, or the one the application
|
|
424
|
+
// names (T-30).
|
|
425
|
+
const shadow = this.skeleton(getThemeConfig(look.variant).themeStyles.css);
|
|
415
426
|
const mount = document.createElement('div');
|
|
416
427
|
mount.className = 'datalayer-app-root';
|
|
417
428
|
shadow.appendChild(mount);
|
|
418
429
|
this.root = createRoot(mount);
|
|
419
430
|
const server = this.getAttribute('server') || undefined;
|
|
420
|
-
this.root.render(_jsx(StyleSheetManager, { target: shadow, children: _jsx(AppEmbed, { app: source.app, mode: look.mode, accent: look.accent, colorMode: look.colorMode, font: look.font, serverUrl: server, ...(server && this.token ? { embedToken: this.token } : {}), height: inlineHeightOf(this.getAttribute('height')), ownPortal: true, host: this.host
|
|
431
|
+
this.root.render(_jsx(StyleSheetManager, { target: shadow, children: _jsx(AppEmbed, { app: source.app, mode: look.mode, accent: look.accent, colorMode: look.colorMode, font: look.font, serverUrl: server, ...(server && this.token ? { embedToken: this.token } : {}), height: inlineHeightOf(this.getAttribute('height')), ownPortal: true, host: this.host,
|
|
432
|
+
// The visit's session kept in the page's storage, and picked
|
|
433
|
+
// up again after a reload, unless the host says not (D-13).
|
|
434
|
+
resume: resume }) }));
|
|
421
435
|
}
|
|
422
436
|
onMessage(event) {
|
|
423
437
|
if (!this.frame || event.source !== this.frame.contentWindow) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module loop/embed/embedConfig
|
|
13
13
|
*/
|
|
14
|
-
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
14
|
+
import type { AppAccent, AppEmbedMode, AppSpec, AppThemeVariant } from '../../types/agentspecs';
|
|
15
15
|
/** The element's name: the one a host writes. */
|
|
16
16
|
export declare const EMBED_TAG = "datalayer-app";
|
|
17
17
|
/** Where the script is served on a Datalayer origin; the stylesheet beside it. */
|
|
@@ -33,6 +33,7 @@ export declare const EMBED_ATTRIBUTES: {
|
|
|
33
33
|
readonly server: "An agent-runtimes server the application’s agent runs on; a Datalayer runtime by default.";
|
|
34
34
|
readonly api: "Datalayer’s API, where an application is read with its token; the platform’s by default.";
|
|
35
35
|
readonly height: "The height of an inline application, in pixels; 640 by default.";
|
|
36
|
+
readonly resume: "true or false: whether a visit’s conversation is picked up again after the page reloads, kept in the page’s storage; true by default.";
|
|
36
37
|
};
|
|
37
38
|
export type EmbedAttribute = keyof typeof EMBED_ATTRIBUTES;
|
|
38
39
|
/** Every attribute the element watches. */
|
|
@@ -86,6 +87,8 @@ export type EmbedLook = {
|
|
|
86
87
|
colorMode: EmbedColorMode;
|
|
87
88
|
/** The host's face; empty for the theme's own. */
|
|
88
89
|
font: string;
|
|
90
|
+
/** The theme the application names (T-30); `loop` when it names none. */
|
|
91
|
+
variant: AppThemeVariant;
|
|
89
92
|
};
|
|
90
93
|
/**
|
|
91
94
|
* What the element shows and how, from what the host wrote and what the
|
|
@@ -94,6 +97,12 @@ export type EmbedLook = {
|
|
|
94
97
|
* sentence (`EmbedAttributeError`), never quietly replaced.
|
|
95
98
|
*/
|
|
96
99
|
export declare function embedLookOf(inputs: EmbedInputs, app?: Pick<AppSpec, 'interface' | 'deployment'>): EmbedLook;
|
|
100
|
+
/**
|
|
101
|
+
* Whether a visit's conversation is picked up again after a reload (LOOP
|
|
102
|
+
* D-13): on unless the host writes `resume="false"`. Anything else is
|
|
103
|
+
* refused in a sentence.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resumeOf(value: string | null | undefined): boolean;
|
|
97
106
|
/** The height of an inline application, in pixels. */
|
|
98
107
|
export declare function inlineHeightOf(value: string | null | undefined): number;
|
|
99
108
|
/** What a snippet says beside the application's id. */
|
|
@@ -24,6 +24,7 @@ export const EMBED_ATTRIBUTES = {
|
|
|
24
24
|
server: 'An agent-runtimes server the application’s agent runs on; a Datalayer runtime by default.',
|
|
25
25
|
api: 'Datalayer’s API, where an application is read with its token; the platform’s by default.',
|
|
26
26
|
height: 'The height of an inline application, in pixels; 640 by default.',
|
|
27
|
+
resume: 'true or false: whether a visit’s conversation is picked up again after the page reloads, kept in the page’s storage; true by default.',
|
|
27
28
|
};
|
|
28
29
|
/** Every attribute the element watches. */
|
|
29
30
|
export const EMBED_OBSERVED_ATTRIBUTES = Object.keys(EMBED_ATTRIBUTES);
|
|
@@ -105,9 +106,19 @@ export function embedLookOf(inputs, app) {
|
|
|
105
106
|
'green';
|
|
106
107
|
const colorMode = oneOf(attributes.theme, EMBED_COLOR_MODES, 'a theme') ??
|
|
107
108
|
oneOf(variables.theme, EMBED_COLOR_MODES, 'a theme') ??
|
|
109
|
+
app?.interface?.theme?.mode ??
|
|
108
110
|
'auto';
|
|
109
111
|
const font = checkedFont(attributes.font) || checkedFont(variables.font);
|
|
110
|
-
|
|
112
|
+
const variant = app?.interface?.theme?.variant ?? 'loop';
|
|
113
|
+
return { mode, accent, colorMode, font, variant };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Whether a visit's conversation is picked up again after a reload (LOOP
|
|
117
|
+
* D-13): on unless the host writes `resume="false"`. Anything else is
|
|
118
|
+
* refused in a sentence.
|
|
119
|
+
*/
|
|
120
|
+
export function resumeOf(value) {
|
|
121
|
+
return (oneOf(value, ['true', 'false'], 'a value of resume') !== 'false');
|
|
111
122
|
}
|
|
112
123
|
/** The height of an inline application, in pixels. */
|
|
113
124
|
export function inlineHeightOf(value) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An embedded application's conversation, picked up again after the host's
|
|
3
|
+
* page reloads (LOOP D-13).
|
|
4
|
+
*
|
|
5
|
+
* The session an embed opened is kept by its uid in the host page's own
|
|
6
|
+
* storage, under the application and the visit its embed token names —
|
|
7
|
+
* the host's server issues the reloaded page a token for the same visit.
|
|
8
|
+
* After the reload, the session is asked for its conversation on the
|
|
9
|
+
* agent-runtimes server that holds it, with that token, and the chat draws
|
|
10
|
+
* it again and goes on with it. A session that is gone, or refused, is
|
|
11
|
+
* forgotten and said once; a new one starts.
|
|
12
|
+
*
|
|
13
|
+
* Kept in memory as well, for as long as the page lives: a token renewed
|
|
14
|
+
* for the visit redraws the application, which goes on with its session
|
|
15
|
+
* even when the host keeps nothing (`resume="false"`).
|
|
16
|
+
*
|
|
17
|
+
* Pure but for the storage and the request, each given or guarded: the page
|
|
18
|
+
* works without storage.
|
|
19
|
+
*
|
|
20
|
+
* @module loop/embed/embedSession
|
|
21
|
+
*/
|
|
22
|
+
import type { ChatMessage } from '../../types/messages';
|
|
23
|
+
/** Where a visit's session is kept in the host's storage: `<prefix>:<app>:<visit>`. */
|
|
24
|
+
export declare const EMBED_SESSION_KEY_PREFIX = "datalayer-app:session";
|
|
25
|
+
/**
|
|
26
|
+
* The visit an embed token was issued for: its `visit` claim, read without
|
|
27
|
+
* checking the signature — the runtime checks the token; the page only
|
|
28
|
+
* needs the name to keep the session under.
|
|
29
|
+
*/
|
|
30
|
+
export declare function visitOfToken(token: string | undefined): string | undefined;
|
|
31
|
+
/** The key a visit's session is kept under. */
|
|
32
|
+
export declare function embedSessionKey(app: string, visit: string): string;
|
|
33
|
+
/** What keeps a visit's session uid. */
|
|
34
|
+
export type SessionKeeper = {
|
|
35
|
+
read: (key: string) => string | undefined;
|
|
36
|
+
keep: (key: string, uid: string) => void;
|
|
37
|
+
forget: (key: string) => void;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The keeper of a visit's session: in memory, and — unless the host opted
|
|
41
|
+
* out — in its storage too, read first. Every access to the storage is
|
|
42
|
+
* guarded: a page whose storage is blocked, full or absent keeps the
|
|
43
|
+
* session in memory and resumes nothing after a reload.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sessionKeeper({ persist, storage, }: {
|
|
46
|
+
persist: boolean;
|
|
47
|
+
storage?: () => Storage | undefined;
|
|
48
|
+
}): SessionKeeper;
|
|
49
|
+
/** Forget what this page holds in memory (a test). */
|
|
50
|
+
export declare function forgetSessionsInMemory(): void;
|
|
51
|
+
/**
|
|
52
|
+
* A session's conversation as the chat draws it, from the AG-UI messages
|
|
53
|
+
* the runtime says (`GET …/sessions/{uid}/messages`): what was said and
|
|
54
|
+
* answered, in words. A page an answer drew is not drawn again here.
|
|
55
|
+
*/
|
|
56
|
+
export declare function messagesOfThread(messages: unknown): ChatMessage[];
|
|
57
|
+
/** What asking for a kept session found. */
|
|
58
|
+
export type Reattached = {
|
|
59
|
+
kind: 'resumed';
|
|
60
|
+
uid: string;
|
|
61
|
+
messages: ChatMessage[];
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'gone';
|
|
64
|
+
uid: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Ask the server that runs the application for a session's conversation,
|
|
68
|
+
* with the visit's token. Anything but an answer — a session the runtime
|
|
69
|
+
* let go, another visit's, a token refused, a server out of reach — is
|
|
70
|
+
* `gone`: a new session starts.
|
|
71
|
+
*/
|
|
72
|
+
export declare function reattachSession({ serverUrl, uid, token, fetcher, }: {
|
|
73
|
+
serverUrl: string;
|
|
74
|
+
uid: string;
|
|
75
|
+
token: string;
|
|
76
|
+
fetcher?: typeof fetch;
|
|
77
|
+
}): Promise<Reattached>;
|
|
78
|
+
/** What the page says, once, when the conversation it kept is gone. */
|
|
79
|
+
export declare const sessionGoneSentence: (name: string) => string;
|