@datalayer/agent-runtimes 1.3.94 → 1.3.96
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/AgentInspector-DaMCROPs.js +1 -0
- package/dist-embed/chunks/{AgentInspectorDialog-LBbHMnDa.js → AgentInspectorDialog-zGMiFR5z.js} +1 -1
- package/dist-embed/chunks/ChatView-DE-jo1oJ.js +288 -0
- package/dist-embed/datalayer-app-main.js +110 -110
- package/dist-embed/datalayer-app.js +1 -1
- package/lib/apps/__tests__/fixtures/sceneCheckCases.json +352 -0
- package/lib/apps/apps/computer.d.ts +25 -1
- package/lib/apps/apps/computer.js +45 -2
- package/lib/apps/apps/sceneChecks.d.ts +92 -0
- package/lib/apps/apps/sceneChecks.js +649 -0
- package/lib/apps/apps/sceneShape.d.ts +8 -0
- package/lib/apps/apps/sceneShape.js +191 -0
- package/lib/apps/apps/sceneYaml.d.ts +64 -0
- package/lib/apps/apps/sceneYaml.js +253 -0
- package/lib/apps/core/canvasParts.d.ts +28 -0
- package/lib/apps/core/canvasParts.js +14 -0
- package/lib/apps/core/index.d.ts +1 -0
- package/lib/apps/core/index.js +2 -0
- package/lib/apps/embed/loader.js +10 -0
- package/lib/apps/plugins/agents/SandboxSelector.js +13 -2
- package/lib/apps/plugins/agents/host.d.ts +2 -0
- package/lib/apps/plugins/agents/host.js +16 -0
- package/lib/apps/plugins/agents/plugin.js +10 -2
- package/lib/apps/plugins/agents/switchable.d.ts +12 -0
- package/lib/apps/plugins/agents/switchable.js +23 -0
- package/lib/apps/plugins/app-computer/AppComputer.js +18 -2
- package/lib/apps/plugins/canvas-parts/index.d.ts +44 -0
- package/lib/apps/plugins/canvas-parts/index.js +209 -0
- package/lib/components/teams/useA2ATeam.js +1 -1
- package/lib/examples/HomeExample.js +1 -1
- package/lib/examples/ScenesExample.d.ts +1 -1
- package/lib/examples/example-selector.js +1 -1
- package/lib/hooks/useAgUi.js +4 -1
- package/lib/protocols/AGUIAdapter.js +5 -1
- package/lib/protocols/agUiRefusal.d.ts +47 -0
- package/lib/protocols/agUiRefusal.js +87 -0
- package/lib/specs/apps.js +32 -24
- package/lib/specs/scenes.js +147 -142
- package/lib/specs/teams/teams.js +13 -13
- package/package.json +3 -1
- package/scripts/dist-embed.mjs +116 -0
- package/scripts/record-scene-checks.py +600 -0
- package/dist-embed/chunks/AgentInspector-DfPciQ79.js +0 -1
- package/dist-embed/chunks/ChatView-BzFbN_mp.js +0 -288
|
@@ -55,6 +55,18 @@ export type SandboxTargetSpec = {
|
|
|
55
55
|
};
|
|
56
56
|
/** The four, in the order the control shows them: nearest first. */
|
|
57
57
|
export declare const SANDBOX_TARGETS: readonly SandboxTarget[];
|
|
58
|
+
/**
|
|
59
|
+
* Why a target is shown but not offered here — said on it — or nothing when
|
|
60
|
+
* it is offered.
|
|
61
|
+
*
|
|
62
|
+
* *Local* is a local agent with the Jupyter server it starts beside itself,
|
|
63
|
+
* and a web page has no machine of the reader's to start them on. Decided
|
|
64
|
+
* 2026-10-10 (Eric): not offered on the web for now, and left in the switch,
|
|
65
|
+
* held, so that a reader sees what exists and why it is not here. Inside
|
|
66
|
+
* JupyterLab it is offered as before — the same line the code-sandbox picker
|
|
67
|
+
* draws for a Jupyter Server's kernels (`codeSandboxProviders`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function heldTargetReason(target: SandboxTarget): string | undefined;
|
|
58
70
|
export declare const TARGET_SPECS: Record<SandboxTarget, SandboxTargetSpec>;
|
|
59
71
|
/** Whether an agent runs on this target — the question the chat asks. */
|
|
60
72
|
export declare function targetHasAgent(target: SandboxTarget): boolean;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* @module apps/plugins/agents/switchable
|
|
23
23
|
*/
|
|
24
24
|
import { computed, signal, } from '@datalayer/reactor';
|
|
25
|
+
import { insideJupyterLab } from './host';
|
|
25
26
|
import { browserSource, createBrowserSandboxService, } from './browserService';
|
|
26
27
|
import { createServerSandboxService, } from './service';
|
|
27
28
|
/**
|
|
@@ -40,6 +41,23 @@ export const SANDBOX_TARGETS = [
|
|
|
40
41
|
'jupyter',
|
|
41
42
|
'datalayer',
|
|
42
43
|
];
|
|
44
|
+
/**
|
|
45
|
+
* Why a target is shown but not offered here — said on it — or nothing when
|
|
46
|
+
* it is offered.
|
|
47
|
+
*
|
|
48
|
+
* *Local* is a local agent with the Jupyter server it starts beside itself,
|
|
49
|
+
* and a web page has no machine of the reader's to start them on. Decided
|
|
50
|
+
* 2026-10-10 (Eric): not offered on the web for now, and left in the switch,
|
|
51
|
+
* held, so that a reader sees what exists and why it is not here. Inside
|
|
52
|
+
* JupyterLab it is offered as before — the same line the code-sandbox picker
|
|
53
|
+
* draws for a Jupyter Server's kernels (`codeSandboxProviders`).
|
|
54
|
+
*/
|
|
55
|
+
export function heldTargetReason(target) {
|
|
56
|
+
if (target === 'local' && !insideJupyterLab()) {
|
|
57
|
+
return 'Not offered on the web for now: a local agent and the Jupyter server it starts run beside JupyterLab, not in a page.';
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
43
61
|
export const TARGET_SPECS = {
|
|
44
62
|
browser: {
|
|
45
63
|
label: 'Browser',
|
|
@@ -298,6 +316,11 @@ export function createSwitchableSandboxService({ serverUrl, initialTarget = 'loc
|
|
|
298
316
|
if (next === previous) {
|
|
299
317
|
return;
|
|
300
318
|
}
|
|
319
|
+
// Held here: refused whoever asks, not only by the switch's button.
|
|
320
|
+
const held = heldTargetReason(next);
|
|
321
|
+
if (held) {
|
|
322
|
+
throw new Error(held);
|
|
323
|
+
}
|
|
301
324
|
// Disconnect first: two live sandboxes would race to report status, and
|
|
302
325
|
// the reader would watch the header flicker between them.
|
|
303
326
|
disconnect?.();
|
|
@@ -126,11 +126,27 @@ export function AppComputer({ app, workspace, }) {
|
|
|
126
126
|
? noRuntimeSaid(workspace?.sandbox ?? IDLE_SANDBOX_SNAPSHOT, chatText)
|
|
127
127
|
: undefined;
|
|
128
128
|
const agentId = workspace?.agentId || app.id;
|
|
129
|
+
const [state, setState] = useState();
|
|
130
|
+
/*
|
|
131
|
+
* Where its files come from (STUDIO D-22).
|
|
132
|
+
*
|
|
133
|
+
* The runtime says it when the plane keeps a host for what applications
|
|
134
|
+
* serve (`servedFrom`, `DATALAYER_USER_APPS_URL`): a file is then fetched
|
|
135
|
+
* from that origin rather than from the runtimes' host, which is every
|
|
136
|
+
* runtime's and so same-origin with all of them. Read from the computer's
|
|
137
|
+
* own answer rather than configured here, because it is the runtime that
|
|
138
|
+
* knows which names it answers to; unsaid, nothing changes.
|
|
139
|
+
*/
|
|
140
|
+
const servedFrom = state?.servedFrom ?? '';
|
|
129
141
|
const context = useMemo(() => serverUrl === undefined
|
|
130
142
|
? undefined
|
|
131
|
-
: {
|
|
143
|
+
: {
|
|
144
|
+
serverUrl,
|
|
145
|
+
agentId,
|
|
146
|
+
token: token || undefined,
|
|
147
|
+
userAppsUrl: servedFrom || undefined,
|
|
148
|
+
}, [serverUrl, agentId, token, servedFrom]);
|
|
132
149
|
const parts = computerParts(app);
|
|
133
|
-
const [state, setState] = useState();
|
|
134
150
|
const [error, setError] = useState('');
|
|
135
151
|
const [busy, setBusy] = useState(false);
|
|
136
152
|
// Nothing is set once it is gone: an answer may come back after.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's toolbox, contributed (LOOP C-20): one plugin per source of
|
|
3
|
+
* parts — the servers of the catalogue a connection reaches, its skills, its
|
|
4
|
+
* models, and the rules, tests and events an agent answers to — each
|
|
5
|
+
* contributing to `loop.canvas.part`. The toolbox is what the enabled
|
|
6
|
+
* plugins contribute, as the palette is (`canvas-blocks`, C-12).
|
|
7
|
+
*
|
|
8
|
+
* @module apps/plugins/canvas-parts
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactorPlatform, type ReactorPlugin } from '@datalayer/reactor';
|
|
11
|
+
import { LoopCanvasPart, type CanvasPartContribution } from '../../core/canvasParts';
|
|
12
|
+
type PartsReader = {
|
|
13
|
+
getContributions: (point: typeof LoopCanvasPart) => ReadonlyArray<{
|
|
14
|
+
value: CanvasPartContribution;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
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"];
|
|
19
|
+
export type CanvasPartSource = (typeof CANVAS_PART_SOURCES)[number];
|
|
20
|
+
export declare const canvasPartsPluginName: (source: CanvasPartSource) => string;
|
|
21
|
+
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
22
|
+
export declare const CONNECTION_PARTS: CanvasPartContribution[];
|
|
23
|
+
/** Each skill of the catalogue. */
|
|
24
|
+
export declare const SKILL_PARTS: CanvasPartContribution[];
|
|
25
|
+
/** Each chat model of the catalogue. */
|
|
26
|
+
export declare const MODEL_PARTS: CanvasPartContribution[];
|
|
27
|
+
/** The rules a person most often sets, each one of the four behaviours. */
|
|
28
|
+
export declare const RULE_PARTS: CanvasPartContribution[];
|
|
29
|
+
/** A test: a question, and what a good answer does. */
|
|
30
|
+
export declare const TEST_PARTS: CanvasPartContribution[];
|
|
31
|
+
/** The events an application answers (C-21): on a schedule, on an event, once. */
|
|
32
|
+
export declare const EVENT_PARTS: CanvasPartContribution[];
|
|
33
|
+
export declare const CANVAS_PART_PLUGINS: ReactorPlugin<Record<string, never>, unknown, unknown>[];
|
|
34
|
+
/** The toolbox's reactor, its named plugins disabled; a name no plugin has is refused. */
|
|
35
|
+
export declare function canvasPartsReactor(disabled?: readonly string[], plugins?: ReactorPlugin<any, any, any>[]): ReactorPlatform;
|
|
36
|
+
/**
|
|
37
|
+
* The toolbox for an organization's plugins off: the ids it turns off that
|
|
38
|
+
* name a source of parts (`skills`, …) take those parts off; the rest are the
|
|
39
|
+
* palette's.
|
|
40
|
+
*/
|
|
41
|
+
export declare function canvasPartsReactorOf(pluginsOff: readonly string[]): ReactorPlatform;
|
|
42
|
+
/** What the toolbox offers: the parts the enabled plugins contribute, in their order. */
|
|
43
|
+
export declare function canvasPartsOf(reactor: PartsReader): CanvasPartContribution[];
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The agent's toolbox, contributed (LOOP C-20): one plugin per source of
|
|
7
|
+
* parts — the servers of the catalogue a connection reaches, its skills, its
|
|
8
|
+
* models, and the rules, tests and events an agent answers to — each
|
|
9
|
+
* contributing to `loop.canvas.part`. The toolbox is what the enabled
|
|
10
|
+
* plugins contribute, as the palette is (`canvas-blocks`, C-12).
|
|
11
|
+
*
|
|
12
|
+
* @module apps/plugins/canvas-parts
|
|
13
|
+
*/
|
|
14
|
+
import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
|
|
15
|
+
import { MCP_SERVER_LIBRARY } from '../../../specs/mcpServers';
|
|
16
|
+
import { listChatModels } from '../../../specs/models';
|
|
17
|
+
import { getSkillSpecs } from '../../../specs/skills';
|
|
18
|
+
import { LoopCanvasPart, } from '../../core/canvasParts';
|
|
19
|
+
/** The short ids an organization turns off, and the plugin each names. */
|
|
20
|
+
export const CANVAS_PART_SOURCES = [
|
|
21
|
+
'connections',
|
|
22
|
+
'skills',
|
|
23
|
+
'models',
|
|
24
|
+
'rules',
|
|
25
|
+
'tests',
|
|
26
|
+
'events',
|
|
27
|
+
];
|
|
28
|
+
export const canvasPartsPluginName = (source) => `@datalayer/loop-plugin-parts-${source}`;
|
|
29
|
+
const partsPlugin = (source, displayName, parts) => definePlugin({
|
|
30
|
+
name: canvasPartsPluginName(source),
|
|
31
|
+
displayName,
|
|
32
|
+
description: `${displayName} in the agent's toolbox on the Canvas.`,
|
|
33
|
+
octicon: 'package',
|
|
34
|
+
contributes: parts.map(part => contribution(LoopCanvasPart, part, { id: part.id })),
|
|
35
|
+
});
|
|
36
|
+
/** A connection to each server of the catalogue: read-only, as its owner, every tool. */
|
|
37
|
+
export const CONNECTION_PARTS = Object.values(MCP_SERVER_LIBRARY).map(server => ({
|
|
38
|
+
id: `connection:${server.id}`,
|
|
39
|
+
kind: 'connection',
|
|
40
|
+
label: server.name,
|
|
41
|
+
says: server.description ?? '',
|
|
42
|
+
emoji: server.emoji,
|
|
43
|
+
example: { server: server.id, access: 'read', as: 'owner', only: [] },
|
|
44
|
+
enabled: server.enabled,
|
|
45
|
+
}));
|
|
46
|
+
/** Each skill of the catalogue. */
|
|
47
|
+
export const SKILL_PARTS = getSkillSpecs().map(skill => ({
|
|
48
|
+
id: `skill:${skill.id}`,
|
|
49
|
+
kind: 'skill',
|
|
50
|
+
label: skill.name,
|
|
51
|
+
says: skill.description,
|
|
52
|
+
emoji: skill.emoji,
|
|
53
|
+
example: skill.id,
|
|
54
|
+
enabled: skill.enabled,
|
|
55
|
+
}));
|
|
56
|
+
/** Each chat model of the catalogue. */
|
|
57
|
+
export const MODEL_PARTS = listChatModels().map(model => ({
|
|
58
|
+
id: `model:${model.id}`,
|
|
59
|
+
kind: 'model',
|
|
60
|
+
label: model.name,
|
|
61
|
+
says: model.description,
|
|
62
|
+
example: model.id,
|
|
63
|
+
// A model's `available` says whether its key is set where the catalogue
|
|
64
|
+
// was read, not whether it may be chosen: every chat model may.
|
|
65
|
+
enabled: true,
|
|
66
|
+
}));
|
|
67
|
+
/** The rules a person most often sets, each one of the four behaviours. */
|
|
68
|
+
export const RULE_PARTS = [
|
|
69
|
+
{
|
|
70
|
+
id: 'rule:ask-before-sending',
|
|
71
|
+
kind: 'rule',
|
|
72
|
+
label: 'Ask before sending',
|
|
73
|
+
says: 'It asks you before it sends anything on your behalf.',
|
|
74
|
+
example: {
|
|
75
|
+
action: 'Send on my behalf',
|
|
76
|
+
applies_to: ['send'],
|
|
77
|
+
behaviour: 'ask_first',
|
|
78
|
+
},
|
|
79
|
+
enabled: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'rule:read-freely',
|
|
83
|
+
kind: 'rule',
|
|
84
|
+
label: 'Read freely',
|
|
85
|
+
says: 'It reads what its connections reach without asking.',
|
|
86
|
+
example: { action: 'Read', applies_to: ['read'], behaviour: 'do_it' },
|
|
87
|
+
enabled: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'rule:never-delete',
|
|
91
|
+
kind: 'rule',
|
|
92
|
+
label: 'Leave deleting to me',
|
|
93
|
+
says: 'It never deletes: it tells you what it would.',
|
|
94
|
+
example: {
|
|
95
|
+
action: 'Delete',
|
|
96
|
+
applies_to: ['delete'],
|
|
97
|
+
behaviour: 'leave_to_me',
|
|
98
|
+
},
|
|
99
|
+
enabled: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'rule:buy-if-asked',
|
|
103
|
+
kind: 'rule',
|
|
104
|
+
label: 'Buy only when asked',
|
|
105
|
+
says: 'It buys only when you ask it to, in so many words.',
|
|
106
|
+
example: { action: 'Buy', applies_to: ['buy'], behaviour: 'if_asked' },
|
|
107
|
+
enabled: true,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
/** A test: a question, and what a good answer does. */
|
|
111
|
+
export const TEST_PARTS = [
|
|
112
|
+
{
|
|
113
|
+
id: 'test:answers-from-sources',
|
|
114
|
+
kind: 'test',
|
|
115
|
+
label: 'Answers from its sources',
|
|
116
|
+
says: 'A question, and what a good answer does.',
|
|
117
|
+
example: {
|
|
118
|
+
ask: 'What is due this week?',
|
|
119
|
+
expect: 'It lists what is due this week, read from its sources, and says where.',
|
|
120
|
+
},
|
|
121
|
+
enabled: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'test:says-when-it-cannot',
|
|
125
|
+
kind: 'test',
|
|
126
|
+
label: 'Says when it cannot',
|
|
127
|
+
says: 'A question it cannot answer, and it says so.',
|
|
128
|
+
example: {
|
|
129
|
+
ask: 'What will the price be next year?',
|
|
130
|
+
expect: 'It says it cannot know, and does not guess.',
|
|
131
|
+
},
|
|
132
|
+
enabled: true,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
/** The events an application answers (C-21): on a schedule, on an event, once. */
|
|
136
|
+
export const EVENT_PARTS = [
|
|
137
|
+
{
|
|
138
|
+
id: 'event:schedule',
|
|
139
|
+
kind: 'event',
|
|
140
|
+
label: 'On a schedule',
|
|
141
|
+
says: 'Every weekday at 9:00, or any time a schedule says.',
|
|
142
|
+
example: {
|
|
143
|
+
type: 'schedule',
|
|
144
|
+
cron: '0 9 * * 1-5',
|
|
145
|
+
description: 'Every weekday at 9:00',
|
|
146
|
+
prompt: 'Summarise what came in since yesterday.',
|
|
147
|
+
},
|
|
148
|
+
enabled: true,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'event:email-received',
|
|
152
|
+
kind: 'event',
|
|
153
|
+
label: 'When an email arrives',
|
|
154
|
+
says: 'A message arriving in the inbox it reads.',
|
|
155
|
+
example: {
|
|
156
|
+
type: 'event',
|
|
157
|
+
event: 'email_received',
|
|
158
|
+
prompt: 'Read the email, and draft a reply for me to approve.',
|
|
159
|
+
},
|
|
160
|
+
enabled: true,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'event:once',
|
|
164
|
+
kind: 'event',
|
|
165
|
+
label: 'Once, at a moment',
|
|
166
|
+
says: 'One run, at a date and a time.',
|
|
167
|
+
example: {
|
|
168
|
+
type: 'once',
|
|
169
|
+
at: '2026-12-31T09:00:00',
|
|
170
|
+
prompt: 'Close the year: list what is still open.',
|
|
171
|
+
},
|
|
172
|
+
enabled: true,
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
export const CANVAS_PART_PLUGINS = [
|
|
176
|
+
partsPlugin('connections', 'Connections', CONNECTION_PARTS),
|
|
177
|
+
partsPlugin('skills', 'Skills', SKILL_PARTS),
|
|
178
|
+
partsPlugin('models', 'Models', MODEL_PARTS),
|
|
179
|
+
partsPlugin('rules', 'Rules', RULE_PARTS),
|
|
180
|
+
partsPlugin('tests', 'Tests', TEST_PARTS),
|
|
181
|
+
partsPlugin('events', 'Events', EVENT_PARTS),
|
|
182
|
+
];
|
|
183
|
+
/** The toolbox's reactor, its named plugins disabled; a name no plugin has is refused. */
|
|
184
|
+
export function canvasPartsReactor(disabled = [], plugins = CANVAS_PART_PLUGINS) {
|
|
185
|
+
const reactor = buildReactorFromPlugins(plugins);
|
|
186
|
+
reactor.start();
|
|
187
|
+
for (const name of disabled) {
|
|
188
|
+
if (!reactor.hasPlugin(name)) {
|
|
189
|
+
throw new Error(`No plugin of the toolbox is named "${name}"; they are ${plugins.map(plugin => plugin.name).join(', ') || 'none'}.`);
|
|
190
|
+
}
|
|
191
|
+
reactor.disable(name);
|
|
192
|
+
}
|
|
193
|
+
return reactor;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The toolbox for an organization's plugins off: the ids it turns off that
|
|
197
|
+
* name a source of parts (`skills`, …) take those parts off; the rest are the
|
|
198
|
+
* palette's.
|
|
199
|
+
*/
|
|
200
|
+
export function canvasPartsReactorOf(pluginsOff) {
|
|
201
|
+
const sources = new Set(CANVAS_PART_SOURCES);
|
|
202
|
+
return canvasPartsReactor(pluginsOff
|
|
203
|
+
.filter(id => sources.has(id))
|
|
204
|
+
.map(id => canvasPartsPluginName(id)));
|
|
205
|
+
}
|
|
206
|
+
/** What the toolbox offers: the parts the enabled plugins contribute, in their order. */
|
|
207
|
+
export function canvasPartsOf(reactor) {
|
|
208
|
+
return reactor.getContributions(LoopCanvasPart).map(entry => entry.value);
|
|
209
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* assessment and Change detection), one `ask_<peer>` tool per peer, with
|
|
15
15
|
* {@link a2aPeerTool}. A team of two names its peer as `peerApp`; a team of
|
|
16
16
|
* N names them as `peers` (LOOP A-08). An entry that runs on a runtime
|
|
17
|
-
* itself (a scene of one member: Month-end
|
|
17
|
+
* itself (a scene of one member: Month-end Close, Crop Monitoring) is asked
|
|
18
18
|
* over A2A from the page, with no agent in the browser: `entryPeer`.
|
|
19
19
|
*
|
|
20
20
|
* What each of them does is kept as a persona — the state its character
|
|
@@ -4,7 +4,7 @@ import { Heading, Text, TextInput, Label } from '@primer/react';
|
|
|
4
4
|
import { SearchIcon } from '@primer/octicons-react';
|
|
5
5
|
const HomeExample = ({ examples = [], searchQuery = '', onSearchChange, onSelectExample, }) => {
|
|
6
6
|
const sortedExamples = [...examples].sort((a, b) => a.title.localeCompare(b.title));
|
|
7
|
-
return (_jsx(Box, { width: "100%", height: "100%", overflow: "auto", bg: "canvas.default", children: _jsxs(Box, { maxWidth: "1600px", margin: "0 auto", p: 4, display: "flex", flexDirection: "column", gap: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 3, flexWrap: "wrap", children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: "Agent Runtimes Examples" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Browse all examples as cards and open any flow in one click." })] }), _jsx(TextInput, { autoFocus: true, value: searchQuery, onChange: event => onSearchChange?.(event.target.value), placeholder: "Search examples", leadingVisual: SearchIcon, sx: { minWidth: ['100%', '320px'] } })] }), sortedExamples.length === 0 ? (_jsx(Box, { border: "1px dashed", borderColor: "border.default", borderRadius:
|
|
7
|
+
return (_jsx(Box, { width: "100%", height: "100%", overflow: "auto", bg: "canvas.default", children: _jsxs(Box, { maxWidth: "1600px", margin: "0 auto", p: 4, display: "flex", flexDirection: "column", gap: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 3, flexWrap: "wrap", children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: "Agent Runtimes Examples" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Browse all examples as cards and open any flow in one click." })] }), _jsx(TextInput, { autoFocus: true, value: searchQuery, onChange: event => onSearchChange?.(event.target.value), placeholder: "Search examples", leadingVisual: SearchIcon, sx: { minWidth: ['100%', '320px'] } })] }), sortedExamples.length === 0 ? (_jsx(Box, { border: "1px dashed", borderColor: "border.default", borderRadius: "card", p: 4, textAlign: "center", color: "fg.muted", children: "No examples match your search." })) : (_jsx(Box, { display: "grid", gridTemplateColumns: [
|
|
8
8
|
'1fr',
|
|
9
9
|
'repeat(2, minmax(0, 1fr))',
|
|
10
10
|
'repeat(4, minmax(0, 1fr))',
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* are served over A2A by `examples/sales-accounting-a2a/serve_scenes.py`,
|
|
13
13
|
* Accounting on :8767 as before and each other member on a runtime of its
|
|
14
14
|
* own from :8768 (`sceneRuntimePorts`), or wherever `VITE_A2A_<MEMBER>_URL`
|
|
15
|
-
* says. A scene whose entry runs on a runtime itself (Month-end
|
|
15
|
+
* says. A scene whose entry runs on a runtime itself (Month-end Close, Crop
|
|
16
16
|
* monitoring) is asked over A2A from the page, as the person
|
|
17
17
|
* (`useA2ATeam`'s `entryPeer`). A member that is not reached is drawn all
|
|
18
18
|
* the same, and the box says what its application needs (`setup`) and how
|
|
@@ -112,7 +112,7 @@ export const EXAMPLE_ENTRIES = [
|
|
|
112
112
|
// for one sandbox — and a second one that would not agree with the first.
|
|
113
113
|
['example', 'loop', 'workspace', 'sandbox', 'owns-sandbox-control']),
|
|
114
114
|
makeEntry('LoopAppComputerExample', () => import('./LoopAppComputerExample'), 'An application beside its computer: what its agent ran on its sandbox, its files, and Take over and Hand back (LOOP R-23).', ['example', 'loop', 'app', 'computer', 'sandbox', 'owns-sandbox-control']),
|
|
115
|
-
makeEntry('ScenesExample', () => import('./ScenesExample'), 'The scenes of the catalogue in tabs — Sales & Accounting, Month-end
|
|
115
|
+
makeEntry('ScenesExample', () => import('./ScenesExample'), 'The scenes of the catalogue in tabs — Sales & Accounting, Month-end Close, Crop Monitoring, Disaster Assessment — each its graph and transcript, its cues as suggestions, played against the local servers (LOOP A-15).', ['example', 'loop', 'scenes', 'a2a', 'agentspecs', 'team']),
|
|
116
116
|
makeEntry('LoopShellExample', () => import('./LoopShellExample'), 'The Loop shell at its most naked: a blank canvas, a floating draggable prompt, and an editor selector in the corner — none, notebook or document.',
|
|
117
117
|
// `owns-sandbox-control`: the shell is pinned to the browser sandbox, so
|
|
118
118
|
// the page's selector would offer three targets it cannot move to.
|
package/lib/hooks/useAgUi.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { agUiRefusal, refusalBody } from '../protocols/agUiRefusal';
|
|
6
7
|
/**
|
|
7
8
|
* Hook to manage chat state with AG-UI protocol.
|
|
8
9
|
*
|
|
@@ -94,7 +95,9 @@ export function useAgUi(options = {}) {
|
|
|
94
95
|
body: JSON.stringify(requestBody),
|
|
95
96
|
});
|
|
96
97
|
if (!response.ok) {
|
|
97
|
-
|
|
98
|
+
// Why, in the page's own words (F-15): a 404 at an agent's AG-UI
|
|
99
|
+
// address means the agent is not on this runtime.
|
|
100
|
+
throw new Error(agUiRefusal(response.status, response.statusText, await refusalBody(response)));
|
|
98
101
|
}
|
|
99
102
|
// Handle streaming response
|
|
100
103
|
const reader = response.body?.getReader();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { generateMessageId, createAssistantMessage } from '../types/messages';
|
|
6
|
+
import { agUiRefusal, refusalBody } from './agUiRefusal';
|
|
6
7
|
import { BaseProtocolAdapter } from './BaseProtocolAdapter';
|
|
7
8
|
/**
|
|
8
9
|
* AG-UI protocol adapter
|
|
@@ -214,7 +215,10 @@ export class AGUIAdapter extends BaseProtocolAdapter {
|
|
|
214
215
|
signal: controller.signal,
|
|
215
216
|
});
|
|
216
217
|
if (!response.ok) {
|
|
217
|
-
|
|
218
|
+
// Why, in the page's own words (F-15): a 404 at an agent's AG-UI
|
|
219
|
+
// address means the agent is not on this runtime, which the status
|
|
220
|
+
// alone never said.
|
|
221
|
+
throw new Error(agUiRefusal(response.status, response.statusText, await refusalBody(response)));
|
|
218
222
|
}
|
|
219
223
|
// Parse SSE stream
|
|
220
224
|
await this.parseSSEStream(response);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why an AG-UI run was not answered, in the page's own words (LOOP F-15).
|
|
3
|
+
*
|
|
4
|
+
* What a person met before was `AG-UI request failed: 404` — the status and
|
|
5
|
+
* nothing else. A 404 at an agent's AG-UI address has one meaning: the agent
|
|
6
|
+
* is not on the runtime the page is addressing. The commonest way to get
|
|
7
|
+
* there is to open an application whose agent runs its code in the browser:
|
|
8
|
+
* the runtime refuses to start it (`browser_sandbox_refusal`, 422), the page
|
|
9
|
+
* addresses it anyway, and the address answers nothing. So the page says
|
|
10
|
+
* that, and says what to do about it.
|
|
11
|
+
*
|
|
12
|
+
* When the server said why — a refusal of the platform's own, carried as
|
|
13
|
+
* `detail` — that sentence is the better one and is used instead: it is
|
|
14
|
+
* about this agent, where ours is about any.
|
|
15
|
+
*
|
|
16
|
+
* Pure, so the hook, the adapter and the tests read a refusal the same way
|
|
17
|
+
* (the repo's jest/vitest rule: rules live outside components).
|
|
18
|
+
*
|
|
19
|
+
* @module protocols/agUiRefusal
|
|
20
|
+
*/
|
|
21
|
+
/** What the page says when an agent's AG-UI address answers nothing. */
|
|
22
|
+
export declare const AG_UI_NOT_ON_RUNTIME: string;
|
|
23
|
+
/** What the page says when the address refused the caller. */
|
|
24
|
+
export declare const AG_UI_NOT_ALLOWED: string;
|
|
25
|
+
/**
|
|
26
|
+
* The `detail` a platform refusal carries, or `''`.
|
|
27
|
+
*
|
|
28
|
+
* A body that is not JSON — an HTML 404 from the page's own origin, which is
|
|
29
|
+
* what a page addressing a runtime it never got answers — carries none.
|
|
30
|
+
*/
|
|
31
|
+
export declare function refusalDetail(body: string | undefined): string;
|
|
32
|
+
/**
|
|
33
|
+
* Why the run was not answered, as the page says it.
|
|
34
|
+
*
|
|
35
|
+
* @param status - The status the address answered with.
|
|
36
|
+
* @param statusText - Its text, for a status we have nothing better for.
|
|
37
|
+
* @param body - What came back, when it was read; a refusal's `detail` wins.
|
|
38
|
+
*/
|
|
39
|
+
export declare function agUiRefusal(status: number, statusText: string, body?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Read a refusal's body without letting the reading itself throw: a body
|
|
42
|
+
* already consumed, or a connection that went away, leaves us with the
|
|
43
|
+
* status alone, which is still better than nothing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function refusalBody(response: {
|
|
46
|
+
text: () => Promise<string>;
|
|
47
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Why an AG-UI run was not answered, in the page's own words (LOOP F-15).
|
|
7
|
+
*
|
|
8
|
+
* What a person met before was `AG-UI request failed: 404` — the status and
|
|
9
|
+
* nothing else. A 404 at an agent's AG-UI address has one meaning: the agent
|
|
10
|
+
* is not on the runtime the page is addressing. The commonest way to get
|
|
11
|
+
* there is to open an application whose agent runs its code in the browser:
|
|
12
|
+
* the runtime refuses to start it (`browser_sandbox_refusal`, 422), the page
|
|
13
|
+
* addresses it anyway, and the address answers nothing. So the page says
|
|
14
|
+
* that, and says what to do about it.
|
|
15
|
+
*
|
|
16
|
+
* When the server said why — a refusal of the platform's own, carried as
|
|
17
|
+
* `detail` — that sentence is the better one and is used instead: it is
|
|
18
|
+
* about this agent, where ours is about any.
|
|
19
|
+
*
|
|
20
|
+
* Pure, so the hook, the adapter and the tests read a refusal the same way
|
|
21
|
+
* (the repo's jest/vitest rule: rules live outside components).
|
|
22
|
+
*
|
|
23
|
+
* @module protocols/agUiRefusal
|
|
24
|
+
*/
|
|
25
|
+
/** What the page says when an agent's AG-UI address answers nothing. */
|
|
26
|
+
export const AG_UI_NOT_ON_RUNTIME = 'This agent is not on the runtime the page is addressing: its AG-UI address answered nothing. ' +
|
|
27
|
+
'An agent that runs its code in the browser is never started on a runtime — run it in the page, ' +
|
|
28
|
+
'or give it a sandbox a runtime has (eval, jupyter-server).';
|
|
29
|
+
/** What the page says when the address refused the caller. */
|
|
30
|
+
export const AG_UI_NOT_ALLOWED = 'This agent did not answer the page: the run was refused. ' +
|
|
31
|
+
'Sign in again, or open the application from its own address.';
|
|
32
|
+
/**
|
|
33
|
+
* The `detail` a platform refusal carries, or `''`.
|
|
34
|
+
*
|
|
35
|
+
* A body that is not JSON — an HTML 404 from the page's own origin, which is
|
|
36
|
+
* what a page addressing a runtime it never got answers — carries none.
|
|
37
|
+
*/
|
|
38
|
+
export function refusalDetail(body) {
|
|
39
|
+
if (!body) {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
let said;
|
|
43
|
+
try {
|
|
44
|
+
said = JSON.parse(body);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
if (!said || typeof said !== 'object') {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
const detail = said.detail;
|
|
53
|
+
return typeof detail === 'string' ? detail.trim() : '';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Why the run was not answered, as the page says it.
|
|
57
|
+
*
|
|
58
|
+
* @param status - The status the address answered with.
|
|
59
|
+
* @param statusText - Its text, for a status we have nothing better for.
|
|
60
|
+
* @param body - What came back, when it was read; a refusal's `detail` wins.
|
|
61
|
+
*/
|
|
62
|
+
export function agUiRefusal(status, statusText, body) {
|
|
63
|
+
const detail = refusalDetail(body);
|
|
64
|
+
if (detail) {
|
|
65
|
+
return detail;
|
|
66
|
+
}
|
|
67
|
+
if (status === 404) {
|
|
68
|
+
return AG_UI_NOT_ON_RUNTIME;
|
|
69
|
+
}
|
|
70
|
+
if (status === 401 || status === 403) {
|
|
71
|
+
return AG_UI_NOT_ALLOWED;
|
|
72
|
+
}
|
|
73
|
+
return `AG-UI request failed: ${status} ${statusText}`.trim();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read a refusal's body without letting the reading itself throw: a body
|
|
77
|
+
* already consumed, or a connection that went away, leaves us with the
|
|
78
|
+
* status alone, which is still better than nothing.
|
|
79
|
+
*/
|
|
80
|
+
export async function refusalBody(response) {
|
|
81
|
+
try {
|
|
82
|
+
return await response.text();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|