@datalayer/agent-runtimes 1.3.106 → 1.3.108
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/datalayer-app-main.js +4 -4
- package/lib/apps/apps/computer.d.ts +55 -2
- package/lib/apps/apps/computer.js +82 -4
- package/lib/apps/plugins/app-computer/AppComputer.d.ts +7 -3
- package/lib/apps/plugins/app-computer/AppComputer.js +91 -4
- package/lib/examples/LoopAppComputerExample.d.ts +4 -3
- package/lib/examples/LoopAppComputerExample.js +11 -6
- package/lib/examples/pap/PapGuidedJourneyView.js +224 -39
- package/lib/specs/actions.js +22 -0
- package/lib/types/agentspecs.d.ts +2 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +6 -0
- package/scripts/validate_pap_examples.py +18 -0
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* An application's computer (LOOP R-23, I-01): the sandbox its agent runs on,
|
|
3
3
|
* as the computer view shows it — its three parts, each off until it is
|
|
4
4
|
* turned on; what its agent ran on it, from the conversation's tool calls;
|
|
5
|
-
* its files, read-only; and *Take over* and
|
|
5
|
+
* its files, read-only; its browser's page, live; and *Take over* and
|
|
6
|
+
* *Hand back* — the person then clicks and types in that page — through the
|
|
6
7
|
* runtime's `/api/v1/apps/agents/{agent}/computer` routes.
|
|
7
8
|
*
|
|
8
9
|
* Pure but for `fetch`: no React.
|
|
@@ -25,7 +26,17 @@ export declare const COMPUTER_WORDS: {
|
|
|
25
26
|
readonly off: "off";
|
|
26
27
|
readonly none: "It has no computer: browse, files and shell are all off. Turn one on in its permissions to give it one.";
|
|
27
28
|
readonly offSaid: (parts: string[]) => string;
|
|
28
|
-
readonly
|
|
29
|
+
readonly browser: "Browser";
|
|
30
|
+
readonly noBrowser: "No browser is installed on its computer.";
|
|
31
|
+
readonly noPage: "Its browser has opened no page yet.";
|
|
32
|
+
readonly pageAlt: (title: string) => string;
|
|
33
|
+
readonly clickToAct: "Click on the page to click there; with the page selected, what you type goes to it.";
|
|
34
|
+
readonly typeOnPage: "Text to type on the page";
|
|
35
|
+
readonly type: "Type";
|
|
36
|
+
readonly address: "An address to open";
|
|
37
|
+
readonly open: "Open";
|
|
38
|
+
readonly scrollUp: "Scroll up";
|
|
39
|
+
readonly scrollDown: "Scroll down";
|
|
29
40
|
readonly agentHas: "Its agent has the computer.";
|
|
30
41
|
readonly youHave: "You have the computer: its agent waits for it until you hand it back.";
|
|
31
42
|
readonly someoneHas: "Somebody else has taken this computer over.";
|
|
@@ -84,7 +95,10 @@ export type ComputerState = {
|
|
|
84
95
|
agent: string;
|
|
85
96
|
app: string;
|
|
86
97
|
parts: ComputerParts;
|
|
98
|
+
/** Whether a browser is installed where its agent runs, browse on. */
|
|
87
99
|
browser: boolean;
|
|
100
|
+
/** The page its browser has open; none before its first, or browse off. */
|
|
101
|
+
page?: BrowserPage | null;
|
|
88
102
|
started: boolean;
|
|
89
103
|
held: ComputerHolder | null;
|
|
90
104
|
/** Whether the person asking is who has it. */
|
|
@@ -96,6 +110,11 @@ export type ComputerState = {
|
|
|
96
110
|
*/
|
|
97
111
|
servedFrom?: string;
|
|
98
112
|
};
|
|
113
|
+
/** A page its browser has open. */
|
|
114
|
+
export type BrowserPage = {
|
|
115
|
+
url: string;
|
|
116
|
+
title: string;
|
|
117
|
+
};
|
|
99
118
|
/** One entry of a directory of its computer. */
|
|
100
119
|
export type ComputerFile = {
|
|
101
120
|
name: string;
|
|
@@ -154,5 +173,39 @@ export declare const takeOverComputer: (context: ComputerContext) => Promise<{
|
|
|
154
173
|
export declare const handBackComputer: (context: ComputerContext) => Promise<void>;
|
|
155
174
|
/** Run code on it, as the person who took it over. */
|
|
156
175
|
export declare const runOnComputer: (context: ComputerContext, code: string) => Promise<RunOutput>;
|
|
176
|
+
/** The keys a person presses on its page, as the runtime takes them. */
|
|
177
|
+
export declare const BROWSER_KEYS: readonly ["Enter", "Tab", "Backspace", "Escape", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown"];
|
|
178
|
+
/**
|
|
179
|
+
* The page its browser shows, as a PNG; undefined before its first page
|
|
180
|
+
* (the runtime answers 404).
|
|
181
|
+
*/
|
|
182
|
+
export declare const readBrowserScreenshot: (context: ComputerContext) => Promise<Blob | undefined>;
|
|
183
|
+
/** Click on its page, at a point of its screenshot, as who took it over. */
|
|
184
|
+
export declare const clickOnBrowser: (context: ComputerContext, x: number, y: number) => Promise<BrowserPage>;
|
|
185
|
+
/** Type where its page's focus is. */
|
|
186
|
+
export declare const typeOnBrowser: (context: ComputerContext, text: string) => Promise<BrowserPage>;
|
|
187
|
+
/** Press one of `BROWSER_KEYS` on its page. */
|
|
188
|
+
export declare const pressOnBrowser: (context: ComputerContext, key: string) => Promise<BrowserPage>;
|
|
189
|
+
/** Scroll its page, down for a positive `dy`. */
|
|
190
|
+
export declare const scrollBrowser: (context: ComputerContext, dy: number) => Promise<BrowserPage>;
|
|
191
|
+
/** Open an address in its browser. */
|
|
192
|
+
export declare const openOnBrowser: (context: ComputerContext, url: string) => Promise<BrowserPage>;
|
|
193
|
+
/**
|
|
194
|
+
* Where a click on a screenshot drawn at another size lands on the page: its
|
|
195
|
+
* pixels are the page's (the runtime takes it at the page's own size).
|
|
196
|
+
*/
|
|
197
|
+
export declare function pointOnPage(clicked: {
|
|
198
|
+
x: number;
|
|
199
|
+
y: number;
|
|
200
|
+
}, drawn: {
|
|
201
|
+
width: number;
|
|
202
|
+
height: number;
|
|
203
|
+
}, natural: {
|
|
204
|
+
width: number;
|
|
205
|
+
height: number;
|
|
206
|
+
}): {
|
|
207
|
+
x: number;
|
|
208
|
+
y: number;
|
|
209
|
+
};
|
|
157
210
|
/** The directory above a path of its working directory. */
|
|
158
211
|
export declare function parentOf(path: string): string;
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
export const COMPUTER_PARTS = ['browse', 'files', 'shell'];
|
|
7
7
|
/** The tools of each part, as the runtime gives them (`loop/apps/computer.py`). */
|
|
8
8
|
export const COMPUTER_PART_TOOLS = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
browse: [
|
|
10
|
+
'open_page',
|
|
11
|
+
'read_page',
|
|
12
|
+
'page_screenshot',
|
|
13
|
+
'click_on_page',
|
|
14
|
+
'type_on_page',
|
|
15
|
+
],
|
|
11
16
|
files: ['list_computer_files', 'read_computer_file', 'write_computer_file'],
|
|
12
17
|
shell: ['execute_code', 'run_skill_script'],
|
|
13
18
|
};
|
|
@@ -23,7 +28,17 @@ export const COMPUTER_WORDS = {
|
|
|
23
28
|
off: 'off',
|
|
24
29
|
none: 'It has no computer: browse, files and shell are all off. Turn one on in its permissions to give it one.',
|
|
25
30
|
offSaid: (parts) => `Off, so its agent is given no tool for it: ${parts.join(', ')}.`,
|
|
26
|
-
|
|
31
|
+
browser: 'Browser',
|
|
32
|
+
noBrowser: 'No browser is installed on its computer.',
|
|
33
|
+
noPage: 'Its browser has opened no page yet.',
|
|
34
|
+
pageAlt: (title) => `The page its browser shows: ${title}`,
|
|
35
|
+
clickToAct: 'Click on the page to click there; with the page selected, what you type goes to it.',
|
|
36
|
+
typeOnPage: 'Text to type on the page',
|
|
37
|
+
type: 'Type',
|
|
38
|
+
address: 'An address to open',
|
|
39
|
+
open: 'Open',
|
|
40
|
+
scrollUp: 'Scroll up',
|
|
41
|
+
scrollDown: 'Scroll down',
|
|
27
42
|
agentHas: 'Its agent has the computer.',
|
|
28
43
|
youHave: 'You have the computer: its agent waits for it until you hand it back.',
|
|
29
44
|
someoneHas: 'Somebody else has taken this computer over.',
|
|
@@ -105,7 +120,7 @@ export function outputOf(value) {
|
|
|
105
120
|
: text;
|
|
106
121
|
}
|
|
107
122
|
function commandOf(args) {
|
|
108
|
-
for (const key of ['code', 'path', 'script_name']) {
|
|
123
|
+
for (const key of ['code', 'path', 'script_name', 'url', 'selector']) {
|
|
109
124
|
const value = args[key];
|
|
110
125
|
if (typeof value === 'string' && value.trim()) {
|
|
111
126
|
return value;
|
|
@@ -225,6 +240,69 @@ export const runOnComputer = async (context, code) => (await asked(context, '/ru
|
|
|
225
240
|
method: 'POST',
|
|
226
241
|
body: JSON.stringify({ code }),
|
|
227
242
|
})).json();
|
|
243
|
+
/** The keys a person presses on its page, as the runtime takes them. */
|
|
244
|
+
export const BROWSER_KEYS = [
|
|
245
|
+
'Enter',
|
|
246
|
+
'Tab',
|
|
247
|
+
'Backspace',
|
|
248
|
+
'Escape',
|
|
249
|
+
'ArrowUp',
|
|
250
|
+
'ArrowDown',
|
|
251
|
+
'ArrowLeft',
|
|
252
|
+
'ArrowRight',
|
|
253
|
+
'PageUp',
|
|
254
|
+
'PageDown',
|
|
255
|
+
];
|
|
256
|
+
/**
|
|
257
|
+
* The page its browser shows, as a PNG; undefined before its first page
|
|
258
|
+
* (the runtime answers 404).
|
|
259
|
+
*/
|
|
260
|
+
export const readBrowserScreenshot = async (context) => {
|
|
261
|
+
const response = await fetch(computerUrl(context, '/browser/screenshot'), {
|
|
262
|
+
headers: context.token ? { Authorization: `Bearer ${context.token}` } : {},
|
|
263
|
+
cache: 'no-store',
|
|
264
|
+
});
|
|
265
|
+
if (response.status === 404) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
let detail = `${response.status}`;
|
|
270
|
+
try {
|
|
271
|
+
const body = (await response.json());
|
|
272
|
+
detail = typeof body.detail === 'string' ? body.detail : detail;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// A refusal without a sentence: its status says it.
|
|
276
|
+
}
|
|
277
|
+
throw new Error(detail);
|
|
278
|
+
}
|
|
279
|
+
return response.blob();
|
|
280
|
+
};
|
|
281
|
+
const onBrowser = async (context, step, body) => (await (await asked(context, `/browser/${step}`, {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
body: JSON.stringify(body),
|
|
284
|
+
})).json()).page;
|
|
285
|
+
/** Click on its page, at a point of its screenshot, as who took it over. */
|
|
286
|
+
export const clickOnBrowser = (context, x, y) => onBrowser(context, 'click', { x, y });
|
|
287
|
+
/** Type where its page's focus is. */
|
|
288
|
+
export const typeOnBrowser = (context, text) => onBrowser(context, 'type', { text });
|
|
289
|
+
/** Press one of `BROWSER_KEYS` on its page. */
|
|
290
|
+
export const pressOnBrowser = (context, key) => onBrowser(context, 'key', { key });
|
|
291
|
+
/** Scroll its page, down for a positive `dy`. */
|
|
292
|
+
export const scrollBrowser = (context, dy) => onBrowser(context, 'scroll', { dy });
|
|
293
|
+
/** Open an address in its browser. */
|
|
294
|
+
export const openOnBrowser = (context, url) => onBrowser(context, 'open', { url });
|
|
295
|
+
/**
|
|
296
|
+
* Where a click on a screenshot drawn at another size lands on the page: its
|
|
297
|
+
* pixels are the page's (the runtime takes it at the page's own size).
|
|
298
|
+
*/
|
|
299
|
+
export function pointOnPage(clicked, drawn, natural) {
|
|
300
|
+
const scale = (value, from, to) => from > 0 ? Math.round((value * to) / from) : value;
|
|
301
|
+
return {
|
|
302
|
+
x: Math.max(0, Math.min(natural.width, scale(clicked.x, drawn.width, natural.width))),
|
|
303
|
+
y: Math.max(0, Math.min(natural.height, scale(clicked.y, drawn.height, natural.height))),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
228
306
|
/** The directory above a path of its working directory. */
|
|
229
307
|
export function parentOf(path) {
|
|
230
308
|
const parts = path.split('/').filter(part => part && part !== '.');
|
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
* wrote, each with what came back, streamed from the conversation's tool
|
|
9
9
|
* calls;
|
|
10
10
|
* - its **files**, read-only, each to download, read again as each turn ends;
|
|
11
|
-
* - its **browser
|
|
11
|
+
* - its **browser**, when browse is on: the page it has open, live — a
|
|
12
|
+
* screenshot read again every moment;
|
|
12
13
|
* - **Take over**: what runs on it is interrupted and its agent's calls to it
|
|
13
|
-
* wait while the person runs code on it themselves
|
|
14
|
-
*
|
|
14
|
+
* wait while the person runs code on it themselves, and clicks and types
|
|
15
|
+
* in its page — a click on the screenshot is a click there; **Hand back**
|
|
16
|
+
* lets its agent go on.
|
|
15
17
|
*
|
|
16
18
|
* @module apps/plugins/app-computer/AppComputer
|
|
17
19
|
*/
|
|
18
20
|
import type { JSX } from 'react';
|
|
19
21
|
import type { AppSpec } from '../../../types/agentspecs';
|
|
20
22
|
import { type LoopWorkspaceContext } from '../../core';
|
|
23
|
+
/** How often the page its browser shows is read again, in milliseconds. */
|
|
24
|
+
export declare const BROWSER_POLL_MS = 1500;
|
|
21
25
|
export declare function AppComputer({ app, workspace, }: {
|
|
22
26
|
app: AppSpec;
|
|
23
27
|
workspace?: LoopWorkspaceContext;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button, Heading, Label, Spinner, Text, Textarea } from '@primer/react';
|
|
3
|
+
import { Button, Heading, Label, Spinner, Text, TextInput, Textarea, } from '@primer/react';
|
|
4
4
|
import { Box } from '@datalayer/primer-addons';
|
|
5
5
|
import { signal } from '@datalayer/reactor';
|
|
6
6
|
import { useContributions, useSignalValue } from '@datalayer/reactor/react';
|
|
7
7
|
import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
|
|
8
8
|
import { useChatWords } from '../../../chat/ChatLanguage';
|
|
9
9
|
import { LoopChatTurn, agentServerOf, noRuntimeSaid, IDLE_SANDBOX_SNAPSHOT, } from '../../core';
|
|
10
|
-
import { COMPUTER_PARTS, COMPUTER_WORDS, computerParts, downloadComputerFile, handBackComputer, hasComputer, listComputerFiles, parentOf, partsOff, readComputer, runOnComputer, takeOverComputer, terminalEntries, } from '../../apps/computer';
|
|
10
|
+
import { BROWSER_KEYS, COMPUTER_PARTS, COMPUTER_WORDS, clickOnBrowser, computerParts, downloadComputerFile, handBackComputer, hasComputer, listComputerFiles, openOnBrowser, parentOf, partsOff, pointOnPage, pressOnBrowser, readBrowserScreenshot, readComputer, runOnComputer, scrollBrowser, takeOverComputer, terminalEntries, typeOnBrowser, } from '../../apps/computer';
|
|
11
11
|
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
12
12
|
const NO_CONVERSATION = signal([]);
|
|
13
13
|
const MONO_FACE = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
|
|
@@ -71,6 +71,93 @@ function Files({ context, state, ended, }) {
|
|
|
71
71
|
: String(failed)));
|
|
72
72
|
}, children: COMPUTER_WORDS.download })] })) }, entry.path)))] }))] }));
|
|
73
73
|
}
|
|
74
|
+
/** How often the page its browser shows is read again, in milliseconds. */
|
|
75
|
+
export const BROWSER_POLL_MS = 1500;
|
|
76
|
+
function BrowserView({ context, state, mine, }) {
|
|
77
|
+
const [shot, setShot] = useState();
|
|
78
|
+
const [none, setNone] = useState(false);
|
|
79
|
+
const [page, setPage] = useState(state.page);
|
|
80
|
+
const [error, setError] = useState('');
|
|
81
|
+
const [text, setText] = useState('');
|
|
82
|
+
const [address, setAddress] = useState('');
|
|
83
|
+
const [asked, setAsked] = useState(0);
|
|
84
|
+
const shown = useRef(undefined);
|
|
85
|
+
useEffect(() => setPage(state.page), [state.page]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!state.browser) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
let cancelled = false;
|
|
91
|
+
const look = () => readBrowserScreenshot(context).then(blob => {
|
|
92
|
+
if (cancelled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setError('');
|
|
96
|
+
setNone(!blob);
|
|
97
|
+
if (shown.current) {
|
|
98
|
+
URL.revokeObjectURL(shown.current);
|
|
99
|
+
}
|
|
100
|
+
shown.current = blob ? URL.createObjectURL(blob) : undefined;
|
|
101
|
+
setShot(shown.current);
|
|
102
|
+
}, (failed) => !cancelled &&
|
|
103
|
+
setError(failed instanceof Error ? failed.message : String(failed)));
|
|
104
|
+
void look();
|
|
105
|
+
const every = setInterval(look, BROWSER_POLL_MS);
|
|
106
|
+
return () => {
|
|
107
|
+
cancelled = true;
|
|
108
|
+
clearInterval(every);
|
|
109
|
+
};
|
|
110
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
111
|
+
}, [context.serverUrl, context.agentId, context.token, state.browser, asked]);
|
|
112
|
+
useEffect(() => () => {
|
|
113
|
+
if (shown.current) {
|
|
114
|
+
URL.revokeObjectURL(shown.current);
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
const act = (step) => step.then(found => {
|
|
118
|
+
setPage(found);
|
|
119
|
+
setError('');
|
|
120
|
+
setAsked(previous => previous + 1);
|
|
121
|
+
}, (failed) => setError(failed instanceof Error ? failed.message : String(failed)));
|
|
122
|
+
const onClick = (event) => {
|
|
123
|
+
const image = event.currentTarget;
|
|
124
|
+
const box = image.getBoundingClientRect();
|
|
125
|
+
const point = pointOnPage({ x: event.clientX - box.left, y: event.clientY - box.top }, { width: box.width, height: box.height }, { width: image.naturalWidth, height: image.naturalHeight });
|
|
126
|
+
image.focus();
|
|
127
|
+
void act(clickOnBrowser(context, point.x, point.y));
|
|
128
|
+
};
|
|
129
|
+
const onKey = (event) => {
|
|
130
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (BROWSER_KEYS.includes(event.key)) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
void act(pressOnBrowser(context, event.key));
|
|
136
|
+
}
|
|
137
|
+
else if (event.key.length === 1) {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
void act(typeOnBrowser(context, event.key));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
return (_jsxs(Box, { "data-testid": "computer-browser", children: [_jsxs(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 1 }, children: [COMPUTER_WORDS.browser, page ? (_jsx(Text, { sx: { fontWeight: 'normal', color: 'fg.muted' }, children: ` · ${page.title || page.url}` })) : null] }), !state.browser ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: COMPUTER_WORDS.noBrowser })) : none || !shot ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0 }, children: none ? COMPUTER_WORDS.noPage : null })) : (_jsxs(_Fragment, { children: [page ? (_jsx(Text, { as: "p", sx: {
|
|
143
|
+
fontFamily: MONO_FACE,
|
|
144
|
+
fontSize: 0,
|
|
145
|
+
color: 'fg.muted',
|
|
146
|
+
m: 0,
|
|
147
|
+
mb: 1,
|
|
148
|
+
wordBreak: 'break-all',
|
|
149
|
+
}, children: page.url })) : null, _jsx("img", { src: shot, alt: COMPUTER_WORDS.pageAlt(page?.title || page?.url || ''), "data-testid": "computer-browser-page", tabIndex: mine ? 0 : -1, onClick: mine ? onClick : undefined, onKeyDown: mine ? onKey : undefined, style: {
|
|
150
|
+
width: '100%',
|
|
151
|
+
height: 'auto',
|
|
152
|
+
display: 'block',
|
|
153
|
+
border: '1px solid var(--borderColor-muted, #d0d7de)',
|
|
154
|
+
borderRadius: 6,
|
|
155
|
+
cursor: mine ? 'pointer' : 'default',
|
|
156
|
+
} })] })), error ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'danger.fg', m: 0, mt: 1 }, children: error })) : null, mine && state.browser ? (_jsxs(Box, { mt: 2, "data-testid": "computer-browser-controls", children: [_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0, mb: 1 }, children: COMPUTER_WORDS.clickToAct }), _jsxs(Box, { display: "flex", gap: 1, mb: 1, children: [_jsx(TextInput, { size: "small", "aria-label": COMPUTER_WORDS.typeOnPage, placeholder: COMPUTER_WORDS.typeOnPage, value: text, onChange: event => setText(event.target.value), sx: { flex: 1 } }), _jsx(Button, { size: "small", disabled: !text, onClick: () => {
|
|
157
|
+
void act(typeOnBrowser(context, text));
|
|
158
|
+
setText('');
|
|
159
|
+
}, children: COMPUTER_WORDS.type }), _jsx(Button, { size: "small", onClick: () => void act(pressOnBrowser(context, 'Enter')), children: "Enter" })] }), _jsxs(Box, { display: "flex", gap: 1, mb: 1, children: [_jsx(TextInput, { size: "small", "aria-label": COMPUTER_WORDS.address, placeholder: COMPUTER_WORDS.address, value: address, onChange: event => setAddress(event.target.value), sx: { flex: 1 } }), _jsx(Button, { size: "small", disabled: !address.trim(), onClick: () => void act(openOnBrowser(context, address.trim())), children: COMPUTER_WORDS.open })] }), _jsxs(Box, { display: "flex", gap: 1, children: [_jsx(Button, { size: "small", onClick: () => void act(scrollBrowser(context, -400)), children: COMPUTER_WORDS.scrollUp }), _jsx(Button, { size: "small", onClick: () => void act(scrollBrowser(context, 400)), children: COMPUTER_WORDS.scrollDown })] })] })) : null] }));
|
|
160
|
+
}
|
|
74
161
|
function YourTerminal({ context }) {
|
|
75
162
|
const [code, setCode] = useState('');
|
|
76
163
|
const [running, setRunning] = useState(false);
|
|
@@ -193,10 +280,10 @@ export function AppComputer({ app, workspace, }) {
|
|
|
193
280
|
});
|
|
194
281
|
};
|
|
195
282
|
const me = Boolean(state?.yours);
|
|
196
|
-
return (_jsxs(Box, { "data-testid": "app-computer", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: COMPUTER_WORDS.title }), _jsx(Parts, { app: app }), !used ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0, mt: 1 }, children: COMPUTER_WORDS.none })) : (_jsxs(_Fragment, { children: [
|
|
283
|
+
return (_jsxs(Box, { "data-testid": "app-computer", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: COMPUTER_WORDS.title }), _jsx(Parts, { app: app }), !used ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', m: 0, mt: 1 }, children: COMPUTER_WORDS.none })) : (_jsxs(_Fragment, { children: [noRuntime ? (_jsx(Text, { as: "p", role: "status", sx: { fontSize: 1, color: 'fg.default', m: 0, mt: 2 }, children: noRuntime })) : null, error ? (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'danger.fg', m: 0, mt: 2 }, children: error })) : null, state && context ? (_jsxs(Box, { mt: 2, "data-testid": "computer-holder", children: [_jsx(Text, { as: "p", sx: { fontSize: 1, m: 0, mb: 1 }, children: !state.held
|
|
197
284
|
? COMPUTER_WORDS.agentHas
|
|
198
285
|
: me
|
|
199
286
|
? COMPUTER_WORDS.youHave
|
|
200
|
-
: COMPUTER_WORDS.someoneHas }), !state.held ? (_jsx(Button, { size: "small", disabled: busy, onClick: () => act(takeOverComputer), children: COMPUTER_WORDS.takeOver })) : me ? (_jsxs(_Fragment, { children: [_jsx(Button, { size: "small", variant: "primary", disabled: busy, onClick: () => act(handBackComputer), children: COMPUTER_WORDS.handBack }), _jsx(YourTerminal, { context: context })] })) : null] })) : null, _jsx(Terminal, { conversation: conversation }), state && context ? (_jsx(Files, { context: context, state: state, ended: ended })) : null] }))] }));
|
|
287
|
+
: COMPUTER_WORDS.someoneHas }), !state.held ? (_jsx(Button, { size: "small", disabled: busy, onClick: () => act(takeOverComputer), children: COMPUTER_WORDS.takeOver })) : me ? (_jsxs(_Fragment, { children: [_jsx(Button, { size: "small", variant: "primary", disabled: busy, onClick: () => act(handBackComputer), children: COMPUTER_WORDS.handBack }), _jsx(YourTerminal, { context: context })] })) : null] })) : null, parts.browse && state && context ? (_jsx(BrowserView, { context: context, state: state, mine: me })) : null, _jsx(Terminal, { conversation: conversation }), state && context ? (_jsx(Files, { context: context, state: state, ended: ended })) : null] }))] }));
|
|
201
288
|
}
|
|
202
289
|
export default AppComputer;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An application's computer, beside its page (LOOP R-23).
|
|
3
3
|
*
|
|
4
|
-
* An application whose files and shell are on, drawn as the Studio's Preview
|
|
4
|
+
* An application whose browse, files and shell are on, drawn as the Studio's Preview
|
|
5
5
|
* draws it — `AppRenderer` with its `sidebar` — on the Local target: its
|
|
6
6
|
* agent runs on the local agent-runtimes server, and the computer view beside
|
|
7
|
-
* it shows what ran on its sandbox, its
|
|
8
|
-
*
|
|
7
|
+
* it shows the page its browser has open, what ran on its sandbox, its
|
|
8
|
+
* files, and *Take over* — clicks and typing then go to its page — and
|
|
9
|
+
* *Hand back*.
|
|
9
10
|
*
|
|
10
11
|
* @module examples/LoopAppComputerExample
|
|
11
12
|
*/
|
|
@@ -6,11 +6,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
/**
|
|
7
7
|
* An application's computer, beside its page (LOOP R-23).
|
|
8
8
|
*
|
|
9
|
-
* An application whose files and shell are on, drawn as the Studio's Preview
|
|
9
|
+
* An application whose browse, files and shell are on, drawn as the Studio's Preview
|
|
10
10
|
* draws it — `AppRenderer` with its `sidebar` — on the Local target: its
|
|
11
11
|
* agent runs on the local agent-runtimes server, and the computer view beside
|
|
12
|
-
* it shows what ran on its sandbox, its
|
|
13
|
-
*
|
|
12
|
+
* it shows the page its browser has open, what ran on its sandbox, its
|
|
13
|
+
* files, and *Take over* — clicks and typing then go to its page — and
|
|
14
|
+
* *Hand back*.
|
|
14
15
|
*
|
|
15
16
|
* @module examples/LoopAppComputerExample
|
|
16
17
|
*/
|
|
@@ -29,14 +30,18 @@ function computerDesk() {
|
|
|
29
30
|
id: 'computer-desk',
|
|
30
31
|
name: 'Computer Desk',
|
|
31
32
|
emoji: '🖥️',
|
|
32
|
-
description: 'Works on its own computer: runs code, reads and writes files.',
|
|
33
|
+
description: 'Works on its own computer: opens web pages, runs code, reads and writes files.',
|
|
33
34
|
connections: [],
|
|
34
35
|
rules: [],
|
|
35
|
-
instructions: 'You have a computer: run Python on it with your tools, and keep what you make in files there.',
|
|
36
|
+
instructions: 'You have a computer: open web pages in its browser, run Python on it with your tools, and keep what you make in files there.',
|
|
36
37
|
interface: {
|
|
37
38
|
...base.interface,
|
|
38
39
|
welcome: 'I work on my own computer. Ask me to compute something.',
|
|
39
40
|
starters: [
|
|
41
|
+
{
|
|
42
|
+
label: 'Open a page',
|
|
43
|
+
message: 'Open https://example.com in your browser, tell me its title and take a screenshot.',
|
|
44
|
+
},
|
|
40
45
|
{
|
|
41
46
|
label: 'Write a file',
|
|
42
47
|
message: 'Run Python that writes the squares of 1 to 5 in squares.txt, then print the file.',
|
|
@@ -45,7 +50,7 @@ function computerDesk() {
|
|
|
45
50
|
},
|
|
46
51
|
permissions: {
|
|
47
52
|
...base.permissions,
|
|
48
|
-
computer: { browse:
|
|
53
|
+
computer: { browse: true, files: true, shell: true },
|
|
49
54
|
},
|
|
50
55
|
};
|
|
51
56
|
}
|