@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.
Files changed (110) hide show
  1. package/lib/agents/AgentDetails.d.ts +16 -2
  2. package/lib/agents/AgentDetails.js +381 -272
  3. package/lib/chat/ChatFloating.d.ts +12 -2
  4. package/lib/chat/ChatFloating.js +48 -11
  5. package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
  6. package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
  7. package/lib/chat/assistant/AssistantStage.d.ts +51 -5
  8. package/lib/chat/assistant/AssistantStage.js +60 -10
  9. package/lib/chat/assistant/BalloonParts.d.ts +18 -4
  10. package/lib/chat/assistant/BalloonParts.js +46 -41
  11. package/lib/chat/assistant/BalloonVisual.js +2 -1
  12. package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
  13. package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
  14. package/lib/chat/assistant/SpeechBalloon.d.ts +8 -6
  15. package/lib/chat/assistant/SpeechBalloon.js +29 -30
  16. package/lib/chat/assistant/assistantDetails.d.ts +84 -0
  17. package/lib/chat/assistant/assistantDetails.js +140 -0
  18. package/lib/chat/assistant/formats/files.d.ts +23 -0
  19. package/lib/chat/assistant/formats/files.js +48 -0
  20. package/lib/chat/assistant/formats/index.d.ts +2 -0
  21. package/lib/chat/assistant/formats/index.js +1 -0
  22. package/lib/chat/assistant/state.d.ts +7 -14
  23. package/lib/chat/assistant/state.js +21 -20
  24. package/lib/chat/assistant/toolLine.d.ts +16 -0
  25. package/lib/chat/assistant/toolLine.js +46 -0
  26. package/lib/chat/base/ChatBase.js +20 -0
  27. package/lib/chat/base/loopMessage.d.ts +37 -0
  28. package/lib/chat/base/loopMessage.js +37 -0
  29. package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
  30. package/lib/chat/messages/ChatMarkdown.js +18 -0
  31. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  32. package/lib/chat/messages/ChatMessageList.js +24 -18
  33. package/lib/chat/styles/streamdownStyles.js +3 -1
  34. package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
  35. package/lib/chat/tools/ToolCallDisplay.js +8 -7
  36. package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
  37. package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
  38. package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
  39. package/lib/components/teams/A2ATeamGraph.d.ts +30 -3
  40. package/lib/components/teams/A2ATeamGraph.js +264 -41
  41. package/lib/components/teams/TeamNotebookView.js +15 -0
  42. package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
  43. package/lib/components/teams/teamNotebookKernel.js +17 -0
  44. package/lib/components/teams/useA2ATeam.d.ts +10 -6
  45. package/lib/components/teams/useA2ATeam.js +29 -8
  46. package/lib/examples/AgentA2ATeamExample.js +6 -0
  47. package/lib/examples/AssistantExample.js +3 -24
  48. package/lib/examples/AssistantGalleryExample.js +2 -1
  49. package/lib/examples/exampleGroups.d.ts +1 -1
  50. package/lib/examples/exampleGroups.js +1 -1
  51. package/lib/examples/utils/assistantGallery.d.ts +7 -6
  52. package/lib/examples/utils/assistantGallery.js +23 -8
  53. package/lib/jupyter/exec/Python.d.ts +0 -1
  54. package/lib/jupyter/exec/Python.js +0 -32
  55. package/lib/jupyter/exec/Snippets.d.ts +4 -12
  56. package/lib/jupyter/exec/Snippets.js +0 -8
  57. package/lib/loop/apps/AppFeedback.js +3 -1
  58. package/lib/loop/apps/AppKept.d.ts +22 -0
  59. package/lib/loop/apps/AppKept.js +36 -0
  60. package/lib/loop/apps/AppRenderer.d.ts +11 -1
  61. package/lib/loop/apps/AppRenderer.js +21 -3
  62. package/lib/loop/apps/checks.d.ts +7 -0
  63. package/lib/loop/apps/checks.js +39 -0
  64. package/lib/loop/apps/index.d.ts +3 -0
  65. package/lib/loop/apps/index.js +3 -0
  66. package/lib/loop/apps/kept.d.ts +30 -0
  67. package/lib/loop/apps/kept.js +52 -0
  68. package/lib/loop/apps/records.d.ts +12 -4
  69. package/lib/loop/apps/records.js +13 -5
  70. package/lib/loop/apps/saved.d.ts +30 -0
  71. package/lib/loop/apps/saved.js +40 -0
  72. package/lib/loop/embed/AppEmbed.d.ts +8 -1
  73. package/lib/loop/embed/AppEmbed.js +4 -4
  74. package/lib/loop/embed/LoopEmbed.d.ts +6 -1
  75. package/lib/loop/embed/LoopEmbed.js +4 -2
  76. package/lib/loop/embed/element.js +1 -1
  77. package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
  78. package/lib/loop/plugins/agents/browserService.d.ts +7 -1
  79. package/lib/loop/plugins/agents/browserService.js +4 -0
  80. package/lib/loop/plugins/agents/plugin.d.ts +16 -0
  81. package/lib/loop/plugins/agents/switchable.d.ts +3 -0
  82. package/lib/loop/plugins/agents/switchable.js +1 -0
  83. package/lib/loop/plugins/app-activity/index.js +3 -0
  84. package/lib/loop/plugins/app-computer/index.js +3 -0
  85. package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
  86. package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
  87. package/lib/loop/plugins/app-rules/index.js +3 -0
  88. package/lib/loop/plugins/assistant/index.d.ts +7 -2
  89. package/lib/loop/plugins/assistant/index.js +53 -4
  90. package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
  91. package/lib/loop/plugins/assistant-characters/index.js +17 -0
  92. package/lib/loop/plugins/chat/ChatView.js +4 -1
  93. package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
  94. package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
  95. package/lib/loop/presets.d.ts +4 -0
  96. package/lib/loop/presets.js +3 -1
  97. package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
  98. package/lib/loop/shell/LoopWorkspace.js +5 -4
  99. package/lib/loop/shell/SidebarRail.d.ts +53 -0
  100. package/lib/loop/shell/SidebarRail.js +74 -0
  101. package/lib/loop/shell/index.d.ts +2 -0
  102. package/lib/loop/shell/index.js +2 -0
  103. package/lib/loop/shell/paneMotion.d.ts +21 -0
  104. package/lib/loop/shell/paneMotion.js +37 -0
  105. package/lib/specs/apps.js +8 -5
  106. package/lib/specs/uiPlugins.js +529 -0
  107. package/lib/types/agentspecs.d.ts +5 -3
  108. package/lib/types/chat.d.ts +5 -0
  109. package/package.json +2 -2
  110. package/scripts/codegen/generate_ui_plugins.py +112 -2
