@jxsuite/studio 8.1.0 → 10.0.0
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/chunks/{2020-dg097k3e.js → 2020-j56903f1.js} +3 -3
- package/dist/chunks/adapter-wc5zvg9a.js +4 -0
- package/dist/chunks/add-new-flow-snx1s4b0.js +4 -0
- package/dist/chunks/cf-account-picker-xyqw0jex.js +4 -0
- package/dist/chunks/cf-settings-hp1pjm0n.js +4 -0
- package/dist/chunks/color-04361ttk.js +4 -0
- package/dist/chunks/{color-area-vh9cew51.js → color-area-h8dbasp1.js} +2 -2
- package/dist/chunks/{color-field-e78f16bs.js → color-field-qv5r2b16.js} +3 -3
- package/dist/chunks/{color-slider-fg4yhvk6.js → color-slider-nz2mrzky.js} +2 -2
- package/dist/chunks/components-nkwphrx3.js +4 -0
- package/dist/chunks/content-source-6a253wmk.js +4 -0
- package/dist/chunks/{convert-to-repeater-grnmea6c.js → convert-to-repeater-fhdwyg14.js} +3 -3
- package/dist/chunks/editor-wqrc68ca.js +4 -0
- package/dist/chunks/entry-commands-nef0wrmp.js +4 -0
- package/dist/chunks/{extension-sections-rp7fkega.js → extension-sections-8ez2rrk8.js} +3 -3
- package/dist/chunks/file-ops-y3w71k1n.js +4 -0
- package/dist/chunks/files-de90apn9.js +4 -0
- package/dist/chunks/format-host-zfjz4rzm.js +4 -0
- package/dist/chunks/fs-events-77c0mr0x.js +4 -0
- package/dist/chunks/grid-open-4n4fjfpf.js +4 -0
- package/dist/chunks/{grid-view-x26rfpz1.js → grid-view-ka55z38f.js} +10 -10
- package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
- package/dist/chunks/{iframe-entry-02wzcyvy.js → iframe-entry-btck0e9s.js} +4 -4
- package/dist/chunks/iframe-host-na2mywz4.js +4 -0
- package/dist/chunks/index-ewmgdb7c.js +4 -0
- package/dist/chunks/{index-203h71wy.js → index-qb98xhse.js} +3 -3
- package/dist/chunks/{javascript-ww5pv5ae.js → javascript-ces13wny.js} +2 -2
- package/dist/chunks/{jsonMode-vwbdwwq3.js → jsonMode-6md16d4v.js} +6 -6
- package/dist/chunks/jx-validate-b309c020.js +4 -0
- package/dist/chunks/layers-8rn8y6qv.js +4 -0
- package/dist/chunks/layers-panel-eyah7h9z.js +4 -0
- package/dist/chunks/lit-html-wyqw04y2.js +4 -0
- package/dist/chunks/media-library-gs3np47y.js +4 -0
- package/dist/chunks/media-picker-tt7dk4tb.js +4 -0
- package/dist/chunks/{monaco-binding-63tqze27.js → monaco-binding-p8j80s4w.js} +3 -3
- package/dist/chunks/monaco-buffer-wsf6cmnq.js +4 -0
- package/dist/chunks/{monaco-setup-zyyynnj8.js → monaco-setup-wasktzfg.js} +3 -3
- package/dist/chunks/notify-qwst108r.js +4 -0
- package/dist/chunks/parse-n8dfxnqf.js +4 -0
- package/dist/chunks/platform-c16mqvmp.js +4 -0
- package/dist/chunks/publish-panel-d34bhvwj.js +4 -0
- package/dist/chunks/{publish-quick-ajzdhhs6.js → publish-quick-vr695j26.js} +3 -3
- package/dist/chunks/publish-workspace-2yr60mt4.js +4 -0
- package/dist/chunks/quick-search-e6xsdv0d.js +4 -0
- package/dist/chunks/references-gsj6trdv.js +4 -0
- package/dist/chunks/right-panel-33jv1knc.js +4 -0
- package/dist/chunks/source-control-bj9bejjv.js +4 -0
- package/dist/chunks/studio-03mqh8ax.js +5 -0
- package/dist/chunks/{studio-957jg8g3.js → studio-06j4en5h.js} +4 -4
- package/dist/chunks/studio-0ydxv2mr.js +7 -0
- package/dist/chunks/{studio-v2ek69cb.js → studio-1e8dw2w2.js} +4 -4
- package/dist/chunks/studio-2hsqbq7b.js +7 -0
- package/dist/chunks/studio-2j86y34y.js +6 -0
- package/dist/chunks/studio-2ks6ka4g.js +5 -0
- package/dist/chunks/studio-34jrz12k.js +5 -0
- package/dist/chunks/{studio-1meqbxns.js → studio-35vrtpdq.js} +4 -4
- package/dist/chunks/studio-38g0jdz7.js +5 -0
- package/dist/chunks/{studio-25a5by3h.js → studio-396srjbk.js} +4 -4
- package/dist/chunks/{studio-zm7dnr9g.js → studio-3c8pp7pb.js} +4 -4
- package/dist/chunks/{studio-x3ge4rts.js → studio-3v5cagy9.js} +4 -4
- package/dist/chunks/studio-3w35qvay.js +5 -0
- package/dist/chunks/studio-3zge48z4.js +7 -0
- package/dist/chunks/studio-45gyxpcq.js +5 -0
- package/dist/chunks/{studio-hw60n5dh.js → studio-4kzbrr5t.js} +3 -3
- package/dist/chunks/{studio-533py3yy.js → studio-5tzgq6fx.js} +4 -4
- package/dist/chunks/studio-5wzdy1jc.js +16 -0
- package/dist/chunks/studio-732mw0g8.js +5 -0
- package/dist/chunks/{studio-3q6n9qvq.js → studio-771k2n12.js} +4 -4
- package/dist/chunks/{studio-z42kysg7.js → studio-7dzyz9n8.js} +137 -137
- package/dist/chunks/{studio-fqs1a22a.js → studio-7xag4na8.js} +4 -4
- package/dist/chunks/{studio-jqrg9xxg.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-88bmt9rh.js +5 -0
- package/dist/chunks/{studio-8nyw10dp.js → studio-9b7177jq.js} +4 -4
- package/dist/chunks/{studio-nm9qev4p.js → studio-9cxmp61c.js} +5 -5
- package/dist/chunks/{studio-4dsdnan2.js → studio-9dz76yqc.js} +5 -5
- package/dist/chunks/{studio-a6x058ew.js → studio-agyvzfm4.js} +4 -4
- package/dist/chunks/{studio-2b8443ms.js → studio-ajw4k8hp.js} +3 -3
- package/dist/chunks/studio-c1pxszat.js +5 -0
- package/dist/chunks/studio-cpkamjdn.js +14 -0
- package/dist/chunks/studio-d038kqbs.js +5 -0
- package/dist/chunks/studio-d83m0wyq.js +5 -0
- package/dist/chunks/studio-dbxv7w4s.js +5 -0
- package/dist/chunks/studio-de1s2x9v.js +5 -0
- package/dist/chunks/{studio-94ppxeqy.js → studio-dx73md4n.js} +8 -8
- package/dist/chunks/studio-ekgtrndg.js +5 -0
- package/dist/chunks/studio-epenqmvk.js +7 -0
- package/dist/chunks/studio-et2q4s8n.js +5 -0
- package/dist/chunks/{studio-txkx6586.js → studio-ewarnzm4.js} +7 -7
- package/dist/chunks/{studio-bf0rgyf7.js → studio-fy7jzj9j.js} +4 -4
- package/dist/chunks/{studio-332hesdk.js → studio-gr36vnte.js} +4 -4
- package/dist/chunks/{studio-rdnezzkd.js → studio-gwrhcrss.js} +5 -5
- package/dist/chunks/{studio-9wyqyfqy.js → studio-h310mbqm.js} +4 -4
- package/dist/chunks/{studio-zht0x0v3.js → studio-h5tpz5ke.js} +4 -4
- package/dist/chunks/studio-h7weqf2q.js +5 -0
- package/dist/chunks/{studio-1gdfsr49.js → studio-hcsba201.js} +7 -7
- package/dist/chunks/studio-hwejq03c.js +5 -0
- package/dist/chunks/{studio-wcakp5bt.js → studio-hy4ye0dx.js} +7 -7
- package/dist/chunks/{studio-02phtmqn.js → studio-jttyqrmh.js} +4 -4
- package/dist/chunks/{studio-j5jqvyn3.js → studio-jw0q5j6k.js} +4 -4
- package/dist/chunks/studio-k3a8rp3d.js +5 -0
- package/dist/chunks/{studio-hnx1h1pr.js → studio-k3bcy02e.js} +4 -4
- package/dist/chunks/studio-m48v13s3.js +4 -0
- package/dist/chunks/{studio-bqgs90r8.js → studio-ndskgqef.js} +4 -4
- package/dist/chunks/studio-p1n2xjda.js +5 -0
- package/dist/chunks/studio-pgm83a2f.js +6 -0
- package/dist/chunks/studio-pqqq6v85.js +5 -0
- package/dist/chunks/studio-psh8mwnw.js +4 -0
- package/dist/chunks/{studio-cpdzrdgp.js → studio-r17asgs0.js} +4 -4
- package/dist/chunks/{studio-6vrxzkba.js → studio-ry81j4q1.js} +4 -4
- package/dist/chunks/studio-rzg8hnkf.js +5 -0
- package/dist/chunks/{studio-nsv7g1qn.js → studio-s0fg2wav.js} +4 -4
- package/dist/chunks/{studio-v0gsjav0.js → studio-tj2s0tks.js} +4 -4
- package/dist/chunks/studio-tyw7tk0x.js +5 -0
- package/dist/chunks/studio-v9ywz2se.js +6 -0
- package/dist/chunks/{studio-dmn7gpba.js → studio-vswcpzk7.js} +5 -5
- package/dist/chunks/studio-vzgmyt0v.js +5 -0
- package/dist/chunks/{studio-2dtc90zs.js → studio-wxdcd42g.js} +6 -6
- package/dist/chunks/{studio-c5dj3dzk.js → studio-wxhy1yh7.js} +4 -4
- package/dist/chunks/studio-x08a3m0a.js +5 -0
- package/dist/chunks/studio-x3mjr5cc.js +5 -0
- package/dist/chunks/{studio-xddgbwhe.js → studio-x97ay0v4.js} +4 -4
- package/dist/chunks/{studio-mmqr9awv.js → studio-xdpz9rpe.js} +33 -33
- package/dist/chunks/studio-xh683qzy.js +5 -0
- package/dist/chunks/{studio-hxq1ebp0.js → studio-xkxetm65.js} +6 -6
- package/dist/chunks/{studio-73wz63nn.js → studio-y26hx9re.js} +4 -4
- package/dist/chunks/{studio-kdseew9m.js → studio-yg3djpnm.js} +4 -4
- package/dist/chunks/studio-yx8hzqja.js +5 -0
- package/dist/chunks/studio-zg16eeq1.js +5 -0
- package/dist/chunks/studio-zgx7svjd.js +9 -0
- package/dist/chunks/studio-zkq3r91p.js +5 -0
- package/dist/chunks/{toast-host-1yg5ar8d.js → toast-host-g3r2s0kn.js} +2 -2
- package/dist/chunks/toast-w11qpkyz.js +4 -0
- package/dist/chunks/{tsMode-vra2hh7y.js → tsMode-h6tkz0ea.js} +5 -5
- package/dist/chunks/workspace-home-zkkrxq3r.js +4 -0
- package/dist/chunks/workspace-tdqqjqaa.js +4 -0
- package/dist/chunks/{ws-client-72x5n99j.js → ws-client-e951my95.js} +3 -3
- package/dist/iframe-entry.js +13 -13
- package/dist/manifest.json +77 -77
- package/dist/studio.js +91 -92
- package/package.json +11 -12
- package/src/canvas/iframe-editable-root.ts +32 -2
- package/src/collab/collab-session.ts +6 -2
- package/src/commands/context.ts +4 -0
- package/src/commands/defaults.ts +2 -0
- package/src/commands/live-context.ts +2 -0
- package/src/files/files.ts +6 -3
- package/src/format/format-host.ts +1 -1
- package/src/github/github-auth-status.ts +2 -2
- package/src/media/media-library.ts +73 -3
- package/src/new-project/new-project-modal.ts +1 -4
- package/src/packages/ensure-deps.ts +5 -5
- package/src/packages/pull-package-sync.ts +4 -4
- package/src/panels/activity-panel.ts +1 -1
- package/src/panels/ai-panel.ts +27 -34
- package/src/platform.ts +20 -0
- package/src/platforms/cloud.ts +48 -34
- package/src/platforms/devserver.ts +2 -120
- package/src/services/ai-ask.ts +29 -171
- package/src/services/ai-command-tools.ts +40 -50
- package/src/services/ai-import-tools.ts +4 -5
- package/src/services/ai-project-tools.ts +13 -3
- package/src/services/ai-tools.ts +26 -564
- package/src/services/ai-writes.ts +84 -2
- package/src/services/document-assistant.ts +29 -58
- package/src/services/harness/assistant-tools.ts +68 -0
- package/src/services/harness/interaction-store.ts +118 -0
- package/src/services/harness/studio-documents.ts +105 -0
- package/src/services/harness/turn-hooks.ts +1 -6
- package/src/services/project-adoption.ts +8 -16
- package/src/services/tool-executor.ts +25 -22
- package/src/settings/extension-commands.ts +1 -1
- package/src/settings/extension-rows.ts +1 -6
- package/src/settings/extensions-section.ts +3 -6
- package/src/state.ts +3 -13
- package/src/surfaces/block-action-bar.json +1 -3
- package/src/surfaces/files-panel.json +12 -0
- package/src/surfaces/files-panel.ts +5 -2
- package/src/surfaces/media-library.json +35 -0
- package/src/surfaces/media-library.ts +2 -0
- package/src/surfaces/menu.ts +62 -1
- package/src/surfaces/settings-packages.ts +1 -1
- package/src/surfaces/welcome.ts +4 -1
- package/src/tabs/selection.ts +14 -109
- package/src/tabs/tab.ts +10 -1
- package/src/tabs/transact.ts +316 -124
- package/src/types.ts +23 -16
- package/src/ui/layers.ts +1 -1
- package/src/ui/progress-modal.ts +3 -3
- package/dist/chunks/adapter-tck81zft.js +0 -4
- package/dist/chunks/add-new-flow-a1w0bnm6.js +0 -4
- package/dist/chunks/cf-account-picker-ce3kp3x1.js +0 -4
- package/dist/chunks/cf-settings-7ye6x7g2.js +0 -4
- package/dist/chunks/color-4gj49xq6.js +0 -4
- package/dist/chunks/components-qce0v93f.js +0 -4
- package/dist/chunks/content-source-jjx8kgzr.js +0 -4
- package/dist/chunks/editor-bkja5dbv.js +0 -4
- package/dist/chunks/entry-commands-1wh0btvg.js +0 -4
- package/dist/chunks/file-ops-6pe4z7an.js +0 -4
- package/dist/chunks/files-ybpp3j38.js +0 -4
- package/dist/chunks/format-host-3mtbhxk1.js +0 -4
- package/dist/chunks/fs-events-xfanfxds.js +0 -4
- package/dist/chunks/grid-open-dg5qmnaj.js +0 -4
- package/dist/chunks/iframe-host-j2bk2xkc.js +0 -4
- package/dist/chunks/index-fkrm7pdk.js +0 -4
- package/dist/chunks/jx-validate-w7v32f6a.js +0 -4
- package/dist/chunks/layers-g7j732cm.js +0 -4
- package/dist/chunks/layers-panel-gjrsxcck.js +0 -4
- package/dist/chunks/lit-html-jdfz9rhc.js +0 -4
- package/dist/chunks/media-library-9j0v4wmk.js +0 -4
- package/dist/chunks/media-picker-cpyyt2m0.js +0 -4
- package/dist/chunks/monaco-buffer-9wcme176.js +0 -4
- package/dist/chunks/notify-rahnnnv4.js +0 -4
- package/dist/chunks/parse-v77xtwv7.js +0 -4
- package/dist/chunks/platform-b2aqv7v5.js +0 -4
- package/dist/chunks/publish-panel-4yk2461b.js +0 -4
- package/dist/chunks/publish-workspace-mxestzcf.js +0 -4
- package/dist/chunks/quick-search-9g6z65b0.js +0 -4
- package/dist/chunks/references-c26enpxs.js +0 -4
- package/dist/chunks/right-panel-9d0e0yg7.js +0 -4
- package/dist/chunks/source-control-xyn3a8ds.js +0 -4
- package/dist/chunks/studio-0khd0xdw.js +0 -5
- package/dist/chunks/studio-1fvzq9vs.js +0 -5
- package/dist/chunks/studio-1yhz0s8p.js +0 -5
- package/dist/chunks/studio-2c2mjyzj.js +0 -6
- package/dist/chunks/studio-3pndhat0.js +0 -9
- package/dist/chunks/studio-4fjqdzrb.js +0 -6
- package/dist/chunks/studio-5abge3wy.js +0 -5
- package/dist/chunks/studio-74nzv280.js +0 -5
- package/dist/chunks/studio-7cg75gtj.js +0 -14
- package/dist/chunks/studio-89cwmnae.js +0 -5
- package/dist/chunks/studio-8m1t83kn.js +0 -7
- package/dist/chunks/studio-a3evp7f6.js +0 -6
- package/dist/chunks/studio-ana67wax.js +0 -5
- package/dist/chunks/studio-b4xhpcwy.js +0 -4
- package/dist/chunks/studio-c4rcaftm.js +0 -5
- package/dist/chunks/studio-cgjt1zce.js +0 -5
- package/dist/chunks/studio-cq87hree.js +0 -5
- package/dist/chunks/studio-d140sy8e.js +0 -5
- package/dist/chunks/studio-e1g64q43.js +0 -5
- package/dist/chunks/studio-e8pzjjhf.js +0 -5
- package/dist/chunks/studio-f8kz3krj.js +0 -7
- package/dist/chunks/studio-fw3jp9m7.js +0 -5
- package/dist/chunks/studio-g6ysm05p.js +0 -7
- package/dist/chunks/studio-gmhd0j3f.js +0 -5
- package/dist/chunks/studio-gqpty626.js +0 -5
- package/dist/chunks/studio-grp684wx.js +0 -5
- package/dist/chunks/studio-h98520qr.js +0 -5
- package/dist/chunks/studio-hvxt8z79.js +0 -5
- package/dist/chunks/studio-j4e627w1.js +0 -5
- package/dist/chunks/studio-kt2j51sa.js +0 -5
- package/dist/chunks/studio-m4w6sygj.js +0 -16
- package/dist/chunks/studio-nqxqf8p2.js +0 -4
- package/dist/chunks/studio-p89w12s2.js +0 -7
- package/dist/chunks/studio-pbagym4j.js +0 -5
- package/dist/chunks/studio-pr23qhrw.js +0 -5
- package/dist/chunks/studio-r4ya3sb2.js +0 -5
- package/dist/chunks/studio-rm9m3z60.js +0 -5
- package/dist/chunks/studio-tpmnbgmt.js +0 -5
- package/dist/chunks/studio-ttxhq5yt.js +0 -5
- package/dist/chunks/studio-y1x65h5r.js +0 -5
- package/dist/chunks/studio-zf188hnd.js +0 -5
- package/dist/chunks/toast-1gv0h53h.js +0 -4
- package/dist/chunks/workspace-3nt93k48.js +0 -4
- package/dist/chunks/workspace-home-zbgyqqws.js +0 -4
- package/src/services/directory-picker.ts +0 -187
- package/src/services/gated-registry.ts +0 -77
package/src/platforms/cloud.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* before Studio boots — while project-less mode (null) powers the /studio welcome screen.
|
|
8
8
|
*
|
|
9
9
|
* Cloud omissions (Studio degrades per @jxsuite/protocol's route table): pickDirectory, package
|
|
10
|
-
* install
|
|
11
|
-
*
|
|
10
|
+
* install and the install-on-open check (a cloud session has no `node_modules`; add, remove and
|
|
11
|
+
* set-versions update `package.json` and `package-lock.json` through the session's package
|
|
12
|
+
* service), multi-window, gitClone, resolveClass, component discovery, code services.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { negotiateCollab } from "@jxsuite/collab/negotiate";
|
|
@@ -43,6 +44,8 @@ import type {
|
|
|
43
44
|
ImportSiteOptions,
|
|
44
45
|
OfferedAction,
|
|
45
46
|
PackageInfo,
|
|
47
|
+
PackageOpResult,
|
|
48
|
+
PackageVersionInfo,
|
|
46
49
|
ProjectListEntry,
|
|
47
50
|
SiteBuildResult,
|
|
48
51
|
ProjectSchemasResponse,
|
|
@@ -947,17 +950,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
947
950
|
}
|
|
948
951
|
}
|
|
949
952
|
|
|
950
|
-
async function writePackageJson(pkg: JsonObject): Promise<void> {
|
|
951
|
-
const res = await api("/file", {
|
|
952
|
-
method: "PUT",
|
|
953
|
-
headers: { "Content-Type": "application/json" },
|
|
954
|
-
body: JSON.stringify({ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}\n` }),
|
|
955
|
-
});
|
|
956
|
-
if (!res.ok) {
|
|
957
|
-
throw new Error(await errorMessage(res, "Failed to update package.json"));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
953
|
// The bound session's /raw tree; see `documentBaseUrl` below for why both members ride together.
|
|
962
954
|
const repoAssets: Pick<StudioPlatform, "assetSpace" | "documentBaseUrl"> = {};
|
|
963
955
|
if (base) {
|
|
@@ -1284,10 +1276,12 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1284
1276
|
*
|
|
1285
1277
|
* A Worker ships a fixed set of extension packages (specs/extensions.md §5.5), decided by the
|
|
1286
1278
|
* platform build rather than by this repository's `extensions/` tree, so the gateway is the
|
|
1287
|
-
* only thing that knows.
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1279
|
+
* only thing that knows. An extension the Worker does not bundle is dropped from the registry
|
|
1280
|
+
* before composition, so advertising it would promise a toggle that silently does nothing.
|
|
1281
|
+
*
|
|
1282
|
+
* What the Worker bundles is what it can EDIT with, not what the project gets: the site is
|
|
1283
|
+
* built in Pages CI from the project's own install, so an entry the project has not declared is
|
|
1284
|
+
* install-then-enable here exactly as on every other backend (specs/extensions.md §3).
|
|
1291
1285
|
*
|
|
1292
1286
|
* `installed` here means DECLARED. Nothing resolves a module in a Worker: `addPackage` is a
|
|
1293
1287
|
* manifest edit and resolution happens later in Pages CI, so the manifest is the only fact this
|
|
@@ -1309,7 +1303,6 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1309
1303
|
...Object.keys(dependencyBlock(pkg, "devDependencies")),
|
|
1310
1304
|
]);
|
|
1311
1305
|
for (const entry of entries) {
|
|
1312
|
-
entry.bundled = true;
|
|
1313
1306
|
entry.installed = declared.has(entry.name);
|
|
1314
1307
|
}
|
|
1315
1308
|
return entries;
|
|
@@ -1435,28 +1428,49 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
1435
1428
|
return null;
|
|
1436
1429
|
},
|
|
1437
1430
|
|
|
1438
|
-
// ─── Packages (
|
|
1431
|
+
// ─── Packages (the session's lockfile service; nothing installs) ───
|
|
1439
1432
|
|
|
1433
|
+
/* Each mutation runs npm's installer lockfile-only on the platform, which writes
|
|
1434
|
+
`package.json` and `package-lock.json` back into the shared tree as one change, so a commit
|
|
1435
|
+
carries a lockfile a build can install from frozen (server.md §4.1). A failure changes
|
|
1436
|
+
nothing and its message names the cause. */
|
|
1440
1437
|
async addPackage(name: string) {
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1438
|
+
const res = await api("/packages/add", {
|
|
1439
|
+
body: JSON.stringify({ name }),
|
|
1440
|
+
headers: { "Content-Type": "application/json" },
|
|
1441
|
+
method: "POST",
|
|
1442
|
+
});
|
|
1443
|
+
if (!res.ok) {
|
|
1444
|
+
throw new Error(await errorMessage(res, `Failed to add ${name}`));
|
|
1445
|
+
}
|
|
1449
1446
|
},
|
|
1450
1447
|
|
|
1451
1448
|
async removePackage(name: string) {
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1449
|
+
const res = await api("/packages/remove", {
|
|
1450
|
+
body: JSON.stringify({ name }),
|
|
1451
|
+
headers: { "Content-Type": "application/json" },
|
|
1452
|
+
method: "POST",
|
|
1453
|
+
});
|
|
1454
|
+
if (!res.ok) {
|
|
1455
|
+
throw new Error(await errorMessage(res, `Failed to remove ${name}`));
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1459
|
+
async setPackageVersions(updates: { name: string; version: string; dev?: boolean }[]) {
|
|
1460
|
+
const res = await api("/packages/set-versions", {
|
|
1461
|
+
body: JSON.stringify({ updates }),
|
|
1462
|
+
headers: { "Content-Type": "application/json" },
|
|
1463
|
+
method: "POST",
|
|
1464
|
+
});
|
|
1465
|
+
if (!res.ok) {
|
|
1466
|
+
return { log: await errorMessage(res, "Failed to update versions"), ok: false };
|
|
1458
1467
|
}
|
|
1459
|
-
|
|
1468
|
+
return readJson<PackageOpResult>(res);
|
|
1469
|
+
},
|
|
1470
|
+
|
|
1471
|
+
async packageVersions() {
|
|
1472
|
+
const res = await api("/packages/versions");
|
|
1473
|
+
return res.ok ? readJson<PackageVersionInfo[]>(res) : [];
|
|
1460
1474
|
},
|
|
1461
1475
|
|
|
1462
1476
|
async listPackages(): Promise<PackageInfo[]> {
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
* Devserver.js — Dev Server Platform Adapter
|
|
3
3
|
*
|
|
4
4
|
* Implements the StudioPlatform interface for the @jxsuite/server development workflow. All file
|
|
5
|
-
* I/O goes through /__studio/* REST endpoints.
|
|
6
|
-
*
|
|
5
|
+
* I/O goes through /__studio/* REST endpoints. There is no Open Project: a dev-server window opens
|
|
6
|
+
* a project from its `?project=` URL parameter (specs/desktop.md §8.2).
|
|
7
7
|
*
|
|
8
8
|
* See spec/desktop.md §8 for the full specification.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { streamImport } from "../services/import-client";
|
|
12
|
-
import { canPickDirectory, pickDirectoryPath } from "../services/directory-picker";
|
|
13
12
|
import { negotiateCollab } from "@jxsuite/collab/negotiate";
|
|
14
13
|
import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
|
|
15
14
|
import type { WsCollabConnection } from "@jxsuite/collab/client";
|
|
16
|
-
import { isFunction } from "@jxsuite/schema/guards";
|
|
17
15
|
import type { JsonObject, JsonValue, JxMutableNode, ProjectConfig } from "@jxsuite/schema/types";
|
|
18
16
|
import type {
|
|
19
17
|
DataConnectionsResponse,
|
|
@@ -80,16 +78,6 @@ interface FsEventBatch {
|
|
|
80
78
|
events?: FsEvent[];
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
/** `showDirectoryPicker()` (WICG File System Access), which only Chromium-based browsers expose. */
|
|
84
|
-
type ShowDirectoryPicker = (options: { mode: string }) => Promise<FileSystemDirectoryHandle>;
|
|
85
|
-
|
|
86
|
-
/** True when the window implements the File System Access directory picker. */
|
|
87
|
-
function hasDirectoryPicker(
|
|
88
|
-
host: Window,
|
|
89
|
-
): host is Window & { showDirectoryPicker: ShowDirectoryPicker } {
|
|
90
|
-
return "showDirectoryPicker" in host && isFunction(host.showDirectoryPicker);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
81
|
/** The options object {@link StudioPlatform.createProject} takes. */
|
|
94
82
|
type CreateProjectOptions = Parameters<StudioPlatform["createProject"]>[0];
|
|
95
83
|
|
|
@@ -225,36 +213,6 @@ export function createDevServerPlatform() {
|
|
|
225
213
|
return p.startsWith(`${_projectRoot}/`) ? p.slice(_projectRoot.length + 1) : p;
|
|
226
214
|
}
|
|
227
215
|
|
|
228
|
-
/**
|
|
229
|
-
* Native folder chooser for the New Project modal's Browse… button. Present only when the browser
|
|
230
|
-
* implements `showDirectoryPicker` (Chromium-based); elsewhere the member stays undefined and the
|
|
231
|
-
* modal falls back to a typed Location field.
|
|
232
|
-
*
|
|
233
|
-
* The picked handle carries no filesystem path, so it is resolved by the id the picker tags the
|
|
234
|
-
* folder with (see `@jxsuite/studio/directory-picker`). Resolves `null` only when the user
|
|
235
|
-
* dismisses the chooser; every other failure rejects with a sentence the modal shows under
|
|
236
|
-
* Location. So `locate` keeps the helper's contract: `null` is "no folder holds this id" (the
|
|
237
|
-
* route's 200 `{ path: null }`), and a non-ok answer throws the problem the server sent rather
|
|
238
|
-
* than posing as a miss. A network failure (`TypeError: Failed to fetch`, e.g. a page that is not
|
|
239
|
-
* being served by the dev server at all) propagates as is, and the picker names it.
|
|
240
|
-
*/
|
|
241
|
-
const directoryPicking: Pick<StudioPlatform, "pickDirectory"> = {};
|
|
242
|
-
if (canPickDirectory()) {
|
|
243
|
-
directoryPicking.pickDirectory = async (): Promise<string | null> =>
|
|
244
|
-
pickDirectoryPath(async ({ id, name }) => {
|
|
245
|
-
const res = await fetch(
|
|
246
|
-
`/__studio/locate-directory?name=${encodeURIComponent(name)}&id=${encodeURIComponent(id)}`,
|
|
247
|
-
);
|
|
248
|
-
if (!res.ok) {
|
|
249
|
-
// A proxy, or a `views://` host with no dev server behind it, may answer in HTML.
|
|
250
|
-
const body = await readJson<JsonValue>(res).catch(() => null);
|
|
251
|
-
throw new Error(problemMessage(body, res.status));
|
|
252
|
-
}
|
|
253
|
-
const found = await readJson<{ path?: string | null }>(res);
|
|
254
|
-
return found.path ?? null;
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
216
|
return {
|
|
259
217
|
id: "devserver",
|
|
260
218
|
|
|
@@ -293,80 +251,6 @@ export function createDevServerPlatform() {
|
|
|
293
251
|
await postActivate(root ?? _projectRoot);
|
|
294
252
|
},
|
|
295
253
|
|
|
296
|
-
// ─── Project opening ──────────────────────────────────────────────────
|
|
297
|
-
|
|
298
|
-
async openProject() {
|
|
299
|
-
// Use Chrome's showDirectoryPicker API
|
|
300
|
-
const host = window;
|
|
301
|
-
if (!hasDirectoryPicker(host)) {
|
|
302
|
-
throw new Error("showDirectoryPicker not available — use a Chromium-based browser");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let dirHandle;
|
|
306
|
-
try {
|
|
307
|
-
dirHandle = await host.showDirectoryPicker({ mode: "readwrite" });
|
|
308
|
-
} catch (error) {
|
|
309
|
-
// User cancelled the picker
|
|
310
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Read project.json from the chosen directory
|
|
317
|
-
let siteHandle;
|
|
318
|
-
try {
|
|
319
|
-
siteHandle = await dirHandle.getFileHandle("project.json");
|
|
320
|
-
} catch {
|
|
321
|
-
throw new Error("No project.json found in selected folder");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const file = await siteHandle.getFile();
|
|
325
|
-
/* SAFETY: the user picked this folder as a project, and project.json is that project's own
|
|
326
|
-
config file; it is matched below against the server's parse of the same file, and every
|
|
327
|
-
adapter hands project.json to Studio as the ProjectConfig the session edits. */
|
|
328
|
-
const config = JSON.parse(await file.text()) as ProjectConfig;
|
|
329
|
-
|
|
330
|
-
// Resolve server-relative path by matching against known sites
|
|
331
|
-
const sitesRes = await fetch("/__studio/sites");
|
|
332
|
-
if (!sitesRes.ok) {
|
|
333
|
-
throw new Error("Failed to fetch site list from server");
|
|
334
|
-
}
|
|
335
|
-
const sites = await readJson<SiteEntry[]>(sitesRes);
|
|
336
|
-
const match = sites.find(
|
|
337
|
-
(s: SiteEntry) => JSON.stringify(s.config) === JSON.stringify(config),
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
if (!match) {
|
|
341
|
-
// Project is outside dev server root — ask the server to find it by directory name
|
|
342
|
-
const findRes = await fetch(
|
|
343
|
-
`/__studio/find-project?name=${encodeURIComponent(dirHandle.name)}`,
|
|
344
|
-
);
|
|
345
|
-
if (!findRes.ok) {
|
|
346
|
-
throw new Error("Could not locate project on disk");
|
|
347
|
-
}
|
|
348
|
-
const found = await readJson<{ path?: string }>(findRes);
|
|
349
|
-
if (!found.path) {
|
|
350
|
-
throw new Error(`Could not find project directory "${dirHandle.name}"`);
|
|
351
|
-
}
|
|
352
|
-
_projectRoot = found.path;
|
|
353
|
-
} else {
|
|
354
|
-
_projectRoot = match.path;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Notify server of active project for static file resolution
|
|
358
|
-
await this.activate();
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
config,
|
|
362
|
-
handle: {
|
|
363
|
-
name: config.name || _projectRoot.split("/").pop()!,
|
|
364
|
-
projectConfig: config,
|
|
365
|
-
root: _projectRoot,
|
|
366
|
-
},
|
|
367
|
-
};
|
|
368
|
-
},
|
|
369
|
-
|
|
370
254
|
/**
|
|
371
255
|
* Probe the server root to see if it is itself a site project. Used at startup to auto-detect
|
|
372
256
|
* projects.
|
|
@@ -408,8 +292,6 @@ export function createDevServerPlatform() {
|
|
|
408
292
|
return await res.json();
|
|
409
293
|
},
|
|
410
294
|
|
|
411
|
-
...directoryPicking,
|
|
412
|
-
|
|
413
295
|
/** List starter templates from the dev server. */
|
|
414
296
|
async listStarters(): Promise<StarterInfo[]> {
|
|
415
297
|
const res = await fetch("/__studio/starters");
|
package/src/services/ai-ask.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ai-ask.ts — the assistant's one way to stop and put a question to the author.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `finishStream` has already set the state to `"idle"` by the time tools run, so a suspended turn
|
|
9
|
-
* is idle by construction and this store is what "waiting" means.
|
|
4
|
+
* `ask_user` declares what it asks (`interaction`) and what its answer means (`settle`), and has no
|
|
5
|
+
* body that waits: the invoker answers `needs_input` before any tool code runs, the turn engine
|
|
6
|
+
* puts the question to the assistant's interaction store (`harness/interaction-store.ts`), and the
|
|
7
|
+
* person's answer comes back through `settle` as the call's result (specs/ai.md §3.4 and §3.11).
|
|
10
8
|
*
|
|
11
9
|
* Two decisions are load-bearing and easy to get wrong on a rewrite:
|
|
12
10
|
*
|
|
@@ -17,29 +15,16 @@
|
|
|
17
15
|
* between them is a 400. The answer travels as the tool result, and the chat view renders it into
|
|
18
16
|
* the question's own chip.
|
|
19
17
|
*
|
|
20
|
-
* Exactly one question can be outstanding, because `runAgentLoop` executes a round's tool calls
|
|
21
|
-
* serially. The guard below is defensive, not load-bearing.
|
|
22
|
-
*
|
|
23
18
|
* @license MIT
|
|
24
19
|
*/
|
|
25
20
|
|
|
21
|
+
import { JX_TOOL_FACTS } from "@jxsuite/ai/jx-tools";
|
|
26
22
|
import { createToolDefinition, toolError, toolSuccess } from "@jxsuite/ai/tools";
|
|
27
23
|
import { isString } from "@jxsuite/schema/guards";
|
|
28
|
-
import { reactive } from "../reactivity";
|
|
29
|
-
|
|
30
|
-
import type { ToolRegistry } from "@jxsuite/ai/tools";
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
export interface PendingAsk {
|
|
34
|
-
id: string;
|
|
35
|
-
question: string;
|
|
36
|
-
/** Short choices rendered as buttons. Free text is always allowed alongside them. */
|
|
37
|
-
options: string[];
|
|
38
|
-
/** One line of why-I'm-asking, rendered under the question. */
|
|
39
|
-
context: string;
|
|
40
|
-
}
|
|
25
|
+
import type { InteractionResponse, ToolRegistry } from "@jxsuite/ai/tools";
|
|
41
26
|
|
|
42
|
-
/**
|
|
27
|
+
/** What a settled question's result carries: the answer, or `null` with `skipped` for "you decide". */
|
|
43
28
|
export interface AskAnswer {
|
|
44
29
|
answer: string | null;
|
|
45
30
|
skipped: boolean;
|
|
@@ -48,112 +33,6 @@ export interface AskAnswer {
|
|
|
48
33
|
/** Max choices rendered as buttons; beyond this the question wants free text, not a menu. */
|
|
49
34
|
const MAX_OPTIONS = 6;
|
|
50
35
|
|
|
51
|
-
const state = reactive<{ pending: PendingAsk | null }>({ pending: null });
|
|
52
|
-
|
|
53
|
-
/** The resolver for {@link state.pending}, kept out of the reactive object (it is not render data). */
|
|
54
|
-
let settle: ((answer: AskAnswer) => void) | null = null;
|
|
55
|
-
|
|
56
|
-
/** Torn down with the question so a stopped turn does not leave a listener on a dead signal. */
|
|
57
|
-
let detachAbort: (() => void) | null = null;
|
|
58
|
-
|
|
59
|
-
function clear() {
|
|
60
|
-
state.pending = null;
|
|
61
|
-
settle = null;
|
|
62
|
-
detachAbort?.();
|
|
63
|
-
detachAbort = null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The outstanding question, or null. Reactive — read it inside an `effect()` to repaint on it.
|
|
68
|
-
*
|
|
69
|
-
* @returns {PendingAsk | null}
|
|
70
|
-
*/
|
|
71
|
-
export function pendingAsk(): PendingAsk | null {
|
|
72
|
-
return state.pending;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Whether a turn is suspended on the author. The composer's Send becomes Answer on this. */
|
|
76
|
-
export function isAwaitingAnswer(): boolean {
|
|
77
|
-
return state.pending !== null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Register a question and suspend until it is settled.
|
|
82
|
-
*
|
|
83
|
-
* @param {PendingAsk} ask
|
|
84
|
-
* @param {AbortSignal} [signal] - The asking call's signal. Without one the question can still be
|
|
85
|
-
* settled by {@link cancelAsk}, but a Stop would leave the turn awaiting a promise nothing could
|
|
86
|
-
* resolve.
|
|
87
|
-
* @returns {Promise<AskAnswer>} Resolves on an answer, a skip, or the turn being stopped (`{
|
|
88
|
-
* answer: null, skipped: false }` — the shape the tool reports as a failure).
|
|
89
|
-
*/
|
|
90
|
-
export function askUser(ask: PendingAsk, signal?: AbortSignal): Promise<AskAnswer> {
|
|
91
|
-
return new Promise<AskAnswer>((resolve) => {
|
|
92
|
-
state.pending = ask;
|
|
93
|
-
settle = resolve;
|
|
94
|
-
|
|
95
|
-
if (signal?.aborted) {
|
|
96
|
-
clear();
|
|
97
|
-
resolve({ answer: null, skipped: false });
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (signal) {
|
|
101
|
-
const onAbort = () => {
|
|
102
|
-
cancelAsk();
|
|
103
|
-
};
|
|
104
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
105
|
-
detachAbort = () => signal.removeEventListener("abort", onAbort);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Answer the outstanding question.
|
|
112
|
-
*
|
|
113
|
-
* @param {string} text
|
|
114
|
-
* @returns {boolean} False when nothing was pending, so a caller can fall through to a normal send.
|
|
115
|
-
*/
|
|
116
|
-
export function answerAsk(text: string): boolean {
|
|
117
|
-
if (!settle) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const resolve = settle;
|
|
121
|
-
clear();
|
|
122
|
-
resolve({ answer: text, skipped: false });
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Decline to answer — "you decide". Settles as a success, so the turn carries on.
|
|
128
|
-
*
|
|
129
|
-
* @returns {boolean} False when nothing was pending.
|
|
130
|
-
*/
|
|
131
|
-
export function skipAsk(): boolean {
|
|
132
|
-
if (!settle) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
const resolve = settle;
|
|
136
|
-
clear();
|
|
137
|
-
resolve({ answer: null, skipped: true });
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Settle an outstanding question as unanswered. Called from `stop()` and from the turn's abort. */
|
|
142
|
-
export function cancelAsk(): void {
|
|
143
|
-
if (!settle) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const resolve = settle;
|
|
147
|
-
clear();
|
|
148
|
-
resolve({ answer: null, skipped: false });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Drop any outstanding question without settling a turn. For New Chat and for tests. */
|
|
152
|
-
export function resetAsk(): void {
|
|
153
|
-
cancelAsk();
|
|
154
|
-
clear();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
36
|
/**
|
|
158
37
|
* Register `ask_user` into a tool registry.
|
|
159
38
|
*
|
|
@@ -163,24 +42,12 @@ export function resetAsk(): void {
|
|
|
163
42
|
*
|
|
164
43
|
* @param {Pick<ToolRegistry, "register">} registry
|
|
165
44
|
*/
|
|
166
|
-
|
|
167
|
-
export interface AskToolOptions {
|
|
168
|
-
/**
|
|
169
|
-
* Called once a question has been put to the author, before the turn waits on the answer. A host
|
|
170
|
-
* saves the conversation here: a turn suspended on a person may wait for as long as they like,
|
|
171
|
-
* and a reload in the meantime must still find the question to restore as open (`specs/ai.md`
|
|
172
|
-
* §3.4).
|
|
173
|
-
*/
|
|
174
|
-
onPending?: () => void;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function registerAskTool(
|
|
178
|
-
registry: Pick<ToolRegistry, "register">,
|
|
179
|
-
{ onPending }: AskToolOptions = {},
|
|
180
|
-
): void {
|
|
45
|
+
export function registerAskTool(registry: Pick<ToolRegistry, "register">): void {
|
|
181
46
|
registry.register(
|
|
182
47
|
createToolDefinition({
|
|
183
48
|
name: "ask_user",
|
|
49
|
+
title: "Ask the user",
|
|
50
|
+
annotations: JX_TOOL_FACTS.ask_user.annotations,
|
|
184
51
|
// It suspends the turn on the author, so a round that only asked spends no work budget.
|
|
185
52
|
interactive: true,
|
|
186
53
|
description:
|
|
@@ -212,43 +79,32 @@ export function registerAskTool(
|
|
|
212
79
|
},
|
|
213
80
|
required: ["question"],
|
|
214
81
|
},
|
|
215
|
-
|
|
82
|
+
interaction(args) {
|
|
216
83
|
/* Every field is any JSON value, narrowed by a guard rather than by `?.`. The registry's
|
|
217
|
-
`validate()` is "a lightweight structural check, not a JSON Schema validator",
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
84
|
+
`validate()` is "a lightweight structural check, not a JSON Schema validator", and this
|
|
85
|
+
tool may be registered into someone else's registry, so an argument that is a number is
|
|
86
|
+
a case, not an impossibility. Optional chaining guards null, not type: `context?.trim()`
|
|
87
|
+
threw on one. */
|
|
221
88
|
const { question, options, context } = args;
|
|
222
89
|
if (!isString(question) || !question.trim()) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
if (isAwaitingAnswer()) {
|
|
226
|
-
return toolError("A question is already waiting for the user.");
|
|
90
|
+
return { refusal: 'Pass "question" — the question to put to the user.' };
|
|
227
91
|
}
|
|
228
92
|
const choices = Array.isArray(options)
|
|
229
93
|
? options
|
|
230
94
|
.filter((o): o is string => isString(o) && o.trim().length > 0)
|
|
231
95
|
.slice(0, MAX_OPTIONS)
|
|
232
96
|
: [];
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ctx.signal,
|
|
245
|
-
);
|
|
246
|
-
// The question is published by now (`askUser` sets it synchronously); the answer is not.
|
|
247
|
-
if (isAwaitingAnswer()) {
|
|
248
|
-
onPending?.();
|
|
249
|
-
}
|
|
250
|
-
const { answer, skipped } = await answering;
|
|
251
|
-
|
|
97
|
+
return {
|
|
98
|
+
context: isString(context) ? context.trim() : "",
|
|
99
|
+
kind: "question",
|
|
100
|
+
options: choices,
|
|
101
|
+
question: question.trim(),
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
settle(_args, response) {
|
|
105
|
+
/* SAFETY: `ask_user` only ever asks a question, so the response the invoker hands back is
|
|
106
|
+
the answer to one. */
|
|
107
|
+
const { answer, skipped } = response as Extract<InteractionResponse, { kind: "question" }>;
|
|
252
108
|
if (skipped) {
|
|
253
109
|
/* A success: the model asked, and "you decide" is an answer. Rendering it as a failure
|
|
254
110
|
would have the model apologise for having asked a fair question. */
|
|
@@ -262,6 +118,8 @@ export function registerAskTool(
|
|
|
262
118
|
}
|
|
263
119
|
return toolSuccess({ answer, skipped: false }, `The user answered: "${answer}"`);
|
|
264
120
|
},
|
|
121
|
+
// Never reached through the invoker, which settles an interaction instead of executing it.
|
|
122
|
+
execute: () => toolError("ask_user needs a person, and none is available here."),
|
|
265
123
|
}),
|
|
266
124
|
);
|
|
267
125
|
}
|