@datalayer/agent-runtimes 1.3.103 → 1.3.104
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/{AgentDetails-CO9iM59C.js → AgentDetails-eWkEnFI1.js} +1 -1
- package/dist-embed/chunks/{AgentDetailsDialog-DmD6xm6k.js → AgentDetailsDialog-BXJek5nf.js} +1 -1
- package/dist-embed/chunks/{AgentInspector-kDMuvh45.js → AgentInspector-B1wLMu4Z.js} +1 -1
- package/dist-embed/chunks/{AgentInspectorDialog-fT96Pb4Z.js → AgentInspectorDialog-DcLrhdWo.js} +1 -1
- package/dist-embed/chunks/Avatar-BrPfm89P.js +1 -0
- package/dist-embed/chunks/{CellSidebarExtension-B-vao30u.js → CellSidebarExtension-BWsHtuqs.js} +2 -2
- package/dist-embed/chunks/ChatView-ef0jot57.js +32 -0
- package/dist-embed/chunks/DatalayerAgentBridge-DvlGUOdE.js +1 -0
- package/dist-embed/chunks/{DeckComponent-Fp6Vw8vN.js → DeckComponent-BfOXdQhk.js} +1 -1
- package/dist-embed/chunks/{DocumentView-DKUbyyJg.js → DocumentView-DCgaQoOU.js} +1 -1
- package/dist-embed/chunks/{EphemeralDocument-CgYPEfGC.js → EphemeralDocument-DVAfnQWS.js} +1 -1
- package/dist-embed/chunks/{EphemeralNotebook-DvDbCKnr.js → EphemeralNotebook-C1ANS_1u.js} +1 -1
- package/dist-embed/chunks/{ExcalidrawComponent-BehqBpwC.js → ExcalidrawComponent-0e3KqmKJ.js} +1 -1
- package/dist-embed/chunks/{ExcalidrawModal-B2sXbVJB.js → ExcalidrawModal-C5Or1sZ5.js} +1 -1
- package/dist-embed/chunks/{Flash-DdFxPtBI.js → Flash-C3nF3s4G.js} +1 -1
- package/dist-embed/chunks/{JupyterLexicalExtension-CUsjvchq.js → JupyterLexicalExtension-CEjN8cLT.js} +7 -7
- package/dist-embed/chunks/{KernelVariables-BUmOt4yF.js → KernelVariables-DyYN9sSS.js} +1 -1
- package/dist-embed/chunks/{NotebookSkeleton-D9WNoiWD.js → NotebookSkeleton-z8GKB7Tb.js} +1 -1
- package/dist-embed/chunks/{NotebookToolbar-C2_ZC9Ej.js → NotebookToolbar-Dv7QkxKA.js} +1 -1
- package/dist-embed/chunks/{NotebookView-BActuABH.js → NotebookView-CNHzc28A.js} +1 -1
- package/dist-embed/chunks/{SandboxDetailsDialog-C0afmOA7.js → SandboxDetailsDialog-D4HA7PZA.js} +1 -1
- package/dist-embed/chunks/{SchemaForm-B351kd79.js → SchemaForm-bWlj1cVy.js} +1 -1
- package/dist-embed/chunks/{Toolbar-B6ROXJ-2.js → Toolbar-BxAi9T92.js} +1 -1
- package/dist-embed/chunks/{YouTubePlugin-CqA-IwCx.js → YouTubePlugin-ZawlUPsx.js} +1 -1
- package/dist-embed/chunks/{ar-SA-G6X2FPQ2-DhQlC4AZ.js → ar-SA-G6X2FPQ2-DqE6ftcU.js} +1 -1
- package/dist-embed/chunks/{az-AZ-76LH7QW2-CDvJeI6H.js → az-AZ-76LH7QW2-8y_pqJhn.js} +1 -1
- package/dist-embed/chunks/{bg-BG-XCXSNQG7-B_2t7dwS.js → bg-BG-XCXSNQG7-4NX5jouU.js} +1 -1
- package/dist-embed/chunks/{bn-BD-2XOGV67Q-B7nTDj-4.js → bn-BD-2XOGV67Q-BB87_pS6.js} +1 -1
- package/dist-embed/chunks/{ca-ES-6MX7JW3Y-llJD_i9l.js → ca-ES-6MX7JW3Y-B9gyglRE.js} +1 -1
- package/dist-embed/chunks/{cs-CZ-2BRQDIVT-Cm_uiYT0.js → cs-CZ-2BRQDIVT-CmZhtHam.js} +1 -1
- package/dist-embed/chunks/{da-DK-5WZEPLOC-B0lo6-wZ.js → da-DK-5WZEPLOC-CU6rP6vm.js} +1 -1
- package/dist-embed/chunks/{de-DE-XR44H4JA-DZrN-mJo.js → de-DE-XR44H4JA-wk6_Fx0e.js} +1 -1
- package/dist-embed/chunks/{el-GR-BZB4AONW-C4D5ZbOc.js → el-GR-BZB4AONW-Cy5yeukb.js} +1 -1
- package/dist-embed/chunks/{es-ES-U4NZUMDT-D1yiS-ZI.js → es-ES-U4NZUMDT-Ccr3g95I.js} +1 -1
- package/dist-embed/chunks/{eu-ES-A7QVB2H4-Zm3lJsL6.js → eu-ES-A7QVB2H4-u0DKk40F.js} +1 -1
- package/dist-embed/chunks/{fa-IR-HGAKTJCU-Dhv2lg_O.js → fa-IR-HGAKTJCU-BYSSU2Gx.js} +1 -1
- package/dist-embed/chunks/{fi-FI-Z5N7JZ37-QeFFEfyg.js → fi-FI-Z5N7JZ37-98vDI-0h.js} +1 -1
- package/dist-embed/chunks/{fr-FR-RHASNOE6-DjbRpOS6.js → fr-FR-RHASNOE6-D2BvErn0.js} +1 -1
- package/dist-embed/chunks/{gl-ES-HMX3MZ6V-C-ev9dqC.js → gl-ES-HMX3MZ6V-DP7FRXtT.js} +1 -1
- package/dist-embed/chunks/{he-IL-6SHJWFNN-DHoNpuA6.js → he-IL-6SHJWFNN-B92ZDKBK.js} +1 -1
- package/dist-embed/chunks/{hi-IN-IWLTKZ5I-JZJZPoWj.js → hi-IN-IWLTKZ5I-Dw2EhOq2.js} +1 -1
- package/dist-embed/chunks/{hu-HU-A5ZG7DT2-RzCikyBW.js → hu-HU-A5ZG7DT2-CVh5JW9n.js} +1 -1
- package/dist-embed/chunks/{id-ID-SAP4L64H-Bn08nS9B.js → id-ID-SAP4L64H-CaKn7Yw0.js} +1 -1
- package/dist-embed/chunks/{index-DIHzcHI1.js → index-5Mm_lDnH.js} +1 -1
- package/dist-embed/chunks/{index-B1StFYI5.js → index-B46RcdCt.js} +1 -1
- package/dist-embed/chunks/{index-qzUKNibz.js → index-B6RMWdb4.js} +1 -1
- package/dist-embed/chunks/{index-88CEdMmU.js → index-BI_ojvKX.js} +1 -1
- package/dist-embed/chunks/{index-uutAM7NJ.js → index-BWxQD9cP.js} +1 -1
- package/dist-embed/chunks/{index-DCX_IqLg.js → index-B_WAs-60.js} +1 -1
- package/dist-embed/chunks/{index-BYofH50G.js → index-BaEB7wmN.js} +1 -1
- package/dist-embed/chunks/{index-C0iUgNwj.js → index-BmSB4k4X.js} +1 -1
- package/dist-embed/chunks/{index-KWzHwvvN.js → index-BsGIdntV.js} +4 -4
- package/dist-embed/chunks/{index-DgnVcOqG.js → index-BsP7mWx7.js} +1 -1
- package/dist-embed/chunks/{index-aX9x7qni.js → index-BzZvBTsd.js} +1 -1
- package/dist-embed/chunks/{index-BA2STD4N.js → index-C0ac25cM.js} +2 -2
- package/dist-embed/chunks/{index-DLk6Znxv.js → index-C0fJQ9v3.js} +1 -1
- package/dist-embed/chunks/{index-Bs1pVq7E.js → index-C0p6TrSH.js} +1 -1
- package/dist-embed/chunks/{index-DZN9JYOE.js → index-C9mAUugR.js} +1 -1
- package/dist-embed/chunks/{index-B1Egld01.js → index-CNBMan5A.js} +1 -1
- package/dist-embed/chunks/{index-1Ekpl0uC.js → index-CawAyW3R.js} +1 -1
- package/dist-embed/chunks/{index-DeYQGrTz.js → index-Cb-91zWU.js} +1 -1
- package/dist-embed/chunks/{index-B2dtLxcg.js → index-CkMGJGPw.js} +1 -1
- package/dist-embed/chunks/{index-Cqur_rhw.js → index-CsbfZWj5.js} +1 -1
- package/dist-embed/chunks/{index-DsX9nIHt.js → index-D1HhRyAi.js} +1 -1
- package/dist-embed/chunks/{index-C5xKtdLU.js → index-D3ml5vHy.js} +1 -1
- package/dist-embed/chunks/{index-CqKV027P.js → index-D7SzNRox.js} +1 -1
- package/dist-embed/chunks/{index-BUbwVG__.js → index-DAwoO3Uu.js} +1 -1
- package/dist-embed/chunks/{index-DHVYKZ44.js → index-DQaxtq0S.js} +1 -1
- package/dist-embed/chunks/{index-BbsUSk_Q.js → index-DbPtsB38.js} +1 -1
- package/dist-embed/chunks/{index-DJomc4za.js → index-DoYPeIKg.js} +1 -1
- package/dist-embed/chunks/{index-hqa_ODOD.js → index-Dr_NppDz.js} +1 -1
- package/dist-embed/chunks/{index-D7rnWGLu.js → index-DuDsMDnx.js} +3 -3
- package/dist-embed/chunks/{index-B5MiaXUV.js → index-IIxS-G5t.js} +1 -1
- package/dist-embed/chunks/{index-90Ss7516.js → index-W7nq_bBb.js} +1 -1
- package/dist-embed/chunks/{index-Cjx6uk6z.js → index-r_7Z9Uyr.js} +1 -1
- package/dist-embed/chunks/{index.esm-DlPZAW-m.js → index.esm-D5bfliIL.js} +1 -1
- package/dist-embed/chunks/{it-IT-JPQ66NNP-C2vJMuKM.js → it-IT-JPQ66NNP-CJGzhajv.js} +1 -1
- package/dist-embed/chunks/{ja-JP-DBVTYXUO-C8KOKQt9.js → ja-JP-DBVTYXUO-Jm7LgxUu.js} +1 -1
- package/dist-embed/chunks/{kaa-6HZHGXH3-CvxJbmMB.js → kaa-6HZHGXH3-DH21E_2q.js} +1 -1
- package/dist-embed/chunks/{kab-KAB-ZGHBKWFO-CQJhf9Nn.js → kab-KAB-ZGHBKWFO-2Cx_3mmx.js} +1 -1
- package/dist-embed/chunks/{kk-KZ-P5N5QNE5-Dl6YC0GH.js → kk-KZ-P5N5QNE5-C2v01t5f.js} +1 -1
- package/dist-embed/chunks/{km-KH-HSX4SM5Z-QYB-tJ4i.js → km-KH-HSX4SM5Z-WL_JAugw.js} +1 -1
- package/dist-embed/chunks/{ko-KR-MTYHY66A-D6yzBqJ5.js → ko-KR-MTYHY66A-DWjrj8WD.js} +1 -1
- package/dist-embed/chunks/{ku-TR-6OUDTVRD-C43l2362.js → ku-TR-6OUDTVRD-CwTRLeeu.js} +1 -1
- package/dist-embed/chunks/{lt-LT-XHIRWOB4-BG1v5NwF.js → lt-LT-XHIRWOB4-GVGjjS1d.js} +1 -1
- package/dist-embed/chunks/{lv-LV-5QDEKY6T-CAwm1y_0.js → lv-LV-5QDEKY6T-C29ftTES.js} +1 -1
- package/dist-embed/chunks/{markdown-CH2i4TVR.js → markdown-Bf4-A1yl.js} +1 -1
- package/dist-embed/chunks/{mr-IN-CRQNXWMA-DSk6q1fp.js → mr-IN-CRQNXWMA-Cyym_1yB.js} +1 -1
- package/dist-embed/chunks/{my-MM-5M5IBNSE-BRiKXulk.js → my-MM-5M5IBNSE-BOkGbWeI.js} +1 -1
- package/dist-embed/chunks/{nb-NO-T6EIAALU-CCDnctVd.js → nb-NO-T6EIAALU-BjaVTNUf.js} +1 -1
- package/dist-embed/chunks/{nl-NL-IS3SIHDZ-DwStD_Ch.js → nl-NL-IS3SIHDZ-DSVCFPQj.js} +1 -1
- package/dist-embed/chunks/{nn-NO-6E72VCQL-DNcfjETk.js → nn-NO-6E72VCQL-D5-_rRv0.js} +1 -1
- package/dist-embed/chunks/{oc-FR-POXYY2M6-BOjjS12P.js → oc-FR-POXYY2M6-CxKcn1R4.js} +1 -1
- package/dist-embed/chunks/{pa-IN-N4M65BXN-B4y5cv2_.js → pa-IN-N4M65BXN-D9r6O5PP.js} +1 -1
- package/dist-embed/chunks/{percentages-BXMCSKIN-BpahO1D1.js → percentages-BXMCSKIN-917PkDRT.js} +3 -3
- package/dist-embed/chunks/{pl-PL-T2D74RX3-CfuLoYHB.js → pl-PL-T2D74RX3-CFXCTA5m.js} +1 -1
- package/dist-embed/chunks/{plugin-BMH63P5M.js → plugin-CA_0EWiO.js} +1 -1
- package/dist-embed/chunks/plugin-LDJjixuG.js +1 -0
- package/dist-embed/chunks/{pt-BR-5N22H2LF-Fk60PfdA.js → pt-BR-5N22H2LF-7A-3KOon.js} +1 -1
- package/dist-embed/chunks/{pt-PT-UZXXM6DQ-SNAgJ5Vr.js → pt-PT-UZXXM6DQ-BJLo5w79.js} +1 -1
- package/dist-embed/chunks/{ro-RO-JPDTUUEW-YkxpKkWn.js → ro-RO-JPDTUUEW-DtvOR1_K.js} +1 -1
- package/dist-embed/chunks/{ru-RU-B4JR7IUQ-CaI1rtvc.js → ru-RU-B4JR7IUQ-CGQ0z7l6.js} +1 -1
- package/dist-embed/chunks/{sessions-ZDYLkdi_.js → sessions-By2lggFS.js} +2 -2
- package/dist-embed/chunks/{si-LK-N5RQ5JYF-CQz29xmh.js → si-LK-N5RQ5JYF-D13wg56d.js} +1 -1
- package/dist-embed/chunks/{sk-SK-C5VTKIMK-CZg7PVzd.js → sk-SK-C5VTKIMK-BqAZ0R3f.js} +1 -1
- package/dist-embed/chunks/{sl-SI-NN7IZMDC-D-EjMWyZ.js → sl-SI-NN7IZMDC-BjPUGdws.js} +1 -1
- package/dist-embed/chunks/{standalone-oyxre9MX.js → standalone-DyCMuq_1.js} +1 -1
- package/dist-embed/chunks/{subset-shared.chunk-BIvvzzX2.js → subset-shared.chunk-D-GUkcCO.js} +1 -1
- package/dist-embed/chunks/{subset-worker.chunk-DGgI6iRY.js → subset-worker.chunk-Bz8aK3Nt.js} +1 -1
- package/dist-embed/chunks/{sv-SE-XGPEYMSR-D8CsNMON.js → sv-SE-XGPEYMSR-DrXTnEWd.js} +1 -1
- package/dist-embed/chunks/{ta-IN-2NMHFXQM-CvuxKO7t.js → ta-IN-2NMHFXQM-bK104VxM.js} +1 -1
- package/dist-embed/chunks/{th-TH-HPSO5L25-NgQJigwq.js → th-TH-HPSO5L25-BSo6r7L4.js} +1 -1
- package/dist-embed/chunks/{tr-TR-DEFEU3FU-eu9-qLmi.js → tr-TR-DEFEU3FU-pYKvN-ro.js} +1 -1
- package/dist-embed/chunks/{uk-UA-QMV73CPH-CbjmuLVL.js → uk-UA-QMV73CPH-DFR0z9gx.js} +1 -1
- package/dist-embed/chunks/{usePrimerThemeBridge-LgREOVgz.js → usePrimerThemeBridge-CuRRiOcg.js} +1 -1
- package/dist-embed/chunks/{vi-VN-M7AON7JQ-u84yOuJ-.js → vi-VN-M7AON7JQ-BwMeNi1s.js} +1 -1
- package/dist-embed/chunks/{zh-CN-LNUGB5OW-ojkSgl-t.js → zh-CN-LNUGB5OW-QC_JIHYo.js} +1 -1
- package/dist-embed/chunks/{zh-HK-E62DVLB3-C6M89-Da.js → zh-HK-E62DVLB3-BHs8J1Gl.js} +1 -1
- package/dist-embed/chunks/{zh-TW-RAJ6MFWO-BaTtFORh.js → zh-TW-RAJ6MFWO-DpqpYlIS.js} +1 -1
- package/dist-embed/datalayer-app-main.js +330 -59
- package/lib/apps/apps/AppBrowserSandbox.d.ts +7 -0
- package/lib/apps/apps/AppBrowserSandbox.js +52 -0
- package/lib/apps/apps/AppRenderer.js +11 -2
- package/lib/apps/apps/appspec.js +31 -0
- package/lib/apps/apps/browserSandbox.d.ts +69 -0
- package/lib/apps/apps/browserSandbox.js +159 -0
- package/lib/apps/apps/index.d.ts +2 -0
- package/lib/apps/apps/index.js +2 -0
- package/lib/apps/plugins/chat/ChatView.js +59 -7
- package/lib/components/inspector/a2aSpans.js +5 -1
- package/lib/specs/apps.js +345 -8
- package/lib/specs/appspecSchema.js +79 -0
- package/lib/types/agentspecs.d.ts +28 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_apps.py +3 -2
- package/dist-embed/chunks/Avatar-Dqz3ZWjZ.js +0 -1
- package/dist-embed/chunks/ChatView-CubqJum2.js +0 -288
- package/dist-embed/chunks/DatalayerAgentBridge-BmJbZ2Lk.js +0 -1
- package/dist-embed/chunks/plugin-DZPXm2dP.js +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
2
|
+
export declare const APP_BROWSER_SANDBOX_PLUGIN_NAME = "@datalayer/loop-plugin-app-browser-sandbox";
|
|
3
|
+
/**
|
|
4
|
+
* The plugin, for one application. Made once per application — a new plugin
|
|
5
|
+
* rebuilds the workspace.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defineAppBrowserSandboxPlugin(app: Pick<AppSpec, 'id' | 'name' | 'samples'>): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* An application's code in the page's sandbox (STUDIO E-11): the tool its
|
|
7
|
+
* agent runs its code with when it turns in the page — `execute_code`,
|
|
8
|
+
* Python in this browser — and its documents put there before its first run
|
|
9
|
+
* (`browserSandbox`).
|
|
10
|
+
*
|
|
11
|
+
* Handed to the agent only while the sandbox is the browser's: on a runtime
|
|
12
|
+
* the agent has a sandbox of its own, and a tool of the page would be a
|
|
13
|
+
* second one beside it. `AppRenderer` adds the plugin for an application
|
|
14
|
+
* whose agent computes in code (`runsCodeInSandbox`).
|
|
15
|
+
*
|
|
16
|
+
* @module apps/apps/AppBrowserSandbox
|
|
17
|
+
*/
|
|
18
|
+
import { definePlugin } from '@datalayer/reactor';
|
|
19
|
+
import { LoopFrontendTool } from '../core';
|
|
20
|
+
import { AGENTS_PLUGIN_NAME, AgentsPlugin, } from '../plugins/agents';
|
|
21
|
+
import { browserCodeTool } from './browserSandbox';
|
|
22
|
+
export const APP_BROWSER_SANDBOX_PLUGIN_NAME = '@datalayer/loop-plugin-app-browser-sandbox';
|
|
23
|
+
/**
|
|
24
|
+
* The plugin, for one application. Made once per application — a new plugin
|
|
25
|
+
* rebuilds the workspace.
|
|
26
|
+
*/
|
|
27
|
+
export function defineAppBrowserSandboxPlugin(app) {
|
|
28
|
+
return definePlugin({
|
|
29
|
+
name: `${APP_BROWSER_SANDBOX_PLUGIN_NAME}-${app.id}`,
|
|
30
|
+
displayName: `${app.name}: its code in the browser`,
|
|
31
|
+
description: `What ${app.name} computes, run in Python in this browser when it turns in the page.`,
|
|
32
|
+
octicon: 'code',
|
|
33
|
+
emoji: '\u{1F40D}',
|
|
34
|
+
// The sandbox is the Agents plugin's: built first, read here.
|
|
35
|
+
dependencies: [AgentsPlugin],
|
|
36
|
+
register({ contribute, reactor }) {
|
|
37
|
+
const sandbox = reactor.getOutput(AGENTS_PLUGIN_NAME)?.sandbox;
|
|
38
|
+
if (!sandbox) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// One tool for the life of the workspace: its documents are put in the
|
|
42
|
+
// kernel once, before its first run.
|
|
43
|
+
const tool = browserCodeTool(app, code => sandbox.execute(code));
|
|
44
|
+
return contribute(LoopFrontendTool, {
|
|
45
|
+
id: `app-browser-sandbox-${app.id}`,
|
|
46
|
+
tools: workspace => workspace.sandbox.target === 'browser' ? [tool] : [],
|
|
47
|
+
// It changes no editor: kept in the chat view and beside a page.
|
|
48
|
+
chatView: true,
|
|
49
|
+
}, { id: `app-browser-sandbox-${app.id}` });
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -74,6 +74,8 @@ import { defineAppRuntimePlugin } from './AppRuntimePlugins';
|
|
|
74
74
|
import { hasFolderModules } from './customComponents';
|
|
75
75
|
import { appPluginPair } from './pluginPair';
|
|
76
76
|
import { visitorAgentOf } from './visitorToken';
|
|
77
|
+
import { defineAppBrowserSandboxPlugin } from './AppBrowserSandbox';
|
|
78
|
+
import { browserSandboxNote, runsCodeInSandbox } from './browserSandbox';
|
|
77
79
|
/** The id of an agent or a Cog, without its version. */
|
|
78
80
|
export const agentIdOf = (app) => {
|
|
79
81
|
const reference = app.agent || app.team;
|
|
@@ -127,8 +129,12 @@ export function defineAppPlugin(app, shown = app) {
|
|
|
127
129
|
suggestions: startersAsOpeners(shown.interface.starters),
|
|
128
130
|
// In the page, the agent is told what a runtime tells it: its own
|
|
129
131
|
// spec's prompt, then the application's instructions; on the
|
|
130
|
-
// application's model when it names one (`apps/apps/agent`).
|
|
131
|
-
|
|
132
|
+
// application's model when it names one (`apps/apps/agent`). An agent
|
|
133
|
+
// that computes in code is told where its code runs there, and where its
|
|
134
|
+
// documents are (STUDIO E-11, `browserSandbox`).
|
|
135
|
+
instructions: runsCodeInSandbox(app)
|
|
136
|
+
? [app.instructions, browserSandboxNote(app)].filter(Boolean).join('\n\n')
|
|
137
|
+
: app.instructions,
|
|
132
138
|
model: app.model,
|
|
133
139
|
// Its modes and its profiles (LOOP P-19, P-20), as the session API
|
|
134
140
|
// applies them: the profile's instructions and those of the options
|
|
@@ -238,6 +244,9 @@ export function appPreset(app, options = {}) {
|
|
|
238
244
|
// page side read from it when its components are files of its folder
|
|
239
245
|
// (P-29).
|
|
240
246
|
defineAppRuntimePlugin(app.id, { pageSide: hasFolderModules(app) }),
|
|
247
|
+
// Its code in the browser's sandbox, when it turns in the page and
|
|
248
|
+
// computes in code (STUDIO E-11): `execute_code`, its documents there.
|
|
249
|
+
...(runsCodeInSandbox(app) ? [defineAppBrowserSandboxPlugin(app)] : []),
|
|
241
250
|
...(withPage ? [defineAppPagePlugin(shown), ...blocks] : []),
|
|
242
251
|
// The components its developer wrote (LOOP P-17): blocks of its page
|
|
243
252
|
// and renderers of its page, its elements and its answers — its own.
|
package/lib/apps/apps/appspec.js
CHANGED
|
@@ -150,6 +150,7 @@ export function emptyAppspec(kind = 'chat') {
|
|
|
150
150
|
triggers: [],
|
|
151
151
|
memory: '',
|
|
152
152
|
notifications: [],
|
|
153
|
+
samples: { documents: [], alternatives: [] },
|
|
153
154
|
setup: [],
|
|
154
155
|
enabled: true,
|
|
155
156
|
unavailable_because: '',
|
|
@@ -191,6 +192,7 @@ const KNOWN_KEYS = [
|
|
|
191
192
|
'memory',
|
|
192
193
|
'notifications',
|
|
193
194
|
'decision',
|
|
195
|
+
'samples',
|
|
194
196
|
'enabled',
|
|
195
197
|
'unavailable_because',
|
|
196
198
|
'tags',
|
|
@@ -542,6 +544,21 @@ function parseDecision(data) {
|
|
|
542
544
|
decisionModel: text(data.decision_model),
|
|
543
545
|
};
|
|
544
546
|
}
|
|
547
|
+
/** What it is tried on before it is anybody's (STUDIO E-06, E-11): read only. */
|
|
548
|
+
function parseSamples(data) {
|
|
549
|
+
return {
|
|
550
|
+
documents: records(data.documents).map(document => ({
|
|
551
|
+
name: text(document.name),
|
|
552
|
+
file: text(document.file),
|
|
553
|
+
text: text(document.text),
|
|
554
|
+
})),
|
|
555
|
+
alternatives: records(data.alternatives).map(alternative => ({
|
|
556
|
+
name: text(alternative.name),
|
|
557
|
+
evidence: text(alternative.evidence),
|
|
558
|
+
metrics: Object.fromEntries(Object.entries(isData(alternative.metrics) ? alternative.metrics : {}).filter((entry) => typeof entry[1] === 'number')),
|
|
559
|
+
})),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
545
562
|
function parseDeployment(data) {
|
|
546
563
|
const deployment = {};
|
|
547
564
|
if (isData(data.hosted)) {
|
|
@@ -707,6 +724,7 @@ export function parseAppspec(document) {
|
|
|
707
724
|
triggers: records(data.triggers).map(parseTrigger),
|
|
708
725
|
memory: text(data.memory),
|
|
709
726
|
notifications: texts(data.notifications),
|
|
727
|
+
samples: parseSamples(isData(data.samples) ? data.samples : {}),
|
|
710
728
|
enabled: flag(data.enabled, true),
|
|
711
729
|
unavailable_because: text(data.unavailable_because),
|
|
712
730
|
tags: texts(data.tags),
|
|
@@ -915,6 +933,18 @@ function dumpDecision(decision) {
|
|
|
915
933
|
.part('weights', sorted(scenario.weights)).data))
|
|
916
934
|
.text('decision_model', decision.decisionModel).data;
|
|
917
935
|
}
|
|
936
|
+
function dumpSamples(samples) {
|
|
937
|
+
return new Writer()
|
|
938
|
+
.list('documents', samples.documents.map(document => new Writer()
|
|
939
|
+
.text('name', document.name, '\u0000')
|
|
940
|
+
.text('file', document.file, '\u0000')
|
|
941
|
+
.text('text', document.text, '\u0000').data))
|
|
942
|
+
.list('alternatives', samples.alternatives.map(alternative => new Writer()
|
|
943
|
+
.text('name', alternative.name, '\u0000')
|
|
944
|
+
.text('evidence', alternative.evidence, '\u0000')
|
|
945
|
+
// By criterion name, as written: the spec keeps their order.
|
|
946
|
+
.part('metrics', { ...alternative.metrics }).data)).data;
|
|
947
|
+
}
|
|
918
948
|
/**
|
|
919
949
|
* An application as the document its file holds.
|
|
920
950
|
*
|
|
@@ -1037,6 +1067,7 @@ export function dumpAppspec(app) {
|
|
|
1037
1067
|
writer.data.decision = dumpDecision(app.decision);
|
|
1038
1068
|
}
|
|
1039
1069
|
writer
|
|
1070
|
+
.part('samples', dumpSamples(app.samples))
|
|
1040
1071
|
.value('enabled', app.enabled, true)
|
|
1041
1072
|
.text('unavailable_because', app.unavailable_because)
|
|
1042
1073
|
.list('tags', app.tags)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An application's code, run in the page (STUDIO E-11).
|
|
3
|
+
*
|
|
4
|
+
* On a runtime, an application's agent runs its code in a sandbox of the
|
|
5
|
+
* runtime's, through `execute_code` — what *Quote Calculator* and *Report
|
|
6
|
+
* from a File* compute with. Turned in the page, the same agent has the
|
|
7
|
+
* page's sandbox instead: Python in this browser (Pyodide), the one the
|
|
8
|
+
* Browser target starts. Nothing of the code runs on Datalayer, and nothing
|
|
9
|
+
* it writes leaves the browser.
|
|
10
|
+
*
|
|
11
|
+
* What it computes from is put there as files, under {@link SANDBOX_INPUTS}:
|
|
12
|
+
* the documents Datalayer publishes with the application (`samples`, read
|
|
13
|
+
* only) before its first run, and the files given on its page with the
|
|
14
|
+
* message that runs on them — which the page used to refuse, having no
|
|
15
|
+
* session to hand them to.
|
|
16
|
+
*
|
|
17
|
+
* Pure: no React, no kernel. The tool that runs it is
|
|
18
|
+
* `AppBrowserSandboxPlugin`'s.
|
|
19
|
+
*
|
|
20
|
+
* @module apps/apps/browserSandbox
|
|
21
|
+
*/
|
|
22
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
23
|
+
import type { FrontendToolDefinition } from '../../types/tools';
|
|
24
|
+
import type { SandboxExecution } from '../plugins/agents/service';
|
|
25
|
+
/** The tool the agent runs its code with: the name a runtime gives it too. */
|
|
26
|
+
export declare const BROWSER_CODE_TOOL = "execute_code";
|
|
27
|
+
/** Where its files are, in the page's sandbox: in memory, gone with the page. */
|
|
28
|
+
export declare const SANDBOX_INPUTS = "/tmp/inputs";
|
|
29
|
+
/** A file for the sandbox: its name there, and what it holds, in base64. */
|
|
30
|
+
export type SandboxFile = {
|
|
31
|
+
file: string;
|
|
32
|
+
base64: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Whether an application's agent runs code: its agent has a sandbox, as every
|
|
36
|
+
* agent a runtime gives `execute_code` has.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runsCodeInSandbox(app: Pick<AppSpec, 'agent'>): boolean;
|
|
39
|
+
/** Where a file is, in the page's sandbox. */
|
|
40
|
+
export declare const sandboxPath: (file: string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* A file's name as the sandbox is given it: its own, without a folder, and
|
|
43
|
+
* only the characters a path says plainly.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sandboxFileName(name: string): string;
|
|
46
|
+
/** UTF-8 text as base64, as the sandbox is given it. */
|
|
47
|
+
export declare function textAsBase64(text: string): string;
|
|
48
|
+
/** The base64 of a `data:` URL, as a page's file is given — or why it has none. */
|
|
49
|
+
export declare function dataUrlBase64(dataUrl: string): string;
|
|
50
|
+
/** The documents Datalayer publishes with the application, as files. */
|
|
51
|
+
export declare function sampleFiles(app: Pick<AppSpec, 'samples'>): SandboxFile[];
|
|
52
|
+
/** The Python that writes files into the sandbox. */
|
|
53
|
+
export declare function writeFilesCode(files: SandboxFile[]): string;
|
|
54
|
+
/**
|
|
55
|
+
* What the agent is told in the page, after its own prompt and the
|
|
56
|
+
* application's instructions: where its code runs, and where its documents
|
|
57
|
+
* are.
|
|
58
|
+
*/
|
|
59
|
+
export declare function browserSandboxNote(app: Pick<AppSpec, 'samples'>): string;
|
|
60
|
+
/** What the message that runs on given files says of them. */
|
|
61
|
+
export declare function givenFilesSentence(files: string[]): string;
|
|
62
|
+
/** What the model reads of an execution: what it printed, what it returned, what failed. */
|
|
63
|
+
export declare function executionForModel(execution: SandboxExecution): Record<string, unknown>;
|
|
64
|
+
/**
|
|
65
|
+
* The tool an application's agent runs its code with, in the page: Python
|
|
66
|
+
* in the browser's sandbox, its documents written there before the first
|
|
67
|
+
* call.
|
|
68
|
+
*/
|
|
69
|
+
export declare function browserCodeTool(app: Pick<AppSpec, 'samples'>, execute: (code: string) => Promise<SandboxExecution>): FrontendToolDefinition;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { resolveAgentspec } from './agent';
|
|
6
|
+
/** The tool the agent runs its code with: the name a runtime gives it too. */
|
|
7
|
+
export const BROWSER_CODE_TOOL = 'execute_code';
|
|
8
|
+
/** Where its files are, in the page's sandbox: in memory, gone with the page. */
|
|
9
|
+
export const SANDBOX_INPUTS = '/tmp/inputs';
|
|
10
|
+
/**
|
|
11
|
+
* Whether an application's agent runs code: its agent has a sandbox, as every
|
|
12
|
+
* agent a runtime gives `execute_code` has.
|
|
13
|
+
*/
|
|
14
|
+
export function runsCodeInSandbox(app) {
|
|
15
|
+
return Boolean(app.agent && resolveAgentspec(app.agent)?.sandboxVariant);
|
|
16
|
+
}
|
|
17
|
+
/** Where a file is, in the page's sandbox. */
|
|
18
|
+
export const sandboxPath = (file) => `${SANDBOX_INPUTS}/${file}`;
|
|
19
|
+
/**
|
|
20
|
+
* A file's name as the sandbox is given it: its own, without a folder, and
|
|
21
|
+
* only the characters a path says plainly.
|
|
22
|
+
*/
|
|
23
|
+
export function sandboxFileName(name) {
|
|
24
|
+
const base = name.split(/[\\/]/).pop() ?? '';
|
|
25
|
+
const plain = base.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[.-]+/, '');
|
|
26
|
+
return plain || 'file';
|
|
27
|
+
}
|
|
28
|
+
/** UTF-8 text as base64, as the sandbox is given it. */
|
|
29
|
+
export function textAsBase64(text) {
|
|
30
|
+
const bytes = new TextEncoder().encode(text);
|
|
31
|
+
let binary = '';
|
|
32
|
+
for (const byte of bytes) {
|
|
33
|
+
binary += String.fromCharCode(byte);
|
|
34
|
+
}
|
|
35
|
+
return btoa(binary);
|
|
36
|
+
}
|
|
37
|
+
/** The base64 of a `data:` URL, as a page's file is given — or why it has none. */
|
|
38
|
+
export function dataUrlBase64(dataUrl) {
|
|
39
|
+
const at = dataUrl.indexOf(',');
|
|
40
|
+
if (!dataUrl.startsWith('data:') ||
|
|
41
|
+
at < 0 ||
|
|
42
|
+
!/;base64$/.test(dataUrl.slice(0, at))) {
|
|
43
|
+
throw new Error('A file given on the page is not a base64 data URL.');
|
|
44
|
+
}
|
|
45
|
+
return dataUrl.slice(at + 1);
|
|
46
|
+
}
|
|
47
|
+
/** The documents Datalayer publishes with the application, as files. */
|
|
48
|
+
export function sampleFiles(app) {
|
|
49
|
+
return (app.samples?.documents ?? []).map(document => ({
|
|
50
|
+
file: document.file,
|
|
51
|
+
base64: textAsBase64(document.text),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
/** The Python that writes files into the sandbox. */
|
|
55
|
+
export function writeFilesCode(files) {
|
|
56
|
+
const entries = files
|
|
57
|
+
.map(({ file, base64 }) => ` (${JSON.stringify(file)}, ${JSON.stringify(base64)}),`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
return [
|
|
60
|
+
'import base64 as _b64, pathlib as _pl',
|
|
61
|
+
`_inputs = _pl.Path(${JSON.stringify(SANDBOX_INPUTS)})`,
|
|
62
|
+
'_inputs.mkdir(parents=True, exist_ok=True)',
|
|
63
|
+
'for _name, _data in [',
|
|
64
|
+
entries,
|
|
65
|
+
']:',
|
|
66
|
+
' (_inputs / _name).write_bytes(_b64.b64decode(_data))',
|
|
67
|
+
'del _b64, _pl, _inputs',
|
|
68
|
+
].join('\n');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What the agent is told in the page, after its own prompt and the
|
|
72
|
+
* application's instructions: where its code runs, and where its documents
|
|
73
|
+
* are.
|
|
74
|
+
*/
|
|
75
|
+
export function browserSandboxNote(app) {
|
|
76
|
+
const documents = app.samples?.documents ?? [];
|
|
77
|
+
return [
|
|
78
|
+
`Your code runs in Python (Pyodide) in this browser, through \`${BROWSER_CODE_TOOL}\`. ` +
|
|
79
|
+
'Variables and imports persist between calls. Compute every figure there, ' +
|
|
80
|
+
'print it, and answer from what it printed.',
|
|
81
|
+
...(documents.length > 0
|
|
82
|
+
? [
|
|
83
|
+
'The documents you answer from are files there, read only: ' +
|
|
84
|
+
documents
|
|
85
|
+
.map(document => `${document.name} at \`${sandboxPath(document.file)}\``)
|
|
86
|
+
.join('; ') +
|
|
87
|
+
'. Read them in code before you answer.',
|
|
88
|
+
]
|
|
89
|
+
: []),
|
|
90
|
+
`A file given on the page is put in \`${SANDBOX_INPUTS}\` too: the message that runs on it names it.`,
|
|
91
|
+
].join('\n\n');
|
|
92
|
+
}
|
|
93
|
+
/** What the message that runs on given files says of them. */
|
|
94
|
+
export function givenFilesSentence(files) {
|
|
95
|
+
return `The files given are in the sandbox: ${files.map(file => `\`${sandboxPath(file)}\``).join(', ')}.`;
|
|
96
|
+
}
|
|
97
|
+
/** What the model reads of an execution: what it printed, what it returned, what failed. */
|
|
98
|
+
export function executionForModel(execution) {
|
|
99
|
+
const results = (execution.outputs ?? []).flatMap(output => {
|
|
100
|
+
const data = output ??
|
|
101
|
+
{};
|
|
102
|
+
if (data.output_type !== 'execute_result' &&
|
|
103
|
+
data.output_type !== 'display_data') {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const plain = data.data?.['text/plain'];
|
|
107
|
+
return typeof plain === 'string' ? [plain] : [];
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
success: execution.success,
|
|
111
|
+
...(execution.stdout ? { stdout: execution.stdout } : {}),
|
|
112
|
+
...(results.length > 0 ? { results } : {}),
|
|
113
|
+
...(execution.error ? { error: execution.error } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The tool an application's agent runs its code with, in the page: Python
|
|
118
|
+
* in the browser's sandbox, its documents written there before the first
|
|
119
|
+
* call.
|
|
120
|
+
*/
|
|
121
|
+
export function browserCodeTool(app, execute) {
|
|
122
|
+
let seeded = null;
|
|
123
|
+
const seed = () => {
|
|
124
|
+
const files = sampleFiles(app);
|
|
125
|
+
seeded =
|
|
126
|
+
seeded ??
|
|
127
|
+
(files.length === 0
|
|
128
|
+
? Promise.resolve()
|
|
129
|
+
: execute(writeFilesCode(files)).then(written => {
|
|
130
|
+
if (!written.success) {
|
|
131
|
+
seeded = null;
|
|
132
|
+
throw new Error(`The application's documents could not be put in the sandbox: ${written.error ?? 'it failed'}.`);
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
return seeded;
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
name: BROWSER_CODE_TOOL,
|
|
139
|
+
description: 'Run Python in the sandbox, in this browser (Pyodide). Variables and imports ' +
|
|
140
|
+
`persist between calls. The application's documents and the files given on ` +
|
|
141
|
+
`its page are under ${SANDBOX_INPUTS}. Returns what it printed, what it ` +
|
|
142
|
+
'returned and any error.',
|
|
143
|
+
parameters: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
code: { type: 'string', description: 'The Python to run.' },
|
|
147
|
+
},
|
|
148
|
+
required: ['code'],
|
|
149
|
+
},
|
|
150
|
+
handler: async (args) => {
|
|
151
|
+
const code = typeof args?.code === 'string' ? args.code : '';
|
|
152
|
+
if (!code.trim()) {
|
|
153
|
+
return { success: false, error: 'No code was given to run.' };
|
|
154
|
+
}
|
|
155
|
+
await seed();
|
|
156
|
+
return executionForModel(await execute(code));
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
package/lib/apps/apps/index.d.ts
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* @module apps/apps
|
|
5
5
|
*/
|
|
6
6
|
export * from './agent';
|
|
7
|
+
export * from './AppBrowserSandbox';
|
|
7
8
|
export * from './AppComposer';
|
|
8
9
|
export * from './AppFeedback';
|
|
9
10
|
export * from './AppKept';
|
|
10
11
|
export * from './AppRenderer';
|
|
11
12
|
export * from './appspec';
|
|
13
|
+
export * from './browserSandbox';
|
|
12
14
|
export * from './checks';
|
|
13
15
|
export * from './composer';
|
|
14
16
|
export * from './computer';
|
package/lib/apps/apps/index.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* @module apps/apps
|
|
9
9
|
*/
|
|
10
10
|
export * from './agent';
|
|
11
|
+
export * from './AppBrowserSandbox';
|
|
11
12
|
export * from './AppComposer';
|
|
12
13
|
export * from './AppFeedback';
|
|
13
14
|
export * from './AppKept';
|
|
14
15
|
export * from './AppRenderer';
|
|
15
16
|
export * from './appspec';
|
|
17
|
+
export * from './browserSandbox';
|
|
16
18
|
export * from './checks';
|
|
17
19
|
export * from './composer';
|
|
18
20
|
export * from './computer';
|
|
@@ -37,7 +37,8 @@ import { INPUT_PROMPT_PLUGIN_NAME, } from '../input-prompt';
|
|
|
37
37
|
import { agentRuntimeStore, useAgentRuntimeContextSnapshot, } from '../../../stores';
|
|
38
38
|
import { useIAMStore } from '../../../state';
|
|
39
39
|
import { useVisitorToken } from '../../apps/visitorToken';
|
|
40
|
-
import { AGENTS_PLUGIN_NAME } from '../agents/plugin';
|
|
40
|
+
import { AGENTS_PLUGIN_NAME, } from '../agents/plugin';
|
|
41
|
+
import { BROWSER_CODE_TOOL, dataUrlBase64, givenFilesSentence, sandboxFileName, writeFilesCode, } from '../../apps/browserSandbox';
|
|
41
42
|
import { useConfig } from '../../../hooks/useConfig';
|
|
42
43
|
import { useSkills, useSkillActions } from '../../../hooks/useSkills';
|
|
43
44
|
import { AI_MODEL_CATALOGUE, isChatModel } from '../../../specs/models';
|
|
@@ -357,6 +358,18 @@ export default function ChatView({ workspace }) {
|
|
|
357
358
|
const heldForAdapter = useRef([]);
|
|
358
359
|
/** Whether the agent is turned in this page: read by `sendNow`, set below. */
|
|
359
360
|
const inPageRef = useRef(false);
|
|
361
|
+
/**
|
|
362
|
+
* The page's sandbox, when the agent turned in the page runs its code
|
|
363
|
+
* there (`execute_code`, STUDIO E-11): what a file given on the page is
|
|
364
|
+
* put in. Read by `sendNow`, set below.
|
|
365
|
+
*/
|
|
366
|
+
const pageSandboxRef = useRef(undefined);
|
|
367
|
+
/**
|
|
368
|
+
* Why its host closed the conversation (`ChatAvailabilityProvider`), or
|
|
369
|
+
* nothing: a page's own send is refused with it as the composer is — a
|
|
370
|
+
* stopped deployment, a visitor's turns all taken. Set below.
|
|
371
|
+
*/
|
|
372
|
+
const hostClosedRef = useRef(undefined);
|
|
360
373
|
/** What goes with every run (an application's modes, LOOP P-19), read when it is sent. */
|
|
361
374
|
const runProps = useContributions(LoopRunProps);
|
|
362
375
|
const runPropsRef = useRef(runProps);
|
|
@@ -369,18 +382,49 @@ export default function ChatView({ workspace }) {
|
|
|
369
382
|
*
|
|
370
383
|
* What the page did besides the message goes with it as AG-UI's
|
|
371
384
|
* `forwardedProps` (`loop`, LOOP R-04): the application's session takes a
|
|
372
|
-
* file there. An agent turned in this page has no session
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
385
|
+
* file there. An agent turned in this page has no session: one that runs
|
|
386
|
+
* its code in the page's sandbox has the files put there, and the message
|
|
387
|
+
* names them (STUDIO E-11); to any other no file is handed — said, rather
|
|
388
|
+
* than dropped. What the workspace's plugins send with every run
|
|
389
|
+
* (`LoopRunProps`: the modes the person is in, LOOP P-19) goes under it —
|
|
390
|
+
* and an agent turned in this page applies the modes itself (`modeEffect`,
|
|
391
|
+
* `BrowserAgentAdapter`).
|
|
377
392
|
*/
|
|
378
393
|
const sendNow = useCallback((message, given) => {
|
|
394
|
+
if (hostClosedRef.current) {
|
|
395
|
+
return hostClosedRef.current;
|
|
396
|
+
}
|
|
379
397
|
const forwardedProps = runForwardedProps(runPropsRef.current.map(entry => entry.value), given);
|
|
380
398
|
// The page's files, or those attached in the composer (LOOP P-21).
|
|
381
399
|
const loop = forwardedProps?.loop;
|
|
382
400
|
if (inPageRef.current && loop?.files && loop.files.length > 0) {
|
|
383
|
-
|
|
401
|
+
const sandbox = pageSandboxRef.current;
|
|
402
|
+
if (!sandbox) {
|
|
403
|
+
return 'A file is given to an application running on a runtime: run it on Datalayer or on your machine to give it one.';
|
|
404
|
+
}
|
|
405
|
+
let files;
|
|
406
|
+
try {
|
|
407
|
+
files = loop.files.map(file => ({
|
|
408
|
+
file: sandboxFileName(String(file.name ?? '')),
|
|
409
|
+
base64: dataUrlBase64(String(file.data_url ?? '')),
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
return error instanceof Error ? error.message : String(error);
|
|
414
|
+
}
|
|
415
|
+
// Put in the sandbox first, then sent: the message names where.
|
|
416
|
+
void sandbox.execute(writeFilesCode(files)).then(written => {
|
|
417
|
+
if (!written.success) {
|
|
418
|
+
setTransient(`The files could not be put in the sandbox: ${written.error ?? 'it failed'}.`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// Sent as any message is, its files now in the sandbox.
|
|
422
|
+
sendNow(`${message}\n\n${givenFilesSentence(files.map(file => file.file))}`, {
|
|
423
|
+
...forwardedProps,
|
|
424
|
+
loop: Object.fromEntries(Object.entries(loop).filter(([key]) => key !== 'files')),
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
384
428
|
}
|
|
385
429
|
// A new turn: whatever the panel showed is gone, this message is it.
|
|
386
430
|
turnFeedRef.current?.begin(message, controlsRef.current?.thread());
|
|
@@ -938,6 +982,11 @@ export default function ChatView({ workspace }) {
|
|
|
938
982
|
...commandTools.filter(tool => !taken.has(tool.name)),
|
|
939
983
|
];
|
|
940
984
|
}, [notebookTools, commandTools]);
|
|
985
|
+
// The page's sandbox, while the agent turned here runs its code in it.
|
|
986
|
+
pageSandboxRef.current =
|
|
987
|
+
inPage && agentTools.some(tool => tool.name === BROWSER_CODE_TOOL)
|
|
988
|
+
? reactor.getOutput(AGENTS_PLUGIN_NAME)?.sandbox
|
|
989
|
+
: undefined;
|
|
941
990
|
/*
|
|
942
991
|
* Where this workspace's agent actually lives.
|
|
943
992
|
*
|
|
@@ -1045,6 +1094,9 @@ export default function ChatView({ workspace }) {
|
|
|
1045
1094
|
* Studio's Preview and the hosted page, rather than left unanswered.
|
|
1046
1095
|
*/
|
|
1047
1096
|
const ambient = useChatAvailability();
|
|
1097
|
+
hostClosedRef.current = ambient.disabled
|
|
1098
|
+
? ambient.disableReason || 'This conversation is closed here.'
|
|
1099
|
+
: undefined;
|
|
1048
1100
|
/*
|
|
1049
1101
|
* No agent to talk to: a Datalayer runtime not assigned (STUDIO P-24,
|
|
1050
1102
|
* round 9). Said in a sentence — why, when its launch was refused — and
|
|
@@ -199,7 +199,11 @@ class ResponseRecorder {
|
|
|
199
199
|
'a2a.artifact.media_type': typeof media === 'string' ? media : undefined,
|
|
200
200
|
},
|
|
201
201
|
};
|
|
202
|
-
chunk
|
|
202
|
+
// An `append` chunk adds to what came; one that is not says the artifact
|
|
203
|
+
// whole — the runtime's last chunk carries the whole answer again. Added
|
|
204
|
+
// to it, an answer said in one chunk (a visitor's refusal) was written
|
|
205
|
+
// twice in the transcript (STUDIO H-03, 2026-10-10).
|
|
206
|
+
chunk.text = update.append ? chunk.text + text : text;
|
|
203
207
|
chunk.events.push(message);
|
|
204
208
|
if (update.append && !update.lastChunk) {
|
|
205
209
|
// A chunk of an artifact being streamed: one event once it is whole.
|