@@ -11,7 +11,11 @@
11
11
  /** What a record entry is. */
12
12
  export type RecordKind = 'session' | 'tool_call' | 'decision' | 'check' | 'approval' | 'output' | 'feedback'
13
13
  /** A turn of a conversation: what was asked, what it answered. */
14
- | 'turn';
14
+ | 'turn'
15
+ /** A run began: what Activity reads as in progress until it answers (R-15). */
16
+ | 'run'
17
+ /** What one of its channels was sent, or why not (R-37). */
18
+ | 'notification';
15
19
  /** One entry of an application's record. */
16
20
  export type RecordEntry = {
17
21
  uid: string;
@@ -36,7 +40,11 @@ export declare const RECORD_KIND_WORDS: Record<RecordKind, string>;
36
40
  export declare function entryOf(raw: Record<string, unknown>): RecordEntry;
37
41
  /** A session in a sentence: how many calls, what stopped it, how it ended. */
38
42
  export declare function sessionSentence(entries: readonly RecordEntry[]): string;
39
- /** The sessions of an application, newest first: their opening entries. */
40
- export declare function listSessions(context: RecordsContext, appUid: string, limit?: number): Promise<RecordEntry[]>;
41
- /** What one session did, in order. */
43
+ /**
44
+ * The sessions of an application, newest first: their opening entries. Real
45
+ * use only — the sessions its tests ran are read apart (R-07) — unless
46
+ * `tests` says otherwise: `true` for the tests only, `null` for both.
47
+ */
48
+ export declare function listSessions(context: RecordsContext, appUid: string, limit?: number, tests?: boolean | null): Promise<RecordEntry[]>;
49
+ /** What one session did, in order: a run's start is a mark, not a step (R-15). */
42
50
  export declare function readSession(context: RecordsContext, sessionUid: string): Promise<RecordEntry[]>;
@@ -12,6 +12,8 @@ export const RECORD_KIND_WORDS = {
12
12
  output: 'Answer',
13
13
  feedback: 'Feedback',
14
14
  turn: 'Turn',
15
+ run: 'Working',
16
+ notification: 'Notification',
15
17
  };
16
18
  /** An entry as ai-agents answers with it. */
17
19
  export function entryOf(raw) {
@@ -65,17 +67,23 @@ async function read(context, path, failure) {
65
67
  }
66
68
  return body;
67
69
  }
68
- /** The sessions of an application, newest first: their opening entries. */
69
- export async function listSessions(context, appUid, limit = 20) {
70
- const body = await read(context, `/sessions?app_uid=${encodeURIComponent(appUid)}&limit=${limit}`, 'The sessions could not be read');
70
+ /**
71
+ * The sessions of an application, newest first: their opening entries. Real
72
+ * use only — the sessions its tests ran are read apart (R-07) — unless
73
+ * `tests` says otherwise: `true` for the tests only, `null` for both.
74
+ */
75
+ export async function listSessions(context, appUid, limit = 20, tests = false) {
76
+ const body = await read(context, `/sessions?app_uid=${encodeURIComponent(appUid)}&limit=${limit}${tests === null ? '' : `&tests=${tests}`}`, 'The sessions could not be read');
71
77
  return Array.isArray(body.sessions)
72
78
  ? body.sessions.map(entryOf)
73
79
  : [];
74
80
  }
75
- /** What one session did, in order. */
81
+ /** What one session did, in order: a run's start is a mark, not a step (R-15). */
76
82
  export async function readSession(context, sessionUid) {
77
83
  const body = await read(context, `/records?session_uid=${encodeURIComponent(sessionUid)}&limit=500`, 'The session could not be read');
78
84
  return Array.isArray(body.entries)
79
- ? body.entries.map(entryOf)
85
+ ? body.entries
86
+ .map(entryOf)
87
+ .filter(entry => entry.kind !== 'run')
80
88
  : [];
81
89
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Approve and save (LOOP R-24): what an application wants to keep, shown
3
+ * before it is kept. The runtime asks every `save_to_space` call on the
4
+ * approvals path with the title and the whole text; an approvals card shows
5
+ * them, with *Approve and save* and *Decline* in place of *Approve* and
6
+ * *Reject*.
7
+ *
8
+ * Pure: no React.
9
+ *
10
+ * @module loop/apps/saved
11
+ */
12
+ /** The runtime's tool that saves a result as a page of a Space. */
13
+ export declare const SAVE_TOOL = "save_to_space";
14
+ /** What an approvals card says of a result to keep. */
15
+ export declare const SAVE_WORDS: {
16
+ readonly approve: "Approve and save";
17
+ readonly decline: "Decline";
18
+ readonly where: (space: string) => string;
19
+ };
20
+ /** The result an approval asks to save: its title, its text, its Space. */
21
+ export type Draft = {
22
+ title: string;
23
+ content: string;
24
+ space: string;
25
+ };
26
+ /** The draft an approval carries, or null for any other approval. */
27
+ export declare function draftOfApproval(approval: {
28
+ tool_name: string;
29
+ tool_args?: Record<string, unknown>;
30
+ }): Draft | null;
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Approve and save (LOOP R-24): what an application wants to keep, shown
7
+ * before it is kept. The runtime asks every `save_to_space` call on the
8
+ * approvals path with the title and the whole text; an approvals card shows
9
+ * them, with *Approve and save* and *Decline* in place of *Approve* and
10
+ * *Reject*.
11
+ *
12
+ * Pure: no React.
13
+ *
14
+ * @module loop/apps/saved
15
+ */
16
+ /** The runtime's tool that saves a result as a page of a Space. */
17
+ export const SAVE_TOOL = 'save_to_space';
18
+ /** What an approvals card says of a result to keep. */
19
+ export const SAVE_WORDS = {
20
+ approve: 'Approve and save',
21
+ decline: 'Decline',
22
+ where: (space) => space
23
+ ? `To keep as a page of ${space}:`
24
+ : 'To keep as a page of its Space:',
25
+ };
26
+ /** The draft an approval carries, or null for any other approval. */
27
+ export function draftOfApproval(approval) {
28
+ if (approval.tool_name !== SAVE_TOOL) {
29
+ return null;
30
+ }
31
+ const said = (key) => {
32
+ const value = approval.tool_args?.[key];
33
+ return typeof value === 'string' ? value : '';
34
+ };
35
+ return {
36
+ title: said('title'),
37
+ content: said('content'),
38
+ space: said('space'),
39
+ };
40
+ }
@@ -23,6 +23,13 @@ export type AppEmbedProps = {
23
23
  serverUrl?: string;
24
24
  /** What its record is kept under, on Datalayer. */
25
25
  instance?: AppInstance;
26
+ /**
27
+ * The embed token the host's server was issued for this visit (LOOP R-20):
28
+ * what the chat speaks to the application's session with on the
29
+ * agent-runtimes server that runs its deployment (`serverUrl`). It runs a
30
+ * session of that deployment, and reaches nothing else.
31
+ */
32
+ embedToken?: string;
26
33
  /** Height of an inline application. */
27
34
  height?: number | string;
28
35
  /**
@@ -55,5 +62,5 @@ export declare function EmbedThemed({ accent, colorMode, font, children, style,
55
62
  children: ReactNode;
56
63
  style?: CSSProperties;
57
64
  }): JSX.Element;
58
- export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, height, ownPortal, plugins, }: AppEmbedProps): JSX.Element;
65
+ export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, }: AppEmbedProps): JSX.Element;
59
66
  export default AppEmbed;
@@ -72,7 +72,7 @@ function Face({ emoji }) {
72
72
  * The workspace tells the chrome what the application is doing and what it
73
73
  * last said (`onPresence`, `onSaying`).
74
74
  */
75
- function FloatingApp({ app, view, colorMode, themeOverrides, character, serverUrl, instance, }) {
75
+ function FloatingApp({ app, view, colorMode, themeOverrides, character, serverUrl, instance, embedToken, }) {
76
76
  const [presence, setPresence] = useState('idle');
77
77
  const [said, setSaid] = useState({ answering: false });
78
78
  const welcome = app.interface.welcome || app.description;
@@ -83,7 +83,7 @@ function FloatingApp({ app, view, colorMode, themeOverrides, character, serverUr
83
83
  return (_jsx(ChatFloating, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), ...(app.interface.balloon
84
84
  ? { balloonDisplay: app.interface.balloon }
85
85
  : {}), 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: {
86
- 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 } : {}), instance: instance,
86
+ 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,
87
87
  // The host's accent and face over the application's own, inside
88
88
  // its conversation too, in the embed's mode.
89
89
  themeOverrides: themeOverrides, colorMode: colorMode,
@@ -139,7 +139,7 @@ export function EmbedThemed({ accent, colorMode, font, children, style, }) {
139
139
  */
140
140
  _jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: loopTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
141
141
  }
142
- export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, height = 640, ownPortal = false, plugins = NO_PLUGINS, }) {
142
+ export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, }) {
143
143
  const system = useSystemMode();
144
144
  const resolvedMode = colorMode === 'auto' ? system : colorMode;
145
145
  const shownAs = mode ?? app.deployment.embedded?.mode ?? 'inline';
@@ -157,7 +157,7 @@ export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUr
157
157
  return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, style: view
158
158
  ? // Floating: nothing in the page's flow, nothing painted behind.
159
159
  { backgroundColor: 'transparent' }
160
- : { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(FloatingApp, { app: app, view: view, serverUrl: serverUrl, instance: instance, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance,
160
+ : { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(FloatingApp, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeOverrides: themeOverrides, character: character })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
161
161
  // The host's accent and face over the application's own, inside
162
162
  // its conversation too, in the embed's mode: the host's or its
163
163
  // visitor's system's, not a Datalayer setting.
@@ -48,6 +48,11 @@ export type LoopEmbedProps = LoopPresetOptions & {
48
48
  * which is what a full-page host wants.
49
49
  */
50
50
  frameHeight?: number | string;
51
+ /**
52
+ * The sidebar as a rail of line icons, one panel shown at a time (LOOP
53
+ * T-07). See `LoopWorkspaceProps.sidebarRail`.
54
+ */
55
+ sidebarRail?: boolean;
51
56
  /**
52
57
  * The host's own plugins, mounted alongside the preset's.
53
58
  *
@@ -58,5 +63,5 @@ export type LoopEmbedProps = LoopPresetOptions & {
58
63
  */
59
64
  plugins?: PluginRef[];
60
65
  };
61
- export declare function LoopEmbed({ agentId, showHeader, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, plugins, ...preset }: LoopEmbedProps): React.JSX.Element;
66
+ export declare function LoopEmbed({ agentId, showHeader, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, sidebarRail, plugins, ...preset }: LoopEmbedProps): React.JSX.Element;
62
67
  export default LoopEmbed;
@@ -30,7 +30,7 @@ import { internalQueryClient } from '../../utils';
30
30
  import { buildLoopReactor, LoopWorkspace } from '../shell';
31
31
  import { loopPlugins } from '../presets';
32
32
  import { WindowFrame } from '../plugins/window-frame';
33
- export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, plugins = [], ...preset }) {
33
+ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, sidebarRail = false, plugins = [], ...preset }) {
34
34
  /*
35
35
  * The latest `firstPromptHook`, kept in a ref rather than closed over
36
36
  * directly.
@@ -77,6 +77,8 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
77
77
  // By its content: an application's spec written inline is a new object each render.
78
78
  JSON.stringify(preset.datalayerCreatePayload ?? null),
79
79
  JSON.stringify(preset.datalayerVisitors ?? null),
80
+ preset.datalayerKept?.url,
81
+ preset.embedToken,
80
82
  preset.defaultEditor,
81
83
  preset.showViewSelector,
82
84
  preset.hideChatHeader,
@@ -135,7 +137,7 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
135
137
  * view and the workspace draws "No view is available yet".
136
138
  */
137
139
  useReactor(reactor);
138
- const shell = (_jsx(LoopWorkspace, { serverUrl: preset.serverUrl ?? '', agentId: agentId, reactor: reactor, manageReactor: false, showViewSelector: preset.showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer }));
140
+ const shell = (_jsx(LoopWorkspace, { serverUrl: preset.serverUrl ?? '', agentId: agentId, reactor: reactor, manageReactor: false, showViewSelector: preset.showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer, sidebarRail: sidebarRail }));
139
141
  return (_jsx(QueryClientProvider, { client: internalQueryClient, children: frameTitle !== undefined ? (_jsx(WindowFrame, { title: frameTitle, height: frameHeight, children: shell })) : (
140
142
  // Filling its host, whatever the host is: in a flex row it used to
141
143
  // shrink to the chat's own width and leave the rest of the frame bare.
@@ -378,7 +378,7 @@ export function defineDatalayerAppElement(options = {}) {
378
378
  shadow.appendChild(mount);
379
379
  this.root = createRoot(mount);
380
380
  const server = this.getAttribute('server') || undefined;
381
- 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, height: inlineHeightOf(this.getAttribute('height')), ownPortal: true }) }));
381
+ 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 }) }));
382
382
  }
383
383
  onMessage(event) {
384
384
  if (!this.frame || event.source !== this.frame.contentWindow) {
@@ -25,6 +25,11 @@ export function DatalayerAgentBridge() {
25
25
  // A conversation without an account (LOOP R-30): the agent is already on
26
26
  // the visitors' runtime, kept warm there; nothing is allocated or made.
27
27
  const visitors = agentsConfig?.datalayerVisitors;
28
+ // A deployment kept on a runtime of its own (LOOP R-33): its agent was made
29
+ // there from the deployment record as the runtime started; nothing is
30
+ // allocated or made either.
31
+ const kept = agentsConfig?.datalayerKept;
32
+ const already = visitors?.url ?? kept?.url;
28
33
  const service = useOptionalSandboxService();
29
34
  const target = useSignalValue(service?.target ?? IDLE_SANDBOX_TARGET_SIGNAL);
30
35
  const onDatalayer = target === 'datalayer';
@@ -68,19 +73,20 @@ export function DatalayerAgentBridge() {
68
73
  agentSpecId,
69
74
  agentConfig,
70
75
  variant: 'cloud-pydanticai',
71
- autoStart: onDatalayer && !visitors,
72
- autoCreateAgent: onDatalayer && !visitors,
76
+ autoStart: onDatalayer && !already,
77
+ autoCreateAgent: onDatalayer && !already,
73
78
  });
74
79
  useEffect(() => {
75
80
  if (!service || !onDatalayer) {
76
81
  return;
77
82
  }
78
- if (visitors) {
83
+ if (already) {
79
84
  service.report({
80
85
  variant: 'datalayer',
81
- // No sandbox: a visitor's application runs no code.
86
+ // No sandbox reported: the agent is already on its runtime, and the
87
+ // chat is all the page talks to.
82
88
  sandbox_running: false,
83
- agent_base_url: visitors.url,
89
+ agent_base_url: already,
84
90
  });
85
91
  return;
86
92
  }
@@ -118,7 +124,7 @@ export function DatalayerAgentBridge() {
118
124
  // host's server for an agent that is not on it.
119
125
  agent_base_url: runtime.agentBaseUrl,
120
126
  });
121
- }, [service, onDatalayer, runtime, status, error, visitors]);
127
+ }, [service, onDatalayer, runtime, status, error, already]);
122
128
  return null;
123
129
  }
