@jxsuite/studio 5.3.0 → 5.4.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/data/css-meta.json +28 -27
- package/dist/chunks/{cf-account-picker-nwnwdm6p.js → cf-account-picker-3f08rry6.js} +21 -18
- package/dist/chunks/{cf-settings-adg74qsk.js → cf-settings-m79r7p5y.js} +4 -4
- package/dist/chunks/{components-5y78e05d.js → components-xm826zjw.js} +3 -3
- package/dist/chunks/{content-source-4rzvmp0m.js → content-source-jt7rbcbc.js} +22 -22
- package/dist/chunks/{convert-to-repeater-jxsv62v7.js → convert-to-repeater-vg38bq99.js} +22 -22
- package/dist/chunks/{editor-3qhbg51x.js → editor-ddy019sn.js} +2 -2
- package/dist/chunks/entry-commands-64nb2a9a.js +42 -0
- package/dist/chunks/{extension-sections-2mrptyek.js → extension-sections-r73an5s6.js} +30 -29
- package/dist/chunks/{file-ops-knfqshrm.js → file-ops-3xan8mnj.js} +21 -21
- package/dist/chunks/{files-y6fr4he9.js → files-egsb0ry8.js} +28 -28
- package/dist/chunks/{format-host-1k4qrcqd.js → format-host-mym8qnd0.js} +3 -3
- package/dist/chunks/{fs-events-3y6y0nrm.js → fs-events-z2gg8qjt.js} +4 -4
- package/dist/chunks/{git-panel-2rkrsjva.js → git-panel-a05atk6q.js} +20 -20
- package/dist/chunks/{grid-view-1p6hsyjx.css → grid-view-3g1h2re8.css} +956 -139
- package/dist/chunks/{grid-view-a7b87n2c.js → grid-view-qaqer9kr.js} +24 -24
- package/dist/chunks/{icons-yy10wk9r.js → icons-drxq9q10.js} +8 -2
- package/dist/chunks/{iframe-host-scpw01h8.js → iframe-host-psetcv20.js} +11 -11
- package/dist/chunks/{index-n4j7qxyr.js → index-9yjae3hz.js} +4 -4
- package/dist/chunks/{javascript-4abzjcy9.css → javascript-bc9z461d.css} +688 -54
- package/dist/chunks/{javascript-xvrfrpkb.js → javascript-hr5sg57n.js} +2 -2
- package/dist/chunks/{jsonMode-vvmpmk34.css → jsonMode-qqd9nm1t.css} +1120 -303
- package/dist/chunks/{jsonMode-tyx2kj6p.js → jsonMode-zs88v0wr.js} +4 -4
- package/dist/chunks/layers-panel-83a16ytx.js +41 -0
- package/dist/chunks/{layers-jnvdbqva.js → layers-s6mc01ry.js} +3 -3
- package/dist/chunks/{media-picker-f66nav16.js → media-picker-nhn6rv4w.js} +11 -11
- package/dist/chunks/{menu-1dfhry4s.js → menu-8j4qxjrp.js} +4 -4
- package/dist/chunks/{monaco-binding-sw6en4dw.js → monaco-binding-e8d9wzyk.js} +3 -3
- package/dist/chunks/{monaco-buffer-pykwbm92.js → monaco-buffer-jaq1t9zj.js} +2 -2
- package/dist/chunks/{monaco-setup-8vbyqnhr.css → monaco-setup-ey9cjcm3.css} +954 -137
- package/dist/chunks/{monaco-setup-km6abc6q.js → monaco-setup-gsjdysze.js} +6 -6
- package/dist/chunks/{platform-ts9cg1a4.js → platform-fnchmhy7.js} +5 -3
- package/dist/chunks/publish-panel-ak6hxfma.js +27 -0
- package/dist/chunks/quick-search-c63mkt3b.js +47 -0
- package/dist/chunks/{references-h69xp9zz.js → references-f7bdv3z5.js} +3 -3
- package/dist/chunks/right-panel-t5n8wttg.js +45 -0
- package/dist/chunks/{studio-ze46gzjh.js → studio-01269reb.js} +7 -7
- package/dist/chunks/{studio-rnyf3f8e.js → studio-0a4gncx0.js} +3 -3
- package/dist/chunks/{studio-e1f24eff.js → studio-0d4p4wxj.js} +6778 -2993
- package/dist/chunks/{studio-4wbb9ct0.js → studio-1tejxvba.js} +3006 -1855
- package/dist/chunks/{studio-d1n2bykn.js → studio-3nprwbd9.js} +117 -25
- package/dist/chunks/{studio-4c041r8y.js → studio-40z39dme.js} +3 -3
- package/dist/chunks/{studio-151w0xnh.js → studio-482eeazk.js} +20 -20
- package/dist/chunks/{studio-cgprcdxn.js → studio-4998spet.js} +10 -9
- package/dist/chunks/{studio-nq5yf5mp.js → studio-557bmn0x.js} +2 -2
- package/dist/chunks/{studio-rtskkm43.js → studio-5w26wza5.js} +3 -3
- package/dist/chunks/{studio-6nzaktvy.js → studio-6pwn9j8c.js} +6 -3
- package/dist/chunks/{studio-wnsyffg1.js → studio-6vg0x5fz.js} +3 -2
- package/dist/chunks/{studio-vn5hske5.js → studio-7d3c1m59.js} +12 -5
- package/dist/chunks/{studio-6gryj0sg.js → studio-9vsvz7w7.js} +6 -6
- package/dist/chunks/{studio-50wfwy44.js → studio-9wm0xzpf.js} +19 -19
- package/dist/chunks/{studio-0j7r7s9g.js → studio-ag3z6shb.js} +10 -4
- package/dist/chunks/studio-aj9drheq.js +177 -0
- package/dist/chunks/{studio-xajk70w0.js → studio-apv9x70c.js} +189 -130
- package/dist/chunks/{studio-c04nzbby.js → studio-b9q3znz8.js} +2 -2
- package/dist/chunks/{studio-nfx354t1.js → studio-bh47w0cy.js} +9 -10
- package/dist/chunks/{studio-gk2twyh4.js → studio-dgdm6vvt.js} +264 -52
- package/dist/chunks/{studio-gtfq1dkz.js → studio-dwdcr2e8.js} +4 -4
- package/dist/chunks/{studio-nqgawfks.js → studio-fbcyz234.js} +146 -139
- package/dist/chunks/{studio-27q131j3.js → studio-fqpkpq1t.js} +7 -4
- package/dist/chunks/{studio-c0qgc3tn.js → studio-fy320dfc.js} +62 -68
- package/dist/chunks/{studio-zjjde71a.js → studio-gjj4bbfx.js} +2 -2
- package/dist/chunks/{studio-g33v4fe9.js → studio-jfhxy8h3.js} +7 -7
- package/dist/chunks/{studio-ab87phj1.js → studio-kwewmhhq.js} +4 -4
- package/dist/chunks/{studio-7rrb1esn.js → studio-m58xn8at.js} +25 -24
- package/dist/chunks/{studio-fzjx96t2.js → studio-mfkh1q8d.js} +17 -17
- package/dist/chunks/{studio-drg8qj9k.js → studio-mrh0a92f.js} +17 -17
- package/dist/chunks/{studio-jt27ta6x.js → studio-mt79tn56.js} +3 -3
- package/dist/chunks/{studio-b66rsh78.js → studio-ne593z1j.js} +38 -31
- package/dist/chunks/{studio-h4nvnjvz.js → studio-ptfcxkz5.js} +418 -232
- package/dist/chunks/{studio-dd1qqkry.js → studio-qxye31h7.js} +7 -7
- package/dist/chunks/{studio-ye9drbk8.js → studio-rav5bpmp.js} +73 -52
- package/dist/chunks/{studio-6kvgrkrp.js → studio-rwmmbrth.js} +6 -6
- package/dist/chunks/{studio-b7xacrwe.js → studio-sceg8ws0.js} +8 -8
- package/dist/chunks/{studio-msc1ee1m.js → studio-sjas5b3a.js} +19 -13
- package/dist/chunks/{studio-a65w20ex.js → studio-vd67b66d.js} +3 -3
- package/dist/chunks/{studio-7144epdr.js → studio-w475cbf9.js} +10 -10
- package/dist/chunks/{studio-4f3psrk1.js → studio-w7gpcvfj.js} +9 -9
- package/dist/chunks/{studio-nsgw7dbk.js → studio-xrbfdxgk.js} +7 -7
- package/dist/chunks/{studio-ke09tn9k.js → studio-y49b6mjv.js} +35 -35
- package/dist/chunks/{studio-j0w60gft.js → studio-ygvr9e4q.js} +10 -10
- package/dist/chunks/{studio-z2smx5ce.js → studio-yxmmjy2a.js} +9 -6
- package/dist/chunks/{tsMode-jy4m86rv.js → tsMode-ay4c78ne.js} +4 -4
- package/dist/chunks/{tsMode-hnxxb73h.css → tsMode-c72wyz27.css} +954 -137
- package/dist/chunks/{workspace-np10mayv.js → workspace-b9k2frks.js} +5 -5
- package/dist/chunks/{ws-client-ny4zc51r.js → ws-client-erze4czt.js} +356 -20
- package/dist/codicon.ttf +0 -0
- package/dist/iframe-entry.js +18 -16
- package/dist/studio.css +956 -139
- package/dist/studio.js +1453 -830
- package/dist/workers/editor.worker.js +196 -107
- package/dist/workers/json.worker.js +196 -107
- package/dist/workers/ts.worker.js +196 -107
- package/package.json +15 -14
- package/src/about/about-modal.ts +4 -3
- package/src/browse/library-commands.ts +15 -13
- package/src/browse/library-pane.ts +2 -2
- package/src/canvas/canvas-render.ts +97 -83
- package/src/canvas/canvas-surface.ts +2 -2
- package/src/canvas/canvas-utils.ts +42 -32
- package/src/canvas/diff-toolbar.ts +21 -2
- package/src/canvas/diff-view.ts +30 -6
- package/src/canvas/iframe-entry.ts +2 -2
- package/src/canvas/iframe-host.ts +5 -5
- package/src/canvas/iframe-interaction.ts +7 -6
- package/src/canvas/iframe-overlay.ts +1 -1
- package/src/canvas/iframe-protocol.ts +6 -5
- package/src/canvas/iframe-render.ts +3 -3
- package/src/canvas/popover-state.ts +1 -1
- package/src/canvas/stage-scrollbar.ts +112 -0
- package/src/canvas/surface-registry.ts +9 -2
- package/src/collab/collab-commands.ts +8 -6
- package/src/collab/collab-session.ts +4 -4
- package/src/collab/collab-state.ts +4 -4
- package/src/collab/presence-chips.ts +3 -3
- package/src/commands/app-commands.ts +7 -7
- package/src/commands/budget.ts +17 -16
- package/src/commands/command-args.ts +10 -9
- package/src/commands/context.ts +20 -10
- package/src/commands/defaults.ts +30 -29
- package/src/commands/levels.ts +8 -6
- package/src/commands/live-context.ts +12 -10
- package/src/commands/reference.ts +5 -5
- package/src/commands/registry.ts +2 -2
- package/src/content/draft-state.ts +9 -8
- package/src/content/entry-commands.ts +16 -14
- package/src/content/entry-editor.ts +2 -2
- package/src/content/entry-fields.ts +1 -1
- package/src/editor/context-menu.ts +4 -4
- package/src/editor/convert-to-component.ts +6 -5
- package/src/editor/inline-edit.ts +3 -3
- package/src/editor/shortcuts.ts +88 -20
- package/src/editor/slash-menu.ts +11 -10
- package/src/files/elements.ts +4 -5
- package/src/files/file-ops.ts +1 -1
- package/src/files/files.ts +21 -17
- package/src/files/serialize-document.ts +1 -1
- package/src/format/convert-file.ts +5 -5
- package/src/github/github-auth-status.ts +107 -0
- package/src/github/github-auth.ts +18 -82
- package/src/github/github-publish.ts +7 -6
- package/src/grid/cell-editors.ts +8 -7
- package/src/grid/grid-layout.ts +1 -1
- package/src/grid/grid-open.ts +6 -5
- package/src/grid/grid-panel.ts +13 -12
- package/src/grid/redirects-grid.ts +14 -11
- package/src/grid/redirects.ts +10 -5
- package/src/grid/sources/content-source.ts +10 -9
- package/src/hosting/document.ts +28 -1
- package/src/hosting/stage.ts +15 -4
- package/src/i18n/i18n-commands.ts +9 -7
- package/src/new-project/location-fields.ts +16 -3
- package/src/new-project/new-project-modal.ts +23 -7
- package/src/page-params.ts +6 -6
- package/src/panels/activity-panel.ts +11 -10
- package/src/panels/ai-chat/chat-view.ts +27 -14
- package/src/panels/ai-chat/composer.ts +10 -3
- package/src/panels/ai-panel.ts +87 -29
- package/src/panels/block-action-bar.ts +59 -33
- package/src/panels/bottom-dock.ts +22 -22
- package/src/panels/data-explorer.ts +9 -9
- package/src/panels/data-grid.ts +1 -1
- package/src/panels/editors.ts +8 -8
- package/src/panels/elements-panel.ts +7 -8
- package/src/panels/empty-state.ts +1 -1
- package/src/panels/events-panel.ts +13 -12
- package/src/panels/formula-workspace.ts +8 -5
- package/src/panels/frontmatter-fields.ts +4 -1
- package/src/panels/frontmatter-panel.ts +18 -18
- package/src/panels/git-panel.ts +19 -13
- package/src/panels/head-panel.ts +9 -10
- package/src/panels/i18n-panel.ts +6 -4
- package/src/panels/imports-panel.ts +1 -1
- package/src/panels/jump-bar.ts +20 -18
- package/src/panels/layers-panel.ts +19 -17
- package/src/panels/left-panel.ts +3 -3
- package/src/panels/navigator-panels.ts +8 -8
- package/src/panels/overlays.ts +1 -1
- package/src/panels/pane-context.ts +56 -28
- package/src/panels/pane-grid.ts +9 -8
- package/src/panels/panel-registry.ts +47 -19
- package/src/panels/problems-panel.ts +11 -12
- package/src/panels/properties-panel.ts +21 -20
- package/src/panels/provenance.ts +3 -3
- package/src/panels/quick-search.ts +15 -11
- package/src/panels/right-panel.ts +4 -3
- package/src/panels/seo-modal.ts +5 -4
- package/src/panels/settings-menu.ts +9 -9
- package/src/panels/settings-pane.ts +3 -3
- package/src/panels/signals-panel.ts +16 -9
- package/src/panels/statement-editor.ts +12 -9
- package/src/panels/style-panel.ts +45 -41
- package/src/panels/stylebook-panel.ts +0 -1
- package/src/panels/tab-strip.ts +26 -23
- package/src/platform-errors.ts +2 -2
- package/src/platform.ts +44 -4
- package/src/platforms/cloud.ts +11 -1
- package/src/platforms/default-platform.ts +141 -4
- package/src/platforms/devserver.ts +23 -3
- package/src/publish/deploy-checklist.ts +11 -10
- package/src/publish/publish-commands.ts +9 -8
- package/src/services/a11y-report.ts +1 -1
- package/src/services/ai-ask.ts +39 -16
- package/src/services/ai-command-tools.ts +28 -14
- package/src/services/ai-import-tools.ts +43 -29
- package/src/services/ai-project-tools.ts +20 -38
- package/src/services/ai-settings.ts +55 -2
- package/src/services/ai-tools.ts +69 -58
- package/src/services/ai-write-report.ts +4 -4
- package/src/services/ai-writes.ts +74 -55
- package/src/services/automation.ts +12 -12
- package/src/services/clock.ts +2 -1
- package/src/services/connection.ts +2 -2
- package/src/services/context-manager.ts +69 -6
- package/src/services/directory-picker.ts +93 -27
- package/src/services/document-assistant.ts +86 -17
- package/src/services/gated-registry.ts +4 -3
- package/src/services/idle.ts +5 -4
- package/src/services/live-surfaces.ts +2 -1
- package/src/services/notify.ts +9 -7
- package/src/services/popover-report.ts +3 -3
- package/src/services/preview-eval.ts +1 -1
- package/src/services/project-adoption.ts +34 -1
- package/src/services/project-path.ts +53 -0
- package/src/services/settings/kernel.ts +13 -2
- package/src/services/tool-executor.ts +135 -30
- package/src/services/tool-outcomes.ts +143 -0
- package/src/settings/contexts-section.ts +8 -8
- package/src/settings/contributed-section.ts +6 -6
- package/src/settings/css-vars-editor.ts +6 -5
- package/src/settings/extension-commands.ts +19 -18
- package/src/settings/extension-sections.ts +7 -7
- package/src/settings/extensions-section.ts +3 -3
- package/src/settings/general-settings.ts +21 -18
- package/src/settings/preferences-accounts.ts +3 -3
- package/src/settings/preferences-dialog.ts +9 -8
- package/src/settings/preferences-sections.ts +1 -1
- package/src/settings/project-sections.ts +2 -2
- package/src/settings/section-registry.ts +7 -6
- package/src/settings/settings-document.ts +41 -43
- package/src/shell/tree.ts +29 -0
- package/src/shell.ts +63 -55
- package/src/site-context.ts +1 -1
- package/src/store.ts +2 -2
- package/src/studio.ts +46 -28
- package/src/style/live-preview.ts +8 -8
- package/src/style/project-styles.ts +8 -9
- package/src/style/token-ref.ts +2 -2
- package/src/surfaces/about.json +6 -2
- package/src/surfaces/add-repo.json +2 -2
- package/src/surfaces/add-repo.ts +2 -2
- package/src/surfaces/ai-chat.json +14 -6
- package/src/surfaces/ai-chat.ts +4 -2
- package/src/surfaces/ai-credentials-form.json +53 -49
- package/src/surfaces/ai-credentials-form.ts +18 -4
- package/src/surfaces/ai-managed-connect.json +4 -4
- package/src/surfaces/block-action-bar.json +4 -4
- package/src/surfaces/block-action-bar.ts +3 -3
- package/src/surfaces/boot-failure.json +228 -0
- package/src/surfaces/boot-failure.ts +156 -0
- package/src/surfaces/bottom-dock.json +1 -1
- package/src/surfaces/canvas-stage.json +9 -40
- package/src/surfaces/canvas-stage.ts +9 -9
- package/src/surfaces/cf-account-picker.json +11 -8
- package/src/surfaces/commandbar.ts +11 -10
- package/src/surfaces/convert-repeater.ts +3 -2
- package/src/surfaces/dialog.json +7 -1
- package/src/surfaces/dialog.ts +2 -2
- package/src/surfaces/doc-header.json +6 -13
- package/src/surfaces/doc-header.ts +4 -5
- package/src/surfaces/entry-editor.json +1 -1
- package/src/surfaces/entry-editor.ts +2 -1
- package/src/surfaces/expression-editor.json +5 -5
- package/src/surfaces/files-panel.json +4 -3
- package/src/surfaces/files-panel.ts +1 -1
- package/src/surfaces/formula-palette.json +2 -2
- package/src/surfaces/formula-palette.ts +1 -1
- package/src/surfaces/git-panel.json +167 -109
- package/src/surfaces/git-panel.ts +2 -2
- package/src/surfaces/github-auth.json +2 -1
- package/src/surfaces/github-publish.ts +3 -3
- package/src/surfaces/grid-open.json +1 -1
- package/src/surfaces/grid-panel.json +2 -2
- package/src/surfaces/grid-views.json +1 -1
- package/src/surfaces/inspector-dock.json +2 -2
- package/src/surfaces/jump-bar.json +1 -1
- package/src/surfaces/jump-bar.ts +1 -1
- package/src/surfaces/library-pane.ts +2 -1
- package/src/surfaces/logic-panel.json +7 -7
- package/src/surfaces/logic-panel.ts +6 -4
- package/src/surfaces/logic-workspace.json +2 -2
- package/src/surfaces/logic-workspace.ts +2 -2
- package/src/surfaces/media-browser.ts +3 -3
- package/src/surfaces/media-pane.ts +2 -1
- package/src/surfaces/menu.ts +5 -5
- package/src/surfaces/new-project.json +1 -1
- package/src/surfaces/pane-context.json +86 -57
- package/src/surfaces/pane-context.ts +2 -1
- package/src/surfaces/pane-grid.json +3 -3
- package/src/surfaces/panel-activity.json +2 -1
- package/src/surfaces/panel-deploy-checklist.json +1 -1
- package/src/surfaces/panel-elements.ts +2 -1
- package/src/surfaces/panel-i18n.ts +2 -1
- package/src/surfaces/panel-outline.json +1 -1
- package/src/surfaces/panel-outline.ts +4 -3
- package/src/surfaces/panel-page.json +2 -2
- package/src/surfaces/panel-page.ts +4 -1
- package/src/surfaces/panel-problems.json +4 -3
- package/src/surfaces/panel-problems.ts +2 -2
- package/src/surfaces/panel-signals.json +23 -8
- package/src/surfaces/panel-signals.ts +5 -1
- package/src/surfaces/panel-stylebook-layers.json +1 -1
- package/src/surfaces/panel-stylebook-layers.ts +2 -2
- package/src/surfaces/preferences.json +21 -8
- package/src/surfaces/preferences.ts +9 -1
- package/src/surfaces/properties-panel.json +4 -4
- package/src/surfaces/properties-panel.ts +2 -2
- package/src/surfaces/publish.json +1 -1
- package/src/surfaces/publish.ts +2 -1
- package/src/surfaces/push-plan.json +4 -3
- package/src/surfaces/rail.json +2 -2
- package/src/surfaces/rail.ts +10 -4
- package/src/surfaces/reference-field.json +1 -1
- package/src/surfaces/schema-form.json +2 -2
- package/src/surfaces/schema-form.ts +1 -1
- package/src/surfaces/seo.json +1 -1
- package/src/surfaces/seo.ts +2 -2
- package/src/surfaces/settings-contributed.ts +2 -1
- package/src/surfaces/settings-css-vars.json +4 -4
- package/src/surfaces/settings-deploy.json +4 -4
- package/src/surfaces/settings-deploy.ts +2 -1
- package/src/surfaces/settings-extensions.json +6 -6
- package/src/surfaces/settings-extensions.ts +6 -2
- package/src/surfaces/settings-locales.json +4 -4
- package/src/surfaces/settings-overview.ts +2 -1
- package/src/surfaces/settings-pane.ts +3 -2
- package/src/surfaces/settings-rawjson.ts +1 -1
- package/src/surfaces/slash-menu.ts +4 -4
- package/src/surfaces/statements.json +3 -3
- package/src/surfaces/statusbar.ts +23 -23
- package/src/surfaces/style-panel.json +9 -5
- package/src/surfaces/style-panel.ts +3 -3
- package/src/surfaces/tab-strip.json +2 -2
- package/src/surfaces/target-line.json +1 -1
- package/src/surfaces/target-line.ts +12 -10
- package/src/surfaces/welcome.json +7 -2
- package/src/tabs/doc-op-apply.ts +5 -5
- package/src/tabs/patch-ops.ts +3 -3
- package/src/tabs/project-config.ts +10 -10
- package/src/tabs/selection.ts +1 -1
- package/src/tabs/tab.ts +13 -14
- package/src/tabs/transact.ts +18 -24
- package/src/types.ts +27 -3
- package/src/ui/ai-credentials-form.ts +74 -16
- package/src/ui/ai-managed-connect.ts +68 -19
- package/src/ui/color-selector.ts +1 -1
- package/src/ui/expression-editor.ts +3 -3
- package/src/ui/form-controls.ts +6 -5
- package/src/ui/formula-catalog.ts +4 -4
- package/src/ui/formula-chips.ts +2 -2
- package/src/ui/layers.ts +20 -18
- package/src/ui/panel-resize.ts +5 -5
- package/src/ui/popover-surface.ts +2 -2
- package/src/ui/progress-modal.ts +3 -3
- package/src/ui/regions.ts +14 -9
- package/src/ui/schema-form.ts +10 -9
- package/src/ui/timing.ts +9 -7
- package/src/ui/value-source.ts +4 -4
- package/src/ui/virtual-window.ts +1 -1
- package/src/utils/base64.ts +20 -1
- package/src/utils/canvas-media.ts +1 -1
- package/src/utils/studio-utils.ts +0 -31
- package/src/view.ts +4 -3
- package/src/workspace/pane-derive.ts +14 -13
- package/src/workspace/session.ts +7 -6
- package/src/workspace/workspace.ts +49 -39
- package/styles/canvas.css +1 -27
- package/styles/inspector.css +4 -3
- package/styles/overlays.css +1 -1
- package/styles/tokens.css +17 -1
- package/styles/tokens.json +6 -1
- package/dist/chunks/entry-commands-1wnsqn5d.js +0 -42
- package/dist/chunks/layers-panel-73gfkcga.js +0 -41
- package/dist/chunks/publish-panel-z1zd2da1.js +0 -27
- package/dist/chunks/quick-search-zdv7h8ht.js +0 -47
- package/dist/chunks/right-panel-889vbnm5.js +0 -45
- package/dist/chunks/studio-727xh0e3.js +0 -76
- package/src/files/json-layout.ts +0 -474
- package/src/services/ai-turn-signal.ts +0 -70
package/src/services/idle.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Idle.ts — "settled" is a predicate, and it can fail.
|
|
4
4
|
*
|
|
5
5
|
* One question — _has Studio finished reacting?_ — asked of the five subsystems that can still be
|
|
6
|
-
* mid-flight, over two consecutive animation frames (spec studio.md §13.5,
|
|
6
|
+
* mid-flight, over two consecutive animation frames (spec studio.md §13.5, and "Determinism" in
|
|
7
|
+
* `scripts/screenshots/README.md`).
|
|
7
8
|
*
|
|
8
9
|
* **The rejection is the point.** The thing this replaces is 115 `wait: {ms}` steps totalling 73
|
|
9
10
|
* seconds, and a sleep cannot fail: a subsystem that was slow that day got answered with `+500 ms`
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
* all thirty-odd `git*`/fs/fetch methods pass through.
|
|
30
31
|
* 4. `layers.overlayIdleBlockers()` — overlays still in transition. Only the SETTLING window counts: a
|
|
31
32
|
* resting toast is not a blocker, which is what lets `toastsAreHeld()` hold one open for a
|
|
32
|
-
* capture without `probeIdle()` waiting forever alongside it (
|
|
33
|
+
* capture without `probeIdle()` waiting forever alongside it (§13.5's toast exception).
|
|
33
34
|
* 5. `activity-panel.activityIdleBlockers()` — operations still RUNNING in the Activity tab. Not a
|
|
34
35
|
* duplicate of `platform`: an activity spans a whole operation (an install, a deploy, a clone)
|
|
35
36
|
* across many PAL calls, so the gaps between them look quiet at the seam while the operation is
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
* selection range `selectableRange: 1` gives them. Nothing else covered Tabulator: a grid
|
|
39
40
|
* command resolves when the panel mounts, which is several frames before the table is drawn.
|
|
40
41
|
*
|
|
41
|
-
* The consumers are the `packages/studio:verify` skill, the screenshot runner and
|
|
42
|
-
* tracker (this is a read-only projection of the same in-flight set that dock renders).
|
|
42
|
+
* The consumers are the `packages/studio:verify` skill, the screenshot runner and the Activity
|
|
43
|
+
* tracker of §16.4 (this is a read-only projection of the same in-flight set that dock renders).
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
import { rendersInFlight } from "../store";
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* outside Studio reaches the shell on the next reload, as it always did, because a watcher is a
|
|
18
18
|
* second writer racing the tab. And it does not diff: a saved document is re-registered whole and
|
|
19
19
|
* every root re-mounted, because a surface's host scope is reused (`SurfaceMount.remount`) and only
|
|
20
|
-
* the surface-local state resets, which is the contract §9.3 gives the
|
|
20
|
+
* the surface-local state resets, which is the contract `studio-ui-guidelines.md` §9.3 gives the
|
|
21
|
+
* registry.
|
|
21
22
|
*
|
|
22
23
|
* A kit redefinition reaches the canvas frames too, with the FILE's URL under the project as its
|
|
23
24
|
* base: the frame draws a project's elements from the project's own files, so a sibling `$ref` in
|
package/src/services/notify.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Notify.ts — the third record type (
|
|
2
|
+
* Notify.ts — the third record type, beside `Command` and `Panel` (§16), and the app's only way to
|
|
3
|
+
* say what happened.
|
|
3
4
|
*
|
|
4
5
|
* What this replaces: 78 `statusMessage()` call sites — 26 failures and 52 successes — rendered in
|
|
5
6
|
* identical 11px grey text in a 24px strip and destroyed after 3000 ms. A failure and a save looked
|
|
@@ -7,20 +8,20 @@
|
|
|
7
8
|
* read them. `statusMessage` is deleted, not wrapped; `scripts/check-styles.ts` bans the name from
|
|
8
9
|
* `src/` so it cannot regrow.
|
|
9
10
|
*
|
|
10
|
-
* **A notification is a record, and its lifetime is chosen by the action it requires** (§
|
|
11
|
+
* **A notification is a record, and its lifetime is chosen by the action it requires** (§16.1):
|
|
11
12
|
*
|
|
12
13
|
* | Tier | Host | Lifetime | Use when |
|
|
13
14
|
* | --------- | -------------------------- | ------------------ | ------------------------------ |
|
|
14
15
|
* | `toast` | the fourth overlay layer | timed, dismissible | reversible, or needs no action |
|
|
15
16
|
* | `problem` | the Bottom dock's Problems | until it is fixed | it must be fixed |
|
|
16
17
|
*
|
|
17
|
-
* §
|
|
18
|
+
* §16.1 names a third tier — **inline**, the refusal a field draws at the control (`jx-field`'s own
|
|
18
19
|
* `[part="error"]`, projected by whichever inspector document owns the row). It is deliberately NOT
|
|
19
20
|
* a member of {@link NotificationTier}: an inline error is a value a control renders next to the
|
|
20
21
|
* field the user is editing, not a record the app posts to a host, and it has no store to live in.
|
|
21
22
|
* Declaring a tier here whose only possible outcome is being dropped on the floor would be exactly
|
|
22
23
|
* the silence this module exists to end, so the union has two members and the third tier arrives as
|
|
23
|
-
* a field's own projected value (
|
|
24
|
+
* a field's own projected value (§16.5).
|
|
24
25
|
*
|
|
25
26
|
* **Recovery is a command id, not a per-call-site closure.** `notify.error("Save failed", { action:
|
|
26
27
|
* "file.save" })` gives the toast and the Problems row a Retry button whose label, availability,
|
|
@@ -64,8 +65,9 @@ const DEFAULT_TIER: Readonly<Record<Severity, NotificationTier>> = {
|
|
|
64
65
|
/**
|
|
65
66
|
* How long a toast rests before it is taken away, by severity.
|
|
66
67
|
*
|
|
67
|
-
* §
|
|
68
|
-
* it, because it is the one a reader is most likely to have looked away
|
|
68
|
+
* `studio-ui-guidelines.md` §13.2 caps the band at 4–8s. A warning that nobody has to act on still
|
|
69
|
+
* deserves the long end of it, because it is the one a reader is most likely to have looked away
|
|
70
|
+
* from.
|
|
69
71
|
*/
|
|
70
72
|
export const TOAST_LIFETIME_MS: Readonly<Record<Severity, number>> = {
|
|
71
73
|
error: 8000,
|
|
@@ -214,7 +216,7 @@ export function notify(
|
|
|
214
216
|
return record;
|
|
215
217
|
}
|
|
216
218
|
|
|
217
|
-
/** `notify.success(msg, opts)` and its three siblings — the shape §
|
|
219
|
+
/** `notify.success(msg, opts)` and its three siblings — the shape §16.1 names. */
|
|
218
220
|
function severityHelper(severity: Severity) {
|
|
219
221
|
return (message: string, options: NotifyOptions = {}) => notify(severity, message, options);
|
|
220
222
|
}
|
|
@@ -113,9 +113,9 @@ export function reportPopoverProblems(doc: JxElement, path?: string): number {
|
|
|
113
113
|
...(path === undefined ? {} : { path }),
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
-
/* The dialog and invoker-command rules (spec §8.7) file under the same source: one report
|
|
117
|
-
everything the platform overlays. They carry no repair button yet — the popover repairs
|
|
118
|
-
`display` into `:popover-open`, and a dialog's open rule is a different selector. */
|
|
116
|
+
/* The dialog and invoker-command rules (`spec.md` §8.7) file under the same source: one report
|
|
117
|
+
for everything the platform overlays. They carry no repair button yet — the popover repairs
|
|
118
|
+
move `display` into `:popover-open`, and a dialog's open rule is a different selector. */
|
|
119
119
|
const dialogDefects = findDialogDefects(doc, KIT_SCOPE);
|
|
120
120
|
for (const defect of dialogDefects) {
|
|
121
121
|
notify(defect.severity, defect.message, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Parent-side live expression preview (spec §19.9).
|
|
3
|
+
* Parent-side live expression preview (`spec.md` §19.9).
|
|
4
4
|
*
|
|
5
5
|
* Evaluates an expression node against the canvas iframe's last dataScope snapshot
|
|
6
6
|
* (tab.session.canvas.scope) on a structuredClone, so mutating operators are safe — they mutate the
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { initProjectRepo } from "../files/files";
|
|
13
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
13
14
|
import { beginBatch, endBatch, isBatching } from "../tabs/transact";
|
|
14
15
|
import { workspace } from "../workspace/workspace";
|
|
15
16
|
import type { Tab } from "../tabs/tab";
|
|
@@ -41,6 +42,12 @@ export interface AdoptOutcome {
|
|
|
41
42
|
adopted: boolean;
|
|
42
43
|
/** Why adoption failed, when it threw. Null when it did not (including when it silently did not). */
|
|
43
44
|
error: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* `adopted` is false BY DESIGN: a different project was already showing here, so the open flow
|
|
47
|
+
* below put the new one in a window of its own instead of replacing what this one is showing. Not
|
|
48
|
+
* a failure — a caller that only checks `adopted` would otherwise report a success as one.
|
|
49
|
+
*/
|
|
50
|
+
openedElsewhere: boolean;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
/** The seams {@link adoptCreatedProject} runs through, so each caller keeps the one it already had. */
|
|
@@ -84,6 +91,32 @@ export async function adoptCreatedProject(
|
|
|
84
91
|
// Never fails the create: a project that was written stays written (initProjectRepo notifies).
|
|
85
92
|
await initRepo(root);
|
|
86
93
|
|
|
94
|
+
/* `initRepo` ran git init through THIS window's backend — `platform.createProject` re-roots it to
|
|
95
|
+
`root` as a side effect of scaffolding, because git init has no explicit-root variant of its
|
|
96
|
+
own. That is fine when this window is about to show `root` too, but wrong when a DIFFERENT
|
|
97
|
+
project was already showing here: `adopt` below (`openRecentProject`) would open `root` in a
|
|
98
|
+
new window on exactly that condition, and that new-window flow dedupes by asking the backend
|
|
99
|
+
which window already has `root` open — which, thanks to the reroot above, now reads as THIS
|
|
100
|
+
one. Left alone, that either hands `root` back to this same window (skipping the new window
|
|
101
|
+
entirely) or opens a second window while leaving this one's backend silently pointed at `root`
|
|
102
|
+
even though its own UI still names the project it had before. Putting this window's backend
|
|
103
|
+
back where its UI says it is — before `adopt` runs — is what keeps both promises: the window
|
|
104
|
+
that already had a project keeps that project, backend included, and `root` gets a window of
|
|
105
|
+
its own. */
|
|
106
|
+
const platform = hasPlatform() ? getPlatform() : null;
|
|
107
|
+
const shownRoot = workspace.projectRoot;
|
|
108
|
+
const openedElsewhere = Boolean(
|
|
109
|
+
platform && shownRoot && shownRoot !== root && platform.openProjectInNewWindow,
|
|
110
|
+
);
|
|
111
|
+
if (openedElsewhere && shownRoot && platform?.setWindowProject) {
|
|
112
|
+
try {
|
|
113
|
+
await platform.setWindowProject(shownRoot);
|
|
114
|
+
} catch {
|
|
115
|
+
/* Best-effort restore — `adopt` below still runs, and a failed restore here is no worse than
|
|
116
|
+
the mismatch this guards against. */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
87
120
|
const wasBatching = isBatching();
|
|
88
121
|
if (wasBatching) {
|
|
89
122
|
endBatch();
|
|
@@ -98,5 +131,5 @@ export async function adoptCreatedProject(
|
|
|
98
131
|
beginBatch(getTab());
|
|
99
132
|
}
|
|
100
133
|
|
|
101
|
-
return { adopted: workspace.projectRoot === root, error: adoptionError };
|
|
134
|
+
return { adopted: workspace.projectRoot === root, error: adoptionError, openedElsewhere };
|
|
102
135
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-path.ts — the containment check the assistant's file tools run before they touch disk.
|
|
3
|
+
*
|
|
4
|
+
* `ai.md` §4: a file tool refuses a path outside the open project itself, rather than leaving the
|
|
5
|
+
* server to. `list_files`, `read_file` and `write_file` always refused one; `create_component`
|
|
6
|
+
* and `create_page` took the model's path as given and handed it to the platform write. One module,
|
|
7
|
+
* so the two sets of tools cannot disagree about what "inside the project" means, and a leaf,
|
|
8
|
+
* because the document tools and the project tools already import each other's neighbours.
|
|
9
|
+
*
|
|
10
|
+
* @docs studio/ai/chat
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ToolResult } from "@jxsuite/ai/tools";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a project-relative path, or return null when it escapes the project (absolute paths,
|
|
18
|
+
* `..` segments, drive letters, a home directory). The server re-checks; this keeps the error
|
|
19
|
+
* actionable.
|
|
20
|
+
*
|
|
21
|
+
* @param {unknown} path
|
|
22
|
+
* @returns {string | null}
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeRelPath(path: unknown): string | null {
|
|
25
|
+
if (typeof path !== "string" || !path.trim()) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
let p = path.trim().replaceAll("\\", "/");
|
|
29
|
+
while (p.startsWith("./")) {
|
|
30
|
+
p = p.slice(2);
|
|
31
|
+
}
|
|
32
|
+
if (p.startsWith("/") || p.startsWith("~") || /^[A-Za-z]:/.test(p)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (p.split("/").includes("..")) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return p;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The refusal a tool returns for a path {@link normalizeRelPath} rejected, quoting what the model
|
|
43
|
+
* sent so it can see what to change.
|
|
44
|
+
*
|
|
45
|
+
* @param {unknown} path
|
|
46
|
+
* @returns {ToolResult}
|
|
47
|
+
*/
|
|
48
|
+
export function invalidPathResult(path: unknown): ToolResult {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: `Invalid path ${JSON.stringify(path)} — use a path relative to the project root (no leading "/", no "..").`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -184,9 +184,20 @@ function apply(patch: SettingsPatch): string[] {
|
|
|
184
184
|
return moved;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* What a write of `value` would store: the definition's own `normalize`, or the value as given.
|
|
189
|
+
*
|
|
190
|
+
* The one home of that rule, and exported for a reason: a form that asks "would Save change
|
|
191
|
+
* anything?" has to compare what the store WOULD hold, not what was typed — or a pasted key's
|
|
192
|
+
* trailing newline, or an endpoint's trailing slash, reads as an edit the store would erase.
|
|
193
|
+
*/
|
|
194
|
+
export function normalizeSetting(definition: SettingDefinition, value: string): string {
|
|
195
|
+
return definition.normalize ? definition.normalize(value) : value;
|
|
196
|
+
}
|
|
197
|
+
|
|
187
198
|
/** Store a value. An empty string is a VALUE; {@link clearSetting} is the only deletion. */
|
|
188
199
|
export function setSetting(definition: SettingDefinition, value: string): void {
|
|
189
|
-
const next = definition
|
|
200
|
+
const next = normalizeSetting(definition, value);
|
|
190
201
|
const moved = apply({ [definition.key]: next });
|
|
191
202
|
if (moved.length > 0) {
|
|
192
203
|
queue.enqueue({ [definition.key]: next });
|
|
@@ -204,7 +215,7 @@ export function setSetting(definition: SettingDefinition, value: string): void {
|
|
|
204
215
|
export function setSettings(entries: readonly [SettingDefinition, string][]): void {
|
|
205
216
|
const patch: SettingsPatch = {};
|
|
206
217
|
for (const [definition, value] of entries) {
|
|
207
|
-
patch[definition.key] = definition
|
|
218
|
+
patch[definition.key] = normalizeSetting(definition, value);
|
|
208
219
|
}
|
|
209
220
|
const moved = apply(patch);
|
|
210
221
|
if (moved.length > 0) {
|
|
@@ -3,16 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Streams a chat round, executes any tool calls the model makes (via a ToolRegistry backed by
|
|
5
5
|
* `transactDoc()`), feeds the results back as `tool` messages, and re-streams — up to a capped
|
|
6
|
-
* number of rounds (
|
|
6
|
+
* number of rounds (specs/ai.md §3.2 to §3.4).
|
|
7
7
|
*
|
|
8
|
-
* Two §
|
|
9
|
-
* and a panel can only render what the run recorded:
|
|
8
|
+
* Two `ai.md` §3.2 honesty rules live here rather than in the panel, because they are properties of
|
|
9
|
+
* the RUN and a panel can only render what the run recorded:
|
|
10
10
|
*
|
|
11
11
|
* - **A partial success is not a failure.** Running out of rounds called `setError`, which paints
|
|
12
12
|
* the turn red and — in `chat-state.ts` — DELETES the streaming message, so a turn that applied
|
|
13
13
|
* four edits and then hit the cap reported as an error that had also erased its own account of
|
|
14
14
|
* the four edits. The cap is now an ordinary assistant message whenever anything was applied, and
|
|
15
|
-
* an error only when nothing was.
|
|
15
|
+
* an error only when nothing was. **Applied means it wrote**: a call counts when it succeeded with
|
|
16
|
+
* a summary AND the write ledger grew by an `ok` write while it ran, not merely when it returned a
|
|
17
|
+
* summary, because a read returns a summary too, and a turn that only looked around was reported
|
|
18
|
+
* as having changed things.
|
|
19
|
+
* - **A turn that drew nothing says so.** A model that answered with neither text nor a tool call
|
|
20
|
+
* left the author's message sitting there with no reply at all. That turn ends on an error row,
|
|
21
|
+
* with Retry, instead.
|
|
16
22
|
* - **A batch belongs to the tab it edits.** `beginBatch(getTab())` ran once, against whichever tab
|
|
17
23
|
* happened to be active when the loop started. A turn that then moved to a second document closed
|
|
18
24
|
* its batch against the FIRST tab, so the second document's edits got neither a history snapshot
|
|
@@ -23,16 +29,22 @@
|
|
|
23
29
|
|
|
24
30
|
import type { createChatState } from "@jxsuite/ai/chat-state";
|
|
25
31
|
import type { StreamingClient } from "@jxsuite/ai/streaming-client";
|
|
26
|
-
import
|
|
32
|
+
import { createSessionFacts, createToolContext, linkCallSignal } from "@jxsuite/ai/tools";
|
|
33
|
+
import type { Actor, SessionFacts, ToolRegistry } from "@jxsuite/ai/tools";
|
|
27
34
|
|
|
28
35
|
import type { Tab } from "../tabs/tab";
|
|
36
|
+
import type { ImportProgressEvent } from "../types";
|
|
29
37
|
import { batchTab, beginBatch, endBatch } from "../tabs/transact";
|
|
30
38
|
import { ensureProxyProbe, resetModelCache } from "./ai-models";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
39
|
+
import { estimatePromptTokens } from "./context-manager";
|
|
40
|
+
import { fileTurn, openTurnLedger, turnAnchor } from "./ai-writes";
|
|
41
|
+
import { recordImportProgress } from "./import-run";
|
|
33
42
|
|
|
34
43
|
const MAX_ROUNDS = 5;
|
|
35
44
|
|
|
45
|
+
/** What a turn that drew nothing says: the model answered with neither text nor a tool call. */
|
|
46
|
+
export const EMPTY_TURN_TEXT = "The model sent back an empty reply.";
|
|
47
|
+
|
|
36
48
|
/**
|
|
37
49
|
* Hard ceiling on rounds of every kind, so the loop terminates whatever the model does.
|
|
38
50
|
*
|
|
@@ -44,9 +56,6 @@ const MAX_ROUNDS = 5;
|
|
|
44
56
|
*/
|
|
45
57
|
const MAX_TOTAL_ROUNDS = 25;
|
|
46
58
|
|
|
47
|
-
/** Tools that suspend the turn on a human rather than doing work. */
|
|
48
|
-
const INTERACTIVE_TOOLS = new Set(["ask_user"]);
|
|
49
|
-
|
|
50
59
|
interface RunAgentLoopOptions {
|
|
51
60
|
chatState: ReturnType<typeof createChatState>;
|
|
52
61
|
streamingClient: StreamingClient;
|
|
@@ -54,6 +63,8 @@ interface RunAgentLoopOptions {
|
|
|
54
63
|
systemPrompt: string;
|
|
55
64
|
signal?: AbortSignal;
|
|
56
65
|
getTab?: () => Tab | null;
|
|
66
|
+
/** The conversation's facts, which every call's context carries; a fresh set when omitted. */
|
|
67
|
+
session?: SessionFacts;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
/**
|
|
@@ -67,18 +78,24 @@ export async function runAgentLoop({
|
|
|
67
78
|
systemPrompt,
|
|
68
79
|
signal,
|
|
69
80
|
getTab,
|
|
81
|
+
session = createSessionFacts(),
|
|
70
82
|
}: RunAgentLoopOptions): Promise<void> {
|
|
71
83
|
const allErrors: string[] = [];
|
|
72
84
|
const appliedSummaries: string[] = [];
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
/* The ledger is filed under the turn's last DRAWN assistant message (see services/ai-writes), so
|
|
87
|
+
the scan starts at the message this turn answers. */
|
|
88
|
+
const turnUserId = chatState.messages.findLast((m) => m.role === "user")?.id;
|
|
89
|
+
/* What every call's context carries (specs/ai.md §3.7): this turn's ledger, the conversation's
|
|
90
|
+
facts, and who the calls act as. The signal and the call id are the call's own, below. */
|
|
91
|
+
const ledger = openTurnLedger(`turn:${chatState.messages.length}`);
|
|
92
|
+
const actor: Actor = {
|
|
93
|
+
id: `assistant:${session.sessionId ?? "local"}:${ledger.turnId}`,
|
|
94
|
+
kind: "assistant",
|
|
95
|
+
model: chatState.model,
|
|
96
|
+
sessionId: session.sessionId,
|
|
97
|
+
turnId: ledger.turnId,
|
|
98
|
+
};
|
|
82
99
|
|
|
83
100
|
// Batch all tool-call mutations into a single undo step, anchored on the tab being edited.
|
|
84
101
|
if (getTab) {
|
|
@@ -149,6 +166,12 @@ export async function runAgentLoop({
|
|
|
149
166
|
chatState.appendToolCallEnd(event.id);
|
|
150
167
|
break;
|
|
151
168
|
}
|
|
169
|
+
case "usage": {
|
|
170
|
+
/* The provider's own count replaces the four-characters-per-token estimate. The prompt
|
|
171
|
+
it covered is recorded with it, because the next send rebuilds the prompt. */
|
|
172
|
+
chatState.recordUsage(event, { systemTokens: estimatePromptTokens(systemPrompt) });
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
152
175
|
case "done": {
|
|
153
176
|
({ stopReason } = event);
|
|
154
177
|
break;
|
|
@@ -164,8 +187,6 @@ export async function runAgentLoop({
|
|
|
164
187
|
}
|
|
165
188
|
}
|
|
166
189
|
|
|
167
|
-
chatState.finishStream(stopReason);
|
|
168
|
-
|
|
169
190
|
if (streamError) {
|
|
170
191
|
/* A lapsed hosted grant is the one stream error that makes the app's OWN reading wrong.
|
|
171
192
|
The probe settles once at boot, so without this every gate keeps offering an assistant
|
|
@@ -175,36 +196,91 @@ export async function runAgentLoop({
|
|
|
175
196
|
resetModelCache();
|
|
176
197
|
ensureProxyProbe();
|
|
177
198
|
}
|
|
199
|
+
/* `setError` alone, never after `finishStream`: it removes the round's partial message,
|
|
200
|
+
and only while that message is still the streaming one. Finishing first let go of it, so
|
|
201
|
+
the partial (a tool call cut off mid-arguments, as often as not) stayed in the
|
|
202
|
+
transcript and went out on the next send. */
|
|
178
203
|
chatState.setError(streamError);
|
|
179
204
|
return;
|
|
180
205
|
}
|
|
181
206
|
|
|
182
|
-
|
|
207
|
+
/* A turn that has drawn nothing by the end of a round with no calls ends here, as an error
|
|
208
|
+
the author can see and retry, rather than as silence under their message. `setError`
|
|
209
|
+
before `finishStream`, so it removes the empty reply. A stopped round is not empty: the
|
|
210
|
+
author ended it. */
|
|
211
|
+
const stopped = stopReason === "cancelled" || signal?.aborted === true;
|
|
212
|
+
if (toolCalls.size === 0 && !stopped && turnAnchor(chatState.messages, turnUserId) === null) {
|
|
213
|
+
chatState.setError(EMPTY_TURN_TEXT);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
chatState.finishStream(stopReason);
|
|
218
|
+
|
|
219
|
+
/* The calls the model streamed decide whether tools run, not the finish reason the provider
|
|
220
|
+
reported beside them. Some OpenAI-compatible backends (Workers AI among them) end a
|
|
221
|
+
tool-calling turn with `finish_reason: "stop"`, and gating on `tool_calls` dropped those
|
|
222
|
+
calls unrun — the turn ended looking finished, and the next send sealed them as failures.
|
|
223
|
+
A call cut off by `length` still runs: its arguments fail to parse, and the model reads
|
|
224
|
+
that as a tool error it can correct.
|
|
225
|
+
|
|
226
|
+
Cancellation is the exception, and it is not a finish reason at all: it is the author
|
|
227
|
+
pressing Stop. A stopped round runs nothing it streamed, including calls whose arguments
|
|
228
|
+
were already complete, because Stop is the one control that must not be outrun. */
|
|
229
|
+
if (toolCalls.size === 0 || stopReason === "cancelled" || signal?.aborted) {
|
|
183
230
|
return;
|
|
184
231
|
}
|
|
185
232
|
|
|
186
233
|
let didWork = false;
|
|
187
234
|
for (const [id, call] of toolCalls) {
|
|
235
|
+
/* A Stop that lands between calls (while `ask_user` waits, or during a long import) stops
|
|
236
|
+
the calls after it too. They stay unanswered, and the send path seals them. */
|
|
237
|
+
if (signal?.aborted) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
188
240
|
let result;
|
|
189
|
-
//
|
|
190
|
-
|
|
241
|
+
// The ledger's length before the call, so the call can be judged by what it recorded.
|
|
242
|
+
const writesBefore = ledger.writes.length;
|
|
243
|
+
/* The call's own signal: it aborts with the turn, so a tool waiting on a person or a crawl
|
|
244
|
+
learns of a Stop, and it is unlinked once the call settles, so a Stop later in the turn
|
|
245
|
+
reaches nothing that has already finished. */
|
|
246
|
+
const link = signal ? linkCallSignal(signal) : null;
|
|
247
|
+
const ctx = createToolContext({
|
|
248
|
+
actor,
|
|
249
|
+
callId: id,
|
|
250
|
+
ledger,
|
|
251
|
+
progress: (event) => {
|
|
252
|
+
recordImportProgress(id, event as unknown as ImportProgressEvent);
|
|
253
|
+
},
|
|
254
|
+
session,
|
|
255
|
+
...(link ? { signal: link.signal } : {}),
|
|
256
|
+
});
|
|
191
257
|
try {
|
|
192
|
-
|
|
193
|
-
result = await toolRegistry.execute(call.name, args);
|
|
258
|
+
result = await toolRegistry.execute(call.name, parseToolArguments(call.arguments), ctx);
|
|
194
259
|
} catch (error) {
|
|
195
260
|
result = {
|
|
196
261
|
success: false,
|
|
197
262
|
error: `Failed to parse arguments: ${(error as Error).message}`,
|
|
198
263
|
};
|
|
264
|
+
} finally {
|
|
265
|
+
link?.release();
|
|
199
266
|
}
|
|
200
|
-
|
|
267
|
+
/* The transcript was replaced while the call ran: another chat was opened from Chat
|
|
268
|
+
History, which stops the turn. The reply belongs to a request that is no longer there,
|
|
269
|
+
so writing it would land a stray `tool` message in the other conversation, and could
|
|
270
|
+
overwrite a restored record's result where a provider reuses call ids. */
|
|
271
|
+
if (turnUserId !== undefined && !chatState.messages.some((m) => m.id === turnUserId)) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// A call that suspends the turn on a person does not spend its work budget.
|
|
275
|
+
if (toolRegistry.getDefinition(call.name)?.interactive !== true) {
|
|
201
276
|
didWork = true;
|
|
202
277
|
}
|
|
203
278
|
reanchorBatch();
|
|
204
279
|
if (!result.success && result.error) {
|
|
205
280
|
allErrors.push(result.error);
|
|
206
281
|
}
|
|
207
|
-
|
|
282
|
+
const wrote = ledger.writes.slice(writesBefore).some((write) => write.ok);
|
|
283
|
+
if (result.success && result.summary && wrote) {
|
|
208
284
|
appliedSummaries.push(result.summary);
|
|
209
285
|
}
|
|
210
286
|
chatState.appendToolResult(id, result);
|
|
@@ -214,6 +290,12 @@ export async function runAgentLoop({
|
|
|
214
290
|
if (didWork) {
|
|
215
291
|
workRounds += 1;
|
|
216
292
|
}
|
|
293
|
+
/* A Stop that landed during the round's last call. Nothing is left to stop in this round, so
|
|
294
|
+
without this the loop opened the next one: a placeholder pushed after the author stopped,
|
|
295
|
+
and a request streamed on the aborted signal. */
|
|
296
|
+
if (signal?.aborted) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
217
299
|
if (workRounds < MAX_ROUNDS && round < MAX_TOTAL_ROUNDS) {
|
|
218
300
|
chatState.beginAssistantTurn();
|
|
219
301
|
}
|
|
@@ -222,7 +304,7 @@ export async function runAgentLoop({
|
|
|
222
304
|
/*
|
|
223
305
|
* The round cap. Surface the actual errors so the user knows what went wrong, not just a
|
|
224
306
|
* generic "I couldn't do it" — and, when anything was applied, say it as the ASSISTANT rather
|
|
225
|
-
* than as a failure (§
|
|
307
|
+
* than as a failure (`ai.md` §3.2). A partial success is not a failure.
|
|
226
308
|
*/
|
|
227
309
|
const uniqueErrors = [...new Set(allErrors)];
|
|
228
310
|
const applied =
|
|
@@ -244,8 +326,31 @@ export async function runAgentLoop({
|
|
|
244
326
|
}
|
|
245
327
|
chatState.setError(tail);
|
|
246
328
|
} finally {
|
|
247
|
-
endTurnSignal();
|
|
248
329
|
endBatch();
|
|
249
|
-
|
|
330
|
+
fileTurn(turnAnchor(chatState.messages, turnUserId) ?? "", ledger.writes);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* A call's arguments as the object a tool takes.
|
|
336
|
+
*
|
|
337
|
+
* An empty string is no arguments. JSON that parses to something other than an object (`null`, an
|
|
338
|
+
* array, a number) is refused here with its own sentence, rather than reaching the registry, where
|
|
339
|
+
* a validator reading a property of `null` threw a message about the validator. The prefix is the
|
|
340
|
+
* one a JSON syntax error already carries, so the model reads both as one kind of mistake.
|
|
341
|
+
*
|
|
342
|
+
* @param {string} text - The call's accumulated argument text
|
|
343
|
+
* @returns {object}
|
|
344
|
+
* @throws {SyntaxError | TypeError} Caught by the loop and returned to the model as a tool error
|
|
345
|
+
*/
|
|
346
|
+
function parseToolArguments(text: string): object {
|
|
347
|
+
if (!text) {
|
|
348
|
+
return {};
|
|
349
|
+
}
|
|
350
|
+
const parsed = JSON.parse(text) as unknown;
|
|
351
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
352
|
+
const type = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
353
|
+
throw new TypeError(`arguments must be a JSON object, got ${type}`);
|
|
250
354
|
}
|
|
355
|
+
return parsed;
|
|
251
356
|
}
|