@tequila99/githuman 0.11.0 → 0.12.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/README.md +62 -2
- package/dist/cli/commands/mcp.js +112 -0
- package/dist/cli/commands/pi-acp.js +21 -0
- package/dist/cli/config.js +7 -1
- package/dist/cli/index.js +19 -8
- package/dist/cli/server-runtime.js +1 -0
- package/dist/server/adapters/agents/artifact-permission.js +88 -0
- package/dist/server/adapters/pi-acp/agent.js +98 -0
- package/dist/server/adapters/pi-acp/compatibility.js +57 -0
- package/dist/server/adapters/pi-acp/control.js +76 -0
- package/dist/server/adapters/pi-acp/errors.js +17 -0
- package/dist/server/adapters/pi-acp/extension.js +255 -0
- package/dist/server/adapters/pi-acp/launch.js +16 -0
- package/dist/server/adapters/pi-acp/mcp.js +153 -0
- package/dist/server/adapters/pi-acp/process.js +199 -0
- package/dist/server/adapters/pi-acp/session.js +409 -0
- package/dist/server/adapters/pi-acp/vendor/LICENSE +21 -0
- package/dist/server/adapters/pi-acp/vendor/README.md +29 -0
- package/dist/server/adapters/pi-acp/vendor/prompt.js +56 -0
- package/dist/server/app.js +94 -5
- package/dist/server/config/agents.js +4 -6
- package/dist/server/db/artifact-graph-migrations.js +56 -0
- package/dist/server/db/artifact-migrations.js +70 -0
- package/dist/server/db/artifact-review-migrations.js +24 -0
- package/dist/server/db/artifact-search-migrations.js +31 -0
- package/dist/server/db/artifact-validation-migrations.js +70 -0
- package/dist/server/db/index.js +3 -0
- package/dist/server/db/migrations.js +10 -0
- package/dist/server/db/sqlite-error.js +15 -0
- package/dist/server/db/transaction.js +40 -0
- package/dist/server/errors/artifacts.js +28 -0
- package/dist/server/errors/database.js +2 -0
- package/dist/server/hooks/access/require-access.js +8 -0
- package/dist/server/repositories/artifact-catalog.repo.js +283 -0
- package/dist/server/repositories/artifact-files.repo.js +181 -0
- package/dist/server/repositories/artifact-graph.repo.js +50 -0
- package/dist/server/repositories/artifact-search.repo.js +192 -0
- package/dist/server/repositories/artifact-transition.repo.js +85 -0
- package/dist/server/repositories/artifact-validation.repo.js +146 -0
- package/dist/server/repositories/notes-catalog.repo.js +2 -10
- package/dist/server/repositories/task.repo.js +114 -0
- package/dist/server/routes/agent.js +8 -1
- package/dist/server/routes/artifact-actions.js +129 -0
- package/dist/server/routes/artifact-agent.js +183 -0
- package/dist/server/routes/artifact-definitions.js +163 -0
- package/dist/server/routes/artifact-transitions.js +80 -0
- package/dist/server/routes/artifacts.js +111 -0
- package/dist/server/routes/events.js +5 -1
- package/dist/server/routes/project-files.js +90 -0
- package/dist/server/services/agent/agent-session.js +180 -10
- package/dist/server/services/agent/context-builder.js +30 -2
- package/dist/server/services/agent/session-registry.js +106 -2
- package/dist/server/services/agent/turn-task-context.js +1 -0
- package/dist/server/services/artifact-authorization.service.js +174 -0
- package/dist/server/services/artifact-definition.service.js +96 -0
- package/dist/server/services/artifact-export.service.js +44 -0
- package/dist/server/services/artifact-graph-validation.js +497 -0
- package/dist/server/services/artifact-graph.service.js +81 -0
- package/dist/server/services/artifact-lifecycle.service.js +343 -0
- package/dist/server/services/artifact-maintenance.service.js +55 -0
- package/dist/server/services/artifact-search.service.js +331 -0
- package/dist/server/services/artifact-storage.service.js +646 -0
- package/dist/server/services/artifact-transition.service.js +607 -0
- package/dist/server/services/artifact-type.service.js +74 -0
- package/dist/server/services/artifact-validation-jobs.service.js +518 -0
- package/dist/server/services/device-connections.js +9 -1
- package/dist/server/services/project-files.service.js +144 -0
- package/dist/server/services/review.service.js +6 -5
- package/dist/server/services/task.service.js +225 -0
- package/dist/server/utils/artifact-actor.js +13 -0
- package/dist/server/utils/artifact-page.js +42 -0
- package/dist/server/utils/take-within-bytes.js +16 -0
- package/dist/server/utils/text.js +7 -0
- package/dist/shared/agents/schemas.js +27 -0
- package/dist/shared/app/storage-constants.js +2 -0
- package/dist/shared/artifact-tools/descriptions.js +11 -0
- package/dist/shared/artifact-tools/instructions.js +14 -0
- package/dist/shared/artifact-tools/schemas.js +119 -0
- package/dist/shared/artifact-transitions/constants.js +21 -0
- package/dist/shared/artifact-transitions/schemas.js +111 -0
- package/dist/shared/artifact-transitions/transitions.js +61 -0
- package/dist/shared/artifact-transitions/types.js +1 -0
- package/dist/shared/artifacts/constants.js +34 -0
- package/dist/shared/artifacts/mcp-constants.js +24 -0
- package/dist/shared/artifacts/normalize.js +9 -0
- package/dist/shared/artifacts/schemas.js +820 -0
- package/dist/shared/artifacts/search.js +101 -0
- package/dist/shared/artifacts/types.js +1 -0
- package/dist/shared/events/schemas.js +1 -1
- package/dist/shared/events/types.js +1 -0
- package/dist/shared/git/constants.js +2 -0
- package/dist/shared/project-files/constants.js +18 -0
- package/dist/shared/project-files/schemas.js +35 -0
- package/dist/shared/project-files/types.js +1 -0
- package/dist/shared/tasks/schemas.js +261 -0
- package/dist/shared/tasks/types.js +1 -0
- package/dist/shared/utils/artifact-body.js +10 -0
- package/dist/shared/utils/canonical-json.js +22 -0
- package/dist/shared/utils/is-safe-prefixed-filename.js +10 -0
- package/dist/shared/utils/is-uuid.js +4 -2
- package/dist/shared/utils/is-valid-artifact-type-id.js +9 -0
- package/dist/shared/utils/is-valid-filename-prefix.js +13 -0
- package/dist/shared/utils/markdown-path.js +4 -0
- package/dist/shared/utils/pdf-path.js +4 -0
- package/dist/web/assets/AccessPage-Dvj0aCkm.js +1 -0
- package/dist/web/assets/AddButton-CMC9qf51.js +4 -0
- package/dist/web/assets/AddToAgentButton-BitTdCEg.js +1 -0
- package/dist/web/assets/AppPage-Cozv2vqG.js +1 -0
- package/dist/web/assets/ArtifactDefinitionPicker-BQOoU60k.js +3 -0
- package/dist/web/assets/ArtifactDefinitionPicker-D3qdrxlC.css +1 -0
- package/dist/web/assets/ArtifactsPage-BJR-XYYs.js +2 -0
- package/dist/web/assets/ArtifactsPage-Cu1xdYNf.css +1 -0
- package/dist/web/assets/CancelButton-mkxRHwoZ.js +1 -0
- package/dist/web/assets/ClosePopup-BtDeDZsk.js +1 -0
- package/dist/web/assets/CreateButton-zCmb7snC.js +1 -0
- package/dist/web/assets/CreateReviewFabButton-BCg1TvFh.js +1 -0
- package/dist/web/assets/DialogHeader-CPL9OtDV.css +1 -0
- package/dist/web/assets/DialogHeader-CQvAZVx7.js +1 -0
- package/dist/web/assets/{ErrorNotFound-DtnO85PU.js → ErrorNotFound-fXS367lZ.js} +1 -1
- package/dist/web/assets/{IndexPage-D5PvyGrX.css → IndexPage-5V5GLtgi.css} +1 -1
- package/dist/web/assets/IndexPage-DMTZyWQA.js +1 -0
- package/dist/web/assets/MainLayout-DWJ_-EMV.js +7 -0
- package/dist/web/assets/MainLayout-Dy74m_q1.css +1 -0
- package/dist/web/assets/NoteVisualEditor-5rHBQ3j0.js +1054 -0
- package/dist/web/assets/{NoteVisualEditor-BYikrY7Q.css → NoteVisualEditor-DE4ACTRJ.css} +1 -1
- package/dist/web/assets/NotesPage-C_eoCUl7.css +1 -0
- package/dist/web/assets/NotesPage-CjaxjLBW.js +4 -0
- package/dist/web/assets/{PdfPreview-kL_nk9my.js → PdfPreview-2NQ5gkjh.js} +2 -2
- package/dist/web/assets/ProjectFilePage-B-WUUxnq.js +1 -0
- package/dist/web/assets/QBtn-V3VXtJXi.js +1 -0
- package/dist/web/assets/QFab-CxpedhPI.js +1 -0
- package/dist/web/assets/QForm-ClpSneeD.js +1 -0
- package/dist/web/assets/QInput-BnqLJGXl.js +1 -0
- package/dist/web/assets/QScrollArea-js5dkUz1.js +1 -0
- package/dist/web/assets/QSpace-C7iAnGlU.js +1 -0
- package/dist/web/assets/QTabs-DIy5gWMb.js +1 -0
- package/dist/web/assets/QVirtualScroll-DkD0TczV.js +1 -0
- package/dist/web/assets/ReviewDetailPage-BgxcsTqY.css +1 -0
- package/dist/web/assets/ReviewDetailPage-Bmc8DmSu.js +1 -0
- package/dist/web/assets/ReviewsPage-DR1IWCmu.js +1 -0
- package/dist/web/assets/SearchInput-BRyN93Z1.js +1 -0
- package/dist/web/assets/StatusSwitcher-CpkH8BuC.js +1 -0
- package/dist/web/assets/{StatusSwitcher-D6i8_OfR.css → StatusSwitcher-DaE1ufAA.css} +1 -1
- package/dist/web/assets/TagFilterField-Bl3FBkB5.js +1 -0
- package/dist/web/assets/TagFilterField-DjUH0m3_.css +1 -0
- package/dist/web/assets/{TerminalView-617DZuPt.css → TerminalView-BCeke4xd.css} +1 -1
- package/dist/web/assets/{TerminalView-BsO_SZut.js → TerminalView-tEyLM5qZ.js} +12 -12
- package/dist/web/assets/{abnfDiagram-O67JEVCF-qUSrlp_c.js → abnfDiagram-O67JEVCF-DN9xiV9p.js} +1 -1
- package/dist/web/assets/{access-store-Q8nkv4Nb.js → access-store-CwkOl71l.js} +1 -1
- package/dist/web/assets/{agent-store-D2jgeBgi.js → agent-store-Grbx4jkz.js} +5 -5
- package/dist/web/assets/architecture-7GRP2DOG-DmaYCP8T.js +1 -0
- package/dist/web/assets/{architectureDiagram-NJMV4G6O-D7RC6OS2.js → architectureDiagram-NJMV4G6O-BVAJOucG.js} +1 -1
- package/dist/web/assets/{blockDiagram-BEXU5L5S-CmNtTpfK.js → blockDiagram-BEXU5L5S-TJAfSX44.js} +1 -1
- package/dist/web/assets/{c4Diagram-YGBWAQC7-CKCcu1qx.js → c4Diagram-YGBWAQC7-5UHyT_tW.js} +1 -1
- package/dist/web/assets/channel-B4_5H-fb.js +1 -0
- package/dist/web/assets/{chunk-3FUC2YCW-Drz6oeKx.js → chunk-3FUC2YCW-CelgLyJi.js} +1 -1
- package/dist/web/assets/{chunk-5DYCD2WN-RItzEgV_.js → chunk-5DYCD2WN-bhEAVIbQ.js} +1 -1
- package/dist/web/assets/{chunk-742MDFTN-D6hKdOv7.js → chunk-742MDFTN-ByLz4EcD.js} +1 -1
- package/dist/web/assets/{chunk-7INBJB4K-Dk7-Eior.js → chunk-7INBJB4K-CK2i0csa.js} +1 -1
- package/dist/web/assets/{chunk-7M6MHVWA-Cw8E0Nkn.js → chunk-7M6MHVWA-CoCiujjC.js} +1 -1
- package/dist/web/assets/{chunk-7PRAP22T-t-mTeNXF.js → chunk-7PRAP22T-CY4NCrCY.js} +1 -1
- package/dist/web/assets/{chunk-GTNCS2PH-C7j2atV8.js → chunk-GTNCS2PH-DMB7bQGB.js} +1 -1
- package/dist/web/assets/{chunk-GWA4HPMP-CH9T9LZO.js → chunk-GWA4HPMP-F6qclPTR.js} +1 -1
- package/dist/web/assets/{chunk-MBY4JIJT-yXOO37xD.js → chunk-MBY4JIJT-CSiiBtlX.js} +1 -1
- package/dist/web/assets/{chunk-NETBCI7D-C-s2UtAR.js → chunk-NETBCI7D-CBh7GChL.js} +1 -1
- package/dist/web/assets/{chunk-O7XYJQB3-5Me_aV8o.js → chunk-O7XYJQB3-B2aqz0sN.js} +1 -1
- package/dist/web/assets/{chunk-UA2S7LBM-BQaibeff.js → chunk-UA2S7LBM-B_-P_eR0.js} +1 -1
- package/dist/web/assets/{chunk-WEXAMYUT-UBcOE4Lf.js → chunk-WEXAMYUT-19nMFxzK.js} +1 -1
- package/dist/web/assets/{chunk-Z7XXMR3K-BsA8o3Pt.js → chunk-Z7XXMR3K-Co7gzRY1.js} +1 -1
- package/dist/web/assets/{chunk-ZIGJFQKS--KwSI1U0.js → chunk-ZIGJFQKS-Eoi4K5bK.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-NBCMYWYE-pMF8oCmq.js → classDiagram-v2-NBCMYWYE-CfFGzL-A.js} +1 -1
- package/dist/web/assets/copy-code-DaqEfdXL.js +1 -0
- package/dist/web/assets/create-DedXY-KP.js +1 -0
- package/dist/web/assets/{cynefin-OW5HDTMX-DFgtXolF.js → cynefin-OW5HDTMX-DdZXl3KU.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-VND7K2PF-CPY0JRyq.js → cynefinDiagram-VND7K2PF-WU21nYH-.js} +1 -1
- package/dist/web/assets/{dagre-6A5THRUB-KdjJKAB7.js → dagre-6A5THRUB-48z3F8Pl.js} +1 -1
- package/dist/web/assets/date-DZIuZp-w.js +1 -0
- package/dist/web/assets/devices-store-Bfk2wdrQ.js +1 -0
- package/dist/web/assets/{diagram-22UHCM2B-BDcOcvF_.js → diagram-22UHCM2B-B0AZgJW9.js} +1 -1
- package/dist/web/assets/{diagram-3UASUU5V-6j2nlZL3.js → diagram-3UASUU5V-CzEzrxuO.js} +1 -1
- package/dist/web/assets/{diagram-ATOU4E4O-BN8cdxED.js → diagram-ATOU4E4O-w6h1bD3w.js} +1 -1
- package/dist/web/assets/{diagram-CDSNMT55-cWPiXHkr.js → diagram-CDSNMT55-BdTHQd_o.js} +1 -1
- package/dist/web/assets/{diagram-MLGK6HIB-DcgkH_Vl.js → diagram-MLGK6HIB-pB4cj6c2.js} +1 -1
- package/dist/web/assets/{diagram-MPIPVDR6-D_e86L99.js → diagram-MPIPVDR6-C7C06qyV.js} +1 -1
- package/dist/web/assets/diff-store-DBDL5Hw5.js +93 -0
- package/dist/web/assets/{dist-Bdsq-Ze1.js → dist-B7Cu0FBW.js} +1 -1
- package/dist/web/assets/{dist-BVVhgjWT.js → dist-B8i8y3GY.js} +1 -1
- package/dist/web/assets/{dist-CjkUzU4s.js → dist-BNgPhnKo.js} +1 -1
- package/dist/web/assets/{dist-Bz40Nd9I.js → dist-BWoPV5P3.js} +1 -1
- package/dist/web/assets/{dist-BP40mvwJ.js → dist-CJ7isxli.js} +1 -1
- package/dist/web/assets/{dist-C9enVshI.js → dist-CTJJYvYG.js} +1 -1
- package/dist/web/assets/{dist-BwpyANeW.js → dist-CZz0KXAA.js} +1 -1
- package/dist/web/assets/{dist-CU43ytsh.js → dist-DNpwgDba.js} +1 -1
- package/dist/web/assets/{dist-BMFtkY2y.js → dist-DWCdhk-O.js} +1 -1
- package/dist/web/assets/{dist-MUQaJRtC.js → dist-DqSUdjG9.js} +1 -1
- package/dist/web/assets/{dist-CLCP7f3x.js → dist-Dy9K9LOR.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-ZINNZB2B-D8xHOFOi.js → ebnfDiagram-ZINNZB2B-75vt7539.js} +1 -1
- package/dist/web/assets/{elk-276RUBZZ-JiDwNyLy.js → elk-276RUBZZ-DXDjJVTy.js} +1 -1
- package/dist/web/assets/{erDiagram-OPXOYQCR-D3J6u81a.js → erDiagram-OPXOYQCR-tNNInTmk.js} +1 -1
- package/dist/web/assets/eventmodeling-NTZA5JFV-QMRehQtM.js +1 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-DJAgsbXC.js +1 -0
- package/dist/web/assets/{ganttDiagram-FUAMR5RP-nvAVhbgI.js → ganttDiagram-FUAMR5RP-h3E8lnCd.js} +1 -1
- package/dist/web/assets/{gitGraph-4MIJSDKK-Bv_wkB2T.js → gitGraph-4MIJSDKK-BUwY-iv2.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-X574FWY7-DxxwadhH.js → gitGraphDiagram-X574FWY7-C0AgQH5k.js} +1 -1
- package/dist/web/assets/i18n-CfTd46OX.js +1 -0
- package/dist/web/assets/icons-BoaZfGBj.js +1 -0
- package/dist/web/assets/index-BQU3KK1E.css +1 -0
- package/dist/web/assets/index-BTpokWxr.js +2 -0
- package/dist/web/assets/{info-A6RAGUB7-Dw18yeIE.js → info-A6RAGUB7-DVMZ4hdK.js} +1 -1
- package/dist/web/assets/{infoDiagram-VRGFBTTK-C3BrgjYg.js → infoDiagram-VRGFBTTK-kLaGXGQ2.js} +1 -1
- package/dist/web/assets/init-app-Bxc_l2yT.js +1 -0
- package/dist/web/assets/{is-D0waCELe.js → is-B1kBQ5Va.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-DU2p9szX.js → ishikawaDiagram-OU5B5YK6-Oj7bZlyo.js} +1 -1
- package/dist/web/assets/{journeyDiagram-ZHPQQLJL-Bs2zQnew.js → journeyDiagram-ZHPQQLJL-BG7V2Srs.js} +1 -1
- package/dist/web/assets/{kanban-definition-PNTS6WVX-pqXd5uNi.js → kanban-definition-PNTS6WVX-CIJiDE6d.js} +1 -1
- package/dist/web/assets/{line-CN4Dxpei.js → line-D2vEKYRZ.js} +1 -1
- package/dist/web/assets/markdown-Bm2wfvok.js +16 -0
- package/dist/web/assets/mdi-Byw1NZrZ.js +1 -0
- package/dist/web/assets/{mermaid-parser.core-DZqDOGRM.js → mermaid-parser.core-BcBEWjW6.js} +3 -3
- package/dist/web/assets/{mermaid.core-CUndCvkz.js → mermaid.core-DobO0deo.js} +4 -4
- package/dist/web/assets/{mindmap-definition-NLK3R4M7-DyK7YqB7.js → mindmap-definition-NLK3R4M7-y7Qz5gBF.js} +1 -1
- package/dist/web/assets/{packet-AYTQ26CC-DvlW_0BP.js → packet-AYTQ26CC-oRQgKr9d.js} +1 -1
- package/dist/web/assets/{pegDiagram-GJSIUBJH-k2e_-FWQ.js → pegDiagram-GJSIUBJH-RIU8XOHq.js} +1 -1
- package/dist/web/assets/{pie-WAS4IAKB-B99zaYjO.js → pie-WAS4IAKB-qmOlsLkx.js} +1 -1
- package/dist/web/assets/{pieDiagram-5QR66LMP-BPbS05lK.js → pieDiagram-5QR66LMP-BM932TDA.js} +1 -1
- package/dist/web/assets/pinia-BfqEuvsi.js +1 -0
- package/dist/web/assets/pinia-Cmp1GI8k.js +1 -0
- package/dist/web/assets/preview-store-D7qF6SgX.js +2 -0
- package/dist/web/assets/project-files-store-OhGs_H7U.js +1 -0
- package/dist/web/assets/{quadrantDiagram-O4NWA36T-DqNNdsd2.js → quadrantDiagram-O4NWA36T-qQZYwTy7.js} +1 -1
- package/dist/web/assets/{radar-RG4KPBEZ-BIAAr9LO.js → radar-RG4KPBEZ-Kdglk6eY.js} +1 -1
- package/dist/web/assets/{railroad-74A4TZTK-DYxr-1-f.js → railroad-74A4TZTK-kVhMYx4I.js} +1 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-MiKgjOXr.js +1 -0
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-BFL9h7Zx.js +1 -0
- package/dist/web/assets/railroad-peg-WCYAUIDC-ChGGfIn7.js +1 -0
- package/dist/web/assets/{railroadDiagram-XR7U4H2S-TNg-LqRR.js → railroadDiagram-XR7U4H2S-K9SrOGcT.js} +1 -1
- package/dist/web/assets/render-Brao9Nj2.js +1 -0
- package/dist/web/assets/repository-store-DZgQ5H7J.js +1 -0
- package/dist/web/assets/{requirementDiagram-PLB6GJNP-hKAyJR6W.js → requirementDiagram-PLB6GJNP-CtA9ZKEx.js} +1 -1
- package/dist/web/assets/runtime-core.esm-bundler-DDnHUoIy.js +1 -0
- package/dist/web/assets/{safe-storage-B1Aq5CGi.js → safe-storage-CvVvrm4v.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-IPEJSGJF-_MSL-tES.js → sankeyDiagram-IPEJSGJF-BH_t4exx.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-PO4LG4MO-Byq-joBG.js → sequenceDiagram-PO4LG4MO-hPNPqpDa.js} +1 -1
- package/dist/web/assets/single-flight-CyDZB3rb.js +1 -0
- package/dist/web/assets/{stateDiagram-v2-GCMORJYK-BR2jAgaa.js → stateDiagram-v2-GCMORJYK-BVmjZDRD.js} +1 -1
- package/dist/web/assets/{swimlanes-2SLR337P-Nt8ojGKK.js → swimlanes-2SLR337P-Tgk8-6jj.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-W1zDrK61.js +8 -0
- package/dist/web/assets/{terminal-store-D0abibMa.js → terminal-store-CKgJjtC6.js} +1 -1
- package/dist/web/assets/theme-slSxLNv6.js +1 -0
- package/dist/web/assets/{timeline-definition-EJHVYXUP-BtihLvxk.js → timeline-definition-EJHVYXUP-lAIHEGe8.js} +1 -1
- package/dist/web/assets/{treeView-Q6P3EWNA-D5HzXeKd.js → treeView-Q6P3EWNA-CdvzKtWf.js} +1 -1
- package/dist/web/assets/{treemap-WGGIJYW6-Dovjuutg.js → treemap-WGGIJYW6-BxDSJfrJ.js} +1 -1
- package/dist/web/assets/uid-B1JNhznI.js +1 -0
- package/dist/web/assets/use-add-agent-context-BSHGL5iV.js +1 -0
- package/dist/web/assets/use-app-theme-fmVV2QM8.js +1 -0
- package/dist/web/assets/use-countdown-D6_qSOZJ.js +1 -0
- package/dist/web/assets/use-notify-error-Dx2D9IEP.js +1 -0
- package/dist/web/assets/use-panel-width-BYOPuHat.js +1 -0
- package/dist/web/assets/use-router-link-D7geCGQI.js +1 -0
- package/dist/web/assets/use-row-height-BK8kNihW.js +1 -0
- package/dist/web/assets/use-server-events-CvcbsbIf.js +1 -0
- package/dist/web/assets/use-virtual-scroll-DixIsJk4.js +1 -0
- package/dist/web/assets/use-workspace-navigation-AaRMWmjQ.js +1 -0
- package/dist/web/assets/{usecaseDiagram-POWQR4AR-DSwRE8zj.js → usecaseDiagram-POWQR4AR-BH3KvMpz.js} +1 -1
- package/dist/web/assets/{vennDiagram-UO4OBE2U-C-B8yP1K.js → vennDiagram-UO4OBE2U-Dfi-raKp.js} +1 -1
- package/dist/web/assets/viewport-store-NqQS7itT.js +1 -0
- package/dist/web/assets/{vue-i18n.runtime-CEtihHHL.js → vue-i18n.runtime-JOo8b7-t.js} +2 -2
- package/dist/web/assets/{wardley-WFR3VGLG-wWgaZuh6.js → wardley-WFR3VGLG-DmbtSM24.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DbhenrHd.js → wardleyDiagram-VNRHLVJA-DIzhGvI7.js} +1 -1
- package/dist/web/assets/weighted-lru-BFtZKA_y.js +1 -0
- package/dist/web/assets/workspace-navigation-xfxULfM7.js +1 -0
- package/dist/web/assets/{xychartDiagram-PMCCYNJV-CxFDmAEt.js → xychartDiagram-PMCCYNJV-XgqSHH_N.js} +1 -1
- package/dist/web/index.html +10 -8
- package/package.json +5 -3
- package/dist/web/assets/AccessPage-_OKn9U0P.js +0 -1
- package/dist/web/assets/AppPage-oQ9Rz0be.js +0 -1
- package/dist/web/assets/CreateButton-D1EtoicG.js +0 -1
- package/dist/web/assets/CreateReviewFabButton-BxvT3i9e.js +0 -1
- package/dist/web/assets/IndexPage-gdD7BHxA.js +0 -1
- package/dist/web/assets/MainLayout-47PTLKZf.js +0 -6
- package/dist/web/assets/MainLayout-ikSrFqBh.css +0 -1
- package/dist/web/assets/NoteVisualEditor-BQ6mVfj0.js +0 -1054
- package/dist/web/assets/NotesPage-DNR9iW_G.js +0 -4
- package/dist/web/assets/NotesPage-_8naQciW.css +0 -1
- package/dist/web/assets/PanelResize-CAQFVc8b.js +0 -1
- package/dist/web/assets/QBtn-CB-yw_iP.js +0 -1
- package/dist/web/assets/QExpansionItem-Dr5oXO0m.js +0 -4
- package/dist/web/assets/QFab-DPz8RiP0.js +0 -1
- package/dist/web/assets/QForm-CASiKhGm.js +0 -1
- package/dist/web/assets/QInput-k34nCPWg.js +0 -1
- package/dist/web/assets/QSelect-CaKn9Tkg.js +0 -1
- package/dist/web/assets/QSpace-Di_bZQY6.js +0 -1
- package/dist/web/assets/QVirtualScroll-oGN_Tl5J.js +0 -1
- package/dist/web/assets/ReviewDetailPage-BKgIprY0.js +0 -1
- package/dist/web/assets/ReviewDetailPage-CkCilMNW.css +0 -1
- package/dist/web/assets/ReviewsPage-6PJ6wofN.js +0 -1
- package/dist/web/assets/StatusSwitcher-s9w6iKpR.js +0 -1
- package/dist/web/assets/architecture-7GRP2DOG-D6PShS0_.js +0 -1
- package/dist/web/assets/channel-D6lb5DbY.js +0 -1
- package/dist/web/assets/create-B9Tn5Xvi.js +0 -1
- package/dist/web/assets/devices-store-CrZ4MxyY.js +0 -1
- package/dist/web/assets/diff-store-ByxMCKoa.js +0 -93
- package/dist/web/assets/eventmodeling-NTZA5JFV-QCwZLvmm.js +0 -1
- package/dist/web/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- package/dist/web/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-D-x-0Q06.woff2 +0 -0
- package/dist/web/assets/flowDiagram-KWPJA3E3-CgF90hDi.js +0 -1
- package/dist/web/assets/i18n-DDCqtTTW.js +0 -1
- package/dist/web/assets/index-BNHt6zmP.js +0 -2
- package/dist/web/assets/index-BwbM_RKi.css +0 -1
- package/dist/web/assets/init-app-BouXTk8p.js +0 -1
- package/dist/web/assets/markdown-jR0fhe4d.js +0 -15
- package/dist/web/assets/pinia-BETEFLDG.js +0 -1
- package/dist/web/assets/pinia-DO0AdKGg.js +0 -1
- package/dist/web/assets/railroad-abnf-HS5TGJTU-GESjhc3t.js +0 -1
- package/dist/web/assets/railroad-ebnf-LZEXJU2U-Cybu0A7J.js +0 -1
- package/dist/web/assets/railroad-peg-WCYAUIDC-DCHxrtbI.js +0 -1
- package/dist/web/assets/runtime-core.esm-bundler-CAvPiMpd.js +0 -1
- package/dist/web/assets/single-flight-DLCbzLI3.js +0 -1
- package/dist/web/assets/swimlanesDiagram-TC7HE7FX-Ds1hVYqh.js +0 -8
- package/dist/web/assets/theme-QEcOl0lf.js +0 -1
- package/dist/web/assets/use-add-agent-context-BaPUIO93.js +0 -1
- package/dist/web/assets/use-app-theme-B1cdbnHn.js +0 -1
- package/dist/web/assets/use-countdown-CTag4T13.js +0 -1
- package/dist/web/assets/use-panel-width-CB-0S3zX.js +0 -1
- package/dist/web/assets/use-router-link-Dr9wnnyZ.js +0 -1
- package/dist/web/assets/use-row-height-DQHVMtUe.js +0 -1
- package/dist/web/assets/use-server-events-BrBCbeHu.js +0 -1
- package/dist/web/assets/use-virtual-scroll-DG0UKjVk.js +0 -1
- package/dist/web/assets/weighted-lru-BSxRnMHR.js +0 -1
- /package/dist/web/assets/{PanelResize-XjrbOj2S.css → AddToAgentButton-XjrbOj2S.css} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function piFailureReason(message) {
|
|
2
|
+
if (typeof message !== 'string')
|
|
3
|
+
return 'Pi model request failed.';
|
|
4
|
+
if (/api.?key|unauthori[sz]ed|authenticat|\b40[13]\b/i.test(message)) {
|
|
5
|
+
return 'Pi model authorization failed. Check the installed pi configuration.';
|
|
6
|
+
}
|
|
7
|
+
if (/no model|model.*not found|model.*not available/i.test(message)) {
|
|
8
|
+
return 'Pi has no usable configured model. Check the installed pi configuration.';
|
|
9
|
+
}
|
|
10
|
+
if (/fetch|connect|ECONN|ENOTFOUND|network/i.test(message)) {
|
|
11
|
+
return 'Pi model connection failed. Check the installed pi endpoint.';
|
|
12
|
+
}
|
|
13
|
+
if (/rate.?limit|quota|\b429\b/i.test(message)) {
|
|
14
|
+
return 'Pi model provider limit was reached.';
|
|
15
|
+
}
|
|
16
|
+
return 'Pi model request failed. Check the installed pi configuration.';
|
|
17
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/// <reference lib="es2024.promise" />
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
import { addAbortListener } from 'node:events';
|
|
4
|
+
import { JsonChannel, CONTROL_VERSION, BRIDGE_TIMEOUT_MS, MAX_PENDING_CALLS } from "./control.js";
|
|
5
|
+
import { isRecord } from "../../../shared/utils/guards.js";
|
|
6
|
+
// Bound late-result tracking without retaining call arguments or results.
|
|
7
|
+
const MAX_RETIRED_CALLS = 10000;
|
|
8
|
+
export default async function extension(pi) {
|
|
9
|
+
const nonce = process.env.GITHUMAN_PI_NONCE ?? '';
|
|
10
|
+
delete process.env.GITHUMAN_PI_NONCE;
|
|
11
|
+
if (!nonce ||
|
|
12
|
+
typeof pi.registerTool !== 'function' ||
|
|
13
|
+
typeof pi.getAllTools !== 'function') {
|
|
14
|
+
throw new Error('Pi bridge extension capabilities are unavailable');
|
|
15
|
+
}
|
|
16
|
+
await initializePiExtension(pi, {
|
|
17
|
+
nonce,
|
|
18
|
+
input: new Socket({ fd: 4, readable: true, writable: false }),
|
|
19
|
+
output: new Socket({ fd: 3, readable: false, writable: true }),
|
|
20
|
+
exit: () => process.exit(1)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export async function initializePiExtension(pi, options) {
|
|
24
|
+
const { nonce } = options;
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
const retired = new Set();
|
|
27
|
+
let generation = 0;
|
|
28
|
+
let configured = false;
|
|
29
|
+
let profile = '';
|
|
30
|
+
let instructions = '';
|
|
31
|
+
let names = new Set();
|
|
32
|
+
let registerTools;
|
|
33
|
+
let ready = false;
|
|
34
|
+
const configuration = Promise.withResolvers();
|
|
35
|
+
const shutdown = () => {
|
|
36
|
+
for (const request of pending.values()) {
|
|
37
|
+
request.reject(new Error('Pi bridge control channel closed'));
|
|
38
|
+
}
|
|
39
|
+
pending.clear();
|
|
40
|
+
return options.exit();
|
|
41
|
+
};
|
|
42
|
+
const channel = new JsonChannel(options.input, options.output, frame => {
|
|
43
|
+
if (frame.nonce !== nonce)
|
|
44
|
+
shutdown();
|
|
45
|
+
if (frame.type === 'configure') {
|
|
46
|
+
if (configured ||
|
|
47
|
+
!Array.isArray(frame.tools) ||
|
|
48
|
+
!['validator', 'chat'].includes(String(frame.profile)) ||
|
|
49
|
+
typeof frame.instructions !== 'string') {
|
|
50
|
+
shutdown();
|
|
51
|
+
}
|
|
52
|
+
configured = true;
|
|
53
|
+
profile = String(frame.profile);
|
|
54
|
+
instructions = frame.instructions;
|
|
55
|
+
const tools = frame.tools.filter(isRegistryTool);
|
|
56
|
+
if (tools.some(tool => !isRecord(tool) ||
|
|
57
|
+
typeof tool.name !== 'string' ||
|
|
58
|
+
typeof tool.description !== 'string' ||
|
|
59
|
+
!isRecord(tool.inputSchema))) {
|
|
60
|
+
shutdown();
|
|
61
|
+
}
|
|
62
|
+
names = new Set(tools.map(tool => tool.name));
|
|
63
|
+
if (names.size !== tools.length)
|
|
64
|
+
shutdown();
|
|
65
|
+
registerTools = () => {
|
|
66
|
+
for (const tool of tools) {
|
|
67
|
+
pi.registerTool({
|
|
68
|
+
name: tool.name,
|
|
69
|
+
label: tool.name,
|
|
70
|
+
description: tool.description,
|
|
71
|
+
parameters: tool.inputSchema,
|
|
72
|
+
execute: async (id, toolInput, signal) => {
|
|
73
|
+
if (!generation ||
|
|
74
|
+
pending.size >= MAX_PENDING_CALLS ||
|
|
75
|
+
signal?.aborted) {
|
|
76
|
+
throw new Error('Pi bridge call is not active');
|
|
77
|
+
}
|
|
78
|
+
const callGeneration = generation;
|
|
79
|
+
const key = `${callGeneration}:${id}`;
|
|
80
|
+
if (pending.has(key) || retired.has(key)) {
|
|
81
|
+
throw new Error('Duplicate pi bridge call ID');
|
|
82
|
+
}
|
|
83
|
+
const response = Promise.withResolvers();
|
|
84
|
+
let abortListener;
|
|
85
|
+
let settled = false;
|
|
86
|
+
const timer = setTimeout(() => reject(new Error('Pi bridge tool deadline exceeded')), BRIDGE_TIMEOUT_MS);
|
|
87
|
+
const cleanup = () => {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
abortListener?.[Symbol.dispose]();
|
|
90
|
+
pending.delete(key);
|
|
91
|
+
};
|
|
92
|
+
function reject(error) {
|
|
93
|
+
if (settled)
|
|
94
|
+
return;
|
|
95
|
+
settled = true;
|
|
96
|
+
// A timed-out or cancelled call can still receive one backend response.
|
|
97
|
+
retired.add(key);
|
|
98
|
+
if (retired.size > MAX_RETIRED_CALLS) {
|
|
99
|
+
retired.delete(retired.values().next().value);
|
|
100
|
+
}
|
|
101
|
+
cleanup();
|
|
102
|
+
response.reject(error);
|
|
103
|
+
}
|
|
104
|
+
pending.set(key, {
|
|
105
|
+
resolve: value => {
|
|
106
|
+
if (settled)
|
|
107
|
+
return;
|
|
108
|
+
settled = true;
|
|
109
|
+
cleanup();
|
|
110
|
+
response.resolve(value);
|
|
111
|
+
},
|
|
112
|
+
reject
|
|
113
|
+
});
|
|
114
|
+
let result;
|
|
115
|
+
try {
|
|
116
|
+
try {
|
|
117
|
+
if (signal) {
|
|
118
|
+
abortListener = addAbortListener(signal, () => reject(new Error('Pi bridge call cancelled')));
|
|
119
|
+
}
|
|
120
|
+
send('call', {
|
|
121
|
+
id,
|
|
122
|
+
generation: callGeneration,
|
|
123
|
+
name: tool.name,
|
|
124
|
+
arguments: toolInput
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
settled = true;
|
|
129
|
+
cleanup();
|
|
130
|
+
response.reject(error);
|
|
131
|
+
}
|
|
132
|
+
result = await response.promise;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
cleanup();
|
|
136
|
+
}
|
|
137
|
+
if (generation !== callGeneration ||
|
|
138
|
+
signal?.aborted ||
|
|
139
|
+
!isRecord(result) ||
|
|
140
|
+
!Array.isArray(result.content)) {
|
|
141
|
+
throw new Error('Pi bridge returned a stale or malformed result');
|
|
142
|
+
}
|
|
143
|
+
if (result.isError === true) {
|
|
144
|
+
throw new Error(result.content
|
|
145
|
+
.filter(isRecord)
|
|
146
|
+
.map(block => block.type === 'text' ? String(block.text) : '')
|
|
147
|
+
.join('\n') || 'MCP tool failed');
|
|
148
|
+
}
|
|
149
|
+
const content = result.content.filter(isRecord).map(block => {
|
|
150
|
+
if (block.type === 'text' || block.type === 'image') {
|
|
151
|
+
return block;
|
|
152
|
+
}
|
|
153
|
+
if (block.type === 'resource' &&
|
|
154
|
+
isRecord(block.resource) &&
|
|
155
|
+
typeof block.resource.text === 'string') {
|
|
156
|
+
return { type: 'text', text: block.resource.text };
|
|
157
|
+
}
|
|
158
|
+
return { type: 'text', text: JSON.stringify(block) };
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
content,
|
|
162
|
+
details: { structuredContent: result.structuredContent }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
configuration.resolve();
|
|
169
|
+
}
|
|
170
|
+
else if (frame.type === 'begin') {
|
|
171
|
+
if (!configured ||
|
|
172
|
+
!Number.isSafeInteger(frame.generation) ||
|
|
173
|
+
Number(frame.generation) <= generation) {
|
|
174
|
+
shutdown();
|
|
175
|
+
}
|
|
176
|
+
generation = Number(frame.generation);
|
|
177
|
+
send('begun', { generation });
|
|
178
|
+
}
|
|
179
|
+
else if (frame.type === 'cancel') {
|
|
180
|
+
generation = 0;
|
|
181
|
+
for (const request of pending.values()) {
|
|
182
|
+
request.reject(new Error('Pi bridge call cancelled'));
|
|
183
|
+
}
|
|
184
|
+
pending.clear();
|
|
185
|
+
}
|
|
186
|
+
else if (frame.type === 'result') {
|
|
187
|
+
if (typeof frame.id !== 'string' ||
|
|
188
|
+
!Number.isSafeInteger(frame.generation)) {
|
|
189
|
+
shutdown();
|
|
190
|
+
}
|
|
191
|
+
const key = `${String(frame.generation)}:${frame.id}`;
|
|
192
|
+
// Only calls already rejected locally may have a harmless late result.
|
|
193
|
+
if (retired.delete(key))
|
|
194
|
+
return;
|
|
195
|
+
if (frame.generation !== generation)
|
|
196
|
+
shutdown();
|
|
197
|
+
const request = pending.get(key);
|
|
198
|
+
if (!request)
|
|
199
|
+
shutdown();
|
|
200
|
+
request.resolve(frame.result);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
shutdown();
|
|
204
|
+
}
|
|
205
|
+
}, shutdown);
|
|
206
|
+
function send(type, fields = {}) {
|
|
207
|
+
channel.send({ ...fields, type, nonce, version: CONTROL_VERSION });
|
|
208
|
+
}
|
|
209
|
+
const timer = setTimeout(shutdown, BRIDGE_TIMEOUT_MS);
|
|
210
|
+
try {
|
|
211
|
+
send('hello');
|
|
212
|
+
await configuration.promise;
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
}
|
|
217
|
+
pi.on('session_start', () => {
|
|
218
|
+
if (ready) {
|
|
219
|
+
send('fatal', { reason: 'reload' });
|
|
220
|
+
shutdown();
|
|
221
|
+
}
|
|
222
|
+
ready = true;
|
|
223
|
+
const all = pi.getAllTools().map(tool => tool.name);
|
|
224
|
+
if ([...names].some(name => all.includes(name))) {
|
|
225
|
+
send('fatal', { reason: 'tool_name_collision' });
|
|
226
|
+
shutdown();
|
|
227
|
+
}
|
|
228
|
+
registerTools?.();
|
|
229
|
+
if (profile === 'validator')
|
|
230
|
+
pi.setActiveTools([...names]);
|
|
231
|
+
send('ready', { profile, registryGeneration: 1 });
|
|
232
|
+
});
|
|
233
|
+
// Only validator installs a native/custom tool guard. MCP approval lives in execute.
|
|
234
|
+
if (profile === 'validator') {
|
|
235
|
+
pi.on('tool_call', event => names.has(String(event.toolName))
|
|
236
|
+
? undefined
|
|
237
|
+
: {
|
|
238
|
+
block: true,
|
|
239
|
+
reason: 'Validator denies tools outside its immutable MCP registry.'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
pi.on('before_agent_start', event => ({
|
|
243
|
+
systemPrompt: `${String(event.systemPrompt)}\n\n${instructions}`
|
|
244
|
+
}));
|
|
245
|
+
pi.on('session_shutdown', shutdown);
|
|
246
|
+
}
|
|
247
|
+
function isRegistryTool(value) {
|
|
248
|
+
return (isRecord(value) &&
|
|
249
|
+
typeof value.name === 'string' &&
|
|
250
|
+
typeof value.description === 'string' &&
|
|
251
|
+
typeof value.server === 'string' &&
|
|
252
|
+
typeof value.originalName === 'string' &&
|
|
253
|
+
isRecord(value.inputSchema) &&
|
|
254
|
+
value.inputSchema.type === 'object');
|
|
255
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
export function localPiLaunch() {
|
|
3
|
+
const entry = import.meta.url.endsWith('.ts')
|
|
4
|
+
? '../../../cli/index.ts'
|
|
5
|
+
: '../../../cli/index.js';
|
|
6
|
+
return {
|
|
7
|
+
command: process.execPath,
|
|
8
|
+
args: [fileURLToPath(new URL(entry, import.meta.url)), 'pi-acp', '--stdio']
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function isLocalPiLaunch(preset) {
|
|
12
|
+
const trusted = localPiLaunch();
|
|
13
|
+
return (preset.command === trusted.command &&
|
|
14
|
+
preset.args.length === trusted.args.length &&
|
|
15
|
+
preset.args.every((arg, index) => arg === trusted.args[index]));
|
|
16
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { terminateProcesses } from "../../services/terminal/processes.js";
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
|
+
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv-provider.js';
|
|
5
|
+
import { isRecord } from "../../../shared/utils/guards.js";
|
|
6
|
+
import { BRIDGE_TIMEOUT_MS } from "./control.js";
|
|
7
|
+
// Bound discovery across pages and servers before exposing any tools.
|
|
8
|
+
const MAX_TOOLS = 512;
|
|
9
|
+
// Repeated or excessive cursors cannot keep session/new pending.
|
|
10
|
+
const MAX_PAGES = 32;
|
|
11
|
+
// pi's built-in tool names must never resolve to MCP backends.
|
|
12
|
+
const BUILTIN_NAMES = new Set([
|
|
13
|
+
'read',
|
|
14
|
+
'write',
|
|
15
|
+
'edit',
|
|
16
|
+
'bash',
|
|
17
|
+
'powershell',
|
|
18
|
+
'grep',
|
|
19
|
+
'find',
|
|
20
|
+
'ls',
|
|
21
|
+
'tool_search',
|
|
22
|
+
'codemode'
|
|
23
|
+
]);
|
|
24
|
+
function isValidMcpName(name) {
|
|
25
|
+
return /^[a-zA-Z0-9_-]+$/.test(name);
|
|
26
|
+
}
|
|
27
|
+
export class McpRegistry {
|
|
28
|
+
#entries = new Map();
|
|
29
|
+
#clients = [];
|
|
30
|
+
#transports = [];
|
|
31
|
+
#instructions = [];
|
|
32
|
+
#closed = false;
|
|
33
|
+
onFailure = () => { };
|
|
34
|
+
get tools() {
|
|
35
|
+
return structuredClone([...this.#entries.values()].map(entry => entry.descriptor));
|
|
36
|
+
}
|
|
37
|
+
get instructions() {
|
|
38
|
+
return this.#instructions.join('\n\n');
|
|
39
|
+
}
|
|
40
|
+
async connect(servers) {
|
|
41
|
+
if (servers.length > 8)
|
|
42
|
+
throw new Error('Too many pi MCP servers');
|
|
43
|
+
const names = new Set();
|
|
44
|
+
const schemas = new AjvJsonSchemaValidator();
|
|
45
|
+
try {
|
|
46
|
+
for (const server of servers) {
|
|
47
|
+
if ('type' in server ||
|
|
48
|
+
!isValidMcpName(server.name) ||
|
|
49
|
+
names.has(server.name)) {
|
|
50
|
+
throw new Error('Pi bridge requires unique server-owned stdio MCP descriptors');
|
|
51
|
+
}
|
|
52
|
+
names.add(server.name);
|
|
53
|
+
const env = {};
|
|
54
|
+
for (const entry of server.env) {
|
|
55
|
+
if (Object.hasOwn(env, entry.name)) {
|
|
56
|
+
throw new Error('Duplicate MCP environment variable');
|
|
57
|
+
}
|
|
58
|
+
env[entry.name] = entry.value;
|
|
59
|
+
}
|
|
60
|
+
const transport = new StdioClientTransport({
|
|
61
|
+
command: server.command,
|
|
62
|
+
args: [...server.args],
|
|
63
|
+
env,
|
|
64
|
+
maxBufferSize: 16 * 1024 * 1024,
|
|
65
|
+
stderr: 'pipe'
|
|
66
|
+
});
|
|
67
|
+
this.#transports.push(transport);
|
|
68
|
+
const client = new Client({ name: 'githuman-pi-bridge', version: '1' });
|
|
69
|
+
this.#clients.push(client);
|
|
70
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- MCP Client exposes onclose as a callback property.
|
|
71
|
+
client.onclose = () => {
|
|
72
|
+
if (!this.#closed)
|
|
73
|
+
this.onFailure();
|
|
74
|
+
};
|
|
75
|
+
transport.stderr?.on('data', () => { });
|
|
76
|
+
await client.connect(transport, { timeout: BRIDGE_TIMEOUT_MS });
|
|
77
|
+
const instructions = client.getInstructions();
|
|
78
|
+
if (instructions)
|
|
79
|
+
this.#instructions.push(instructions);
|
|
80
|
+
const cursors = new Set();
|
|
81
|
+
let cursor;
|
|
82
|
+
for (let page = 0;; page++) {
|
|
83
|
+
if (page >= MAX_PAGES) {
|
|
84
|
+
throw new Error('MCP tools/list page limit exceeded');
|
|
85
|
+
}
|
|
86
|
+
const response = await client.listTools(cursor ? { cursor } : {}, {
|
|
87
|
+
timeout: BRIDGE_TIMEOUT_MS
|
|
88
|
+
});
|
|
89
|
+
for (const tool of response.tools) {
|
|
90
|
+
const name = `mcp__${server.name}__${tool.name}`;
|
|
91
|
+
if (!isValidMcpName(tool.name) ||
|
|
92
|
+
name.length > 128 ||
|
|
93
|
+
BUILTIN_NAMES.has(tool.name) ||
|
|
94
|
+
this.#entries.has(name) ||
|
|
95
|
+
this.#entries.size >= MAX_TOOLS) {
|
|
96
|
+
throw new Error('MCP tool name collision or registry limit exceeded');
|
|
97
|
+
}
|
|
98
|
+
const descriptor = structuredClone({
|
|
99
|
+
name,
|
|
100
|
+
description: tool.description ?? tool.name,
|
|
101
|
+
inputSchema: tool.inputSchema,
|
|
102
|
+
server: server.name,
|
|
103
|
+
originalName: tool.name
|
|
104
|
+
});
|
|
105
|
+
this.#entries.set(name, {
|
|
106
|
+
descriptor,
|
|
107
|
+
client,
|
|
108
|
+
validate: schemas.getValidator(descriptor.inputSchema)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
cursor = response.nextCursor;
|
|
112
|
+
if (!cursor)
|
|
113
|
+
break;
|
|
114
|
+
if (cursors.has(cursor)) {
|
|
115
|
+
throw new Error('MCP tools/list repeated a cursor');
|
|
116
|
+
}
|
|
117
|
+
cursors.add(cursor);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
await this.close();
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
snapshot(name, input) {
|
|
127
|
+
const entry = this.#entries.get(name);
|
|
128
|
+
if (!entry || !isRecord(input)) {
|
|
129
|
+
throw new Error('Unknown MCP tool or invalid arguments');
|
|
130
|
+
}
|
|
131
|
+
const args = JSON.parse(JSON.stringify(input));
|
|
132
|
+
if (!isRecord(args) || !entry.validate(args).valid) {
|
|
133
|
+
throw new Error('MCP arguments do not match the registered schema');
|
|
134
|
+
}
|
|
135
|
+
return { descriptor: structuredClone(entry.descriptor), arguments: args };
|
|
136
|
+
}
|
|
137
|
+
async call(name, args, signal) {
|
|
138
|
+
const entry = this.#entries.get(name);
|
|
139
|
+
if (!entry || this.#closed || signal.aborted) {
|
|
140
|
+
throw new Error('MCP call is no longer active');
|
|
141
|
+
}
|
|
142
|
+
return entry.client.callTool({ name: entry.descriptor.originalName, arguments: args }, undefined, { signal, timeout: BRIDGE_TIMEOUT_MS });
|
|
143
|
+
}
|
|
144
|
+
async close() {
|
|
145
|
+
if (this.#closed)
|
|
146
|
+
return;
|
|
147
|
+
this.#closed = true;
|
|
148
|
+
await Promise.allSettled(this.#transports.map(transport => transport.pid ? terminateProcesses(transport.pid) : Promise.resolve()));
|
|
149
|
+
await Promise.allSettled(this.#clients.map(client => client.close()));
|
|
150
|
+
await Promise.allSettled(this.#transports.map(transport => transport.close()));
|
|
151
|
+
this.#entries.clear();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { piFailureReason } from "./errors.js";
|
|
2
|
+
import { terminateProcesses } from "../../services/terminal/processes.js";
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
5
|
+
import { Socket } from 'node:net';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { JsonChannel, CONTROL_VERSION, BRIDGE_TIMEOUT_MS, MAX_CONTROL_BYTES, MAX_PENDING_CALLS } from "./control.js";
|
|
8
|
+
import { piLaunchArgs } from "./compatibility.js";
|
|
9
|
+
import { isRecord } from "../../../shared/utils/guards.js";
|
|
10
|
+
export class PiProcess {
|
|
11
|
+
#pending = new Map();
|
|
12
|
+
#nonce = randomBytes(32).toString('hex');
|
|
13
|
+
#child;
|
|
14
|
+
#channel;
|
|
15
|
+
#closed = false;
|
|
16
|
+
#rpcBuffer = Buffer.alloc(0);
|
|
17
|
+
#hello = false;
|
|
18
|
+
#ready = false;
|
|
19
|
+
onControl = () => { };
|
|
20
|
+
onEvent = () => { };
|
|
21
|
+
onFailure = () => { };
|
|
22
|
+
async start(cwd, profile, config, env = process.env) {
|
|
23
|
+
const command = env.PI_ACP_PI_COMMAND || 'pi';
|
|
24
|
+
const extension = fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? './extension.ts' : './extension.js', import.meta.url));
|
|
25
|
+
const args = await piLaunchArgs(command, profile, extension);
|
|
26
|
+
if (this.#closed) {
|
|
27
|
+
throw new Error('Pi bridge closed during runtime discovery');
|
|
28
|
+
}
|
|
29
|
+
const piEnv = { ...env, GITHUMAN_PI_NONCE: this.#nonce };
|
|
30
|
+
for (const key of [
|
|
31
|
+
'GITHUMAN_AGENT_KEY',
|
|
32
|
+
'GITHUMAN_AGENT_URL',
|
|
33
|
+
'GITHUMAN_PI_PROFILE'
|
|
34
|
+
]) {
|
|
35
|
+
delete piEnv[key];
|
|
36
|
+
}
|
|
37
|
+
const child = spawn(command, args, {
|
|
38
|
+
cwd,
|
|
39
|
+
env: piEnv,
|
|
40
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
|
|
41
|
+
detached: true
|
|
42
|
+
});
|
|
43
|
+
this.#child = child;
|
|
44
|
+
child.stdin.on('error', () => this.fail());
|
|
45
|
+
child.stderr.resume();
|
|
46
|
+
child.on('error', () => this.fail());
|
|
47
|
+
child.on('exit', () => this.fail());
|
|
48
|
+
const startup = new Promise((resolve, reject) => {
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
reject(new Error('Pi bridge startup deadline exceeded'));
|
|
51
|
+
this.fail();
|
|
52
|
+
}, BRIDGE_TIMEOUT_MS);
|
|
53
|
+
this.#pending.set('startup', {
|
|
54
|
+
resolve: () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
resolve();
|
|
57
|
+
},
|
|
58
|
+
reject: error => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
reject(error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
const requestPipe = child.stdio[3];
|
|
65
|
+
const responsePipe = child.stdio[4];
|
|
66
|
+
if (!(requestPipe instanceof Socket) || !(responsePipe instanceof Socket)) {
|
|
67
|
+
throw new Error('Pi control pipes unavailable');
|
|
68
|
+
}
|
|
69
|
+
this.#channel = new JsonChannel(requestPipe, responsePipe, frame => {
|
|
70
|
+
if (frame.nonce !== this.#nonce)
|
|
71
|
+
return this.fail();
|
|
72
|
+
if (frame.type === 'fatal') {
|
|
73
|
+
this.fail(frame.reason === 'tool_name_collision'
|
|
74
|
+
? 'Pi MCP tool name collision with an installed extension.'
|
|
75
|
+
: 'Pi extension reload requires a new chat.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (frame.type === 'hello') {
|
|
79
|
+
if (this.#hello)
|
|
80
|
+
return this.fail();
|
|
81
|
+
this.#hello = true;
|
|
82
|
+
this.send('configure', { ...config, profile });
|
|
83
|
+
}
|
|
84
|
+
else if (frame.type === 'ready') {
|
|
85
|
+
if (!this.#hello ||
|
|
86
|
+
this.#ready ||
|
|
87
|
+
frame.profile !== profile ||
|
|
88
|
+
frame.registryGeneration !== 1) {
|
|
89
|
+
return this.fail();
|
|
90
|
+
}
|
|
91
|
+
this.#ready = true;
|
|
92
|
+
this.#pending.get('startup')?.resolve({});
|
|
93
|
+
this.#pending.delete('startup');
|
|
94
|
+
}
|
|
95
|
+
else if (this.#ready) {
|
|
96
|
+
this.onControl(frame);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.fail();
|
|
100
|
+
}
|
|
101
|
+
}, () => this.fail());
|
|
102
|
+
child.stdout.on('data', (chunk) => this.readRpc(chunk));
|
|
103
|
+
await startup;
|
|
104
|
+
}
|
|
105
|
+
send(type, fields = {}) {
|
|
106
|
+
if (!this.#channel || this.#closed) {
|
|
107
|
+
throw new Error('Pi bridge control channel unavailable');
|
|
108
|
+
}
|
|
109
|
+
this.#channel.send({
|
|
110
|
+
...fields,
|
|
111
|
+
type,
|
|
112
|
+
nonce: this.#nonce,
|
|
113
|
+
version: CONTROL_VERSION
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
request(type, fields = {}) {
|
|
117
|
+
if (this.#closed ||
|
|
118
|
+
!this.#child?.stdin ||
|
|
119
|
+
this.#pending.size >= MAX_PENDING_CALLS) {
|
|
120
|
+
return Promise.reject(new Error('Pi RPC unavailable or pending limit exceeded'));
|
|
121
|
+
}
|
|
122
|
+
const id = randomUUID();
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const timer = setTimeout(() => {
|
|
125
|
+
this.#pending.delete(id);
|
|
126
|
+
reject(new Error('Pi RPC deadline exceeded'));
|
|
127
|
+
this.fail();
|
|
128
|
+
}, type === 'prompt' ? 300000 : BRIDGE_TIMEOUT_MS);
|
|
129
|
+
this.#pending.set(id, {
|
|
130
|
+
resolve: value => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
this.#pending.delete(id);
|
|
133
|
+
resolve(value);
|
|
134
|
+
},
|
|
135
|
+
reject: error => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
this.#pending.delete(id);
|
|
138
|
+
reject(error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
this.#child.stdin.write(JSON.stringify({ ...fields, type, id }) + '\n');
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
readRpc(chunk) {
|
|
145
|
+
if (this.#rpcBuffer.length + chunk.length > MAX_CONTROL_BYTES) {
|
|
146
|
+
return this.fail();
|
|
147
|
+
}
|
|
148
|
+
this.#rpcBuffer = Buffer.concat([this.#rpcBuffer, chunk]);
|
|
149
|
+
let index;
|
|
150
|
+
while ((index = this.#rpcBuffer.indexOf(10)) >= 0) {
|
|
151
|
+
const line = this.#rpcBuffer.subarray(0, index).toString();
|
|
152
|
+
this.#rpcBuffer = this.#rpcBuffer.subarray(index + 1);
|
|
153
|
+
let event;
|
|
154
|
+
try {
|
|
155
|
+
event = JSON.parse(line);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!isRecord(event))
|
|
161
|
+
return this.fail();
|
|
162
|
+
if (event.type === 'response') {
|
|
163
|
+
const request = this.#pending.get(String(event.id));
|
|
164
|
+
if (request) {
|
|
165
|
+
if (event.success === true) {
|
|
166
|
+
request.resolve(isRecord(event.data) ? event.data : {});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
request.reject(new Error(piFailureReason(event.error)));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
this.onEvent(event);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
fail(reason) {
|
|
179
|
+
if (this.#closed)
|
|
180
|
+
return;
|
|
181
|
+
this.close(reason);
|
|
182
|
+
this.onFailure();
|
|
183
|
+
}
|
|
184
|
+
close(reason = 'Pi bridge process or control channel closed') {
|
|
185
|
+
if (this.#closed)
|
|
186
|
+
return;
|
|
187
|
+
this.#closed = true;
|
|
188
|
+
for (const pending of this.#pending.values()) {
|
|
189
|
+
pending.reject(new Error(reason));
|
|
190
|
+
}
|
|
191
|
+
this.#pending.clear();
|
|
192
|
+
const child = this.#child;
|
|
193
|
+
if (!child?.pid) {
|
|
194
|
+
this.#channel?.close();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
void terminateProcesses(child.pid).finally(() => this.#channel?.close());
|
|
198
|
+
}
|
|
199
|
+
}
|