124
130
  export default DatalayerAgentBridge;
@@ -1,4 +1,4 @@
1
- import type { ServiceManager } from '@jupyterlab/services';
1
+ import type { Kernel, ServiceManager } from '@jupyterlab/services';
2
2
  import type { SandboxService } from './service';
3
3
  /** What a browser sandbox additionally exposes. */
4
4
  export type BrowserSandboxService = SandboxService & {
@@ -13,6 +13,12 @@ export type BrowserSandboxService = SandboxService & {
13
13
  * point of a shared sandbox.
14
14
  */
15
15
  getServiceManager: () => ServiceManager.IManager | null;
16
+ /**
17
+ * The kernel's connection, once it is up: what a view that follows the
18
+ * kernel itself listens to — its variables (jupyter-react's
19
+ * `KernelVariables`), refreshed after each execution.
20
+ */
21
+ getKernelConnection: () => Kernel.IKernelConnection | null;
16
22
  };
17
23
  /**
18
24
  * How a kernel-backed sandbox gets its services.
@@ -126,6 +126,7 @@ export function createBrowserSandboxService(source = browserSource()) {
126
126
  const ready = computed(() => lifecycle.value === 'running');
127
127
  let serviceManager = null;
128
128
  let kernel = null;
129
+ let kernelConnection = null;
129
130
  let starting = null;
130
131
  async function start() {
131
132
  if (kernel) {
@@ -139,6 +140,7 @@ export function createBrowserSandboxService(source = browserSource()) {
139
140
  type: 'notebook',
140
141
  kernel: { name: 'python' },
141
142
  });
143
+ kernelConnection = connection.kernel;
142
144
  kernel = connection.kernel;
143
145
  status.value = {
144
146
  variant: source.variant,
@@ -158,6 +160,7 @@ export function createBrowserSandboxService(source = browserSource()) {
158
160
  // lives on the server — should ask `kind` rather than discover it here.
159
161
  serverUrl: '',
160
162
  getServiceManager: () => serviceManager,
163
+ getKernelConnection: () => kernelConnection,
161
164
  report(next) {
162
165
  status.value = next;
163
166
  },
@@ -182,6 +185,7 @@ export function createBrowserSandboxService(source = browserSource()) {
182
185
  // notebook that is open.
183
186
  void kernel?.shutdown?.();
184
187
  kernel = null;
188
+ kernelConnection = null;
185
189
  serviceManager = source.owned ? null : serviceManager;
186
190
  // And hand back whatever the source borrowed from the page.
187
191
  source.release?.();
@@ -103,6 +103,22 @@ export type AgentsConfig = {
103
103
  * and the chat speaks to it with a visitor's token for the application.
104
104
  */
105
105
  datalayerVisitors?: DatalayerVisitors;
106
+ /**
107
+ * A deployment kept on a runtime of its own (LOOP R-33): its agent is
108
+ * already there, made from the deployment record when the runtime started,
109
+ * so nothing is allocated or created, and the chat speaks to it with the
110
+ * person's own token. `url` is the runtime's agent-runtimes address.
111
+ */
112
+ datalayerKept?: {
113
+ url: string;
114
+ };
115
+ /**
116
+ * An embed token for the application (LOOP R-20): what an embedded
117
+ * application's chat speaks to its session with, for a visitor the platform
118
+ * does not know — it runs a session of the application's deployment, and
119
+ * reaches nothing else.
120
+ */
121
+ embedToken?: string;
106
122
  /**
107
123
  * The target is the host's decision, not the reader's: started there even
108
124
  * with the control hidden. For an application that runs where it is
@@ -18,6 +18,7 @@
18
18
  * @module loop/plugins/agents/switchable
19
19
  */
20
20
  import { type ReadonlySignal } from '@datalayer/reactor';
21
+ import type { Kernel } from '@jupyterlab/services';
21
22
  import { type ServiceManagerSource } from './browserService';
22
23
  import { type SandboxService } from './service';
23
24
  /** Where the sandbox runs. */
@@ -72,6 +73,8 @@ export type SwitchableSandboxService = SandboxService & {
72
73
  setTarget: (target: SandboxTarget) => Promise<void>;
73
74
  /** The in-browser services, when the browser is the active target. */
74
75
  getServiceManager: () => unknown | null;
76
+ /** The in-browser kernel's connection, when the browser is the active target. */
77
+ getKernelConnection: () => Kernel.IKernelConnection | null;
75
78
  };
76
79
  export type SwitchableConfig = {
77
80
  serverUrl: string;
@@ -239,6 +239,7 @@ export function createSwitchableSandboxService({ serverUrl, initialTarget = 'loc
239
239
  lastExecution,
240
240
  serverUrl,
241
241
  getServiceManager: () => target.peek() === 'browser' ? browser.getServiceManager() : null,
242
+ getKernelConnection: () => target.peek() === 'browser' ? browser.getKernelConnection() : null,
242
243
  report(next) {
243
244
  active.peek().report(next);
244
245
  },
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { definePlugin } from '@datalayer/reactor';
3
+ import { PulseIcon } from '@primer/octicons-react';
3
4
  import { LoopSlots } from '../../core';
4
5
  import { AppActivity } from './AppActivity';
5
6
  export const APP_ACTIVITY_PLUGIN_NAME = '@datalayer/loop-plugin-app-activity';
@@ -25,6 +26,8 @@ export function defineAppActivityPlugin(app, appUid) {
25
26
  slot: LoopSlots.sidebar,
26
27
  order: 20,
27
28
  Component: Feed,
29
+ // Its line icon on the workspace's rail (T-07).
30
+ rail: { label: 'Activity', icon: PulseIcon },
28
31
  },
29
32
  ],
30
33
  }),
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { definePlugin } from '@datalayer/reactor';
3
+ import { DeviceDesktopIcon } from '@primer/octicons-react';
3
4
  import { LoopSlots } from '../../core';
4
5
  import { AppComputer } from './AppComputer';
5
6
  export const APP_COMPUTER_PLUGIN_NAME = '@datalayer/loop-plugin-app-computer';
@@ -21,6 +22,8 @@ export function defineAppComputerPlugin(app) {
21
22
  slot: LoopSlots.sidebar,
22
23
  order: 30,
23
24
  Component: Computer,
25
+ // Its line icon on the workspace's rail (T-07).
26
+ rail: { label: 'Computer', icon: DeviceDesktopIcon },
24
27
  },
25
28
  ],
