@pasko70/pibo 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/.codex/skills/pi-agent-harness/SKILL.md +319 -0
- package/.codex/skills/pi-agent-harness/agents/openai.yaml +4 -0
- package/README.md +318 -0
- package/context/codex-base-prompt.md +144 -0
- package/context/pibo-compaction-prompt.md +100 -0
- package/context/pibo-system-prompt.md +17 -0
- package/dist/apps/chat/agent-profiles.js +26 -0
- package/dist/apps/chat/agent-store.js +336 -0
- package/dist/apps/chat/event-log.js +307 -0
- package/dist/apps/chat/read-model.js +218 -0
- package/dist/apps/chat/rooms.js +321 -0
- package/dist/apps/chat/session-metadata.js +14 -0
- package/dist/apps/chat/stream.js +122 -0
- package/dist/apps/chat/trace.js +1182 -0
- package/dist/apps/chat/web-app.js +2569 -0
- package/dist/apps/chat-ui/assets/apl-BxEMbctQ.js +1 -0
- package/dist/apps/chat-ui/assets/asciiarmor-eFjeGRuZ.js +1 -0
- package/dist/apps/chat-ui/assets/asn1-Cfx-0pt9.js +1 -0
- package/dist/apps/chat-ui/assets/asterisk-B5BHTsBr.js +1 -0
- package/dist/apps/chat-ui/assets/brainfuck-CAA2xofr.js +1 -0
- package/dist/apps/chat-ui/assets/clike-BoDCOLzN.js +1 -0
- package/dist/apps/chat-ui/assets/clojure-kVFSWX0o.js +1 -0
- package/dist/apps/chat-ui/assets/cmake-BUPAhezI.js +1 -0
- package/dist/apps/chat-ui/assets/cobol-BfgHZgup.js +1 -0
- package/dist/apps/chat-ui/assets/coffeescript-G1Pe_1AZ.js +1 -0
- package/dist/apps/chat-ui/assets/commonlisp-B3N0CjHn.js +1 -0
- package/dist/apps/chat-ui/assets/crystal-C15ndaGM.js +1 -0
- package/dist/apps/chat-ui/assets/css-BqOeyZOV.js +1 -0
- package/dist/apps/chat-ui/assets/cypher-KjIKv-wW.js +1 -0
- package/dist/apps/chat-ui/assets/d-BlT5AEC-.js +1 -0
- package/dist/apps/chat-ui/assets/diff-oF98tbqj.js +1 -0
- package/dist/apps/chat-ui/assets/dist--55mTWk3.js +1 -0
- package/dist/apps/chat-ui/assets/dist-B5rcjVCj.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BPv5MdlL.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BZ39rB1W.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BsedZGJo.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BvO6SpaM.js +9 -0
- package/dist/apps/chat-ui/assets/dist-C5YJOwhH.js +1 -0
- package/dist/apps/chat-ui/assets/dist-C5oXtFSP.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CG_gRSwv.js +23 -0
- package/dist/apps/chat-ui/assets/dist-CKM5j-iy.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CUp9us2q.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CZ9T4Cly.js +1 -0
- package/dist/apps/chat-ui/assets/dist-D4HhiFWB.js +6 -0
- package/dist/apps/chat-ui/assets/dist-DHE-yBLH.js +1 -0
- package/dist/apps/chat-ui/assets/dist-DSKQpPaz.js +1 -0
- package/dist/apps/chat-ui/assets/dist-L1B-KWZO.js +1 -0
- package/dist/apps/chat-ui/assets/dist-SVPsM5Oi.js +13 -0
- package/dist/apps/chat-ui/assets/dist-Zqt9kNG9.js +1 -0
- package/dist/apps/chat-ui/assets/dist-_DPv0-QF.js +1 -0
- package/dist/apps/chat-ui/assets/dist-_oM7g0eh.js +11 -0
- package/dist/apps/chat-ui/assets/dist-laadbMWg.js +1 -0
- package/dist/apps/chat-ui/assets/dist-nFui3f-r.js +2 -0
- package/dist/apps/chat-ui/assets/dist-tGfufz3b.js +1 -0
- package/dist/apps/chat-ui/assets/dockerfile-CUaqzcqp.js +1 -0
- package/dist/apps/chat-ui/assets/dtd-VCA9yr61.js +1 -0
- package/dist/apps/chat-ui/assets/dylan-O-V5Dw5W.js +1 -0
- package/dist/apps/chat-ui/assets/ebnf-SPmdBkvT.js +1 -0
- package/dist/apps/chat-ui/assets/ecl-ChfCttfI.js +1 -0
- package/dist/apps/chat-ui/assets/eiffel-cB9EbjeN.js +1 -0
- package/dist/apps/chat-ui/assets/elm-BTiVflBU.js +1 -0
- package/dist/apps/chat-ui/assets/erlang-egf0TXFm.js +1 -0
- package/dist/apps/chat-ui/assets/factor-C2tAxNob.js +1 -0
- package/dist/apps/chat-ui/assets/fcl-BiCjh1tj.js +1 -0
- package/dist/apps/chat-ui/assets/forth-C4kDGW6u.js +1 -0
- package/dist/apps/chat-ui/assets/fortran-DzkLaPnd.js +1 -0
- package/dist/apps/chat-ui/assets/gas-DMOxskQF.js +1 -0
- package/dist/apps/chat-ui/assets/gherkin-BPxBpp-k.js +1 -0
- package/dist/apps/chat-ui/assets/groovy-CkEnc0ZY.js +1 -0
- package/dist/apps/chat-ui/assets/haskell-Dadpo9-w.js +1 -0
- package/dist/apps/chat-ui/assets/haxe-CDNZL9MW.js +1 -0
- package/dist/apps/chat-ui/assets/http-jy9Zxxt3.js +1 -0
- package/dist/apps/chat-ui/assets/idl-CGSB6iiy.js +1 -0
- package/dist/apps/chat-ui/assets/index-BdcqTugD.js +151 -0
- package/dist/apps/chat-ui/assets/index-Dk-opWl3.css +1 -0
- package/dist/apps/chat-ui/assets/javascript-DSlzRUd8.js +1 -0
- package/dist/apps/chat-ui/assets/julia-DTiF9v6R.js +1 -0
- package/dist/apps/chat-ui/assets/livescript-Ci8Xxna6.js +1 -0
- package/dist/apps/chat-ui/assets/lua-eVxIrt9t.js +1 -0
- package/dist/apps/chat-ui/assets/mathematica-Da3grj36.js +1 -0
- package/dist/apps/chat-ui/assets/mbox-BWBMSafM.js +1 -0
- package/dist/apps/chat-ui/assets/mirc-CtHu9S1B.js +1 -0
- package/dist/apps/chat-ui/assets/mllike-wTFlPWXS.js +1 -0
- package/dist/apps/chat-ui/assets/modelica-cKbQ0UXu.js +1 -0
- package/dist/apps/chat-ui/assets/mscgen-C5pXQCMK.js +1 -0
- package/dist/apps/chat-ui/assets/mumps-CKBHkOQq.js +1 -0
- package/dist/apps/chat-ui/assets/nginx-B00AiQel.js +1 -0
- package/dist/apps/chat-ui/assets/nsis-SMDwKltR.js +1 -0
- package/dist/apps/chat-ui/assets/ntriples-B_36EBV0.js +1 -0
- package/dist/apps/chat-ui/assets/octave-BXPjzAif.js +1 -0
- package/dist/apps/chat-ui/assets/oz-Dedpat1M.js +1 -0
- package/dist/apps/chat-ui/assets/pascal-CMcUg72K.js +1 -0
- package/dist/apps/chat-ui/assets/perl-DoVTbnzt.js +1 -0
- package/dist/apps/chat-ui/assets/pig-CULXT1YV.js +1 -0
- package/dist/apps/chat-ui/assets/powershell-Cv5Uhf6b.js +1 -0
- package/dist/apps/chat-ui/assets/properties-CQ1C2P-M.js +1 -0
- package/dist/apps/chat-ui/assets/protobuf-SRlvzrl6.js +1 -0
- package/dist/apps/chat-ui/assets/pug-Bo3wfL-f.js +1 -0
- package/dist/apps/chat-ui/assets/puppet-ClSqCGj6.js +1 -0
- package/dist/apps/chat-ui/assets/python-C2_X1GpX.js +1 -0
- package/dist/apps/chat-ui/assets/q-B_6Gdyzl.js +1 -0
- package/dist/apps/chat-ui/assets/r-BD07NGED.js +1 -0
- package/dist/apps/chat-ui/assets/rolldown-runtime-S-ySWqyJ.js +1 -0
- package/dist/apps/chat-ui/assets/rpm-CRu5Z2Jl.js +1 -0
- package/dist/apps/chat-ui/assets/ruby-dZPnOz8h.js +1 -0
- package/dist/apps/chat-ui/assets/sas-S2t8_sDI.js +1 -0
- package/dist/apps/chat-ui/assets/scheme-WcyFWLW2.js +1 -0
- package/dist/apps/chat-ui/assets/shell-DUelnYSh.js +1 -0
- package/dist/apps/chat-ui/assets/sieve-jDjlVhNh.js +1 -0
- package/dist/apps/chat-ui/assets/simple-mode-CL04yRVp.js +1 -0
- package/dist/apps/chat-ui/assets/smalltalk-ddWDYy10.js +1 -0
- package/dist/apps/chat-ui/assets/solr-C7rQhLuW.js +1 -0
- package/dist/apps/chat-ui/assets/sparql--fPCCgVd.js +1 -0
- package/dist/apps/chat-ui/assets/spreadsheet-DjthqD5G.js +1 -0
- package/dist/apps/chat-ui/assets/sql-D_dieYbr.js +1 -0
- package/dist/apps/chat-ui/assets/stex-rHcFYcA0.js +1 -0
- package/dist/apps/chat-ui/assets/stylus-DxF70RNr.js +1 -0
- package/dist/apps/chat-ui/assets/swift-T0hDoOb-.js +1 -0
- package/dist/apps/chat-ui/assets/tcl-Dlzy1mPk.js +1 -0
- package/dist/apps/chat-ui/assets/textile-Dy5rFZe5.js +1 -0
- package/dist/apps/chat-ui/assets/tiddlywiki-DQP1OymS.js +1 -0
- package/dist/apps/chat-ui/assets/tiki-0vOj3DB2.js +1 -0
- package/dist/apps/chat-ui/assets/toml-DEltN0lI.js +1 -0
- package/dist/apps/chat-ui/assets/troff-CRbBpEqY.js +1 -0
- package/dist/apps/chat-ui/assets/ttcn-M5q1nLKg.js +1 -0
- package/dist/apps/chat-ui/assets/ttcn-cfg-Rmw9NWq9.js +1 -0
- package/dist/apps/chat-ui/assets/turtle-BCDWhJK0.js +1 -0
- package/dist/apps/chat-ui/assets/vb-QQ7LGlOz.js +1 -0
- package/dist/apps/chat-ui/assets/vbscript-BiSEdUzz.js +1 -0
- package/dist/apps/chat-ui/assets/velocity-CzMg5_6w.js +1 -0
- package/dist/apps/chat-ui/assets/verilog-BJOO83gk.js +1 -0
- package/dist/apps/chat-ui/assets/vhdl-ed4zAZAA.js +1 -0
- package/dist/apps/chat-ui/assets/webidl-Ddy6hbV6.js +1 -0
- package/dist/apps/chat-ui/assets/xquery-Bw1zwhh6.js +1 -0
- package/dist/apps/chat-ui/assets/yacas-DZdTXy3s.js +1 -0
- package/dist/apps/chat-ui/assets/z80-DMG02_YV.js +1 -0
- package/dist/apps/chat-ui/index.html +15 -0
- package/dist/apps/context-files-ui/assets/apl-14BqhLYK.js +1 -0
- package/dist/apps/context-files-ui/assets/asciiarmor-WLvjqI7d.js +1 -0
- package/dist/apps/context-files-ui/assets/asn1-Bg4FbdCg.js +1 -0
- package/dist/apps/context-files-ui/assets/asterisk-B2MO-X4d.js +1 -0
- package/dist/apps/context-files-ui/assets/brainfuck-CV9kufud.js +1 -0
- package/dist/apps/context-files-ui/assets/clike-CjmcUpt6.js +1 -0
- package/dist/apps/context-files-ui/assets/clojure-De3QGfx6.js +1 -0
- package/dist/apps/context-files-ui/assets/cmake-i2VDNaRq.js +1 -0
- package/dist/apps/context-files-ui/assets/cobol-BwO6DF08.js +1 -0
- package/dist/apps/context-files-ui/assets/coffeescript-BtivDvRz.js +1 -0
- package/dist/apps/context-files-ui/assets/commonlisp-DFkEQdLP.js +1 -0
- package/dist/apps/context-files-ui/assets/crystal-CU0NkcVL.js +1 -0
- package/dist/apps/context-files-ui/assets/css-CbMZAQ1W.js +1 -0
- package/dist/apps/context-files-ui/assets/cypher-Dke5QOzv.js +1 -0
- package/dist/apps/context-files-ui/assets/d-TPqSVaHM.js +1 -0
- package/dist/apps/context-files-ui/assets/diff-B365LSoZ.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-B5PTVnox.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-B5ZAfwf5.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-BJW3917N.js +11 -0
- package/dist/apps/context-files-ui/assets/dist-BkUONE3N.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CKQF1zEu.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CwbH_3RN.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CxRLRmMx.js +2 -0
- package/dist/apps/context-files-ui/assets/dist-D1KyNDg5.js +13 -0
- package/dist/apps/context-files-ui/assets/dist-DFi2YGux.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DKoyMSNr.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DLMjd26T.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DQXzt2Ng.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DotDq_kD.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DyfSeBMu.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-MyZgX9K2.js +9 -0
- package/dist/apps/context-files-ui/assets/dist-QQuDy4KA.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-YwR9dpb-.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-dTBsFYAl.js +1 -0
- package/dist/apps/context-files-ui/assets/dockerfile-CJb2sekW.js +1 -0
- package/dist/apps/context-files-ui/assets/dtd-CBMymJ7O.js +1 -0
- package/dist/apps/context-files-ui/assets/dylan-08Azy1NK.js +1 -0
- package/dist/apps/context-files-ui/assets/ebnf-Dkum_1J8.js +1 -0
- package/dist/apps/context-files-ui/assets/ecl-dhxjHL8y.js +1 -0
- package/dist/apps/context-files-ui/assets/eiffel-C446m6dI.js +1 -0
- package/dist/apps/context-files-ui/assets/elm-po1E9de0.js +1 -0
- package/dist/apps/context-files-ui/assets/erlang-BD5LkVkR.js +1 -0
- package/dist/apps/context-files-ui/assets/factor-B2EMQZkP.js +1 -0
- package/dist/apps/context-files-ui/assets/fcl-DkEOamHd.js +1 -0
- package/dist/apps/context-files-ui/assets/forth-dCg2_HVB.js +1 -0
- package/dist/apps/context-files-ui/assets/fortran-3ZpXLFh1.js +1 -0
- package/dist/apps/context-files-ui/assets/gas-DMmxLZRI.js +1 -0
- package/dist/apps/context-files-ui/assets/gherkin-DUSFg2R6.js +1 -0
- package/dist/apps/context-files-ui/assets/groovy-Cmduloe1.js +1 -0
- package/dist/apps/context-files-ui/assets/haskell-Cyr255R9.js +1 -0
- package/dist/apps/context-files-ui/assets/haxe-BJVKtprM.js +1 -0
- package/dist/apps/context-files-ui/assets/http-DszHFpc-.js +1 -0
- package/dist/apps/context-files-ui/assets/idl-Cati9Nrv.js +1 -0
- package/dist/apps/context-files-ui/assets/index-BwRDoHMC.css +1 -0
- package/dist/apps/context-files-ui/assets/index-GwvjXytt.js +202 -0
- package/dist/apps/context-files-ui/assets/javascript-C5pDVaoP.js +1 -0
- package/dist/apps/context-files-ui/assets/julia-DlqY1cTU.js +1 -0
- package/dist/apps/context-files-ui/assets/livescript-C6p8-2W1.js +1 -0
- package/dist/apps/context-files-ui/assets/lua-BxuTHaF7.js +1 -0
- package/dist/apps/context-files-ui/assets/mathematica-nDHZUMJT.js +1 -0
- package/dist/apps/context-files-ui/assets/mbox-DAaR0Hcz.js +1 -0
- package/dist/apps/context-files-ui/assets/mirc-CCA0FRfl.js +1 -0
- package/dist/apps/context-files-ui/assets/mllike-Cw3Iccsb.js +1 -0
- package/dist/apps/context-files-ui/assets/modelica-Czx80fNR.js +1 -0
- package/dist/apps/context-files-ui/assets/mscgen-BmfuTbzG.js +1 -0
- package/dist/apps/context-files-ui/assets/mumps-Ds_ZaumL.js +1 -0
- package/dist/apps/context-files-ui/assets/nginx-D9M2PQ4U.js +1 -0
- package/dist/apps/context-files-ui/assets/nsis-DOo3SJmd.js +1 -0
- package/dist/apps/context-files-ui/assets/ntriples-DLHGTvHs.js +1 -0
- package/dist/apps/context-files-ui/assets/octave-8jj96JiC.js +1 -0
- package/dist/apps/context-files-ui/assets/oz-BmMq7iC4.js +1 -0
- package/dist/apps/context-files-ui/assets/pascal-BgMr6XpT.js +1 -0
- package/dist/apps/context-files-ui/assets/perl-D6YJObi_.js +1 -0
- package/dist/apps/context-files-ui/assets/pig-nx7uchs8.js +1 -0
- package/dist/apps/context-files-ui/assets/powershell-B4IPkVxR.js +1 -0
- package/dist/apps/context-files-ui/assets/properties-CoavQdLC.js +1 -0
- package/dist/apps/context-files-ui/assets/protobuf-D5EWhcmc.js +1 -0
- package/dist/apps/context-files-ui/assets/pug-QU72BPAa.js +1 -0
- package/dist/apps/context-files-ui/assets/puppet-B_0GFuyD.js +1 -0
- package/dist/apps/context-files-ui/assets/python-Cs7sNzy_.js +1 -0
- package/dist/apps/context-files-ui/assets/q-BIB9pPtI.js +1 -0
- package/dist/apps/context-files-ui/assets/r-CN5s4f3o.js +1 -0
- package/dist/apps/context-files-ui/assets/rpm-Ct_Ck8mQ.js +1 -0
- package/dist/apps/context-files-ui/assets/ruby-BVPradRk.js +1 -0
- package/dist/apps/context-files-ui/assets/sas-DN4j74IF.js +1 -0
- package/dist/apps/context-files-ui/assets/scheme-CvNgHAnh.js +1 -0
- package/dist/apps/context-files-ui/assets/shell-BjiMLAaM.js +1 -0
- package/dist/apps/context-files-ui/assets/sieve-C2U1O5Z4.js +1 -0
- package/dist/apps/context-files-ui/assets/simple-mode-DnMbQuKW.js +1 -0
- package/dist/apps/context-files-ui/assets/smalltalk-DkYwYufV.js +1 -0
- package/dist/apps/context-files-ui/assets/solr-BopnSD-v.js +1 -0
- package/dist/apps/context-files-ui/assets/sparql-Cpg3YQfd.js +1 -0
- package/dist/apps/context-files-ui/assets/spreadsheet-kRxtVt9J.js +1 -0
- package/dist/apps/context-files-ui/assets/sql-DYI2GwJR.js +1 -0
- package/dist/apps/context-files-ui/assets/stex-BCRT8IJ3.js +1 -0
- package/dist/apps/context-files-ui/assets/stylus-CARoeVE9.js +1 -0
- package/dist/apps/context-files-ui/assets/swift-DKNaLAK6.js +1 -0
- package/dist/apps/context-files-ui/assets/tcl-CQyXkgMH.js +1 -0
- package/dist/apps/context-files-ui/assets/textile-Yg_UWn-t.js +1 -0
- package/dist/apps/context-files-ui/assets/tiddlywiki-dqVbkfX4.js +1 -0
- package/dist/apps/context-files-ui/assets/tiki-CC_ic8g0.js +1 -0
- package/dist/apps/context-files-ui/assets/toml-B7gGzVkJ.js +1 -0
- package/dist/apps/context-files-ui/assets/troff-Bq3z6oAu.js +1 -0
- package/dist/apps/context-files-ui/assets/ttcn-GvRgs2AT.js +1 -0
- package/dist/apps/context-files-ui/assets/ttcn-cfg-CGCXz7Vo.js +1 -0
- package/dist/apps/context-files-ui/assets/turtle-CwIpz2yy.js +1 -0
- package/dist/apps/context-files-ui/assets/vb-BmcHUM2C.js +1 -0
- package/dist/apps/context-files-ui/assets/vbscript-Cs9dylre.js +1 -0
- package/dist/apps/context-files-ui/assets/velocity-CBWU0VCy.js +1 -0
- package/dist/apps/context-files-ui/assets/verilog-DVCHmufx.js +1 -0
- package/dist/apps/context-files-ui/assets/vhdl-BGDk4f1d.js +1 -0
- package/dist/apps/context-files-ui/assets/webidl-egXQEWlC.js +1 -0
- package/dist/apps/context-files-ui/assets/xquery-5f0hn7cg.js +1 -0
- package/dist/apps/context-files-ui/assets/yacas-DNs9XkmQ.js +1 -0
- package/dist/apps/context-files-ui/assets/z80-B7Dfb4UO.js +1 -0
- package/dist/apps/context-files-ui/index.html +14 -0
- package/dist/auth/better-auth.js +129 -0
- package/dist/auth/types.js +14 -0
- package/dist/bin/pibo.js +3 -0
- package/dist/channels/types.js +1 -0
- package/dist/cli-errors.js +18 -0
- package/dist/cli.js +264 -0
- package/dist/config/config.js +167 -0
- package/dist/core/base-prompt.js +104 -0
- package/dist/core/codex-compat.js +44 -0
- package/dist/core/compaction-prompt.js +258 -0
- package/dist/core/events.js +1 -0
- package/dist/core/profiles.js +138 -0
- package/dist/core/routed-session.js +538 -0
- package/dist/core/runtime.js +323 -0
- package/dist/core/session-router.js +437 -0
- package/dist/core/system-prompt-template.js +56 -0
- package/dist/core/thinking.js +9 -0
- package/dist/core/workspace.js +5 -0
- package/dist/debug/events.js +92 -0
- package/dist/debug/index.js +631 -0
- package/dist/debug/session.js +200 -0
- package/dist/debug/sql.js +238 -0
- package/dist/debug/stores.js +49 -0
- package/dist/debug/trace.js +245 -0
- package/dist/env.js +32 -0
- package/dist/gateway/client.js +200 -0
- package/dist/gateway/protocol.js +46 -0
- package/dist/gateway/request.js +146 -0
- package/dist/gateway/server.js +221 -0
- package/dist/gateway/tool.js +69 -0
- package/dist/gateway/web.js +80 -0
- package/dist/index.js +23 -0
- package/dist/local/client.js +77 -0
- package/dist/local/extension.js +548 -0
- package/dist/local/tui.js +25 -0
- package/dist/mcp/agent-context.js +123 -0
- package/dist/mcp/client.js +332 -0
- package/dist/mcp/commands/call.js +146 -0
- package/dist/mcp/commands/grep.js +145 -0
- package/dist/mcp/commands/info.js +71 -0
- package/dist/mcp/commands/list.js +94 -0
- package/dist/mcp/config-command.js +217 -0
- package/dist/mcp/config.js +440 -0
- package/dist/mcp/daemon-client.js +266 -0
- package/dist/mcp/daemon.js +327 -0
- package/dist/mcp/errors.js +287 -0
- package/dist/mcp/index.js +431 -0
- package/dist/mcp/output.js +171 -0
- package/dist/mcp/python-runtime.js +131 -0
- package/dist/mcp/registry.js +116 -0
- package/dist/mcp/version.js +5 -0
- package/dist/pi-packages/cli.js +92 -0
- package/dist/pi-packages/installer.js +70 -0
- package/dist/pi-packages/metadata.js +231 -0
- package/dist/pi-packages/runtime.js +61 -0
- package/dist/pi-packages/store.js +175 -0
- package/dist/pi-packages/types.js +1 -0
- package/dist/plugins/better-auth.js +11 -0
- package/dist/plugins/builtin.js +249 -0
- package/dist/plugins/chat-web.js +11 -0
- package/dist/plugins/codex-compat.js +72 -0
- package/dist/plugins/context-files-store.js +354 -0
- package/dist/plugins/context-files.js +999 -0
- package/dist/plugins/core-tools.js +94 -0
- package/dist/plugins/example.js +67 -0
- package/dist/plugins/registry.js +368 -0
- package/dist/plugins/remote-agent.js +9 -0
- package/dist/plugins/types.js +1 -0
- package/dist/plugins/web.js +11 -0
- package/dist/reliability/store.js +759 -0
- package/dist/remote/channel.js +176 -0
- package/dist/remote/client.js +92 -0
- package/dist/remote/examples/tui-controller.js +263 -0
- package/dist/remote/protocol.js +55 -0
- package/dist/remote/session-client.js +189 -0
- package/dist/runs/registry.js +323 -0
- package/dist/runs/tools.js +185 -0
- package/dist/sessions/bindings.js +41 -0
- package/dist/sessions/sqlite-store.js +154 -0
- package/dist/sessions/store.js +125 -0
- package/dist/shared/trace-order.js +83 -0
- package/dist/subagents/tool.js +63 -0
- package/dist/tools/browser-use-cdp.js +207 -0
- package/dist/tools/browser-use-leases.js +338 -0
- package/dist/tools/browser-use-wrapper.js +338 -0
- package/dist/tools/codex-compat.js +85 -0
- package/dist/tools/desktop-env.js +90 -0
- package/dist/tools/guides.js +276 -0
- package/dist/tools/index.js +366 -0
- package/dist/tools/python-runtime.js +154 -0
- package/dist/tools/registry.js +107 -0
- package/dist/tools/web-search.js +137 -0
- package/dist/web/auth.js +10 -0
- package/dist/web/channel.js +151 -0
- package/dist/web/http.js +110 -0
- package/dist/web/types.js +1 -0
- package/package.json +74 -0
- package/src/mcp/LICENSE.mcp-cli +21 -0
|
@@ -0,0 +1,999 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { PiboWebHttpError, readJsonBody, responseHtml, responseJson } from "../web/http.js";
|
|
6
|
+
import { definePiboPlugin } from "./registry.js";
|
|
7
|
+
import { buildContextFileDiff, ContextFileMetadataStore, hashContextFileContent, } from "./context-files-store.js";
|
|
8
|
+
export const CONTEXT_FILES_APP_NAME = "pibo.context-files";
|
|
9
|
+
export const CONTEXT_FILES_MOUNT_PATH = "/apps/context-files";
|
|
10
|
+
export const CONTEXT_FILES_API_PREFIX = "/api/context-files";
|
|
11
|
+
const CONTEXT_FILES_UI_DIST_DIR = resolve(process.cwd(), "dist/apps/context-files-ui");
|
|
12
|
+
const POLL_INTERVAL_MS = 1000;
|
|
13
|
+
function getPiboHome() {
|
|
14
|
+
return process.env.PIBO_HOME || join(homedir(), ".pibo");
|
|
15
|
+
}
|
|
16
|
+
function resolveContextFilesPaths(options) {
|
|
17
|
+
const managedRoot = resolve(options.managedRoot ?? join(getPiboHome(), "context-files"));
|
|
18
|
+
const explicitStorePath = options.storePath ? resolve(options.storePath) : undefined;
|
|
19
|
+
const metadataPath = resolve(options.metadataPath
|
|
20
|
+
?? (explicitStorePath && extname(explicitStorePath) === ".sqlite"
|
|
21
|
+
? explicitStorePath
|
|
22
|
+
: join(managedRoot, "context-files.sqlite")));
|
|
23
|
+
const legacyStorePath = resolve(explicitStorePath && extname(explicitStorePath) !== ".sqlite"
|
|
24
|
+
? explicitStorePath
|
|
25
|
+
: join(managedRoot, "index.json"));
|
|
26
|
+
return {
|
|
27
|
+
metadataPath,
|
|
28
|
+
legacyStorePath,
|
|
29
|
+
managedRoot,
|
|
30
|
+
globalDir: resolve(options.globalDir ?? join(managedRoot, "global")),
|
|
31
|
+
agentWorkspaceRoot: resolve(options.agentWorkspaceRoot ?? join(getPiboHome(), "agent-workspaces")),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function normalizeLabel(value) {
|
|
35
|
+
if (typeof value !== "string")
|
|
36
|
+
throw new PiboWebHttpError("label must be a string", 400);
|
|
37
|
+
const trimmed = value.trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
throw new PiboWebHttpError("label is required", 400);
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
42
|
+
function normalizeOptionalLabel(value) {
|
|
43
|
+
if (value === undefined || value === null)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (typeof value !== "string")
|
|
46
|
+
throw new PiboWebHttpError("label must be a string", 400);
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
return trimmed || undefined;
|
|
49
|
+
}
|
|
50
|
+
function normalizeMarkdown(value) {
|
|
51
|
+
if (typeof value !== "string")
|
|
52
|
+
throw new PiboWebHttpError("markdown must be a string", 400);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function normalizeExpectedVersion(value) {
|
|
56
|
+
if (value === undefined || value === null)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (typeof value !== "string")
|
|
59
|
+
throw new PiboWebHttpError("expectedVersion must be a string", 400);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function normalizeScope(value, fallback = "global") {
|
|
63
|
+
if (value === undefined || value === null)
|
|
64
|
+
return fallback;
|
|
65
|
+
if (value === "global" || value === "agent")
|
|
66
|
+
return value;
|
|
67
|
+
throw new PiboWebHttpError("scope must be global or agent", 400);
|
|
68
|
+
}
|
|
69
|
+
function normalizeAgentProfileName(value, required) {
|
|
70
|
+
if (value === undefined || value === null) {
|
|
71
|
+
if (required)
|
|
72
|
+
throw new PiboWebHttpError("agentProfileName is required for agent context files", 400);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (typeof value !== "string")
|
|
76
|
+
throw new PiboWebHttpError("agentProfileName must be a string", 400);
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
if (!trimmed) {
|
|
79
|
+
if (required)
|
|
80
|
+
throw new PiboWebHttpError("agentProfileName is required for agent context files", 400);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return trimmed;
|
|
84
|
+
}
|
|
85
|
+
function normalizeBoolean(value, fallback = false) {
|
|
86
|
+
if (value === undefined || value === null)
|
|
87
|
+
return fallback;
|
|
88
|
+
if (typeof value !== "boolean")
|
|
89
|
+
throw new PiboWebHttpError("boolean field must be a boolean", 400);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function normalizeRevisionId(value) {
|
|
93
|
+
if (typeof value !== "string" || !value.trim())
|
|
94
|
+
throw new PiboWebHttpError("revisionId is required", 400);
|
|
95
|
+
return value.trim();
|
|
96
|
+
}
|
|
97
|
+
function normalizeDiffSide(value, fallback) {
|
|
98
|
+
if (!value)
|
|
99
|
+
return fallback;
|
|
100
|
+
if (value === "source" || value === "working")
|
|
101
|
+
return value;
|
|
102
|
+
throw new PiboWebHttpError("diff side must be source or working", 400);
|
|
103
|
+
}
|
|
104
|
+
function slugSegment(value) {
|
|
105
|
+
const key = value
|
|
106
|
+
.toLowerCase()
|
|
107
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
108
|
+
.replace(/^-+|-+$/g, "");
|
|
109
|
+
return key || "context-file";
|
|
110
|
+
}
|
|
111
|
+
function uniqueKey(base, used) {
|
|
112
|
+
if (!used.has(base))
|
|
113
|
+
return base;
|
|
114
|
+
let index = 2;
|
|
115
|
+
while (used.has(`${base}-${index}`))
|
|
116
|
+
index += 1;
|
|
117
|
+
return `${base}-${index}`;
|
|
118
|
+
}
|
|
119
|
+
function uniquePath(dir, filename, currentPath) {
|
|
120
|
+
const extension = extname(filename) || ".md";
|
|
121
|
+
const baseName = slugSegment(filename.slice(0, filename.length - extension.length) || filename);
|
|
122
|
+
let candidate = resolve(dir, `${baseName}${extension}`);
|
|
123
|
+
if (currentPath && candidate === currentPath)
|
|
124
|
+
return candidate;
|
|
125
|
+
let index = 2;
|
|
126
|
+
while (existsSync(candidate) && candidate !== currentPath) {
|
|
127
|
+
candidate = resolve(dir, `${baseName}-${index}${extension}`);
|
|
128
|
+
index += 1;
|
|
129
|
+
}
|
|
130
|
+
return candidate;
|
|
131
|
+
}
|
|
132
|
+
function managedFileName(label) {
|
|
133
|
+
return `${slugSegment(label)}.md`;
|
|
134
|
+
}
|
|
135
|
+
function profileForManaged(file) {
|
|
136
|
+
return {
|
|
137
|
+
key: file.key,
|
|
138
|
+
label: file.label,
|
|
139
|
+
path: file.managedPath,
|
|
140
|
+
source: "managed",
|
|
141
|
+
scope: file.scope,
|
|
142
|
+
agentProfileName: file.agentProfileName,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async function fileSnapshot(path) {
|
|
146
|
+
try {
|
|
147
|
+
const [stats, content] = await Promise.all([stat(path), readFile(path, "utf8")]);
|
|
148
|
+
return {
|
|
149
|
+
exists: true,
|
|
150
|
+
content,
|
|
151
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
152
|
+
updatedAt: stats.mtime.toISOString(),
|
|
153
|
+
version: hashContextFileContent(content),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
158
|
+
if (code === "ENOENT")
|
|
159
|
+
return { exists: false };
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function snapshotFromSync(path) {
|
|
164
|
+
try {
|
|
165
|
+
const stats = statSync(path);
|
|
166
|
+
const content = readFileSync(path, "utf8");
|
|
167
|
+
return {
|
|
168
|
+
exists: true,
|
|
169
|
+
content,
|
|
170
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
171
|
+
updatedAt: stats.mtime.toISOString(),
|
|
172
|
+
version: hashContextFileContent(content),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return { exists: false };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function sameSnapshot(left, right) {
|
|
180
|
+
return left?.exists === right.exists
|
|
181
|
+
&& left?.version === right.version
|
|
182
|
+
&& left?.updatedAt === right.updatedAt
|
|
183
|
+
&& left?.bytes === right.bytes
|
|
184
|
+
&& left?.linkState === right.linkState
|
|
185
|
+
&& left?.sourceHash === right.sourceHash
|
|
186
|
+
&& left?.activeRevisionId === right.activeRevisionId;
|
|
187
|
+
}
|
|
188
|
+
function contentType(pathname) {
|
|
189
|
+
const ext = extname(pathname);
|
|
190
|
+
if (ext === ".js")
|
|
191
|
+
return "text/javascript; charset=utf-8";
|
|
192
|
+
if (ext === ".css")
|
|
193
|
+
return "text/css; charset=utf-8";
|
|
194
|
+
if (ext === ".svg")
|
|
195
|
+
return "image/svg+xml";
|
|
196
|
+
if (ext === ".json")
|
|
197
|
+
return "application/json; charset=utf-8";
|
|
198
|
+
return "application/octet-stream";
|
|
199
|
+
}
|
|
200
|
+
function builtAsset(pathname) {
|
|
201
|
+
const relativePath = pathname.slice(`${CONTEXT_FILES_MOUNT_PATH}/`.length);
|
|
202
|
+
if (!relativePath || relativePath.includes(".."))
|
|
203
|
+
return undefined;
|
|
204
|
+
const assetPath = resolve(CONTEXT_FILES_UI_DIST_DIR, relativePath);
|
|
205
|
+
if (!assetPath.startsWith(CONTEXT_FILES_UI_DIST_DIR) || !existsSync(assetPath))
|
|
206
|
+
return undefined;
|
|
207
|
+
return new Response(readFileSync(assetPath), {
|
|
208
|
+
headers: { "content-type": contentType(assetPath) },
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function responseBuiltIndex() {
|
|
212
|
+
const indexPath = resolve(CONTEXT_FILES_UI_DIST_DIR, "index.html");
|
|
213
|
+
if (!existsSync(indexPath))
|
|
214
|
+
return undefined;
|
|
215
|
+
return responseHtml(readFileSync(indexPath, "utf8"));
|
|
216
|
+
}
|
|
217
|
+
function fallbackHtml() {
|
|
218
|
+
return responseHtml(`<!doctype html>
|
|
219
|
+
<html lang="en">
|
|
220
|
+
<head><meta charset="utf-8"><title>Pibo Context Files</title></head>
|
|
221
|
+
<body><p>Context Files UI has not been built. Run <code>npm run context-files-ui:build</code>.</p></body>
|
|
222
|
+
</html>`);
|
|
223
|
+
}
|
|
224
|
+
function writeSse(controller, eventName, payload, id) {
|
|
225
|
+
const encoder = new TextEncoder();
|
|
226
|
+
const lines = [`event: ${eventName}`];
|
|
227
|
+
if (id)
|
|
228
|
+
lines.push(`id: ${id}`);
|
|
229
|
+
lines.push(`data: ${JSON.stringify(payload)}`, "", "");
|
|
230
|
+
controller.enqueue(encoder.encode(lines.join("\n")));
|
|
231
|
+
}
|
|
232
|
+
function writeSseComment(controller, comment) {
|
|
233
|
+
controller.enqueue(new TextEncoder().encode(`: ${comment}\n\n`));
|
|
234
|
+
}
|
|
235
|
+
function productEventMatches(event) {
|
|
236
|
+
return event.type.startsWith("context-file.");
|
|
237
|
+
}
|
|
238
|
+
function emitContextFileEvent(context, type, source, actorId, payload) {
|
|
239
|
+
return context.channelContext.emitProductEvent?.({
|
|
240
|
+
type,
|
|
241
|
+
source,
|
|
242
|
+
actorId,
|
|
243
|
+
payload,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function contextFilePayload(file) {
|
|
247
|
+
return {
|
|
248
|
+
key: file.key,
|
|
249
|
+
...(file.label ? { label: file.label } : {}),
|
|
250
|
+
path: file.path,
|
|
251
|
+
absolutePath: file.absolutePath,
|
|
252
|
+
source: file.source,
|
|
253
|
+
scope: file.scope,
|
|
254
|
+
managed: file.managed,
|
|
255
|
+
editable: file.editable,
|
|
256
|
+
removable: file.removable,
|
|
257
|
+
linkState: file.linkState,
|
|
258
|
+
...(file.agentProfileName ? { agentProfileName: file.agentProfileName } : {}),
|
|
259
|
+
...(file.sourceRef ? { sourceRef: file.sourceRef } : {}),
|
|
260
|
+
...(file.sourceHash ? { sourceHash: file.sourceHash } : {}),
|
|
261
|
+
...(file.activeRevisionId ? { activeRevisionId: file.activeRevisionId } : {}),
|
|
262
|
+
exists: file.exists,
|
|
263
|
+
...(file.bytes !== undefined ? { bytes: file.bytes } : {}),
|
|
264
|
+
...(file.updatedAt ? { updatedAt: file.updatedAt } : {}),
|
|
265
|
+
...(file.version ? { version: file.version } : {}),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function eventStream(context) {
|
|
269
|
+
let unsubscribe;
|
|
270
|
+
let heartbeat;
|
|
271
|
+
const stream = new ReadableStream({
|
|
272
|
+
start(controller) {
|
|
273
|
+
writeSse(controller, "pibo-product", {
|
|
274
|
+
type: "context-file.ready",
|
|
275
|
+
source: "plugin",
|
|
276
|
+
payload: {},
|
|
277
|
+
});
|
|
278
|
+
unsubscribe = context.channelContext.subscribeProductEvents?.((event) => {
|
|
279
|
+
if (!productEventMatches(event))
|
|
280
|
+
return;
|
|
281
|
+
writeSse(controller, "pibo-product", event, event.id);
|
|
282
|
+
});
|
|
283
|
+
heartbeat = setInterval(() => writeSseComment(controller, "heartbeat"), 25000);
|
|
284
|
+
},
|
|
285
|
+
cancel() {
|
|
286
|
+
unsubscribe?.();
|
|
287
|
+
if (heartbeat)
|
|
288
|
+
clearInterval(heartbeat);
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
return new Response(stream, {
|
|
292
|
+
headers: {
|
|
293
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
294
|
+
"cache-control": "no-cache",
|
|
295
|
+
connection: "keep-alive",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
class ContextFileService {
|
|
300
|
+
paths;
|
|
301
|
+
api;
|
|
302
|
+
store;
|
|
303
|
+
managed = new Map();
|
|
304
|
+
snapshots = new Map();
|
|
305
|
+
pollTimer;
|
|
306
|
+
constructor(paths, api) {
|
|
307
|
+
this.paths = paths;
|
|
308
|
+
this.api = api;
|
|
309
|
+
this.store = new ContextFileMetadataStore(paths.metadataPath, paths.legacyStorePath);
|
|
310
|
+
for (const file of this.store.listFiles()) {
|
|
311
|
+
this.managed.set(file.key, file);
|
|
312
|
+
this.api.upsertContextFile(profileForManaged(file));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
list(context) {
|
|
316
|
+
const catalog = context.channelContext.getCapabilityCatalog?.().contextFiles ?? [];
|
|
317
|
+
return catalog.map((file) => this.resolveCatalogInfo(context, file));
|
|
318
|
+
}
|
|
319
|
+
async read(context, key) {
|
|
320
|
+
const managed = this.managed.get(key);
|
|
321
|
+
if (managed) {
|
|
322
|
+
const file = this.resolveManagedInfo(context, managed);
|
|
323
|
+
const activeRevision = file.activeRevisionId ? this.store.getRevision(file.activeRevisionId) : undefined;
|
|
324
|
+
const snapshot = await fileSnapshot(file.absolutePath);
|
|
325
|
+
const markdown = snapshot.exists ? (snapshot.content ?? "") : (activeRevision?.content ?? "");
|
|
326
|
+
return {
|
|
327
|
+
...file,
|
|
328
|
+
bytes: snapshot.bytes ?? file.bytes,
|
|
329
|
+
updatedAt: snapshot.updatedAt ?? file.updatedAt,
|
|
330
|
+
version: snapshot.version ?? file.version,
|
|
331
|
+
exists: snapshot.exists,
|
|
332
|
+
markdown,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const catalogFile = this.requirePluginCatalogFile(context, key);
|
|
336
|
+
const source = await fileSnapshot(this.resolveCatalogPath(catalogFile.path));
|
|
337
|
+
return {
|
|
338
|
+
...this.resolvePluginInfo(catalogFile),
|
|
339
|
+
bytes: source.bytes,
|
|
340
|
+
updatedAt: source.updatedAt,
|
|
341
|
+
version: source.version,
|
|
342
|
+
exists: source.exists,
|
|
343
|
+
markdown: source.content ?? "",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async create(context, body, webSession) {
|
|
347
|
+
const label = normalizeLabel(body.label ?? body.title);
|
|
348
|
+
const markdown = normalizeMarkdown(body.markdown ?? "");
|
|
349
|
+
const scope = normalizeScope(body.scope);
|
|
350
|
+
const agentProfileName = normalizeAgentProfileName(body.agentProfileName, scope === "agent");
|
|
351
|
+
const targetDir = this.resolveManagedDir(scope, agentProfileName);
|
|
352
|
+
const absolutePath = uniquePath(targetDir, managedFileName(label));
|
|
353
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
354
|
+
await writeFile(absolutePath, markdown, "utf8");
|
|
355
|
+
const record = this.store.createFile({
|
|
356
|
+
key: uniqueKey(`ctx:${slugSegment(label)}`, new Set(this.list(context).map((file) => file.key))),
|
|
357
|
+
label,
|
|
358
|
+
managedPath: absolutePath,
|
|
359
|
+
scope,
|
|
360
|
+
agentProfileName,
|
|
361
|
+
});
|
|
362
|
+
const revision = this.store.appendRevision({
|
|
363
|
+
contextFileKey: record.key,
|
|
364
|
+
kind: "working",
|
|
365
|
+
contentHash: hashContextFileContent(markdown),
|
|
366
|
+
content: markdown,
|
|
367
|
+
actorId: webSession.ownerScope,
|
|
368
|
+
note: "Managed context file created",
|
|
369
|
+
});
|
|
370
|
+
const updated = this.store.updateFile({
|
|
371
|
+
...record,
|
|
372
|
+
activeRevisionId: revision.id,
|
|
373
|
+
updatedAt: new Date().toISOString(),
|
|
374
|
+
});
|
|
375
|
+
this.upsertManaged(updated);
|
|
376
|
+
const document = await this.read(context, updated.key);
|
|
377
|
+
this.snapshots.set(updated.key, this.snapshotFromInfo(document));
|
|
378
|
+
this.emitChanged(context, "context-file.created", "web", webSession.ownerScope, document);
|
|
379
|
+
return document;
|
|
380
|
+
}
|
|
381
|
+
async createLinkedCopy(context, sourceKey, body, webSession) {
|
|
382
|
+
const sourceFile = this.requirePluginCatalogFile(context, sourceKey);
|
|
383
|
+
const sourceDescriptor = this.requireLivePluginSource(sourceFile);
|
|
384
|
+
const label = normalizeOptionalLabel(body.label) ?? sourceFile.label ?? sourceKey;
|
|
385
|
+
const scope = normalizeScope(body.scope, "global");
|
|
386
|
+
const agentProfileName = normalizeAgentProfileName(body.agentProfileName, scope === "agent");
|
|
387
|
+
const targetDir = this.resolveManagedDir(scope, agentProfileName);
|
|
388
|
+
const absolutePath = uniquePath(targetDir, managedFileName(label));
|
|
389
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
390
|
+
await writeFile(absolutePath, sourceDescriptor.content ?? "", "utf8");
|
|
391
|
+
const record = this.store.createFile({
|
|
392
|
+
key: uniqueKey(`ctx:${slugSegment(label)}`, new Set(this.list(context).map((file) => file.key))),
|
|
393
|
+
label,
|
|
394
|
+
managedPath: absolutePath,
|
|
395
|
+
scope,
|
|
396
|
+
agentProfileName,
|
|
397
|
+
sourceRef: this.pluginSourceRef(sourceFile),
|
|
398
|
+
sourceHash: sourceDescriptor.contentHash,
|
|
399
|
+
});
|
|
400
|
+
const sourceRevision = this.store.appendRevision({
|
|
401
|
+
contextFileKey: record.key,
|
|
402
|
+
kind: "source-snapshot",
|
|
403
|
+
contentHash: sourceDescriptor.contentHash,
|
|
404
|
+
content: sourceDescriptor.content ?? "",
|
|
405
|
+
actorId: webSession.ownerScope,
|
|
406
|
+
sourceHashAtCreation: sourceDescriptor.contentHash,
|
|
407
|
+
note: "Plugin source linked",
|
|
408
|
+
});
|
|
409
|
+
const workingRevision = this.store.appendRevision({
|
|
410
|
+
contextFileKey: record.key,
|
|
411
|
+
kind: "working",
|
|
412
|
+
contentHash: sourceDescriptor.contentHash,
|
|
413
|
+
content: sourceDescriptor.content ?? "",
|
|
414
|
+
actorId: webSession.ownerScope,
|
|
415
|
+
basedOnRevisionId: sourceRevision.id,
|
|
416
|
+
sourceHashAtCreation: sourceDescriptor.contentHash,
|
|
417
|
+
note: "Managed working copy created from plugin source",
|
|
418
|
+
});
|
|
419
|
+
const updated = this.store.updateFile({
|
|
420
|
+
...record,
|
|
421
|
+
activeRevisionId: workingRevision.id,
|
|
422
|
+
sourceHash: sourceDescriptor.contentHash,
|
|
423
|
+
updatedAt: new Date().toISOString(),
|
|
424
|
+
});
|
|
425
|
+
this.upsertManaged(updated);
|
|
426
|
+
const document = await this.read(context, updated.key);
|
|
427
|
+
this.snapshots.set(updated.key, this.snapshotFromInfo(document));
|
|
428
|
+
this.emitChanged(context, "context-file.linked_from_plugin", "web", webSession.ownerScope, document);
|
|
429
|
+
return document;
|
|
430
|
+
}
|
|
431
|
+
async update(context, key, body, webSession) {
|
|
432
|
+
const record = this.requireManagedRecord(key);
|
|
433
|
+
const markdown = normalizeMarkdown(body.markdown);
|
|
434
|
+
const expectedVersion = normalizeExpectedVersion(body.expectedVersion);
|
|
435
|
+
const current = await this.read(context, key);
|
|
436
|
+
if (expectedVersion && current.version && expectedVersion !== current.version) {
|
|
437
|
+
return Promise.reject(new ContextFileConflictError(current));
|
|
438
|
+
}
|
|
439
|
+
if (current.markdown === markdown)
|
|
440
|
+
return current;
|
|
441
|
+
const updatedRecord = await this.writeWorkingContent(record, markdown, {
|
|
442
|
+
actorId: webSession.ownerScope,
|
|
443
|
+
note: "Managed context file updated",
|
|
444
|
+
});
|
|
445
|
+
const updated = await this.read(context, updatedRecord.key);
|
|
446
|
+
this.snapshots.set(updated.key, this.snapshotFromInfo(updated));
|
|
447
|
+
this.emitChanged(context, "context-file.updated", "web", webSession.ownerScope, updated);
|
|
448
|
+
return updated;
|
|
449
|
+
}
|
|
450
|
+
async updateMetadata(context, key, body, webSession) {
|
|
451
|
+
const record = this.requireManagedRecord(key);
|
|
452
|
+
const label = normalizeOptionalLabel(body.label) ?? record.label;
|
|
453
|
+
const scope = normalizeScope(body.scope, record.scope);
|
|
454
|
+
const agentProfileName = normalizeAgentProfileName(body.agentProfileName, scope === "agent");
|
|
455
|
+
const oldPath = record.managedPath;
|
|
456
|
+
const nextPath = scope !== record.scope || agentProfileName !== record.agentProfileName || label !== record.label
|
|
457
|
+
? uniquePath(this.resolveManagedDir(scope, agentProfileName), managedFileName(label), oldPath)
|
|
458
|
+
: oldPath;
|
|
459
|
+
if (nextPath !== oldPath) {
|
|
460
|
+
const currentContent = this.currentWorkingContent(record);
|
|
461
|
+
await mkdir(dirname(nextPath), { recursive: true });
|
|
462
|
+
await writeFile(nextPath, currentContent, "utf8");
|
|
463
|
+
await rm(oldPath, { force: true });
|
|
464
|
+
}
|
|
465
|
+
const updated = this.store.updateFile({
|
|
466
|
+
...record,
|
|
467
|
+
label,
|
|
468
|
+
managedPath: nextPath,
|
|
469
|
+
scope,
|
|
470
|
+
agentProfileName,
|
|
471
|
+
updatedAt: new Date().toISOString(),
|
|
472
|
+
});
|
|
473
|
+
this.upsertManaged(updated);
|
|
474
|
+
const document = await this.read(context, key);
|
|
475
|
+
this.snapshots.set(key, this.snapshotFromInfo(document));
|
|
476
|
+
this.emitChanged(context, "context-file.metadata_updated", "web", webSession.ownerScope, document);
|
|
477
|
+
return document;
|
|
478
|
+
}
|
|
479
|
+
async resetToSource(context, key, webSession) {
|
|
480
|
+
const record = this.requireManagedRecord(key);
|
|
481
|
+
if (!record.sourceRef)
|
|
482
|
+
throw new PiboWebHttpError("Only linked managed files can be reset to source", 400);
|
|
483
|
+
const liveSource = this.requireLiveSourceForRecord(context, record);
|
|
484
|
+
this.ensureSourceSnapshot(record, liveSource, webSession.ownerScope, "Source snapshot refreshed during reset");
|
|
485
|
+
const updatedRecord = await this.writeWorkingContent(record, liveSource.content ?? "", {
|
|
486
|
+
actorId: webSession.ownerScope,
|
|
487
|
+
sourceHash: liveSource.contentHash,
|
|
488
|
+
note: "Working copy reset to source",
|
|
489
|
+
});
|
|
490
|
+
const document = await this.read(context, updatedRecord.key);
|
|
491
|
+
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
492
|
+
this.emitChanged(context, "context-file.reset_to_source", "web", webSession.ownerScope, document);
|
|
493
|
+
return document;
|
|
494
|
+
}
|
|
495
|
+
async adoptSource(context, key, webSession) {
|
|
496
|
+
const record = this.requireManagedRecord(key);
|
|
497
|
+
if (!record.sourceRef)
|
|
498
|
+
throw new PiboWebHttpError("Only linked managed files can adopt a source", 400);
|
|
499
|
+
const liveSource = this.requireLiveSourceForRecord(context, record);
|
|
500
|
+
this.ensureSourceSnapshot(record, liveSource, webSession.ownerScope, "Source snapshot adopted");
|
|
501
|
+
const updatedRecord = await this.writeWorkingContent(record, liveSource.content ?? "", {
|
|
502
|
+
actorId: webSession.ownerScope,
|
|
503
|
+
sourceHash: liveSource.contentHash,
|
|
504
|
+
note: "Plugin source adopted",
|
|
505
|
+
});
|
|
506
|
+
const document = await this.read(context, updatedRecord.key);
|
|
507
|
+
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
508
|
+
this.emitChanged(context, "context-file.source_adopted", "web", webSession.ownerScope, document);
|
|
509
|
+
return document;
|
|
510
|
+
}
|
|
511
|
+
async restoreRevision(context, key, body, webSession) {
|
|
512
|
+
const record = this.requireManagedRecord(key);
|
|
513
|
+
const revisionId = normalizeRevisionId(body.revisionId);
|
|
514
|
+
const revision = this.store.getRevision(revisionId);
|
|
515
|
+
if (!revision || revision.contextFileKey !== key)
|
|
516
|
+
throw new PiboWebHttpError(`Unknown revision "${revisionId}"`, 404);
|
|
517
|
+
const updatedRecord = await this.writeWorkingContent(record, revision.content, {
|
|
518
|
+
actorId: webSession.ownerScope,
|
|
519
|
+
basedOnRevisionId: revision.id,
|
|
520
|
+
note: `Revision restored from ${revision.id}`,
|
|
521
|
+
});
|
|
522
|
+
const document = await this.read(context, updatedRecord.key);
|
|
523
|
+
this.snapshots.set(updatedRecord.key, this.snapshotFromInfo(document));
|
|
524
|
+
this.emitChanged(context, "context-file.revision_restored", "web", webSession.ownerScope, document);
|
|
525
|
+
return document;
|
|
526
|
+
}
|
|
527
|
+
listRevisions(context, key) {
|
|
528
|
+
const record = this.ensureManagedRecordSynced(this.requireManagedRecord(key));
|
|
529
|
+
const revisions = this.store.listRevisions(key).map((revision) => this.revisionInfoFromRecord(revision, record.activeRevisionId));
|
|
530
|
+
return {
|
|
531
|
+
revisions,
|
|
532
|
+
...(record.activeRevisionId ? { activeRevisionId: record.activeRevisionId } : {}),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
diff(context, key, baseKind, targetKind) {
|
|
536
|
+
const record = this.ensureManagedRecordSynced(this.requireManagedRecord(key));
|
|
537
|
+
const base = this.resolveDiffSide(context, record, baseKind);
|
|
538
|
+
const target = this.resolveDiffSide(context, record, targetKind);
|
|
539
|
+
return {
|
|
540
|
+
base: {
|
|
541
|
+
kind: baseKind,
|
|
542
|
+
contentHash: base.contentHash,
|
|
543
|
+
},
|
|
544
|
+
target: {
|
|
545
|
+
kind: targetKind,
|
|
546
|
+
contentHash: target.contentHash,
|
|
547
|
+
},
|
|
548
|
+
chunks: buildContextFileDiff(base.content, target.content),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async remove(context, key, body, webSession) {
|
|
552
|
+
const file = this.requireInfo(context, key);
|
|
553
|
+
const record = this.requireManagedRecord(key);
|
|
554
|
+
const deleteFile = normalizeBoolean(body.deleteFile, true);
|
|
555
|
+
if (deleteFile)
|
|
556
|
+
await rm(record.managedPath, { force: true });
|
|
557
|
+
this.managed.delete(key);
|
|
558
|
+
this.store.deleteFile(key);
|
|
559
|
+
this.api.removeContextFile(key);
|
|
560
|
+
this.snapshots.delete(key);
|
|
561
|
+
this.emitChanged(context, "context-file.removed", "web", webSession.ownerScope, file);
|
|
562
|
+
return { removed: key };
|
|
563
|
+
}
|
|
564
|
+
startWatcher(context) {
|
|
565
|
+
if (this.pollTimer)
|
|
566
|
+
return;
|
|
567
|
+
for (const file of this.list(context))
|
|
568
|
+
this.snapshots.set(file.key, this.snapshotFromInfo(file));
|
|
569
|
+
this.pollTimer = setInterval(() => {
|
|
570
|
+
void this.poll(context);
|
|
571
|
+
}, POLL_INTERVAL_MS);
|
|
572
|
+
this.pollTimer.unref?.();
|
|
573
|
+
}
|
|
574
|
+
stopWatcher() {
|
|
575
|
+
if (!this.pollTimer)
|
|
576
|
+
return;
|
|
577
|
+
clearInterval(this.pollTimer);
|
|
578
|
+
this.pollTimer = undefined;
|
|
579
|
+
}
|
|
580
|
+
async poll(context) {
|
|
581
|
+
for (const file of this.list(context)) {
|
|
582
|
+
const nextSnapshot = this.snapshotFromInfo(file);
|
|
583
|
+
const previous = this.snapshots.get(file.key);
|
|
584
|
+
if (sameSnapshot(previous, nextSnapshot))
|
|
585
|
+
continue;
|
|
586
|
+
this.snapshots.set(file.key, nextSnapshot);
|
|
587
|
+
const eventType = previous?.linkState !== "orphaned" && file.linkState === "orphaned"
|
|
588
|
+
? "context-file.source_orphaned"
|
|
589
|
+
: "context-file.external_updated";
|
|
590
|
+
emitContextFileEvent(context, eventType, "filesystem", undefined, contextFilePayload(file));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
resolveCatalogInfo(context, file) {
|
|
594
|
+
const managed = this.managed.get(file.key);
|
|
595
|
+
if (managed)
|
|
596
|
+
return this.resolveManagedInfo(context, managed);
|
|
597
|
+
return this.resolvePluginInfo(file);
|
|
598
|
+
}
|
|
599
|
+
resolvePluginInfo(file) {
|
|
600
|
+
const absolutePath = this.resolveCatalogPath(file.path);
|
|
601
|
+
const snapshot = snapshotFromSync(absolutePath);
|
|
602
|
+
return {
|
|
603
|
+
key: file.key,
|
|
604
|
+
label: file.label,
|
|
605
|
+
path: file.path,
|
|
606
|
+
absolutePath,
|
|
607
|
+
source: "plugin",
|
|
608
|
+
scope: file.scope ?? "global",
|
|
609
|
+
agentProfileName: file.agentProfileName,
|
|
610
|
+
managed: false,
|
|
611
|
+
dynamic: false,
|
|
612
|
+
editable: false,
|
|
613
|
+
removable: false,
|
|
614
|
+
exists: snapshot.exists,
|
|
615
|
+
bytes: snapshot.bytes,
|
|
616
|
+
updatedAt: snapshot.updatedAt,
|
|
617
|
+
version: snapshot.version,
|
|
618
|
+
sourceRef: file.pluginId ? this.pluginSourceRef(file) : undefined,
|
|
619
|
+
sourceHash: snapshot.version,
|
|
620
|
+
linkState: "plugin-only",
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
resolveManagedInfo(context, file) {
|
|
624
|
+
const record = this.ensureManagedRecordSynced(file);
|
|
625
|
+
const snapshot = snapshotFromSync(record.managedPath);
|
|
626
|
+
const activeRevision = record.activeRevisionId ? this.store.getRevision(record.activeRevisionId) : undefined;
|
|
627
|
+
const sourceDescriptor = record.sourceRef ? this.resolveLiveSource(context, record.sourceRef) : undefined;
|
|
628
|
+
const sourceHash = sourceDescriptor?.contentHash ?? record.sourceHash;
|
|
629
|
+
const workingHash = snapshot.version ?? activeRevision?.contentHash;
|
|
630
|
+
const linkState = this.computeLinkState(record, sourceDescriptor, workingHash);
|
|
631
|
+
return {
|
|
632
|
+
key: record.key,
|
|
633
|
+
label: record.label,
|
|
634
|
+
path: record.managedPath,
|
|
635
|
+
absolutePath: record.managedPath,
|
|
636
|
+
source: "managed",
|
|
637
|
+
scope: record.scope,
|
|
638
|
+
agentProfileName: record.agentProfileName,
|
|
639
|
+
managed: true,
|
|
640
|
+
dynamic: true,
|
|
641
|
+
editable: true,
|
|
642
|
+
removable: true,
|
|
643
|
+
exists: snapshot.exists,
|
|
644
|
+
bytes: snapshot.bytes ?? (activeRevision ? Buffer.byteLength(activeRevision.content, "utf8") : undefined),
|
|
645
|
+
updatedAt: snapshot.updatedAt ?? record.updatedAt,
|
|
646
|
+
version: snapshot.version ?? activeRevision?.contentHash,
|
|
647
|
+
sourceRef: record.sourceRef,
|
|
648
|
+
sourceHash,
|
|
649
|
+
linkState,
|
|
650
|
+
activeRevisionId: record.activeRevisionId,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
computeLinkState(record, sourceDescriptor, workingHash) {
|
|
654
|
+
if (!record.sourceRef)
|
|
655
|
+
return "managed-unlinked";
|
|
656
|
+
if (!sourceDescriptor?.exists || !sourceDescriptor.contentHash)
|
|
657
|
+
return "orphaned";
|
|
658
|
+
if (record.sourceHash && sourceDescriptor.contentHash !== record.sourceHash)
|
|
659
|
+
return "linked-stale";
|
|
660
|
+
return workingHash === sourceDescriptor.contentHash ? "linked-clean" : "linked-dirty";
|
|
661
|
+
}
|
|
662
|
+
ensureManagedRecordSynced(record) {
|
|
663
|
+
const snapshot = snapshotFromSync(record.managedPath);
|
|
664
|
+
if (!snapshot.exists || !snapshot.version)
|
|
665
|
+
return record;
|
|
666
|
+
const activeRevision = record.activeRevisionId ? this.store.getRevision(record.activeRevisionId) : undefined;
|
|
667
|
+
if (activeRevision?.contentHash === snapshot.version)
|
|
668
|
+
return record;
|
|
669
|
+
const revision = this.store.appendRevision({
|
|
670
|
+
contextFileKey: record.key,
|
|
671
|
+
kind: "working",
|
|
672
|
+
contentHash: snapshot.version,
|
|
673
|
+
content: snapshot.content ?? "",
|
|
674
|
+
basedOnRevisionId: record.activeRevisionId,
|
|
675
|
+
sourceHashAtCreation: record.sourceHash,
|
|
676
|
+
note: activeRevision ? "External file update detected" : "Recovered working content from disk",
|
|
677
|
+
});
|
|
678
|
+
const updated = this.store.updateFile({
|
|
679
|
+
...record,
|
|
680
|
+
activeRevisionId: revision.id,
|
|
681
|
+
updatedAt: snapshot.updatedAt ?? new Date().toISOString(),
|
|
682
|
+
});
|
|
683
|
+
this.upsertManaged(updated);
|
|
684
|
+
return updated;
|
|
685
|
+
}
|
|
686
|
+
currentWorkingContent(record) {
|
|
687
|
+
const snapshot = snapshotFromSync(record.managedPath);
|
|
688
|
+
if (snapshot.exists)
|
|
689
|
+
return snapshot.content ?? "";
|
|
690
|
+
const revision = record.activeRevisionId ? this.store.getRevision(record.activeRevisionId) : undefined;
|
|
691
|
+
return revision?.content ?? "";
|
|
692
|
+
}
|
|
693
|
+
async writeWorkingContent(record, content, options) {
|
|
694
|
+
await mkdir(dirname(record.managedPath), { recursive: true });
|
|
695
|
+
await writeFile(record.managedPath, content, "utf8");
|
|
696
|
+
const revision = this.store.appendRevision({
|
|
697
|
+
contextFileKey: record.key,
|
|
698
|
+
kind: "working",
|
|
699
|
+
contentHash: hashContextFileContent(content),
|
|
700
|
+
content,
|
|
701
|
+
actorId: options.actorId,
|
|
702
|
+
basedOnRevisionId: options.basedOnRevisionId ?? record.activeRevisionId,
|
|
703
|
+
sourceHashAtCreation: options.sourceHash ?? record.sourceHash,
|
|
704
|
+
note: options.note,
|
|
705
|
+
});
|
|
706
|
+
const updated = this.store.updateFile({
|
|
707
|
+
...record,
|
|
708
|
+
activeRevisionId: revision.id,
|
|
709
|
+
sourceHash: options.sourceHash ?? record.sourceHash,
|
|
710
|
+
updatedAt: new Date().toISOString(),
|
|
711
|
+
});
|
|
712
|
+
this.upsertManaged(updated);
|
|
713
|
+
return updated;
|
|
714
|
+
}
|
|
715
|
+
ensureSourceSnapshot(record, source, actorId, note) {
|
|
716
|
+
const existing = source.contentHash ? this.store.findLatestSourceSnapshot(record.key, source.contentHash) : undefined;
|
|
717
|
+
if (existing)
|
|
718
|
+
return existing;
|
|
719
|
+
return this.store.appendRevision({
|
|
720
|
+
contextFileKey: record.key,
|
|
721
|
+
kind: "source-snapshot",
|
|
722
|
+
contentHash: source.contentHash ?? hashContextFileContent(source.content ?? ""),
|
|
723
|
+
content: source.content ?? "",
|
|
724
|
+
actorId,
|
|
725
|
+
sourceHashAtCreation: source.contentHash,
|
|
726
|
+
note,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
revisionInfoFromRecord(revision, activeRevisionId) {
|
|
730
|
+
return {
|
|
731
|
+
id: revision.id,
|
|
732
|
+
kind: revision.kind,
|
|
733
|
+
contentHash: revision.contentHash,
|
|
734
|
+
createdAt: revision.createdAt,
|
|
735
|
+
actorId: revision.actorId,
|
|
736
|
+
basedOnRevisionId: revision.basedOnRevisionId,
|
|
737
|
+
sourceHashAtCreation: revision.sourceHashAtCreation,
|
|
738
|
+
note: revision.note,
|
|
739
|
+
content: revision.content,
|
|
740
|
+
active: revision.id === activeRevisionId,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
resolveDiffSide(context, record, kind) {
|
|
744
|
+
if (kind === "working") {
|
|
745
|
+
const content = this.currentWorkingContent(record);
|
|
746
|
+
return { content, contentHash: hashContextFileContent(content) };
|
|
747
|
+
}
|
|
748
|
+
const liveSource = record.sourceRef ? this.resolveLiveSource(context, record.sourceRef) : undefined;
|
|
749
|
+
if (liveSource?.exists) {
|
|
750
|
+
return {
|
|
751
|
+
content: liveSource.content ?? "",
|
|
752
|
+
contentHash: liveSource.contentHash,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
const snapshot = record.sourceHash
|
|
756
|
+
? this.store.findLatestSourceSnapshot(record.key, record.sourceHash)
|
|
757
|
+
: this.store.findLatestSourceSnapshot(record.key);
|
|
758
|
+
if (!snapshot)
|
|
759
|
+
throw new PiboWebHttpError("No source snapshot available for diff", 404);
|
|
760
|
+
return {
|
|
761
|
+
content: snapshot.content,
|
|
762
|
+
contentHash: snapshot.contentHash,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
requirePluginCatalogFile(context, key) {
|
|
766
|
+
const file = (context.channelContext.getCapabilityCatalog?.().contextFiles ?? []).find((candidate) => candidate.key === key);
|
|
767
|
+
if (!file || (file.source ?? "plugin") !== "plugin")
|
|
768
|
+
throw new PiboWebHttpError(`Unknown plugin context file "${key}"`, 404);
|
|
769
|
+
return file;
|
|
770
|
+
}
|
|
771
|
+
requireLivePluginSource(file) {
|
|
772
|
+
const descriptor = this.resolvePluginDescriptor(file);
|
|
773
|
+
if (!descriptor.exists || !descriptor.contentHash)
|
|
774
|
+
throw new PiboWebHttpError(`Plugin context file "${file.key}" is not readable`, 404);
|
|
775
|
+
return descriptor;
|
|
776
|
+
}
|
|
777
|
+
requireLiveSourceForRecord(context, record) {
|
|
778
|
+
const descriptor = record.sourceRef ? this.resolveLiveSource(context, record.sourceRef) : undefined;
|
|
779
|
+
if (!descriptor?.exists || !descriptor.contentHash)
|
|
780
|
+
throw new PiboWebHttpError("Linked source is not available", 404);
|
|
781
|
+
return descriptor;
|
|
782
|
+
}
|
|
783
|
+
resolveLiveSource(context, sourceRef) {
|
|
784
|
+
const parsed = parseSourceRef(sourceRef);
|
|
785
|
+
if (!parsed)
|
|
786
|
+
return undefined;
|
|
787
|
+
const file = (context.channelContext.getCapabilityCatalog?.().contextFiles ?? []).find((candidate) => ((candidate.source ?? "plugin") === "plugin"
|
|
788
|
+
&& candidate.key === parsed.key
|
|
789
|
+
&& candidate.pluginId === parsed.pluginId));
|
|
790
|
+
return file ? this.resolvePluginDescriptor(file) : undefined;
|
|
791
|
+
}
|
|
792
|
+
resolvePluginDescriptor(file) {
|
|
793
|
+
const absolutePath = this.resolveCatalogPath(file.path);
|
|
794
|
+
const snapshot = snapshotFromSync(absolutePath);
|
|
795
|
+
return {
|
|
796
|
+
key: file.key,
|
|
797
|
+
label: file.label,
|
|
798
|
+
absolutePath,
|
|
799
|
+
exists: snapshot.exists,
|
|
800
|
+
content: snapshot.content,
|
|
801
|
+
contentHash: snapshot.version,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
pluginSourceRef(file) {
|
|
805
|
+
if (!file.pluginId)
|
|
806
|
+
throw new PiboWebHttpError(`Plugin id missing for context file "${file.key}"`, 500);
|
|
807
|
+
return `plugin:${file.pluginId}:${file.key}`;
|
|
808
|
+
}
|
|
809
|
+
resolveCatalogPath(path) {
|
|
810
|
+
return isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
811
|
+
}
|
|
812
|
+
upsertManaged(file) {
|
|
813
|
+
this.managed.set(file.key, file);
|
|
814
|
+
this.api.upsertContextFile(profileForManaged(file));
|
|
815
|
+
}
|
|
816
|
+
requireInfo(context, key) {
|
|
817
|
+
const file = this.list(context).find((item) => item.key === key);
|
|
818
|
+
if (!file)
|
|
819
|
+
throw new PiboWebHttpError(`Unknown context file "${key}"`, 404);
|
|
820
|
+
return file;
|
|
821
|
+
}
|
|
822
|
+
requireManagedRecord(key) {
|
|
823
|
+
const record = this.managed.get(key);
|
|
824
|
+
if (!record)
|
|
825
|
+
throw new PiboWebHttpError("Only managed context files can be changed", 403);
|
|
826
|
+
return record;
|
|
827
|
+
}
|
|
828
|
+
resolveManagedDir(scope, agentProfileName) {
|
|
829
|
+
if (scope === "global")
|
|
830
|
+
return this.paths.globalDir;
|
|
831
|
+
if (!agentProfileName)
|
|
832
|
+
throw new PiboWebHttpError("agentProfileName is required for agent context files", 400);
|
|
833
|
+
return resolve(this.paths.agentWorkspaceRoot, slugSegment(agentProfileName), "context-files");
|
|
834
|
+
}
|
|
835
|
+
snapshotFromInfo(file) {
|
|
836
|
+
return {
|
|
837
|
+
exists: file.exists,
|
|
838
|
+
bytes: file.bytes,
|
|
839
|
+
updatedAt: file.updatedAt,
|
|
840
|
+
version: file.version,
|
|
841
|
+
linkState: file.linkState,
|
|
842
|
+
sourceHash: file.sourceHash,
|
|
843
|
+
activeRevisionId: file.activeRevisionId,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
emitChanged(context, type, source, actorId, file) {
|
|
847
|
+
emitContextFileEvent(context, type, source, actorId, contextFilePayload(file));
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
class ContextFileConflictError extends Error {
|
|
851
|
+
document;
|
|
852
|
+
constructor(document) {
|
|
853
|
+
super("Context file changed before save");
|
|
854
|
+
this.document = document;
|
|
855
|
+
this.name = "ContextFileConflictError";
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function parseSourceRef(value) {
|
|
859
|
+
if (!value.startsWith("plugin:"))
|
|
860
|
+
return undefined;
|
|
861
|
+
const remainder = value.slice("plugin:".length);
|
|
862
|
+
const separator = remainder.indexOf(":");
|
|
863
|
+
if (separator === -1)
|
|
864
|
+
return undefined;
|
|
865
|
+
const pluginId = remainder.slice(0, separator);
|
|
866
|
+
const key = remainder.slice(separator + 1);
|
|
867
|
+
if (!pluginId || !key)
|
|
868
|
+
return undefined;
|
|
869
|
+
return { pluginId, key };
|
|
870
|
+
}
|
|
871
|
+
function apiPath(pathname) {
|
|
872
|
+
if (!pathname.startsWith(`${CONTEXT_FILES_API_PREFIX}/`))
|
|
873
|
+
return undefined;
|
|
874
|
+
const suffix = pathname.slice(CONTEXT_FILES_API_PREFIX.length + 1);
|
|
875
|
+
if (!suffix)
|
|
876
|
+
return undefined;
|
|
877
|
+
const [encodedKey, ...actionParts] = suffix.split("/");
|
|
878
|
+
if (!encodedKey)
|
|
879
|
+
return undefined;
|
|
880
|
+
return {
|
|
881
|
+
key: decodeURIComponent(encodedKey),
|
|
882
|
+
...(actionParts.length > 0 ? { action: actionParts.join("/") } : {}),
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function isAppPath(pathname) {
|
|
886
|
+
return pathname === CONTEXT_FILES_MOUNT_PATH || pathname.startsWith(`${CONTEXT_FILES_MOUNT_PATH}/`);
|
|
887
|
+
}
|
|
888
|
+
function createContextFilesWebApp(service) {
|
|
889
|
+
return {
|
|
890
|
+
name: CONTEXT_FILES_APP_NAME,
|
|
891
|
+
mountPath: CONTEXT_FILES_MOUNT_PATH,
|
|
892
|
+
apiPrefix: CONTEXT_FILES_API_PREFIX,
|
|
893
|
+
async handleRequest(request, context) {
|
|
894
|
+
const url = new URL(request.url);
|
|
895
|
+
const asset = builtAsset(url.pathname);
|
|
896
|
+
if (asset)
|
|
897
|
+
return asset;
|
|
898
|
+
if (isAppPath(url.pathname) && request.method === "GET") {
|
|
899
|
+
return responseBuiltIndex() ?? fallbackHtml();
|
|
900
|
+
}
|
|
901
|
+
if (url.pathname === CONTEXT_FILES_API_PREFIX && request.method === "GET") {
|
|
902
|
+
await context.requireSession({ request });
|
|
903
|
+
service.startWatcher(context);
|
|
904
|
+
return responseJson({ files: service.list(context) });
|
|
905
|
+
}
|
|
906
|
+
if (url.pathname === CONTEXT_FILES_API_PREFIX && request.method === "POST") {
|
|
907
|
+
const webSession = await context.requireSession({ request });
|
|
908
|
+
service.startWatcher(context);
|
|
909
|
+
const body = await readJsonBody(request);
|
|
910
|
+
return responseJson({ file: await service.create(context, body, webSession) }, { status: 201 });
|
|
911
|
+
}
|
|
912
|
+
if (url.pathname === `${CONTEXT_FILES_API_PREFIX}/events` && request.method === "GET") {
|
|
913
|
+
await context.requireSession({ request });
|
|
914
|
+
service.startWatcher(context);
|
|
915
|
+
return eventStream(context);
|
|
916
|
+
}
|
|
917
|
+
const path = apiPath(url.pathname);
|
|
918
|
+
if (!path || path.key === "events")
|
|
919
|
+
return undefined;
|
|
920
|
+
if (!path.action && request.method === "GET") {
|
|
921
|
+
await context.requireSession({ request });
|
|
922
|
+
service.startWatcher(context);
|
|
923
|
+
return responseJson({ file: await service.read(context, path.key) });
|
|
924
|
+
}
|
|
925
|
+
if (!path.action && request.method === "PUT") {
|
|
926
|
+
const webSession = await context.requireSession({ request });
|
|
927
|
+
service.startWatcher(context);
|
|
928
|
+
const body = await readJsonBody(request);
|
|
929
|
+
try {
|
|
930
|
+
return responseJson({ file: await service.update(context, path.key, body, webSession) });
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
if (error instanceof ContextFileConflictError) {
|
|
934
|
+
return responseJson({ error: error.message, file: error.document }, { status: 409 });
|
|
935
|
+
}
|
|
936
|
+
throw error;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (!path.action && request.method === "PATCH") {
|
|
940
|
+
const webSession = await context.requireSession({ request });
|
|
941
|
+
service.startWatcher(context);
|
|
942
|
+
const body = await readJsonBody(request);
|
|
943
|
+
return responseJson({ file: await service.updateMetadata(context, path.key, body, webSession) });
|
|
944
|
+
}
|
|
945
|
+
if (!path.action && request.method === "DELETE") {
|
|
946
|
+
const webSession = await context.requireSession({ request });
|
|
947
|
+
service.startWatcher(context);
|
|
948
|
+
const body = await readJsonBody(request);
|
|
949
|
+
return responseJson(await service.remove(context, path.key, body, webSession));
|
|
950
|
+
}
|
|
951
|
+
if (path.action === "link-from-plugin" && request.method === "POST") {
|
|
952
|
+
const webSession = await context.requireSession({ request });
|
|
953
|
+
service.startWatcher(context);
|
|
954
|
+
const body = await readJsonBody(request);
|
|
955
|
+
return responseJson({ file: await service.createLinkedCopy(context, path.key, body, webSession) }, { status: 201 });
|
|
956
|
+
}
|
|
957
|
+
if (path.action === "reset-to-source" && request.method === "POST") {
|
|
958
|
+
const webSession = await context.requireSession({ request });
|
|
959
|
+
service.startWatcher(context);
|
|
960
|
+
return responseJson({ file: await service.resetToSource(context, path.key, webSession) });
|
|
961
|
+
}
|
|
962
|
+
if (path.action === "restore-revision" && request.method === "POST") {
|
|
963
|
+
const webSession = await context.requireSession({ request });
|
|
964
|
+
service.startWatcher(context);
|
|
965
|
+
const body = await readJsonBody(request);
|
|
966
|
+
return responseJson({ file: await service.restoreRevision(context, path.key, body, webSession) });
|
|
967
|
+
}
|
|
968
|
+
if (path.action === "revisions" && request.method === "GET") {
|
|
969
|
+
await context.requireSession({ request });
|
|
970
|
+
service.startWatcher(context);
|
|
971
|
+
return responseJson(service.listRevisions(context, path.key));
|
|
972
|
+
}
|
|
973
|
+
if (path.action === "diff" && request.method === "GET") {
|
|
974
|
+
await context.requireSession({ request });
|
|
975
|
+
service.startWatcher(context);
|
|
976
|
+
const base = normalizeDiffSide(url.searchParams.get("base"), "source");
|
|
977
|
+
const target = normalizeDiffSide(url.searchParams.get("target"), "working");
|
|
978
|
+
return responseJson(service.diff(context, path.key, base, target));
|
|
979
|
+
}
|
|
980
|
+
if (path.action === "adopt-source" && request.method === "POST") {
|
|
981
|
+
const webSession = await context.requireSession({ request });
|
|
982
|
+
service.startWatcher(context);
|
|
983
|
+
return responseJson({ file: await service.adoptSource(context, path.key, webSession) });
|
|
984
|
+
}
|
|
985
|
+
return undefined;
|
|
986
|
+
},
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
export function createPiboContextFilesPlugin(options = {}) {
|
|
990
|
+
const paths = resolveContextFilesPaths(options);
|
|
991
|
+
return definePiboPlugin({
|
|
992
|
+
id: "pibo.context-files",
|
|
993
|
+
name: "Pibo Context Files",
|
|
994
|
+
register(api) {
|
|
995
|
+
const service = new ContextFileService(paths, api);
|
|
996
|
+
api.registerWebApp(createContextFilesWebApp(service));
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
}
|