@jxsuite/studio 8.0.1 → 9.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-5wtzce5g.js → 2020-89fz3pfy.js} +3 -3
- package/dist/chunks/adapter-ay13gema.js +4 -0
- package/dist/chunks/add-new-flow-nmkqpjym.js +4 -0
- package/dist/chunks/cf-account-picker-2e06qxey.js +4 -0
- package/dist/chunks/cf-settings-5y04810c.js +4 -0
- package/dist/chunks/color-04361ttk.js +4 -0
- package/dist/chunks/{color-area-6rbra5dy.js → color-area-h8dbasp1.js} +2 -2
- package/dist/chunks/{color-field-gy56c94g.js → color-field-qv5r2b16.js} +3 -3
- package/dist/chunks/{color-slider-sqqdyf6c.js → color-slider-nz2mrzky.js} +2 -2
- package/dist/chunks/{combobox-72h32ejk.js → combobox-6hs00996.js} +2 -2
- package/dist/chunks/components-3vf75n83.js +4 -0
- package/dist/chunks/content-source-4f2raf4a.js +4 -0
- package/dist/chunks/{convert-to-repeater-he70fc2b.js → convert-to-repeater-j9hfcj4f.js} +3 -3
- package/dist/chunks/editor-645krvtz.js +4 -0
- package/dist/chunks/entry-commands-3qmv8wkr.js +4 -0
- package/dist/chunks/{extension-sections-0egb88dm.js → extension-sections-thfke054.js} +3 -3
- package/dist/chunks/file-ops-kkhp9ma7.js +4 -0
- package/dist/chunks/files-yswdhxrf.js +4 -0
- package/dist/chunks/format-host-4g8ex6bz.js +4 -0
- package/dist/chunks/fs-events-z9gpvc6t.js +4 -0
- package/dist/chunks/grid-open-fm4j183c.js +4 -0
- package/dist/chunks/{grid-view-qg1b5eqx.js → grid-view-7d2nx83y.js} +16 -15
- package/dist/chunks/{icons-63bjtbs7.js → icons-853n6qt3.js} +2 -2
- package/dist/chunks/{iframe-entry-pkgmcgp0.js → iframe-entry-2x9bn5ng.js} +4 -4
- package/dist/chunks/{iframe-entry-99nr5zfb.js → iframe-entry-42hpmwz5.js} +4 -4
- package/dist/chunks/{iframe-entry-76j8vwwx.js → iframe-entry-b5qgggyp.js} +2 -2
- package/dist/chunks/{iframe-entry-2zggzajv.js → iframe-entry-btck0e9s.js} +4 -4
- package/dist/chunks/iframe-host-q7fedsmr.js +4 -0
- package/dist/chunks/{index-32eqzv1k.js → index-jxmt7vh8.js} +3 -3
- package/dist/chunks/index-pt4wh98x.js +4 -0
- package/dist/chunks/{javascript-pjp734q4.js → javascript-vmqp56ke.js} +2 -2
- package/dist/chunks/{jsonMode-njkvsa02.js → jsonMode-n3wmpant.js} +5 -5
- package/dist/chunks/jx-validate-19y62knt.js +4 -0
- package/dist/chunks/layers-panel-1qvc83y9.js +4 -0
- package/dist/chunks/layers-qf0bc7ze.js +4 -0
- package/dist/chunks/{listbox-c421cf8c.js → listbox-7gka1h86.js} +2 -2
- package/dist/chunks/lit-html-wyqw04y2.js +4 -0
- package/dist/chunks/media-library-f9d5qcjb.js +4 -0
- package/dist/chunks/media-picker-rsb1fmk2.js +4 -0
- package/dist/chunks/{menu-bdpzbfvs.js → menu-d2jxpgma.js} +2 -2
- package/dist/chunks/{monaco-binding-vq1hgnn0.js → monaco-binding-wfhmz89d.js} +3 -3
- package/dist/chunks/monaco-buffer-ak0raadw.js +4 -0
- package/dist/chunks/{monaco-setup-xrd2cbkp.js → monaco-setup-fty7a8vg.js} +3 -3
- package/dist/chunks/notify-4qj70q0x.js +4 -0
- package/dist/chunks/parse-n8dfxnqf.js +4 -0
- package/dist/chunks/platform-ehg6t9vd.js +4 -0
- package/dist/chunks/{popover-b89sfej7.js → popover-9tf4cjrk.js} +2 -2
- package/dist/chunks/publish-panel-yhmda3cd.js +4 -0
- package/dist/chunks/{publish-quick-qx2455cj.js → publish-quick-12hh8926.js} +3 -3
- package/dist/chunks/publish-workspace-hx8qdv9p.js +4 -0
- package/dist/chunks/quick-search-r955m3q1.js +4 -0
- package/dist/chunks/references-8gxv0x7t.js +4 -0
- package/dist/chunks/right-panel-jarwqjpy.js +4 -0
- package/dist/chunks/source-control-84qt25m9.js +4 -0
- package/dist/chunks/{split-c9zfdgxf.js → split-m5rwn31z.js} +2 -2
- package/dist/chunks/studio-0nefxhar.js +7 -0
- package/dist/chunks/studio-0rg30pvs.js +5 -0
- package/dist/chunks/{studio-s32578pn.js → studio-14hh60j8.js} +4 -4
- package/dist/chunks/studio-1cfyd1ys.js +5 -0
- package/dist/chunks/studio-2bq7hheq.js +5 -0
- package/dist/chunks/{studio-zz9qe3rw.js → studio-32d8wyxq.js} +37 -37
- package/dist/chunks/studio-3hpcawh8.js +4 -0
- package/dist/chunks/{studio-svb7yyve.js → studio-4j35hgsw.js} +4 -4
- package/dist/chunks/{studio-x1x167jh.js → studio-4qg61g9j.js} +4 -4
- package/dist/chunks/studio-4rgem12q.js +5 -0
- package/dist/chunks/studio-6k9djnxw.js +5 -0
- package/dist/chunks/studio-6rbaedw2.js +9 -0
- package/dist/chunks/studio-732mw0g8.js +5 -0
- package/dist/chunks/studio-79r8msnr.js +7 -0
- package/dist/chunks/studio-7q6jdd4h.js +5 -0
- package/dist/chunks/{studio-r5qy30vx.js → studio-8hf8qcm1.js} +4 -4
- package/dist/chunks/{studio-8tr6cfrc.js → studio-8pne0rx4.js} +4 -4
- package/dist/chunks/{studio-y0n7yxea.js → studio-9179bsm7.js} +7 -7
- package/dist/chunks/{studio-pbxa5fpc.js → studio-9ff0vqff.js} +3 -3
- package/dist/chunks/studio-9gzvypw4.js +5 -0
- package/dist/chunks/studio-9ndgd55w.js +5 -0
- package/dist/chunks/studio-9ra9e7m4.js +5 -0
- package/dist/chunks/studio-a6e5fzve.js +5 -0
- package/dist/chunks/studio-a9k86nq6.js +5 -0
- package/dist/chunks/studio-ae7a69jc.js +6 -0
- package/dist/chunks/studio-aghsg0ds.js +7 -0
- package/dist/chunks/{studio-k3xxbagr.js → studio-ajw4k8hp.js} +3 -3
- package/dist/chunks/{studio-qmn1xc24.js → studio-apapy17g.js} +4 -4
- package/dist/chunks/{studio-8m0y2qe7.js → studio-by8x0jvc.js} +4 -4
- package/dist/chunks/{studio-wbcjbhy1.js → studio-c88gq88j.js} +5 -5
- package/dist/chunks/{studio-n0c5h6mf.js → studio-cg54vtet.js} +4 -4
- package/dist/chunks/studio-dvhxxxbd.js +5 -0
- package/dist/chunks/{studio-3q40e3rj.js → studio-e4rq6x0q.js} +4 -4
- package/dist/chunks/{studio-fbhxxc5f.js → studio-ekzbgnjp.js} +4 -4
- package/dist/chunks/studio-fg4fj9ec.js +10 -0
- package/dist/chunks/studio-fj6jzgxw.js +5 -0
- package/dist/chunks/{studio-nh6d342n.js → studio-g45r5vm8.js} +7 -7
- package/dist/chunks/{studio-a2bvaptz.js → studio-gczve8zf.js} +4 -4
- package/dist/chunks/studio-ge08zwpv.js +5 -0
- package/dist/chunks/studio-gk9yk8dj.js +12 -0
- package/dist/chunks/{studio-bbe4c90a.js → studio-gskkgfe3.js} +4 -4
- package/dist/chunks/{studio-83mk4bgg.js → studio-h4jbgne2.js} +8 -8
- package/dist/chunks/{studio-b7y5mr30.js → studio-hcy9zeac.js} +4 -4
- package/dist/chunks/studio-ht81xjkr.js +5 -0
- package/dist/chunks/{studio-jaaz8ce9.js → studio-jr30g4ae.js} +4 -4
- package/dist/chunks/{studio-qgh13jtz.js → studio-k5n4es74.js} +4 -4
- package/dist/chunks/{studio-ccv2svc6.js → studio-k6hbx2fc.js} +4 -4
- package/dist/chunks/{studio-9tmebf0s.js → studio-kqh7fxn6.js} +4 -4
- package/dist/chunks/{studio-q0mvbaq6.js → studio-kyw47pet.js} +4 -4
- package/dist/chunks/studio-mkw17ngg.js +5 -0
- package/dist/chunks/{studio-x9hsze61.js → studio-myb4mf0h.js} +4 -4
- package/dist/chunks/studio-n4qq1n57.js +5 -0
- package/dist/chunks/studio-nfmpmkqb.js +5 -0
- package/dist/chunks/studio-nfwacw31.js +5 -0
- package/dist/chunks/{studio-rthyg42z.js → studio-nm86874b.js} +172 -172
- package/dist/chunks/studio-nqexty8x.js +16 -0
- package/dist/chunks/studio-nr0kcqc6.js +5 -0
- package/dist/chunks/{studio-jsgf2q5k.js → studio-nvfa137z.js} +5 -5
- package/dist/chunks/studio-psh8mwnw.js +4 -0
- package/dist/chunks/studio-qd0cycm4.js +9 -0
- package/dist/chunks/{studio-tfkznv39.js → studio-qevf5z9d.js} +4 -4
- package/dist/chunks/studio-qs5fmhtj.js +14 -0
- package/dist/chunks/studio-ry4789ng.js +7 -0
- package/dist/chunks/studio-s4fq9jht.js +5 -0
- package/dist/chunks/studio-sc35ttdk.js +5 -0
- package/dist/chunks/studio-sm2f0sc6.js +6 -0
- package/dist/chunks/{studio-3qeh9t0h.js → studio-spza69tb.js} +5 -5
- package/dist/chunks/studio-t6dqnnft.js +5 -0
- package/dist/chunks/{studio-43sd2mam.js → studio-tfmhnwyr.js} +3 -3
- package/dist/chunks/studio-tn7v8gey.js +6 -0
- package/dist/chunks/{studio-tft6r105.js → studio-trcfer2b.js} +4 -4
- package/dist/chunks/studio-tyw7tk0x.js +5 -0
- package/dist/chunks/{studio-jcnsdaep.js → studio-tyzgte21.js} +4 -4
- package/dist/chunks/studio-vtey9w98.js +5 -0
- package/dist/chunks/studio-ws2r1d7e.js +5 -0
- package/dist/chunks/studio-x2trkhk5.js +5 -0
- package/dist/chunks/{studio-mn0476ag.js → studio-xzgp0cp7.js} +4 -4
- package/dist/chunks/studio-y47090vq.js +5 -0
- package/dist/chunks/{studio-5g0rprdt.js → studio-yjh9gddd.js} +5 -5
- package/dist/chunks/studio-ywf9y1e2.js +5 -0
- package/dist/chunks/{studio-k7evxqh4.js → studio-z1p91z5a.js} +4 -4
- package/dist/chunks/studio-ztppbam0.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/{tooltip-yypz28gq.js → tooltip-dj1pvgz7.js} +2 -2
- package/dist/chunks/{tree-9xa4hm7n.js → tree-j0ebwr5s.js} +2 -2
- package/dist/chunks/{tsMode-epyfz1hv.js → tsMode-byje4a01.js} +5 -5
- package/dist/chunks/workspace-7q4arnt3.js +4 -0
- package/dist/chunks/workspace-home-ge027kr6.js +4 -0
- package/dist/chunks/{ws-client-bv552sfx.js → ws-client-h09h6sqg.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +76 -76
- package/dist/studio.js +88 -88
- package/package.json +15 -16
- package/src/browse/component-examples.ts +15 -224
- package/src/browse/library-preview.ts +6 -13
- package/src/browse/preview-root.ts +7 -4
- package/src/canvas/canvas-utils.ts +18 -4
- package/src/canvas/iframe-editable-root.ts +13 -4
- package/src/canvas/iframe-entry.ts +3 -1
- package/src/canvas/iframe-interaction.ts +11 -2
- package/src/canvas/iframe-render.ts +25 -3
- package/src/canvas/preview-placeholder.ts +120 -0
- 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/editor/shortcuts.ts +3 -0
- package/src/files/files.ts +6 -3
- package/src/new-project/new-project-modal.ts +1 -4
- package/src/panels/ai-panel.ts +27 -34
- package/src/panels/component-preview.ts +5 -0
- package/src/panels/pane-context.ts +43 -18
- package/src/panels/site-model.ts +7 -19
- package/src/panels/site-panel.ts +2 -25
- package/src/panels/stylebook-doc.ts +2 -53
- package/src/platform.ts +20 -0
- 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/references.ts +0 -0
- package/src/services/tool-executor.ts +25 -22
- package/src/state.ts +3 -13
- package/src/style/stylebook-specimens.ts +11 -15
- package/src/surfaces/files-panel.json +12 -0
- package/src/surfaces/files-panel.ts +5 -2
- package/src/surfaces/pane-context.json +286 -265
- package/src/surfaces/pane-context.ts +17 -8
- package/src/surfaces/welcome.ts +4 -1
- package/src/tabs/selection.ts +14 -109
- package/src/tabs/tab.ts +15 -7
- package/src/tabs/transact.ts +316 -124
- package/src/types.ts +13 -8
- package/src/utils/geometry.ts +49 -2
- package/dist/chunks/adapter-5tphg7xe.js +0 -4
- package/dist/chunks/add-new-flow-psym14sn.js +0 -4
- package/dist/chunks/cf-account-picker-haqazq5r.js +0 -4
- package/dist/chunks/cf-settings-h2wrh6hq.js +0 -4
- package/dist/chunks/color-m7r1vk54.js +0 -4
- package/dist/chunks/components-r22xca09.js +0 -4
- package/dist/chunks/content-source-mmppmcyg.js +0 -4
- package/dist/chunks/editor-ccd9jwsa.js +0 -4
- package/dist/chunks/entry-commands-6qxz9e8p.js +0 -4
- package/dist/chunks/file-ops-qnab36ek.js +0 -4
- package/dist/chunks/files-rcn56qvm.js +0 -4
- package/dist/chunks/format-host-yxz0kjjs.js +0 -4
- package/dist/chunks/fs-events-d1qxq4z4.js +0 -4
- package/dist/chunks/grid-open-4z1g062t.js +0 -4
- package/dist/chunks/iframe-host-ssv7kyj0.js +0 -4
- package/dist/chunks/index-f0jn7nnp.js +0 -4
- package/dist/chunks/jx-validate-ttasrbh7.js +0 -4
- package/dist/chunks/layers-bgyr11tr.js +0 -4
- package/dist/chunks/layers-panel-7ez66zeq.js +0 -4
- package/dist/chunks/lit-html-v9gh687x.js +0 -4
- package/dist/chunks/media-library-sq468wts.js +0 -4
- package/dist/chunks/media-picker-shfe59pt.js +0 -4
- package/dist/chunks/monaco-buffer-3afgwvbp.js +0 -4
- package/dist/chunks/notify-kzrjr3fv.js +0 -4
- package/dist/chunks/parse-v77xtwv7.js +0 -4
- package/dist/chunks/platform-7v4rtd7m.js +0 -4
- package/dist/chunks/publish-panel-2z9gw8ee.js +0 -4
- package/dist/chunks/publish-workspace-wrc1dgwx.js +0 -4
- package/dist/chunks/quick-search-n0t0eqxb.js +0 -4
- package/dist/chunks/references-9547rg5s.js +0 -4
- package/dist/chunks/right-panel-wsgnr9pn.js +0 -4
- package/dist/chunks/source-control-kwktr9y6.js +0 -4
- package/dist/chunks/studio-03ct6yfv.js +0 -9
- package/dist/chunks/studio-07rcfje3.js +0 -5
- package/dist/chunks/studio-3e54qh39.js +0 -7
- package/dist/chunks/studio-3exxhz5g.js +0 -5
- package/dist/chunks/studio-3qqjeep5.js +0 -6
- package/dist/chunks/studio-4hp8pkyt.js +0 -5
- package/dist/chunks/studio-58xg6j2q.js +0 -9
- package/dist/chunks/studio-6m380wp7.js +0 -5
- package/dist/chunks/studio-80jyh1xh.js +0 -4
- package/dist/chunks/studio-8vcvfs7r.js +0 -6
- package/dist/chunks/studio-bavb31r0.js +0 -5
- package/dist/chunks/studio-c05wdnt3.js +0 -5
- package/dist/chunks/studio-d5grtb9e.js +0 -16
- package/dist/chunks/studio-df0baea9.js +0 -5
- package/dist/chunks/studio-dgk4dqtx.js +0 -7
- package/dist/chunks/studio-e1g64q43.js +0 -5
- package/dist/chunks/studio-ed1hp6zf.js +0 -6
- package/dist/chunks/studio-ed3xx40h.js +0 -7
- package/dist/chunks/studio-f14e1zdj.js +0 -5
- package/dist/chunks/studio-fyh0jc96.js +0 -5
- package/dist/chunks/studio-gcxk20n7.js +0 -5
- package/dist/chunks/studio-kt3pjkwd.js +0 -5
- package/dist/chunks/studio-m8g16bgz.js +0 -5
- package/dist/chunks/studio-mx1smyq0.js +0 -5
- package/dist/chunks/studio-nqxqf8p2.js +0 -4
- package/dist/chunks/studio-p4nrkvqr.js +0 -5
- package/dist/chunks/studio-pgmvdv7z.js +0 -7
- package/dist/chunks/studio-ph84avkf.js +0 -5
- package/dist/chunks/studio-q0tccd43.js +0 -5
- package/dist/chunks/studio-q7rtx6zq.js +0 -14
- package/dist/chunks/studio-qwdqwkj0.js +0 -5
- package/dist/chunks/studio-rj9eamf8.js +0 -5
- package/dist/chunks/studio-rptgsk96.js +0 -5
- package/dist/chunks/studio-s19tp1xr.js +0 -5
- package/dist/chunks/studio-syreb644.js +0 -5
- package/dist/chunks/studio-tf9jryb1.js +0 -10
- package/dist/chunks/studio-txf2kysx.js +0 -5
- package/dist/chunks/studio-v8867ddv.js +0 -12
- package/dist/chunks/studio-vxq7fcvv.js +0 -5
- package/dist/chunks/studio-w4rz9hdk.js +0 -5
- package/dist/chunks/studio-wc649wss.js +0 -5
- package/dist/chunks/studio-wys23mxz.js +0 -5
- package/dist/chunks/studio-y3ahhdpx.js +0 -5
- package/dist/chunks/studio-z97c48fk.js +0 -5
- package/dist/chunks/studio-zat62m0c.js +0 -5
- package/dist/chunks/studio-zcd2e7yz.js +0 -5
- package/dist/chunks/toast-1gv0h53h.js +0 -4
- package/dist/chunks/workspace-dz7nxfhv.js +0 -4
- package/dist/chunks/workspace-home-xsd7hkjw.js +0 -4
- package/src/services/directory-picker.ts +0 -187
- package/src/services/gated-registry.ts +0 -77
|
@@ -21,15 +21,9 @@ import { isKeyframesAtRule } from "@jxsuite/runtime/css";
|
|
|
21
21
|
import type { StylebookEntry } from "./stylebook-panel";
|
|
22
22
|
import type { ComponentEntry } from "../files/components";
|
|
23
23
|
import type { JxPath } from "../state";
|
|
24
|
-
import { isNonNullObject
|
|
24
|
+
import { isNonNullObject } from "@jxsuite/schema/guards";
|
|
25
25
|
import type { StylebookSpecimens } from "../style/stylebook-specimens";
|
|
26
|
-
import type {
|
|
27
|
-
JxDocument,
|
|
28
|
-
JxHeadEntry,
|
|
29
|
-
JxMutableNode,
|
|
30
|
-
JxStateDefinition,
|
|
31
|
-
JxStyle,
|
|
32
|
-
} from "@jxsuite/schema/types";
|
|
26
|
+
import type { JxHeadEntry, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
|
|
33
27
|
|
|
34
28
|
export interface StylebookDocResult {
|
|
35
29
|
/** The specimen document (plain JSON — safe to post over the bridge). */
|
|
@@ -210,40 +204,6 @@ export function catalogueRows(
|
|
|
210
204
|
return out;
|
|
211
205
|
}
|
|
212
206
|
|
|
213
|
-
/**
|
|
214
|
-
* A card's specimen for a component drawn as the first row of a list
|
|
215
|
-
* (`style/stylebook-specimens.ts`): the list's repeater, reading its row from `rootState[key]` so
|
|
216
|
-
* two cards' lists cannot collide, and the rest of the page state the row was drawn in, the first
|
|
217
|
-
* card's value winning a shared name (`$page`, `$site`). Null when the sample is not a repeater
|
|
218
|
-
* over a state list.
|
|
219
|
-
*/
|
|
220
|
-
export function sampleRowSpecimen(
|
|
221
|
-
sample: JxDocument,
|
|
222
|
-
key: string,
|
|
223
|
-
rootState: Record<string, JxStateDefinition>,
|
|
224
|
-
): JxMutableNode | null {
|
|
225
|
-
const [list] = Array.isArray(sample.children) ? sample.children : [];
|
|
226
|
-
if (!isNonNullObject(list) || Array.isArray(list) || !isRef(list.items)) {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
const { $ref } = list.items;
|
|
230
|
-
const listKey = $ref.startsWith("#/state/") ? $ref.slice("#/state/".length) : "";
|
|
231
|
-
const rows = sample.state?.[listKey];
|
|
232
|
-
if (!Array.isArray(rows)) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
rootState[key] = rows;
|
|
236
|
-
for (const [name, value] of Object.entries(sample.state ?? {})) {
|
|
237
|
-
if (name !== listKey && !(name in rootState)) {
|
|
238
|
-
rootState[name] = value;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
/* SAFETY: `list` is the repeater object a page's `children` held; only its `items` is replaced,
|
|
242
|
-
with a ref of the same shape. */
|
|
243
|
-
const repeater = { ...list, items: { $ref: `#/state/${key}` } } as JxMutableNode;
|
|
244
|
-
return { children: [repeater], tagName: "div" };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
207
|
/** Options for {@link buildStylebookDoc}. */
|
|
248
208
|
export interface BuildStylebookDocOpts {
|
|
249
209
|
meta: { $sections: { label: string; elements: StylebookEntry[] }[] };
|
|
@@ -374,8 +334,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
|
|
|
374
334
|
|
|
375
335
|
// Custom components from the registry — live custom elements registered via $elements.
|
|
376
336
|
const $elements: (string | { $ref: string })[] = [];
|
|
377
|
-
// The data the sample-row cards read: each row's list, and the page context it was drawn in.
|
|
378
|
-
const rootState: Record<string, JxStateDefinition> = {};
|
|
379
337
|
{
|
|
380
338
|
let comps = opts.components;
|
|
381
339
|
if (filter) {
|
|
@@ -404,12 +362,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
|
|
|
404
362
|
const specimen: JxMutableNode = { $props: drawn.props, tagName: comp.tagName };
|
|
405
363
|
return cardNode(comp.tagName, `<${comp.tagName}>`, specimen, cardPath, null);
|
|
406
364
|
}
|
|
407
|
-
if (drawn?.kind === "row") {
|
|
408
|
-
const row = sampleRowSpecimen(drawn.sample.doc, `stylebookRow${ci}`, rootState);
|
|
409
|
-
if (row) {
|
|
410
|
-
return cardNode(comp.tagName, `<${comp.tagName}>`, row, cardPath, null);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
365
|
if (drawn?.kind !== "none") {
|
|
414
366
|
const attributes: Record<string, string> = {};
|
|
415
367
|
for (const p of comp.props ?? []) {
|
|
@@ -455,9 +407,6 @@ export function buildStylebookDoc(opts: BuildStylebookDocOpts): StylebookDocResu
|
|
|
455
407
|
doc.$head = opts.effectiveHead;
|
|
456
408
|
}
|
|
457
409
|
doc.$media = opts.effectiveMedia;
|
|
458
|
-
if (Object.keys(rootState).length > 0) {
|
|
459
|
-
doc.state = rootState;
|
|
460
|
-
}
|
|
461
410
|
doc.attributes = { class: "sb-root" };
|
|
462
411
|
doc.children = sections;
|
|
463
412
|
doc.style = transposeStylebookStyle(opts.effectiveStyle);
|
package/src/platform.ts
CHANGED
|
@@ -50,6 +50,26 @@ declare global {
|
|
|
50
50
|
var __jxLauncher: LauncherSignal | undefined;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Whether this host offers Open Project at all: a native dialog (`openProject`), or the cloud's
|
|
55
|
+
* repository picker. The dev server has neither, because its windows open a project from the
|
|
56
|
+
* `?project=` URL parameter, so every surface that offers Open Project asks this first rather than
|
|
57
|
+
* drawing a control that cannot do anything.
|
|
58
|
+
*/
|
|
59
|
+
export function canOpenProject(platform: StudioPlatform | null | undefined): boolean {
|
|
60
|
+
if (!platform) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (isFunction(platform.openProject)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
platform.openProjectPicker === "repo-list" &&
|
|
68
|
+
isFunction(platform.listRepos) &&
|
|
69
|
+
isFunction(platform.importProject)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/** @param {StudioPlatform} platform */
|
|
54
74
|
export function registerPlatform(platform: StudioPlatform) {
|
|
55
75
|
globalThis.__jxPlatform = platform;
|
|
@@ -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
|
}
|