@youweichen/pi-harness 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +74 -2
- package/bin/pi-harness.mjs +1819 -0
- package/deploy/com.youweichen.pi-harness.plist +48 -0
- package/deploy/nginx-subpath.conf +88 -0
- package/deploy/pi-harness-task.xml +71 -0
- package/deploy/pi-harness.service +31 -0
- package/dist/server/agent-service.js +3051 -0
- package/dist/server/app-version.js +19 -0
- package/dist/server/attachments.js +526 -0
- package/dist/server/auth-file.js +61 -0
- package/dist/server/bg-servers.js +154 -0
- package/dist/server/client-eviction.js +19 -0
- package/dist/server/client-id.js +9 -0
- package/dist/server/client-state.js +202 -0
- package/dist/server/codemode-image-routes.js +36 -0
- package/dist/server/codemode-presentation.js +19 -0
- package/dist/server/component-updates.js +135 -0
- package/dist/server/context-breakdown.js +56 -0
- package/dist/server/control-socket.js +177 -0
- package/dist/server/conversation.js +65 -0
- package/dist/server/dir-browser.js +77 -0
- package/dist/server/editor-snapshot.js +38 -0
- package/dist/server/ensure-bash.js +80 -0
- package/dist/server/extension-display.js +23 -0
- package/dist/server/extensions-catalog.js +64 -0
- package/dist/server/extensions-model.js +46 -0
- package/dist/server/extensions-routes.js +255 -0
- package/dist/server/extensions-worker.js +187 -0
- package/dist/server/files-service.js +795 -0
- package/dist/server/index.js +1029 -0
- package/dist/server/markdown-images.js +42 -0
- package/dist/server/model-admin.js +801 -0
- package/dist/server/model-config-merge.js +32 -0
- package/dist/server/native-mcp-config.js +103 -0
- package/dist/server/native-mcp-presentation.js +12 -0
- package/dist/server/native-prompt-template.js +12 -0
- package/dist/server/native-tools.js +32 -0
- package/dist/server/node-sources.js +142 -0
- package/dist/server/node-workbench.js +668 -0
- package/dist/server/origin-policy.js +50 -0
- package/dist/server/patch-node-pty.js +96 -0
- package/dist/server/pi-environment.js +203 -0
- package/dist/server/plan/extension.js +74 -0
- package/dist/server/plan/progress.js +90 -0
- package/dist/server/plan/settings.js +41 -0
- package/dist/server/plan/state.js +113 -0
- package/dist/server/plugin-facilities.js +304 -0
- package/dist/server/plugin-updater.js +245 -0
- package/dist/server/plugins.js +712 -0
- package/dist/server/private-file.js +28 -0
- package/dist/server/process-utils.js +112 -0
- package/dist/server/prompt-delivery.js +80 -0
- package/dist/server/protocol-version.js +11 -0
- package/dist/server/protocol.js +6 -0
- package/dist/server/provider-auth.js +99 -0
- package/dist/server/query-cache.js +45 -0
- package/dist/server/recovery-state.js +37 -0
- package/dist/server/scm.js +459 -0
- package/dist/server/security-headers.js +24 -0
- package/dist/server/serialize.js +254 -0
- package/dist/server/session-file-read.js +225 -0
- package/dist/server/session-record-worker.js +21 -0
- package/dist/server/session-reload.js +13 -0
- package/dist/server/session-tree-controller.js +250 -0
- package/dist/server/session-tree.js +191 -0
- package/dist/server/settings-service.js +53 -0
- package/dist/server/skills-service.js +87 -0
- package/dist/server/slash-commands.js +280 -0
- package/dist/server/sqlite-preview.js +58 -0
- package/dist/server/sqlite-query.js +67 -0
- package/dist/server/sqlite-worker.js +12 -0
- package/dist/server/system-prompt-files.js +158 -0
- package/dist/server/system-prompt-routes.js +40 -0
- package/dist/server/system-prompt-view.js +49 -0
- package/dist/server/task-progress.js +233 -0
- package/dist/server/terminals.js +833 -0
- package/dist/server/text-sniff.js +269 -0
- package/dist/server/thinking-timing.js +91 -0
- package/dist/server/token-auth.js +37 -0
- package/dist/server/tool-output.js +97 -0
- package/dist/server/tool-text-incidents.js +82 -0
- package/dist/server/tool-text.js +67 -0
- package/dist/server/uploads.js +112 -0
- package/dist/server/user-attachments.js +96 -0
- package/dist/server/webui-context.js +257 -0
- package/dist/server/wiki-links.js +136 -0
- package/dist/server/wiki-pdf-worker.js +23 -0
- package/dist/server/wiki-pdf.js +36 -0
- package/dist/server/wiki-routes.js +177 -0
- package/dist/server/wiki-service.js +511 -0
- package/electron/main.mjs +607 -0
- package/extensions/webui.ts +192 -0
- package/package.json +122 -4
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/NodeWorkbench-C4U1PldE.js +3 -0
- package/web/dist/assets/TerminalPanel-CWh6vw_N.js +2 -0
- package/web/dist/assets/abnfDiagram-VCTEODGH-D5Ti0Kpa.js +1 -0
- package/web/dist/assets/arc-DKJilLd6.js +1 -0
- package/web/dist/assets/architectureDiagram-5GKGNRK7-Cf0w38g-.js +36 -0
- package/web/dist/assets/blockDiagram-I7D4REHJ-BOTLWLIL.js +129 -0
- package/web/dist/assets/c4Diagram-7LVT6UL2-CLCCoJs3.js +38 -0
- package/web/dist/assets/channel-D8Hd7T4P.js +1 -0
- package/web/dist/assets/chunk-2Q5K7J3B-BHjga2Ge.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-CQJ5VfDZ.js +15 -0
- package/web/dist/assets/chunk-F27PBJKO-C67qoPJH.js +1 -0
- package/web/dist/assets/chunk-IMKFNOWR-CgXXGwI8.js +231 -0
- package/web/dist/assets/chunk-JWPE2WC7-D0RPyByP.js +1 -0
- package/web/dist/assets/chunk-POPQ4Y6H-BeYC9s9x.js +1 -0
- package/web/dist/assets/chunk-SVP7TREG-eQPl4UBQ.js +88 -0
- package/web/dist/assets/chunk-TICWLB2K-DT73arPU.js +206 -0
- package/web/dist/assets/chunk-XXDRQBXY-B4FwQcP-.js +1 -0
- package/web/dist/assets/classDiagram-ZZMXUADV-B_btgYRB.js +1 -0
- package/web/dist/assets/classDiagram-v2-VYDZK3BY-B_btgYRB.js +1 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-Bnn5nlQN.js +1 -0
- package/web/dist/assets/cynefin-OW5HDTMX-CStWX9kE.js +178 -0
- package/web/dist/assets/cynefinDiagram-5FMLGOSQ-BMVHC03f.js +62 -0
- package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
- package/web/dist/assets/dagre-GXQ25YYZ-ClSaO4ml.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-S7CK7UJ4-C43LL08C.js +30 -0
- package/web/dist/assets/diagram-UQ7AKVKN-BPVn_ySW.js +41 -0
- package/web/dist/assets/diagram-VSXAHHWV-C34FFhRi.js +3 -0
- package/web/dist/assets/diagram-VX7I27RA-BRk4bncf.js +24 -0
- package/web/dist/assets/diagram-Z3DM3KII-O5hYPLEd.js +24 -0
- package/web/dist/assets/ebnfDiagram-PWID7BFC-DVGEBm99.js +1 -0
- package/web/dist/assets/erDiagram-RLTQ6QDP-M-BLpTEV.js +99 -0
- package/web/dist/assets/flowDiagram-HODETNUW-BwdLopEx.js +168 -0
- package/web/dist/assets/ganttDiagram-EL5Y4UJY-DYxx2ppA.js +292 -0
- package/web/dist/assets/gitGraphDiagram-WWUBYQGX-DUU0wMYB.js +106 -0
- package/web/dist/assets/index-CI2KfVSr.css +10 -0
- package/web/dist/assets/index-DQcpggFU.js +275 -0
- package/web/dist/assets/infoDiagram-27XIBGKW-CFaW5C0C.js +2 -0
- package/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/web/dist/assets/ishikawaDiagram-5VMMS53U--ENP1MTB.js +70 -0
- package/web/dist/assets/journeyDiagram-3NMN7TZE-DgnAzSTj.js +139 -0
- package/web/dist/assets/kanban-definition-UXKFOSKX-_STZN1vU.js +89 -0
- package/web/dist/assets/katex-C5jXJg4s.js +257 -0
- package/web/dist/assets/layout-CkUClkXt.js +1 -0
- package/web/dist/assets/linear-sYHiUOxa.js +1 -0
- package/web/dist/assets/markdown-vNTCZJ6d.js +51 -0
- package/web/dist/assets/mermaid.core-B8fKGWsc.js +312 -0
- package/web/dist/assets/mindmap-definition-YA3MSWOX-DP0BYOEx.js +96 -0
- package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web/dist/assets/pegDiagram-XKGWAZYB-DkWTmZre.js +1 -0
- package/web/dist/assets/pieDiagram-E7YTZNPT-DfZgMV4u.js +39 -0
- package/web/dist/assets/quadrantDiagram-AXDQQJYC-Djl4TT7q.js +7 -0
- package/web/dist/assets/railroadDiagram-O6MQD6OU-DbKF6zXn.js +1 -0
- package/web/dist/assets/react-BNj3n7jX.js +24 -0
- package/web/dist/assets/requirementDiagram-BXWQKSXE-idola5qb.js +84 -0
- package/web/dist/assets/sankeyDiagram-P5KCCOFB-KA0hQOle.js +40 -0
- package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BXSKvxzP.js +162 -0
- package/web/dist/assets/sizeCapture-INFHLROL-C_Ewv3S5.js +1 -0
- package/web/dist/assets/stateDiagram-D77RDMKH-DIf5X8oo.js +1 -0
- package/web/dist/assets/stateDiagram-v2-MP3YSRHH-DYwe_2mB.js +1 -0
- package/web/dist/assets/swimlanes-42K2YHIH-Cx_Rc8M2.js +1 -0
- package/web/dist/assets/swimlanesDiagram-VR7AAH4N-52Y8tgRV.js +8 -0
- package/web/dist/assets/timeline-definition-24CTP7MA-BRrDhWVz.js +120 -0
- package/web/dist/assets/vennDiagram-4TSXK5OY-Ad6G0YwD.js +34 -0
- package/web/dist/assets/wardleyDiagram-VM6X3IG4-X6-ENpSi.js +78 -0
- package/web/dist/assets/xterm-6GBZ9nXN.css +32 -0
- package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
- package/web/dist/assets/xychartDiagram-S5SC5T6Z-DhegNS6d.js +7 -0
- package/web/dist/brand-mark.svg +5 -0
- package/web/dist/favicon-32.png +0 -0
- package/web/dist/favicon.svg +5 -0
- package/web/dist/fonts/WIKI-FONTS.md +5 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/LICENSE +93 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
- package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
- package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/LICENSE +93 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/LICENSE +94 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
- package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/LICENSE +93 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
- package/web/dist/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
- package/web/dist/icon/1a-mark.svg +1 -0
- package/web/dist/icon.ico +0 -0
- package/web/dist/index.html +18 -0
- package/web/public/apple-touch-icon.png +0 -0
- package/web/public/brand-mark.svg +5 -0
- package/web/public/favicon-32.png +0 -0
- package/web/public/favicon.svg +5 -0
- package/web/public/fonts/WIKI-FONTS.md +5 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/LICENSE +93 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
- package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
- package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/LICENSE +93 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/LICENSE +94 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
- package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
- package/web/public/fonts/source-serif-4/LICENSE +93 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
- package/web/public/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
- package/web/public/icon/1a-mark.svg +1 -0
- package/web/public/icon.ico +0 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { reloadSession } from "./session-reload.js";
|
|
2
|
+
/** Slash commands implemented natively by the web server (the pi CLI's built-in
|
|
3
|
+
* interactive commands like /model and /new are NOT handled by the SDK's
|
|
4
|
+
* prompt() — without this they'd be sent to the model as plain text). Keep in
|
|
5
|
+
* sync with exec(). */
|
|
6
|
+
export const NATIVE_COMMANDS = [
|
|
7
|
+
{ name: "tree", description: "查看会话树与切换分支", descriptionEn: "Browse the session tree and switch branches" },
|
|
8
|
+
{ name: "clone", description: "复制当前分支为新会话", descriptionEn: "Clone this branch into a new session" },
|
|
9
|
+
{ name: "fork", description: "选择用户消息派生新会话", descriptionEn: "Choose a user message to fork" },
|
|
10
|
+
{ name: "name", description: "重命名当前会话", descriptionEn: "Rename this session", argumentHint: "<名称>", argumentHintEn: "<name>" },
|
|
11
|
+
{ name: "new", description: "新建会话,保留当前会话历史", descriptionEn: "Start a new session, keeping previous history" },
|
|
12
|
+
{ name: "model", description: "切换模型", descriptionEn: "Switch model", argumentHint: "[名称]", argumentHintEn: "[name]" },
|
|
13
|
+
{ name: "compact", description: "压缩上下文", descriptionEn: "Compact context", argumentHint: "[说明]", argumentHintEn: "[instructions]" },
|
|
14
|
+
{ name: "cwd", description: "切换工作目录", descriptionEn: "Switch workspace", argumentHint: "<路径>", argumentHintEn: "<path>" },
|
|
15
|
+
{
|
|
16
|
+
name: "thinking",
|
|
17
|
+
description: "设置思考强度",
|
|
18
|
+
descriptionEn: "Set thinking level",
|
|
19
|
+
argumentHint: "<off|low|medium|high|xhigh|max>",
|
|
20
|
+
argumentHintEn: "<off|low|medium|high|xhigh|max>",
|
|
21
|
+
},
|
|
22
|
+
{ name: "resume", description: "刷新会话列表", descriptionEn: "Refresh session list" },
|
|
23
|
+
{ name: "reload", description: "重新加载扩展、技能与模板", descriptionEn: "Reload extensions, skills & templates" },
|
|
24
|
+
{ name: "help", description: "显示全部命令", descriptionEn: "Show all commands" },
|
|
25
|
+
{ name: "copy", description: "复制上一条助手回复", descriptionEn: "Copy last assistant reply" },
|
|
26
|
+
{ name: "pi-harness:quit", description: "退出服务", descriptionEn: "Quit server (supervisor will restart)" },
|
|
27
|
+
];
|
|
28
|
+
/** Parse a prompt into "/command args" — returns null when it isn't one. */
|
|
29
|
+
export function parseSlash(text) {
|
|
30
|
+
const trimmed = text.trim();
|
|
31
|
+
if (!trimmed.startsWith("/"))
|
|
32
|
+
return null;
|
|
33
|
+
const m = trimmed.match(/^\/([^\s]+)\s*([\s\S]*)$/);
|
|
34
|
+
if (!m || !m[1])
|
|
35
|
+
return null;
|
|
36
|
+
return { name: m[1], args: m[2].trim() };
|
|
37
|
+
}
|
|
38
|
+
export class SlashCommandsService {
|
|
39
|
+
host;
|
|
40
|
+
constructor(host) {
|
|
41
|
+
this.host = host;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Catalog of slash commands for the chat input: web-native builtins first,
|
|
45
|
+
* then the SDK's invokable commands for the ACTIVE conversation (extension
|
|
46
|
+
* commands, prompt templates, skills) — the same set the SDK expands when a
|
|
47
|
+
* prompt text starts with "/" (see AgentSession.prompt).
|
|
48
|
+
*/
|
|
49
|
+
async push() {
|
|
50
|
+
const commands = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const c of NATIVE_COMMANDS) {
|
|
53
|
+
commands.push({ ...c, source: "builtin" });
|
|
54
|
+
seen.add(c.name);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const s = this.host.getSession();
|
|
58
|
+
// Extension commands — the SDK already suffixes collisions with builtin
|
|
59
|
+
// names ("new:2"), and those still reach the SDK since exec() only
|
|
60
|
+
// intercepts the exact native names.
|
|
61
|
+
for (const cmd of s.extensionRunner.getRegisteredCommands()) {
|
|
62
|
+
if (seen.has(cmd.invocationName))
|
|
63
|
+
continue;
|
|
64
|
+
commands.push({
|
|
65
|
+
name: cmd.invocationName,
|
|
66
|
+
description: cmd.description,
|
|
67
|
+
source: "extension",
|
|
68
|
+
});
|
|
69
|
+
seen.add(cmd.invocationName);
|
|
70
|
+
}
|
|
71
|
+
// Prompt templates: /templatename args
|
|
72
|
+
for (const t of s.promptTemplates) {
|
|
73
|
+
if (seen.has(t.name))
|
|
74
|
+
continue;
|
|
75
|
+
commands.push({
|
|
76
|
+
name: t.name,
|
|
77
|
+
description: t.description,
|
|
78
|
+
source: "prompt",
|
|
79
|
+
});
|
|
80
|
+
seen.add(t.name);
|
|
81
|
+
}
|
|
82
|
+
// Skills: /skill:name args
|
|
83
|
+
for (const skill of s.resourceLoader.getSkills().skills) {
|
|
84
|
+
const name = `skill:${skill.name}`;
|
|
85
|
+
if (seen.has(name))
|
|
86
|
+
continue;
|
|
87
|
+
commands.push({
|
|
88
|
+
name,
|
|
89
|
+
description: skill.description,
|
|
90
|
+
source: "skill",
|
|
91
|
+
});
|
|
92
|
+
seen.add(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Session not ready yet — native-only catalog still serves the picker.
|
|
97
|
+
}
|
|
98
|
+
// UI 插件注册的命令(host.registerCommand)——全局,不依赖会话就绪。
|
|
99
|
+
this.host.emit({ type: "slash_commands", commands });
|
|
100
|
+
}
|
|
101
|
+
/** Run a native slash command (see NATIVE_COMMANDS). Returns false when the
|
|
102
|
+
* name is not a native command (the prompt falls through to the SDK). */
|
|
103
|
+
async exec(name, args, context) {
|
|
104
|
+
switch (name) {
|
|
105
|
+
case "tree":
|
|
106
|
+
case "clone":
|
|
107
|
+
case "fork":
|
|
108
|
+
case "name":
|
|
109
|
+
await this.host.treeCommand?.(name, args, context);
|
|
110
|
+
return true;
|
|
111
|
+
case "new":
|
|
112
|
+
await this.host.startNewSession();
|
|
113
|
+
return true;
|
|
114
|
+
case "model": {
|
|
115
|
+
if (!args) {
|
|
116
|
+
const current = this.host.getSession().model;
|
|
117
|
+
this.host.emit({
|
|
118
|
+
type: "notice",
|
|
119
|
+
level: "info",
|
|
120
|
+
text: current
|
|
121
|
+
? `当前模型:${current.name}(${current.provider}/${current.id})。用法:/model <名称>`
|
|
122
|
+
: `用法:/model <名称>`,
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const query = args.toLowerCase();
|
|
127
|
+
const available = await this.host.getSession().modelRuntime.getAvailable();
|
|
128
|
+
// Prefer an exact "provider/id" match, else id/name substring.
|
|
129
|
+
const exact = available.find((m) => m.provider + "/" + m.id === args.trim());
|
|
130
|
+
const matches = exact
|
|
131
|
+
? [exact]
|
|
132
|
+
: available.filter((m) => m.id.toLowerCase().includes(query) ||
|
|
133
|
+
m.name.toLowerCase().includes(query) ||
|
|
134
|
+
m.provider.toLowerCase().includes(query));
|
|
135
|
+
if (matches.length === 0) {
|
|
136
|
+
this.host.emit({
|
|
137
|
+
type: "notice",
|
|
138
|
+
level: "error",
|
|
139
|
+
text: `没有匹配到模型:${args}(可用模型见顶栏模型列表)`,
|
|
140
|
+
});
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const pick = matches[0];
|
|
144
|
+
if (matches.length > 1) {
|
|
145
|
+
this.host.emit({
|
|
146
|
+
type: "notice",
|
|
147
|
+
level: "warning",
|
|
148
|
+
text: `找到 ${matches.length} 个匹配模型,已选用:${pick.name}(精确匹配请用 provider/id)`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
await this.host.setModel(`${pick.provider}/${pick.id}`);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
case "compact":
|
|
155
|
+
try {
|
|
156
|
+
await this.host.getSession().compact(args || undefined);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
this.host.emit({
|
|
160
|
+
type: "notice",
|
|
161
|
+
level: "error",
|
|
162
|
+
text: `压缩上下文失败:${err.message}`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
case "cwd":
|
|
167
|
+
if (!args) {
|
|
168
|
+
this.host.emit({
|
|
169
|
+
type: "notice",
|
|
170
|
+
level: "info",
|
|
171
|
+
text: `当前工作目录:${this.host.cwd()}。用法:/cwd <路径>`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
await this.host.setCwd(args);
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
case "thinking": {
|
|
179
|
+
const ALIAS = {
|
|
180
|
+
off: "off",
|
|
181
|
+
minimal: "minimal",
|
|
182
|
+
low: "low",
|
|
183
|
+
medium: "medium",
|
|
184
|
+
high: "high",
|
|
185
|
+
xhigh: "xhigh",
|
|
186
|
+
max: "max",
|
|
187
|
+
关闭: "off",
|
|
188
|
+
极简: "minimal",
|
|
189
|
+
低: "low",
|
|
190
|
+
中: "medium",
|
|
191
|
+
高: "high",
|
|
192
|
+
极高: "xhigh",
|
|
193
|
+
最大: "max",
|
|
194
|
+
};
|
|
195
|
+
const level = ALIAS[args.trim().toLowerCase()];
|
|
196
|
+
if (!level) {
|
|
197
|
+
this.host.emit({
|
|
198
|
+
type: "notice",
|
|
199
|
+
level: "error",
|
|
200
|
+
text: `无效的思考强度:${args || "(空)"}。可用:off / minimal / low / medium / high / xhigh / max`,
|
|
201
|
+
});
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
this.host.setThinking(level);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
case "resume":
|
|
208
|
+
await this.host.refreshSessions();
|
|
209
|
+
this.host.emit({
|
|
210
|
+
type: "notice",
|
|
211
|
+
level: "info",
|
|
212
|
+
text: "会话列表已刷新,请在左侧「历史对话」中选择",
|
|
213
|
+
});
|
|
214
|
+
return true;
|
|
215
|
+
case "reload":
|
|
216
|
+
const startedAt = Date.now();
|
|
217
|
+
const s = this.host.getSession();
|
|
218
|
+
this.host.emit({ type: "reload_status", ...context, phase: "running", timestamp: startedAt });
|
|
219
|
+
try {
|
|
220
|
+
// Re-discovers extensions / skills / prompt templates from disk and
|
|
221
|
+
// re-pushes the picker catalog (the CLI's /reload semantics).
|
|
222
|
+
await reloadSession(s);
|
|
223
|
+
// reload() 会把 custom 工具加回活跃集——重放设置门控(终端开关等)。
|
|
224
|
+
if (this.host.getSession() === s) {
|
|
225
|
+
this.host.afterReload?.();
|
|
226
|
+
await this.push();
|
|
227
|
+
}
|
|
228
|
+
const extensions = s.resourceLoader.getExtensions();
|
|
229
|
+
const skills = s.resourceLoader.getSkills();
|
|
230
|
+
const prompts = s.resourceLoader.getPrompts();
|
|
231
|
+
const errors = [
|
|
232
|
+
...extensions.errors.map((e) => ({ path: e.path, message: e.error })),
|
|
233
|
+
...skills.diagnostics.filter((d) => d.type === "error").map((d) => ({ path: d.path ?? "", message: d.message })),
|
|
234
|
+
...prompts.diagnostics.filter((d) => d.type === "error").map((d) => ({ path: d.path ?? "", message: d.message })),
|
|
235
|
+
];
|
|
236
|
+
this.host.emit({
|
|
237
|
+
type: "reload_status", ...context, phase: "done", timestamp: startedAt,
|
|
238
|
+
durationMs: Date.now() - startedAt,
|
|
239
|
+
extensions: extensions.extensions.length, skills: skills.skills.length,
|
|
240
|
+
prompts: prompts.prompts.length, errors,
|
|
241
|
+
resources: {
|
|
242
|
+
extensions: extensions.extensions.map((extension) => extension.path),
|
|
243
|
+
skills: skills.skills.map((skill) => skill.name),
|
|
244
|
+
prompts: prompts.prompts.map((prompt) => prompt.name),
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
this.host.emit({
|
|
250
|
+
type: "reload_status", ...context, phase: "done", timestamp: startedAt,
|
|
251
|
+
durationMs: Date.now() - startedAt,
|
|
252
|
+
errors: [{ path: "", message: err.message }],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
case "pi-harness:quit": {
|
|
257
|
+
this.host.emit({
|
|
258
|
+
type: "notice",
|
|
259
|
+
level: "info",
|
|
260
|
+
text: "正在退出 pi-harness… supervisor 将自动重启服务",
|
|
261
|
+
});
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
const didSchedule = this.host.onQuit?.() ?? false;
|
|
264
|
+
if (!didSchedule) {
|
|
265
|
+
setTimeout(() => process.exit(0), 100);
|
|
266
|
+
}
|
|
267
|
+
}, 300);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
case "help":
|
|
271
|
+
case "copy":
|
|
272
|
+
// Client-side UI actions — the client handles them before sending;
|
|
273
|
+
// swallow here so the SDK never sees them as plain prompt text.
|
|
274
|
+
return true;
|
|
275
|
+
default:
|
|
276
|
+
// 插件命令:拦截执行(纯配置动作,与内置命令同级,不到 SDK)。
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import { realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { workspacePath } from "./files-service.js";
|
|
4
|
+
let running = 0;
|
|
5
|
+
/** Queries run outside the chat process, with bounded concurrency and a hard deadline. */
|
|
6
|
+
export function readSqlitePreview(request, signal) {
|
|
7
|
+
if (signal?.aborted)
|
|
8
|
+
return Promise.reject(new Error("Request cancelled"));
|
|
9
|
+
if (running >= 2)
|
|
10
|
+
return Promise.reject(new Error("Database viewer is busy; retry shortly"));
|
|
11
|
+
if (!Number.isSafeInteger(request.offset) || request.offset < 0 || request.offset > 10_000_000)
|
|
12
|
+
return Promise.reject(new Error("Invalid page offset"));
|
|
13
|
+
const root = realpathSync(request.cwd);
|
|
14
|
+
const path = workspacePath(root, request.path);
|
|
15
|
+
if (!path)
|
|
16
|
+
throw new Error("Path outside workspace");
|
|
17
|
+
const absolute = realpathSync(path.abs);
|
|
18
|
+
if (!workspacePath(root, absolute) || !statSync(absolute).isFile())
|
|
19
|
+
throw new Error("Path outside workspace");
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const workerUrl = new URL(import.meta.url.endsWith(".ts") ? "./sqlite-worker.ts" : "./sqlite-worker.js", import.meta.url);
|
|
22
|
+
const child = fork(workerUrl, [], {
|
|
23
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"], serialization: "json",
|
|
24
|
+
execArgv: process.execArgv.filter((arg) => !arg.startsWith("--watch")),
|
|
25
|
+
});
|
|
26
|
+
running++;
|
|
27
|
+
// Consume experimental-runtime warnings without leaking paths into the UI.
|
|
28
|
+
child.stderr?.resume();
|
|
29
|
+
let settled = false;
|
|
30
|
+
const finish = (error, data) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
signal?.removeEventListener("abort", abort);
|
|
36
|
+
child.kill("SIGKILL");
|
|
37
|
+
if (error)
|
|
38
|
+
reject(error);
|
|
39
|
+
else
|
|
40
|
+
resolve(data);
|
|
41
|
+
};
|
|
42
|
+
const abort = () => finish(new Error("Request cancelled"));
|
|
43
|
+
const timer = setTimeout(() => finish(new Error("Database query timed out")), 8000);
|
|
44
|
+
child.once("exit", () => { running--; finish(new Error("Database reader stopped unexpectedly")); });
|
|
45
|
+
child.once("error", (error) => finish(error));
|
|
46
|
+
child.once("message", (message) => {
|
|
47
|
+
if (message.data)
|
|
48
|
+
finish(undefined, message.data);
|
|
49
|
+
else
|
|
50
|
+
finish(new Error(message.error || "Unable to read database"));
|
|
51
|
+
});
|
|
52
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
53
|
+
if (signal?.aborted)
|
|
54
|
+
abort();
|
|
55
|
+
else
|
|
56
|
+
child.send({ absolute, table: request.table, offset: request.offset });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
3
|
+
const PAGE_SIZE = 50;
|
|
4
|
+
const MAX_COLUMNS = 64;
|
|
5
|
+
const MAX_CELL_TEXT = 256;
|
|
6
|
+
function identifier(name) { return '"' + name.replace(/"/g, '""') + '"'; }
|
|
7
|
+
/** Only fixed, parameterized read queries; no user SQL or extension loading. */
|
|
8
|
+
export function querySqlite(absolute, table, offset) {
|
|
9
|
+
const fd = openSync(absolute, "r");
|
|
10
|
+
const header = Buffer.alloc(16);
|
|
11
|
+
try {
|
|
12
|
+
readSync(fd, header, 0, 16, 0);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
closeSync(fd);
|
|
16
|
+
}
|
|
17
|
+
if (header.toString("utf8") !== "SQLite format 3\0")
|
|
18
|
+
throw new Error("Not a SQLite database (encrypted databases are not supported)");
|
|
19
|
+
const db = new DatabaseSync(absolute, { readOnly: true, allowExtension: false, timeout: 200 });
|
|
20
|
+
try {
|
|
21
|
+
db.exec("PRAGMA query_only=ON; PRAGMA trusted_schema=OFF; PRAGMA temp_store=MEMORY; BEGIN");
|
|
22
|
+
const allTables = db.prepare("SELECT name, type, sql FROM sqlite_schema WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' ORDER BY name LIMIT 501").all();
|
|
23
|
+
const tables = allTables.slice(0, 500).map((row) => ({ name: String(row.name), type: row.type === "view" ? "view" : "table" }));
|
|
24
|
+
const chosen = table === undefined ? tables[0]?.name : table;
|
|
25
|
+
const selected = allTables.find((row) => row.name === chosen);
|
|
26
|
+
const base = { tables, tablesTruncated: allTables.length > 500, offset, pageSize: PAGE_SIZE };
|
|
27
|
+
if (chosen === undefined)
|
|
28
|
+
return { ...base, table: null, schema: "", columns: [], columnsTruncated: false, rows: [], hasMore: false };
|
|
29
|
+
if (!selected || !tables.some((entry) => entry.name === chosen))
|
|
30
|
+
throw new Error("Table or view no longer exists");
|
|
31
|
+
try {
|
|
32
|
+
const info = db.prepare("SELECT name, type, pk FROM pragma_table_xinfo(?) WHERE hidden != 1 ORDER BY cid LIMIT 65").all(chosen);
|
|
33
|
+
const columns = info.slice(0, MAX_COLUMNS).map((column) => ({ name: String(column.name), type: String(column.type || ""), primaryKey: Number(column.pk) }));
|
|
34
|
+
if (!columns.length)
|
|
35
|
+
throw new Error("No readable columns");
|
|
36
|
+
const projection = columns.flatMap((column, index) => {
|
|
37
|
+
const name = identifier(column.name);
|
|
38
|
+
return [
|
|
39
|
+
`CASE typeof(${name}) WHEN 'blob' THEN NULL WHEN 'text' THEN substr(${name},1,${MAX_CELL_TEXT + 1}) ELSE ${name} END AS v${index}`,
|
|
40
|
+
`typeof(${name}) AS t${index}`,
|
|
41
|
+
`CASE WHEN typeof(${name})='blob' THEN length(${name}) END AS b${index}`,
|
|
42
|
+
];
|
|
43
|
+
}).join(",");
|
|
44
|
+
const keys = columns.filter((column) => column.primaryKey).sort((a, b) => a.primaryKey - b.primaryKey);
|
|
45
|
+
const order = keys.length ? " ORDER BY " + keys.map((column) => identifier(column.name)).join(",") : "";
|
|
46
|
+
const statement = db.prepare(`SELECT ${projection} FROM ${identifier(chosen)}${order} LIMIT ? OFFSET ?`);
|
|
47
|
+
statement.setReadBigInts(true);
|
|
48
|
+
const records = statement.all(PAGE_SIZE + 1, offset);
|
|
49
|
+
const rows = records.slice(0, PAGE_SIZE).map((record) => columns.map((_, index) => {
|
|
50
|
+
const kind = record[`t${index}`];
|
|
51
|
+
if (kind === "null")
|
|
52
|
+
return { kind: "null", value: "" };
|
|
53
|
+
if (kind === "blob")
|
|
54
|
+
return { kind: "blob", value: String(record[`b${index}`] ?? 0) };
|
|
55
|
+
const value = String(record[`v${index}`] ?? "");
|
|
56
|
+
return { kind: kind === "integer" || kind === "real" ? "number" : "text", value: value.slice(0, MAX_CELL_TEXT), truncated: value.length > MAX_CELL_TEXT };
|
|
57
|
+
}));
|
|
58
|
+
return { ...base, table: chosen, schema: String(selected.sql ?? "").slice(0, 16384), columns, columnsTruncated: info.length > MAX_COLUMNS, rows, hasMore: records.length > PAGE_SIZE };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return { ...base, table: chosen, schema: String(selected.sql ?? "").slice(0, 16384), columns: [], columnsTruncated: false, rows: [], hasMore: false, queryError: error.message };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
db.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { querySqlite } from "./sqlite-query.js";
|
|
2
|
+
process.once("message", (request) => {
|
|
3
|
+
try {
|
|
4
|
+
process.send?.({ data: querySqlite(request.absolute, request.table, request.offset) });
|
|
5
|
+
}
|
|
6
|
+
catch (error) {
|
|
7
|
+
process.send?.({ error: error.message });
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
process.disconnect();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { promptView, estimatePromptTokens } from "./system-prompt-view.js";
|
|
5
|
+
import { reloadSession } from "./session-reload.js";
|
|
6
|
+
const LIMIT = 512 * 1024;
|
|
7
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
8
|
+
/** Include the canonical target in CAS, so changing a symlink cannot redirect an outstanding edit. */
|
|
9
|
+
function read(path) {
|
|
10
|
+
if (!existsSync(path)) {
|
|
11
|
+
if (lstatMissing(path))
|
|
12
|
+
return { content: "", version: hash(`missing:${canonicalParent(path)}`), exists: false };
|
|
13
|
+
throw Error("Broken symlink");
|
|
14
|
+
}
|
|
15
|
+
const target = realpathSync(path), info = statSync(target);
|
|
16
|
+
if (!info.isFile() || info.size > LIMIT)
|
|
17
|
+
throw Error("Prompt file must be text and at most 512 KB");
|
|
18
|
+
const data = readFileSync(target);
|
|
19
|
+
const content = new TextDecoder("utf-8", { fatal: true }).decode(data);
|
|
20
|
+
return { content, version: hash(Buffer.concat([Buffer.from(`${target}\0`), data])), exists: true };
|
|
21
|
+
}
|
|
22
|
+
function lstatMissing(path) { try {
|
|
23
|
+
lstatSync(path);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
if (e.code === "ENOENT")
|
|
28
|
+
return true;
|
|
29
|
+
throw e;
|
|
30
|
+
} }
|
|
31
|
+
function canonicalParent(path) { let parent = dirname(path), suffix = path.slice(parent.length); while (!existsSync(parent)) {
|
|
32
|
+
const previous = parent;
|
|
33
|
+
parent = dirname(parent);
|
|
34
|
+
suffix = previous.slice(parent.length) + suffix;
|
|
35
|
+
} return realpathSync(parent) + suffix; }
|
|
36
|
+
function catalog(session, cwd, agentDir) {
|
|
37
|
+
const loader = session.resourceLoader, files = [];
|
|
38
|
+
const systemPath = loader.getSystemPromptSource()?.path, appendPaths = loader.getAppendSystemPromptSources().map(f => resolve(f.path));
|
|
39
|
+
for (const scope of ["user", "project"])
|
|
40
|
+
for (const kind of ["system", "append"]) {
|
|
41
|
+
const path = join(scope === "user" ? agentDir : join(cwd, ".pi"), kind === "system" ? "SYSTEM.md" : "APPEND_SYSTEM.md");
|
|
42
|
+
files.push({ id: `${kind}:${scope}`, path, kind, scope, active: kind === "system" ? !!systemPath && resolve(systemPath) === resolve(path) : appendPaths.includes(resolve(path)), editable: scope === "user" || session.settingsManager.isProjectTrusted(), tokens: 0, content: "", version: "", exists: false });
|
|
43
|
+
}
|
|
44
|
+
for (const file of loader.getAgentsFiles().agentsFiles)
|
|
45
|
+
files.push({ id: `context:${hash(resolve(file.path))}`, path: resolve(file.path), kind: "context", scope: "context", active: true, editable: true, tokens: 0, content: "", version: "", exists: true });
|
|
46
|
+
if (session.settingsManager.isProjectTrusted() && !files.some(file => file.kind === "context") && !existsSync(join(cwd, "AGENTS.md")))
|
|
47
|
+
files.push({ id: "context:new", path: join(cwd, "AGENTS.md"), kind: "context", scope: "context", active: false, editable: true, tokens: 0, content: "", version: "", exists: false });
|
|
48
|
+
for (const file of files)
|
|
49
|
+
try {
|
|
50
|
+
Object.assign(file, read(file.path));
|
|
51
|
+
const loaded = file.kind === "context" ? loader.getAgentsFiles().agentsFiles.find(f => resolve(f.path) === file.path)?.content : file.active ? (file.kind === "system" ? loader.getSystemPrompt() : loader.getAppendSystemPrompt().join("\n\n")) : undefined;
|
|
52
|
+
file.tokens = estimatePromptTokens(loaded ?? file.content);
|
|
53
|
+
file.changedOnDisk = loaded !== undefined && loaded !== file.content;
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
file.editable = false;
|
|
57
|
+
file.error = e.message;
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
61
|
+
export function getSystemPromptState(session, cwd, agentDir) {
|
|
62
|
+
const view = promptView(session, cwd), definitions = session.getAllTools();
|
|
63
|
+
const files = catalog(session, cwd, agentDir);
|
|
64
|
+
if (view.opaque)
|
|
65
|
+
for (const file of files)
|
|
66
|
+
file.editable = false;
|
|
67
|
+
const options = view.options;
|
|
68
|
+
const toolNames = options?.selectedTools ?? session.getActiveToolNames();
|
|
69
|
+
return { raw: view.raw, tokens: estimatePromptTokens(view.raw), defaultPreamble: view.defaultPreamble, sections: view.sections, rules: view.rules, opaque: view.opaque, forced: view.forced,
|
|
70
|
+
custom: !!options?.customPrompt, tools: toolNames.map(name => { const tool = definitions.find(t => t.name === name); return { name, custom: !!tool?.sourceInfo && !tool.sourceInfo.path.startsWith("builtin:") && !/^<inline:(codemode|mcp|tool-search)>$/.test(tool.sourceInfo.path), path: tool?.sourceInfo.path }; }), files, trusted: session.settingsManager.isProjectTrusted(), ...promptReloadStatus(session), busy: !session.isIdle };
|
|
71
|
+
}
|
|
72
|
+
export function writeSystemPromptFile(session, cwd, agentDir, id, version, content, restore = false) {
|
|
73
|
+
const state = getSystemPromptState(session, cwd, agentDir), file = state.files.find(f => f.id === id);
|
|
74
|
+
if (!file?.editable || state.opaque)
|
|
75
|
+
throw Error("This prompt source is read-only");
|
|
76
|
+
if (file.version !== version)
|
|
77
|
+
throw Error("File changed externally; reopen it before saving");
|
|
78
|
+
if (restore) {
|
|
79
|
+
if (file.kind !== "system" || !file.exists)
|
|
80
|
+
throw Error("Only an existing SYSTEM.md can be restored");
|
|
81
|
+
unlinkSync(file.path);
|
|
82
|
+
return file.path;
|
|
83
|
+
}
|
|
84
|
+
if (typeof content !== "string" || Buffer.byteLength(content) > LIMIT)
|
|
85
|
+
throw Error("Prompt text exceeds 512 KB");
|
|
86
|
+
const target = file.exists ? realpathSync(file.path) : canonicalParent(file.path);
|
|
87
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
88
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(temp, content, { flag: "wx", mode: file.exists ? statSync(target).mode : 0o600 });
|
|
91
|
+
renameSync(temp, target);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
if (existsSync(temp))
|
|
95
|
+
unlinkSync(temp);
|
|
96
|
+
}
|
|
97
|
+
return file.path;
|
|
98
|
+
}
|
|
99
|
+
export function promptUsesFile(session, cwd, agentDir, path) {
|
|
100
|
+
const identity = (file) => { try {
|
|
101
|
+
return realpathSync(file);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return resolve(file);
|
|
105
|
+
} };
|
|
106
|
+
const target = identity(path);
|
|
107
|
+
return session.settingsManager.isProjectTrusted() && resolve(path) === join(cwd, "AGENTS.md") || catalog(session, cwd, agentDir).some(file => resolve(file.path) === resolve(path) || identity(file.path) === target);
|
|
108
|
+
}
|
|
109
|
+
const reloads = new WeakMap();
|
|
110
|
+
export function promptReloadStatus(session) {
|
|
111
|
+
const state = reloads.get(session);
|
|
112
|
+
return { pending: !!state && (state.dirty || !!state.promise), ...(state?.error ? { reloadError: state.error } : {}) };
|
|
113
|
+
}
|
|
114
|
+
export async function flushPromptReload(session, retry = false) {
|
|
115
|
+
const state = reloads.get(session);
|
|
116
|
+
if (!state)
|
|
117
|
+
return;
|
|
118
|
+
if (state.promise)
|
|
119
|
+
return state.promise;
|
|
120
|
+
if (!state.dirty || !session.isIdle || (state.error && !retry))
|
|
121
|
+
return;
|
|
122
|
+
state.error = undefined;
|
|
123
|
+
state.dirty = false;
|
|
124
|
+
state.promise = (async () => {
|
|
125
|
+
try {
|
|
126
|
+
await reloadSession(session);
|
|
127
|
+
state.refreshed();
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
state.error = e.message;
|
|
131
|
+
state.dirty = true;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
state.promise = undefined;
|
|
135
|
+
if (!state.dirty) {
|
|
136
|
+
state.unsubscribe?.();
|
|
137
|
+
reloads.delete(session);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
await state.promise;
|
|
142
|
+
if (state.dirty && !state.error && session.isIdle)
|
|
143
|
+
await flushPromptReload(session);
|
|
144
|
+
}
|
|
145
|
+
export async function queuePromptReload(session, refreshed) {
|
|
146
|
+
let state = reloads.get(session);
|
|
147
|
+
if (!state) {
|
|
148
|
+
state = { dirty: true, refreshed };
|
|
149
|
+
reloads.set(session, state);
|
|
150
|
+
state.unsubscribe = session.subscribe((event) => {
|
|
151
|
+
if (event.type === "agent_settled")
|
|
152
|
+
void flushPromptReload(session);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
state.dirty = true;
|
|
156
|
+
state.error = undefined;
|
|
157
|
+
await flushPromptReload(session);
|
|
158
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** File changes stay outside agent messages and use the native resource reload. */
|
|
2
|
+
export function installSystemPromptRoutes(app, service, originAllowed) {
|
|
3
|
+
app.post("/api/system-prompt", async (req, res) => {
|
|
4
|
+
res.setHeader("Cache-Control", "no-store");
|
|
5
|
+
if (!originAllowed(req) || !req.is("application/json")) {
|
|
6
|
+
res.status(403).json({ error: "Forbidden" });
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { clientId, cwd, conversationId, action, id, version, content } = req.body ?? {};
|
|
10
|
+
const cs = typeof clientId === "string" ? service().get(clientId) : undefined;
|
|
11
|
+
if (!cs || cs.switchingWorkspace || cs.cwd !== cwd || cs.conversationId !== conversationId) {
|
|
12
|
+
res.status(409).json({ error: "Conversation unavailable" });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const session = cs.session;
|
|
16
|
+
const valid = () => cs.cwd === cwd && cs.session === session && !cs.switchingWorkspace;
|
|
17
|
+
try {
|
|
18
|
+
if (!["get", "save", "restore", "retry"].includes(action))
|
|
19
|
+
throw Error("Invalid prompt action");
|
|
20
|
+
if (action !== "get" && service().quiesceInfo().quiesced)
|
|
21
|
+
throw Error("Service draining");
|
|
22
|
+
if (action === "save" || action === "restore") {
|
|
23
|
+
if (typeof id !== "string" || typeof version !== "string")
|
|
24
|
+
throw Error("Invalid file request");
|
|
25
|
+
await cs.savePromptFile(id, version, content, action === "restore");
|
|
26
|
+
}
|
|
27
|
+
if (action === "retry")
|
|
28
|
+
await cs.retryPromptReload();
|
|
29
|
+
if (!valid())
|
|
30
|
+
throw Error("Conversation changed");
|
|
31
|
+
const state = await cs.systemPromptState();
|
|
32
|
+
if (!valid())
|
|
33
|
+
throw Error("Conversation changed");
|
|
34
|
+
res.json(state);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
res.status(400).json({ error: error.message });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|