@datalayer/agent-runtimes 1.3.33 → 1.3.35
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/loop/apps/appspec.d.ts +9 -2
- package/lib/loop/apps/appspec.js +14 -8
- package/lib/loop/apps/checks.js +4 -3
- package/lib/loop/plugins/assistant/index.d.ts +38 -0
- package/lib/loop/plugins/assistant/index.js +140 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +25 -0
- package/lib/loop/plugins/assistant-characters/index.js +29 -1
- package/lib/specs/appspecSchema.js +5 -8
- package/lib/types/agentspecs.d.ts +10 -4
- package/package.json +1 -1
|
@@ -33,8 +33,15 @@ export declare const APP_KINDS: AppKind[];
|
|
|
33
33
|
export declare const APP_ACCENTS: AppAccent[];
|
|
34
34
|
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
35
35
|
export declare const APP_EMBED_MODES: AppEmbedMode[];
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* How an Appspec names the character of its floating assistant (LOOP T-24):
|
|
38
|
+
* the id a plugin contributes it under, `paperclip` or `acme-owl` — as
|
|
39
|
+
* agentspecs' `ASSISTANT_CHARACTER_ID`. Which ids exist is what the enabled
|
|
40
|
+
* plugins contribute (`assistantCharacterNamed`), not the spec's to say.
|
|
41
|
+
*/
|
|
42
|
+
export declare const APP_ASSISTANT_CHARACTER_ID: RegExp;
|
|
43
|
+
/** Whether a value is shaped as a character id an Appspec may name. */
|
|
44
|
+
export declare function isAssistantCharacterId(value: unknown): value is AppAssistantCharacter;
|
|
38
45
|
/** The layout a kind starts with. */
|
|
39
46
|
export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
|
|
40
47
|
/** What an application keeps of what it did, when it does not say. */
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -23,13 +23,19 @@ export const APP_EMBED_MODES = [
|
|
|
23
23
|
'panel',
|
|
24
24
|
'assistant',
|
|
25
25
|
];
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]
|
|
26
|
+
/**
|
|
27
|
+
* How an Appspec names the character of its floating assistant (LOOP T-24):
|
|
28
|
+
* the id a plugin contributes it under, `paperclip` or `acme-owl` — as
|
|
29
|
+
* agentspecs' `ASSISTANT_CHARACTER_ID`. Which ids exist is what the enabled
|
|
30
|
+
* plugins contribute (`assistantCharacterNamed`), not the spec's to say.
|
|
31
|
+
*/
|
|
32
|
+
export const APP_ASSISTANT_CHARACTER_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
33
|
+
/** Whether a value is shaped as a character id an Appspec may name. */
|
|
34
|
+
export function isAssistantCharacterId(value) {
|
|
35
|
+
return (typeof value === 'string' &&
|
|
36
|
+
value.length <= 64 &&
|
|
37
|
+
APP_ASSISTANT_CHARACTER_ID.test(value));
|
|
38
|
+
}
|
|
33
39
|
const ACTION_CLASS_NAMES = [
|
|
34
40
|
'read',
|
|
35
41
|
'write',
|
|
@@ -218,7 +224,7 @@ function parseInterface(data, kind) {
|
|
|
218
224
|
if (isData(data.surface)) {
|
|
219
225
|
parsed.surface = parseSurface(data.surface);
|
|
220
226
|
}
|
|
221
|
-
if (
|
|
227
|
+
if (isAssistantCharacterId(data.assistant)) {
|
|
222
228
|
parsed.assistant = data.assistant;
|
|
223
229
|
}
|
|
224
230
|
return parsed;
|
package/lib/loop/apps/checks.js
CHANGED
|
@@ -39,7 +39,7 @@ import { getTeamSpec } from '../../specs/teams';
|
|
|
39
39
|
import { getToolSpec } from '../../specs/tools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
41
|
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
42
|
-
import { parseAppspec } from './appspec';
|
|
42
|
+
import { isAssistantCharacterId, parseAppspec } from './appspec';
|
|
43
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
44
44
|
export const NOT_READY = 'Not ready';
|
|
45
45
|
export const NEEDS_ATTENTION = 'Needs attention';
|
|
@@ -378,7 +378,6 @@ const ENUMS = {
|
|
|
378
378
|
],
|
|
379
379
|
visibility: ['private', 'invited', 'organization', 'link', 'public'],
|
|
380
380
|
mode: ['inline', 'bubble', 'panel', 'assistant'],
|
|
381
|
-
character: ['paperclip', 'wizard', 'cat', 'eyes'],
|
|
382
381
|
trigger: ['schedule', 'event', 'once'],
|
|
383
382
|
criterion: ['metric', 'noul', 'choice', 'score'],
|
|
384
383
|
direction: ['higher', 'lower'],
|
|
@@ -506,7 +505,9 @@ export function documentShapeProblems(document) {
|
|
|
506
505
|
mapping(d.interface, 'interface', ui => {
|
|
507
506
|
oneOf(ui.layout, ENUMS.layout, 'interface.layout');
|
|
508
507
|
oneOf(ui.accent, ENUMS.accent, 'interface.accent');
|
|
509
|
-
|
|
508
|
+
if (ui.assistant !== undefined && !isAssistantCharacterId(ui.assistant)) {
|
|
509
|
+
at('interface.assistant', 'names a character by the id a plugin contributes it under, lowercase words joined by a hyphen: `paperclip`, `acme-owl`');
|
|
510
|
+
}
|
|
510
511
|
text(ui.welcome, 'interface.welcome');
|
|
511
512
|
texts(ui.components, 'interface.components');
|
|
512
513
|
records(ui.starters, 'interface.starters', (item, where) => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The floating assistant in the LOOP workspace (LOOP T-24): the character a
|
|
3
|
+
* page draws over its workspace, chosen from what the enabled plugins
|
|
4
|
+
* contribute to `loop.assistant.character` — the application's own
|
|
5
|
+
* (`interface.assistant`) when its Appspec names one, else the person's
|
|
6
|
+
* choice from their settings, else the paper clip. It acts out the chat's
|
|
7
|
+
* turn (T-22), says the agent's newest words in its balloon while the
|
|
8
|
+
* conversation is out of sight (T-23), opens the conversation when clicked,
|
|
9
|
+
* is dragged about and sent away as the chat's own assistant is (T-27).
|
|
10
|
+
*
|
|
11
|
+
* The workspace's chat is the conversation: on a page layout, its side panel
|
|
12
|
+
* is opened and closed; where the conversation is always on screen, a click
|
|
13
|
+
* takes the person to its composer.
|
|
14
|
+
*
|
|
15
|
+
* @module loop/plugins/assistant
|
|
16
|
+
*/
|
|
17
|
+
import type { JSX } from 'react';
|
|
18
|
+
import { type AssistantState } from '../../../chat/assistant/state';
|
|
19
|
+
import { type ChatTurnSnapshot } from '../../core';
|
|
20
|
+
export declare const LOOP_ASSISTANT_PLUGIN_NAME = "@datalayer/loop-plugin-assistant";
|
|
21
|
+
export type LoopAssistantConfig = {
|
|
22
|
+
/** The character the application's Appspec names; it wins. */
|
|
23
|
+
app?: string;
|
|
24
|
+
/** The character the person chose in their settings. */
|
|
25
|
+
person?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* What the character acts out, from the workspace's chat turn (T-22): it
|
|
29
|
+
* thinks while the agent thinks, works while a tool runs (the turn's
|
|
30
|
+
* `activity`), speaks while the answer is written, and idles otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assistantStateOfTurn(turn: ChatTurnSnapshot, moment?: {
|
|
33
|
+
arriving?: boolean;
|
|
34
|
+
leaving?: boolean;
|
|
35
|
+
}): AssistantState;
|
|
36
|
+
export declare function LoopAssistant({ app, person, }: LoopAssistantConfig): JSX.Element | null;
|
|
37
|
+
export declare const LoopAssistantPlugin: import("@datalayer/reactor").ReactorPlugin<LoopAssistantConfig, unknown, unknown>;
|
|
38
|
+
export default LoopAssistantPlugin;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { definePlugin, signal } from '@datalayer/reactor';
|
|
4
|
+
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
5
|
+
import { Text } from '@primer/react';
|
|
6
|
+
import { Box } from '@datalayer/primer-addons';
|
|
7
|
+
import { pageLayoutPanelOpen, togglePagePanel, } from '@datalayer/primer-addons/lib/reactor';
|
|
8
|
+
import { AssistantStage } from '../../../chat/assistant/AssistantStage';
|
|
9
|
+
import { assistantStateOf, keepAway, keptAway, latestSaying, } from '../../../chat/assistant/state';
|
|
10
|
+
import { useViewportDrag } from '../../../chat/useViewportDrag';
|
|
11
|
+
import { LoopAssistantCharacter, LoopChatLayout, LoopChatTurn, LoopSlots, } from '../../core';
|
|
12
|
+
import { assistantCharacterFor } from '../assistant-characters';
|
|
13
|
+
export const LOOP_ASSISTANT_PLUGIN_NAME = '@datalayer/loop-plugin-assistant';
|
|
14
|
+
/** The character's size, in pixels, as the chat's own assistant. */
|
|
15
|
+
const SIZE = 88;
|
|
16
|
+
/* A signal to read when no chat contributed a turn: the hook needs one. */
|
|
17
|
+
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
18
|
+
/**
|
|
19
|
+
* What the character acts out, from the workspace's chat turn (T-22): it
|
|
20
|
+
* thinks while the agent thinks, works while a tool runs (the turn's
|
|
21
|
+
* `activity`), speaks while the answer is written, and idles otherwise.
|
|
22
|
+
*/
|
|
23
|
+
export function assistantStateOfTurn(turn, moment = {}) {
|
|
24
|
+
const busy = turn.status === 'thinking' || turn.status === 'streaming';
|
|
25
|
+
const presence = !busy
|
|
26
|
+
? 'idle'
|
|
27
|
+
: turn.activity
|
|
28
|
+
? 'working'
|
|
29
|
+
: 'thinking';
|
|
30
|
+
return assistantStateOf(presence, {
|
|
31
|
+
...moment,
|
|
32
|
+
speaking: turn.status === 'streaming' && !turn.activity,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Take the person to the workspace's composer. */
|
|
36
|
+
function focusComposer() {
|
|
37
|
+
const composer = document.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"]');
|
|
38
|
+
composer?.focus();
|
|
39
|
+
}
|
|
40
|
+
export function LoopAssistant({ app, person, }) {
|
|
41
|
+
const contributed = useContributions(LoopAssistantCharacter).map(entry => entry.value);
|
|
42
|
+
const chosen = assistantCharacterFor(contributed, { app, person });
|
|
43
|
+
const turnEntries = useContributions(LoopChatTurn);
|
|
44
|
+
const turn = useSignalValue(turnEntries[0]?.value.turn ?? NO_TURN);
|
|
45
|
+
// On a page layout the conversation is its side panel; elsewhere it is
|
|
46
|
+
// always on screen.
|
|
47
|
+
const onPageLayout = useContributions(LoopChatLayout).some(entry => entry.value.id === 'page-layout');
|
|
48
|
+
const panelOpen = useSignalValue(pageLayoutPanelOpen);
|
|
49
|
+
const open = onPageLayout && panelOpen;
|
|
50
|
+
const [away, setAway] = useState(keptAway);
|
|
51
|
+
const [arriving, setArriving] = useState(true);
|
|
52
|
+
const [leaving, setLeaving] = useState(false);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const timer = setTimeout(() => setArriving(false), 1800);
|
|
55
|
+
return () => clearTimeout(timer);
|
|
56
|
+
}, []);
|
|
57
|
+
/* The agent's newest words, said in the balloon until heard (T-23). */
|
|
58
|
+
const saying = turn.assistant
|
|
59
|
+
? latestSaying([
|
|
60
|
+
{ id: String(turn.id), role: 'assistant', content: turn.assistant },
|
|
61
|
+
])
|
|
62
|
+
: undefined;
|
|
63
|
+
const [heardId, setHeardId] = useState();
|
|
64
|
+
const [fresh, setFresh] = useState(false);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!saying || open) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
setFresh(true);
|
|
70
|
+
const timer = setTimeout(() => setFresh(false), 12000);
|
|
71
|
+
return () => clearTimeout(timer);
|
|
72
|
+
}, [saying?.id, saying?.text, open]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (open && saying) {
|
|
75
|
+
setHeardId(saying.id);
|
|
76
|
+
}
|
|
77
|
+
}, [open, saying?.id]);
|
|
78
|
+
const stageRef = useRef(null);
|
|
79
|
+
const drag = useViewportDrag(stageRef, { whole: true });
|
|
80
|
+
if (away !== 'none') {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if ('problem' in chosen) {
|
|
84
|
+
// Not replaced by another: what was named is said, where it would stand.
|
|
85
|
+
return (_jsx(Box, { role: "status", "data-assistant-problem": chosen.saidBy, sx: {
|
|
86
|
+
position: 'fixed',
|
|
87
|
+
right: 24,
|
|
88
|
+
bottom: 24,
|
|
89
|
+
zIndex: 1000,
|
|
90
|
+
maxWidth: 320,
|
|
91
|
+
p: 2,
|
|
92
|
+
borderRadius: 2,
|
|
93
|
+
border: '1px solid',
|
|
94
|
+
borderColor: 'attention.muted',
|
|
95
|
+
bg: 'attention.subtle',
|
|
96
|
+
boxShadow: 'shadow.medium',
|
|
97
|
+
}, children: _jsx(Text, { sx: { fontSize: 1 }, children: chosen.problem }) }));
|
|
98
|
+
}
|
|
99
|
+
const state = assistantStateOfTurn(turn, { arriving, leaving });
|
|
100
|
+
const unheard = !!saying && saying.id !== heardId;
|
|
101
|
+
const balloon = unheard && saying
|
|
102
|
+
? { text: saying.text, more: saying.more }
|
|
103
|
+
: { text: 'Click me to open the conversation.' };
|
|
104
|
+
const insist = state === 'greeting' || (unheard && (state === 'speaking' || fresh));
|
|
105
|
+
const onToggle = () => {
|
|
106
|
+
if (onPageLayout) {
|
|
107
|
+
togglePagePanel();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
focusComposer();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const onDismiss = (next) => {
|
|
114
|
+
keepAway(next);
|
|
115
|
+
setLeaving(true);
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
setLeaving(false);
|
|
118
|
+
setAway(next);
|
|
119
|
+
}, 600);
|
|
120
|
+
};
|
|
121
|
+
return (_jsx(AssistantStage, { character: chosen.character, state: state, size: SIZE, place: drag.position
|
|
122
|
+
? { left: `${drag.position.left}px`, top: `${drag.position.top}px` }
|
|
123
|
+
: { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, insist: insist, onDismiss: onDismiss }));
|
|
124
|
+
}
|
|
125
|
+
export const LoopAssistantPlugin = definePlugin({
|
|
126
|
+
name: LOOP_ASSISTANT_PLUGIN_NAME,
|
|
127
|
+
config: { app: undefined, person: undefined },
|
|
128
|
+
displayName: 'Floating assistant',
|
|
129
|
+
description: 'The chat as a character on the page: the application’s own, or the one the person chose, from the characters the enabled plugins contribute.',
|
|
130
|
+
octicon: 'paperclip',
|
|
131
|
+
build: ({ config }) => {
|
|
132
|
+
const Configured = () => (_jsx(LoopAssistant, { app: config.app, person: config.person }));
|
|
133
|
+
return {
|
|
134
|
+
components: [
|
|
135
|
+
{ id: 'loop-assistant', slot: LoopSlots.root, Component: Configured },
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
export default LoopAssistantPlugin;
|
|
@@ -27,4 +27,29 @@ export declare function assistantCharactersOf(reactor: ContributionsReader): {
|
|
|
27
27
|
* enabled plugin contributes is an error that says which ids there are.
|
|
28
28
|
*/
|
|
29
29
|
export declare function assistantCharacterNamed(reactor: ContributionsReader, id: string): AssistantCharacter | AssistantCharacterData;
|
|
30
|
+
/** Whose choice the character drawn is (T-24). */
|
|
31
|
+
export type AssistantCharacterSaidBy = 'app' | 'person' | 'default';
|
|
32
|
+
/** The character drawn, or why none can be. */
|
|
33
|
+
export type AssistantCharacterChosen = {
|
|
34
|
+
id: string;
|
|
35
|
+
character: AssistantCharacter | AssistantCharacterData;
|
|
36
|
+
saidBy: AssistantCharacterSaidBy;
|
|
37
|
+
} | {
|
|
38
|
+
problem: string;
|
|
39
|
+
saidBy: AssistantCharacterSaidBy;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The character the floating assistant draws on a page (T-24): the one the
|
|
43
|
+
* application's Appspec names (`interface.assistant`), which wins; else the
|
|
44
|
+
* one the person chose in their settings; else the paper clip — each looked
|
|
45
|
+
* up in what the enabled plugins contribute. An id no enabled plugin gives
|
|
46
|
+
* is not replaced by another: it is said, with the ids there are.
|
|
47
|
+
*/
|
|
48
|
+
export declare function assistantCharacterFor(contributed: ReadonlyArray<{
|
|
49
|
+
id: string;
|
|
50
|
+
character: AssistantCharacter | AssistantCharacterData;
|
|
51
|
+
}>, choice: {
|
|
52
|
+
app?: string;
|
|
53
|
+
person?: string;
|
|
54
|
+
}): AssistantCharacterChosen;
|
|
30
55
|
export default AssistantCharactersPlugin;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @module loop/plugins/assistant-characters
|
|
11
11
|
*/
|
|
12
12
|
import { contribution, definePlugin, } from '@datalayer/reactor';
|
|
13
|
-
import { ASSISTANT_CHARACTERS, } from '../../../chat/assistant/characters';
|
|
13
|
+
import { ASSISTANT_CHARACTERS, DEFAULT_ASSISTANT_CHARACTER, } from '../../../chat/assistant/characters';
|
|
14
14
|
import { LoopAssistantCharacter, } from '../../core';
|
|
15
15
|
export const ASSISTANT_CHARACTERS_PLUGIN_NAME = '@datalayer/loop-plugin-assistant-characters';
|
|
16
16
|
export const AssistantCharactersPlugin = definePlugin({
|
|
@@ -38,4 +38,32 @@ export function assistantCharacterNamed(reactor, id) {
|
|
|
38
38
|
}
|
|
39
39
|
return found.character;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The character the floating assistant draws on a page (T-24): the one the
|
|
43
|
+
* application's Appspec names (`interface.assistant`), which wins; else the
|
|
44
|
+
* one the person chose in their settings; else the paper clip — each looked
|
|
45
|
+
* up in what the enabled plugins contribute. An id no enabled plugin gives
|
|
46
|
+
* is not replaced by another: it is said, with the ids there are.
|
|
47
|
+
*/
|
|
48
|
+
export function assistantCharacterFor(contributed, choice) {
|
|
49
|
+
const saidBy = choice.app
|
|
50
|
+
? 'app'
|
|
51
|
+
: choice.person
|
|
52
|
+
? 'person'
|
|
53
|
+
: 'default';
|
|
54
|
+
const id = choice.app ?? choice.person ?? DEFAULT_ASSISTANT_CHARACTER;
|
|
55
|
+
const found = contributed.find(entry => entry.id === id);
|
|
56
|
+
if (found) {
|
|
57
|
+
return { id, character: found.character, saidBy };
|
|
58
|
+
}
|
|
59
|
+
const whose = saidBy === 'app'
|
|
60
|
+
? 'This application names'
|
|
61
|
+
: saidBy === 'person'
|
|
62
|
+
? 'Your settings name'
|
|
63
|
+
: 'The default is';
|
|
64
|
+
return {
|
|
65
|
+
saidBy,
|
|
66
|
+
problem: `${whose} the character “${id}”, which nothing enabled here draws; the characters are ${contributed.map(entry => entry.id).join(', ') || 'none'}.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
41
69
|
export default AssistantCharactersPlugin;
|
|
@@ -314,14 +314,17 @@ export const APPSPEC_SCHEMA = {
|
|
|
314
314
|
assistant: {
|
|
315
315
|
anyOf: [
|
|
316
316
|
{
|
|
317
|
-
|
|
317
|
+
maxLength: 64,
|
|
318
|
+
pattern: '^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$',
|
|
319
|
+
type: 'string',
|
|
318
320
|
},
|
|
319
321
|
{
|
|
320
322
|
type: 'null',
|
|
321
323
|
},
|
|
322
324
|
],
|
|
323
325
|
default: null,
|
|
324
|
-
description:
|
|
326
|
+
description: "The character its floating assistant shows, by the id a plugin contributes it under (lowercase letters and digits, words joined by a hyphen): Datalayer's are `paperclip`, `wizard`, `cat` and `eyes`. The paper clip when unsaid; an id no enabled plugin contributes is refused where the plugins are known, the runtime and the page. Said here, it wins over a person's own choice in their settings",
|
|
327
|
+
title: 'Assistant',
|
|
325
328
|
},
|
|
326
329
|
},
|
|
327
330
|
title: 'AppInterface',
|
|
@@ -677,12 +680,6 @@ export const APPSPEC_SCHEMA = {
|
|
|
677
680
|
title: 'AppTrigger',
|
|
678
681
|
type: 'object',
|
|
679
682
|
},
|
|
680
|
-
AssistantCharacter: {
|
|
681
|
-
description: "The character an application's floating assistant shows (LOOP T-24):\none of those a UI plugin contributes. Datalayer's are these four.",
|
|
682
|
-
enum: ['paperclip', 'wizard', 'cat', 'eyes'],
|
|
683
|
-
title: 'AssistantCharacter',
|
|
684
|
-
type: 'string',
|
|
685
|
-
},
|
|
686
683
|
Behaviour: {
|
|
687
684
|
description: 'What an application does when it meets an action: the four a person chooses from.',
|
|
688
685
|
enum: ['do_it', 'if_asked', 'ask_first', 'leave_to_me'],
|
|
@@ -384,10 +384,13 @@ export type AppLayout = 'chat' | 'page' | 'split';
|
|
|
384
384
|
/** The one colour of an application; everything else is neutral. */
|
|
385
385
|
export type AppAccent = 'green' | 'rose' | 'sky' | 'lime' | 'sun' | 'violet';
|
|
386
386
|
/**
|
|
387
|
-
* The character an application's floating assistant shows (LOOP T-24):
|
|
388
|
-
*
|
|
387
|
+
* The character an application's floating assistant shows (LOOP T-24): the id
|
|
388
|
+
* an enabled plugin contributes it under to `loop.assistant.character` —
|
|
389
|
+
* Datalayer's are `paperclip`, `wizard`, `cat` and `eyes`. The spec checks
|
|
390
|
+
* its shape (`APP_ASSISTANT_CHARACTER_ID`); whether a plugin gives it is
|
|
391
|
+
* known only where the plugins are.
|
|
389
392
|
*/
|
|
390
|
-
export type AppAssistantCharacter =
|
|
393
|
+
export type AppAssistantCharacter = string;
|
|
391
394
|
/** How an application sits in another product's page (LOOP D-07). */
|
|
392
395
|
export type AppEmbedMode = 'inline' | 'bubble' | 'panel' | 'assistant';
|
|
393
396
|
/** Something an application reaches. */
|
|
@@ -456,7 +459,10 @@ export interface AppInterfaceSpec {
|
|
|
456
459
|
/** The components of the catalog the surface may use. */
|
|
457
460
|
components: string[];
|
|
458
461
|
surface?: AppSurfaceSpec;
|
|
459
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* The character its floating assistant shows; the paper clip when unsaid.
|
|
464
|
+
* Said, it wins over the one the person chose in their settings.
|
|
465
|
+
*/
|
|
460
466
|
assistant?: AppAssistantCharacter;
|
|
461
467
|
}
|
|
462
468
|
export interface AppTestCaseSpec {
|