26
29
  }),
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import type { JSX } from 'react';
14
14
  import type { AppRuleSpec, AppSpec } from '../../../types/agentspecs';
15
+ import { type Draft } from '../../apps/saved';
15
16
  import { type ApprovalRecord } from '../../../hooks/useToolApprovals';
16
17
  /** What the card says. */
17
18
  export declare const APP_RULES_WORDS: {
@@ -45,6 +46,10 @@ export declare const ruleOfApproval: (approval: ApprovalRecord) => string;
45
46
  * person's pages over the same `/ws`.
46
47
  */
47
48
  export declare function approvalsOfApp(approvals: ApprovalRecord[], appId: string): ApprovalRecord[];
49
+ /** A result it wants to keep, shown whole before it is (LOOP R-24). */
50
+ export declare function DraftShown({ draft }: {
51
+ draft: Draft;
52
+ }): JSX.Element;
48
53
  export declare function AppRulesCard({ app }: {
49
54
  app: AppSpec;
50
55
  }): JSX.Element;
@@ -4,6 +4,7 @@ import { Button, Heading, Label, Text } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
5
  import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
6
6
  import { BEHAVIOUR_WORDS, coverOf } from '../../apps/rules';
7
+ import { SAVE_WORDS, draftOfApproval } from '../../apps/saved';
7
8
  import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
8
9
  /** What the card says. */
9
10
  export const APP_RULES_WORDS = {
@@ -46,17 +47,35 @@ export function approvalsOfApp(approvals, appId) {
46
47
  return approvals.filter(approval => approval.agent_id === appId || approval.tool_args?._app === appId);
47
48
  }
48
49
  const PENDING = { status: 'pending' };
50
+ /** A result it wants to keep, shown whole before it is (LOOP R-24). */
51
+ export function DraftShown({ draft }) {
52
+ return (_jsxs(Box, { "data-testid": "app-draft", sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontSize: 0, color: 'fg.muted' }, children: SAVE_WORDS.where(draft.space) }), _jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: draft.title }), _jsx(Box, { as: "pre", sx: {
53
+ m: 0,
54
+ mt: 1,
55
+ p: 2,
56
+ maxHeight: 240,
57
+ overflow: 'auto',
58
+ whiteSpace: 'pre-wrap',
59
+ fontFamily: 'inherit',
60
+ fontSize: 0,
61
+ bg: 'canvas.subtle',
62
+ borderRadius: 2,
63
+ }, children: draft.content })] }));
64
+ }
49
65
  function Approvals({ app }) {
50
66
  const query = useToolApprovalsQuery(PENDING);
51
67
  const approve = useApproveToolRequest();
52
68
  const reject = useRejectToolRequest();
53
69
  const waiting = useMemo(() => approvalsOfApp(query.data?.approvals ?? [], app.id), [query.data, app.id]);
54
- return (_jsxs(Box, { "data-testid": "app-approvals", children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 1 }, children: APP_RULES_WORDS.waiting }), approve.connectionState !== 'connected' ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0, mb: 1 }, children: APP_RULES_WORDS.offline })) : null, waiting.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_RULES_WORDS.nothingWaiting })) : (_jsx(Box, { as: "ul", sx: { listStyle: 'none', p: 0, m: 0 }, children: waiting.map(approval => (_jsxs(Box, { as: "li", sx: {
55
- py: 2,
56
- borderTop: '1px solid',
57
- borderColor: 'border.muted',
58
- fontSize: 1,
59
- }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: approval.tool_name }), ruleOfApproval(approval) ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: ruleOfApproval(approval) })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 1 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approve.isPending, onClick: () => approve.mutate({ id: approval.id }), children: APP_RULES_WORDS.approve }), _jsx(Button, { size: "small", disabled: reject.isPending, onClick: () => reject.mutate({ id: approval.id }), children: APP_RULES_WORDS.reject })] })] }, approval.id))) }))] }));
70
+ return (_jsxs(Box, { "data-testid": "app-approvals", children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 1 }, children: APP_RULES_WORDS.waiting }), approve.connectionState !== 'connected' ? (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0, mb: 1 }, children: APP_RULES_WORDS.offline })) : null, waiting.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: APP_RULES_WORDS.nothingWaiting })) : (_jsx(Box, { as: "ul", sx: { listStyle: 'none', p: 0, m: 0 }, children: waiting.map(approval => {
71
+ const draft = draftOfApproval(approval);
72
+ return (_jsxs(Box, { as: "li", sx: {
73
+ py: 2,
74
+ borderTop: '1px solid',
75
+ borderColor: 'border.muted',
76
+ fontSize: 1,
77
+ }, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: approval.tool_name }), ruleOfApproval(approval) ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: ruleOfApproval(approval) })) : null, draft ? _jsx(DraftShown, { draft: draft }) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 1 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approve.isPending, onClick: () => approve.mutate({ id: approval.id }), children: draft ? SAVE_WORDS.approve : APP_RULES_WORDS.approve }), _jsx(Button, { size: "small", disabled: reject.isPending, onClick: () => reject.mutate({ id: approval.id }), children: draft ? SAVE_WORDS.decline : APP_RULES_WORDS.reject })] })] }, approval.id));
78
+ }) }))] }));
60
79
  }
