@datalayer/agent-runtimes 1.3.29 → 1.3.32
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/AgentNode.js +14 -33
- package/lib/chat/ChatFloating.js +40 -8
- package/lib/chat/assistant/AssistantStage.d.ts +26 -7
- package/lib/chat/assistant/AssistantStage.js +115 -8
- package/lib/chat/assistant/SpeechBalloon.js +7 -3
- package/lib/chat/assistant/characters.d.ts +32 -1
- package/lib/chat/assistant/characters.js +180 -15
- package/lib/chat/assistant/state.d.ts +28 -0
- package/lib/chat/assistant/state.js +31 -0
- package/lib/chat/base/ChatBase.js +74 -50
- package/lib/chat/base/modelChoice.d.ts +40 -1
- package/lib/chat/base/modelChoice.js +42 -15
- package/lib/chat/prompt/InputPrompt.d.ts +7 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptBase.js +1 -1
- package/lib/chat/prompt/menus/ModelSelector.d.ts +13 -1
- package/lib/chat/prompt/menus/ModelSelector.js +17 -5
- package/lib/chat/useViewportDrag.d.ts +9 -1
- package/lib/chat/useViewportDrag.js +4 -4
- package/lib/components/a2ui/index.d.ts +2 -0
- package/lib/components/a2ui/index.js +2 -0
- package/lib/components/a2ui/visibility.d.ts +15 -0
- package/lib/components/a2ui/visibility.js +24 -0
- package/lib/components/a2ui/visibleWhen.d.ts +17 -0
- package/lib/components/a2ui/visibleWhen.js +54 -0
- package/lib/examples/ChatAssistantExample.d.ts +4 -0
- package/lib/examples/ChatAssistantExample.js +26 -5
- package/lib/examples/utils/owlCharacterPlugin.d.ts +7 -0
- package/lib/examples/utils/owlCharacterPlugin.js +44 -0
- package/lib/loop/apps/AppFeedback.d.ts +27 -0
- package/lib/loop/apps/AppFeedback.js +78 -0
- package/lib/loop/apps/AppRenderer.d.ts +33 -1
- package/lib/loop/apps/AppRenderer.js +111 -29
- package/lib/loop/apps/agent.d.ts +15 -0
- package/lib/loop/apps/agent.js +42 -0
- package/lib/loop/apps/appspec.js +6 -3
- package/lib/loop/apps/checks.d.ts +10 -2
- package/lib/loop/apps/checks.js +21 -10
- package/lib/loop/apps/feedback.d.ts +51 -0
- package/lib/loop/apps/feedback.js +61 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/rules.d.ts +14 -0
- package/lib/loop/apps/rules.js +24 -0
- package/lib/loop/core/canvasBlocks.d.ts +16 -0
- package/lib/loop/core/canvasBlocks.js +13 -0
- package/lib/loop/core/index.d.ts +15 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +42 -0
- package/lib/loop/embed/AppEmbed.js +205 -0
- package/lib/loop/embed/datalayer-app.d.ts +1 -0
- package/lib/loop/embed/datalayer-app.js +16 -0
- package/lib/loop/embed/element.d.ts +57 -0
- package/lib/loop/embed/element.js +405 -0
- package/lib/loop/embed/embedConfig.d.ts +120 -0
- package/lib/loop/embed/embedConfig.js +148 -0
- package/lib/loop/embed/embedTheme.d.ts +24 -0
- package/lib/loop/embed/embedTheme.js +83 -0
- package/lib/loop/index.d.ts +6 -2
- package/lib/loop/index.js +8 -2
- package/lib/loop/plugins/a2ui/A2uiView.js +2 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +23 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +36 -9
- package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -1
- package/lib/loop/plugins/agents/switchable.d.ts +7 -0
- package/lib/loop/plugins/agents/switchable.js +91 -88
- package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
- package/lib/loop/plugins/app-page/AppPage.js +53 -0
- package/lib/loop/plugins/app-page/appPageModel.d.ts +180 -0
- package/lib/loop/plugins/app-page/appPageModel.js +443 -0
- package/lib/loop/plugins/app-page/index.d.ts +8 -0
- package/lib/loop/plugins/app-page/index.js +34 -0
- package/lib/loop/plugins/canvas-blocks/index.d.ts +63 -0
- package/lib/loop/plugins/canvas-blocks/index.js +118 -0
- package/lib/loop/plugins/chat/ChatView.js +52 -26
- package/lib/loop/plugins/chat/turnState.d.ts +5 -2
- package/lib/loop/plugins/chat/turnState.js +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
- package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
- package/lib/loop/plugins/page-layout/index.d.ts +13 -0
- package/lib/loop/plugins/page-layout/index.js +10 -1
- package/lib/loop/presets.d.ts +12 -0
- package/lib/loop/presets.js +5 -3
- package/lib/specs/actions.js +9 -0
- package/lib/specs/agents/agents.js +20 -4
- package/lib/specs/apps.d.ts +10 -1
- package/lib/specs/apps.js +1779 -154
- package/lib/specs/appspecSchema.js +14 -8
- package/lib/specs/models.d.ts +1 -1
- package/lib/specs/models.js +8 -8
- package/lib/specs/tools.d.ts +1 -0
- package/lib/specs/tools.js +19 -0
- package/lib/stories/loop/LoopReference.stories.d.ts +21 -0
- package/lib/stories/loop/LoopReference.stories.js +31 -0
- package/lib/stories/loop/ReferenceScreens.d.ts +53 -0
- package/lib/stories/loop/ReferenceScreens.js +256 -0
- package/lib/stories/loop/pictures-main.d.ts +1 -0
- package/lib/stories/loop/pictures-main.js +64 -0
- package/lib/types/agentspecs.d.ts +17 -1
- package/lib/types/chat.d.ts +5 -0
- package/lib/types/config.d.ts +20 -0
- package/package.json +10 -6
- package/scripts/check-cloudflare-models.py +2 -2
- package/scripts/codegen/generate_agents.py +50 -2
- package/scripts/codegen/generate_apps.py +38 -7
- package/scripts/codegen/generate_models.py +4 -4
|
@@ -16,15 +16,30 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* created with the application in its payload — so the runtime reaches only
|
|
17
17
|
* what the application connects to and enforces its rules before every tool
|
|
18
18
|
* call — and the application's starters as the openers of the empty chat;
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
19
|
+
* - its **layout**, as `interface.layout` says (`appLayoutOptions`, LOOP
|
|
20
|
+
* T-07), on the workspace's own `page-layout` plugin: `chat` is the
|
|
21
|
+
* conversation alone, with the A2UI surfaces its answers draw; `page` is
|
|
22
|
+
* the page on a sheet with the composer over it and the conversation in a
|
|
23
|
+
* panel the composer's display modes open; `split` is the conversation and
|
|
24
|
+
* the page side by side, a hairline between them to drag;
|
|
25
|
+
* - its **page**, when it has one (`hasAppPage`: a chat, a widget or a worker
|
|
26
|
+
* with a `page` or `split` layout): its A2UI surface drawn by the
|
|
27
|
+
* `app-page` plugin, in place of the notebook and the document, fed from
|
|
28
|
+
* the conversation and answered through it (`APP_KIND_PATHS`). A
|
|
29
|
+
* decision's page stays the Studio's run page (R-02); its layout keeps the
|
|
30
|
+
* editors;
|
|
31
|
+
* - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
|
|
32
|
+
* window, its title the application's face and name.
|
|
21
33
|
*
|
|
22
34
|
* @module loop/apps/AppRenderer
|
|
23
35
|
*/
|
|
24
36
|
import { useMemo } from 'react';
|
|
25
37
|
import { defineAgentCapacityPlugin } from '../plugins/agent-capacity';
|
|
38
|
+
import { APP_PAGE_SURFACE, defineAppPagePlugin, hasAppPage, } from '../plugins/app-page';
|
|
26
39
|
import { LoopEmbed } from '../embed/LoopEmbed';
|
|
27
40
|
import { dumpAppspec } from './appspec';
|
|
41
|
+
import { defineAppFeedbackPlugin } from './AppFeedback';
|
|
42
|
+
import { keepsFeedback } from './feedback';
|
|
28
43
|
/** The id of an agent or a Cog, without its version. */
|
|
29
44
|
export const agentIdOf = (app) => {
|
|
30
45
|
const reference = app.agent || app.team;
|
|
@@ -64,8 +79,75 @@ export function defineAppPlugin(app) {
|
|
|
64
79
|
text: starter.label,
|
|
65
80
|
message: starter.message,
|
|
66
81
|
})),
|
|
82
|
+
// In the page, the agent is told what a runtime tells it: its own
|
|
83
|
+
// spec's prompt, then the application's instructions; on the
|
|
84
|
+
// application's model when it names one (`loop/apps/agent`).
|
|
85
|
+
instructions: app.instructions,
|
|
86
|
+
model: app.model,
|
|
67
87
|
});
|
|
68
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The workspace an application's layout asks for (LOOP T-07).
|
|
91
|
+
*
|
|
92
|
+
* - `chat`: the conversation alone — no editor, no page, no strip.
|
|
93
|
+
* - `page`: the page layout — its page (or, without one, the editors) on the
|
|
94
|
+
* sheet; the composer a card over it, starting at the bottom, whose display
|
|
95
|
+
* modes put it in the conversation panel beside, over or in the corner of
|
|
96
|
+
* the page; the current turn under the composer, its copy and dismiss only.
|
|
97
|
+
* - `split`: the page layout's split — the conversation on the left with its
|
|
98
|
+
* composer, the page on the right, a hairline between them to drag.
|
|
99
|
+
*
|
|
100
|
+
* With a page, the page is the one editor, opened, without a strip to swap
|
|
101
|
+
* it; without one (a decision, whose page is the Studio's), the editors stay.
|
|
102
|
+
*/
|
|
103
|
+
export function appLayoutOptions(app) {
|
|
104
|
+
const layout = app.interface.layout;
|
|
105
|
+
if (layout === 'chat') {
|
|
106
|
+
return { editors: false, showViewSelector: false };
|
|
107
|
+
}
|
|
108
|
+
const withPage = hasAppPage(app);
|
|
109
|
+
return {
|
|
110
|
+
editors: !withPage,
|
|
111
|
+
showViewSelector: !withPage,
|
|
112
|
+
...(withPage ? { defaultEditor: APP_PAGE_SURFACE } : {}),
|
|
113
|
+
pageLayout: true,
|
|
114
|
+
pageLayoutArrangement: layout === 'split' ? 'split' : 'page',
|
|
115
|
+
pageLayoutPrompt: 'floating',
|
|
116
|
+
pageLayoutPromptAnchor: 'bottom',
|
|
117
|
+
pageLayoutTurnPanelFooter: 'actions',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* What an application's agent is created with on a Datalayer runtime: under
|
|
122
|
+
* the application's id, over AG-UI, with the application's own document —
|
|
123
|
+
* so that the runtime registers it and decides every tool call by its rules
|
|
124
|
+
* (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
|
|
125
|
+
* place it runs: the workspace (`AppRenderer`) and the embed's floating
|
|
126
|
+
* modes (`AppEmbed`).
|
|
127
|
+
*/
|
|
128
|
+
export function appDatalayerCreatePayload(app, instance) {
|
|
129
|
+
return {
|
|
130
|
+
// Under the application's id, over AG-UI: what the workspace's
|
|
131
|
+
// chat addresses (`AppRenderer` gives it as `agentId`).
|
|
132
|
+
name: app.id,
|
|
133
|
+
transport: 'ag-ui',
|
|
134
|
+
agent_spec_id: agentIdOf(app),
|
|
135
|
+
app_spec: dumpAppspec(app),
|
|
136
|
+
// Code Mode calls every tool through `execute_code`, which an
|
|
137
|
+
// application without a shell is refused: its tools are called
|
|
138
|
+
// one by one instead, each decided by its rules.
|
|
139
|
+
enable_codemode: Boolean(app.permissions?.computer?.shell),
|
|
140
|
+
...(instance
|
|
141
|
+
? {
|
|
142
|
+
app_instance: {
|
|
143
|
+
app_uid: instance.appUid ?? '',
|
|
144
|
+
deployment_uid: instance.deploymentUid ?? '',
|
|
145
|
+
version: instance.version ?? 0,
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
69
151
|
/**
|
|
70
152
|
* An application in a LOOP workspace: its agent, its starters, its layout.
|
|
71
153
|
*
|
|
@@ -76,8 +158,7 @@ export function defineAppPlugin(app) {
|
|
|
76
158
|
/** The agentspec a runtime is allocated with before an application's agent is created on it. */
|
|
77
159
|
export const DATALAYER_BOOTSTRAP_AGENTSPEC = 'example-simple';
|
|
78
160
|
const NO_PLUGINS = [];
|
|
79
|
-
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, ...embed }) {
|
|
80
|
-
const chatOnly = app.interface.layout === 'chat';
|
|
161
|
+
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, ...embed }) {
|
|
81
162
|
/*
|
|
82
163
|
* The application plugin, made once per application. `LoopEmbed` rebuilds
|
|
83
164
|
* its whole reactor when its plugins change, so a new plugin on every
|
|
@@ -88,36 +169,33 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
|
|
|
88
169
|
const appPlugin = useMemo(() => (app.agent ? defineAppPlugin(app) : null),
|
|
89
170
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
171
|
[source]);
|
|
91
|
-
const
|
|
172
|
+
const withPage = hasAppPage(app);
|
|
173
|
+
const pagePlugin = useMemo(() => (withPage ? defineAppPagePlugin(app) : null),
|
|
174
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
175
|
+
[source, withPage]);
|
|
176
|
+
/*
|
|
177
|
+
* A thumb and a comment on each answer, kept in its record (LOOP V-18):
|
|
178
|
+
* only for an application whose record keeps feedback.
|
|
179
|
+
*/
|
|
180
|
+
const keeps = keepsFeedback(app);
|
|
181
|
+
const feedbackPlugin = useMemo(() => (keeps ? defineAppFeedbackPlugin(app) : null),
|
|
182
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
+
[source, keeps]);
|
|
184
|
+
const allPlugins = useMemo(() => appPlugin
|
|
185
|
+
? [
|
|
186
|
+
appPlugin,
|
|
187
|
+
...(pagePlugin ? [pagePlugin] : []),
|
|
188
|
+
...(feedbackPlugin ? [feedbackPlugin] : []),
|
|
189
|
+
...plugins,
|
|
190
|
+
]
|
|
191
|
+
: plugins, [appPlugin, pagePlugin, feedbackPlugin, plugins]);
|
|
92
192
|
/*
|
|
93
193
|
* On Datalayer, the application runs on a runtime: allocated with a plain
|
|
94
194
|
* agentspec, its agent created there with the application's spec — so that
|
|
95
195
|
* the runtime registers it and decides every tool call by its rules (LOOP
|
|
96
196
|
* R-03, R-05), as `loop apps run --cloud` does.
|
|
97
197
|
*/
|
|
98
|
-
const datalayerCreatePayload = useMemo(() => app.agent
|
|
99
|
-
? {
|
|
100
|
-
// Under the application's id, over AG-UI: what the workspace's
|
|
101
|
-
// chat addresses (`agentId` below).
|
|
102
|
-
name: app.id,
|
|
103
|
-
transport: 'ag-ui',
|
|
104
|
-
agent_spec_id: agentIdOf(app),
|
|
105
|
-
app_spec: dumpAppspec(app),
|
|
106
|
-
// Code Mode calls every tool through `execute_code`, which an
|
|
107
|
-
// application without a shell is refused: its tools are called
|
|
108
|
-
// one by one instead, each decided by its rules.
|
|
109
|
-
enable_codemode: Boolean(app.permissions?.computer?.shell),
|
|
110
|
-
...(instance
|
|
111
|
-
? {
|
|
112
|
-
app_instance: {
|
|
113
|
-
app_uid: instance.appUid ?? '',
|
|
114
|
-
deployment_uid: instance.deploymentUid ?? '',
|
|
115
|
-
version: instance.version ?? 0,
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
: {}),
|
|
119
|
-
}
|
|
120
|
-
: undefined,
|
|
198
|
+
const datalayerCreatePayload = useMemo(() => (app.agent ? appDatalayerCreatePayload(app, instance) : undefined),
|
|
121
199
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
200
|
[source, instance?.appUid, instance?.deploymentUid, instance?.version]);
|
|
123
201
|
if (!appPlugin) {
|
|
@@ -129,7 +207,11 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
|
|
|
129
207
|
, {
|
|
130
208
|
// The application's own agent: created under its id, so that its
|
|
131
209
|
// spec is applied to an agent of its own, never to the one it extends.
|
|
132
|
-
agentId: app.id,
|
|
210
|
+
agentId: app.id, ...appLayoutOptions(app), ...(frame
|
|
211
|
+
? {
|
|
212
|
+
frameTitle: [app.emoji, app.name].filter(Boolean).join(' '),
|
|
213
|
+
}
|
|
214
|
+
: {}), teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
133
215
|
// Where an application runs is decided by its host — the Studio's
|
|
134
216
|
// Preview, the hosted page — not offered to its user.
|
|
135
217
|
targetFixed: true,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Agentspec } from '../../types/agentspecs';
|
|
2
|
+
/** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
|
|
3
|
+
export declare function resolveAgentspec(reference: string): Agentspec | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* What the agent is told: its spec's system prompt, then what the
|
|
6
|
+
* application tells it on top — as the runtime joins them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function agentInstructions(spec: Pick<Agentspec, 'systemPrompt'> | undefined, extra?: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Why an agent turned in the page cannot run, or undefined when it can.
|
|
11
|
+
*
|
|
12
|
+
* An agent named by a reference that resolves to nothing would answer with
|
|
13
|
+
* no instructions — bare — which is not the agent that was asked for.
|
|
14
|
+
*/
|
|
15
|
+
export declare function inPageAgentRefusal(reference: string | undefined, spec: Agentspec | undefined): string | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The agent an application runs, as the page turns it (LOOP H-01, E-11).
|
|
7
|
+
*
|
|
8
|
+
* On a runtime, an application's agent is its agent's spec — an agent of the
|
|
9
|
+
* catalogue, or a Cog's, which is an agent equipped with Frames, their
|
|
10
|
+
* context already in its system prompt — and the application's own
|
|
11
|
+
* instructions on top of it (`get_library_agent_spec` and the create route
|
|
12
|
+
* in `agent_runtimes/routes/agents.py`). The page turns the same agent in the
|
|
13
|
+
* browser, so it resolves it the same way: an application on a Cog that the
|
|
14
|
+
* page looked up among the agentspecs alone ran with no instructions at all.
|
|
15
|
+
*
|
|
16
|
+
* @module loop/apps/agent
|
|
17
|
+
*/
|
|
18
|
+
import { getAgentspecs } from '../../specs/agents';
|
|
19
|
+
import { getCog } from '../../specs/cogs';
|
|
20
|
+
/** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
|
|
21
|
+
export function resolveAgentspec(reference) {
|
|
22
|
+
return getAgentspecs(reference) ?? getCog(reference)?.spec;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What the agent is told: its spec's system prompt, then what the
|
|
26
|
+
* application tells it on top — as the runtime joins them.
|
|
27
|
+
*/
|
|
28
|
+
export function agentInstructions(spec, extra) {
|
|
29
|
+
const parts = [spec?.systemPrompt?.trim(), extra?.trim()].filter((part) => Boolean(part));
|
|
30
|
+
return parts.length > 0 ? parts.join('\n\n') : undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Why an agent turned in the page cannot run, or undefined when it can.
|
|
34
|
+
*
|
|
35
|
+
* An agent named by a reference that resolves to nothing would answer with
|
|
36
|
+
* no instructions — bare — which is not the agent that was asked for.
|
|
37
|
+
*/
|
|
38
|
+
export function inPageAgentRefusal(reference, spec) {
|
|
39
|
+
return reference && !spec
|
|
40
|
+
? `There is no agent or Cog named “${reference}” in this page, so it cannot answer here.`
|
|
41
|
+
: undefined;
|
|
42
|
+
}
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -105,6 +105,7 @@ export function emptyAppspec(kind = 'chat') {
|
|
|
105
105
|
keepFor: DEFAULT_KEEP_FOR,
|
|
106
106
|
retentionDays: retentionDays(DEFAULT_KEEP_FOR),
|
|
107
107
|
include: [...DEFAULT_RECORD_INCLUDE],
|
|
108
|
+
suggestTests: false,
|
|
108
109
|
},
|
|
109
110
|
checks: { guards: [], gates: [], track: '' },
|
|
110
111
|
deployment: {},
|
|
@@ -243,7 +244,7 @@ function parseDecision(data) {
|
|
|
243
244
|
name: text(scenario.name),
|
|
244
245
|
weights: Object.fromEntries(Object.entries(isData(scenario.weights) ? scenario.weights : {}).filter((entry) => typeof entry[1] === 'number')),
|
|
245
246
|
})),
|
|
246
|
-
|
|
247
|
+
decisionModel: text(data.decision_model),
|
|
247
248
|
};
|
|
248
249
|
}
|
|
249
250
|
function parseDeployment(data) {
|
|
@@ -359,6 +360,7 @@ export function parseAppspec(document) {
|
|
|
359
360
|
include: Array.isArray(record.include)
|
|
360
361
|
? texts(record.include)
|
|
361
362
|
: [...DEFAULT_RECORD_INCLUDE],
|
|
363
|
+
suggestTests: record.suggest_tests === true,
|
|
362
364
|
},
|
|
363
365
|
checks: {
|
|
364
366
|
guards: texts(checks.guards),
|
|
@@ -493,7 +495,7 @@ function dumpDecision(decision) {
|
|
|
493
495
|
.text('name', scenario.name, '\u0000')
|
|
494
496
|
// By criterion name, in alphabetical order: the spec declares no order.
|
|
495
497
|
.part('weights', sorted(scenario.weights)).data))
|
|
496
|
-
.text('
|
|
498
|
+
.text('decision_model', decision.decisionModel).data;
|
|
497
499
|
}
|
|
498
500
|
/**
|
|
499
501
|
* An application as the document its file holds.
|
|
@@ -554,7 +556,8 @@ export function dumpAppspec(app) {
|
|
|
554
556
|
}))).data)
|
|
555
557
|
.part('record', new Writer()
|
|
556
558
|
.text('keep_for', app.record.keepFor, DEFAULT_KEEP_FOR)
|
|
557
|
-
.list('include', app.record.include, DEFAULT_RECORD_INCLUDE)
|
|
559
|
+
.list('include', app.record.include, DEFAULT_RECORD_INCLUDE)
|
|
560
|
+
.value('suggest_tests', app.record.suggestTests, false).data)
|
|
558
561
|
.part('checks', new Writer()
|
|
559
562
|
.list('guards', app.checks.guards)
|
|
560
563
|
.list('gates', app.checks.gates)
|
|
@@ -16,8 +16,16 @@ export interface AppCheck {
|
|
|
16
16
|
export declare const componentNamed: (name: string) => ComponentSpec | undefined;
|
|
17
17
|
/** The Guards the runtime executes (agent-runtimes `loop/apps/guards.py`). */
|
|
18
18
|
export declare const EXECUTED_GUARDS: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* Where an application is checked: the plugins its organization has turned
|
|
21
|
+
* off (catalogue ids, `plugins_off` in IAM), none when it has decided nothing
|
|
22
|
+
* or the checks run with no organization.
|
|
23
|
+
*/
|
|
24
|
+
export interface CheckContext {
|
|
25
|
+
pluginsOff: readonly string[];
|
|
26
|
+
}
|
|
19
27
|
/** The instant checks of an application as an editor holds it. */
|
|
20
|
-
export declare function checkApp(app: AppSpec, read?: string[]): AppCheck;
|
|
28
|
+
export declare function checkApp(app: AppSpec, read?: string[], context?: CheckContext): AppCheck;
|
|
21
29
|
/**
|
|
22
30
|
* What the document says in a shape the spec refuses — a list where a list is
|
|
23
31
|
* not, a word where a choice is — which the tolerant reader replaces with a
|
|
@@ -25,4 +33,4 @@ export declare function checkApp(app: AppSpec, read?: string[]): AppCheck;
|
|
|
25
33
|
*/
|
|
26
34
|
export declare function documentShapeProblems(document: unknown): string[];
|
|
27
35
|
/** The instant checks of an application's document, what reading it found included. */
|
|
28
|
-
export declare function checkAppspec(document: unknown): AppCheck;
|
|
36
|
+
export declare function checkAppspec(document: unknown, context?: CheckContext): AppCheck;
|
package/lib/loop/apps/checks.js
CHANGED
|
@@ -38,6 +38,7 @@ import { getSkillSpec } from '../../specs/skills';
|
|
|
38
38
|
import { getTeamSpec } from '../../specs/teams';
|
|
39
39
|
import { getToolSpec } from '../../specs/tools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
|
+
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
41
42
|
import { parseAppspec } from './appspec';
|
|
42
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
43
44
|
export const NOT_READY = 'Not ready';
|
|
@@ -192,14 +193,14 @@ function referenceProblems(app) {
|
|
|
192
193
|
problems.push(`The surface's “${String(node.id)}” is a “${String(node.component)}”, which the catalog does not have.`);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
const model = getModel(
|
|
196
|
+
const decider = app.decision?.decisionModel;
|
|
197
|
+
if (decider) {
|
|
198
|
+
const model = getModel(decider);
|
|
198
199
|
if (!model) {
|
|
199
|
-
problems.push(`There is no model named “${
|
|
200
|
+
problems.push(`There is no model named “${decider}” to decide with.`);
|
|
200
201
|
}
|
|
201
|
-
else if (!(model.capabilities ?? []).includes('
|
|
202
|
-
problems.push(`The model “${
|
|
202
|
+
else if (!(model.capabilities ?? []).includes('decisions')) {
|
|
203
|
+
problems.push(`The model “${decider}” does not answer typed decisions.`);
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
const run = new Set(app.checks.guards.map(idOf));
|
|
@@ -336,10 +337,16 @@ function setupNotes(app) {
|
|
|
336
337
|
note('notification', ref, getNotificationSpec(idOf(ref)));
|
|
337
338
|
return setup;
|
|
338
339
|
}
|
|
340
|
+
const NO_ORGANIZATION = { pluginsOff: [] };
|
|
339
341
|
/** The instant checks of an application as an editor holds it. */
|
|
340
|
-
export function checkApp(app, read = []) {
|
|
342
|
+
export function checkApp(app, read = [], context = NO_ORGANIZATION) {
|
|
341
343
|
const problems = [...read, ...shapeProblems(app), ...referenceProblems(app)];
|
|
342
|
-
|
|
344
|
+
// A block its page uses from a plugin its organization turned off is said
|
|
345
|
+
// with the rest of what is not enabled today (C-12).
|
|
346
|
+
const setup = [
|
|
347
|
+
...setupNotes(app),
|
|
348
|
+
...pluginsOffSetupNotes(app, context.pluginsOff),
|
|
349
|
+
];
|
|
343
350
|
if (problems.length > 0) {
|
|
344
351
|
return { verdict: NOT_READY, problems, attention: [], setup };
|
|
345
352
|
}
|
|
@@ -540,6 +547,10 @@ export function documentShapeProblems(document) {
|
|
|
540
547
|
else
|
|
541
548
|
record.include.forEach((item, index) => oneOf(item, ENUMS.record, `record.include.${index}`));
|
|
542
549
|
}
|
|
550
|
+
if (record.suggest_tests !== undefined &&
|
|
551
|
+
typeof record.suggest_tests !== 'boolean') {
|
|
552
|
+
at('record.suggest_tests', 'is true or false');
|
|
553
|
+
}
|
|
543
554
|
});
|
|
544
555
|
mapping(d.checks, 'checks', checks => {
|
|
545
556
|
texts(checks.guards, 'checks.guards');
|
|
@@ -596,7 +607,7 @@ export function documentShapeProblems(document) {
|
|
|
596
607
|
return problems;
|
|
597
608
|
}
|
|
598
609
|
/** The instant checks of an application's document, what reading it found included. */
|
|
599
|
-
export function checkAppspec(document) {
|
|
610
|
+
export function checkAppspec(document, context = NO_ORGANIZATION) {
|
|
600
611
|
const { app, problems } = parseAppspec(document);
|
|
601
|
-
return checkApp(app, [...problems, ...documentShapeProblems(document)]);
|
|
612
|
+
return checkApp(app, [...problems, ...documentShapeProblems(document)], context);
|
|
602
613
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback from the people who use an application (LOOP V-18): a thumb up or
|
|
3
|
+
* down on an answer, and a comment, kept in the application's record.
|
|
4
|
+
*
|
|
5
|
+
* Kept only when the application's `record.include` names `feedback` — its
|
|
6
|
+
* builder says what its record keeps, and the chat offers nothing it would
|
|
7
|
+
* not keep. Sent to the runtime the conversation runs on
|
|
8
|
+
* (`POST /api/v1/apps/feedback`), which writes it through the recorder of
|
|
9
|
+
* that conversation, under its session: the conversation's AG-UI thread.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no React; the network is the `fetch` it is handed.
|
|
12
|
+
*
|
|
13
|
+
* @module loop/apps/feedback
|
|
14
|
+
*/
|
|
15
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
16
|
+
/** The longest comment kept, as the runtime holds it (`COMMENT_LIMIT`). */
|
|
17
|
+
export declare const FEEDBACK_COMMENT_LIMIT = 2000;
|
|
18
|
+
/** What a person says of an answer. */
|
|
19
|
+
export type Feedback = {
|
|
20
|
+
/** The conversation, as the runtime knows it. */
|
|
21
|
+
session: string;
|
|
22
|
+
liked: boolean;
|
|
23
|
+
comment: string;
|
|
24
|
+
};
|
|
25
|
+
/** What the feedback strip says, in a person's words. */
|
|
26
|
+
export declare const FEEDBACK_WORDS: {
|
|
27
|
+
readonly ask: "Was this answer useful?";
|
|
28
|
+
readonly liked: "Useful";
|
|
29
|
+
readonly disliked: "Not useful";
|
|
30
|
+
readonly comment: "Anything to add? (optional)";
|
|
31
|
+
readonly send: "Send";
|
|
32
|
+
readonly kept: (app: string) => string;
|
|
33
|
+
readonly where: (app: string) => string;
|
|
34
|
+
readonly tooLong: "A comment is at most 2000 characters.";
|
|
35
|
+
readonly noSession: "This conversation has no name the application knows yet: send a message first.";
|
|
36
|
+
};
|
|
37
|
+
/** Whether an application keeps feedback: its record names it. */
|
|
38
|
+
export declare const keepsFeedback: (app: Pick<AppSpec, "record">) => boolean;
|
|
39
|
+
/** Where feedback is sent, on the runtime an application runs on. */
|
|
40
|
+
export declare const feedbackEndpoint: (agentBaseUrl: string) => string;
|
|
41
|
+
/** Why feedback cannot be sent as it is; nothing when it can. */
|
|
42
|
+
export declare function feedbackProblems(feedback: Feedback): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Send feedback to the runtime, and say what it kept: the entry, in a
|
|
45
|
+
* sentence. A refusal throws the runtime's own sentence.
|
|
46
|
+
*/
|
|
47
|
+
export declare function sendFeedback(feedback: Feedback, { agentBaseUrl, token, fetcher, }: {
|
|
48
|
+
agentBaseUrl: string;
|
|
49
|
+
token?: string | null;
|
|
50
|
+
fetcher?: typeof fetch;
|
|
51
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** The longest comment kept, as the runtime holds it (`COMMENT_LIMIT`). */
|
|
6
|
+
export const FEEDBACK_COMMENT_LIMIT = 2000;
|
|
7
|
+
/** What the feedback strip says, in a person's words. */
|
|
8
|
+
export const FEEDBACK_WORDS = {
|
|
9
|
+
ask: 'Was this answer useful?',
|
|
10
|
+
liked: 'Useful',
|
|
11
|
+
disliked: 'Not useful',
|
|
12
|
+
comment: 'Anything to add? (optional)',
|
|
13
|
+
send: 'Send',
|
|
14
|
+
kept: (app) => `Thank you. Kept in ${app}’s record.`,
|
|
15
|
+
where: (app) => `What you say is kept with this conversation in ${app}’s record, for its builder to read.`,
|
|
16
|
+
tooLong: `A comment is at most ${FEEDBACK_COMMENT_LIMIT} characters.`,
|
|
17
|
+
noSession: 'This conversation has no name the application knows yet: send a message first.',
|
|
18
|
+
};
|
|
19
|
+
/** Whether an application keeps feedback: its record names it. */
|
|
20
|
+
export const keepsFeedback = (app) => (app.record?.include ?? []).includes('feedback');
|
|
21
|
+
/** Where feedback is sent, on the runtime an application runs on. */
|
|
22
|
+
export const feedbackEndpoint = (agentBaseUrl) => `${agentBaseUrl.replace(/\/+$/, '')}/api/v1/apps/feedback`;
|
|
23
|
+
/** Why feedback cannot be sent as it is; nothing when it can. */
|
|
24
|
+
export function feedbackProblems(feedback) {
|
|
25
|
+
const problems = [];
|
|
26
|
+
if (!feedback.session.trim()) {
|
|
27
|
+
problems.push(FEEDBACK_WORDS.noSession);
|
|
28
|
+
}
|
|
29
|
+
if (feedback.comment.trim().length > FEEDBACK_COMMENT_LIMIT) {
|
|
30
|
+
problems.push(FEEDBACK_WORDS.tooLong);
|
|
31
|
+
}
|
|
32
|
+
return problems;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Send feedback to the runtime, and say what it kept: the entry, in a
|
|
36
|
+
* sentence. A refusal throws the runtime's own sentence.
|
|
37
|
+
*/
|
|
38
|
+
export async function sendFeedback(feedback, { agentBaseUrl, token, fetcher = fetch, }) {
|
|
39
|
+
const problems = feedbackProblems(feedback);
|
|
40
|
+
if (problems.length) {
|
|
41
|
+
throw new Error(problems.join(' '));
|
|
42
|
+
}
|
|
43
|
+
const response = await fetcher(feedbackEndpoint(agentBaseUrl), {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
session: feedback.session,
|
|
51
|
+
liked: feedback.liked,
|
|
52
|
+
comment: feedback.comment.trim(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
const body = (await response.json().catch(() => ({})));
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const detail = typeof body.detail === 'string' ? body.detail : `${response.status}`;
|
|
58
|
+
throw new Error(`The feedback was not kept: ${detail}`);
|
|
59
|
+
}
|
|
60
|
+
return String(body.summary ?? '');
|
|
61
|
+
}
|
package/lib/loop/apps/index.d.ts
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module loop/apps
|
|
5
5
|
*/
|
|
6
|
+
export * from './agent';
|
|
7
|
+
export * from './AppFeedback';
|
|
6
8
|
export * from './AppRenderer';
|
|
7
9
|
export * from './appspec';
|
|
8
10
|
export * from './checks';
|
|
11
|
+
export * from './feedback';
|
|
9
12
|
export * from './rules';
|
|
10
13
|
export * from './yaml';
|
package/lib/loop/apps/index.js
CHANGED
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module loop/apps
|
|
9
9
|
*/
|
|
10
|
+
export * from './agent';
|
|
11
|
+
export * from './AppFeedback';
|
|
10
12
|
export * from './AppRenderer';
|
|
11
13
|
export * from './appspec';
|
|
12
14
|
export * from './checks';
|
|
15
|
+
export * from './feedback';
|
|
13
16
|
export * from './rules';
|
|
14
17
|
export * from './yaml';
|
package/lib/loop/apps/rules.d.ts
CHANGED
|
@@ -39,6 +39,20 @@ export declare function classesOf(ref: string, args?: ToolArguments): ActionClas
|
|
|
39
39
|
export declare const isReadOnly: (classes: ActionClass[]) => boolean;
|
|
40
40
|
/** The most restricted of several behaviours. */
|
|
41
41
|
export declare const strictest: (behaviours: AppBehaviour[]) => AppBehaviour;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the application is given a tool of a server: its connection's
|
|
44
|
+
* level (LOOP U-15). A connection gives the tools of its server it reaches
|
|
45
|
+
* (`only`); one that only reads gives only the tools that only read — a tool
|
|
46
|
+
* nobody classed is taken to write, so a read connection does not give it.
|
|
47
|
+
* A tool of a server the application is not connected to is not given.
|
|
48
|
+
*
|
|
49
|
+
* The runtime gives its agent only these (`AppRulesCapability.prepare_tools`,
|
|
50
|
+
* through `agent_runtimes.loop.apps.rules.gives`, its Python twin).
|
|
51
|
+
*
|
|
52
|
+
* @throws When `tool` names no server: a catalogue tool is given by the
|
|
53
|
+
* spec's `tools`, not by a connection.
|
|
54
|
+
*/
|
|
55
|
+
export declare function gives(app: Pick<AppSpec, 'connections'>, tool: string): boolean;
|
|
42
56
|
/** What `behaviourFor` may be told about the call. */
|
|
43
57
|
export interface BehaviourOptions {
|
|
44
58
|
/** The arguments of the call; without them the decision is for the worst the tool can do. */
|
package/lib/loop/apps/rules.js
CHANGED
|
@@ -161,6 +161,30 @@ export const strictest = (behaviours) => behaviours.reduce((worst, behaviour) =>
|
|
|
161
161
|
const connectionTo = (app, server) => app.connections.find(connection => idOf(connection.server) === server);
|
|
162
162
|
const reaches = (connection, tool) => connection.only.length === 0 ||
|
|
163
163
|
connection.only.some(pattern => matchesPattern(tool, pattern));
|
|
164
|
+
/**
|
|
165
|
+
* Whether the application is given a tool of a server: its connection's
|
|
166
|
+
* level (LOOP U-15). A connection gives the tools of its server it reaches
|
|
167
|
+
* (`only`); one that only reads gives only the tools that only read — a tool
|
|
168
|
+
* nobody classed is taken to write, so a read connection does not give it.
|
|
169
|
+
* A tool of a server the application is not connected to is not given.
|
|
170
|
+
*
|
|
171
|
+
* The runtime gives its agent only these (`AppRulesCapability.prepare_tools`,
|
|
172
|
+
* through `agent_runtimes.loop.apps.rules.gives`, its Python twin).
|
|
173
|
+
*
|
|
174
|
+
* @throws When `tool` names no server: a catalogue tool is given by the
|
|
175
|
+
* spec's `tools`, not by a connection.
|
|
176
|
+
*/
|
|
177
|
+
export function gives(app, tool) {
|
|
178
|
+
const [server, name] = splitRef(tool);
|
|
179
|
+
if (server === undefined) {
|
|
180
|
+
throw new Error(`\`${tool}\` is not a tool of a server (\`server.tool\`).`);
|
|
181
|
+
}
|
|
182
|
+
const connection = connectionTo(app, server);
|
|
183
|
+
if (!connection || !reaches(connection, name)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
return (connection.access !== 'read' || isReadOnly(classesOf(`${server}.${name}`)));
|
|
187
|
+
}
|
|
164
188
|
const isClass = (target) => Object.prototype.hasOwnProperty.call(DEFAULT_BEHAVIOURS, target);
|
|
165
189
|
/** What a rule applies to, versions aside. */
|
|
166
190
|
const normal = (target) => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ComponentSpec } from '../../types/agentspecs';
|
|
2
|
+
/**
|
|
3
|
+
* A block the Canvas may place (LOOP C-12): one component of the catalog of
|
|
4
|
+
* visual components (C-13), contributed by the plugin of the UI plugin that
|
|
5
|
+
* renders it. The Canvas's palette is what the enabled plugins contribute
|
|
6
|
+
* here, and nothing else: a plugin disabled takes its blocks off it, and an
|
|
7
|
+
* application that uses one of them says so in its setup notes.
|
|
8
|
+
*/
|
|
9
|
+
export type CanvasBlockContribution = {
|
|
10
|
+
/** The name a surface gives it, the component's id (e.g. 'Table'). */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The UI plugin of the catalogue that renders it (e.g. 'a2ui'). */
|
|
13
|
+
uiPlugin: string;
|
|
14
|
+
component: ComponentSpec;
|
|
15
|
+
};
|
|
16
|
+
export declare const LoopCanvasBlock: import("@datalayer/reactor").ContributionPoint<CanvasBlockContribution>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Canvas's palette as a contribution point (LOOP C-12), re-exported by
|
|
7
|
+
* `loop/core`: kept apart so that reading the palette loads the reactor and
|
|
8
|
+
* the catalogue, and nothing of the workspace.
|
|
9
|
+
*
|
|
10
|
+
* @module loop/core/canvasBlocks
|
|
11
|
+
*/
|
|
12
|
+
import { defineContributionPoint } from '@datalayer/reactor';
|
|
13
|
+
export const LoopCanvasBlock = defineContributionPoint('loop.canvas.block');
|
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -483,6 +483,14 @@ export type AgentBlueprintContribution = {
|
|
|
483
483
|
specId?: string;
|
|
484
484
|
/** Extra fields merged into the server's create-agent payload. */
|
|
485
485
|
createPayload?: Record<string, unknown>;
|
|
486
|
+
/**
|
|
487
|
+
* What the agent is told on top of its spec's system prompt — an
|
|
488
|
+
* application's instructions — when the page turns it, as a runtime
|
|
489
|
+
* appends them.
|
|
490
|
+
*/
|
|
491
|
+
instructions?: string;
|
|
492
|
+
/** The model it runs on in the page, in place of its spec's. */
|
|
493
|
+
model?: string;
|
|
486
494
|
};
|
|
487
495
|
export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
|
|
488
496
|
/** One conversation opener a plugin offers on the empty chat. */
|
|
@@ -518,6 +526,7 @@ export type AssistantCharacterContribution = {
|
|
|
518
526
|
character: AssistantCharacter | AssistantCharacterData;
|
|
519
527
|
};
|
|
520
528
|
export declare const LoopAssistantCharacter: import("@datalayer/reactor").ContributionPoint<AssistantCharacterContribution>;
|
|
529
|
+
export { LoopCanvasBlock, type CanvasBlockContribution } from './canvasBlocks';
|
|
521
530
|
/**
|
|
522
531
|
* The per-example chat extras a host feeds the loop's conversation live.
|
|
523
532
|
*
|
|
@@ -927,6 +936,12 @@ export type ChatTurnSnapshot = {
|
|
|
927
936
|
user?: string;
|
|
928
937
|
/** What the agent has said so far. */
|
|
929
938
|
assistant?: string;
|
|
939
|
+
/**
|
|
940
|
+
* The conversation the turn belongs to, as the runtime knows it (AG-UI's
|
|
941
|
+
* thread) — what an application's record keeps its session under, and
|
|
942
|
+
* what feedback on it names (LOOP V-18). Absent when the chat does not say.
|
|
943
|
+
*/
|
|
944
|
+
thread?: string;
|
|
930
945
|
status: ChatTurnStatus;
|
|
931
946
|
/**
|
|
932
947
|
* The context window as the agent last reported it — what a turn footer
|
package/lib/loop/core/index.js
CHANGED
|
@@ -142,6 +142,9 @@ export const LoopChatHeader = defineContributionPoint('loop.chat.header');
|
|
|
142
142
|
export const LoopAgentBlueprint = defineContributionPoint('loop.agent.blueprint');
|
|
143
143
|
export const LoopChatSuggestion = defineContributionPoint('loop.chat.suggestion');
|
|
144
144
|
export const LoopAssistantCharacter = defineContributionPoint('loop.assistant.character');
|
|
145
|
+
// A block the Canvas may place (LOOP C-12), in a module of its own so that
|
|
146
|
+
// the palette is read without the rest of the contracts.
|
|
147
|
+
export { LoopCanvasBlock } from './canvasBlocks';
|
|
145
148
|
export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
|
|
146
149
|
export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
|
|
147
150
|
export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
|