@theoremjs/studio 0.0.0-stage → 0.3.2
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/LICENSE +21 -0
- package/README.md +295 -2
- package/agreement.d.ts +14 -0
- package/agreement.js +80 -0
- package/asks.d.ts +47 -0
- package/asks.js +54 -0
- package/browser-connection.d.ts +49 -0
- package/browser-connection.js +215 -0
- package/browser-engine.d.ts +11 -0
- package/browser-engine.js +11 -0
- package/browser-execution.d.ts +6 -0
- package/browser-execution.js +18 -0
- package/browser-live.d.ts +4 -0
- package/browser-live.js +132 -0
- package/browser-transport.d.ts +33 -0
- package/browser-transport.js +229 -0
- package/browser.d.ts +4 -0
- package/browser.js +4 -0
- package/canonical.d.ts +10 -0
- package/canonical.js +25 -0
- package/compile-workspace.d.ts +28 -0
- package/compile-workspace.js +295 -0
- package/compile.d.ts +65 -0
- package/compile.js +1605 -0
- package/concierge-demo.d.ts +17 -0
- package/concierge-demo.js +1230 -0
- package/conversation-key.d.ts +5 -0
- package/conversation-key.js +10 -0
- package/decision-example-images.d.ts +30 -0
- package/decision-example-images.js +30 -0
- package/deno.json +3 -0
- package/draft.d.ts +407 -0
- package/draft.js +505 -0
- package/editors.d.ts +29 -0
- package/editors.js +51 -0
- package/example-embedding-files.d.ts +11 -0
- package/example-embedding-files.js +20 -0
- package/example.d.ts +36 -0
- package/example.js +357 -0
- package/guardrail-probe.d.ts +100 -0
- package/guardrail-probe.js +722 -0
- package/held-credentials.d.ts +28 -0
- package/held-credentials.js +87 -0
- package/installed.mjs +40 -0
- package/lent-keys.d.ts +122 -0
- package/lent-keys.js +47 -0
- package/line-diff.d.ts +15 -0
- package/line-diff.js +41 -0
- package/live-connection.d.ts +22 -0
- package/live-connection.js +27 -0
- package/live-session-bridge.d.ts +15 -0
- package/live-session-bridge.js +202 -0
- package/map-line.d.ts +19 -0
- package/map-line.js +42 -0
- package/map.d.ts +154 -0
- package/map.js +525 -0
- package/media-accept.d.ts +19 -0
- package/media-accept.js +65 -0
- package/mod.d.ts +39 -0
- package/mod.js +33 -0
- package/origins.d.ts +52 -0
- package/origins.js +165 -0
- package/package.json +85 -4
- package/page/assets/Banner-BISz3nXA.js +2 -0
- package/page/assets/BottomSheet-BwfBDtje.js +1 -0
- package/page/assets/Card-Lp-0P5eL.js +1 -0
- package/page/assets/CardWall-CBRLdDZW.js +1 -0
- package/page/assets/Center-nHS1JSux.js +1 -0
- package/page/assets/CheckboxInput-Cj5pk-KI.js +2 -0
- package/page/assets/CodeBlock-C7XJ4ioE.js +51 -0
- package/page/assets/CollapsibleGroup-N7_SOjbm.js +2 -0
- package/page/assets/ContextMenu-CBu9H-9H.js +2 -0
- package/page/assets/DateInput-BGrQ5tPC.js +1 -0
- package/page/assets/Dialog-DELWQo-v.js +1 -0
- package/page/assets/EmptyState-C8W4JKBZ.js +1 -0
- package/page/assets/HoverCard-D1Y1ltWQ.js +1 -0
- package/page/assets/Icon-JrysT8aU.js +3 -0
- package/page/assets/IconAlertTriangle-BqAEqqdU.js +1 -0
- package/page/assets/IconButton-DlOGzJIX.js +1 -0
- package/page/assets/IconEye-BB5aY-wQ.js +1 -0
- package/page/assets/IconFileText-6zOFKKrF.js +1 -0
- package/page/assets/IconSearch-BTx66a40.js +1 -0
- package/page/assets/IconShield-DDOh7Gr5.js +1 -0
- package/page/assets/ItemSwipeLayer-Ckn4X6h9.js +1 -0
- package/page/assets/PaneState-BvA2tLQg.js +1 -0
- package/page/assets/PaneState-eUzUkNE8.css +1 -0
- package/page/assets/ScrollableArea-Ckugdj1N.js +1 -0
- package/page/assets/Section-DJ97F7QE.js +1 -0
- package/page/assets/SegmentedControlItem-h-aaXzyS.js +1 -0
- package/page/assets/SidePanel-BL32abk8.js +1 -0
- package/page/assets/StackItem-CQviqAfM.js +1 -0
- package/page/assets/Text-bum6zWWx.js +2 -0
- package/page/assets/TimestampHoverCard-DQIG0RZF.js +1 -0
- package/page/assets/ToolResultCharts-CgshSv9Q.js +1 -0
- package/page/assets/Tooltip-BH9xiRMl.js +1 -0
- package/page/assets/TraceInspector-_yZiz05W.js +1 -0
- package/page/assets/TraceOverview-BPO-HIYK.js +57 -0
- package/page/assets/VStack-CkVMcNiK.js +1 -0
- package/page/assets/_virtual_studio-type-sources-ByxqQaOG.js +12936 -0
- package/page/assets/arrive-DvbqpeRS.js +1 -0
- package/page/assets/browser-engine-CYq9DQAM.js +9 -0
- package/page/assets/browser-transport-D-2jDYe7.js +47 -0
- package/page/assets/codicon-DF1abBS2.ttf +0 -0
- package/page/assets/compile-egress-BSxOdX4K.js +23 -0
- package/page/assets/container.stylex-BmOi-8DQ.js +1 -0
- package/page/assets/createReactComponent-CZwsv6DD.js +1 -0
- package/page/assets/decision-example-images-CZGr7YIT.js +1 -0
- package/page/assets/devWarning-DxuplmRl.js +1 -0
- package/page/assets/editor-DjpsE8to.css +1 -0
- package/page/assets/editor.api-Cd3ViPQa.js +908 -0
- package/page/assets/editor.worker-6GY8RhDc.js +1 -0
- package/page/assets/editor.worker-BmdWdrHZ.js +33 -0
- package/page/assets/editors-BJX_S59A.js +1 -0
- package/page/assets/egress-automata-B5ZGE1mT.js +1 -0
- package/page/assets/embedding-space-view-CfQBPDxD.js +140 -0
- package/page/assets/encode-files-Bc3HJyAh.js +1 -0
- package/page/assets/export-agent-C3yyzXH8.js +43 -0
- package/page/assets/figtree-latin-400-normal-BD4aNku5.woff +0 -0
- package/page/assets/figtree-latin-400-normal-g7Dtegnw.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-BWnGEVsr.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-B_yy1phn.woff +0 -0
- package/page/assets/figtree-latin-600-normal-BM_oTo4n.woff +0 -0
- package/page/assets/figtree-latin-600-normal-Cv_xCTDl.woff2 +0 -0
- package/page/assets/figtree-latin-700-normal-BCCt2T1q.woff +0 -0
- package/page/assets/figtree-latin-700-normal-th6qEP7c.woff2 +0 -0
- package/page/assets/focusOutline.stylex-BT0kfa4t.js +1 -0
- package/page/assets/focusableSelector-DXZeMU85.js +1 -0
- package/page/assets/groupStyles-C7Ter7zC.js +1 -0
- package/page/assets/guardrail-tester-Gut5xK4a.js +9 -0
- package/page/assets/host-patterns-C9RMP16Q.js +1 -0
- package/page/assets/index-CvvmFKgS.js +105 -0
- package/page/assets/index-DOaHlqBo.css +1 -0
- package/page/assets/isRtlElement-BG33-k_H.js +1 -0
- package/page/assets/labels-provider-4TIWDvsE.js +3 -0
- package/page/assets/lent-keys-C32ems-I.js +1 -0
- package/page/assets/live-ingress-lDUGPwth.js +1 -0
- package/page/assets/live-messages-CnkL4GWi.js +1 -0
- package/page/assets/mergeProps-ptS-gIMz.js +1 -0
- package/page/assets/padding.stylex-B76907Zf.js +1 -0
- package/page/assets/preload-helper-DpV6A1kQ.js +1 -0
- package/page/assets/reader-BOCCYaRp.js +7 -0
- package/page/assets/register-CDgivxvX.js +2 -0
- package/page/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/page/assets/schemas-BQ9Jydzb.js +56 -0
- package/page/assets/shaped-data-BcarWqd5.js +16 -0
- package/page/assets/sharedResizeObserver-4U9Xq5Oq.js +1 -0
- package/page/assets/stub-DhdcU_Ew.js +471 -0
- package/page/assets/studio-code-pane-C_SAbcOa.js +8 -0
- package/page/assets/studio-console-DZwlEsvr.js +3 -0
- package/page/assets/studio-decision-B0FnCAXA.js +1 -0
- package/page/assets/studio-embedding-CmA51HVo.js +2 -0
- package/page/assets/studio-host-console-By7hqu2l.js +2 -0
- package/page/assets/studio-live-BrcVQcyV.js +1 -0
- package/page/assets/studio-map-B_xSia-z.js +1 -0
- package/page/assets/studio-map-Bb06Tw1F.css +1 -0
- package/page/assets/studio-monaco-CbKf90i6.css +1 -0
- package/page/assets/studio-monaco-xfyfezQS.js +34 -0
- package/page/assets/studio-ts.worker-B-EXG4Sv.js +67741 -0
- package/page/assets/studio-ts.worker-BXtW3z-O.js +1 -0
- package/page/assets/studio-type-checker-BOvxudki.js +23 -0
- package/page/assets/themeProps-3IqahpCr.js +1 -0
- package/page/assets/tokens.stylex-DkHf0P9C.js +1 -0
- package/page/assets/tool-schema-idaOe2nI.js +18 -0
- package/page/assets/trace-patterns-DnYJa0nI.js +1 -0
- package/page/assets/trace-record-C3zNksA_.js +1 -0
- package/page/assets/trace-schema-9CdEyG6Q.js +1 -0
- package/page/assets/tree-DqyAMsbI.js +3 -0
- package/page/assets/turn-client-Du3Um3Mg.js +2 -0
- package/page/assets/types-BhfXQCLx.js +1 -0
- package/page/assets/typescript-20GgZ8PJ.js +1 -0
- package/page/assets/use-one-call-Hcs8YBp1.js +1 -0
- package/page/assets/useAdaptivePresentation-BzmPCddb.js +1 -0
- package/page/assets/useClipboard-BgGRxRMJ.js +1 -0
- package/page/assets/useContainerReveal-BFy8N_QU.js +1 -0
- package/page/assets/useDevWarning-Z9SoixOP.js +1 -0
- package/page/assets/useLayerDismissal-B_G7IWpC.js +1 -0
- package/page/assets/useMediaQuery-BCT0NUeZ.js +1 -0
- package/page/assets/useScrollLock-Dyf6PHJt.js +1 -0
- package/page/assets/useScrollableArea-0NzdiBCq.js +1 -0
- package/page/assets/useTooltip-CJM_xhLB.js +1 -0
- package/page/assets/workspace-DZALqEDX.js +1 -0
- package/page/assets/zip-DTcEfps6.js +1 -0
- package/page/index.html +90 -0
- package/page.mjs +49 -0
- package/people.d.ts +87 -0
- package/people.js +110 -0
- package/policy.d.ts +43 -0
- package/policy.js +102 -0
- package/ports.d.mts +32 -0
- package/ports.mjs +85 -0
- package/probe-battery.d.ts +13 -0
- package/probe-battery.js +404 -0
- package/providers.d.ts +149 -0
- package/providers.js +306 -0
- package/read-source.d.ts +48 -0
- package/read-source.js +1301 -0
- package/read-steps.d.ts +23 -0
- package/read-steps.js +2 -0
- package/reader.d.ts +11 -0
- package/reader.js +11 -0
- package/registrations.d.ts +83 -0
- package/registrations.js +1 -0
- package/requirements.d.ts +22 -0
- package/requirements.js +38 -0
- package/run-payload-core.d.ts +28 -0
- package/run-payload-core.js +123 -0
- package/run-payload.d.ts +32 -0
- package/run-payload.js +26 -0
- package/runtime-scope.d.ts +84 -0
- package/runtime-scope.js +308 -0
- package/runtime.d.ts +84 -0
- package/runtime.js +387 -0
- package/section-notes.d.ts +33 -0
- package/section-notes.js +38 -0
- package/serve-page.mjs +96 -0
- package/server/draft-decide.d.ts +72 -0
- package/server/draft-decide.js +111 -0
- package/server/draft-embedding.d.ts +3 -0
- package/server/draft-embedding.js +35 -0
- package/server/draft-handler.d.ts +82 -0
- package/server/draft-handler.js +213 -0
- package/server/draft-probe.d.ts +14 -0
- package/server/draft-probe.js +61 -0
- package/server/draft-replies.d.ts +22 -0
- package/server/draft-replies.js +46 -0
- package/server/edited-session.d.ts +28 -0
- package/server/edited-session.js +83 -0
- package/server/edits.d.ts +38 -0
- package/server/edits.js +195 -0
- package/server/handler.d.ts +133 -0
- package/server/handler.js +598 -0
- package/server/lent-draft.d.ts +19 -0
- package/server/lent-draft.js +23 -0
- package/server/live.d.ts +15 -0
- package/server/live.js +96 -0
- package/server/open-editor.d.ts +63 -0
- package/server/open-editor.js +115 -0
- package/server/origins.d.ts +18 -0
- package/server/origins.js +330 -0
- package/server/project-source.d.ts +208 -0
- package/server/project-source.js +1042 -0
- package/server/project.d.ts +22 -0
- package/server/project.js +56 -0
- package/server/run-traces.d.ts +20 -0
- package/server/run-traces.js +96 -0
- package/server/save-new.d.ts +29 -0
- package/server/save-new.js +514 -0
- package/server/save-plan.d.ts +46 -0
- package/server/save-plan.js +1134 -0
- package/server/save-remove.d.ts +21 -0
- package/server/save-remove.js +528 -0
- package/server/save-session.d.ts +96 -0
- package/server/save-session.js +418 -0
- package/server/save-wire.d.ts +235 -0
- package/server/save-wire.js +8 -0
- package/server/save.d.ts +52 -0
- package/server/save.js +209 -0
- package/server/serve.d.ts +23 -0
- package/server/serve.js +413 -0
- package/server/test-connection.d.ts +17 -0
- package/server/test-connection.js +362 -0
- package/server/test-key.d.ts +2 -0
- package/server/test-key.js +83 -0
- package/server/watch.d.ts +28 -0
- package/server/watch.js +93 -0
- package/server/zod-edit.d.ts +74 -0
- package/server/zod-edit.js +558 -0
- package/server.d.ts +8 -0
- package/server.js +8 -0
- package/setups.mjs +170 -0
- package/shared-carry.d.ts +114 -0
- package/shared-carry.js +523 -0
- package/shared-settings.d.ts +34 -0
- package/shared-settings.js +70 -0
- package/source.d.ts +75 -0
- package/source.js +414 -0
- package/start.mjs +169 -0
- package/stream-lines.d.ts +9 -0
- package/stream-lines.js +27 -0
- package/stub.d.ts +6 -0
- package/stub.js +33 -0
- package/surface.d.ts +53 -0
- package/surface.js +631 -0
- package/system-markup.d.ts +20 -0
- package/system-markup.js +51 -0
- package/tool-schema.d.ts +23 -0
- package/tool-schema.js +69 -0
- package/tools.d.ts +16 -0
- package/tools.js +91 -0
- package/traces.d.ts +24 -0
- package/traces.js +29 -0
- package/transport.d.ts +57 -0
- package/transport.js +187 -0
- package/tree.d.ts +32 -0
- package/tree.js +83 -0
- package/types.d.ts +62 -0
- package/types.js +6 -0
- package/ui/acting-as-button.d.ts +2 -0
- package/ui/acting-as-button.js +16 -0
- package/ui/add-provider.d.ts +13 -0
- package/ui/add-provider.js +20 -0
- package/ui/boot-mark.css +53 -0
- package/ui/boot-mark.d.ts +19 -0
- package/ui/boot-mark.js +128 -0
- package/ui/boot-paint.d.ts +8 -0
- package/ui/boot-paint.js +8 -0
- package/ui/brand-icons.d.ts +57 -0
- package/ui/brand-icons.js +148 -0
- package/ui/built/theme.css +830 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem-studio.d.ts +13 -0
- package/ui/built/theorem-studio.js +705 -0
- package/ui/built/theorem-studio.variants.d.ts +28 -0
- package/ui/code/monaco-contributions.d.ts +45 -0
- package/ui/code/studio-code.d.ts +7 -0
- package/ui/code/studio-code.js +253 -0
- package/ui/code/studio-monaco.d.ts +44 -0
- package/ui/code/studio-monaco.js +48 -0
- package/ui/code/studio-ts.worker.d.ts +1 -0
- package/ui/code/studio-ts.worker.js +16 -0
- package/ui/code/studio-type-checker.d.ts +12 -0
- package/ui/code/studio-type-checker.js +308 -0
- package/ui/code/studio-type-sources.d.ts +26 -0
- package/ui/confirm-write.d.ts +12 -0
- package/ui/confirm-write.js +45 -0
- package/ui/diff-block.d.ts +21 -0
- package/ui/diff-block.js +17 -0
- package/ui/figtree.css +59 -0
- package/ui/file-place.d.ts +4 -0
- package/ui/file-place.js +26 -0
- package/ui/guardrail-tester.d.ts +18 -0
- package/ui/guardrail-tester.js +248 -0
- package/ui/inspector-context.d.ts +119 -0
- package/ui/inspector-context.js +84 -0
- package/ui/inspector.d.ts +247 -0
- package/ui/inspector.js +284 -0
- package/ui/lib/acting-as.d.ts +25 -0
- package/ui/lib/acting-as.js +90 -0
- package/ui/lib/boot-sequence.d.ts +32 -0
- package/ui/lib/boot-sequence.js +57 -0
- package/ui/lib/export-agent.d.ts +11 -0
- package/ui/lib/export-agent.js +335 -0
- package/ui/lib/facet-icons.d.ts +20 -0
- package/ui/lib/facet-icons.js +21 -0
- package/ui/lib/panel-motion.d.ts +8 -0
- package/ui/lib/panel-motion.js +74 -0
- package/ui/lib/project-address.d.ts +14 -0
- package/ui/lib/project-address.js +24 -0
- package/ui/lib/project-starting.d.ts +13 -0
- package/ui/lib/project-starting.js +30 -0
- package/ui/lib/project-traces.d.ts +17 -0
- package/ui/lib/project-traces.js +54 -0
- package/ui/lib/read-map.d.ts +30 -0
- package/ui/lib/read-map.js +173 -0
- package/ui/lib/read-progress.d.ts +14 -0
- package/ui/lib/read-progress.js +12 -0
- package/ui/lib/refusal-lines.d.ts +3 -0
- package/ui/lib/refusal-lines.js +8 -0
- package/ui/lib/studio-activity.d.ts +5 -0
- package/ui/lib/studio-activity.js +17 -0
- package/ui/lib/studio-console.d.ts +27 -0
- package/ui/lib/studio-console.js +9 -0
- package/ui/lib/studio-conversation.d.ts +20 -0
- package/ui/lib/studio-conversation.js +130 -0
- package/ui/lib/studio-examples.d.ts +31 -0
- package/ui/lib/studio-examples.js +25 -0
- package/ui/lib/studio-labels.d.ts +6 -0
- package/ui/lib/studio-labels.js +13 -0
- package/ui/lib/studio-map-kept.d.ts +17 -0
- package/ui/lib/studio-map-kept.js +48 -0
- package/ui/lib/studio-open.d.ts +22 -0
- package/ui/lib/studio-open.js +54 -0
- package/ui/lib/studio-page.d.ts +33 -0
- package/ui/lib/studio-page.js +121 -0
- package/ui/lib/studio-prefs.d.ts +26 -0
- package/ui/lib/studio-prefs.js +87 -0
- package/ui/lib/studio-project.d.ts +106 -0
- package/ui/lib/studio-project.js +177 -0
- package/ui/lib/studio-restore.d.ts +49 -0
- package/ui/lib/studio-restore.js +251 -0
- package/ui/lib/studio-run-open.d.ts +15 -0
- package/ui/lib/studio-run-open.js +16 -0
- package/ui/lib/studio-session.d.ts +34 -0
- package/ui/lib/studio-session.js +17 -0
- package/ui/lib/studio-settings.d.ts +15 -0
- package/ui/lib/studio-settings.js +48 -0
- package/ui/lib/studio-store.d.ts +64 -0
- package/ui/lib/studio-store.js +391 -0
- package/ui/lib/studio-watch.d.ts +12 -0
- package/ui/lib/studio-watch.js +27 -0
- package/ui/lib/tab-fills.d.ts +6 -0
- package/ui/lib/tab-fills.js +14 -0
- package/ui/lib/tool-credentials.d.ts +3 -0
- package/ui/lib/tool-credentials.js +23 -0
- package/ui/lib/tool-probe.d.ts +19 -0
- package/ui/lib/tool-probe.js +104 -0
- package/ui/lib/type-icons.d.ts +35 -0
- package/ui/lib/type-icons.js +38 -0
- package/ui/lib/when-idle.d.ts +5 -0
- package/ui/lib/when-idle.js +14 -0
- package/ui/lib/zip.d.ts +5 -0
- package/ui/lib/zip.js +8 -0
- package/ui/map-token.css +11 -0
- package/ui/map-token.d.ts +17 -0
- package/ui/map-token.js +29 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/motion.css +113 -0
- package/ui/pan.d.ts +32 -0
- package/ui/pan.js +90 -0
- package/ui/profile-editor.d.ts +33 -0
- package/ui/profile-editor.js +2638 -0
- package/ui/provider-editor.d.ts +15 -0
- package/ui/provider-editor.js +92 -0
- package/ui/read-map.css +48 -0
- package/ui/read-map.d.ts +7 -0
- package/ui/read-map.js +163 -0
- package/ui/refusal-detail.d.ts +4 -0
- package/ui/refusal-detail.js +21 -0
- package/ui/shared-change.d.ts +9 -0
- package/ui/shared-change.js +28 -0
- package/ui/shell-motion.css +121 -0
- package/ui/shell-motion.d.ts +73 -0
- package/ui/shell-motion.js +194 -0
- package/ui/studio-code-pane.d.ts +19 -0
- package/ui/studio-code-pane.js +44 -0
- package/ui/studio-compare.d.ts +80 -0
- package/ui/studio-compare.js +326 -0
- package/ui/studio-conflicts.d.ts +17 -0
- package/ui/studio-conflicts.js +87 -0
- package/ui/studio-connection.d.ts +64 -0
- package/ui/studio-connection.js +286 -0
- package/ui/studio-decision.d.ts +6 -0
- package/ui/studio-decision.js +49 -0
- package/ui/studio-embedding.d.ts +6 -0
- package/ui/studio-embedding.js +30 -0
- package/ui/studio-host-console.d.ts +6 -0
- package/ui/studio-host-console.js +17 -0
- package/ui/studio-host.d.ts +73 -0
- package/ui/studio-host.js +12 -0
- package/ui/studio-live.d.ts +17 -0
- package/ui/studio-live.js +26 -0
- package/ui/studio-map.css +273 -0
- package/ui/studio-map.d.ts +17 -0
- package/ui/studio-map.js +397 -0
- package/ui/studio-nav.d.ts +24 -0
- package/ui/studio-nav.js +17 -0
- package/ui/studio-no-project.d.ts +5 -0
- package/ui/studio-no-project.js +9 -0
- package/ui/studio-run.css +76 -0
- package/ui/studio-run.d.ts +14 -0
- package/ui/studio-run.js +119 -0
- package/ui/studio-runner.d.ts +45 -0
- package/ui/studio-runner.js +187 -0
- package/ui/studio-save.d.ts +49 -0
- package/ui/studio-save.js +307 -0
- package/ui/studio-screen.d.ts +61 -0
- package/ui/studio-screen.js +2078 -0
- package/ui/studio-settings.d.ts +31 -0
- package/ui/studio-settings.js +288 -0
- package/ui/studio-shell.css +27 -0
- package/ui/studio-shell.d.ts +43 -0
- package/ui/studio-shell.js +87 -0
- package/ui/studio-theme.d.ts +7 -0
- package/ui/studio-theme.js +94 -0
- package/ui/studio.css +427 -0
- package/ui/theorem-mark.css +84 -0
- package/ui/theorem-mark.d.ts +22 -0
- package/ui/theorem-mark.js +21 -0
- package/vite.d.mts +30 -0
- package/vite.mjs +277 -0
- package/workspace.d.ts +230 -0
- package/workspace.js +1054 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends one text across one guardrail boundary of a draft and reports what its
|
|
3
|
+
* guardrails did. The turn is the kernel's own on a scripted model, so every
|
|
4
|
+
* check is the one a real turn runs, on the draft's guardrails as written, and
|
|
5
|
+
* no model or host is called. A probe keeps what each hit matched: its text
|
|
6
|
+
* is the builder's own sample, and its trace goes nowhere but its result.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { TheoremError, } from '@theoremjs/agents';
|
|
11
|
+
import { actionAt, DETECTORS, resolveDetect, sanitizeTurnRequestWithEvents, TOOL_RULES, } from '@theoremjs/agents/guardrails';
|
|
12
|
+
import { keySlotsOf } from '@theoremjs/agents/kernel';
|
|
13
|
+
import { firstServed } from "./policy.js";
|
|
14
|
+
import { agentCallHook, runtimeProvider, writtenScope, } from "./runtime-scope.js";
|
|
15
|
+
/** Where a probe's text enters the turn. */
|
|
16
|
+
export const PROBE_BOUNDARIES = [
|
|
17
|
+
'user',
|
|
18
|
+
'history',
|
|
19
|
+
'system',
|
|
20
|
+
'tool_result_local',
|
|
21
|
+
'tool_result_remote',
|
|
22
|
+
'tool_arguments',
|
|
23
|
+
'reply',
|
|
24
|
+
'thought',
|
|
25
|
+
];
|
|
26
|
+
/** The kernel boundary a probe's text crosses. */
|
|
27
|
+
const PROBE_READS = {
|
|
28
|
+
user: 'user',
|
|
29
|
+
history: 'history',
|
|
30
|
+
system: 'system',
|
|
31
|
+
tool_result_local: 'tool_output_function',
|
|
32
|
+
tool_result_remote: 'tool_output_agent',
|
|
33
|
+
tool_arguments: 'tool_arguments_function',
|
|
34
|
+
reply: 'reply',
|
|
35
|
+
thought: 'thought',
|
|
36
|
+
};
|
|
37
|
+
/** Each boundary: its name, what crosses it, when the kernel reads it and what it does there. */
|
|
38
|
+
export const PROBE_BOUNDARY_NOTES = {
|
|
39
|
+
user: {
|
|
40
|
+
label: 'User message',
|
|
41
|
+
note: 'What a person types into the chat.',
|
|
42
|
+
when: 'Before the first model call of the turn.',
|
|
43
|
+
checks: 'Injection phrasing and sensitive data are replaced with a placeholder (detect). The model reads the rest, fenced as user data.',
|
|
44
|
+
},
|
|
45
|
+
history: {
|
|
46
|
+
label: 'Earlier message',
|
|
47
|
+
note: 'A user message the host replays from an earlier turn.',
|
|
48
|
+
when: 'Before the first model call, on every turn that replays it.',
|
|
49
|
+
checks: 'Read as a new user message is: injection phrasing and sensitive data are replaced.',
|
|
50
|
+
},
|
|
51
|
+
system: {
|
|
52
|
+
label: 'Host system text',
|
|
53
|
+
note: 'Text the host adds to the system prompt for one turn, such as retrieved documents.',
|
|
54
|
+
when: 'Before the first model call of the turn.',
|
|
55
|
+
checks: 'Read as assembled, not trusted: injection phrasing and sensitive data are replaced. The profile’s own system prompt is trusted and not read.',
|
|
56
|
+
},
|
|
57
|
+
tool_result_local: {
|
|
58
|
+
label: 'Local tool result',
|
|
59
|
+
note: 'What one of the host’s own functions returns.',
|
|
60
|
+
when: 'When the tool returns, before the model reads the result.',
|
|
61
|
+
checks: 'Injection phrasing and sensitive data are replaced in the result the model reads.',
|
|
62
|
+
},
|
|
63
|
+
tool_result_remote: {
|
|
64
|
+
label: 'Remote tool result',
|
|
65
|
+
note: 'What a tool returns from outside the host: a web page, an MCP server, another agent. It runs here as another agent’s reply, and the model then makes a destructive call.',
|
|
66
|
+
when: 'When the tool returns, and again at each call the model makes after it.',
|
|
67
|
+
checks: 'Replaced as a local result is, fenced as tool data, and read for instructions to the agent (a claim of authority, an order, a callable tool’s name), which tool_instructions reports by default. Every later call is reported, and refused where taint.afterRemoteRead says.',
|
|
68
|
+
},
|
|
69
|
+
tool_arguments: {
|
|
70
|
+
label: 'Tool arguments',
|
|
71
|
+
note: 'What the model sends to a tool.',
|
|
72
|
+
when: 'After the model asks for the call, before the tool runs.',
|
|
73
|
+
checks: 'A credential in the arguments is reported. The call still runs.',
|
|
74
|
+
},
|
|
75
|
+
reply: {
|
|
76
|
+
label: 'Model reply',
|
|
77
|
+
note: 'What the model says to the user, streamed.',
|
|
78
|
+
when: 'As it streams: text is held only while it could still be the start of a match.',
|
|
79
|
+
checks: 'Each detector takes the action detect sets for a reply: sensitive data, injection phrasing, the canary, a private prompt echo, the user-data fence, and an image or a link to a URL the model was not given.',
|
|
80
|
+
},
|
|
81
|
+
thought: {
|
|
82
|
+
label: 'Model thought',
|
|
83
|
+
note: 'The model’s reasoning, where the host shows it.',
|
|
84
|
+
when: 'As it streams, where the profile streams thoughts.',
|
|
85
|
+
checks: 'Never stops the turn. The canary, a private prompt echo, the user-data fence, and an image or a link to a URL the model was not given get the action detect sets for a thought. Sensitive data and injection phrasing are not read.',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
/** The longest text a probe carries. */
|
|
89
|
+
export const PROBE_TEXT_LIMIT = 8000;
|
|
90
|
+
/**
|
|
91
|
+
* What the guardrails did with a probed text, mildest first. `flagged` is a
|
|
92
|
+
* report that changed nothing; a call made after a remote read is reported
|
|
93
|
+
* whatever was read, so that report is `taint` and no status.
|
|
94
|
+
*/
|
|
95
|
+
export const PROBE_STATUSES = ['passed', 'flagged', 'redacted', 'blocked'];
|
|
96
|
+
const LOCAL_TOOL = 'guardrail_probe_read';
|
|
97
|
+
const REMOTE_TOOL = 'guardrail_probe_fetch';
|
|
98
|
+
const SEND_TOOL = 'guardrail_probe_send';
|
|
99
|
+
const ACT_TOOL = 'guardrail_probe_delete';
|
|
100
|
+
const REMOTE_AGENT = 'guardrail-probe.remote';
|
|
101
|
+
/** The reply's deltas, in characters: short enough that a pattern spans several. */
|
|
102
|
+
const REPLY_CHUNK = 7;
|
|
103
|
+
const TEXT_SCHEMA = {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: { text: { type: 'string' } },
|
|
106
|
+
};
|
|
107
|
+
function probeTool(name, access, stub) {
|
|
108
|
+
return {
|
|
109
|
+
type: 'function',
|
|
110
|
+
name,
|
|
111
|
+
description: 'A guardrail probe.',
|
|
112
|
+
category: 'probe',
|
|
113
|
+
access,
|
|
114
|
+
permission: 'auto',
|
|
115
|
+
loadTier: 'T0',
|
|
116
|
+
paths: ['*'],
|
|
117
|
+
inputSchema: TEXT_SCHEMA,
|
|
118
|
+
outputSchema: TEXT_SCHEMA,
|
|
119
|
+
...(stub ? { stubResponse: stub } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* An agent whose reply is the probe's text. It has a tool of its own, so the
|
|
124
|
+
* kernel reads its reply as remote content, as it reads an HTTP or MCP result.
|
|
125
|
+
*/
|
|
126
|
+
function remoteAgent(profile) {
|
|
127
|
+
// why: A host profile runs no model, so its probe agent gets a binding of its own. The scripted
|
|
128
|
+
// provider answers for it, whatever it names.
|
|
129
|
+
const base = profile.type === 'host'
|
|
130
|
+
? {
|
|
131
|
+
type: 'text',
|
|
132
|
+
models: { probe: { ...firstServed('gemini', 'text'), apiId: 'probe' } },
|
|
133
|
+
defaultModel: 'probe',
|
|
134
|
+
inputs: { text: true },
|
|
135
|
+
}
|
|
136
|
+
: unobserved(profile);
|
|
137
|
+
const { outputs: _outputs, turnBehaviour: _turnBehaviour, ...rest } = base;
|
|
138
|
+
return {
|
|
139
|
+
profile: {
|
|
140
|
+
...rest,
|
|
141
|
+
id: REMOTE_AGENT,
|
|
142
|
+
identity: { handle: 'probe', system: '' },
|
|
143
|
+
tools: { allow: [LOCAL_TOOL] },
|
|
144
|
+
guardrails: { detect: 'ignore' },
|
|
145
|
+
},
|
|
146
|
+
customTools: [probeTool(LOCAL_TOOL, 'read-only')],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** A probe's trace goes to its result alone: its scope has none of the draft's destinations. */
|
|
150
|
+
function unobserved(profile) {
|
|
151
|
+
const { observability: _observability, ...rest } = profile;
|
|
152
|
+
return rest;
|
|
153
|
+
}
|
|
154
|
+
/** `spec` with every detector but `key` set to ignore. */
|
|
155
|
+
function detectOnly(spec, key) {
|
|
156
|
+
// why: One action for everything covers Theorem's detectors, never a host's own.
|
|
157
|
+
const blanket = key.includes('.') ? undefined : spec;
|
|
158
|
+
const rule = typeof spec === 'object' ? spec[key] : blanket;
|
|
159
|
+
const others = DETECTORS.filter((detector) => detector !== key);
|
|
160
|
+
const only = Object.fromEntries(others.map((detector) => [detector, 'ignore']));
|
|
161
|
+
if (rule !== undefined)
|
|
162
|
+
only[key] = rule;
|
|
163
|
+
return only;
|
|
164
|
+
}
|
|
165
|
+
/** `profile` reading with the detector `key` alone. */
|
|
166
|
+
function onlyDetector(profile, key) {
|
|
167
|
+
const detect = detectOnly(profile.guardrails?.detect, key);
|
|
168
|
+
return { ...profile, guardrails: { ...profile.guardrails, detect } };
|
|
169
|
+
}
|
|
170
|
+
/** The boundaries where the detector `key` of `profile` does something with a match. */
|
|
171
|
+
function readBy(profile, key) {
|
|
172
|
+
const detect = resolveDetect(profile.guardrails?.detect);
|
|
173
|
+
return probeBoundariesOf(profile).filter((boundary) => actionAt(detect, key, boundaryRead(profile, boundary)) !== 'ignore');
|
|
174
|
+
}
|
|
175
|
+
/** The kernel boundary `boundary` is for `profile`: a Live session reads its own at the two ends. */
|
|
176
|
+
function boundaryRead(profile, boundary) {
|
|
177
|
+
if (profile.type !== 'live')
|
|
178
|
+
return PROBE_READS[boundary];
|
|
179
|
+
if (boundary === 'user')
|
|
180
|
+
return 'live_user';
|
|
181
|
+
return boundary === 'reply' ? 'live_reply' : PROBE_READS[boundary];
|
|
182
|
+
}
|
|
183
|
+
/** The failures that are a guardrail's doing; any other is the probe's own. */
|
|
184
|
+
const REFUSALS = new Set(['safety', 'blocked']);
|
|
185
|
+
/**
|
|
186
|
+
* Whether a turn that failed with `kind` was refused by a guardrail. A block of the request
|
|
187
|
+
* fails as a bad input does, so there the block already among `events` tells them apart.
|
|
188
|
+
*/
|
|
189
|
+
function refusedBy(kind, events) {
|
|
190
|
+
if (REFUSALS.has(kind))
|
|
191
|
+
return true;
|
|
192
|
+
return (kind === 'input' &&
|
|
193
|
+
events.some((event) => event.type === 'guardrail' && event.guardrail.action === 'block'));
|
|
194
|
+
}
|
|
195
|
+
/** The draft with the probe's tools beside its own. */
|
|
196
|
+
function probedDraft(profile, customTools, structured, text) {
|
|
197
|
+
if (profile.type === 'speech') {
|
|
198
|
+
return {
|
|
199
|
+
profile: {
|
|
200
|
+
...unobserved(profile),
|
|
201
|
+
observability: { include: { guardrailMatchPreview: true } },
|
|
202
|
+
},
|
|
203
|
+
customTools: [],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const live = profile.type === 'live';
|
|
207
|
+
const tools = [
|
|
208
|
+
probeTool(LOCAL_TOOL, 'read-only', { text }),
|
|
209
|
+
probeTool(SEND_TOOL, 'read-write'),
|
|
210
|
+
probeTool(ACT_TOOL, 'destructive'),
|
|
211
|
+
...(live ? [] : [remoteTool()]),
|
|
212
|
+
];
|
|
213
|
+
return {
|
|
214
|
+
profile: {
|
|
215
|
+
...unobserved(profile),
|
|
216
|
+
// why: The probe types its text into the session, whatever the draft takes in.
|
|
217
|
+
...(profile.type === 'live'
|
|
218
|
+
? { live: { ...profile.live, ingress: { ...profile.live?.ingress, text: true } } }
|
|
219
|
+
: {}),
|
|
220
|
+
observability: { include: { guardrailMatchPreview: true } },
|
|
221
|
+
tools: {
|
|
222
|
+
...profile.tools,
|
|
223
|
+
allow: [...(profile.tools?.allow ?? []), ...tools.map((tool) => tool.name)],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
customTools: [...customTools, ...tools],
|
|
227
|
+
structured,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function remoteTool() {
|
|
231
|
+
return {
|
|
232
|
+
type: 'agent',
|
|
233
|
+
name: REMOTE_TOOL,
|
|
234
|
+
description: 'A guardrail probe.',
|
|
235
|
+
category: 'probe',
|
|
236
|
+
access: 'read-only',
|
|
237
|
+
permission: 'auto',
|
|
238
|
+
loadTier: 'T0',
|
|
239
|
+
paths: ['*'],
|
|
240
|
+
profile: REMOTE_AGENT,
|
|
241
|
+
inputSchema: TEXT_SCHEMA,
|
|
242
|
+
outputSchema: TEXT_SCHEMA,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function call(name, args) {
|
|
246
|
+
return { type: 'tool', tool: { name, arguments: args, callId: `probe_${name}` } };
|
|
247
|
+
}
|
|
248
|
+
function chunks(text) {
|
|
249
|
+
const chars = [...text];
|
|
250
|
+
const out = [];
|
|
251
|
+
for (let at = 0; at < chars.length; at += REPLY_CHUNK) {
|
|
252
|
+
out.push(chars.slice(at, at + REPLY_CHUNK).join(''));
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
/** What the scripted model says at each of its steps; past them it says `ok`. */
|
|
257
|
+
function script({ boundary, text }) {
|
|
258
|
+
switch (boundary) {
|
|
259
|
+
case 'tool_result_local':
|
|
260
|
+
return [[call(LOCAL_TOOL, {})]];
|
|
261
|
+
// The call after the read is the one `taint.afterRemoteRead` decides.
|
|
262
|
+
case 'tool_result_remote':
|
|
263
|
+
return [[call(REMOTE_TOOL, { text: 'read' })], [call(ACT_TOOL, {})]];
|
|
264
|
+
case 'tool_arguments':
|
|
265
|
+
return [[call(SEND_TOOL, { text })]];
|
|
266
|
+
case 'reply':
|
|
267
|
+
return [chunks(text).map((chunk) => ({ type: 'text', text: chunk }))];
|
|
268
|
+
case 'thought':
|
|
269
|
+
return [[{ type: 'thought', text }]];
|
|
270
|
+
default:
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function scripted(steps, seen) {
|
|
275
|
+
let step = 0;
|
|
276
|
+
return {
|
|
277
|
+
async *complete(req) {
|
|
278
|
+
seen.push(req);
|
|
279
|
+
const events = steps[step] ?? [{ type: 'text', text: 'ok' }];
|
|
280
|
+
step += 1;
|
|
281
|
+
await Promise.resolve();
|
|
282
|
+
yield* events;
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function turnInput({ boundary, text }) {
|
|
287
|
+
if (boundary === 'user')
|
|
288
|
+
return { text };
|
|
289
|
+
if (boundary === 'history') {
|
|
290
|
+
return {
|
|
291
|
+
text: 'Go on.',
|
|
292
|
+
history: [
|
|
293
|
+
{ role: 'user', content: text },
|
|
294
|
+
{ role: 'assistant', content: 'ok' },
|
|
295
|
+
],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return { text: 'Run the probe.' };
|
|
299
|
+
}
|
|
300
|
+
/** The tool a host probe calls, and what it sends. */
|
|
301
|
+
function hostCall({ boundary, text }) {
|
|
302
|
+
switch (boundary) {
|
|
303
|
+
case 'tool_result_remote':
|
|
304
|
+
return { name: REMOTE_TOOL, input: { text: 'read' } };
|
|
305
|
+
case 'tool_arguments':
|
|
306
|
+
return { name: SEND_TOOL, input: { text } };
|
|
307
|
+
default:
|
|
308
|
+
return { name: LOCAL_TOOL, input: {} };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** A scripted Gemini Live socket: it completes setup, and keeps what the session sent. */
|
|
312
|
+
class ProbeSocket extends EventTarget {
|
|
313
|
+
readyState = 0;
|
|
314
|
+
sent = [];
|
|
315
|
+
onopen = null;
|
|
316
|
+
onmessage = null;
|
|
317
|
+
onerror = null;
|
|
318
|
+
onclose = null;
|
|
319
|
+
send(data) {
|
|
320
|
+
this.sent.push(data);
|
|
321
|
+
if (data.includes('"setup"'))
|
|
322
|
+
queueMicrotask(() => this.deliver({ setupComplete: true }));
|
|
323
|
+
}
|
|
324
|
+
close() {
|
|
325
|
+
this.readyState = 3;
|
|
326
|
+
const event = new CloseEvent('close', { code: 1000 });
|
|
327
|
+
this.onclose?.(event);
|
|
328
|
+
this.dispatchEvent(event);
|
|
329
|
+
}
|
|
330
|
+
open() {
|
|
331
|
+
this.readyState = 1;
|
|
332
|
+
const event = new Event('open');
|
|
333
|
+
this.onopen?.(event);
|
|
334
|
+
this.dispatchEvent(event);
|
|
335
|
+
}
|
|
336
|
+
deliver(payload) {
|
|
337
|
+
const event = new MessageEvent('message', { data: JSON.stringify(payload) });
|
|
338
|
+
this.onmessage?.(event);
|
|
339
|
+
this.dispatchEvent(event);
|
|
340
|
+
}
|
|
341
|
+
/** The frames the session sent, parsed. */
|
|
342
|
+
frames() {
|
|
343
|
+
return this.sent.map((frame) => JSON.parse(frame));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const LIVE_SESSION_BOUNDARIES = new Set([
|
|
347
|
+
'user',
|
|
348
|
+
'history',
|
|
349
|
+
'system',
|
|
350
|
+
'reply',
|
|
351
|
+
]);
|
|
352
|
+
/** A session that stays quiet this long after its last event is done. */
|
|
353
|
+
const LIVE_IDLE_MS = 1500;
|
|
354
|
+
function record(value) {
|
|
355
|
+
return typeof value === 'object' && value !== null
|
|
356
|
+
? value
|
|
357
|
+
: undefined;
|
|
358
|
+
}
|
|
359
|
+
/** The strings under `text` in a list of wire parts. */
|
|
360
|
+
function partTexts(parts) {
|
|
361
|
+
const found = [];
|
|
362
|
+
for (const part of Array.isArray(parts) ? parts : []) {
|
|
363
|
+
const text = record(part)?.text;
|
|
364
|
+
if (typeof text === 'string')
|
|
365
|
+
found.push(text);
|
|
366
|
+
}
|
|
367
|
+
return found;
|
|
368
|
+
}
|
|
369
|
+
/** The user turns of a `clientContent` frame, as their parts' text. */
|
|
370
|
+
function userTurnTexts(turns) {
|
|
371
|
+
return (Array.isArray(turns) ? turns : []).flatMap((turn) => {
|
|
372
|
+
const row = record(turn);
|
|
373
|
+
return row?.role === 'user' ? partTexts(row.parts) : [];
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/** The text of a frame the session sent, by the part of the wire it rides in. */
|
|
377
|
+
function frameTexts(frame, boundary) {
|
|
378
|
+
if (boundary === 'system')
|
|
379
|
+
return partTexts(record(record(frame.setup)?.systemInstruction)?.parts);
|
|
380
|
+
if (boundary === 'history')
|
|
381
|
+
return userTurnTexts(record(frame.clientContent)?.turns);
|
|
382
|
+
const text = record(frame.realtimeInput)?.text;
|
|
383
|
+
return typeof text === 'string' ? [text] : [];
|
|
384
|
+
}
|
|
385
|
+
/** What the session sent at `boundary`: the last text for the user, every text otherwise. */
|
|
386
|
+
function sentText(socket, boundary) {
|
|
387
|
+
const texts = socket.frames().flatMap((frame) => frameTexts(frame, boundary));
|
|
388
|
+
return boundary === 'user' ? texts.at(-1) : texts.join('\n') || undefined;
|
|
389
|
+
}
|
|
390
|
+
/** Runs one probe in a Live session on a scripted socket, and returns what the session yielded. */
|
|
391
|
+
async function runLiveSession(scope, profile, probe, sink, events, signal) {
|
|
392
|
+
const { boundary, text } = probe;
|
|
393
|
+
const socket = new ProbeSocket();
|
|
394
|
+
const binding = profile.models[profile.defaultModel];
|
|
395
|
+
const named = keySlotsOf(scope.providers.require(binding.provider, binding.apiId), binding);
|
|
396
|
+
const slots = [named.keySlot, named.fallbackKeySlot].filter((slot) => slot !== undefined);
|
|
397
|
+
if (boundary === 'history' || boundary === 'system') {
|
|
398
|
+
// why: A session that refuses what it is seeded with throws before it reports anything, so a
|
|
399
|
+
// blocked probe reads the request first to show what was found. Otherwise the session reports it.
|
|
400
|
+
const seeded = sanitizeTurnRequestWithEvents({
|
|
401
|
+
profile: profile.id,
|
|
402
|
+
...(boundary === 'system' ? { system: text } : {}),
|
|
403
|
+
input: boundary === 'history' ? { history: [{ role: 'user', content: text }] } : {},
|
|
404
|
+
}, profile);
|
|
405
|
+
if (seeded.refusal) {
|
|
406
|
+
events.push(...seeded.events);
|
|
407
|
+
throw seeded.refusal;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const session = await scope.runSession({
|
|
411
|
+
profile: profile.id,
|
|
412
|
+
...(boundary === 'system' ? { system: text } : {}),
|
|
413
|
+
...(boundary === 'history'
|
|
414
|
+
? {
|
|
415
|
+
history: [
|
|
416
|
+
{ role: 'user', content: text },
|
|
417
|
+
{ role: 'assistant', content: 'ok' },
|
|
418
|
+
],
|
|
419
|
+
}
|
|
420
|
+
: {}),
|
|
421
|
+
...(signal ? { signal } : {}),
|
|
422
|
+
}, {
|
|
423
|
+
// why: The scripted socket needs explicit fixture entries for the selected credential slots.
|
|
424
|
+
vault: Object.fromEntries(slots.map((slot) => [slot, 'probe'])),
|
|
425
|
+
openWebSocket: () => {
|
|
426
|
+
queueMicrotask(() => {
|
|
427
|
+
socket.open();
|
|
428
|
+
});
|
|
429
|
+
return Promise.resolve(socket);
|
|
430
|
+
},
|
|
431
|
+
}, sink);
|
|
432
|
+
let last = Date.now();
|
|
433
|
+
let finished = false;
|
|
434
|
+
const drain = (async () => {
|
|
435
|
+
for await (const event of session.events()) {
|
|
436
|
+
events.push(event);
|
|
437
|
+
last = Date.now();
|
|
438
|
+
if (event.type === 'error')
|
|
439
|
+
finished = true;
|
|
440
|
+
if (event.type === 'done' && event.stop?.kind !== 'generation_complete')
|
|
441
|
+
finished = true;
|
|
442
|
+
}
|
|
443
|
+
})();
|
|
444
|
+
try {
|
|
445
|
+
await session.sendText(boundary === 'user' ? text : 'Run the probe.');
|
|
446
|
+
if (boundary === 'reply') {
|
|
447
|
+
socket.deliver({
|
|
448
|
+
serverContent: {
|
|
449
|
+
modelTurn: {
|
|
450
|
+
parts: [{ inlineData: { mimeType: 'audio/pcm;rate=24000', data: 'AAAA' } }],
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
for (const chunk of chunks(text)) {
|
|
455
|
+
socket.deliver({ serverContent: { outputTranscription: { text: chunk } } });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
socket.deliver({ serverContent: { generationComplete: true } });
|
|
459
|
+
socket.deliver({ serverContent: { turnComplete: true } });
|
|
460
|
+
while (!finished && Date.now() - last < LIVE_IDLE_MS && !signal?.aborted) {
|
|
461
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
finally {
|
|
465
|
+
socket.close();
|
|
466
|
+
await session.close();
|
|
467
|
+
await drain.catch(() => undefined);
|
|
468
|
+
}
|
|
469
|
+
return socket;
|
|
470
|
+
}
|
|
471
|
+
function readBack(events, name) {
|
|
472
|
+
for (const event of events) {
|
|
473
|
+
if (event.type !== 'tool' || event.tool.name !== name)
|
|
474
|
+
continue;
|
|
475
|
+
if ('readBack' in event.tool && event.tool.readBack !== undefined)
|
|
476
|
+
return event.tool.readBack;
|
|
477
|
+
}
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
function said(events, type) {
|
|
481
|
+
const text = events.flatMap((event) => (event.type === type ? [event.text] : [])).join('');
|
|
482
|
+
return text === '' ? undefined : text;
|
|
483
|
+
}
|
|
484
|
+
/** The probe's text past its boundary, from what the model was sent and the host got. */
|
|
485
|
+
function passed(boundary, events, seen) {
|
|
486
|
+
const [first] = seen;
|
|
487
|
+
const users = (first?.history ?? []).filter((message) => message.role === 'user');
|
|
488
|
+
switch (boundary) {
|
|
489
|
+
case 'user':
|
|
490
|
+
return users.at(-1)?.content;
|
|
491
|
+
case 'history':
|
|
492
|
+
return users[0]?.content;
|
|
493
|
+
case 'system':
|
|
494
|
+
return first?.system;
|
|
495
|
+
case 'tool_result_local':
|
|
496
|
+
return readBack(events, LOCAL_TOOL);
|
|
497
|
+
case 'tool_result_remote':
|
|
498
|
+
return readBack(events, REMOTE_TOOL);
|
|
499
|
+
case 'tool_arguments':
|
|
500
|
+
return readBack(events, SEND_TOOL) === undefined ? undefined : seenArguments(events);
|
|
501
|
+
case 'reply':
|
|
502
|
+
return said(events, 'text');
|
|
503
|
+
case 'thought':
|
|
504
|
+
return said(events, 'thought');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/** The probe's text past its boundary in a Live session: what went out on the socket, or what the user got. */
|
|
508
|
+
function passedLive(boundary, events, socket) {
|
|
509
|
+
switch (boundary) {
|
|
510
|
+
case 'user':
|
|
511
|
+
case 'history':
|
|
512
|
+
case 'system':
|
|
513
|
+
return sentText(socket, boundary);
|
|
514
|
+
case 'reply': {
|
|
515
|
+
// why: Speech is heard, so what the user got is the transcript the gate released.
|
|
516
|
+
const spoken = events
|
|
517
|
+
.flatMap((event) => event.type === 'evidence' && event.evidence.kind === 'output_transcription'
|
|
518
|
+
? [event.text ?? '']
|
|
519
|
+
: [])
|
|
520
|
+
.join('');
|
|
521
|
+
return said(events, 'text') ?? (spoken === '' ? undefined : spoken);
|
|
522
|
+
}
|
|
523
|
+
default:
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function seenArguments(events) {
|
|
528
|
+
for (const event of events) {
|
|
529
|
+
if (event.type === 'tool' && event.tool.name === SEND_TOOL && 'arguments' in event.tool) {
|
|
530
|
+
return JSON.stringify(event.tool.arguments);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
function withoutHeaders(tools) {
|
|
536
|
+
return tools.map((tool) => {
|
|
537
|
+
if (!('headers' in tool))
|
|
538
|
+
return tool;
|
|
539
|
+
const { headers: _headers, ...rest } = tool;
|
|
540
|
+
return rest;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The draft as a probe is sent it: a probe calls none of the draft's tools,
|
|
545
|
+
* so the headers they would send, where a key may sit, stay behind.
|
|
546
|
+
*/
|
|
547
|
+
export function probeDraft(draft) {
|
|
548
|
+
return {
|
|
549
|
+
profile: draft.profile,
|
|
550
|
+
customTools: withoutHeaders(draft.customTools),
|
|
551
|
+
...(draft.structured ? { structured: draft.structured } : {}),
|
|
552
|
+
dependencies: (draft.dependencies ?? []).map((dependency) => ({
|
|
553
|
+
...dependency,
|
|
554
|
+
customTools: withoutHeaders(dependency.customTools),
|
|
555
|
+
})),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/** The agent types the tester runs, with the boundaries each one's turn crosses. */
|
|
559
|
+
const TYPE_BOUNDARIES = {
|
|
560
|
+
text: PROBE_BOUNDARIES,
|
|
561
|
+
image: PROBE_BOUNDARIES,
|
|
562
|
+
// A speech agent reads what it is asked to say, as the transcript. It takes no system prompt,
|
|
563
|
+
// calls no tool and writes no reply.
|
|
564
|
+
speech: ['user', 'history'],
|
|
565
|
+
// A host runs tools and no model: only what crosses a tool is read.
|
|
566
|
+
host: ['tool_result_local', 'tool_result_remote', 'tool_arguments'],
|
|
567
|
+
// A Live session reads what is typed into it, what it is seeded with, what its tools take and
|
|
568
|
+
// return, and what it says. A remote tool's reply and a thought are not probed.
|
|
569
|
+
live: ['user', 'history', 'system', 'tool_result_local', 'tool_arguments', 'reply'],
|
|
570
|
+
};
|
|
571
|
+
/** The boundaries a turn of `profile` crosses, in the order of {@linkcode PROBE_BOUNDARIES}. */
|
|
572
|
+
function probeBoundariesOf(profile) {
|
|
573
|
+
return TYPE_BOUNDARIES[profile.type] ?? [];
|
|
574
|
+
}
|
|
575
|
+
/** Why `profile` can't be probed, or undefined when it can. */
|
|
576
|
+
export function probeRefusal(profile) {
|
|
577
|
+
if (TYPE_BOUNDARIES[profile.type])
|
|
578
|
+
return undefined;
|
|
579
|
+
return profile.type === 'decision'
|
|
580
|
+
? 'A decision agent runs no text guardrails; what leaves its state is gated by guardrails.disclosure.'
|
|
581
|
+
: `Guardrail tests don’t run on ${profile.type} agents yet.`;
|
|
582
|
+
}
|
|
583
|
+
function probeStatus(guardrails, refused) {
|
|
584
|
+
const did = (action) => guardrails.some((event) => event.action === action);
|
|
585
|
+
if (refused || did('block'))
|
|
586
|
+
return 'blocked';
|
|
587
|
+
if (did('redact'))
|
|
588
|
+
return 'redacted';
|
|
589
|
+
const reported = guardrails.some((event) => event.action === 'flag' &&
|
|
590
|
+
event.hits.some((hit) => hit.rule !== TOOL_RULES.taintedTurn && hit.rule !== TOOL_RULES.steeredTurn));
|
|
591
|
+
return reported ? 'flagged' : 'passed';
|
|
592
|
+
}
|
|
593
|
+
/** Throws when `profile` cannot be sent `probe`: it is refused, has no such boundary, or the text is too long. */
|
|
594
|
+
function assertProbeable(profile, probe) {
|
|
595
|
+
const refusal = probeRefusal(profile);
|
|
596
|
+
if (refusal || profile.type === 'decision' || profile.type === 'embedding') {
|
|
597
|
+
throw new TheoremError('request', refusal ?? ''); // labels-exempt: builder diagnostic
|
|
598
|
+
}
|
|
599
|
+
if (!probeBoundariesOf(profile).includes(probe.boundary)) {
|
|
600
|
+
// labels-exempt: builder diagnostic
|
|
601
|
+
throw new TheoremError('request', `A ${profile.type} agent has no ${PROBE_BOUNDARY_NOTES[probe.boundary].label} boundary.`);
|
|
602
|
+
}
|
|
603
|
+
if (probe.text.length > PROBE_TEXT_LIMIT) {
|
|
604
|
+
throw new TheoremError('request', `A probe is at most ${PROBE_TEXT_LIMIT} characters.`); // labels-exempt: builder diagnostic
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/** Where a probe runs: a host or a live agent calls the tool, any other takes a turn. */
|
|
608
|
+
function probeStream(run) {
|
|
609
|
+
const { scope, profile, probe, signal, onAgentCall, sink } = run;
|
|
610
|
+
if (run.type === 'host' || run.type === 'live') {
|
|
611
|
+
return scope.invokeTool({ profile: profile.id, ...hostCall(probe), signal, onAgentCall }, sink);
|
|
612
|
+
}
|
|
613
|
+
return scope.runTurn({
|
|
614
|
+
profile: profile.id,
|
|
615
|
+
input: turnInput(probe),
|
|
616
|
+
...(probe.boundary === 'system' ? { system: probe.text } : {}),
|
|
617
|
+
signal,
|
|
618
|
+
onAgentCall,
|
|
619
|
+
}, runtimeProvider({ mode: 'byok', provider: run.provider }, scope, profile), sink);
|
|
620
|
+
}
|
|
621
|
+
/** What stopped the turn, when its guardrails did; throws for any other failure. */
|
|
622
|
+
function refusalOf(events) {
|
|
623
|
+
const failure = events.find((event) => event.type === 'error');
|
|
624
|
+
if (failure?.type !== 'error')
|
|
625
|
+
return undefined;
|
|
626
|
+
if (!refusedBy(failure.errorKind, events)) {
|
|
627
|
+
throw new TheoremError(failure.errorKind, failure.errorInternal ?? failure.error ?? ''); // labels-exempt: builder diagnostic
|
|
628
|
+
}
|
|
629
|
+
return { kind: failure.errorKind, message: failure.error };
|
|
630
|
+
}
|
|
631
|
+
/** Whether the turn read a remote result as steering, or only as tainted. */
|
|
632
|
+
function taintOf(guardrails) {
|
|
633
|
+
const rules = new Set(guardrails.flatMap((event) => event.hits.map((hit) => hit.rule)));
|
|
634
|
+
if (rules.has(TOOL_RULES.steeredTurn))
|
|
635
|
+
return 'steered';
|
|
636
|
+
return rules.has(TOOL_RULES.taintedTurn) ? 'tainted' : undefined;
|
|
637
|
+
}
|
|
638
|
+
/** Runs `probe` on the draft and reports what its guardrails did. */
|
|
639
|
+
export async function runGuardrailProbe(args) {
|
|
640
|
+
const { probe } = args;
|
|
641
|
+
const asked = args.profile;
|
|
642
|
+
assertProbeable(asked, probe);
|
|
643
|
+
const { scope, profile } = await writtenScope([
|
|
644
|
+
...(args.dependencies ?? []),
|
|
645
|
+
...(asked.type === 'speech' || asked.type === 'live' ? [] : [remoteAgent(asked)]),
|
|
646
|
+
probedDraft(asked, args.customTools, args.structured, probe.text),
|
|
647
|
+
]);
|
|
648
|
+
if (!profile)
|
|
649
|
+
throw new TheoremError('request', 'No agent to probe.'); // labels-exempt: builder diagnostic
|
|
650
|
+
const seen = [];
|
|
651
|
+
const model = scripted(script(probe), seen);
|
|
652
|
+
const remote = scripted([[{ type: 'text', text: probe.text }]], []);
|
|
653
|
+
const provider = (asked) => (asked.id === REMOTE_AGENT ? remote : model);
|
|
654
|
+
const events = [];
|
|
655
|
+
const traces = [];
|
|
656
|
+
const sink = {
|
|
657
|
+
write: (record) => {
|
|
658
|
+
traces.push(record);
|
|
659
|
+
return Promise.resolve();
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
const onAgentCall = agentCallHook(scope, { mode: 'byok', provider });
|
|
663
|
+
let refused;
|
|
664
|
+
let socket;
|
|
665
|
+
// why: A Live session reads what goes in and what it says; a tool it calls is run by the host
|
|
666
|
+
// through `invokeTool`, which is where the arguments and the result are read.
|
|
667
|
+
const viaSession = asked.type === 'live' && LIVE_SESSION_BOUNDARIES.has(probe.boundary);
|
|
668
|
+
try {
|
|
669
|
+
if (viaSession) {
|
|
670
|
+
if (profile.type !== 'live')
|
|
671
|
+
throw new Error('A live probe requires a live profile');
|
|
672
|
+
socket = await runLiveSession(scope, profile, probe, sink, events, args.signal);
|
|
673
|
+
}
|
|
674
|
+
for await (const event of viaSession
|
|
675
|
+
? []
|
|
676
|
+
: probeStream({
|
|
677
|
+
scope,
|
|
678
|
+
profile,
|
|
679
|
+
type: asked.type,
|
|
680
|
+
probe,
|
|
681
|
+
signal: args.signal,
|
|
682
|
+
onAgentCall,
|
|
683
|
+
provider,
|
|
684
|
+
sink,
|
|
685
|
+
})) {
|
|
686
|
+
events.push(event);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
if (!(error instanceof TheoremError && refusedBy(error.kind, events)))
|
|
691
|
+
throw error;
|
|
692
|
+
refused = { kind: error.kind, message: error.message };
|
|
693
|
+
}
|
|
694
|
+
refused ??= refusalOf(events);
|
|
695
|
+
const guardrails = events.flatMap((event) => event.type === 'guardrail' ? [event.guardrail] : []);
|
|
696
|
+
const text = socket
|
|
697
|
+
? passedLive(probe.boundary, events, socket)
|
|
698
|
+
: passed(probe.boundary, events, seen);
|
|
699
|
+
const taint = taintOf(guardrails);
|
|
700
|
+
return {
|
|
701
|
+
status: probeStatus(guardrails, refused !== undefined),
|
|
702
|
+
guardrails,
|
|
703
|
+
...(text === undefined ? {} : { passed: text }),
|
|
704
|
+
...(taint ? { taint } : {}),
|
|
705
|
+
...(refused ? { refused } : {}),
|
|
706
|
+
traces,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Runs `text` across every boundary of the draft, each in its own turn, in the order of
|
|
711
|
+
* {@linkcode PROBE_BOUNDARIES}. Given `only`, a detector's key, the draft reads with that
|
|
712
|
+
* detector alone, and the answers are the boundaries where it is not set to ignore.
|
|
713
|
+
*/
|
|
714
|
+
export function runGuardrailProbes(args) {
|
|
715
|
+
const { text, only, ...draft } = args;
|
|
716
|
+
const profile = only === undefined ? draft.profile : onlyDetector(draft.profile, only);
|
|
717
|
+
const boundaries = only === undefined ? probeBoundariesOf(profile) : readBy(profile, only);
|
|
718
|
+
return Promise.all(boundaries.map(async (boundary) => ({
|
|
719
|
+
boundary,
|
|
720
|
+
...(await runGuardrailProbe({ ...draft, profile, probe: { boundary, text } })),
|
|
721
|
+
})));
|
|
722
|
+
}
|