@datalayer/agent-runtimes 1.5.42 → 1.5.44
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/dist-embed/chunks/1f30a-DxwAt2Ba.js +1 -0
- package/dist-embed/chunks/1f310-AqUM9a8q.js +1 -0
- package/dist-embed/chunks/1f324-8MHLOhTj.js +1 -0
- package/dist-embed/chunks/1f326-YSBr3aXI.js +1 -0
- package/dist-embed/chunks/1f327-Ie-nQBNO.js +1 -0
- package/dist-embed/chunks/1f33e-Cfl2WM63.js +1 -0
- package/dist-embed/chunks/1f372-3XLDlXkE.js +1 -0
- package/dist-embed/chunks/1f373-BPUJt2bp.js +1 -0
- package/dist-embed/chunks/1f39b-CSNtbAXr.js +1 -0
- package/dist-embed/chunks/1f3ab-BUCNQHz1.js +1 -0
- package/dist-embed/chunks/1f41b-Dre1z0gP.js +1 -0
- package/dist-embed/chunks/1f441-D8yah4C4.js +1 -0
- package/dist-embed/chunks/1f465-CPle6Hjq.js +1 -0
- package/dist-embed/chunks/1f4b9-aacFyckt.js +1 -0
- package/dist-embed/chunks/1f4be-DPAcAU4R.js +1 -0
- package/dist-embed/chunks/1f4c1-Ch6aJ0cj.js +1 -0
- package/dist-embed/chunks/1f4c2-Dx7BKmLO.js +1 -0
- package/dist-embed/chunks/1f4c4-CNvjvC5k.js +1 -0
- package/dist-embed/chunks/1f4d3-GTxRgjv7.js +1 -0
- package/dist-embed/chunks/1f4d6-COOZY-Q_.js +1 -0
- package/dist-embed/chunks/1f4dc-BhoW4cjD.js +1 -0
- package/dist-embed/chunks/1f4e1-DezU8ow-.js +1 -0
- package/dist-embed/chunks/1f4e2-DsStZsI-.js +1 -0
- package/dist-embed/chunks/1f4e7-BBi7c_zx.js +1 -0
- package/dist-embed/chunks/1f500-D9xRo-IT.js +1 -0
- package/dist-embed/chunks/1f501-CzKeDPcw.js +1 -0
- package/dist-embed/chunks/1f504-BYIHMoDP.js +1 -0
- package/dist-embed/chunks/1f52d-2QxCLKhU.js +1 -0
- package/dist-embed/chunks/1f552-4d0Y0kn7.js +1 -0
- package/dist-embed/chunks/1f58b-Cp8F_HBg.js +1 -0
- package/dist-embed/chunks/1f5b2-D6BUULuh.js +1 -0
- package/dist-embed/chunks/1f5c3-DjkgJLaz.js +1 -0
- package/dist-embed/chunks/1f5d1-C4Kpe4HL.js +1 -0
- package/dist-embed/chunks/1f5dc-DyLO7aB9.js +1 -0
- package/dist-embed/chunks/1f604-CEekcaYE.js +1 -0
- package/dist-embed/chunks/1f917-CyyS9fgf.js +1 -0
- package/dist-embed/chunks/1f9d1-200d-2696-D9eSB-0q.js +1 -0
- package/dist-embed/chunks/1fa79-DHhIZbWB.js +1 -0
- package/dist-embed/chunks/1fa9d-DUI9e77m.js +1 -0
- package/dist-embed/chunks/1fa9f-10ALsMaD.js +1 -0
- package/dist-embed/chunks/23f3-ASjqFsbT.js +1 -0
- package/dist-embed/chunks/2611-BnnmLm-K.js +1 -0
- package/dist-embed/chunks/2702-CfLyp35r.js +1 -0
- package/dist-embed/chunks/270f-CLwo30mk.js +1 -0
- package/dist-embed/chunks/2728-Qg6be-6p.js +1 -0
- package/dist-embed/chunks/2b1c-Uc1aRwAf.js +1 -0
- package/dist-embed/datalayer-app-main.js +67 -67
- package/lib/apps/apps/sessionRules.d.ts +14 -1
- package/lib/apps/apps/sessionRules.js +68 -2
- package/lib/apps/core/canvasParts.d.ts +7 -1
- package/lib/apps/plugins/app-rules/AppRulesCard.js +2 -2
- package/lib/apps/plugins/canvas-parts/index.d.ts +9 -1
- package/lib/apps/plugins/canvas-parts/index.js +28 -0
- package/lib/chat/ChatInline.js +43 -22
- package/lib/chat/base/ChatBase.js +43 -2
- package/lib/components/agents/AgentSummary.d.ts +2 -24
- package/lib/components/agents/AgentSummary.js +3 -19
- package/lib/hooks/useChat.js +110 -144
- package/lib/portable/activity.d.ts +78 -0
- package/lib/portable/activity.js +129 -0
- package/lib/portable/fixtures.d.ts +7 -0
- package/lib/portable/fixtures.js +50 -0
- package/lib/portable/index.d.ts +4 -1
- package/lib/portable/index.js +7 -1
- package/lib/portable/lifecycle.d.ts +77 -0
- package/lib/portable/lifecycle.js +135 -0
- package/lib/portable/runtimes.d.ts +68 -0
- package/lib/portable/runtimes.js +69 -0
- package/lib/voice/VoiceMenu.d.ts +2 -1
- package/lib/voice/VoiceMenu.js +2 -2
- package/lib/voice/chatVoice.d.ts +5 -2
- package/lib/voice/chatVoice.js +10 -4
- package/lib/voice/choices.d.ts +2 -0
- package/lib/voice/choices.js +6 -0
- package/lib/voice/index.d.ts +1 -0
- package/lib/voice/index.js +1 -0
- package/lib/voice/sayAloud.d.ts +25 -0
- package/lib/voice/sayAloud.js +34 -0
- package/lib/voice/serverHearing.d.ts +34 -2
- package/lib/voice/serverHearing.js +234 -6
- package/lib/voice/settings.d.ts +12 -0
- package/lib/voice/settings.js +9 -0
- package/package.json +1 -1
|
@@ -61,8 +61,21 @@ export declare const SESSION_RULES_WORDS: {
|
|
|
61
61
|
readonly goneWithSession: "In force at once, for this session only; gone when it ends.";
|
|
62
62
|
readonly unavailable: (sentence: string) => string;
|
|
63
63
|
};
|
|
64
|
-
/**
|
|
64
|
+
/** Where a file rule holds, in words (SHIELD decision 5). */
|
|
65
|
+
export declare const WHERE_WORDS: Record<'inside' | 'outside', string>;
|
|
66
|
+
/**
|
|
67
|
+
* A rule in words: *writing, sending: leave it to me*; with decision 5's file
|
|
68
|
+
* and shell wording when it says where or what (SHIELD SH-43) — *writing
|
|
69
|
+
* inside the workspace (src/**): do it*, *running `git push`: ask me first*.
|
|
70
|
+
*/
|
|
65
71
|
export declare function sessionRuleInWords(rule: SessionRule): string;
|
|
72
|
+
/**
|
|
73
|
+
* Decision 5's file and shell rules, offered on a running session in the
|
|
74
|
+
* Shield's own words: each one a rule a person adds as it stands, and the
|
|
75
|
+
* runtime refuses one that would loosen what the organization says
|
|
76
|
+
* (SHIELD §6.2, §6.3).
|
|
77
|
+
*/
|
|
78
|
+
export declare const FILE_AND_SHELL_RULES: readonly SessionRule[];
|
|
66
79
|
/** What the person typed, as the rule the runtime takes; a sentence when it cannot be one. */
|
|
67
80
|
export declare function sessionRuleOf(input: {
|
|
68
81
|
name: string;
|
|
@@ -54,12 +54,78 @@ export const SESSION_RULES_WORDS = {
|
|
|
54
54
|
goneWithSession: 'In force at once, for this session only; gone when it ends.',
|
|
55
55
|
unavailable: (sentence) => `Nothing is decided right now: ${sentence}`,
|
|
56
56
|
};
|
|
57
|
-
/**
|
|
57
|
+
/** Where a file rule holds, in words (SHIELD decision 5). */
|
|
58
|
+
export const WHERE_WORDS = {
|
|
59
|
+
inside: 'inside the workspace',
|
|
60
|
+
outside: 'outside the workspace',
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A rule in words: *writing, sending: leave it to me*; with decision 5's file
|
|
64
|
+
* and shell wording when it says where or what (SHIELD SH-43) — *writing
|
|
65
|
+
* inside the workspace (src/**): do it*, *running `git push`: ask me first*.
|
|
66
|
+
*/
|
|
58
67
|
export function sessionRuleInWords(rule) {
|
|
59
68
|
const classes = (rule.classes ?? []).map(item => SESSION_RULES_WORDS.classes[item] ?? item);
|
|
60
|
-
|
|
69
|
+
let covers = classes.length > 0 ? classes.join(', ') : 'everything';
|
|
70
|
+
if (rule.where === 'inside' || rule.where === 'outside') {
|
|
71
|
+
covers = `${covers} ${WHERE_WORDS[rule.where]}`;
|
|
72
|
+
}
|
|
73
|
+
if (rule.paths && rule.paths.length > 0) {
|
|
74
|
+
covers = `${covers} (${rule.paths.join(', ')})`;
|
|
75
|
+
}
|
|
76
|
+
if (rule.commands && rule.commands.length > 0) {
|
|
77
|
+
covers = `${covers} matching ${rule.commands.map(command => `\`${command}\``).join(' or ')}`;
|
|
78
|
+
}
|
|
61
79
|
return `${covers}: ${SESSION_RULES_WORDS.decisions[rule.decision]}`;
|
|
62
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Decision 5's file and shell rules, offered on a running session in the
|
|
83
|
+
* Shield's own words: each one a rule a person adds as it stands, and the
|
|
84
|
+
* runtime refuses one that would loosen what the organization says
|
|
85
|
+
* (SHIELD §6.2, §6.3).
|
|
86
|
+
*/
|
|
87
|
+
export const FILE_AND_SHELL_RULES = [
|
|
88
|
+
{
|
|
89
|
+
name: 'Edit inside the workspace',
|
|
90
|
+
decision: 'allow',
|
|
91
|
+
classes: ['write'],
|
|
92
|
+
where: 'inside',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Ask before deleting a file',
|
|
96
|
+
decision: 'ask',
|
|
97
|
+
classes: ['delete'],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'Ask before running a command',
|
|
101
|
+
decision: 'ask',
|
|
102
|
+
classes: ['execute'],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Leave files outside the workspace to me',
|
|
106
|
+
decision: 'deny',
|
|
107
|
+
classes: ['read', 'write', 'delete'],
|
|
108
|
+
where: 'outside',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Ask before git push',
|
|
112
|
+
decision: 'ask',
|
|
113
|
+
classes: ['execute'],
|
|
114
|
+
commands: ['^git\\s+push\\b'],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'Ask before installing a package',
|
|
118
|
+
decision: 'ask',
|
|
119
|
+
classes: ['execute'],
|
|
120
|
+
commands: ['\\b(pip|npm|uv|yarn|apt(-get)?)\\s+(install|add)\\b'],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Leave sudo to me',
|
|
124
|
+
decision: 'deny',
|
|
125
|
+
classes: ['execute'],
|
|
126
|
+
commands: ['^sudo\\b'],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
63
129
|
/** What the person typed, as the rule the runtime takes; a sentence when it cannot be one. */
|
|
64
130
|
export function sessionRuleOf(input) {
|
|
65
131
|
const name = input.name.trim();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The kinds of part an agent is built from on the Canvas. */
|
|
2
|
-
export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
|
|
2
|
+
export type CanvasPartKind = 'agent' | 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
|
|
3
3
|
/**
|
|
4
4
|
* A part of an agent the Canvas may place (LOOP C-20): a connection to a
|
|
5
5
|
* server of the catalogue, a skill, a model, a rule, a test, an event, a
|
|
@@ -18,6 +18,12 @@ export type CanvasPartContribution = {
|
|
|
18
18
|
/** One line of what it is. */
|
|
19
19
|
says: string;
|
|
20
20
|
emoji?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The harness it runs on, for an agent part (SHIELD SH-43): the catalogue
|
|
23
|
+
* harness's id, whose terms, credential and what it honours the Properties
|
|
24
|
+
* panel reads from `specs/harnesses`.
|
|
25
|
+
*/
|
|
26
|
+
harness?: string;
|
|
21
27
|
/**
|
|
22
28
|
* What it arrives with, spelled as the Appspec's text spells it: a
|
|
23
29
|
* connection's, a rule's, a test's or an event's object, a skill's, a
|
|
@@ -5,7 +5,7 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
5
5
|
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
6
6
|
import { agentServerOf } from '../../core';
|
|
7
7
|
import { BEHAVIOUR_WORDS, coverOf } from '../../apps/rules';
|
|
8
|
-
import { SESSION_RULES_WORDS, SESSION_RULE_CLASSES, SESSION_RULE_DECISIONS, addSessionRule, readSessionRules, removeSessionRule, sessionRuleInWords, sessionRuleOf, } from '../../apps/sessionRules';
|
|
8
|
+
import { FILE_AND_SHELL_RULES, SESSION_RULES_WORDS, SESSION_RULE_CLASSES, SESSION_RULE_DECISIONS, addSessionRule, readSessionRules, removeSessionRule, sessionRuleInWords, sessionRuleOf, } from '../../apps/sessionRules';
|
|
9
9
|
import { SAVE_WORDS, draftOfApproval } from '../../apps/saved';
|
|
10
10
|
import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
|
|
11
11
|
/** What the card says. */
|
|
@@ -105,7 +105,7 @@ export function SessionRules({ agentServerUrl, sessionUid, token, }) {
|
|
|
105
105
|
}
|
|
106
106
|
void act(() => addSessionRule(agentServerUrl, token, sessionUid, rule)).then(() => setName(''));
|
|
107
107
|
};
|
|
108
|
-
return (_jsxs(Box, { "data-testid": "session-rules", mt: 3, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 1 }, children: SESSION_RULES_WORDS.title }), inForce?.unavailable ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mb: 1 }, children: SESSION_RULES_WORDS.unavailable(inForce.unavailable) })) : null, inForce && inForce.session_rules.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: SESSION_RULES_WORDS.none })) : (_jsx(Box, { as: "ul", listStyle: "none", p: 0, m: 0, children: (inForce?.session_rules ?? []).map(rule => (_jsxs(Box, { as: "li", py: 1, fontSize: 1, display: "flex", gap: 2, alignItems: "center", children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: rule.name }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: sessionRuleInWords(rule) }), _jsx(Button, { size: "small", variant: "invisible", disabled: busy, onClick: () => void act(() => removeSessionRule(agentServerUrl, token, sessionUid, rule.name)), children: SESSION_RULES_WORDS.remove })] }, rule.name))) })), _jsxs(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", alignItems: "center", children: [_jsx(TextInput, { size: "small", value: name, placeholder: SESSION_RULES_WORDS.namePlaceholder, "aria-label": "The rule's name", onChange: event => setName(event.target.value) }), _jsx(Select, { size: "small", value: kind, "aria-label": "What it covers", onChange: event => setKind(event.target.value), children: SESSION_RULE_CLASSES.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.classes[item] }, item))) }), _jsx(Select, { size: "small", value: decision, "aria-label": "What it says", onChange: event => setDecision(event.target.value), children: SESSION_RULE_DECISIONS.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.decisions[item] }, item))) }), _jsx(Button, { size: "small", disabled: busy, onClick: add, children: SESSION_RULES_WORDS.add })] }), problem ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mt: 1 }, children: problem })) : null, _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1, mb: 0 }, children: SESSION_RULES_WORDS.goneWithSession })] }));
|
|
108
|
+
return (_jsxs(Box, { "data-testid": "session-rules", mt: 3, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 1 }, children: SESSION_RULES_WORDS.title }), inForce?.unavailable ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mb: 1 }, children: SESSION_RULES_WORDS.unavailable(inForce.unavailable) })) : null, inForce && inForce.session_rules.length === 0 ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: SESSION_RULES_WORDS.none })) : (_jsx(Box, { as: "ul", listStyle: "none", p: 0, m: 0, children: (inForce?.session_rules ?? []).map(rule => (_jsxs(Box, { as: "li", py: 1, fontSize: 1, display: "flex", gap: 2, alignItems: "center", children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: rule.name }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: sessionRuleInWords(rule) }), _jsx(Button, { size: "small", variant: "invisible", disabled: busy, onClick: () => void act(() => removeSessionRule(agentServerUrl, token, sessionUid, rule.name)), children: SESSION_RULES_WORDS.remove })] }, rule.name))) })), _jsxs(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", alignItems: "center", children: [_jsx(TextInput, { size: "small", value: name, placeholder: SESSION_RULES_WORDS.namePlaceholder, "aria-label": "The rule's name", onChange: event => setName(event.target.value) }), _jsx(Select, { size: "small", value: kind, "aria-label": "What it covers", onChange: event => setKind(event.target.value), children: SESSION_RULE_CLASSES.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.classes[item] }, item))) }), _jsx(Select, { size: "small", value: decision, "aria-label": "What it says", onChange: event => setDecision(event.target.value), children: SESSION_RULE_DECISIONS.map(item => (_jsx(Select.Option, { value: item, children: SESSION_RULES_WORDS.decisions[item] }, item))) }), _jsx(Button, { size: "small", disabled: busy, onClick: add, children: SESSION_RULES_WORDS.add })] }), _jsx(Box, { display: "flex", gap: 1, mt: 1, flexWrap: "wrap", "data-testid": "session-rules-file-and-shell", children: FILE_AND_SHELL_RULES.map(rule => (_jsx(Button, { size: "small", variant: "invisible", disabled: busy, title: sessionRuleInWords(rule), onClick: () => void act(() => addSessionRule(agentServerUrl, token, sessionUid, rule)), children: rule.name }, rule.name))) }), problem ? (_jsx(Text, { as: "p", role: "alert", sx: { fontSize: 0, m: 0, mt: 1 }, children: problem })) : null, _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 1, mb: 0 }, children: SESSION_RULES_WORDS.goneWithSession })] }));
|
|
109
109
|
}
|
|
110
110
|
export function AppRulesCard({ app, workspace, }) {
|
|
111
111
|
const token = useIAMStore(state => state.token) ?? '';
|
|
@@ -15,9 +15,17 @@ type PartsReader = {
|
|
|
15
15
|
}>;
|
|
16
16
|
};
|
|
17
17
|
/** The short ids an organization turns off, and the plugin each names. */
|
|
18
|
-
export declare const CANVAS_PART_SOURCES: readonly ["connections", "skills", "models", "rules", "tests", "events", "protocols", "ui", "characters"];
|
|
18
|
+
export declare const CANVAS_PART_SOURCES: readonly ["agents", "connections", "skills", "models", "rules", "tests", "events", "protocols", "ui", "characters"];
|
|
19
19
|
export type CanvasPartSource = (typeof CANVAS_PART_SOURCES)[number];
|
|
20
20
|
export declare const canvasPartsPluginName: (source: CanvasPartSource) => string;
|
|
21
|
+
/**
|
|
22
|
+
* The catalogue's harnesses as the agent (SHIELD SH-43): each harness an
|
|
23
|
+
* agent program over ACP behind the Shield, offered by the catalogue agent
|
|
24
|
+
* that runs on it — dropped on the surface, it is the application's agent.
|
|
25
|
+
* A harness no agent of the catalogue runs yet is offered off, and says so;
|
|
26
|
+
* `custom` is an organization's own and never offered here.
|
|
27
|
+
*/
|
|
28
|
+
export declare const AGENT_PARTS: CanvasPartContribution[];
|
|
21
29
|
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
22
30
|
export declare const CONNECTION_PARTS: CanvasPartContribution[];
|
|
23
31
|
/** Each skill of the catalogue. */
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
|
|
15
15
|
import { MCP_SERVER_LIBRARY } from '../../../specs/mcpServers';
|
|
16
|
+
import { listAgentspecs } from '../../../specs/agents';
|
|
17
|
+
import { listHarnesses } from '../../../specs/harnesses';
|
|
16
18
|
import { listChatModels } from '../../../specs/models';
|
|
17
19
|
import { getSkillSpecs } from '../../../specs/skills';
|
|
18
20
|
import { listProtocolSpecs } from '../../../specs/protocols';
|
|
@@ -21,6 +23,7 @@ import { ASSISTANT_CHARACTERS } from '../../../chat/assistant/characters';
|
|
|
21
23
|
import { LoopCanvasPart, } from '../../core/canvasParts';
|
|
22
24
|
/** The short ids an organization turns off, and the plugin each names. */
|
|
23
25
|
export const CANVAS_PART_SOURCES = [
|
|
26
|
+
'agents',
|
|
24
27
|
'connections',
|
|
25
28
|
'skills',
|
|
26
29
|
'models',
|
|
@@ -39,6 +42,30 @@ const partsPlugin = (source, displayName, parts) => definePlugin({
|
|
|
39
42
|
octicon: 'package',
|
|
40
43
|
contributes: parts.map(part => contribution(LoopCanvasPart, part, { id: part.id })),
|
|
41
44
|
});
|
|
45
|
+
/**
|
|
46
|
+
* The catalogue's harnesses as the agent (SHIELD SH-43): each harness an
|
|
47
|
+
* agent program over ACP behind the Shield, offered by the catalogue agent
|
|
48
|
+
* that runs on it — dropped on the surface, it is the application's agent.
|
|
49
|
+
* A harness no agent of the catalogue runs yet is offered off, and says so;
|
|
50
|
+
* `custom` is an organization's own and never offered here.
|
|
51
|
+
*/
|
|
52
|
+
export const AGENT_PARTS = listHarnesses()
|
|
53
|
+
.filter(harness => harness.id !== 'custom')
|
|
54
|
+
.map(harness => {
|
|
55
|
+
const agent = listAgentspecs().find(spec => spec.harness === harness.id);
|
|
56
|
+
return {
|
|
57
|
+
id: `agent:${harness.id}`,
|
|
58
|
+
kind: 'agent',
|
|
59
|
+
label: harness.name,
|
|
60
|
+
says: agent
|
|
61
|
+
? `${agent.name}: ${harness.description}`
|
|
62
|
+
: `${harness.description} No agent of the catalogue runs on it yet.`,
|
|
63
|
+
emoji: '🛡️',
|
|
64
|
+
harness: harness.id,
|
|
65
|
+
example: agent?.id ?? '',
|
|
66
|
+
enabled: Boolean(agent) && harness.enabled,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
42
69
|
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
43
70
|
export const CONNECTION_PARTS = Object.values(MCP_SERVER_LIBRARY).map(server => ({
|
|
44
71
|
id: `connection:${server.id}`,
|
|
@@ -210,6 +237,7 @@ export const CHARACTER_PARTS = ASSISTANT_CHARACTERS.map(character => ({
|
|
|
210
237
|
enabled: true,
|
|
211
238
|
}));
|
|
212
239
|
export const CANVAS_PART_PLUGINS = [
|
|
240
|
+
partsPlugin('agents', 'Agents', AGENT_PARTS),
|
|
213
241
|
partsPlugin('connections', 'Connections', CONNECTION_PARTS),
|
|
214
242
|
partsPlugin('skills', 'Skills', SKILL_PARTS),
|
|
215
243
|
partsPlugin('models', 'Models', MODEL_PARTS),
|
package/lib/chat/ChatInline.js
CHANGED
|
@@ -6,7 +6,18 @@ import { CopyIcon, ArrowLeftIcon, SyncIcon, TrashIcon, CheckIcon, PlusIcon, Penc
|
|
|
6
6
|
import { SparklesIcon } from '@datalayer/icons-react';
|
|
7
7
|
import { AGUIAdapter } from '../protocols';
|
|
8
8
|
import { generateMessageId } from '../types/messages';
|
|
9
|
-
import {
|
|
9
|
+
import { BROWSER_LIMITS, emptyConversation, reduceConversation, } from '../portable/conversation';
|
|
10
|
+
import { conversationEventsOf } from '../portable/adapterEvents';
|
|
11
|
+
/** The answer so far: the newest assistant message of the conversation. */
|
|
12
|
+
function answerOf(state) {
|
|
13
|
+
for (let index = state.items.length - 1; index >= 0; index -= 1) {
|
|
14
|
+
const item = state.items[index];
|
|
15
|
+
if (item.kind === 'message' && item.role === 'assistant') {
|
|
16
|
+
return item.text;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
10
21
|
/**
|
|
11
22
|
* Pre-defined AI action groups
|
|
12
23
|
*/
|
|
@@ -79,8 +90,15 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
79
90
|
const [input, setInput] = useState('');
|
|
80
91
|
// AI state
|
|
81
92
|
const [aiState, setAiState] = useState('initial');
|
|
82
|
-
//
|
|
83
|
-
|
|
93
|
+
// The one-shot conversation (MOBILE M-030): `reduceConversation`'s, as in
|
|
94
|
+
// ChatBase and on the phone; the response is its newest assistant message.
|
|
95
|
+
const [conversation, setConversation] = useState(() => emptyConversation(BROWSER_LIMITS));
|
|
96
|
+
const conversationRef = useRef(conversation);
|
|
97
|
+
const changeConversation = useCallback((next) => {
|
|
98
|
+
conversationRef.current = next;
|
|
99
|
+
setConversation(next);
|
|
100
|
+
}, []);
|
|
101
|
+
const aiResponse = useMemo(() => answerOf(conversation), [conversation]);
|
|
84
102
|
// Previous prompt for regeneration
|
|
85
103
|
const [previousPrompt, setPreviousPrompt] = useState('');
|
|
86
104
|
// Current page in options menu (for sub-menus like translate)
|
|
@@ -112,20 +130,23 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
112
130
|
adapterRef.current = adapter;
|
|
113
131
|
// Subscribe to events
|
|
114
132
|
unsubscribeRef.current = adapter.subscribe((event) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
if (event.type === 'error') {
|
|
134
|
+
console.error('[ChatInline] Protocol error:', event.error);
|
|
135
|
+
setError(event.error?.message || 'Unknown error');
|
|
136
|
+
setAiState('initial');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// The event, as the conversation reads it: one reducer whatever the protocol.
|
|
140
|
+
try {
|
|
141
|
+
const inputs = conversationEventsOf(event, conversationRef.current);
|
|
142
|
+
if (inputs.length) {
|
|
143
|
+
changeConversation(inputs.reduce((kept, input) => reduceConversation(kept, input), conversationRef.current));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (failure) {
|
|
147
|
+
console.error('[ChatInline] Protocol event not read:', failure);
|
|
148
|
+
setError(failure instanceof Error ? failure.message : String(failure));
|
|
149
|
+
setAiState('initial');
|
|
129
150
|
}
|
|
130
151
|
});
|
|
131
152
|
// Connect
|
|
@@ -134,7 +155,7 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
134
155
|
unsubscribeRef.current?.();
|
|
135
156
|
adapterRef.current?.disconnect();
|
|
136
157
|
};
|
|
137
|
-
}, [protocol]);
|
|
158
|
+
}, [protocol, changeConversation]);
|
|
138
159
|
// Submit prompt to AI
|
|
139
160
|
const submitPrompt = useCallback(async (prompt) => {
|
|
140
161
|
if (!adapterRef.current) {
|
|
@@ -145,7 +166,7 @@ export function ChatInline({ selectedText, protocol, onReplaceSelection, onInser
|
|
|
145
166
|
setInput('');
|
|
146
167
|
setPreviousPrompt(prompt);
|
|
147
168
|
setError(null);
|
|
148
|
-
|
|
169
|
+
changeConversation(emptyConversation(BROWSER_LIMITS));
|
|
149
170
|
// Build system message with selected text context
|
|
150
171
|
const systemContext = `Do not surround your answer in quote marks. Only return the answer, nothing else. The user is selecting this text:
|
|
151
172
|
|
|
@@ -178,7 +199,7 @@ ${selectedText || ''}
|
|
|
178
199
|
setError(err.message);
|
|
179
200
|
setAiState('initial');
|
|
180
201
|
}
|
|
181
|
-
}, [selectedText]);
|
|
202
|
+
}, [selectedText, changeConversation]);
|
|
182
203
|
// Handle pending prompt from toolbar dropdown
|
|
183
204
|
useEffect(() => {
|
|
184
205
|
if (pendingPrompt && aiState === 'initial') {
|
|
@@ -235,8 +256,8 @@ ${selectedText || ''}
|
|
|
235
256
|
// Handle back to options
|
|
236
257
|
const handleBackToOptions = useCallback(() => {
|
|
237
258
|
setAiState('initial');
|
|
238
|
-
|
|
239
|
-
}, []);
|
|
259
|
+
changeConversation(emptyConversation(BROWSER_LIMITS));
|
|
260
|
+
}, [changeConversation]);
|
|
240
261
|
// Handle discard
|
|
241
262
|
const handleDiscard = useCallback(() => {
|
|
242
263
|
onClose?.();
|
|
@@ -25,7 +25,7 @@ import { SkeletonText } from '@primer/react/experimental';
|
|
|
25
25
|
import { SidebarExpandIcon } from '@primer/octicons-react';
|
|
26
26
|
import { notebookStore, JupyterReactTheme } from '@datalayer/jupyter-react';
|
|
27
27
|
import { Box, setupPrimerPortals, useThemeStore, getColorPalette, getThemeConfig, useSystemColorMode, } from '@datalayer/primer-addons';
|
|
28
|
-
import { AlertIcon, PersonIcon } from '@primer/octicons-react';
|
|
28
|
+
import { AlertIcon, PersonIcon, UnmuteIcon } from '@primer/octicons-react';
|
|
29
29
|
import { AiAgentIcon } from '@datalayer/icons-react';
|
|
30
30
|
import { QueryClientProvider, QueryClientContext } from '@tanstack/react-query';
|
|
31
31
|
import { useCoreStore } from '@datalayer/core';
|
|
@@ -57,6 +57,7 @@ import { settledVoice } from '../../voice/chatVoice';
|
|
|
57
57
|
import { useVoiceSettings } from '../../voice/useVoiceSettings';
|
|
58
58
|
import { useVoiceSpeaker } from '../../voice/useVoiceSpeaker';
|
|
59
59
|
import { useSpokenAnswers } from '../../voice/useSpokenAnswers';
|
|
60
|
+
import { lineToSay } from '../../voice/sayAloud';
|
|
60
61
|
import { isFloatingChatViewMode } from '../viewModes';
|
|
61
62
|
import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
|
|
62
63
|
import { initialModelId, isOffered, offeredModels as offeredModelsFor, } from './modelChoice';
|
|
@@ -1819,6 +1820,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1819
1820
|
recorderRef.current = null;
|
|
1820
1821
|
}
|
|
1821
1822
|
const inspectAnswerRef = useRef('');
|
|
1823
|
+
// A line the agent says aloud, from a run's `voice.say` (VOICE.md VO-35).
|
|
1824
|
+
const voiceSayRef = useRef(undefined);
|
|
1822
1825
|
const handleRespondRef = useRef(null);
|
|
1823
1826
|
// A decision made outside the chat — a host page's own approval banner —
|
|
1824
1827
|
// is answered exactly as the tool card's buttons answer it: through the
|
|
@@ -2801,6 +2804,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2801
2804
|
// What the server says besides the conversation's text comes on
|
|
2802
2805
|
// the ui socket (REACTOR_UI RU-W); the run's own — `loop.session`
|
|
2803
2806
|
// among them — are the conversation's.
|
|
2807
|
+
if (event.activity) {
|
|
2808
|
+
voiceSayRef.current?.(event.activity);
|
|
2809
|
+
}
|
|
2804
2810
|
read();
|
|
2805
2811
|
break;
|
|
2806
2812
|
case 'done':
|
|
@@ -2968,6 +2974,31 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2968
2974
|
const ownVoice = useVoiceSpeaker(settled, !hostSpeaks);
|
|
2969
2975
|
useSpokenAnswers(displayItems, isLoading, !hostSpeaks && settled?.output === 'always', ownVoice.speaker);
|
|
2970
2976
|
const speakingNow = voiceSpeaking || ownVoice.state.speaking;
|
|
2977
|
+
/*
|
|
2978
|
+
* A line the agent wants heard, not written (VO-35): a caption while the
|
|
2979
|
+
* run goes on, said by the speaker where answers are said by themselves.
|
|
2980
|
+
*/
|
|
2981
|
+
const [voiceSaid, setVoiceSaid] = useState('');
|
|
2982
|
+
voiceSayRef.current = activity => {
|
|
2983
|
+
const line = lineToSay(activity);
|
|
2984
|
+
if (!line) {
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
setVoiceSaid(line);
|
|
2988
|
+
if (settled?.output === 'always') {
|
|
2989
|
+
if (onReadAloud) {
|
|
2990
|
+
onReadAloud(line);
|
|
2991
|
+
}
|
|
2992
|
+
else {
|
|
2993
|
+
ownVoice.speaker?.say(line);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
useEffect(() => {
|
|
2998
|
+
if (!isLoading) {
|
|
2999
|
+
setVoiceSaid('');
|
|
3000
|
+
}
|
|
3001
|
+
}, [isLoading]);
|
|
2971
3002
|
// Answers spoken over, hands-free (VOICE.md VO-13): marked *interrupted*.
|
|
2972
3003
|
const [interruptedIds, setInterruptedIds] = useState(() => new Set());
|
|
2973
3004
|
const stopSpeaking = onStopSpeaking ?? (ownVoice.speaker ? ownVoice.stop : undefined);
|
|
@@ -3762,7 +3793,17 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
3762
3793
|
}, send: (text, metadata) => void handleSend(text, undefined, metadata) })) : undefined;
|
|
3763
3794
|
// Why an answer could not be heard, in a sentence, by the composer.
|
|
3764
3795
|
const voiceRefused = !hostSpeaks && ownVoice.state.refused ? (_jsx(Text, { role: "alert", sx: { fontSize: 0, color: 'danger.fg', maxWidth: 260 }, "data-voice-refused": "", children: ownVoice.state.refused })) : null;
|
|
3765
|
-
|
|
3796
|
+
// The line said aloud, written as it is said: captions always (VO-65).
|
|
3797
|
+
const voiceSayCaption = voiceSaid ? (_jsxs(Text, { "aria-live": "polite", sx: {
|
|
3798
|
+
fontSize: 0,
|
|
3799
|
+
fontStyle: 'italic',
|
|
3800
|
+
color: 'fg.muted',
|
|
3801
|
+
maxWidth: 260,
|
|
3802
|
+
display: 'inline-flex',
|
|
3803
|
+
alignItems: 'center',
|
|
3804
|
+
gap: 1,
|
|
3805
|
+
}, "data-voice-say": "", children: [_jsx(UnmuteIcon, { size: 12, "aria-label": "Said aloud" }), voiceSaid] })) : null;
|
|
3806
|
+
const inputToolbar = showInput ? (_jsx(InputPrompt, { footerExtras: voiceRefused || voiceSayCaption ? (_jsxs(_Fragment, { children: [voiceSayCaption, voiceRefused, voiceControl] })) : (voiceControl), interim: voiceInterim, input: input, setInput: setInput, isLoading: isLoading, kernelStatus: liveKernelStatus, connectionConfirmed: connectionConfirmed, placeholder: placeholder,
|
|
3766
3807
|
/*
|
|
3767
3808
|
The same openers the empty state offers, typed into the box they would
|
|
3768
3809
|
be typed into.
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
location: string;
|
|
5
|
-
specId?: string;
|
|
6
|
-
/**
|
|
7
|
-
* The framework turning this agent's loop, as its spec declares it —
|
|
8
|
-
* `pydantic-ai` server-side, `vercel-ai` in the page.
|
|
9
|
-
*/
|
|
10
|
-
harness?: string;
|
|
11
|
-
/**
|
|
12
|
-
* The runtime variant the host chose: `<location>-<harness>`.
|
|
13
|
-
*
|
|
14
|
-
* Shown next to the harness rather than instead of it, because they answer
|
|
15
|
-
* different questions and can disagree: the harness is what the spec asked
|
|
16
|
-
* for, the variant is what it actually got.
|
|
17
|
-
*/
|
|
18
|
-
variant?: string;
|
|
19
|
-
status?: string;
|
|
20
|
-
baseUrl?: string;
|
|
21
|
-
sandboxBaseUrl?: string;
|
|
22
|
-
agentId?: string;
|
|
23
|
-
isReady?: boolean;
|
|
24
|
-
error?: string;
|
|
25
|
-
}
|
|
2
|
+
import { type AgentSummaryData } from '../../portable/runtimes';
|
|
3
|
+
export type { AgentSummaryData } from '../../portable/runtimes';
|
|
26
4
|
export interface AgentSummaryProps {
|
|
27
5
|
summary: AgentSummaryData | null;
|
|
28
6
|
title?: string;
|
|
@@ -7,19 +7,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
7
7
|
import { Spinner } from '@primer/react';
|
|
8
8
|
import { Box } from '@datalayer/primer-addons';
|
|
9
9
|
import { strip } from '@datalayer/core/lib/utils';
|
|
10
|
-
|
|
11
|
-
* Statuses that represent an agent actively being created/started. The spinner
|
|
12
|
-
* only shows for these; app-navigation states ("selected", "switching") and
|
|
13
|
-
* settled states ("ready", "running", ...) never spin.
|
|
14
|
-
*/
|
|
15
|
-
const CREATING_STATUSES = new Set([
|
|
16
|
-
'creating',
|
|
17
|
-
'starting',
|
|
18
|
-
'launching',
|
|
19
|
-
'connecting',
|
|
20
|
-
'pending',
|
|
21
|
-
'resuming',
|
|
22
|
-
]);
|
|
10
|
+
import { isCreatingAgent, } from '../../portable/runtimes';
|
|
23
11
|
const MAX_AGENT_NAME_LENGTH = 32;
|
|
24
12
|
const toSandboxStatusUrl = (value) => {
|
|
25
13
|
const raw = String(value || '').trim();
|
|
@@ -95,12 +83,8 @@ export const AgentSummary = ({ summary, title = 'Active Agent', }) => {
|
|
|
95
83
|
if (!summary) {
|
|
96
84
|
return null;
|
|
97
85
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// ready, has no id yet, and its status is an active creation/startup state.
|
|
101
|
-
const isCreating = !summary.isReady &&
|
|
102
|
-
!summary.agentId &&
|
|
103
|
-
CREATING_STATUSES.has(normalizedStatus);
|
|
86
|
+
// Only show the spinner while the agent is genuinely being created.
|
|
87
|
+
const isCreating = isCreatingAgent(summary);
|
|
104
88
|
const hasError = Boolean(summary.error);
|
|
105
89
|
// The agent is optional: some examples run only a code sandbox. Show the
|
|
106
90
|
// agent status when there is an agent (or one is being created).
|