61
80
  export function AppRulesCard({ app }) {
62
81
  const signedIn = Boolean(useIAMStore(state => state.token));
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { definePlugin } from '@datalayer/reactor';
3
+ import { ShieldCheckIcon } from '@primer/octicons-react';
3
4
  import { LoopSlots } from '../../core';
4
5
  import { AppRulesCard } from './AppRulesCard';
5
6
  export const APP_RULES_PLUGIN_NAME = '@datalayer/loop-plugin-app-rules';
@@ -21,6 +22,8 @@ export function defineAppRulesPlugin(app) {
21
22
  slot: LoopSlots.sidebar,
22
23
  order: 10,
23
24
  Component: Card,
25
+ // Its line icon on the workspace's rail (T-07).
26
+ rail: { label: 'Rules', icon: ShieldCheckIcon },
24
27
  },
25
28
  ],
26
29
  }),
@@ -28,7 +28,7 @@
28
28
  import type { JSX } from 'react';
29
29
  import { type AssistantState } from '../../../chat/assistant/state';
30
30
  import type { PresenceState } from '../../../chat/presence/presenceStatus';
31
- import { type ChatTurnSnapshot } from '../../core';
31
+ import { type ChatTurnSnapshot, type LoopWorkspaceContext } from '../../core';
32
32
  import type { BalloonDisplay } from '../../../chat/assistant/toolLine';
