@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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin / Host admission policy for HTTP and WebSocket requests.
|
|
3
|
+
*
|
|
4
|
+
* Pure (no env, no I/O) so it can be unit-tested; server/index.ts builds one
|
|
5
|
+
* instance from the environment.
|
|
6
|
+
*/
|
|
7
|
+
const LOOPBACK_BIND_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
8
|
+
/** URL#hostname spelling of the loopback names a local browser can use. */
|
|
9
|
+
const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "[::1]"];
|
|
10
|
+
/** "host" or "host:port" → { hostname, port }. */
|
|
11
|
+
export function parseAuthority(a) {
|
|
12
|
+
try {
|
|
13
|
+
const u = new URL(`http://${a}`);
|
|
14
|
+
return { hostname: u.hostname.toLowerCase(), port: u.port || "80" };
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return { hostname: "", port: "" };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Effective Host allowlist. An explicit list always wins. When the server is
|
|
22
|
+
* bound to loopback only, default to loopback hostnames: otherwise a DNS
|
|
23
|
+
* rebinding page (evil.com → 127.0.0.1) passes the same-authority Origin/Host
|
|
24
|
+
* check and gets full control of the agent. Non-loopback binds (LAN, Docker,
|
|
25
|
+
* reverse proxy) have no safe default and keep the old behavior.
|
|
26
|
+
*/
|
|
27
|
+
export function effectiveAllowHosts(bindHost, allowHosts) {
|
|
28
|
+
if (allowHosts.length > 0)
|
|
29
|
+
return allowHosts;
|
|
30
|
+
return LOOPBACK_BIND_HOSTS.has(bindHost.toLowerCase()) ? LOOPBACK_HOSTNAMES : [];
|
|
31
|
+
}
|
|
32
|
+
export function createOriginPolicy(options) {
|
|
33
|
+
const allowHosts = effectiveAllowHosts(options.bindHost, options.allowHosts);
|
|
34
|
+
return (headers) => {
|
|
35
|
+
const host = parseAuthority((headers.host ?? "").toLowerCase());
|
|
36
|
+
if (allowHosts.length > 0 && !allowHosts.includes(host.hostname))
|
|
37
|
+
return false;
|
|
38
|
+
const origin = headers.origin;
|
|
39
|
+
if (!origin)
|
|
40
|
+
return true; // non-browser client
|
|
41
|
+
const o = origin.toLowerCase();
|
|
42
|
+
if (options.allowOrigins.includes(o))
|
|
43
|
+
return true;
|
|
44
|
+
if (o === "null")
|
|
45
|
+
return false; // file:// pages etc. are not trusted
|
|
46
|
+
const ori = parseAuthority(o.replace(/^[a-z]+:\/\//, ""));
|
|
47
|
+
// Same host:port only; other ports on the same host are different origins.
|
|
48
|
+
return ori.hostname === host.hostname && ori.port === host.port;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-pty compatibility patches for Node `--watch` and Windows ConPTY.
|
|
3
|
+
*
|
|
4
|
+
* Node's `--watch` mode (the dev script runs `node --watch --import tsx`)
|
|
5
|
+
* pushes `watch:require` / `watch:import` messages over the IPC channels of
|
|
6
|
+
* spawned worker threads AND forked child processes to track module
|
|
7
|
+
* dependencies. node-pty 1.1.0 does not recognize these and mishandles them in
|
|
8
|
+
* two places:
|
|
9
|
+
*
|
|
10
|
+
* · windowsConoutConnection.js — logs every unknown worker message via
|
|
11
|
+
* `console.warn`, producing hundreds of "Unexpected ConoutWorkerMessage"
|
|
12
|
+
* lines per terminal (the SCM panel's hidden query PTY triggers it on every
|
|
13
|
+
* git-view open).
|
|
14
|
+
* · windowsPtyAgent.js — the kill path's `_getConsoleProcessList()` treats ANY
|
|
15
|
+
* message from the forked console-list agent as its reply; a watch message
|
|
16
|
+
* has no `consoleProcessList` property, so `message.consoleProcessList` is
|
|
17
|
+
* undefined and the `.forEach` on it crashes the server.
|
|
18
|
+
*
|
|
19
|
+
* Both are benign protocol noise: the ConPTY worker only ever sends its READY
|
|
20
|
+
* sentinel and the agent only sends `{ consoleProcessList }` — both of which
|
|
21
|
+
* node-pty handles. These patches make node-pty ignore unrelated IPC traffic.
|
|
22
|
+
* The console-list helper also needs to report an empty list when a shell has
|
|
23
|
+
* already exited: node-pty 1.1.0 lets AttachConsole throw at module scope,
|
|
24
|
+
* producing an uncaught stack trace and making its parent wait five seconds.
|
|
25
|
+
*
|
|
26
|
+
* IMPORTANT: this module MUST be imported before node-pty (see terminals.ts) —
|
|
27
|
+
* it rewrites the installed copies on disk so the subsequently-compiled
|
|
28
|
+
* node-pty modules contain the fixed handlers. Best-effort and idempotent
|
|
29
|
+
* (checks before rewriting), mirroring the spawn-helper chmod self-heal; a
|
|
30
|
+
* read-only node_modules or a different node-pty version just skips the patch
|
|
31
|
+
* (the runtime console.warn filter in terminals.ts still guards the flood).
|
|
32
|
+
*/
|
|
33
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
34
|
+
import { createRequire } from "node:module";
|
|
35
|
+
import { dirname, join } from "node:path";
|
|
36
|
+
const require = createRequire(import.meta.url);
|
|
37
|
+
/** Absolute path of the installed node-pty package root, or null. */
|
|
38
|
+
function ptyPackageDir() {
|
|
39
|
+
try {
|
|
40
|
+
// require.resolve("node-pty") → <pkg>/lib/index.js → package root is two up.
|
|
41
|
+
return dirname(dirname(require.resolve("node-pty")));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Idempotently replace one code fragment in a node-pty source file. */
|
|
48
|
+
function patchFile(pkgDir, rel, find, replace) {
|
|
49
|
+
try {
|
|
50
|
+
const file = join(pkgDir, rel);
|
|
51
|
+
if (!existsSync(file))
|
|
52
|
+
return;
|
|
53
|
+
const src = readFileSync(file, "utf8");
|
|
54
|
+
if (src.includes(replace))
|
|
55
|
+
return; // already patched
|
|
56
|
+
if (!src.includes(find))
|
|
57
|
+
return; // unexpected content — leave alone
|
|
58
|
+
writeFileSync(file, src.replace(find, replace), "utf8");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// best-effort — the runtime console.warn filter still guards the flood
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function applyPatches() {
|
|
65
|
+
const pkgDir = ptyPackageDir();
|
|
66
|
+
if (!pkgDir)
|
|
67
|
+
return;
|
|
68
|
+
// 1. ConoutConnection: ignore unknown worker messages instead of warning.
|
|
69
|
+
patchFile(pkgDir, "lib/windowsConoutConnection.js", "console.warn('Unexpected ConoutWorkerMessage', message);", "break; // pi-harness: ignore Node --watch worker messages");
|
|
70
|
+
// 2. windowsPtyAgent kill path: only accept a real console-list reply
|
|
71
|
+
// (watch messages arrive first on the fork channel and carry no
|
|
72
|
+
// consoleProcessList — previously resolved undefined and crashed).
|
|
73
|
+
patchFile(pkgDir, "lib/windowsPtyAgent.js", "agent.on('message', function (message) {\n clearTimeout(timeout);\n resolve(message.consoleProcessList);\n });", [
|
|
74
|
+
" agent.on('message', function (message) {",
|
|
75
|
+
" // pi-harness: ignore Node --watch messages on the fork channel",
|
|
76
|
+
" if (message && Array.isArray(message.consoleProcessList)) {",
|
|
77
|
+
" clearTimeout(timeout);",
|
|
78
|
+
" resolve(message.consoleProcessList);",
|
|
79
|
+
" }",
|
|
80
|
+
" });",
|
|
81
|
+
].join("\n"));
|
|
82
|
+
// 3. A shell may exit before the console-list helper attaches. Always reply
|
|
83
|
+
// to the parent so a normal terminal close cannot print an uncaught error
|
|
84
|
+
// or hold its cleanup path for the full five-second fallback timeout.
|
|
85
|
+
patchFile(pkgDir, "lib/conpty_console_list_agent.js", "var consoleProcessList = getConsoleProcessList(shellPid);\nprocess.send({ consoleProcessList: consoleProcessList });", [
|
|
86
|
+
"try {",
|
|
87
|
+
" var consoleProcessList = getConsoleProcessList(shellPid);",
|
|
88
|
+
" process.send({ consoleProcessList: consoleProcessList });",
|
|
89
|
+
"} catch (error) {",
|
|
90
|
+
" if (!error || error.message !== 'AttachConsole failed') throw error;",
|
|
91
|
+
" // pi-harness: the console may already be gone during ConPTY teardown",
|
|
92
|
+
" process.send({ consoleProcessList: [] });",
|
|
93
|
+
"}",
|
|
94
|
+
].join("\n"));
|
|
95
|
+
}
|
|
96
|
+
applyPatches();
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { appVersion } from "./app-version.js";
|
|
5
|
+
/**
|
|
6
|
+
* Whether the pi agent config looks ready: the agent dir exists and
|
|
7
|
+
* auth.json has at least one provider credential. Cached for 2s.
|
|
8
|
+
*/
|
|
9
|
+
export class PiConfigProbe {
|
|
10
|
+
agentDir;
|
|
11
|
+
cache = null;
|
|
12
|
+
constructor(agentDir) {
|
|
13
|
+
this.agentDir = agentDir;
|
|
14
|
+
}
|
|
15
|
+
isConfigured() {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
const cached = this.cache;
|
|
18
|
+
if (cached && now - cached.at < 2000)
|
|
19
|
+
return cached.configured;
|
|
20
|
+
let configured = false;
|
|
21
|
+
try {
|
|
22
|
+
const authPath = join(this.agentDir, "auth.json");
|
|
23
|
+
if (existsSync(authPath)) {
|
|
24
|
+
const data = JSON.parse(readFileSync(authPath, "utf8"));
|
|
25
|
+
configured =
|
|
26
|
+
typeof data === "object" &&
|
|
27
|
+
data !== null &&
|
|
28
|
+
Object.keys(data).length > 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
configured = false;
|
|
33
|
+
}
|
|
34
|
+
this.cache = { at: now, configured };
|
|
35
|
+
return configured;
|
|
36
|
+
}
|
|
37
|
+
invalidate() {
|
|
38
|
+
this.cache = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether the pi CLI binary is installed and runnable (`pi --version`
|
|
43
|
+
* probe). Cached machine-wide (same binary for every client) for 10s —
|
|
44
|
+
* the check is only rerun after install or when the cache expires.
|
|
45
|
+
*/
|
|
46
|
+
let piCliProbe = null;
|
|
47
|
+
const PI_CLI_PROBE_TTL_MS = 10_000;
|
|
48
|
+
export function isPiCliInstalled() {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const cached = piCliProbe;
|
|
51
|
+
if (cached && now - cached.at < PI_CLI_PROBE_TTL_MS)
|
|
52
|
+
return cached.installed;
|
|
53
|
+
let installed = false;
|
|
54
|
+
try {
|
|
55
|
+
const res = spawnSync("pi", ["--version"], {
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
stdio: "ignore",
|
|
58
|
+
// Windows: `pi` resolves to a pi.cmd shim — spawnSync can only
|
|
59
|
+
// exec those through a shell (else ENOENT).
|
|
60
|
+
shell: process.platform === "win32",
|
|
61
|
+
});
|
|
62
|
+
installed = !res.error && res.status === 0;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
installed = false;
|
|
66
|
+
}
|
|
67
|
+
piCliProbe = { at: now, installed };
|
|
68
|
+
return installed;
|
|
69
|
+
}
|
|
70
|
+
function invalidatePiCliProbe() {
|
|
71
|
+
piCliProbe = null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Run a command async, collecting stdout+stderr; kills on timeout.
|
|
75
|
+
* Never throws / never crashes the server: spawn errors (ENOENT etc.)
|
|
76
|
+
* resolve with code -1 so callers can report them as notices.
|
|
77
|
+
*/
|
|
78
|
+
function runAsync(cmd, args, timeoutMs, cwd) {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
let p;
|
|
81
|
+
try {
|
|
82
|
+
p = spawn(cmd, args, {
|
|
83
|
+
...(cwd ? { cwd } : {}),
|
|
84
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
85
|
+
// Windows: npm and friends are .cmd shims — Node can only exec
|
|
86
|
+
// them through the shell (otherwise spawn npm → ENOENT).
|
|
87
|
+
shell: process.platform === "win32",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
resolve({ code: -1, out: String(err) });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
let out = "";
|
|
95
|
+
let settled = false;
|
|
96
|
+
const done = (code, text) => {
|
|
97
|
+
if (settled)
|
|
98
|
+
return;
|
|
99
|
+
settled = true;
|
|
100
|
+
clearTimeout(t);
|
|
101
|
+
resolve({ code, out: text ?? out });
|
|
102
|
+
};
|
|
103
|
+
const t = setTimeout(() => p.kill(), timeoutMs);
|
|
104
|
+
p.stdout?.on("data", (d) => (out += d.toString()));
|
|
105
|
+
p.stderr?.on("data", (d) => (out += d.toString()));
|
|
106
|
+
p.on("error", (err) => done(-1, String(err)));
|
|
107
|
+
p.on("close", (code) => done(code));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Simple numeric semver compare: >0 means a newer than b. */
|
|
111
|
+
export function compareVersions(a, b) {
|
|
112
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
113
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
const x = pa[i] ?? 0;
|
|
116
|
+
const y = pb[i] ?? 0;
|
|
117
|
+
if (x !== y)
|
|
118
|
+
return x - y;
|
|
119
|
+
}
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
/** Ask the npm registry for the latest pi-harness version and report it. */
|
|
123
|
+
export async function checkUpdate(emit) {
|
|
124
|
+
const current = appVersion();
|
|
125
|
+
try {
|
|
126
|
+
// Fetch the full package doc (not /latest): it carries the per-version
|
|
127
|
+
// publish timestamps so the UI can hint when a version was JUST
|
|
128
|
+
// published and the registry/CDN caches may not have caught up yet.
|
|
129
|
+
const res = await fetch("https://registry.npmjs.org/@youweichen%2fpi-harness", {
|
|
130
|
+
signal: AbortSignal.timeout(8_000),
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok)
|
|
133
|
+
throw new Error(`HTTP ${res.status}`);
|
|
134
|
+
const data = (await res.json());
|
|
135
|
+
const latest = data["dist-tags"]?.latest ?? null;
|
|
136
|
+
const latestPublishedAt = latest && data.time ? (data.time[latest] ?? null) : null;
|
|
137
|
+
const upToDate = latest === null || compareVersions(current, latest) >= 0;
|
|
138
|
+
emit({
|
|
139
|
+
type: "update_status",
|
|
140
|
+
current,
|
|
141
|
+
latest,
|
|
142
|
+
latestPublishedAt,
|
|
143
|
+
upToDate,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
emit({
|
|
148
|
+
type: "update_status",
|
|
149
|
+
current,
|
|
150
|
+
latest: null,
|
|
151
|
+
latestPublishedAt: null,
|
|
152
|
+
upToDate: false,
|
|
153
|
+
error: `检查更新失败:${err.message}`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Auto-install the pi agent: ensure the config dir exists and install the
|
|
159
|
+
* pi CLI globally (npm i -g). Auth is configured afterwards via the API key
|
|
160
|
+
* form or by running `pi` in a terminal. Callers should re-send a snapshot
|
|
161
|
+
* afterwards so the UI re-probes the CLI.
|
|
162
|
+
*/
|
|
163
|
+
export async function installPiAgent(agentDir, emit) {
|
|
164
|
+
try {
|
|
165
|
+
mkdirSync(agentDir, { recursive: true });
|
|
166
|
+
emit({
|
|
167
|
+
type: "notice",
|
|
168
|
+
level: "info",
|
|
169
|
+
text: "正在安装 pi agent CLI(npm i -g @earendil-works/pi-coding-agent)…",
|
|
170
|
+
});
|
|
171
|
+
const { code, out } = await runAsync("npm", ["i", "-g", "@earendil-works/pi-coding-agent"], 180_000);
|
|
172
|
+
if (code === 0) {
|
|
173
|
+
emit({
|
|
174
|
+
type: "notice",
|
|
175
|
+
level: "info",
|
|
176
|
+
text: "✅ pi agent CLI 安装完成。填入 API 密钥即可开始,或在终端运行 pi 完成登录。",
|
|
177
|
+
});
|
|
178
|
+
emit({ type: "install_result", ok: true, detail: "" });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
emit({
|
|
182
|
+
type: "notice",
|
|
183
|
+
level: "error",
|
|
184
|
+
text: `pi agent 安装失败(${code ?? "timeout"}):${out.slice(0, 400)}`,
|
|
185
|
+
});
|
|
186
|
+
emit({
|
|
187
|
+
type: "install_result",
|
|
188
|
+
ok: false,
|
|
189
|
+
detail: out.slice(0, 600),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
emit({
|
|
195
|
+
type: "notice",
|
|
196
|
+
level: "error",
|
|
197
|
+
text: `pi agent 安装失败:${err.message}`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// The CLI may just have landed on PATH (or the install may have failed) —
|
|
201
|
+
// drop the probe cache so the next snapshot re-checks.
|
|
202
|
+
invalidatePiCliProbe();
|
|
203
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { editableState, latestPlan, snapshotFromDetails, transition } from "./state.js";
|
|
3
|
+
const string = (maxLength) => Type.String({ minLength: 1, maxLength });
|
|
4
|
+
export const planParameters = Type.Object({
|
|
5
|
+
action: Type.Union([Type.Literal("create"), Type.Literal("update")]),
|
|
6
|
+
planId: Type.Optional(string(128)), expectedRevision: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
7
|
+
title: string(80), status: Type.Union([Type.Literal("active"), Type.Literal("completed"), Type.Literal("cancelled"), Type.Literal("failed")]),
|
|
8
|
+
steps: Type.Array(Type.Object({ id: string(64), title: string(80), detail: Type.Optional(string(500)) }), { minItems: 1, maxItems: 16, description: "Required for BOTH create and update, including completion. Send every step with its id, title and detail; never omit unchanged steps." }),
|
|
9
|
+
currentStepId: Type.Union([string(64), Type.Null()]), completedStepIds: Type.Array(string(64), { maxItems: 16, uniqueItems: true }),
|
|
10
|
+
completionCriteria: string(240), changeSummary: Type.Optional(string(240)),
|
|
11
|
+
});
|
|
12
|
+
export function restorePlanContext(messages, branch) {
|
|
13
|
+
const latest = latestPlan(branch);
|
|
14
|
+
if (!latest || latest.snapshot.status !== "active")
|
|
15
|
+
return messages;
|
|
16
|
+
const { snapshot, toolCallId } = latest;
|
|
17
|
+
const visibleResult = messages.some(message => message.role === "toolResult" && message.toolName === "plan" && message.toolCallId === toolCallId && !message.isError && JSON.stringify(snapshotFromDetails(message.details)) === JSON.stringify(snapshot));
|
|
18
|
+
const visibleCall = messages.some(message => message.role === "assistant" && message.content.some(block => {
|
|
19
|
+
if (block.type !== "toolCall" || block.name !== "plan" || block.id !== toolCallId)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const args = block.arguments;
|
|
23
|
+
const identity = args.action === "create" ? snapshot.revision === 1 : args.action === "update" && args.planId === snapshot.planId && args.expectedRevision === snapshot.revision - 1;
|
|
24
|
+
return identity && JSON.stringify(editableState(args)) === JSON.stringify(editableState(snapshot));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
const lastUser = messages.findLastIndex(message => message.role === "user");
|
|
31
|
+
const lastPlanResult = messages.slice(lastUser + 1).findLast(message => message.role === "toolResult" && message.toolName === "plan");
|
|
32
|
+
const failedCall = lastPlanResult?.role === "toolResult" && lastPlanResult.isError;
|
|
33
|
+
if (visibleResult && visibleCall && !failedCall)
|
|
34
|
+
return messages;
|
|
35
|
+
const content = `${failedCall ? "Plan call failed; the saved plan below is unchanged. To correct the call, submit the full state including steps, even if unchanged or marking completion. Use the saved planId and expectedRevision. This reminder does not authorize continuing work.\n" : ""}Historical plan background, not authorization to continue. Judge relevance to the current request yourself. For related implementation, first update this plan with its current identity and revision. For unrelated multi-step work, create a new plan. Ordinary questions do not require a plan. Follow user/skill instructions and waiting requirements; this tool grants no extra permissions.\n${JSON.stringify({ action: "update", planId: snapshot.planId, expectedRevision: snapshot.revision, ...editableState(snapshot) })}`;
|
|
36
|
+
const reminder = { role: "custom", customType: "pi-harness:plan-background", content, display: false, timestamp: 0 };
|
|
37
|
+
const summary = messages.findLastIndex(message => message.role === "compactionSummary");
|
|
38
|
+
const user = messages.findLastIndex(message => message.role === "user");
|
|
39
|
+
let at = summary >= 0 ? summary + 1 : user >= 0 ? user : 0;
|
|
40
|
+
// A user interruption may occur inside an outstanding call/result group.
|
|
41
|
+
// Keep that entire group together when selecting the insertion boundary.
|
|
42
|
+
const open = new Map();
|
|
43
|
+
for (let index = 0; index < at; index++) {
|
|
44
|
+
const message = messages[index];
|
|
45
|
+
if (message.role === "assistant")
|
|
46
|
+
for (const block of message.content)
|
|
47
|
+
if (block.type === "toolCall")
|
|
48
|
+
open.set(block.id, index);
|
|
49
|
+
if (message.role === "toolResult")
|
|
50
|
+
open.delete(message.toolCallId);
|
|
51
|
+
}
|
|
52
|
+
if (open.size)
|
|
53
|
+
at = Math.min(at, ...open.values());
|
|
54
|
+
return [...messages.slice(0, at), reminder, ...messages.slice(at)];
|
|
55
|
+
}
|
|
56
|
+
export const createPlanExtension = () => pi => {
|
|
57
|
+
pi.registerTool({
|
|
58
|
+
name: "plan", label: "Plan", description: "Create or update a complete, versioned implementation plan on this session branch. Every call requires title, status, steps, currentStepId, completedStepIds and completionCriteria, even when completing or only changing status. Updates also require the latest planId and expectedRevision. This is full replacement, not a partial patch. Ended plans require create.",
|
|
59
|
+
exposure: "model-only", executionMode: "sequential", defaultActive: false,
|
|
60
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
61
|
+
promptSnippet: "Record and update a multi-step implementation plan.",
|
|
62
|
+
promptGuidelines: ["For multi-step implementation, create or update a relevant plan before execution and update the current step before starting it. Submit the full state each time. Wait for create to return its identity before updating. Follow user and skill instructions, including waiting; a plan does not grant authorization. Ordinary questions need no plan. When work is merged into another step or becomes unnecessary, update or remove the superseded step and explain the change instead of leaving a stale pending item. Never mark unfinished work completed. Mark completion explicitly; never infer it from a stopped run."],
|
|
63
|
+
parameters: planParameters,
|
|
64
|
+
async execute(_id, args, _signal, _update, ctx) {
|
|
65
|
+
const snapshot = transition(args, latestPlan(ctx.sessionManager.getBranch())?.snapshot);
|
|
66
|
+
return { content: [{ type: "text", text: `Plan ${snapshot.planId}, revision ${snapshot.revision}: ${snapshot.status}.` }], details: { planSnapshot: snapshot } };
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
pi.on("context", (event, ctx) => {
|
|
70
|
+
if (!pi.getActiveTools().includes("plan") || !["builtin:pi-harness-plan", "<inline:pi-harness-plan>"].includes(pi.getAllTools().find(tool => tool.name === "plan")?.sourceInfo.path ?? ""))
|
|
71
|
+
return;
|
|
72
|
+
return { messages: restorePlanContext(event.messages, ctx.sessionManager.getBranch()) };
|
|
73
|
+
});
|
|
74
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isToolTextContinuation } from "../tool-text.js";
|
|
2
|
+
import { latestPlan } from "./state.js";
|
|
3
|
+
const histories = new WeakMap();
|
|
4
|
+
export function taskHistoryFromSession(manager, serialize) {
|
|
5
|
+
const leaf = manager.getLeafId();
|
|
6
|
+
const previous = histories.get(manager);
|
|
7
|
+
if (previous?.leaf === leaf)
|
|
8
|
+
return previous.messages;
|
|
9
|
+
const branch = manager.getBranch();
|
|
10
|
+
const messages = latestPlan(branch) ? branch.flatMap(entry => {
|
|
11
|
+
if (entry.type !== "message")
|
|
12
|
+
return [];
|
|
13
|
+
const message = serialize(entry.message, entry.id);
|
|
14
|
+
return message ? [{ ...message, entryId: entry.id }] : [];
|
|
15
|
+
}) : undefined;
|
|
16
|
+
histories.set(manager, { leaf, messages });
|
|
17
|
+
return messages;
|
|
18
|
+
}
|
|
19
|
+
/** Replay assistant call order, pairing results before the following call. */
|
|
20
|
+
export function planFromTranscript(messages) {
|
|
21
|
+
const results = new Map(messages.filter(message => message.role === "toolResult" && message.toolCallId).map(message => [message.toolCallId, message]));
|
|
22
|
+
let latest;
|
|
23
|
+
let confirmed = false;
|
|
24
|
+
let changes = [];
|
|
25
|
+
let changeSummary;
|
|
26
|
+
const tools = new Map();
|
|
27
|
+
const removed = new Map();
|
|
28
|
+
const times = new Map();
|
|
29
|
+
for (const [index, message] of messages.entries()) {
|
|
30
|
+
if (message.role === "user") {
|
|
31
|
+
if (!isToolTextContinuation(messages, index))
|
|
32
|
+
confirmed = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (message.role !== "assistant")
|
|
36
|
+
continue;
|
|
37
|
+
for (const part of message.content) {
|
|
38
|
+
if (part.type !== "toolCall")
|
|
39
|
+
continue;
|
|
40
|
+
const call = part;
|
|
41
|
+
if (call.name !== "plan") {
|
|
42
|
+
if (confirmed && latest?.status === "active" && latest.currentStepId)
|
|
43
|
+
tools.set(latest.currentStepId, [...(tools.get(latest.currentStepId) ?? []), call.id]);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const result = results.get(call.id);
|
|
47
|
+
const next = !result?.isError ? result?.planSnapshot : undefined;
|
|
48
|
+
if (!next)
|
|
49
|
+
continue;
|
|
50
|
+
if (next.planId !== latest?.planId) {
|
|
51
|
+
tools.clear();
|
|
52
|
+
removed.clear();
|
|
53
|
+
times.clear();
|
|
54
|
+
changes = [];
|
|
55
|
+
changeSummary = undefined;
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
for (const step of latest.steps)
|
|
59
|
+
if (!next.steps.some(item => item.id === step.id))
|
|
60
|
+
removed.set(step.id, step);
|
|
61
|
+
for (const step of next.steps) {
|
|
62
|
+
removed.delete(step.id);
|
|
63
|
+
const time = times.get(step.id) ?? {};
|
|
64
|
+
if (next.currentStepId === step.id && time.startedAt === undefined)
|
|
65
|
+
time.startedAt = result?.timestamp;
|
|
66
|
+
if (next.completedStepIds.includes(step.id) && time.endedAt === undefined)
|
|
67
|
+
time.endedAt = result?.timestamp;
|
|
68
|
+
times.set(step.id, time);
|
|
69
|
+
}
|
|
70
|
+
const structuralChanges = next.changes.flatMap(change => ["added", "removed", "updated"].includes(change.kind) && "title" in change ? [{ kind: change.kind, title: change.title, position: change.position }] : []);
|
|
71
|
+
if (structuralChanges.length)
|
|
72
|
+
changes = structuralChanges;
|
|
73
|
+
if (next.changeSummary)
|
|
74
|
+
changeSummary = next.changeSummary;
|
|
75
|
+
latest = next;
|
|
76
|
+
confirmed = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!latest)
|
|
80
|
+
return { known: false };
|
|
81
|
+
if (!confirmed && latest.status !== "active")
|
|
82
|
+
return { known: true };
|
|
83
|
+
return { known: true, plan: {
|
|
84
|
+
source: "plan", origin: latest.planId, revision: latest.revision, status: latest.status, awaitingConfirmation: !confirmed,
|
|
85
|
+
title: latest.title, completionCriteria: latest.completionCriteria, changeSummary,
|
|
86
|
+
added: latest.changes.filter(change => change.kind === "added").length, removed: removed.size,
|
|
87
|
+
changes,
|
|
88
|
+
items: [...latest.steps.map(step => ({ ...step, status: latest.completedStepIds.includes(step.id) ? "done" : confirmed && step.id === latest.currentStepId ? "running" : "pending", toolCallIds: tools.get(step.id) ?? [], ...times.get(step.id) })), ...[...removed.values()].map(step => ({ ...step, status: "removed", toolCallIds: tools.get(step.id) ?? [], ...times.get(step.id) }))],
|
|
89
|
+
} };
|
|
90
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
/** One service instance preference. Sessions only hold their effective loadout. */
|
|
5
|
+
export class PlanSettings {
|
|
6
|
+
path;
|
|
7
|
+
enabled = true;
|
|
8
|
+
constructor(dataDir = resolve(process.env.PI_WEB_DATA_DIR ?? join(homedir(), ".pi-web"))) {
|
|
9
|
+
this.path = join(dataDir, "plan-settings.json");
|
|
10
|
+
try {
|
|
11
|
+
const saved = JSON.parse(readFileSync(this.path, "utf8"));
|
|
12
|
+
if (typeof saved.enabled === "boolean")
|
|
13
|
+
this.enabled = saved.enabled;
|
|
14
|
+
}
|
|
15
|
+
catch { /* Missing or invalid preference: default on. */ }
|
|
16
|
+
}
|
|
17
|
+
set(enabled) {
|
|
18
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
19
|
+
const temporary = `${this.path}.${process.pid}.tmp`;
|
|
20
|
+
writeFileSync(temporary, JSON.stringify({ enabled }) + "\n", { mode: 0o600 });
|
|
21
|
+
renameSync(temporary, this.path);
|
|
22
|
+
this.enabled = enabled;
|
|
23
|
+
}
|
|
24
|
+
state(session) {
|
|
25
|
+
const tool = session.getAllTools().find(tool => tool.name === "plan");
|
|
26
|
+
const available = !!tool && ["builtin:pi-harness-plan", "<inline:pi-harness-plan>"].includes(tool.sourceInfo.path);
|
|
27
|
+
const effective = available && session.getActiveToolNames().includes("plan");
|
|
28
|
+
return { enabled: this.enabled, available, effective, pending: available && effective !== this.enabled, ...(!available ? { reason: tool ? "conflict" : "missing" } : {}) };
|
|
29
|
+
}
|
|
30
|
+
coordinate(session, writable) {
|
|
31
|
+
const state = this.state(session);
|
|
32
|
+
if (!state.available || !state.pending || !session.isIdle || !writable)
|
|
33
|
+
return;
|
|
34
|
+
const active = session.getActiveToolNames().filter(name => name !== "plan");
|
|
35
|
+
if (this.enabled)
|
|
36
|
+
active.push("plan");
|
|
37
|
+
session.setActiveToolsByName(active);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let shared;
|
|
41
|
+
export const planSettings = () => shared ??= new PlanSettings();
|