@datalayer/agent-runtimes 1.3.75 → 1.3.79
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/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +48 -11
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantStage.d.ts +51 -5
- package/lib/chat/assistant/AssistantStage.js +60 -10
- package/lib/chat/assistant/BalloonParts.d.ts +18 -4
- package/lib/chat/assistant/BalloonParts.js +46 -41
- package/lib/chat/assistant/BalloonVisual.js +2 -1
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +8 -6
- package/lib/chat/assistant/SpeechBalloon.js +29 -30
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +20 -0
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +24 -18
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- package/lib/components/teams/A2ATeamGraph.d.ts +30 -3
- package/lib/components/teams/A2ATeamGraph.js +264 -41
- package/lib/components/teams/TeamNotebookView.js +15 -0
- package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +10 -6
- package/lib/components/teams/useA2ATeam.js +29 -8
- package/lib/examples/AgentA2ATeamExample.js +6 -0
- package/lib/examples/AssistantExample.js +3 -24
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +21 -3
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +53 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +5 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -25,7 +25,7 @@ import { AssistantCharactersPlugin, assistantCharacterNamed, assistantCharacters
|
|
|
25
25
|
import { OwlCharacterPlugin } from './utils/owlCharacterPlugin';
|
|
26
26
|
import { readTestSpriteCharacter } from './utils/testSpriteCharacter';
|
|
27
27
|
import { CLIPPY_JS_CHARACTERS, CLIPPY_JS_VERSION, readClippyJsCharacter, } from './utils/clippyJsCharacters';
|
|
28
|
-
import {
|
|
28
|
+
import { CHARACTER_FILES_ACCEPT, readCharacterFiles, } from '../chat/assistant/formats';
|
|
29
29
|
/**
|
|
30
30
|
* The agent-runtimes server the assistant talks to: `?agentRuntimesUrl=`
|
|
31
31
|
* when given, else this machine's on port 8765. It serves the `assistant`
|
|
@@ -75,27 +75,6 @@ const CATALOGUE = assistantCharactersOf(reactor).map(entry => ({
|
|
|
75
75
|
id: entry.id,
|
|
76
76
|
name: entry.character.name,
|
|
77
77
|
}));
|
|
78
|
-
/**
|
|
79
|
-
* A character file a person picked, read in the page (T-26): one `.acs`, or
|
|
80
|
-
* a clippy.js character's `agent.js` and `map.png` (and its sounds file).
|
|
81
|
-
*/
|
|
82
|
-
async function readPicked(files) {
|
|
83
|
-
const acs = files.find(file => file.name.toLowerCase().endsWith('.acs'));
|
|
84
|
-
if (acs) {
|
|
85
|
-
return readAcsCharacter(await acs.arrayBuffer());
|
|
86
|
-
}
|
|
87
|
-
const agentJs = files.find(file => file.name.toLowerCase() === 'agent.js');
|
|
88
|
-
const mapPng = files.find(file => /\.(png|gif|webp|jpe?g)$/i.test(file.name));
|
|
89
|
-
const soundsJs = files.find(file => /^sounds-.*\.js$/i.test(file.name));
|
|
90
|
-
if (!agentJs || !mapPng) {
|
|
91
|
-
throw new Error('Pick one .acs file, or a clippy.js character: its agent.js and its map image (and a sounds file if you have one).');
|
|
92
|
-
}
|
|
93
|
-
return readClippyCharacter({
|
|
94
|
-
agentJs: await agentJs.text(),
|
|
95
|
-
mapPng,
|
|
96
|
-
soundsJs: soundsJs ? await soundsJs.text() : undefined,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
78
|
const AssistantExample = () => {
|
|
100
79
|
const [character, setCharacter] = useState('paperclip');
|
|
101
80
|
// A character chosen by id is the one its plugin contributes.
|
|
@@ -185,13 +164,13 @@ const AssistantExample = () => {
|
|
|
185
164
|
onClick: () => setBalloon(balloon === 'history' ? 'current' : 'history') }), _jsx(Text, { "aria-live": "polite", "data-toggle-state": "", sx: { color: 'fg.muted' }, children: balloon === 'history' ? 'History' : 'Current' })] })] }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "The clippy.js characters" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["Fetched when picked from the", ' ', _jsxs("code", { children: ["clippyjs@", CLIPPY_JS_VERSION] }), " package on jsDelivr and read as data by the same reader. They are not in this repository: clippy.js\u2019s licence covers its code only, and the characters are Microsoft\u2019s."] }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: CLIPPY_JS_CHARACTERS.map(name => {
|
|
186
165
|
const picked = clippyJs[name] !== undefined && character === clippyJs[name];
|
|
187
166
|
return (_jsx(Button, { variant: picked ? 'primary' : 'default', onClick: () => void pickClippyJs(name), disabled: fetching !== undefined, "aria-pressed": picked, "data-assistant-character": `clippy-js-${name.toLowerCase()}`, children: fetching === name ? `${name}…` : name }, name));
|
|
188
|
-
}) })] }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept:
|
|
167
|
+
}) })] }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept: CHARACTER_FILES_ACCEPT, "aria-label": "Character files", onChange: async (event) => {
|
|
189
168
|
const files = Array.from(event.target.files ?? []);
|
|
190
169
|
if (!files.length) {
|
|
191
170
|
return;
|
|
192
171
|
}
|
|
193
172
|
try {
|
|
194
|
-
setCharacter(await
|
|
173
|
+
setCharacter(await readCharacterFiles(files));
|
|
195
174
|
setLoadError(undefined);
|
|
196
175
|
}
|
|
197
176
|
catch (error) {
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module examples/exampleGroups
|
|
7
7
|
*/
|
|
8
|
-
export declare const EXAMPLE_GROUP_ORDER: readonly ["Apps", "A2UI", "A2A", "AG-UI", "
|
|
8
|
+
export declare const EXAMPLE_GROUP_ORDER: readonly ["Apps", "Assistant", "A2UI", "A2A", "AG-UI", "Chat", "Document", "Notebook", "Capabilities", "Cell", "CopilotKit"];
|
|
9
9
|
export declare const getExampleGroup: (id: string) => string;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { AssistantStageProps } from '../../chat/assistant/AssistantStage';
|
|
10
10
|
import { type BalloonToolLine, type ToolLinePhase } from '../../chat/assistant/toolLine';
|
|
11
|
-
import { type AssistantState, type BalloonApproval
|
|
11
|
+
import { type AssistantState, type BalloonApproval } from '../../chat/assistant/state';
|
|
12
|
+
import type { DisplayItem } from '../../types/chat';
|
|
12
13
|
/** What the gallery poses a character in: a state, or stepped aside. */
|
|
13
14
|
export type GalleryPose = AssistantState | 'aside';
|
|
14
15
|
/** Every state, in the order an assistant lives them, then stepped aside. */
|
|
@@ -29,11 +30,11 @@ export declare function sampleSaying(index: number): {
|
|
|
29
30
|
more: boolean;
|
|
30
31
|
};
|
|
31
32
|
/**
|
|
32
|
-
* The conversation so far, after `count` sample exchanges (wrapping), as
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* The conversation so far, after `count` sample exchanges (wrapping), as
|
|
34
|
+
* the chat holds it — its messages and tool calls — which the `history`
|
|
35
|
+
* balloon draws with the chat's components.
|
|
35
36
|
*/
|
|
36
|
-
export declare function sampleHistory(count: number):
|
|
37
|
+
export declare function sampleHistory(count: number): DisplayItem[];
|
|
37
38
|
/** The tool the gallery calls (T-23): Odoo's, as Accounting calls it. */
|
|
38
39
|
export declare const SAMPLE_TOOL = "list_invoices";
|
|
39
40
|
/** The sample tool's line, at a phase: "Using list_invoices…", "Done: …", "… failed". */
|
|
@@ -133,7 +134,7 @@ export declare function balloonForPose(pose: GalleryPose, { saying, approval, to
|
|
|
133
134
|
/** What goes with the words in a `current` balloon: a notebook. */
|
|
134
135
|
attachment?: NonNullable<AssistantStageProps['balloon']>['attachment'];
|
|
135
136
|
/** The conversation so far, listed in a `history` balloon. */
|
|
136
|
-
history?: readonly
|
|
137
|
+
history?: readonly DisplayItem[];
|
|
137
138
|
/** The large visual the attachment is the compact form of: expandable. */
|
|
138
139
|
visual?: NonNullable<AssistantStageProps['balloon']>['visual'];
|
|
139
140
|
}): AssistantStageProps['balloon'];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { toolLineText, } from '../../chat/assistant/toolLine';
|
|
6
|
-
import { ASSISTANT_WORDS,
|
|
6
|
+
import { ASSISTANT_WORDS, latestSaying, } from '../../chat/assistant/state';
|
|
7
7
|
/** Every state, in the order an assistant lives them, then stepped aside. */
|
|
8
8
|
export const GALLERY_POSES = [
|
|
9
9
|
'idle',
|
|
@@ -75,20 +75,35 @@ export function sampleSaying(index) {
|
|
|
75
75
|
return { text: saying.text, more: saying.more };
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* The conversation so far, after `count` sample exchanges (wrapping), as
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* The conversation so far, after `count` sample exchanges (wrapping), as
|
|
79
|
+
* the chat holds it — its messages and tool calls — which the `history`
|
|
80
|
+
* balloon draws with the chat's components.
|
|
81
81
|
*/
|
|
82
82
|
export function sampleHistory(count) {
|
|
83
83
|
const items = [];
|
|
84
84
|
for (let turn = 0; turn < count; turn += 1) {
|
|
85
85
|
const conversation = SAMPLE_CONVERSATIONS[turn % SAMPLE_CONVERSATIONS.length];
|
|
86
|
-
for (const
|
|
87
|
-
const
|
|
88
|
-
|
|
86
|
+
for (const entry of conversation) {
|
|
87
|
+
const item = entry;
|
|
88
|
+
const id = `${turn}-${item.id}`;
|
|
89
|
+
items.push(item.toolName
|
|
90
|
+
? {
|
|
91
|
+
id,
|
|
92
|
+
type: 'tool-call',
|
|
93
|
+
toolCallId: id,
|
|
94
|
+
toolName: item.toolName,
|
|
95
|
+
args: {},
|
|
96
|
+
status: 'complete',
|
|
97
|
+
}
|
|
98
|
+
: {
|
|
99
|
+
id,
|
|
100
|
+
role: item.role,
|
|
101
|
+
content: item.content,
|
|
102
|
+
createdAt: new Date(0),
|
|
103
|
+
});
|
|
89
104
|
}
|
|
90
105
|
}
|
|
91
|
-
return
|
|
106
|
+
return items;
|
|
92
107
|
}
|
|
93
108
|
/** The tool the gallery calls (T-23): Odoo's, as Accounting calls it. */
|
|
94
109
|
export const SAMPLE_TOOL = 'list_invoices';
|
|
@@ -2,38 +2,6 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
export function listVariables() {
|
|
6
|
-
return `
|
|
7
|
-
def _list_variables():
|
|
8
|
-
import json
|
|
9
|
-
from types import BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodType, MethodWrapperType, ModuleType, TracebackType
|
|
10
|
-
|
|
11
|
-
_FORBIDDEN_TYPES = [type, BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodType, MethodWrapperType, ModuleType, TracebackType]
|
|
12
|
-
try:
|
|
13
|
-
from IPython.core.autocall import ExitAutocall
|
|
14
|
-
_FORBIDDEN_TYPES.append(ExitAutocall)
|
|
15
|
-
except ImportError:
|
|
16
|
-
pass
|
|
17
|
-
_exclude = tuple(_FORBIDDEN_TYPES)
|
|
18
|
-
|
|
19
|
-
_all = frozenset(globals())
|
|
20
|
-
_vars = {}
|
|
21
|
-
for _n in _all:
|
|
22
|
-
_v = globals()[_n]
|
|
23
|
-
|
|
24
|
-
if not (
|
|
25
|
-
_n.startswith('_') or
|
|
26
|
-
isinstance(_v, _exclude) or
|
|
27
|
-
# Special IPython variables
|
|
28
|
-
(_n == 'In' and isinstance(_v, list)) or
|
|
29
|
-
(_n == 'Out' and isinstance(_v, dict))
|
|
30
|
-
):
|
|
31
|
-
_vars[_n] = type(_v).__qualname__
|
|
32
|
-
|
|
33
|
-
return json.dumps(_vars)
|
|
34
|
-
|
|
35
|
-
_list_variables()`;
|
|
36
|
-
}
|
|
37
5
|
export function saveVariables(variables) {
|
|
38
6
|
return `
|
|
39
7
|
def _pickle_variables():
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime snippets.
|
|
3
|
+
*
|
|
4
|
+
* Listing a kernel's variables is not one of them: jupyter-react's
|
|
5
|
+
* `listKernelVariables` does it, with the one snippet every variables view
|
|
6
|
+
* shares.
|
|
3
7
|
*/
|
|
4
8
|
export interface IRuntimeSnippets {
|
|
5
9
|
/**
|
|
@@ -9,12 +13,6 @@ export interface IRuntimeSnippets {
|
|
|
9
13
|
* @returns The code snippet
|
|
10
14
|
*/
|
|
11
15
|
changeCurrentWorkingDirectory(path: string): string;
|
|
12
|
-
/**
|
|
13
|
-
* Code snippet to list the transferable kernel variables.
|
|
14
|
-
*
|
|
15
|
-
* @returns The code snippet
|
|
16
|
-
*/
|
|
17
|
-
listVariables(): string;
|
|
18
16
|
/**
|
|
19
17
|
* Code snippet to load kernel variables.
|
|
20
18
|
*
|
|
@@ -75,12 +73,6 @@ export declare class RuntimeSnippetsFacade {
|
|
|
75
73
|
* @returns The code snippet
|
|
76
74
|
*/
|
|
77
75
|
changeCurrentWorkingDirectory(path: string): string;
|
|
78
|
-
/**
|
|
79
|
-
* Code snippet to list the transferable kernel variables.
|
|
80
|
-
*
|
|
81
|
-
* @returns The code snippet
|
|
82
|
-
*/
|
|
83
|
-
listVariables(): string;
|
|
84
76
|
/**
|
|
85
77
|
* Code snippet to load kernel variables.
|
|
86
78
|
*
|
|
@@ -56,14 +56,6 @@ export class RuntimeSnippetsFacade {
|
|
|
56
56
|
.get(this.language)
|
|
57
57
|
.changeCurrentWorkingDirectory(path);
|
|
58
58
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Code snippet to list the transferable kernel variables.
|
|
61
|
-
*
|
|
62
|
-
* @returns The code snippet
|
|
63
|
-
*/
|
|
64
|
-
listVariables() {
|
|
65
|
-
return RuntimeSnippetsFacade.$language.get(this.language).listVariables();
|
|
66
|
-
}
|
|
67
59
|
/**
|
|
68
60
|
* Code snippet to load kernel variables.
|
|
69
61
|
*
|
|
@@ -55,7 +55,9 @@ export function AppFeedback({ app, workspace, }) {
|
|
|
55
55
|
flexDirection: 'column',
|
|
56
56
|
gap: 4,
|
|
57
57
|
padding: '4px 8px',
|
|
58
|
-
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: FEEDBACK_WORDS.ask }), _jsx(IconButton, { size: "small", variant: choice === true ? '
|
|
58
|
+
}, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: FEEDBACK_WORDS.ask }), _jsx(IconButton, { size: "small", variant: choice === true ? 'default' : 'invisible', icon: ThumbsupIcon, "aria-label": FEEDBACK_WORDS.liked, "aria-pressed": choice === true, onClick: () => setLiked({ turn: turn.id, liked: true }) }), _jsx(IconButton, { size: "small", variant: choice === false ? 'default' : 'invisible', icon: ThumbsdownIcon, "aria-label": FEEDBACK_WORDS.disliked, "aria-pressed": choice === false, onClick: () => setLiked({ turn: turn.id, liked: false }) })] }), choice !== null ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { "aria-label": FEEDBACK_WORDS.comment, placeholder: FEEDBACK_WORDS.comment, value: comment, rows: 2, resize: "vertical", block: true, onChange: event => setComment(event.target.value) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(Button, { size: "small", variant: "default", disabled: done?.state === 'sending', onClick: () => void send(), children: FEEDBACK_WORDS.send }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: FEEDBACK_WORDS.where(app.name) })] })] })) : null, done?.state === 'refused' ? (
|
|
59
|
+
// Said in the ink, as an alert: not a verdict, so not its colour.
|
|
60
|
+
_jsx(Text, { role: "alert", sx: { fontSize: 0, color: 'fg.default' }, children: done.says })) : null] }));
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
61
63
|
* The plugin, for one application: the strip above its prompt. Made once per
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a hosted or embedded application keeps, said under its prompt before
|
|
3
|
+
* the first message (LOOP R-31) — in every layout, since the composer draws
|
|
4
|
+
* what is hung on `LoopPromptPanel` wherever it stands. Gone once a message
|
|
5
|
+
* is sent: what it keeps was said before anything was.
|
|
6
|
+
*
|
|
7
|
+
* `AppRenderer` adds the plugin for an application that runs as a
|
|
8
|
+
* deployment — at its address, embedded — not for its builder's Preview.
|
|
9
|
+
*
|
|
10
|
+
* @module loop/apps/AppKept
|
|
11
|
+
*/
|
|
12
|
+
import type { JSX } from 'react';
|
|
13
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
14
|
+
export declare const APP_KEPT_PLUGIN_NAME = "@datalayer/loop-plugin-app-kept";
|
|
15
|
+
export declare function AppKept({ says }: {
|
|
16
|
+
says: string;
|
|
17
|
+
}): JSX.Element | null;
|
|
18
|
+
/**
|
|
19
|
+
* The plugin, for one application: what it keeps, under its prompt. Made
|
|
20
|
+
* once per application and sentence — a new plugin rebuilds the workspace.
|
|
21
|
+
*/
|
|
22
|
+
export declare function defineAppKeptPlugin(app: Pick<AppSpec, 'id' | 'name'>, says: string): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from '@primer/react';
|
|
3
|
+
import { contribution, definePlugin, signal } from '@datalayer/reactor';
|
|
4
|
+
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
5
|
+
import { LoopChatTurn, LoopPromptPanel } from '../core';
|
|
6
|
+
import { beforeFirstMessage } from './kept';
|
|
7
|
+
export const APP_KEPT_PLUGIN_NAME = '@datalayer/loop-plugin-app-kept';
|
|
8
|
+
/* A signal to read when no chat contributed a turn: the hook needs one. */
|
|
9
|
+
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
10
|
+
export function AppKept({ says }) {
|
|
11
|
+
const turns = useContributions(LoopChatTurn);
|
|
12
|
+
const turn = useSignalValue(turns[0]?.value.turn ?? NO_TURN);
|
|
13
|
+
if (!beforeFirstMessage(turn)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return (_jsx("div", { role: "note", style: { padding: '4px 8px' }, children: _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: says }) }));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The plugin, for one application: what it keeps, under its prompt. Made
|
|
20
|
+
* once per application and sentence — a new plugin rebuilds the workspace.
|
|
21
|
+
*/
|
|
22
|
+
export function defineAppKeptPlugin(app, says) {
|
|
23
|
+
function Panel() {
|
|
24
|
+
return _jsx(AppKept, { says: says });
|
|
25
|
+
}
|
|
26
|
+
return definePlugin({
|
|
27
|
+
name: `${APP_KEPT_PLUGIN_NAME}-${app.id}`,
|
|
28
|
+
displayName: `${app.name}: what it keeps`,
|
|
29
|
+
description: `What ${app.name} keeps of a conversation, and for how long, said before the first message.`,
|
|
30
|
+
octicon: 'archive',
|
|
31
|
+
emoji: '\u{1F5C4}',
|
|
32
|
+
contributes: [
|
|
33
|
+
contribution(LoopPromptPanel, { id: 'app-kept', placement: 'below', order: 30, Component: Panel }, { id: 'app-kept' }),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -157,7 +157,9 @@ export type AppPreset = {
|
|
|
157
157
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
158
158
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
159
159
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
160
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
160
|
+
* comment on each answer when its record keeps feedback (V-18); what it
|
|
161
|
+
* keeps and for how long, under its prompt before the first message, when
|
|
162
|
+
* `kept` says it (R-31); with
|
|
161
163
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
162
164
|
* computer (R-01b, R-23);
|
|
163
165
|
* laid out as `interface.layout` says;
|
|
@@ -174,6 +176,14 @@ export declare function appPreset(app: AppSpec, options?: {
|
|
|
174
176
|
sidebar?: boolean;
|
|
175
177
|
/** What its record is kept under: its activity's. */
|
|
176
178
|
appUid?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Say what it keeps before the first message (R-31): at its address and
|
|
181
|
+
* embedded, not in its builder's Preview — to a `visitor` not signed
|
|
182
|
+
* in, that nothing is kept (R-30).
|
|
183
|
+
*/
|
|
184
|
+
kept?: {
|
|
185
|
+
visitor: boolean;
|
|
186
|
+
};
|
|
177
187
|
}): AppPreset;
|
|
178
188
|
/**
|
|
179
189
|
* What an application's agent is created with on a Datalayer runtime: under
|
|
@@ -55,7 +55,9 @@ import { defineAppActivityPlugin } from '../plugins/app-activity';
|
|
|
55
55
|
import { defineAppComputerPlugin } from '../plugins/app-computer';
|
|
56
56
|
import { defineAppRulesPlugin } from '../plugins/app-rules';
|
|
57
57
|
import { defineAppFeedbackPlugin } from './AppFeedback';
|
|
58
|
+
import { defineAppKeptPlugin } from './AppKept';
|
|
58
59
|
import { keepsFeedback } from './feedback';
|
|
60
|
+
import { keptBeforeFirstMessage } from './kept';
|
|
59
61
|
/** The id of an agent or a Cog, without its version. */
|
|
60
62
|
export const agentIdOf = (app) => {
|
|
61
63
|
const reference = app.agent || app.team;
|
|
@@ -144,7 +146,9 @@ export function appLayoutOptions(app) {
|
|
|
144
146
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
145
147
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
146
148
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
147
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
149
|
+
* comment on each answer when its record keeps feedback (V-18); what it
|
|
150
|
+
* keeps and for how long, under its prompt before the first message, when
|
|
151
|
+
* `kept` says it (R-31); with
|
|
148
152
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
149
153
|
* computer (R-01b, R-23);
|
|
150
154
|
* laid out as `interface.layout` says;
|
|
@@ -179,6 +183,10 @@ export function appPreset(app, options = {}) {
|
|
|
179
183
|
// A thumb and a comment on each answer, kept in its record (LOOP
|
|
180
184
|
// V-18): only for an application whose record keeps feedback.
|
|
181
185
|
...(keepsFeedback(app) ? [defineAppFeedbackPlugin(app)] : []),
|
|
186
|
+
// What it keeps, and for how long, before anything is said (R-31).
|
|
187
|
+
...(options.kept
|
|
188
|
+
? [defineAppKeptPlugin(app, keptBeforeFirstMessage(app, options.kept))]
|
|
189
|
+
: []),
|
|
182
190
|
// What its builder reads beside its page (R-01b): its rules and the
|
|
183
191
|
// approvals waiting, and what it did.
|
|
184
192
|
...(options.sidebar
|
|
@@ -261,6 +269,13 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
261
269
|
*/
|
|
262
270
|
const source = JSON.stringify(dumpAppspec(app));
|
|
263
271
|
const off = [...(pluginsOff ?? [])].sort().join(',');
|
|
272
|
+
/*
|
|
273
|
+
* What it keeps, said before the first message (R-31): run as a
|
|
274
|
+
* deployment — at its address, embedded — and not in its builder's
|
|
275
|
+
* Preview; to a visitor not signed in, that nothing is kept (R-30).
|
|
276
|
+
*/
|
|
277
|
+
const deployed = Boolean(instance?.deploymentUid);
|
|
278
|
+
const visitor = Boolean(embed.datalayerVisitors);
|
|
264
279
|
const preset = useMemo(() => {
|
|
265
280
|
try {
|
|
266
281
|
return appPreset(app, {
|
|
@@ -268,6 +283,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
268
283
|
pluginsOff,
|
|
269
284
|
sidebar,
|
|
270
285
|
appUid: instance?.appUid,
|
|
286
|
+
...(deployed ? { kept: { visitor } } : {}),
|
|
271
287
|
});
|
|
272
288
|
}
|
|
273
289
|
catch (error) {
|
|
@@ -277,7 +293,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
277
293
|
}
|
|
278
294
|
},
|
|
279
295
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
280
|
-
[source, page, off, sidebar, instance?.appUid]);
|
|
296
|
+
[source, page, off, sidebar, instance?.appUid, deployed, visitor]);
|
|
281
297
|
const allPlugins = useMemo(() => ('problem' in preset ? plugins : [...preset.plugins, ...plugins]), [preset, plugins]);
|
|
282
298
|
/*
|
|
283
299
|
* On Datalayer, the application runs on a runtime: allocated with a plain
|
|
@@ -312,7 +328,9 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
312
328
|
? {
|
|
313
329
|
frameTitle: [app.emoji, app.name].filter(Boolean).join(' '),
|
|
314
330
|
}
|
|
315
|
-
: {}),
|
|
331
|
+
: {}),
|
|
332
|
+
// Its rules, activity and computer on a rail, one at a time (T-07).
|
|
333
|
+
sidebarRail: sidebar, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
316
334
|
// Where an application runs is decided by its host — the Studio's
|
|
317
335
|
// Preview, the hosted page — not offered to its user.
|
|
318
336
|
targetFixed: true,
|
|
@@ -15,6 +15,13 @@ export interface AppCheck {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const componentNamed: (name: string) => ComponentSpec | undefined;
|
|
17
17
|
/** How an organization's own context is named: never a catalogue id (agentspecs' `ORGANIZATION_FRAME_PREFIX`). */
|
|
18
|
+
/**
|
|
19
|
+
* What stops a Form block from asking (LOOP C-16), in agentspecs'
|
|
20
|
+
* `form_problems` sentences: its schema is an object of named fields, each
|
|
21
|
+
* required one among them, so that the page draws it and the runtime checks
|
|
22
|
+
* what it receives against the same schema.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formProblems(node: Record<string, unknown>): string[];
|
|
18
25
|
export declare const ORGANIZATION_FRAME_PREFIX = "org-";
|
|
19
26
|
/** Whether a reference names an organization's own context rather than the catalogue's (LOOP U-32). */
|
|
20
27
|
export declare const isOrganizationFrame: (ref: string) => boolean;
|
package/lib/loop/apps/checks.js
CHANGED
|
@@ -135,6 +135,42 @@ function shapeProblems(app) {
|
|
|
135
135
|
return problems;
|
|
136
136
|
}
|
|
137
137
|
/** How an organization's own context is named: never a catalogue id (agentspecs' `ORGANIZATION_FRAME_PREFIX`). */
|
|
138
|
+
/**
|
|
139
|
+
* What stops a Form block from asking (LOOP C-16), in agentspecs'
|
|
140
|
+
* `form_problems` sentences: its schema is an object of named fields, each
|
|
141
|
+
* required one among them, so that the page draws it and the runtime checks
|
|
142
|
+
* what it receives against the same schema.
|
|
143
|
+
*/
|
|
144
|
+
export function formProblems(node) {
|
|
145
|
+
const said = `The form “${String(node.id)}”`;
|
|
146
|
+
const schema = node.schema;
|
|
147
|
+
if (!isPlainRecord(schema)) {
|
|
148
|
+
return [
|
|
149
|
+
`${said} has no fields: its schema is the JSON Schema of what it asks.`,
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
const fields = schema.properties;
|
|
153
|
+
if (schema.type !== 'object' ||
|
|
154
|
+
!isPlainRecord(fields) ||
|
|
155
|
+
Object.keys(fields).length === 0) {
|
|
156
|
+
return [
|
|
157
|
+
`${said} asks for no named field: its schema is an object with properties.`,
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
const problems = [];
|
|
161
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
162
|
+
if (!isPlainRecord(field)) {
|
|
163
|
+
problems.push(`${said}'s field “${name}” is not a schema.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
167
|
+
const missing = required.filter(name => !(String(name) in fields));
|
|
168
|
+
if (missing.length > 0) {
|
|
169
|
+
problems.push(`${said} requires ${missing.map(name => `“${String(name)}”`).join(', ')}, which it does not ask.`);
|
|
170
|
+
}
|
|
171
|
+
return problems;
|
|
172
|
+
}
|
|
173
|
+
const isPlainRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
138
174
|
export const ORGANIZATION_FRAME_PREFIX = 'org-';
|
|
139
175
|
/** Whether a reference names an organization's own context rather than the catalogue's (LOOP U-32). */
|
|
140
176
|
export const isOrganizationFrame = (ref) => idOf(ref).startsWith(ORGANIZATION_FRAME_PREFIX);
|
|
@@ -208,6 +244,9 @@ function referenceProblems(app, organizationFrames) {
|
|
|
208
244
|
if (!componentNamed(String(node.component))) {
|
|
209
245
|
problems.push(`The surface's “${String(node.id)}” is a “${String(node.component)}”, which the catalog does not have.`);
|
|
210
246
|
}
|
|
247
|
+
else if (node.component === 'Form') {
|
|
248
|
+
problems.push(...formProblems(node));
|
|
249
|
+
}
|
|
211
250
|
}
|
|
212
251
|
const decider = app.decision?.decisionModel;
|
|
213
252
|
if (decider) {
|
package/lib/loop/apps/index.d.ts
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export * from './agent';
|
|
7
7
|
export * from './AppFeedback';
|
|
8
|
+
export * from './AppKept';
|
|
8
9
|
export * from './AppRenderer';
|
|
9
10
|
export * from './appspec';
|
|
10
11
|
export * from './checks';
|
|
11
12
|
export * from './computer';
|
|
12
13
|
export * from './feedback';
|
|
14
|
+
export * from './kept';
|
|
13
15
|
export * from './records';
|
|
14
16
|
export * from './rules';
|
|
17
|
+
export * from './saved';
|
|
15
18
|
export * from './yaml';
|
package/lib/loop/apps/index.js
CHANGED
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export * from './agent';
|
|
11
11
|
export * from './AppFeedback';
|
|
12
|
+
export * from './AppKept';
|
|
12
13
|
export * from './AppRenderer';
|
|
13
14
|
export * from './appspec';
|
|
14
15
|
export * from './checks';
|
|
15
16
|
export * from './computer';
|
|
16
17
|
export * from './feedback';
|
|
18
|
+
export * from './kept';
|
|
17
19
|
export * from './records';
|
|
18
20
|
export * from './rules';
|
|
21
|
+
export * from './saved';
|
|
19
22
|
export * from './yaml';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application keeps, said before the first message (LOOP R-31).
|
|
3
|
+
*
|
|
4
|
+
* A hosted or embedded application tells whoever uses it what its record
|
|
5
|
+
* keeps of the conversation — what its `record.include` names — and for how
|
|
6
|
+
* long — its `record.keep_for` — before they write anything. A session's
|
|
7
|
+
* start is kept whatever `include` says, so an application that keeps
|
|
8
|
+
* nothing else says that the conversation took place is kept. A visitor not
|
|
9
|
+
* signed in has nothing kept (R-30), and is told so.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no React.
|
|
12
|
+
*
|
|
13
|
+
* @module loop/apps/kept
|
|
14
|
+
*/
|
|
15
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
16
|
+
import type { ChatTurnSnapshot } from '../core';
|
|
17
|
+
/** What each item of `record.include` keeps, in a person's words. */
|
|
18
|
+
export declare const KEPT_ITEMS: Record<string, string>;
|
|
19
|
+
/** A retention in words: `30_days` → "30 days", `1_years` → "a year". */
|
|
20
|
+
export declare function keptForWords(keepFor: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* What an application says it keeps, before the first message.
|
|
23
|
+
*
|
|
24
|
+
* `visitor`: the person is not signed in, and nothing of theirs is kept.
|
|
25
|
+
*/
|
|
26
|
+
export declare function keptBeforeFirstMessage(app: Pick<AppSpec, 'name' | 'record'>, { visitor }?: {
|
|
27
|
+
visitor?: boolean;
|
|
28
|
+
}): string;
|
|
29
|
+
/** Whether it is still before the first message: what it keeps is said then only. */
|
|
30
|
+
export declare const beforeFirstMessage: (turn: ChatTurnSnapshot) => boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** What each item of `record.include` keeps, in a person's words. */
|
|
6
|
+
export const KEPT_ITEMS = {
|
|
7
|
+
conversations: 'this conversation',
|
|
8
|
+
actions: 'what it does',
|
|
9
|
+
decisions: 'how its rules decided',
|
|
10
|
+
approvals: 'what is approved',
|
|
11
|
+
checks: 'what its checks stopped',
|
|
12
|
+
outputs: 'its answers',
|
|
13
|
+
feedback: 'what you say of its answers',
|
|
14
|
+
};
|
|
15
|
+
const RETENTION = /^([1-9]\d*)_(day|days|month|months|year|years)$/;
|
|
16
|
+
/** A retention in words: `30_days` → "30 days", `1_years` → "a year". */
|
|
17
|
+
export function keptForWords(keepFor) {
|
|
18
|
+
const matched = RETENTION.exec(keepFor.trim());
|
|
19
|
+
if (!matched) {
|
|
20
|
+
throw new Error(`Cannot read the retention \`${keepFor}\`: write 90_days, 18_months or 1_years.`);
|
|
21
|
+
}
|
|
22
|
+
const count = Number(matched[1]);
|
|
23
|
+
const unit = matched[2].replace(/s$/, '');
|
|
24
|
+
return count === 1 ? `a ${unit}` : `${count} ${unit}s`;
|
|
25
|
+
}
|
|
26
|
+
/** A list in words: "a", "a and b", "a, b and c". */
|
|
27
|
+
function inWords(items) {
|
|
28
|
+
return items.length < 2
|
|
29
|
+
? (items[0] ?? '')
|
|
30
|
+
: `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What an application says it keeps, before the first message.
|
|
34
|
+
*
|
|
35
|
+
* `visitor`: the person is not signed in, and nothing of theirs is kept.
|
|
36
|
+
*/
|
|
37
|
+
export function keptBeforeFirstMessage(app, { visitor = false } = {}) {
|
|
38
|
+
if (visitor) {
|
|
39
|
+
return 'Nothing of this conversation is kept: you are not signed in.';
|
|
40
|
+
}
|
|
41
|
+
const name = app.name || 'This application';
|
|
42
|
+
const days = keptForWords(app.record.keepFor);
|
|
43
|
+
const kept = app.record.include
|
|
44
|
+
.map(item => KEPT_ITEMS[item])
|
|
45
|
+
.filter((words) => Boolean(words));
|
|
46
|
+
if (!kept.length) {
|
|
47
|
+
return `${name} keeps only that this conversation took place, for ${days}.`;
|
|
48
|
+
}
|
|
49
|
+
return `${name} keeps ${inWords(kept)} for ${days}, then deletes it.`;
|
|
50
|
+
}
|
|
51
|
+
/** Whether it is still before the first message: what it keeps is said then only. */
|
|
52
|
+
export const beforeFirstMessage = (turn) => turn.id === 0 && !turn.user;
|