33
33
  export declare const LOOP_ASSISTANT_PLUGIN_NAME = "@datalayer/loop-plugin-assistant";
34
34
  export type LoopAssistantConfig = {
@@ -58,6 +58,11 @@ export type LoopAssistantConfig = {
58
58
  * (`current`). The Appspec's `interface.balloon`.
59
59
  */
60
60
  balloon?: BalloonDisplay;
61
+ /**
62
+ * The workspace it is drawn over (the root slot's): its agent and
63
+ * server, for *Agent Details…* and *Code Sandbox Details…*.
64
+ */
65
+ workspace?: LoopWorkspaceContext;
61
66
  };
62
67
  /**
63
68
  * What the character acts out (T-22), from what the application is doing as
@@ -68,6 +73,6 @@ export declare function assistantStateOfTurn(turn: ChatTurnSnapshot, presence: P
68
73
  arriving?: boolean;
69
74
  leaving?: boolean;
70
75
  }): AssistantState;
71
- export declare function LoopAssistant({ app, person, appId, decisions, balloon: displayGiven, }: LoopAssistantConfig): JSX.Element | null;
76
+ export declare function LoopAssistant({ app, person, appId, decisions, balloon: displayGiven, workspace, }: LoopAssistantConfig): JSX.Element | null;
72
77
  export declare const LoopAssistantPlugin: import("@datalayer/reactor").ReactorPlugin<LoopAssistantConfig, unknown, unknown>;
73
78
  export default LoopAssistantPlugin;