panopticon-cli 0.6.5 → 0.6.7
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 +2 -2
- package/dist/{agents-DfYify9s.js → agents-CfFDs52G.js} +14 -14
- package/dist/{agents-DfYify9s.js.map → agents-CfFDs52G.js.map} +1 -1
- package/dist/{agents-BKsVoIc9.js → agents-D_2oRFVf.js} +1 -1
- package/dist/{archive-planning-BJrZ3tmN.js → archive-planning-D97ziGec.js} +3 -3
- package/dist/{archive-planning-BJrZ3tmN.js.map → archive-planning-D97ziGec.js.map} +1 -1
- package/dist/{archive-planning-C3m3hfa5.js → archive-planning-DK90wn9Q.js} +1 -1
- package/dist/{browser-Cvdznzc0.js → browser-CX7jXfXX.js} +1 -1
- package/dist/{browser-Cvdznzc0.js.map → browser-CX7jXfXX.js.map} +1 -1
- package/dist/{clean-planning-DvhZAUv4.js → clean-planning-D_lz4aQq.js} +2 -2
- package/dist/{clean-planning-DvhZAUv4.js.map → clean-planning-D_lz4aQq.js.map} +1 -1
- package/dist/clean-planning-x1S-JdmO.js +2 -0
- package/dist/cli/index.js +291 -760
- package/dist/cli/index.js.map +1 -1
- package/dist/{close-issue-Dr7yZmrr.js → close-issue-CaFE0stN.js} +11 -7
- package/dist/close-issue-CaFE0stN.js.map +1 -0
- package/dist/close-issue-CjcfZI9s.js +2 -0
- package/dist/compact-beads-B0_qE1w3.js +2 -0
- package/dist/{compact-beads-BCOtIIRl.js → compact-beads-CjFkteSU.js} +2 -2
- package/dist/{compact-beads-BCOtIIRl.js.map → compact-beads-CjFkteSU.js.map} +1 -1
- package/dist/{config-CRzMQRgA.js → config-BQNKsi9G.js} +2 -2
- package/dist/{config-CRzMQRgA.js.map → config-BQNKsi9G.js.map} +1 -1
- package/dist/{config-BYgUzQ21.js → config-agyKgF5C.js} +1 -1
- package/dist/{config-yaml-BgOACZAB.js → config-yaml-DGbLSMCa.js} +1 -1
- package/dist/{config-yaml-BgOACZAB.js.map → config-yaml-DGbLSMCa.js.map} +1 -1
- package/dist/{config-yaml-fdyvyL0S.js → config-yaml-Dqt4FWQH.js} +1 -1
- package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js → acceptance-criteria-Dk9hhiYj.js} +1 -1
- package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js.map → acceptance-criteria-Dk9hhiYj.js.map} +1 -1
- package/dist/dashboard/{agent-enrichment-C67LJBgD.js → agent-enrichment-DdO7ZqjI.js} +11 -7
- package/dist/dashboard/agent-enrichment-DdO7ZqjI.js.map +1 -0
- package/dist/dashboard/{agent-enrichment-Cq0P1cNZ.js → agent-enrichment-dLeGE1fX.js} +1 -1
- package/dist/dashboard/{agents-YyO6t5Xa.js → agents-DCpQQ_W5.js} +14 -14
- package/dist/dashboard/{agents-YyO6t5Xa.js.map → agents-DCpQQ_W5.js.map} +1 -1
- package/dist/dashboard/{agents-BVBVCyat.js → agents-Dgh2TjSp.js} +1 -1
- package/dist/dashboard/{archive-planning-h-hAjk0P.js → archive-planning-BmW9UDTr.js} +3 -3
- package/dist/dashboard/{archive-planning-h-hAjk0P.js.map → archive-planning-BmW9UDTr.js.map} +1 -1
- package/dist/dashboard/{archive-planning-CScs1MOC.js → archive-planning-C3Ebf9yC.js} +1 -1
- package/dist/dashboard/{beads-qNB0yAHV.js → beads-Bv-AdX7G.js} +3 -3
- package/dist/dashboard/{beads-qNB0yAHV.js.map → beads-Bv-AdX7G.js.map} +1 -1
- package/dist/dashboard/{beads-D_FRedEJ.js → beads-By6-X07V.js} +1 -1
- package/dist/dashboard/clean-planning-D60L8rPY.js +2 -0
- package/dist/dashboard/{clean-planning-qafw99vY.js → clean-planning-VEJu5suh.js} +2 -2
- package/dist/dashboard/{clean-planning-qafw99vY.js.map → clean-planning-VEJu5suh.js.map} +1 -1
- package/dist/dashboard/close-issue-C2KeSKKJ.js +2 -0
- package/dist/dashboard/{close-issue-DfIggeZD.js → close-issue-DtKdsSTm.js} +11 -7
- package/dist/dashboard/close-issue-DtKdsSTm.js.map +1 -0
- package/dist/dashboard/compact-beads-C7BN5N11.js +2 -0
- package/dist/dashboard/{compact-beads-Dt0qTqsC.js → compact-beads-D8Vt3qyv.js} +2 -2
- package/dist/dashboard/{compact-beads-Dt0qTqsC.js.map → compact-beads-D8Vt3qyv.js.map} +1 -1
- package/dist/dashboard/{config-CUREjHP7.js → config-CDkGjnwy.js} +2 -2
- package/dist/dashboard/{config-CUREjHP7.js.map → config-CDkGjnwy.js.map} +1 -1
- package/dist/dashboard/{config-BeI3uy-8.js → config-CTXkBATQ.js} +1 -1
- package/dist/dashboard/{database-CozA13Wy.js → database-DhqASALP.js} +1 -1
- package/dist/dashboard/{database-C0y0hXBx.js → database-cxmQryoh.js} +2 -2
- package/dist/dashboard/{database-C0y0hXBx.js.map → database-cxmQryoh.js.map} +1 -1
- package/dist/dashboard/{dist-src-oG2iHzgI.js → dist-src-DTm11oQr.js} +1 -1
- package/dist/dashboard/{dist-src-oG2iHzgI.js.map → dist-src-DTm11oQr.js.map} +1 -1
- package/dist/dashboard/{event-store-D7kLBd07.js → event-store-VWWUmOfn.js} +1 -1
- package/dist/dashboard/{event-store-O9q0Gweh.js → event-store-vSmAA3Zp.js} +9 -4
- package/dist/dashboard/event-store-vSmAA3Zp.js.map +1 -0
- package/dist/dashboard/{factory-BnLdiQW-.js → factory-C8nhLGHB.js} +3 -3
- package/dist/dashboard/{factory-BnLdiQW-.js.map → factory-C8nhLGHB.js.map} +1 -1
- package/dist/dashboard/{feedback-writer-DyovUANg.js → feedback-writer-CudSe1WK.js} +2 -2
- package/dist/dashboard/{feedback-writer-DyovUANg.js.map → feedback-writer-CudSe1WK.js.map} +1 -1
- package/dist/dashboard/{feedback-writer-gSUv_W0h.js → feedback-writer-Wgv1cd1r.js} +1 -1
- package/dist/dashboard/{git-utils-BJRioREj.js → git-utils-C1m4SwAe.js} +1 -1
- package/dist/dashboard/{git-utils-BJRioREj.js.map → git-utils-C1m4SwAe.js.map} +1 -1
- package/dist/dashboard/{git-utils-BtCRddq3.js → git-utils-DQI8EYoj.js} +1 -1
- package/dist/dashboard/{github-app-XO-LBUGk.js → github-app-DClWjjHr.js} +1 -1
- package/dist/dashboard/{github-app-XO-LBUGk.js.map → github-app-DClWjjHr.js.map} +1 -1
- package/dist/dashboard/{health-events-db-584nYgJB.js → health-events-db-BMXQfInV.js} +1 -1
- package/dist/dashboard/{health-events-db-B3ChzN65.js → health-events-db-Do4NrOhC.js} +2 -2
- package/dist/dashboard/{health-events-db-B3ChzN65.js.map → health-events-db-Do4NrOhC.js.map} +1 -1
- package/dist/dashboard/{hooks-CKhs3N68.js → hooks-CB4T47NC.js} +1 -1
- package/dist/dashboard/{hooks-CErbP8Oq.js → hooks-CjqXOlNb.js} +2 -2
- package/dist/dashboard/{hooks-CErbP8Oq.js.map → hooks-CjqXOlNb.js.map} +1 -1
- package/dist/dashboard/hume-CA2pftu_.js +3 -0
- package/dist/dashboard/{hume-CX_U3Qha.js → hume-JsAlMOJC.js} +2 -2
- package/dist/dashboard/{hume-CX_U3Qha.js.map → hume-JsAlMOJC.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-B57kGDUV.js → inspect-agent-7eour7EA.js} +3 -3
- package/dist/dashboard/{inspect-agent-B57kGDUV.js.map → inspect-agent-7eour7EA.js.map} +1 -1
- package/dist/dashboard/{io-yGovuG4U.js → io-CWlFW78i.js} +1 -1
- package/dist/dashboard/{io-AJg-mzFi.js → io-DKS6359z.js} +1 -1
- package/dist/dashboard/{io-AJg-mzFi.js.map → io-DKS6359z.js.map} +1 -1
- package/dist/dashboard/issue-id-vwYJdsf8.js +62 -0
- package/dist/dashboard/issue-id-vwYJdsf8.js.map +1 -0
- package/dist/dashboard/{issue-service-singleton-DQK42EqH.js → issue-service-singleton-Co__-6kL.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js → issue-service-singleton-Wv4xBm3y.js} +7 -7
- package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js.map → issue-service-singleton-Wv4xBm3y.js.map} +1 -1
- package/dist/dashboard/{label-cleanup-CZEsbtq9.js → label-cleanup-nVKTmIIW.js} +7 -4
- package/dist/dashboard/label-cleanup-nVKTmIIW.js.map +1 -0
- package/dist/dashboard/lifecycle-BcUmtkR4.js +7 -0
- package/dist/dashboard/{merge-agent-GLtMEsTu.js → merge-agent-CGN3TT0a.js} +1 -1
- package/dist/dashboard/{merge-agent-twroFuAh.js → merge-agent-yudQOPZc.js} +148 -46
- package/dist/dashboard/merge-agent-yudQOPZc.js.map +1 -0
- package/dist/dashboard/{paths-COdEvoXR.js → paths-BDyJ7BiV.js} +19 -2
- package/dist/dashboard/{paths-COdEvoXR.js.map → paths-BDyJ7BiV.js.map} +1 -1
- package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js → pipeline-notifier-CCSN-jar.js} +1 -1
- package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js.map → pipeline-notifier-CCSN-jar.js.map} +1 -1
- package/dist/dashboard/{plan-utils-BkCIhn3B.js → plan-utils-Bkcsqr_s.js} +3 -3
- package/dist/dashboard/{plan-utils-BkCIhn3B.js.map → plan-utils-Bkcsqr_s.js.map} +1 -1
- package/dist/dashboard/{prd-draft-D09Afalc.js → prd-draft-BD8oMkZ1.js} +2 -2
- package/dist/dashboard/{prd-draft-D09Afalc.js.map → prd-draft-BD8oMkZ1.js.map} +1 -1
- package/dist/dashboard/{projection-cache-DQ9zegkK.js → projection-cache-C0EL8s8h.js} +1 -1
- package/dist/dashboard/{projection-cache-DQ9zegkK.js.map → projection-cache-C0EL8s8h.js.map} +1 -1
- package/dist/dashboard/{projects-DyT3vSy-.js → projects-C5ozxjwP.js} +1 -1
- package/dist/dashboard/{projects-Cq3TWdPS.js → projects-CFVl4oHn.js} +25 -13
- package/dist/dashboard/projects-CFVl4oHn.js.map +1 -0
- package/dist/dashboard/{providers-Ck2sQd_F.js → providers-B5Y4H2Mg.js} +4 -4
- package/dist/dashboard/providers-B5Y4H2Mg.js.map +1 -0
- package/dist/dashboard/{providers-DVQnDekG.js → providers-csVZVPkE.js} +1 -1
- package/dist/dashboard/public/assets/{dist-CCJbQrSB.js → dist-CXaO6nOE.js} +1 -1
- package/dist/dashboard/public/assets/index-CzFZIb87.js +212 -0
- package/dist/dashboard/public/assets/index-OEEbThNN.css +1 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/rally-6McpKKRa.js +3 -0
- package/dist/dashboard/{rally-Cwuae-4C.js → rally-YjFRxIiC.js} +2 -2
- package/dist/dashboard/{rally-Cwuae-4C.js.map → rally-YjFRxIiC.js.map} +1 -1
- package/dist/dashboard/{rally-api-DSUxm7EO.js → rally-api-C0WqCSkT.js} +1 -1
- package/dist/dashboard/{rally-api-DSUxm7EO.js.map → rally-api-C0WqCSkT.js.map} +1 -1
- package/dist/dashboard/{rally-api-CEH5KZi4.js → rally-api-DNttdCW4.js} +1 -1
- package/dist/dashboard/{remote-BHTTMpJJ.js → remote-Cigqjj3f.js} +2 -2
- package/dist/dashboard/{remote-BXo_iIku.js → remote-ObpNZ7hF.js} +2 -2
- package/dist/dashboard/{remote-BXo_iIku.js.map → remote-ObpNZ7hF.js.map} +1 -1
- package/dist/dashboard/{remote-agents-CTKVhFFY.js → remote-agents-Bf3GuM7t.js} +1 -1
- package/dist/dashboard/{remote-agents-C0_0LLNd.js → remote-agents-DFyjT1Le.js} +1 -1
- package/dist/dashboard/{remote-agents-C0_0LLNd.js.map → remote-agents-DFyjT1Le.js.map} +1 -1
- package/dist/dashboard/{review-status-CK3eBGyb.js → review-status-BtXqWBhS.js} +1 -1
- package/dist/dashboard/{review-status-CV55Tl-n.js → review-status-Bymwzh2i.js} +44 -4
- package/dist/dashboard/{review-status-CV55Tl-n.js.map → review-status-Bymwzh2i.js.map} +1 -1
- package/dist/dashboard/server.js +559 -253
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{settings-CuHV-wcv.js → settings-BHlDG7TK.js} +2 -2
- package/dist/dashboard/settings-BHlDG7TK.js.map +1 -0
- package/dist/dashboard/settings-XWvDcj-D.js +2 -0
- package/dist/dashboard/{shadow-engineering-BUeZunaE.js → shadow-engineering-lIn1W_95.js} +1 -1
- package/dist/dashboard/{shadow-engineering-BUeZunaE.js.map → shadow-engineering-lIn1W_95.js.map} +1 -1
- package/dist/dashboard/{shadow-state-DHQ-kASN.js → shadow-state-BIexcxkv.js} +1 -1
- package/dist/dashboard/{shadow-state-DHQ-kASN.js.map → shadow-state-BIexcxkv.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js → spawn-planning-session-33Jf-d5T.js} +6 -6
- package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js.map → spawn-planning-session-33Jf-d5T.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-U0Lqpjen.js → spawn-planning-session-D5hrVdWM.js} +1 -1
- package/dist/dashboard/{specialist-context-ColzlmGE.js → specialist-context-DGukHSn8.js} +6 -6
- package/dist/dashboard/{specialist-context-ColzlmGE.js.map → specialist-context-DGukHSn8.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-BhmDpFIq.js → specialist-logs-CIw4qfTy.js} +1 -1
- package/dist/dashboard/{specialists-C6s3U6tX.js → specialists-B_zrayaP.js} +37 -36
- package/dist/dashboard/specialists-B_zrayaP.js.map +1 -0
- package/dist/dashboard/{specialists-Cny632-T.js → specialists-Cp-PgspS.js} +1 -1
- package/dist/dashboard/{test-agent-queue-tqI4VDsu.js → test-agent-queue-ypF_ecHo.js} +4 -4
- package/dist/dashboard/{test-agent-queue-tqI4VDsu.js.map → test-agent-queue-ypF_ecHo.js.map} +1 -1
- package/dist/dashboard/{tldr-daemon-BNFyS7W_.js → tldr-daemon-B_oLRD8z.js} +2 -2
- package/dist/dashboard/{tldr-daemon-BNFyS7W_.js.map → tldr-daemon-B_oLRD8z.js.map} +1 -1
- package/dist/dashboard/{tldr-daemon-A6JqC59u.js → tldr-daemon-Cfs0bXTi.js} +1 -1
- package/dist/dashboard/{tmux-DYGAVJfb.js → tmux-BzxdKItf.js} +1 -1
- package/dist/dashboard/{tmux-IlN1Slv-.js → tmux-LwG0tHhU.js} +2 -2
- package/dist/dashboard/{tmux-IlN1Slv-.js.map → tmux-LwG0tHhU.js.map} +1 -1
- package/dist/dashboard/{tracker-config-BzNLnmcE.js → tracker-config-BP59uH4V.js} +1 -1
- package/dist/dashboard/{tracker-config-CNM_5rEf.js → tracker-config-e7ph1QqT.js} +2 -2
- package/dist/dashboard/{tracker-config-CNM_5rEf.js.map → tracker-config-e7ph1QqT.js.map} +1 -1
- package/dist/dashboard/{tunnel-D2BkwU7k.js → tunnel-0RzzuXPf.js} +1 -1
- package/dist/dashboard/{tunnel-Dub2hiAA.js → tunnel-DldbBPWL.js} +2 -2
- package/dist/dashboard/{tunnel-Dub2hiAA.js.map → tunnel-DldbBPWL.js.map} +1 -1
- package/dist/dashboard/{types-CWA-o4UN.js → types-RKZjGE5N.js} +1 -1
- package/dist/dashboard/{types-CWA-o4UN.js.map → types-RKZjGE5N.js.map} +1 -1
- package/dist/dashboard/{vtt-parser-BAXygRf0.js → vtt-parser-99vFekRQ.js} +1 -1
- package/dist/dashboard/{vtt-parser-BAXygRf0.js.map → vtt-parser-99vFekRQ.js.map} +1 -1
- package/dist/dashboard/{work-agent-prompt-JYq_OugP.js → work-agent-prompt-fCg67nyo.js} +65 -10
- package/dist/dashboard/{work-agent-prompt-JYq_OugP.js.map → work-agent-prompt-fCg67nyo.js.map} +1 -1
- package/dist/dashboard/{work-type-router-Cxp8_ur2.js → work-type-router-CWVW2Wk_.js} +1 -1
- package/dist/dashboard/{work-type-router-Cxp8_ur2.js.map → work-type-router-CWVW2Wk_.js.map} +1 -1
- package/dist/dashboard/{work-type-router-Com2amST.js → work-type-router-Di5gCQwh.js} +1 -1
- package/dist/dashboard/{workflows-N1UTipYl.js → workflows-BSMipN07.js} +35 -17
- package/dist/dashboard/workflows-BSMipN07.js.map +1 -0
- package/dist/dashboard/workflows-DaYWQIS2.js +2 -0
- package/dist/dashboard/{workspace-config-cmp5_ipD.js → workspace-config-DVDR-Ukh.js} +1 -1
- package/dist/dashboard/workspace-config-DVDR-Ukh.js.map +1 -0
- package/dist/dashboard/{workspace-manager-CjpWPgzL.js → workspace-manager-BYfzs_t2.js} +1 -1
- package/dist/dashboard/{workspace-manager-D_y9ZmW_.js → workspace-manager-C7OfT62A.js} +44 -24
- package/dist/dashboard/workspace-manager-C7OfT62A.js.map +1 -0
- package/dist/{dns-BKzHm-2q.js → dns-D_aKQJjb.js} +1 -1
- package/dist/{dns-DZwOWvVO.js → dns-Yxq4NNS7.js} +1 -1
- package/dist/{dns-DZwOWvVO.js.map → dns-Yxq4NNS7.js.map} +1 -1
- package/dist/{factory-DFu3IT4r.js → factory-BRBGw6OB.js} +1 -1
- package/dist/{factory-DfzczxN1.js → factory-DzsOiZVc.js} +3 -3
- package/dist/{factory-DfzczxN1.js.map → factory-DzsOiZVc.js.map} +1 -1
- package/dist/{feedback-writer-CwdnOkPO.js → feedback-writer-ygXN5F9N.js} +2 -2
- package/dist/{feedback-writer-CwdnOkPO.js.map → feedback-writer-ygXN5F9N.js.map} +1 -1
- package/dist/{github-app-CHKwxOeQ.js → github-app-DykduJ0X.js} +1 -1
- package/dist/{github-app-CHKwxOeQ.js.map → github-app-DykduJ0X.js.map} +1 -1
- package/dist/hume-9nv1VmMV.js +3 -0
- package/dist/{hume-DnV-tDsh.js → hume-DoCbph2h.js} +2 -2
- package/dist/{hume-DnV-tDsh.js.map → hume-DoCbph2h.js.map} +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/issue-id-CAcekoIw.js +62 -0
- package/dist/issue-id-CAcekoIw.js.map +1 -0
- package/dist/{label-cleanup-31ElPqqv.js → label-cleanup-C8R9Rspn.js} +7 -4
- package/dist/label-cleanup-C8R9Rspn.js.map +1 -0
- package/dist/{manifest-DL0oDbpv.js → manifest-B4ghOD-V.js} +1 -1
- package/dist/{manifest-DL0oDbpv.js.map → manifest-B4ghOD-V.js.map} +1 -1
- package/dist/{merge-agent-VQH9z9t8.js → merge-agent-DlUiUanN.js} +86 -33
- package/dist/merge-agent-DlUiUanN.js.map +1 -0
- package/dist/{paths-lMaxrYtT.js → paths-CDJ_HsbN.js} +19 -2
- package/dist/{paths-lMaxrYtT.js.map → paths-CDJ_HsbN.js.map} +1 -1
- package/dist/{pipeline-notifier-OJ-d3Y60.js → pipeline-notifier-XgDdCdvT.js} +1 -1
- package/dist/{pipeline-notifier-OJ-d3Y60.js.map → pipeline-notifier-XgDdCdvT.js.map} +1 -1
- package/dist/{projects-CvLepaxC.js → projects-Bk-5QhFQ.js} +25 -13
- package/dist/projects-Bk-5QhFQ.js.map +1 -0
- package/dist/{projects-DMWmPeIU.js → projects-DhU7rAVN.js} +1 -1
- package/dist/{providers-DcCPZ5K4.js → providers-DSU1vfQF.js} +4 -4
- package/dist/providers-DSU1vfQF.js.map +1 -0
- package/dist/rally-DdPvGa-w.js +3 -0
- package/dist/{rally-uUUZXp1h.js → rally-Dy00NElU.js} +1 -1
- package/dist/{rally-uUUZXp1h.js.map → rally-Dy00NElU.js.map} +1 -1
- package/dist/{remote-CkLBqLJc.js → remote-CYiOJg0q.js} +2 -2
- package/dist/{remote-CkLBqLJc.js.map → remote-CYiOJg0q.js.map} +1 -1
- package/dist/{remote-agents-C5Bd2fgt.js → remote-agents-CZXrUF4f.js} +1 -1
- package/dist/{remote-agents-C5Bd2fgt.js.map → remote-agents-CZXrUF4f.js.map} +1 -1
- package/dist/{remote-agents-BTzD-wMQ.js → remote-agents-ycHHVsgf.js} +1 -1
- package/dist/{remote-workspace-Dxghqiti.js → remote-workspace-CA33UuVI.js} +4 -4
- package/dist/{remote-workspace-Dxghqiti.js.map → remote-workspace-CA33UuVI.js.map} +1 -1
- package/dist/{review-status-2TdtHNcs.js → review-status-D6H2WOw8.js} +1 -1
- package/dist/{review-status-Bm1bWNEa.js → review-status-DEDvCKMP.js} +44 -4
- package/dist/{review-status-Bm1bWNEa.js.map → review-status-DEDvCKMP.js.map} +1 -1
- package/dist/{tracker-C_62ukEq.js → settings-BcWPTrua.js} +7 -199
- package/dist/settings-BcWPTrua.js.map +1 -0
- package/dist/shadow-state-BZzxfEGw.js +2 -0
- package/dist/{shadow-state-CFFHf05M.js → shadow-state-CE3dQfll.js} +1 -1
- package/dist/{shadow-state-CFFHf05M.js.map → shadow-state-CE3dQfll.js.map} +1 -1
- package/dist/{specialist-context-BdNFsfMG.js → specialist-context-BAUWL1Fl.js} +6 -6
- package/dist/{specialist-context-BdNFsfMG.js.map → specialist-context-BAUWL1Fl.js.map} +1 -1
- package/dist/{specialist-logs-CLztE_bE.js → specialist-logs-DQKKQV9B.js} +1 -1
- package/dist/{specialists-aUoUVWsN.js → specialists-Bfb9ATzw.js} +1 -1
- package/dist/{specialists-DEKqgkxp.js → specialists-D7Kj5o6s.js} +35 -34
- package/dist/specialists-D7Kj5o6s.js.map +1 -0
- package/dist/sync-DMfgd389.js +693 -0
- package/dist/sync-DMfgd389.js.map +1 -0
- package/dist/sync-TL6y-8K6.js +2 -0
- package/dist/{tldr-daemon-BCEFPItr.js → tldr-daemon-CFx4LXAl.js} +2 -2
- package/dist/{tldr-daemon-BCEFPItr.js.map → tldr-daemon-CFx4LXAl.js.map} +1 -1
- package/dist/{tldr-daemon-xBAx4cBE.js → tldr-daemon-D_EooADG.js} +1 -1
- package/dist/{tmux-DN6H886Y.js → tmux-CBtui_Cl.js} +1 -1
- package/dist/{tmux-CKdNxxJx.js → tmux-D6Ah4I8z.js} +2 -2
- package/dist/{tmux-CKdNxxJx.js.map → tmux-D6Ah4I8z.js.map} +1 -1
- package/dist/tracker-BhYYvU3p.js +198 -0
- package/dist/tracker-BhYYvU3p.js.map +1 -0
- package/dist/{tracker-utils-CVU2W1sX.js → tracker-utils-ChQyut8w.js} +34 -12
- package/dist/tracker-utils-ChQyut8w.js.map +1 -0
- package/dist/{traefik-DHgBoWXX.js → traefik-C80EbDu_.js} +4 -4
- package/dist/{traefik-DHgBoWXX.js.map → traefik-C80EbDu_.js.map} +1 -1
- package/dist/{traefik-BR-edbZv.js → traefik-CgHl7Bge.js} +1 -1
- package/dist/{tunnel-BZO9Q5oe.js → tunnel-DXOJ1wMM.js} +1 -1
- package/dist/{tunnel-Bl1qNSyQ.js → tunnel-DzXEPwIc.js} +2 -2
- package/dist/{tunnel-Bl1qNSyQ.js.map → tunnel-DzXEPwIc.js.map} +1 -1
- package/dist/{types-DewGdaIP.js → types-BhJj1SP1.js} +1 -1
- package/dist/{types-DewGdaIP.js.map → types-BhJj1SP1.js.map} +1 -1
- package/dist/{work-type-router-CS2BB1vS.js → work-type-router-CHjciPyS.js} +3 -3
- package/dist/{work-type-router-CS2BB1vS.js.map → work-type-router-CHjciPyS.js.map} +1 -1
- package/dist/{workspace-config-CNXOpKuj.js → workspace-config-fUafvYMp.js} +1 -1
- package/dist/workspace-config-fUafvYMp.js.map +1 -0
- package/dist/workspace-manager-B9jS4Dsq.js +3 -0
- package/dist/{workspace-manager-CncdZkIy.js → workspace-manager-DuLhnzJV.js} +112 -27
- package/dist/workspace-manager-DuLhnzJV.js.map +1 -0
- package/package.json +2 -1
- package/scripts/post-merge-deploy.sh +25 -5
- package/scripts/record-cost-event.js +57 -7
- package/scripts/record-cost-event.js.map +1 -1
- package/skills/pan-help/SKILL.md +1 -1
- package/skills/pan-sync/SKILL.md +6 -6
- package/skills/workspace-add-repo/skill.md +46 -0
- package/templates/claude-md/sections/warnings.md +15 -2
- package/dist/clean-planning-sZXvy3Y5.js +0 -2
- package/dist/close-issue-Dml437qV.js +0 -2
- package/dist/close-issue-Dr7yZmrr.js.map +0 -1
- package/dist/compact-beads-iu218JcO.js +0 -2
- package/dist/dashboard/agent-enrichment-C67LJBgD.js.map +0 -1
- package/dist/dashboard/clean-planning-DCu3cOTu.js +0 -2
- package/dist/dashboard/close-issue-DfIggeZD.js.map +0 -1
- package/dist/dashboard/close-issue-DwdwYtar.js +0 -2
- package/dist/dashboard/compact-beads-DXY2fK2s.js +0 -2
- package/dist/dashboard/event-store-O9q0Gweh.js.map +0 -1
- package/dist/dashboard/hume-MZndNDVU.js +0 -3
- package/dist/dashboard/label-cleanup-CZEsbtq9.js.map +0 -1
- package/dist/dashboard/lifecycle-ZTYdrr2O.js +0 -7
- package/dist/dashboard/merge-agent-twroFuAh.js.map +0 -1
- package/dist/dashboard/projects-Cq3TWdPS.js.map +0 -1
- package/dist/dashboard/providers-Ck2sQd_F.js.map +0 -1
- package/dist/dashboard/public/assets/index-CpSmB2ts.css +0 -1
- package/dist/dashboard/public/assets/index-yarWhi0M.js +0 -214
- package/dist/dashboard/rally-CQ1OBJrJ.js +0 -3
- package/dist/dashboard/settings-CuHV-wcv.js.map +0 -1
- package/dist/dashboard/settings-DMeGBRsk.js +0 -2
- package/dist/dashboard/specialists-C6s3U6tX.js.map +0 -1
- package/dist/dashboard/workflows-B2ARUpOa.js +0 -2
- package/dist/dashboard/workflows-N1UTipYl.js.map +0 -1
- package/dist/dashboard/workspace-config-cmp5_ipD.js.map +0 -1
- package/dist/dashboard/workspace-manager-D_y9ZmW_.js.map +0 -1
- package/dist/hume-BjmwmJ9E.js +0 -3
- package/dist/label-cleanup-31ElPqqv.js.map +0 -1
- package/dist/merge-agent-VQH9z9t8.js.map +0 -1
- package/dist/projects-CvLepaxC.js.map +0 -1
- package/dist/providers-DcCPZ5K4.js.map +0 -1
- package/dist/rally-DR9x8--6.js +0 -3
- package/dist/shadow-state-p3jpGRPJ.js +0 -2
- package/dist/specialists-DEKqgkxp.js.map +0 -1
- package/dist/tracker-C_62ukEq.js.map +0 -1
- package/dist/tracker-utils-CVU2W1sX.js.map +0 -1
- package/dist/workspace-config-CNXOpKuj.js.map +0 -1
- package/dist/workspace-manager-CncdZkIy.js.map +0 -1
- package/dist/workspace-manager-Cx0r2Jnv.js +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers-DSU1vfQF.js","names":[],"sources":["../src/lib/providers.ts"],"sourcesContent":["/**\r\n * Provider Configuration and Compatibility\r\n *\r\n * Defines which LLM providers are compatible with Claude Code's API format.\r\n * - Direct providers: Implement Anthropic-compatible API (no router needed)\r\n * - Router providers: Require claude-code-router for API translation\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\r\nimport { join } from 'path';\r\nimport type { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel, MiniMaxModel } from './settings.js';\r\n\r\nexport type ProviderName = 'anthropic' | 'kimi' | 'openai' | 'google' | 'zai' | 'minimax' | 'openrouter';\r\n\r\n/**\r\n * Provider compatibility types\r\n * - direct: Anthropic-compatible API, use ANTHROPIC_BASE_URL directly\r\n * - router: Incompatible API, requires claude-code-router for translation\r\n */\r\nexport type ProviderCompatibility = 'direct' | 'router';\r\n\r\n/**\r\n * Provider configuration\r\n */\r\n/**\r\n * Auth type for direct providers:\r\n * - static: Use a long-lived API key passed via ANTHROPIC_AUTH_TOKEN (default)\r\n * - credential-file: Use apiKeyHelper to read a fresh token from a credential file.\r\n * Used for providers like Kimi Code Plan whose JWT tokens expire every ~15 minutes.\r\n */\r\nexport type ProviderAuthType = 'static' | 'credential-file';\r\n\r\nexport interface ProviderConfig {\r\n name: ProviderName;\r\n displayName: string;\r\n compatibility: ProviderCompatibility;\r\n baseUrl?: string; // For direct providers\r\n authType?: ProviderAuthType; // Defaults to 'static'\r\n credentialFile?: string; // Path to credential file (for 'credential-file' auth)\r\n credentialHelper?: string; // Script that reads credential file and prints token\r\n models: ModelId[];\r\n tested: boolean; // Whether compatibility has been verified\r\n description: string;\r\n}\r\n\r\n/**\r\n * All provider configurations\r\n */\r\nexport const PROVIDERS: Record<ProviderName, ProviderConfig> = {\r\n anthropic: {\r\n name: 'anthropic',\r\n displayName: 'Anthropic',\r\n compatibility: 'direct',\r\n models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'],\r\n tested: true,\r\n description: 'Native Claude API',\r\n },\r\n\r\n kimi: {\r\n name: 'kimi',\r\n displayName: 'Kimi (Moonshot AI)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.kimi.com/coding/',\r\n authType: 'credential-file',\r\n credentialFile: '~/.kimi/credentials/kimi-code.json',\r\n credentialHelper: '~/.panopticon/bin/kimi-token-helper.sh',\r\n models: [], // Kimi uses same model names as Anthropic\r\n tested: true,\r\n description: 'Anthropic-compatible API via Kimi Code Plan (OAuth token refresh)',\r\n },\r\n\r\n zai: {\r\n name: 'zai',\r\n displayName: 'Z.AI (GLM)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.z.ai/api/anthropic',\r\n models: ['glm-4.7-flash'],\r\n tested: true,\r\n description: 'Anthropic-compatible API, GLM-4.7 Flash (31B, fast and affordable)',\r\n },\r\n\r\n openai: {\r\n name: 'openai',\r\n displayName: 'OpenAI',\r\n compatibility: 'router',\r\n models: ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n\r\n google: {\r\n name: 'google',\r\n displayName: 'Google (Gemini)',\r\n compatibility: 'router',\r\n models: ['gemini-3-pro-preview', 'gemini-3-flash-preview'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n\r\n minimax: {\r\n name: 'minimax',\r\n displayName: 'MiniMax',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.minimax.io/anthropic',\r\n models: ['minimax-m2.7', 'minimax-m2.7-highspeed'],\r\n tested: true,\r\n description: 'Anthropic-compatible API, 10B active params, 100 tps highspeed variant',\r\n },\r\n\r\n openrouter: {\r\n name: 'openrouter',\r\n displayName: 'OpenRouter',\r\n compatibility: 'direct',\r\n baseUrl: 'https://openrouter.ai/api',\r\n models: [], // Dynamic models fetched from OpenRouter API; IDs contain '/'\r\n tested: true,\r\n description: 'Anthropic-compatible API aggregator. Model IDs contain \\'/\\' (e.g. qwen/qwen3.6-plus:free)',\r\n },\r\n};\r\n\r\n/**\r\n * Get provider for a given model ID\r\n */\r\nexport function getProviderForModel(modelId: ModelId | string): ProviderConfig {\r\n // OpenRouter model IDs always contain '/' (e.g. 'qwen/qwen3.6-plus:free')\r\n if (modelId.includes('/')) {\r\n return PROVIDERS.openrouter;\r\n }\r\n\r\n // Check Anthropic models\r\n if (['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'].includes(modelId)) {\r\n return PROVIDERS.anthropic;\r\n }\r\n\r\n // Check OpenAI models\r\n if (['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'].includes(modelId)) {\r\n return PROVIDERS.openai;\r\n }\r\n\r\n // Check Google models\r\n if (['gemini-3-pro-preview', 'gemini-3-flash-preview'].includes(modelId)) {\r\n return PROVIDERS.google;\r\n }\r\n\r\n // Check Z.AI models\r\n if (['glm-4.7-flash'].includes(modelId)) {\r\n return PROVIDERS.zai;\r\n }\r\n\r\n // Check Kimi models\r\n if (['kimi-k2', 'kimi-k2.5'].includes(modelId)) {\r\n return PROVIDERS.kimi;\r\n }\r\n\r\n // Check MiniMax models\r\n if (['minimax-m2.7', 'minimax-m2.7-highspeed'].includes(modelId)) {\r\n return PROVIDERS.minimax;\r\n }\r\n\r\n // Default to Anthropic if unknown\r\n return PROVIDERS.anthropic;\r\n}\r\n\r\n/**\r\n * Check if a provider requires claude-code-router\r\n */\r\nexport function requiresRouter(provider: ProviderName): boolean {\r\n return PROVIDERS[provider].compatibility === 'router';\r\n}\r\n\r\n/**\r\n * Get all providers that require router (have router compatibility)\r\n */\r\nexport function getRouterProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'router');\r\n}\r\n\r\n/**\r\n * Get all direct-compatible providers\r\n */\r\nexport function getDirectProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'direct');\r\n}\r\n\r\n/**\r\n * Check if any configured providers require router\r\n * Used to determine if router installation is needed\r\n */\r\nexport function needsRouter(apiKeys: { openai?: string; google?: string; zai?: string }): boolean {\r\n return !!(apiKeys.openai || apiKeys.google);\r\n}\r\n\r\n/**\r\n * Get environment variables for spawning agent with specific provider\r\n */\r\nexport function getProviderEnv(\r\n provider: ProviderConfig,\r\n apiKey: string\r\n): Record<string, string> {\r\n if (provider.compatibility === 'direct') {\r\n // Direct providers use ANTHROPIC_BASE_URL\r\n const env: Record<string, string> = {};\r\n\r\n if (provider.baseUrl) {\r\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\r\n }\r\n\r\n if (provider.name !== 'anthropic') {\r\n if (provider.authType === 'credential-file') {\r\n // Credential-file providers use apiKeyHelper for dynamic token refresh.\r\n // We still need an initial ANTHROPIC_AUTH_TOKEN for the first request,\r\n // but apiKeyHelper (configured via setupCredentialFileAuth) will keep it fresh.\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n // Refresh token every 60 seconds (kimi-cli refreshes credential file automatically)\r\n env.CLAUDE_CODE_API_KEY_HELPER_TTL_MS = '60000';\r\n } else {\r\n // Static providers use a long-lived API key\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n }\r\n }\r\n\r\n // Z.AI recommends longer timeout\r\n if (provider.name === 'zai') {\r\n env.API_TIMEOUT_MS = '300000';\r\n }\r\n\r\n return env;\r\n } else {\r\n // Router providers use local router proxy\r\n return {\r\n ANTHROPIC_BASE_URL: 'http://localhost:3456',\r\n ANTHROPIC_AUTH_TOKEN: 'router-managed',\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * For credential-file providers (e.g. Kimi Code Plan), configure Claude Code's\r\n * apiKeyHelper in the workspace settings so tokens are refreshed dynamically.\r\n *\r\n * This writes to .claude/settings.local.json in the workspace directory.\r\n * Must be called before spawning the agent.\r\n */\r\nexport function setupCredentialFileAuth(provider: ProviderConfig, workspacePath: string): void {\r\n if (provider.authType !== 'credential-file' || !provider.credentialHelper) return;\r\n\r\n const helperPath = provider.credentialHelper.replace('~', process.env.HOME || '');\r\n const claudeDir = join(workspacePath, '.claude');\r\n const settingsPath = join(claudeDir, 'settings.local.json');\r\n\r\n if (!existsSync(claudeDir)) {\r\n mkdirSync(claudeDir, { recursive: true });\r\n }\r\n\r\n // Read existing settings or start fresh\r\n let settings: Record<string, unknown> = {};\r\n if (existsSync(settingsPath)) {\r\n try {\r\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n } catch { /* start fresh */ }\r\n }\r\n\r\n // Set the apiKeyHelper to our token reader script\r\n settings.apiKeyHelper = helperPath;\r\n\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n}\r\n\r\n/**\r\n * Clear credential-file auth from workspace settings.\r\n *\r\n * When switching from a credential-file provider (e.g. Kimi) to a static/plan-based\r\n * provider (e.g. Anthropic), the apiKeyHelper must be removed from\r\n * .claude/settings.local.json. Otherwise Claude Code will keep using the stale\r\n * token helper and fail with \"Invalid API key\".\r\n */\r\nexport function clearCredentialFileAuth(workspacePath: string): void {\r\n const settingsPath = join(workspacePath, '.claude', 'settings.local.json');\r\n if (!existsSync(settingsPath)) return;\r\n\r\n try {\r\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n if (!settings.apiKeyHelper) return; // Nothing to clear\r\n\r\n delete settings.apiKeyHelper;\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n } catch { /* non-fatal */ }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -538,4 +538,4 @@ var init_rally = __esmMin((() => {
|
|
|
538
538
|
//#endregion
|
|
539
539
|
export { TrackerAuthError as a, NotImplementedError as i, init_rally as n, init_interface as o, IssueNotFoundError as r, RallyTracker as t };
|
|
540
540
|
|
|
541
|
-
//# sourceMappingURL=rally-
|
|
541
|
+
//# sourceMappingURL=rally-Dy00NElU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rally-uUUZXp1h.js","names":[],"sources":["../src/lib/tracker/interface.ts","../src/lib/tracker/rally-api.ts","../src/lib/tracker/rally.ts"],"sourcesContent":["/**\n * Issue Tracker Abstraction Layer\n *\n * Provides a unified interface for different issue tracking systems\n * (Linear, GitHub Issues, GitLab Issues, etc.)\n */\n\n// Supported tracker types\nexport type TrackerType = 'linear' | 'github' | 'gitlab' | 'rally';\n\n// Normalized issue state (lowest common denominator)\nexport type IssueState = 'open' | 'in_progress' | 'in_review' | 'closed';\n\n// Normalized issue format\nexport interface Issue {\n /** Tracker-specific unique ID */\n id: string;\n\n /** Human-readable reference (e.g., MIN-630, #42) */\n ref: string;\n\n /** Issue title */\n title: string;\n\n /** Issue description/body (markdown) */\n description: string;\n\n /** Normalized state */\n state: IssueState;\n\n /** Labels/tags */\n labels: string[];\n\n /** Assignee username/name */\n assignee?: string;\n\n /** Web URL to the issue */\n url: string;\n\n /** Which tracker this issue came from */\n tracker: TrackerType;\n\n /** Cross-tracker linked issue references */\n linkedIssues?: string[];\n\n /** Priority (1=urgent, 2=high, 3=normal, 4=low) */\n priority?: number;\n\n /** Due date (ISO string) */\n dueDate?: string;\n\n /** Creation timestamp (ISO string) */\n createdAt: string;\n\n /** Last update timestamp (ISO string) */\n updatedAt: string;\n\n /** Parent issue FormattedID (e.g., \"F1234\") for Rally hierarchy */\n parentRef?: string;\n\n /** Rally artifact type (e.g., \"HierarchicalRequirement\", \"PortfolioItem/Feature\") */\n artifactType?: string;\n\n /** Raw tracker state name before normalization (e.g., \"Discovering\", \"In-Progress\") */\n rawState?: string;\n}\n\n// Comment on an issue\nexport interface Comment {\n id: string;\n issueId: string;\n body: string;\n author: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Filters for listing issues\nexport interface IssueFilters {\n /** Filter by state */\n state?: IssueState;\n\n /** Filter by labels (AND logic) */\n labels?: string[];\n\n /** Filter by assignee */\n assignee?: string;\n\n /** Filter by team/project (tracker-specific) */\n team?: string;\n\n /** Search query for title/description */\n query?: string;\n\n /** Maximum number of results */\n limit?: number;\n\n /** Include closed issues (default: false) */\n includeClosed?: boolean;\n}\n\n// Data for creating a new issue\nexport interface NewIssue {\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n team?: string;\n priority?: number;\n dueDate?: string;\n}\n\n// Data for updating an issue\nexport interface IssueUpdate {\n title?: string;\n description?: string;\n state?: IssueState;\n labels?: string[];\n assignee?: string;\n priority?: number;\n dueDate?: string;\n}\n\n/**\n * Abstract interface for issue trackers.\n * Implementations must handle normalization to/from tracker-specific formats.\n */\nexport interface IssueTracker {\n /** Tracker type identifier */\n readonly name: TrackerType;\n\n /**\n * List issues matching filters\n */\n listIssues(filters?: IssueFilters): Promise<Issue[]>;\n\n /**\n * Get a single issue by ID or ref\n * @param id - Issue ID or human-readable ref (e.g., \"MIN-630\", \"#42\")\n */\n getIssue(id: string): Promise<Issue>;\n\n /**\n * Update an existing issue\n */\n updateIssue(id: string, update: IssueUpdate): Promise<Issue>;\n\n /**\n * Create a new issue\n */\n createIssue(issue: NewIssue): Promise<Issue>;\n\n /**\n * Get comments on an issue\n */\n getComments(issueId: string): Promise<Comment[]>;\n\n /**\n * Add a comment to an issue\n */\n addComment(issueId: string, body: string): Promise<Comment>;\n\n /**\n * Transition issue to a new state\n */\n transitionIssue(id: string, state: IssueState): Promise<void>;\n\n /**\n * Link a PR/MR to an issue\n */\n linkPR(issueId: string, prUrl: string): Promise<void>;\n}\n\n/**\n * Error thrown when a tracker feature is not implemented\n */\nexport class NotImplementedError extends Error {\n constructor(feature: string) {\n super(`Not implemented: ${feature}`);\n this.name = 'NotImplementedError';\n }\n}\n\n/**\n * Error thrown when an issue is not found\n */\nexport class IssueNotFoundError extends Error {\n constructor(id: string, tracker: TrackerType) {\n super(`Issue not found: ${id} (tracker: ${tracker})`);\n this.name = 'IssueNotFoundError';\n }\n}\n\n/**\n * Error thrown when tracker authentication fails\n */\nexport class TrackerAuthError extends Error {\n constructor(tracker: TrackerType, message: string) {\n super(`Authentication failed for ${tracker}: ${message}`);\n this.name = 'TrackerAuthError';\n }\n}\n","/**\n * Rally WSAPI REST Client\n *\n * Thin wrapper around native fetch for Rally Web Services API v2.0.\n * Provides typed methods for query, create, and update operations.\n */\n\nexport interface RallyQueryConfig {\n type: string;\n fetch?: string[];\n query?: string;\n limit?: number;\n workspace?: string;\n project?: string;\n projectScopeDown?: boolean;\n order?: string;\n}\n\nexport interface RallyQueryResult {\n QueryResult: {\n Results: any[];\n TotalResultCount: number;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyCreateConfig {\n type: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyCreateResult {\n CreateResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyUpdateConfig {\n type: string;\n ref: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyUpdateResult {\n OperationResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyApiConfig {\n apiKey: string;\n server?: string;\n requestOptions?: {\n headers?: Record<string, string>;\n };\n}\n\nexport class RallyRestApi {\n private apiKey: string;\n public server: string;\n private customHeaders: Record<string, string>;\n\n constructor(config: RallyApiConfig) {\n this.apiKey = config.apiKey;\n this.server = config.server || 'https://rally1.rallydev.com';\n this.customHeaders = config.requestOptions?.headers || {};\n }\n\n /**\n * Query Rally artifacts\n */\n async query(config: RallyQueryConfig): Promise<RallyQueryResult> {\n const params = new URLSearchParams();\n\n if (config.query) {\n params.set('query', config.query);\n }\n\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n if (config.limit !== undefined) {\n params.set('pagesize', String(config.limit));\n }\n\n if (config.workspace) {\n params.set('workspace', config.workspace);\n }\n\n if (config.project) {\n params.set('project', config.project);\n if (config.projectScopeDown) {\n params.set('projectScopeDown', 'true');\n }\n }\n\n if (config.order) {\n params.set('order', config.order);\n }\n\n const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Unauthorized: Invalid API key or insufficient permissions');\n }\n throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyQueryResult;\n\n if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {\n const errorDetail = result.QueryResult.Errors.join(', ');\n const queryDetail = config.query ? ` (Query: ${config.query})` : '';\n if (process.env.DEBUG?.includes('rally')) {\n console.error('[Rally WSAPI] Query failed:', { query: config.query, errors: result.QueryResult.Errors });\n }\n throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);\n }\n\n return result;\n }\n\n /**\n * Create a Rally object\n */\n async create(config: RallyCreateConfig): Promise<RallyCreateResult> {\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyCreateResult;\n\n if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {\n throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n\n /**\n * Update a Rally object\n */\n async update(config: RallyUpdateConfig): Promise<RallyUpdateResult> {\n // Extract ObjectID from ref (e.g., \"/hierarchicalrequirement/12345\" -> \"12345\")\n const objectId = config.ref.split('/').pop();\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyUpdateResult;\n\n if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {\n throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n}\n","/**\n * Rally Tracker Adapter\n *\n * Implements IssueTracker interface for Broadcom Rally (formerly CA Agile Central).\n * Supports all Rally work item types: User Stories, Defects, Tasks, and Features.\n */\n\nimport { RallyRestApi } from './rally-api.js';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError, NotImplementedError } from './interface.js';\n\n// Map Rally ScheduleState/State to normalized IssueState.\n// Covers all standard states for User Stories, Defects, Tasks, and Features.\nconst STATE_MAP: Record<string, IssueState> = {\n // User Stories (ScheduleState)\n 'New': 'open',\n 'Idea': 'open',\n 'Defined': 'open',\n 'In-Progress': 'in_progress',\n 'Completed': 'closed',\n 'Accepted': 'closed',\n\n // Defects (State)\n 'Submitted': 'open',\n 'Open': 'in_progress', // \"Open\" defects are actively being worked\n 'Fixed': 'closed',\n 'Closed': 'closed',\n\n // Features / PortfolioItems (State)\n 'Discovering': 'open',\n 'Developing': 'in_progress',\n 'Done': 'closed',\n};\n\n// Rally artifact types we support\ntype RallyArtifactType = 'HierarchicalRequirement' | 'Defect' | 'Task' | 'PortfolioItem/Feature';\n\n/**\n * Type-specific query configuration.\n *\n * Rally WSAPI cannot filter generic Artifact by ScheduleState because not all\n * subtypes have that field. Instead, we query each type separately with its\n * own state field and merge the results. (PAN-168)\n */\ninterface ArtifactTypeQuery {\n /** WSAPI endpoint type (lowercase) */\n type: string;\n /** The state field used for filtering on this artifact type */\n stateField: 'ScheduleState' | 'State';\n /** State values that represent \"closed\" for this type */\n closedStates: string[];\n}\n\nconst QUERYABLE_TYPES: ArtifactTypeQuery[] = [\n { type: 'hierarchicalrequirement', stateField: 'ScheduleState', closedStates: ['Completed', 'Accepted'] },\n { type: 'defect', stateField: 'State', closedStates: ['Closed'] },\n { type: 'task', stateField: 'State', closedStates: ['Completed'] },\n { type: 'portfolioitem/feature', stateField: 'State', closedStates: ['Done'] },\n];\n\nconst FETCH_FIELDS = [\n 'ObjectID',\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n 'PortfolioItem',\n '_type',\n];\n\n// Rally priority strings to numbers\nconst PRIORITY_MAP: Record<string, number> = {\n 'Resolve Immediately': 0,\n High: 1,\n Normal: 2,\n Low: 3,\n};\n\n// Reverse priority mapping\nconst REVERSE_PRIORITY_MAP: Record<number, string> = {\n 0: 'Resolve Immediately',\n 1: 'High',\n 2: 'Normal',\n 3: 'Low',\n 4: 'Low',\n};\n\nexport interface RallyConfig {\n apiKey: string;\n server?: string; // Default: rally1.rallydev.com\n workspace?: string; // Rally workspace OID (e.g., \"/workspace/12345\")\n project?: string; // Rally project OID (e.g., \"/project/67890\")\n}\n\nexport class RallyTracker implements IssueTracker {\n readonly name: TrackerType = 'rally' as TrackerType;\n private restApi: RallyRestApi;\n private workspace?: string;\n private project?: string;\n\n constructor(config: RallyConfig) {\n if (!config.apiKey) {\n throw new TrackerAuthError('rally' as TrackerType, 'API key is required');\n }\n\n this.restApi = new RallyRestApi({\n apiKey: config.apiKey,\n server: config.server || 'https://rally1.rallydev.com',\n requestOptions: {\n headers: {\n 'X-RallyIntegrationType': 'Panopticon',\n 'X-RallyIntegrationName': 'Panopticon CLI',\n 'X-RallyIntegrationVendor': 'Mind Your Now',\n 'X-RallyIntegrationVersion': '0.2.0',\n },\n },\n });\n\n this.workspace = config.workspace;\n this.project = config.project;\n }\n\n /**\n * List issues by querying each artifact type separately and merging results.\n *\n * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact\n * endpoint because not all subtypes have that field. We query each type with\n * its own state field, then merge and sort. (PAN-168)\n */\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n if (process.env.DEBUG?.includes('rally')) {\n console.debug('[Rally] Query filters:', JSON.stringify(filters));\n }\n\n const limit = filters?.limit ?? 50;\n\n // Extract ObjectID from project ref for explicit query scoping\n // e.g., \"/project/822404704163\" → \"822404704163\"\n let projectObjectId: string | undefined;\n if (this.project) {\n const match = this.project.match(/\\/project\\/(\\d+)/);\n if (match) projectObjectId = match[1];\n }\n\n const queries = QUERYABLE_TYPES.map(async (artifactType) => {\n const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);\n\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] ${artifactType.type} query:`, queryString);\n }\n\n const query: any = {\n type: artifactType.type,\n fetch: FETCH_FIELDS,\n limit,\n query: queryString,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n if (this.project) {\n query.project = this.project;\n query.projectScopeDown = true;\n }\n\n try {\n const result = await this.queryRally(query);\n return result.Results.map((artifact: any) => this.normalizeIssue(artifact));\n } catch (error: any) {\n if (error.message?.includes('Unauthorized') || error.message?.includes('401')) {\n throw new TrackerAuthError('rally' as TrackerType, 'Invalid API key or insufficient permissions');\n }\n // Log and skip individual type failures so other types still return\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);\n }\n return [];\n }\n });\n\n const results = await Promise.all(queries);\n const allIssues = results.flat();\n\n // Sort by most recently updated first, then apply overall limit\n allIssues.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n\n return allIssues.slice(0, limit);\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Rally FormattedIDs look like: US123, DE456, TA789, F012\n const query: any = {\n type: 'artifact',\n fetch: [\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n '_type',\n ],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n return this.normalizeIssue(result.Results[0]);\n } catch (error: any) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n // Get the Rally object reference\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', '_type'],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.Name = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.Description = update.description;\n }\n if (update.state !== undefined) {\n const artifactType = (artifact._type || '').toLowerCase();\n const kind = artifactType.startsWith('portfolioitem') ? 'feature'\n : artifactType === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(update.state, kind);\n if (kind === 'story') {\n updatePayload.ScheduleState = rallyState;\n } else {\n updatePayload.State = rallyState;\n }\n }\n if (update.priority !== undefined) {\n updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || 'Normal';\n }\n if (update.dueDate !== undefined) {\n updatePayload.DueDate = update.dueDate;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n if (!this.project && !newIssue.team) {\n throw new Error('Project is required to create an issue. Set it in config or provide team field.');\n }\n\n const project = newIssue.team || this.project;\n\n // Default to HierarchicalRequirement (User Story) for new issues\n const createPayload: Record<string, unknown> = {\n Name: newIssue.title,\n Description: newIssue.description || '',\n Project: project,\n };\n\n if (newIssue.priority !== undefined) {\n createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || 'Normal';\n }\n if (newIssue.dueDate) {\n createPayload.DueDate = newIssue.dueDate;\n }\n if (this.workspace) {\n createPayload.Workspace = this.workspace;\n }\n\n const result = await this.createRally('hierarchicalrequirement', createPayload);\n\n // Fetch the created issue to return normalized format\n return this.getIssue(result.Object.FormattedID);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.getIssue(issueId);\n\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n return [];\n }\n\n const artifact = result.Results[0];\n if (!artifact.Discussion) {\n return [];\n }\n\n // Query ConversationPosts for this Discussion\n const postsQuery: any = {\n type: 'conversationpost',\n fetch: ['ObjectID', 'Text', 'User', 'CreationDate', 'PostNumber'],\n query: `(Discussion = \"${artifact.Discussion._ref}\")`,\n order: 'PostNumber',\n };\n\n const postsResult = await this.queryRally(postsQuery);\n\n return (postsResult.Results || []).map((post: any) => ({\n id: post.ObjectID,\n issueId,\n body: post.Text || '',\n author: post.User?._refObjectName || 'Unknown',\n createdAt: post.CreationDate,\n updatedAt: post.CreationDate, // Rally doesn't track comment updates separately\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(issueId, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n\n // If no Discussion exists, create one\n let discussionRef = artifact.Discussion?._ref;\n if (!discussionRef) {\n const discussionResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: discussionResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n // Add a post to existing Discussion\n const postResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: postResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n await this.updateIssue(id, { state });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // Add a comment with the PR link\n await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);\n }\n\n // Private helper methods\n\n /**\n * Build a Rally WSAPI query string for a specific artifact type.\n *\n * Each artifact type has its own state field:\n * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)\n * - Defect: State (Submitted, Open, Fixed, Closed)\n * - Task: State (Defined, In-Progress, Completed)\n *\n * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.\n * (PAN-166, PAN-168)\n */\n private buildQueryStringForType(\n filters: IssueFilters | undefined,\n artifactType: ArtifactTypeQuery,\n projectObjectId?: string,\n ): string {\n const conditions: string[] = [];\n\n // Explicit project scoping — more reliable than WSAPI project param alone\n if (projectObjectId) {\n conditions.push(`(Project.ObjectID = \"${projectObjectId}\")`);\n }\n\n if (filters?.state && !filters.includeClosed) {\n const kind = artifactType.type.startsWith('portfolioitem') ? 'feature'\n : artifactType.type === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(filters.state, kind);\n conditions.push(`(${artifactType.stateField} = \"${rallyState}\")`);\n }\n\n if (!filters?.includeClosed) {\n // Exclude closed states for this specific artifact type\n const closedConditions = artifactType.closedStates.map(\n (state) => `(${artifactType.stateField} != \"${state}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const closedExpr = closedConditions.reduce(\n (acc, cond) => (acc ? `(${acc} AND ${cond})` : cond),\n '',\n );\n conditions.push(closedExpr);\n }\n\n if (filters?.assignee) {\n conditions.push(`(Owner.Name contains \"${filters.assignee}\")`);\n }\n\n if (filters?.labels && filters.labels.length > 0) {\n const labelConditions = filters.labels.map(\n (label) => `(Tags.Name contains \"${label}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n conditions.push(labelExpr);\n }\n\n if (filters?.query) {\n conditions.push(`((Name contains \"${filters.query}\") OR (Description contains \"${filters.query}\"))`);\n }\n\n // Rally WSAPI only supports binary (expr AND expr) — reduce into nested pairs\n return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n }\n\n private normalizeIssue(rallyArtifact: any): Issue {\n // Determine state from ScheduleState (User Stories, Tasks) or State (Defects, Features)\n // For PortfolioItem/Feature, State is a Rally ref object with Name/_refObjectName, not a string\n const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || 'Defined';\n const stateValue = typeof rawStateValue === 'object' && rawStateValue !== null\n ? (rawStateValue.Name || rawStateValue._refObjectName || 'Defined')\n : rawStateValue;\n const state = this.mapState(stateValue);\n\n // Extract tags — ensure all entries are strings\n const labels: string[] = [];\n if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {\n for (const tag of rallyArtifact.Tags._tagsNameArray) {\n if (typeof tag === 'string') {\n labels.push(tag);\n } else if (tag?.Name) {\n labels.push(tag.Name);\n }\n }\n }\n\n // Map priority\n const priority = rallyArtifact.Priority\n ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2\n : undefined;\n\n // Use ObjectID if available, fall back to FormattedID\n const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;\n const artifactType = rallyArtifact._type || 'artifact';\n\n // Build URL - Rally's web UI detail path\n const baseUrl = this.restApi.server.replace('/slm/webservice/', '');\n const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;\n\n // Resolve parent reference.\n // For User Stories, PortfolioItem links to the parent Feature (F-prefixed),\n // while Parent links to a parent Story in the hierarchy. Prefer PortfolioItem\n // so that stories are correctly grouped under their Feature. (PAN-202)\n let parentRef: string | undefined;\n if (rallyArtifact.PortfolioItem) {\n if (rallyArtifact.PortfolioItem.FormattedID) {\n parentRef = rallyArtifact.PortfolioItem.FormattedID;\n } else if (rallyArtifact.PortfolioItem._refObjectName) {\n parentRef = rallyArtifact.PortfolioItem._refObjectName;\n }\n } else if (rallyArtifact.Parent) {\n if (rallyArtifact.Parent.FormattedID) {\n parentRef = rallyArtifact.Parent.FormattedID;\n } else if (rallyArtifact.Parent._refObjectName) {\n parentRef = rallyArtifact.Parent._refObjectName;\n }\n }\n\n return {\n id: String(objectId),\n ref: rallyArtifact.FormattedID,\n title: rallyArtifact.Name || '',\n description: rallyArtifact.Description || '',\n state,\n labels,\n assignee: rallyArtifact.Owner?._refObjectName,\n url,\n tracker: 'rally' as TrackerType,\n priority,\n dueDate: rallyArtifact.DueDate,\n createdAt: rallyArtifact.CreationDate,\n updatedAt: rallyArtifact.LastUpdateDate,\n parentRef,\n artifactType,\n rawState: stateValue,\n };\n }\n\n private mapState(rallyState: string): IssueState {\n return STATE_MAP[rallyState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState, kind: 'story' | 'defect' | 'feature' = 'story'): string {\n if (kind === 'feature') {\n // Features / PortfolioItems use State: Discovering, Developing, Done\n switch (state) {\n case 'open': return 'Discovering';\n case 'in_progress':\n case 'in_review': return 'Developing';\n case 'closed': return 'Done';\n default: return 'Discovering';\n }\n }\n if (kind === 'defect') {\n // Defects use State: Submitted, Open, Fixed, Closed\n switch (state) {\n case 'open': return 'Submitted';\n case 'in_progress':\n case 'in_review': return 'Open';\n case 'closed': return 'Closed';\n default: return 'Submitted';\n }\n }\n // User Stories / Tasks use ScheduleState: Defined, In-Progress, Completed\n switch (state) {\n case 'open': return 'Defined';\n case 'in_progress':\n case 'in_review': return 'In-Progress';\n case 'closed': return 'Completed';\n default: return 'Defined';\n }\n }\n\n // Rally API wrapper methods\n private async queryRally(queryConfig: any): Promise<any> {\n const result = await this.restApi.query(queryConfig);\n // Extract Results from WSAPI response format\n return {\n Results: result.QueryResult.Results,\n TotalResultCount: result.QueryResult.TotalResultCount,\n };\n }\n\n private async createRally(type: string, data: any): Promise<any> {\n const result = await this.restApi.create({\n type,\n data,\n fetch: ['FormattedID', 'ObjectID', '_ref'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.CreateResult.Object,\n };\n }\n\n private async updateRally(type: string, ref: string, data: any): Promise<any> {\n const result = await this.restApi.update({\n type,\n ref,\n data,\n fetch: ['FormattedID', 'ObjectID'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.OperationResult.Object,\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rally-Dy00NElU.js","names":[],"sources":["../src/lib/tracker/interface.ts","../src/lib/tracker/rally-api.ts","../src/lib/tracker/rally.ts"],"sourcesContent":["/**\n * Issue Tracker Abstraction Layer\n *\n * Provides a unified interface for different issue tracking systems\n * (Linear, GitHub Issues, GitLab Issues, etc.)\n */\n\n// Supported tracker types\nexport type TrackerType = 'linear' | 'github' | 'gitlab' | 'rally';\n\n// Normalized issue state (lowest common denominator)\nexport type IssueState = 'open' | 'in_progress' | 'in_review' | 'closed';\n\n// Normalized issue format\nexport interface Issue {\n /** Tracker-specific unique ID */\n id: string;\n\n /** Human-readable reference (e.g., MIN-630, #42) */\n ref: string;\n\n /** Issue title */\n title: string;\n\n /** Issue description/body (markdown) */\n description: string;\n\n /** Normalized state */\n state: IssueState;\n\n /** Labels/tags */\n labels: string[];\n\n /** Assignee username/name */\n assignee?: string;\n\n /** Web URL to the issue */\n url: string;\n\n /** Which tracker this issue came from */\n tracker: TrackerType;\n\n /** Cross-tracker linked issue references */\n linkedIssues?: string[];\n\n /** Priority (1=urgent, 2=high, 3=normal, 4=low) */\n priority?: number;\n\n /** Due date (ISO string) */\n dueDate?: string;\n\n /** Creation timestamp (ISO string) */\n createdAt: string;\n\n /** Last update timestamp (ISO string) */\n updatedAt: string;\n\n /** Parent issue FormattedID (e.g., \"F1234\") for Rally hierarchy */\n parentRef?: string;\n\n /** Rally artifact type (e.g., \"HierarchicalRequirement\", \"PortfolioItem/Feature\") */\n artifactType?: string;\n\n /** Raw tracker state name before normalization (e.g., \"Discovering\", \"In-Progress\") */\n rawState?: string;\n}\n\n// Comment on an issue\nexport interface Comment {\n id: string;\n issueId: string;\n body: string;\n author: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Filters for listing issues\nexport interface IssueFilters {\n /** Filter by state */\n state?: IssueState;\n\n /** Filter by labels (AND logic) */\n labels?: string[];\n\n /** Filter by assignee */\n assignee?: string;\n\n /** Filter by team/project (tracker-specific) */\n team?: string;\n\n /** Search query for title/description */\n query?: string;\n\n /** Maximum number of results */\n limit?: number;\n\n /** Include closed issues (default: false) */\n includeClosed?: boolean;\n}\n\n// Data for creating a new issue\nexport interface NewIssue {\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n team?: string;\n priority?: number;\n dueDate?: string;\n}\n\n// Data for updating an issue\nexport interface IssueUpdate {\n title?: string;\n description?: string;\n state?: IssueState;\n labels?: string[];\n assignee?: string;\n priority?: number;\n dueDate?: string;\n}\n\n/**\n * Abstract interface for issue trackers.\n * Implementations must handle normalization to/from tracker-specific formats.\n */\nexport interface IssueTracker {\n /** Tracker type identifier */\n readonly name: TrackerType;\n\n /**\n * List issues matching filters\n */\n listIssues(filters?: IssueFilters): Promise<Issue[]>;\n\n /**\n * Get a single issue by ID or ref\n * @param id - Issue ID or human-readable ref (e.g., \"MIN-630\", \"#42\")\n */\n getIssue(id: string): Promise<Issue>;\n\n /**\n * Update an existing issue\n */\n updateIssue(id: string, update: IssueUpdate): Promise<Issue>;\n\n /**\n * Create a new issue\n */\n createIssue(issue: NewIssue): Promise<Issue>;\n\n /**\n * Get comments on an issue\n */\n getComments(issueId: string): Promise<Comment[]>;\n\n /**\n * Add a comment to an issue\n */\n addComment(issueId: string, body: string): Promise<Comment>;\n\n /**\n * Transition issue to a new state\n */\n transitionIssue(id: string, state: IssueState): Promise<void>;\n\n /**\n * Link a PR/MR to an issue\n */\n linkPR(issueId: string, prUrl: string): Promise<void>;\n}\n\n/**\n * Error thrown when a tracker feature is not implemented\n */\nexport class NotImplementedError extends Error {\n constructor(feature: string) {\n super(`Not implemented: ${feature}`);\n this.name = 'NotImplementedError';\n }\n}\n\n/**\n * Error thrown when an issue is not found\n */\nexport class IssueNotFoundError extends Error {\n constructor(id: string, tracker: TrackerType) {\n super(`Issue not found: ${id} (tracker: ${tracker})`);\n this.name = 'IssueNotFoundError';\n }\n}\n\n/**\n * Error thrown when tracker authentication fails\n */\nexport class TrackerAuthError extends Error {\n constructor(tracker: TrackerType, message: string) {\n super(`Authentication failed for ${tracker}: ${message}`);\n this.name = 'TrackerAuthError';\n }\n}\n","/**\n * Rally WSAPI REST Client\n *\n * Thin wrapper around native fetch for Rally Web Services API v2.0.\n * Provides typed methods for query, create, and update operations.\n */\n\nexport interface RallyQueryConfig {\n type: string;\n fetch?: string[];\n query?: string;\n limit?: number;\n workspace?: string;\n project?: string;\n projectScopeDown?: boolean;\n order?: string;\n}\n\nexport interface RallyQueryResult {\n QueryResult: {\n Results: any[];\n TotalResultCount: number;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyCreateConfig {\n type: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyCreateResult {\n CreateResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyUpdateConfig {\n type: string;\n ref: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyUpdateResult {\n OperationResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyApiConfig {\n apiKey: string;\n server?: string;\n requestOptions?: {\n headers?: Record<string, string>;\n };\n}\n\nexport class RallyRestApi {\n private apiKey: string;\n public server: string;\n private customHeaders: Record<string, string>;\n\n constructor(config: RallyApiConfig) {\n this.apiKey = config.apiKey;\n this.server = config.server || 'https://rally1.rallydev.com';\n this.customHeaders = config.requestOptions?.headers || {};\n }\n\n /**\n * Query Rally artifacts\n */\n async query(config: RallyQueryConfig): Promise<RallyQueryResult> {\n const params = new URLSearchParams();\n\n if (config.query) {\n params.set('query', config.query);\n }\n\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n if (config.limit !== undefined) {\n params.set('pagesize', String(config.limit));\n }\n\n if (config.workspace) {\n params.set('workspace', config.workspace);\n }\n\n if (config.project) {\n params.set('project', config.project);\n if (config.projectScopeDown) {\n params.set('projectScopeDown', 'true');\n }\n }\n\n if (config.order) {\n params.set('order', config.order);\n }\n\n const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Unauthorized: Invalid API key or insufficient permissions');\n }\n throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyQueryResult;\n\n if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {\n const errorDetail = result.QueryResult.Errors.join(', ');\n const queryDetail = config.query ? ` (Query: ${config.query})` : '';\n if (process.env.DEBUG?.includes('rally')) {\n console.error('[Rally WSAPI] Query failed:', { query: config.query, errors: result.QueryResult.Errors });\n }\n throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);\n }\n\n return result;\n }\n\n /**\n * Create a Rally object\n */\n async create(config: RallyCreateConfig): Promise<RallyCreateResult> {\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyCreateResult;\n\n if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {\n throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n\n /**\n * Update a Rally object\n */\n async update(config: RallyUpdateConfig): Promise<RallyUpdateResult> {\n // Extract ObjectID from ref (e.g., \"/hierarchicalrequirement/12345\" -> \"12345\")\n const objectId = config.ref.split('/').pop();\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyUpdateResult;\n\n if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {\n throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n}\n","/**\n * Rally Tracker Adapter\n *\n * Implements IssueTracker interface for Broadcom Rally (formerly CA Agile Central).\n * Supports all Rally work item types: User Stories, Defects, Tasks, and Features.\n */\n\nimport { RallyRestApi } from './rally-api.js';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError, NotImplementedError } from './interface.js';\n\n// Map Rally ScheduleState/State to normalized IssueState.\n// Covers all standard states for User Stories, Defects, Tasks, and Features.\nconst STATE_MAP: Record<string, IssueState> = {\n // User Stories (ScheduleState)\n 'New': 'open',\n 'Idea': 'open',\n 'Defined': 'open',\n 'In-Progress': 'in_progress',\n 'Completed': 'closed',\n 'Accepted': 'closed',\n\n // Defects (State)\n 'Submitted': 'open',\n 'Open': 'in_progress', // \"Open\" defects are actively being worked\n 'Fixed': 'closed',\n 'Closed': 'closed',\n\n // Features / PortfolioItems (State)\n 'Discovering': 'open',\n 'Developing': 'in_progress',\n 'Done': 'closed',\n};\n\n// Rally artifact types we support\ntype RallyArtifactType = 'HierarchicalRequirement' | 'Defect' | 'Task' | 'PortfolioItem/Feature';\n\n/**\n * Type-specific query configuration.\n *\n * Rally WSAPI cannot filter generic Artifact by ScheduleState because not all\n * subtypes have that field. Instead, we query each type separately with its\n * own state field and merge the results. (PAN-168)\n */\ninterface ArtifactTypeQuery {\n /** WSAPI endpoint type (lowercase) */\n type: string;\n /** The state field used for filtering on this artifact type */\n stateField: 'ScheduleState' | 'State';\n /** State values that represent \"closed\" for this type */\n closedStates: string[];\n}\n\nconst QUERYABLE_TYPES: ArtifactTypeQuery[] = [\n { type: 'hierarchicalrequirement', stateField: 'ScheduleState', closedStates: ['Completed', 'Accepted'] },\n { type: 'defect', stateField: 'State', closedStates: ['Closed'] },\n { type: 'task', stateField: 'State', closedStates: ['Completed'] },\n { type: 'portfolioitem/feature', stateField: 'State', closedStates: ['Done'] },\n];\n\nconst FETCH_FIELDS = [\n 'ObjectID',\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n 'PortfolioItem',\n '_type',\n];\n\n// Rally priority strings to numbers\nconst PRIORITY_MAP: Record<string, number> = {\n 'Resolve Immediately': 0,\n High: 1,\n Normal: 2,\n Low: 3,\n};\n\n// Reverse priority mapping\nconst REVERSE_PRIORITY_MAP: Record<number, string> = {\n 0: 'Resolve Immediately',\n 1: 'High',\n 2: 'Normal',\n 3: 'Low',\n 4: 'Low',\n};\n\nexport interface RallyConfig {\n apiKey: string;\n server?: string; // Default: rally1.rallydev.com\n workspace?: string; // Rally workspace OID (e.g., \"/workspace/12345\")\n project?: string; // Rally project OID (e.g., \"/project/67890\")\n}\n\nexport class RallyTracker implements IssueTracker {\n readonly name: TrackerType = 'rally' as TrackerType;\n private restApi: RallyRestApi;\n private workspace?: string;\n private project?: string;\n\n constructor(config: RallyConfig) {\n if (!config.apiKey) {\n throw new TrackerAuthError('rally' as TrackerType, 'API key is required');\n }\n\n this.restApi = new RallyRestApi({\n apiKey: config.apiKey,\n server: config.server || 'https://rally1.rallydev.com',\n requestOptions: {\n headers: {\n 'X-RallyIntegrationType': 'Panopticon',\n 'X-RallyIntegrationName': 'Panopticon CLI',\n 'X-RallyIntegrationVendor': 'Mind Your Now',\n 'X-RallyIntegrationVersion': '0.2.0',\n },\n },\n });\n\n this.workspace = config.workspace;\n this.project = config.project;\n }\n\n /**\n * List issues by querying each artifact type separately and merging results.\n *\n * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact\n * endpoint because not all subtypes have that field. We query each type with\n * its own state field, then merge and sort. (PAN-168)\n */\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n if (process.env.DEBUG?.includes('rally')) {\n console.debug('[Rally] Query filters:', JSON.stringify(filters));\n }\n\n const limit = filters?.limit ?? 50;\n\n // Extract ObjectID from project ref for explicit query scoping\n // e.g., \"/project/822404704163\" → \"822404704163\"\n let projectObjectId: string | undefined;\n if (this.project) {\n const match = this.project.match(/\\/project\\/(\\d+)/);\n if (match) projectObjectId = match[1];\n }\n\n const queries = QUERYABLE_TYPES.map(async (artifactType) => {\n const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);\n\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] ${artifactType.type} query:`, queryString);\n }\n\n const query: any = {\n type: artifactType.type,\n fetch: FETCH_FIELDS,\n limit,\n query: queryString,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n if (this.project) {\n query.project = this.project;\n query.projectScopeDown = true;\n }\n\n try {\n const result = await this.queryRally(query);\n return result.Results.map((artifact: any) => this.normalizeIssue(artifact));\n } catch (error: any) {\n if (error.message?.includes('Unauthorized') || error.message?.includes('401')) {\n throw new TrackerAuthError('rally' as TrackerType, 'Invalid API key or insufficient permissions');\n }\n // Log and skip individual type failures so other types still return\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);\n }\n return [];\n }\n });\n\n const results = await Promise.all(queries);\n const allIssues = results.flat();\n\n // Sort by most recently updated first, then apply overall limit\n allIssues.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n\n return allIssues.slice(0, limit);\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Rally FormattedIDs look like: US123, DE456, TA789, F012\n const query: any = {\n type: 'artifact',\n fetch: [\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n '_type',\n ],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n return this.normalizeIssue(result.Results[0]);\n } catch (error: any) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n // Get the Rally object reference\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', '_type'],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.Name = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.Description = update.description;\n }\n if (update.state !== undefined) {\n const artifactType = (artifact._type || '').toLowerCase();\n const kind = artifactType.startsWith('portfolioitem') ? 'feature'\n : artifactType === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(update.state, kind);\n if (kind === 'story') {\n updatePayload.ScheduleState = rallyState;\n } else {\n updatePayload.State = rallyState;\n }\n }\n if (update.priority !== undefined) {\n updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || 'Normal';\n }\n if (update.dueDate !== undefined) {\n updatePayload.DueDate = update.dueDate;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n if (!this.project && !newIssue.team) {\n throw new Error('Project is required to create an issue. Set it in config or provide team field.');\n }\n\n const project = newIssue.team || this.project;\n\n // Default to HierarchicalRequirement (User Story) for new issues\n const createPayload: Record<string, unknown> = {\n Name: newIssue.title,\n Description: newIssue.description || '',\n Project: project,\n };\n\n if (newIssue.priority !== undefined) {\n createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || 'Normal';\n }\n if (newIssue.dueDate) {\n createPayload.DueDate = newIssue.dueDate;\n }\n if (this.workspace) {\n createPayload.Workspace = this.workspace;\n }\n\n const result = await this.createRally('hierarchicalrequirement', createPayload);\n\n // Fetch the created issue to return normalized format\n return this.getIssue(result.Object.FormattedID);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.getIssue(issueId);\n\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n return [];\n }\n\n const artifact = result.Results[0];\n if (!artifact.Discussion) {\n return [];\n }\n\n // Query ConversationPosts for this Discussion\n const postsQuery: any = {\n type: 'conversationpost',\n fetch: ['ObjectID', 'Text', 'User', 'CreationDate', 'PostNumber'],\n query: `(Discussion = \"${artifact.Discussion._ref}\")`,\n order: 'PostNumber',\n };\n\n const postsResult = await this.queryRally(postsQuery);\n\n return (postsResult.Results || []).map((post: any) => ({\n id: post.ObjectID,\n issueId,\n body: post.Text || '',\n author: post.User?._refObjectName || 'Unknown',\n createdAt: post.CreationDate,\n updatedAt: post.CreationDate, // Rally doesn't track comment updates separately\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(issueId, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n\n // If no Discussion exists, create one\n let discussionRef = artifact.Discussion?._ref;\n if (!discussionRef) {\n const discussionResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: discussionResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n // Add a post to existing Discussion\n const postResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: postResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n await this.updateIssue(id, { state });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // Add a comment with the PR link\n await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);\n }\n\n // Private helper methods\n\n /**\n * Build a Rally WSAPI query string for a specific artifact type.\n *\n * Each artifact type has its own state field:\n * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)\n * - Defect: State (Submitted, Open, Fixed, Closed)\n * - Task: State (Defined, In-Progress, Completed)\n *\n * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.\n * (PAN-166, PAN-168)\n */\n private buildQueryStringForType(\n filters: IssueFilters | undefined,\n artifactType: ArtifactTypeQuery,\n projectObjectId?: string,\n ): string {\n const conditions: string[] = [];\n\n // Explicit project scoping — more reliable than WSAPI project param alone\n if (projectObjectId) {\n conditions.push(`(Project.ObjectID = \"${projectObjectId}\")`);\n }\n\n if (filters?.state && !filters.includeClosed) {\n const kind = artifactType.type.startsWith('portfolioitem') ? 'feature'\n : artifactType.type === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(filters.state, kind);\n conditions.push(`(${artifactType.stateField} = \"${rallyState}\")`);\n }\n\n if (!filters?.includeClosed) {\n // Exclude closed states for this specific artifact type\n const closedConditions = artifactType.closedStates.map(\n (state) => `(${artifactType.stateField} != \"${state}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const closedExpr = closedConditions.reduce(\n (acc, cond) => (acc ? `(${acc} AND ${cond})` : cond),\n '',\n );\n conditions.push(closedExpr);\n }\n\n if (filters?.assignee) {\n conditions.push(`(Owner.Name contains \"${filters.assignee}\")`);\n }\n\n if (filters?.labels && filters.labels.length > 0) {\n const labelConditions = filters.labels.map(\n (label) => `(Tags.Name contains \"${label}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n conditions.push(labelExpr);\n }\n\n if (filters?.query) {\n conditions.push(`((Name contains \"${filters.query}\") OR (Description contains \"${filters.query}\"))`);\n }\n\n // Rally WSAPI only supports binary (expr AND expr) — reduce into nested pairs\n return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n }\n\n private normalizeIssue(rallyArtifact: any): Issue {\n // Determine state from ScheduleState (User Stories, Tasks) or State (Defects, Features)\n // For PortfolioItem/Feature, State is a Rally ref object with Name/_refObjectName, not a string\n const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || 'Defined';\n const stateValue = typeof rawStateValue === 'object' && rawStateValue !== null\n ? (rawStateValue.Name || rawStateValue._refObjectName || 'Defined')\n : rawStateValue;\n const state = this.mapState(stateValue);\n\n // Extract tags — ensure all entries are strings\n const labels: string[] = [];\n if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {\n for (const tag of rallyArtifact.Tags._tagsNameArray) {\n if (typeof tag === 'string') {\n labels.push(tag);\n } else if (tag?.Name) {\n labels.push(tag.Name);\n }\n }\n }\n\n // Map priority\n const priority = rallyArtifact.Priority\n ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2\n : undefined;\n\n // Use ObjectID if available, fall back to FormattedID\n const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;\n const artifactType = rallyArtifact._type || 'artifact';\n\n // Build URL - Rally's web UI detail path\n const baseUrl = this.restApi.server.replace('/slm/webservice/', '');\n const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;\n\n // Resolve parent reference.\n // For User Stories, PortfolioItem links to the parent Feature (F-prefixed),\n // while Parent links to a parent Story in the hierarchy. Prefer PortfolioItem\n // so that stories are correctly grouped under their Feature. (PAN-202)\n let parentRef: string | undefined;\n if (rallyArtifact.PortfolioItem) {\n if (rallyArtifact.PortfolioItem.FormattedID) {\n parentRef = rallyArtifact.PortfolioItem.FormattedID;\n } else if (rallyArtifact.PortfolioItem._refObjectName) {\n parentRef = rallyArtifact.PortfolioItem._refObjectName;\n }\n } else if (rallyArtifact.Parent) {\n if (rallyArtifact.Parent.FormattedID) {\n parentRef = rallyArtifact.Parent.FormattedID;\n } else if (rallyArtifact.Parent._refObjectName) {\n parentRef = rallyArtifact.Parent._refObjectName;\n }\n }\n\n return {\n id: String(objectId),\n ref: rallyArtifact.FormattedID,\n title: rallyArtifact.Name || '',\n description: rallyArtifact.Description || '',\n state,\n labels,\n assignee: rallyArtifact.Owner?._refObjectName,\n url,\n tracker: 'rally' as TrackerType,\n priority,\n dueDate: rallyArtifact.DueDate,\n createdAt: rallyArtifact.CreationDate,\n updatedAt: rallyArtifact.LastUpdateDate,\n parentRef,\n artifactType,\n rawState: stateValue,\n };\n }\n\n private mapState(rallyState: string): IssueState {\n return STATE_MAP[rallyState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState, kind: 'story' | 'defect' | 'feature' = 'story'): string {\n if (kind === 'feature') {\n // Features / PortfolioItems use State: Discovering, Developing, Done\n switch (state) {\n case 'open': return 'Discovering';\n case 'in_progress':\n case 'in_review': return 'Developing';\n case 'closed': return 'Done';\n default: return 'Discovering';\n }\n }\n if (kind === 'defect') {\n // Defects use State: Submitted, Open, Fixed, Closed\n switch (state) {\n case 'open': return 'Submitted';\n case 'in_progress':\n case 'in_review': return 'Open';\n case 'closed': return 'Closed';\n default: return 'Submitted';\n }\n }\n // User Stories / Tasks use ScheduleState: Defined, In-Progress, Completed\n switch (state) {\n case 'open': return 'Defined';\n case 'in_progress':\n case 'in_review': return 'In-Progress';\n case 'closed': return 'Completed';\n default: return 'Defined';\n }\n }\n\n // Rally API wrapper methods\n private async queryRally(queryConfig: any): Promise<any> {\n const result = await this.restApi.query(queryConfig);\n // Extract Results from WSAPI response format\n return {\n Results: result.QueryResult.Results,\n TotalResultCount: result.QueryResult.TotalResultCount,\n };\n }\n\n private async createRally(type: string, data: any): Promise<any> {\n const result = await this.restApi.create({\n type,\n data,\n fetch: ['FormattedID', 'ObjectID', '_ref'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.CreateResult.Object,\n };\n }\n\n private async updateRally(type: string, ref: string, data: any): Promise<any> {\n const result = await this.restApi.update({\n type,\n ref,\n data,\n fetch: ['FormattedID', 'ObjectID'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.OperationResult.Object,\n };\n }\n}\n"],"mappings":";;;;AAgLa,uBAAb,cAAyC,MAAM;EAC7C,YAAY,SAAiB;AAC3B,SAAM,oBAAoB,UAAU;AACpC,QAAK,OAAO;;;AAOH,sBAAb,cAAwC,MAAM;EAC5C,YAAY,IAAY,SAAsB;AAC5C,SAAM,oBAAoB,GAAG,aAAa,QAAQ,GAAG;AACrD,QAAK,OAAO;;;AAOH,oBAAb,cAAsC,MAAM;EAC1C,YAAY,SAAsB,SAAiB;AACjD,SAAM,6BAA6B,QAAQ,IAAI,UAAU;AACzD,QAAK,OAAO;;;;;;;;ACvIH,gBAAb,MAA0B;EACxB;EACA;EACA;EAEA,YAAY,QAAwB;AAClC,QAAK,SAAS,OAAO;AACrB,QAAK,SAAS,OAAO,UAAU;AAC/B,QAAK,gBAAgB,OAAO,gBAAgB,WAAW,EAAE;;;;;EAM3D,MAAM,MAAM,QAAqD;GAC/D,MAAM,SAAS,IAAI,iBAAiB;AAEpC,OAAI,OAAO,MACT,QAAO,IAAI,SAAS,OAAO,MAAM;AAGnC,OAAI,OAAO,SAAS,OAAO,MAAM,SAAS,EACxC,QAAO,IAAI,SAAS,OAAO,MAAM,KAAK,IAAI,CAAC;AAG7C,OAAI,OAAO,UAAU,KAAA,EACnB,QAAO,IAAI,YAAY,OAAO,OAAO,MAAM,CAAC;AAG9C,OAAI,OAAO,UACT,QAAO,IAAI,aAAa,OAAO,UAAU;AAG3C,OAAI,OAAO,SAAS;AAClB,WAAO,IAAI,WAAW,OAAO,QAAQ;AACrC,QAAI,OAAO,iBACT,QAAO,IAAI,oBAAoB,OAAO;;AAI1C,OAAI,OAAO,MACT,QAAO,IAAI,SAAS,OAAO,MAAM;GAGnC,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB,OAAO,KAAK,GAAG,OAAO,UAAU;GAElF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ;IACR,SAAS;KACP,cAAc,KAAK;KACnB,gBAAgB;KAChB,GAAG,KAAK;KACT;IACF,CAAC;AAEF,OAAI,CAAC,SAAS,IAAI;AAChB,QAAI,SAAS,WAAW,IACtB,OAAM,IAAI,MAAM,4DAA4D;AAE9E,UAAM,IAAI,MAAM,2BAA2B,SAAS,OAAO,GAAG,SAAS,aAAa;;GAGtF,MAAM,SAAS,MAAM,SAAS,MAAM;AAEpC,OAAI,OAAO,YAAY,UAAU,OAAO,YAAY,OAAO,SAAS,GAAG;IACrE,MAAM,cAAc,OAAO,YAAY,OAAO,KAAK,KAAK;IACxD,MAAM,cAAc,OAAO,QAAQ,YAAY,OAAO,MAAM,KAAK;AACjE,QAAI,QAAQ,IAAI,OAAO,SAAS,QAAQ,CACtC,SAAQ,MAAM,+BAA+B;KAAE,OAAO,OAAO;KAAO,QAAQ,OAAO,YAAY;KAAQ,CAAC;AAE1G,UAAM,IAAI,MAAM,2BAA2B,cAAc,cAAc;;AAGzE,UAAO;;;;;EAMT,MAAM,OAAO,QAAuD;GAClE,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB,OAAO,KAAK;GAE9D,MAAM,OAAY,GACf,OAAO,OAAO,OAAO,MACvB;GAED,MAAM,SAAS,IAAI,iBAAiB;AACpC,OAAI,OAAO,SAAS,OAAO,MAAM,SAAS,EACxC,QAAO,IAAI,SAAS,OAAO,MAAM,KAAK,IAAI,CAAC;GAG7C,MAAM,WAAW,OAAO,UAAU,GAAG,GAAG,IAAI,GAAG,OAAO,UAAU,KAAK;GAErE,MAAM,WAAW,MAAM,MAAM,UAAU;IACrC,QAAQ;IACR,SAAS;KACP,cAAc,KAAK;KACnB,gBAAgB;KAChB,GAAG,KAAK;KACT;IACD,MAAM,KAAK,UAAU,KAAK;IAC3B,CAAC;AAEF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,4BAA4B,SAAS,OAAO,GAAG,SAAS,aAAa;GAGvF,MAAM,SAAS,MAAM,SAAS,MAAM;AAEpC,OAAI,OAAO,aAAa,UAAU,OAAO,aAAa,OAAO,SAAS,EACpE,OAAM,IAAI,MAAM,4BAA4B,OAAO,aAAa,OAAO,KAAK,KAAK,GAAG;AAGtF,UAAO;;;;;EAMT,MAAM,OAAO,QAAuD;GAElE,MAAM,WAAW,OAAO,IAAI,MAAM,IAAI,CAAC,KAAK;GAC5C,MAAM,MAAM,GAAG,KAAK,OAAO,uBAAuB,OAAO,KAAK,GAAG;GAEjE,MAAM,OAAY,GACf,OAAO,OAAO,OAAO,MACvB;GAED,MAAM,SAAS,IAAI,iBAAiB;AACpC,OAAI,OAAO,SAAS,OAAO,MAAM,SAAS,EACxC,QAAO,IAAI,SAAS,OAAO,MAAM,KAAK,IAAI,CAAC;GAG7C,MAAM,WAAW,OAAO,UAAU,GAAG,GAAG,IAAI,GAAG,OAAO,UAAU,KAAK;GAErE,MAAM,WAAW,MAAM,MAAM,UAAU;IACrC,QAAQ;IACR,SAAS;KACP,cAAc,KAAK;KACnB,gBAAgB;KAChB,GAAG,KAAK;KACT;IACD,MAAM,KAAK,UAAU,KAAK;IAC3B,CAAC;AAEF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,4BAA4B,SAAS,OAAO,GAAG,SAAS,aAAa;GAGvF,MAAM,SAAS,MAAM,SAAS,MAAM;AAEpC,OAAI,OAAO,gBAAgB,UAAU,OAAO,gBAAgB,OAAO,SAAS,EAC1E,OAAM,IAAI,MAAM,4BAA4B,OAAO,gBAAgB,OAAO,KAAK,KAAK,GAAG;AAGzF,UAAO;;;;;;;;iBCpNmC;iBAW6C;AAIrF,aAAwC;EAE5C,OAAO;EACP,QAAQ;EACR,WAAW;EACX,eAAe;EACf,aAAa;EACb,YAAY;EAGZ,aAAa;EACb,QAAQ;EACR,SAAS;EACT,UAAU;EAGV,eAAe;EACf,cAAc;EACd,QAAQ;EACT;AAqBK,mBAAuC;EAC3C;GAAE,MAAM;GAA2B,YAAY;GAAiB,cAAc,CAAC,aAAa,WAAW;GAAE;EACzG;GAAE,MAAM;GAAU,YAAY;GAAS,cAAc,CAAC,SAAS;GAAE;EACjE;GAAE,MAAM;GAAQ,YAAY;GAAS,cAAc,CAAC,YAAY;GAAE;EAClE;GAAE,MAAM;GAAyB,YAAY;GAAS,cAAc,CAAC,OAAO;GAAE;EAC/E;AAEK,gBAAe;EACnB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AAGK,gBAAuC;EAC3C,uBAAuB;EACvB,MAAM;EACN,QAAQ;EACR,KAAK;EACN;AAGK,wBAA+C;EACnD,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACJ;AASY,gBAAb,MAAkD;EAChD,OAA6B;EAC7B;EACA;EACA;EAEA,YAAY,QAAqB;AAC/B,OAAI,CAAC,OAAO,OACV,OAAM,IAAI,iBAAiB,SAAwB,sBAAsB;AAG3E,QAAK,UAAU,IAAI,aAAa;IAC9B,QAAQ,OAAO;IACf,QAAQ,OAAO,UAAU;IACzB,gBAAgB,EACd,SAAS;KACP,0BAA0B;KAC1B,0BAA0B;KAC1B,4BAA4B;KAC5B,6BAA6B;KAC9B,EACF;IACF,CAAC;AAEF,QAAK,YAAY,OAAO;AACxB,QAAK,UAAU,OAAO;;;;;;;;;EAUxB,MAAM,WAAW,SAA0C;AACzD,OAAI,QAAQ,IAAI,OAAO,SAAS,QAAQ,CACtC,SAAQ,MAAM,0BAA0B,KAAK,UAAU,QAAQ,CAAC;GAGlE,MAAM,QAAQ,SAAS,SAAS;GAIhC,IAAI;AACJ,OAAI,KAAK,SAAS;IAChB,MAAM,QAAQ,KAAK,QAAQ,MAAM,mBAAmB;AACpD,QAAI,MAAO,mBAAkB,MAAM;;GAGrC,MAAM,UAAU,gBAAgB,IAAI,OAAO,iBAAiB;IAC1D,MAAM,cAAc,KAAK,wBAAwB,SAAS,cAAc,gBAAgB;AAExF,QAAI,QAAQ,IAAI,OAAO,SAAS,QAAQ,CACtC,SAAQ,MAAM,WAAW,aAAa,KAAK,UAAU,YAAY;IAGnE,MAAM,QAAa;KACjB,MAAM,aAAa;KACnB,OAAO;KACP;KACA,OAAO;KACR;AAED,QAAI,KAAK,UACP,OAAM,YAAY,KAAK;AAEzB,QAAI,KAAK,SAAS;AAChB,WAAM,UAAU,KAAK;AACrB,WAAM,mBAAmB;;AAG3B,QAAI;AAEF,aADe,MAAM,KAAK,WAAW,MAAM,EAC7B,QAAQ,KAAK,aAAkB,KAAK,eAAe,SAAS,CAAC;aACpE,OAAY;AACnB,SAAI,MAAM,SAAS,SAAS,eAAe,IAAI,MAAM,SAAS,SAAS,MAAM,CAC3E,OAAM,IAAI,iBAAiB,SAAwB,8CAA8C;AAGnG,SAAI,QAAQ,IAAI,OAAO,SAAS,QAAQ,CACtC,SAAQ,MAAM,2BAA2B,aAAa,KAAK,IAAI,MAAM,QAAQ;AAE/E,YAAO,EAAE;;KAEX;GAGF,MAAM,aADU,MAAM,QAAQ,IAAI,QAAQ,EAChB,MAAM;AAGhC,aAAU,MACP,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAC5E;AAED,UAAO,UAAU,MAAM,GAAG,MAAM;;EAGlC,MAAM,SAAS,IAA4B;AACzC,OAAI;IAEF,MAAM,QAAa;KACjB,MAAM;KACN,OAAO;MACL;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACD;KACD,OAAO,mBAAmB,GAAG;KAC9B;AAED,QAAI,KAAK,UACP,OAAM,YAAY,KAAK;IAGzB,MAAM,SAAS,MAAM,KAAK,WAAW,MAAM;AAE3C,QAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAC/C,OAAM,IAAI,mBAAmB,IAAI,QAAuB;AAG1D,WAAO,KAAK,eAAe,OAAO,QAAQ,GAAG;YACtC,OAAY;AACnB,QAAI,iBAAiB,mBAAoB,OAAM;AAC/C,UAAM,IAAI,mBAAmB,IAAI,QAAuB;;;EAI5D,MAAM,YAAY,IAAY,QAAqC;AACnD,SAAM,KAAK,SAAS,GAAG;GAGrC,MAAM,QAAa;IACjB,MAAM;IACN,OAAO;KAAC;KAAY;KAAQ;KAAQ;IACpC,OAAO,mBAAmB,GAAG;IAC9B;AAED,OAAI,KAAK,UACP,OAAM,YAAY,KAAK;GAGzB,MAAM,SAAS,MAAM,KAAK,WAAW,MAAM;AAC3C,OAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAC/C,OAAM,IAAI,mBAAmB,IAAI,QAAuB;GAG1D,MAAM,WAAW,OAAO,QAAQ;GAChC,MAAM,gBAAyC,EAAE;AAEjD,OAAI,OAAO,UAAU,KAAA,EACnB,eAAc,OAAO,OAAO;AAE9B,OAAI,OAAO,gBAAgB,KAAA,EACzB,eAAc,cAAc,OAAO;AAErC,OAAI,OAAO,UAAU,KAAA,GAAW;IAC9B,MAAM,gBAAgB,SAAS,SAAS,IAAI,aAAa;IACzD,MAAM,OAAO,aAAa,WAAW,gBAAgB,GAAG,YACpD,iBAAiB,WAAW,WAAW;IAC3C,MAAM,aAAa,KAAK,gBAAgB,OAAO,OAAO,KAAK;AAC3D,QAAI,SAAS,QACX,eAAc,gBAAgB;QAE9B,eAAc,QAAQ;;AAG1B,OAAI,OAAO,aAAa,KAAA,EACtB,eAAc,WAAW,qBAAqB,OAAO,aAAa;AAEpE,OAAI,OAAO,YAAY,KAAA,EACrB,eAAc,UAAU,OAAO;AAGjC,OAAI,OAAO,KAAK,cAAc,CAAC,SAAS,EACtC,OAAM,KAAK,YAAY,SAAS,MAAM,aAAa,EAAE,SAAS,MAAM,cAAc;AAGpF,UAAO,KAAK,SAAS,GAAG;;EAG1B,MAAM,YAAY,UAAoC;AACpD,OAAI,CAAC,KAAK,WAAW,CAAC,SAAS,KAC7B,OAAM,IAAI,MAAM,kFAAkF;GAGpG,MAAM,UAAU,SAAS,QAAQ,KAAK;GAGtC,MAAM,gBAAyC;IAC7C,MAAM,SAAS;IACf,aAAa,SAAS,eAAe;IACrC,SAAS;IACV;AAED,OAAI,SAAS,aAAa,KAAA,EACxB,eAAc,WAAW,qBAAqB,SAAS,aAAa;AAEtE,OAAI,SAAS,QACX,eAAc,UAAU,SAAS;AAEnC,OAAI,KAAK,UACP,eAAc,YAAY,KAAK;GAGjC,MAAM,SAAS,MAAM,KAAK,YAAY,2BAA2B,cAAc;AAG/E,UAAO,KAAK,SAAS,OAAO,OAAO,YAAY;;EAGjD,MAAM,YAAY,SAAqC;AACvC,SAAM,KAAK,SAAS,QAAQ;GAG1C,MAAM,QAAa;IACjB,MAAM;IACN,OAAO;KAAC;KAAY;KAAQ;KAAa;IACzC,OAAO,mBAAmB,QAAQ;IACnC;AAED,OAAI,KAAK,UACP,OAAM,YAAY,KAAK;GAGzB,MAAM,SAAS,MAAM,KAAK,WAAW,MAAM;AAC3C,OAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAC/C,QAAO,EAAE;GAGX,MAAM,WAAW,OAAO,QAAQ;AAChC,OAAI,CAAC,SAAS,WACZ,QAAO,EAAE;GAIX,MAAM,aAAkB;IACtB,MAAM;IACN,OAAO;KAAC;KAAY;KAAQ;KAAQ;KAAgB;KAAa;IACjE,OAAO,kBAAkB,SAAS,WAAW,KAAK;IAClD,OAAO;IACR;AAID,YAFoB,MAAM,KAAK,WAAW,WAAW,EAEjC,WAAW,EAAE,EAAE,KAAK,UAAe;IACrD,IAAI,KAAK;IACT;IACA,MAAM,KAAK,QAAQ;IACnB,QAAQ,KAAK,MAAM,kBAAkB;IACrC,WAAW,KAAK;IAChB,WAAW,KAAK;IACjB,EAAE;;EAGL,MAAM,WAAW,SAAiB,MAAgC;GAEhE,MAAM,QAAa;IACjB,MAAM;IACN,OAAO;KAAC;KAAY;KAAQ;KAAa;IACzC,OAAO,mBAAmB,QAAQ;IACnC;AAED,OAAI,KAAK,UACP,OAAM,YAAY,KAAK;GAGzB,MAAM,SAAS,MAAM,KAAK,WAAW,MAAM;AAC3C,OAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAC/C,OAAM,IAAI,mBAAmB,SAAS,QAAuB;GAG/D,MAAM,WAAW,OAAO,QAAQ;AAIhC,OAAI,CADgB,SAAS,YAAY,KAOvC,QAAO;IACL,KANuB,MAAM,KAAK,YAAY,oBAAoB;KAClE,UAAU,SAAS;KACnB,MAAM;KACP,CAAC,EAGqB,OAAO;IAC5B;IACA;IACA,QAAQ;IACR,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;AASH,UAAO;IACL,KANiB,MAAM,KAAK,YAAY,oBAAoB;KAC5D,UAAU,SAAS;KACnB,MAAM;KACP,CAAC,EAGe,OAAO;IACtB;IACA;IACA,QAAQ;IACR,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;IACpC;;EAGH,MAAM,gBAAgB,IAAY,OAAkC;AAClE,SAAM,KAAK,YAAY,IAAI,EAAE,OAAO,CAAC;;EAGvC,MAAM,OAAO,SAAiB,OAA8B;AAE1D,SAAM,KAAK,WAAW,SAAS,wBAAwB,QAAQ;;;;;;;;;;;;;EAgBjE,wBACE,SACA,cACA,iBACQ;GACR,MAAM,aAAuB,EAAE;AAG/B,OAAI,gBACF,YAAW,KAAK,wBAAwB,gBAAgB,IAAI;AAG9D,OAAI,SAAS,SAAS,CAAC,QAAQ,eAAe;IAC5C,MAAM,OAAO,aAAa,KAAK,WAAW,gBAAgB,GAAG,YACzD,aAAa,SAAS,WAAW,WAAW;IAChD,MAAM,aAAa,KAAK,gBAAgB,QAAQ,OAAO,KAAK;AAC5D,eAAW,KAAK,IAAI,aAAa,WAAW,MAAM,WAAW,IAAI;;AAGnE,OAAI,CAAC,SAAS,eAAe;IAM3B,MAAM,aAJmB,aAAa,aAAa,KAChD,UAAU,IAAI,aAAa,WAAW,OAAO,MAAM,IACrD,CAEmC,QACjC,KAAK,SAAU,MAAM,IAAI,IAAI,OAAO,KAAK,KAAK,MAC/C,GACD;AACD,eAAW,KAAK,WAAW;;AAG7B,OAAI,SAAS,SACX,YAAW,KAAK,yBAAyB,QAAQ,SAAS,IAAI;AAGhE,OAAI,SAAS,UAAU,QAAQ,OAAO,SAAS,GAAG;IAKhD,MAAM,YAJkB,QAAQ,OAAO,KACpC,UAAU,wBAAwB,MAAM,IAC1C,CAEiC,QAAQ,KAAK,SAAS,MAAM,IAAI,IAAI,OAAO,KAAK,KAAK,MAAM,GAAG;AAChG,eAAW,KAAK,UAAU;;AAG5B,OAAI,SAAS,MACX,YAAW,KAAK,oBAAoB,QAAQ,MAAM,+BAA+B,QAAQ,MAAM,KAAK;AAItG,UAAO,WAAW,QAAQ,KAAK,SAAS,MAAM,IAAI,IAAI,OAAO,KAAK,KAAK,MAAM,GAAG;;EAGlF,eAAuB,eAA2B;GAGhD,MAAM,gBAAgB,cAAc,iBAAiB,cAAc,SAAS;GAC5E,MAAM,aAAa,OAAO,kBAAkB,YAAY,kBAAkB,OACrE,cAAc,QAAQ,cAAc,kBAAkB,YACvD;GACJ,MAAM,QAAQ,KAAK,SAAS,WAAW;GAGvC,MAAM,SAAmB,EAAE;AAC3B,OAAI,cAAc,QAAQ,cAAc,KAAK;SACtC,MAAM,OAAO,cAAc,KAAK,eACnC,KAAI,OAAO,QAAQ,SACjB,QAAO,KAAK,IAAI;aACP,KAAK,KACd,QAAO,KAAK,IAAI,KAAK;;GAM3B,MAAM,WAAW,cAAc,WAC3B,aAAa,cAAc,aAAa,IACxC,KAAA;GAGJ,MAAM,WAAW,cAAc,YAAY,cAAc;GACzD,MAAM,eAAe,cAAc,SAAS;GAI5C,MAAM,MAAM,GADI,KAAK,QAAQ,OAAO,QAAQ,oBAAoB,GAAG,CAC5C,YAAY,aAAa,aAAa,CAAC,GAAG;GAMjE,IAAI;AACJ,OAAI,cAAc;QACZ,cAAc,cAAc,YAC9B,aAAY,cAAc,cAAc;aAC/B,cAAc,cAAc,eACrC,aAAY,cAAc,cAAc;cAEjC,cAAc;QACnB,cAAc,OAAO,YACvB,aAAY,cAAc,OAAO;aACxB,cAAc,OAAO,eAC9B,aAAY,cAAc,OAAO;;AAIrC,UAAO;IACL,IAAI,OAAO,SAAS;IACpB,KAAK,cAAc;IACnB,OAAO,cAAc,QAAQ;IAC7B,aAAa,cAAc,eAAe;IAC1C;IACA;IACA,UAAU,cAAc,OAAO;IAC/B;IACA,SAAS;IACT;IACA,SAAS,cAAc;IACvB,WAAW,cAAc;IACzB,WAAW,cAAc;IACzB;IACA;IACA,UAAU;IACX;;EAGH,SAAiB,YAAgC;AAC/C,UAAO,UAAU,eAAe;;EAGlC,gBAAwB,OAAmB,OAAuC,SAAiB;AACjG,OAAI,SAAS,UAEX,SAAQ,OAAR;IACE,KAAK,OAAQ,QAAO;IACpB,KAAK;IACL,KAAK,YAAa,QAAO;IACzB,KAAK,SAAU,QAAO;IACtB,QAAS,QAAO;;AAGpB,OAAI,SAAS,SAEX,SAAQ,OAAR;IACE,KAAK,OAAQ,QAAO;IACpB,KAAK;IACL,KAAK,YAAa,QAAO;IACzB,KAAK,SAAU,QAAO;IACtB,QAAS,QAAO;;AAIpB,WAAQ,OAAR;IACE,KAAK,OAAQ,QAAO;IACpB,KAAK;IACL,KAAK,YAAa,QAAO;IACzB,KAAK,SAAU,QAAO;IACtB,QAAS,QAAO;;;EAKpB,MAAc,WAAW,aAAgC;GACvD,MAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,YAAY;AAEpD,UAAO;IACL,SAAS,OAAO,YAAY;IAC5B,kBAAkB,OAAO,YAAY;IACtC;;EAGH,MAAc,YAAY,MAAc,MAAyB;AAO/D,UAAO,EACL,SAPa,MAAM,KAAK,QAAQ,OAAO;IACvC;IACA;IACA,OAAO;KAAC;KAAe;KAAY;KAAO;IAC3C,CAAC,EAGe,aAAa,QAC7B;;EAGH,MAAc,YAAY,MAAc,KAAa,MAAyB;AAQ5E,UAAO,EACL,SARa,MAAM,KAAK,QAAQ,OAAO;IACvC;IACA;IACA;IACA,OAAO,CAAC,eAAe,WAAW;IACnC,CAAC,EAGe,gBAAgB,QAChC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __require } from "./chunk-ruWRV7i3.js";
|
|
2
|
-
import { o as createFlyProvider, s as init_fly_provider, t as init_remote_agents } from "./remote-agents-
|
|
2
|
+
import { o as createFlyProvider, s as init_fly_provider, t as init_remote_agents } from "./remote-agents-CZXrUF4f.js";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
@@ -93,4 +93,4 @@ function createFlyProviderFromConfig(remoteConfig) {
|
|
|
93
93
|
//#endregion
|
|
94
94
|
export { findRemoteWorkspaceMetadata as a, deleteWorkspaceMetadata as i, isRemoteAvailable as n, loadWorkspaceMetadata as o, WORKSPACES_DIR as r, saveWorkspaceMetadata as s, createFlyProviderFromConfig as t };
|
|
95
95
|
|
|
96
|
-
//# sourceMappingURL=remote-
|
|
96
|
+
//# sourceMappingURL=remote-CYiOJg0q.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-
|
|
1
|
+
{"version":3,"file":"remote-CYiOJg0q.js","names":["parse"],"sources":["../src/lib/remote/workspace-metadata.ts","../src/lib/remote/index.ts"],"sourcesContent":["/**\n * Workspace Metadata Management\n *\n * Shared module for loading, saving, and listing workspace metadata.\n * Used by both workspace.ts and work/issue.ts for remote workspace support.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { parse, stringify } from 'yaml';\nimport type { RemoteWorkspaceMetadata } from './interface.js';\n\n// Path for workspace metadata\nexport const WORKSPACES_DIR = join(homedir(), '.panopticon', 'workspaces');\n\n/**\n * Save workspace metadata to ~/.panopticon/workspaces/{issueId}.yaml\n */\nexport function saveWorkspaceMetadata(metadata: RemoteWorkspaceMetadata): void {\n if (!existsSync(WORKSPACES_DIR)) {\n mkdirSync(WORKSPACES_DIR, { recursive: true });\n }\n\n const filename = join(WORKSPACES_DIR, `${metadata.id}.yaml`);\n writeFileSync(filename, stringify(metadata), 'utf-8');\n}\n\n/**\n * Load workspace metadata from ~/.panopticon/workspaces/{issueId}.yaml\n */\nexport function loadWorkspaceMetadata(issueId: string): RemoteWorkspaceMetadata | null {\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const filename = join(WORKSPACES_DIR, `${normalizedId}.yaml`);\n\n if (!existsSync(filename)) {\n return null;\n }\n\n try {\n const content = readFileSync(filename, 'utf-8');\n return parse(content) as RemoteWorkspaceMetadata;\n } catch {\n return null;\n }\n}\n\n/**\n * List all workspace metadata files\n */\nexport function listWorkspaceMetadata(): RemoteWorkspaceMetadata[] {\n if (!existsSync(WORKSPACES_DIR)) {\n return [];\n }\n\n const files = readdirSync(WORKSPACES_DIR).filter(f => f.endsWith('.yaml'));\n const workspaces: RemoteWorkspaceMetadata[] = [];\n\n for (const file of files) {\n try {\n const content = readFileSync(join(WORKSPACES_DIR, file), 'utf-8');\n workspaces.push(parse(content) as RemoteWorkspaceMetadata);\n } catch {\n // Skip invalid files\n }\n }\n\n return workspaces;\n}\n\n/**\n * Check if a workspace exists (local or remote)\n * Returns metadata if remote workspace exists, null otherwise\n */\nexport function findRemoteWorkspaceMetadata(issueId: string): RemoteWorkspaceMetadata | null {\n return loadWorkspaceMetadata(issueId);\n}\n\n/**\n * Delete workspace metadata\n */\nexport function deleteWorkspaceMetadata(issueId: string): boolean {\n const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n const filename = join(WORKSPACES_DIR, `${normalizedId}.yaml`);\n\n if (!existsSync(filename)) {\n return false;\n }\n\n try {\n const { unlinkSync } = require('fs');\n unlinkSync(filename);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Remote Workspace Module\n *\n * Provides support for running workspaces on remote Fly.io Machines\n * to offload Claude agents from the local machine.\n */\n\nexport type {\n RemoteProvider,\n VmInfo,\n VmStatus,\n ExecResult,\n RemoteProviderConfig,\n RemoteWorkspaceMetadata,\n} from './interface.js';\n\nexport { FlyProvider, createFlyProvider } from './fly-provider.js';\nexport type { FlyProviderConfig } from './fly-provider.js';\n\n// Remote agent management\nexport {\n spawnRemoteAgent,\n getRemoteAgentOutput,\n sendToRemoteAgent,\n isRemoteAgentRunning,\n killRemoteAgent,\n listRemoteAgents,\n pollRemoteAgentStatus,\n loadRemoteAgentState,\n} from './remote-agents.js';\nexport type { RemoteAgentState, SpawnRemoteAgentOptions } from './remote-agents.js';\n\n// Workspace metadata management\nexport {\n saveWorkspaceMetadata,\n loadWorkspaceMetadata,\n listWorkspaceMetadata,\n deleteWorkspaceMetadata,\n findRemoteWorkspaceMetadata,\n WORKSPACES_DIR,\n} from './workspace-metadata.js';\n\nimport { FlyProvider, createFlyProvider } from './fly-provider.js';\nimport type { RemoteProvider, RemoteProviderConfig } from './interface.js';\n\nexport type ProviderType = 'fly';\n\n/**\n * Get a remote provider by type\n */\nexport function getRemoteProvider(\n type: ProviderType,\n config?: RemoteProviderConfig\n): RemoteProvider {\n switch (type) {\n case 'fly':\n return createFlyProvider();\n default:\n throw new Error(`Unknown remote provider type: ${type}`);\n }\n}\n\n/**\n * Check if remote providers are available\n */\nexport async function isRemoteAvailable(): Promise<{ available: boolean; reason?: string }> {\n const fly = createFlyProvider();\n\n try {\n const isAuth = await fly.isAuthenticated();\n if (!isAuth) {\n return {\n available: false,\n reason: 'Not authenticated with Fly.io. Set FLY_API_TOKEN or run: fly auth login',\n };\n }\n return { available: true };\n } catch (error: any) {\n return {\n available: false,\n reason: `Fly.io not available: ${error.message}`,\n };\n }\n}\n\n/**\n * Create a FlyProvider from config settings\n */\nexport function createFlyProviderFromConfig(remoteConfig?: {\n fly?: {\n app?: string;\n org?: string;\n region?: string;\n vm_size?: string;\n vm_memory?: number;\n image?: string;\n api_token_env?: string;\n };\n}): FlyProvider {\n const fly = remoteConfig?.fly;\n const tokenEnv = fly?.api_token_env ?? 'FLY_API_TOKEN';\n return createFlyProvider({\n app: fly?.app,\n org: fly?.org,\n region: fly?.region,\n vmSize: fly?.vm_size,\n vmMemory: fly?.vm_memory,\n image: fly?.image,\n apiToken: process.env[tokenEnv],\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,MAAa,iBAAiB,KAAK,SAAS,EAAE,eAAe,aAAa;;;;AAK1E,SAAgB,sBAAsB,UAAyC;AAC7E,KAAI,CAAC,WAAW,eAAe,CAC7B,WAAU,gBAAgB,EAAE,WAAW,MAAM,CAAC;AAIhD,eADiB,KAAK,gBAAgB,GAAG,SAAS,GAAG,OAAO,EACpC,UAAU,SAAS,EAAE,QAAQ;;;;;AAMvD,SAAgB,sBAAsB,SAAiD;CAErF,MAAM,WAAW,KAAK,gBAAgB,GADjB,QAAQ,aAAa,CAAC,QAAQ,eAAe,IAAI,CAChB,OAAO;AAE7D,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAGT,KAAI;AAEF,SAAOA,QADS,aAAa,UAAU,QAAQ,CAC1B;SACf;AACN,SAAO;;;;;;;AA+BX,SAAgB,4BAA4B,SAAiD;AAC3F,QAAO,sBAAsB,QAAQ;;;;;AAMvC,SAAgB,wBAAwB,SAA0B;CAEhE,MAAM,WAAW,KAAK,gBAAgB,GADjB,QAAQ,aAAa,CAAC,QAAQ,eAAe,IAAI,CAChB,OAAO;AAE7D,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAGT,KAAI;EACF,MAAM,EAAE,eAAA,UAAuB,KAAK;AACpC,aAAW,SAAS;AACpB,SAAO;SACD;AACN,SAAO;;;;;mBC9EwD;oBAavC;;;;AAoC5B,eAAsB,oBAAsE;CAC1F,MAAM,MAAM,mBAAmB;AAE/B,KAAI;AAEF,MAAI,CADW,MAAM,IAAI,iBAAiB,CAExC,QAAO;GACL,WAAW;GACX,QAAQ;GACT;AAEH,SAAO,EAAE,WAAW,MAAM;UACnB,OAAY;AACnB,SAAO;GACL,WAAW;GACX,QAAQ,yBAAyB,MAAM;GACxC;;;;;;AAOL,SAAgB,4BAA4B,cAU5B;CACd,MAAM,MAAM,cAAc;CAC1B,MAAM,WAAW,KAAK,iBAAiB;AACvC,QAAO,kBAAkB;EACvB,KAAK,KAAK;EACV,KAAK,KAAK;EACV,QAAQ,KAAK;EACb,QAAQ,KAAK;EACb,UAAU,KAAK;EACf,OAAO,KAAK;EACZ,UAAU,QAAQ,IAAI;EACvB,CAAC"}
|
|
@@ -557,4 +557,4 @@ var init_remote_agents = __esmMin((() => {
|
|
|
557
557
|
//#endregion
|
|
558
558
|
export { spawnRemoteAgent as a, sendToRemoteAgent as i, isRemoteAgentRunning as n, createFlyProvider as o, loadRemoteAgentState as r, init_fly_provider as s, init_remote_agents as t };
|
|
559
559
|
|
|
560
|
-
//# sourceMappingURL=remote-agents-
|
|
560
|
+
//# sourceMappingURL=remote-agents-CZXrUF4f.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-agents-C5Bd2fgt.js","names":["parse"],"sources":["../src/lib/remote/fly-api.ts","../src/lib/remote/fly-provider.ts","../src/lib/remote/remote-agents.ts"],"sourcesContent":["/**\n * Fly Machines REST API Client\n *\n * Wraps the Fly Machines API (flaps) for machine lifecycle management.\n * Base URL: https://api.machines.dev/v1\n * Auth: FLY_API_TOKEN environment variable\n */\n\nexport interface FlyMachineConfig {\n image: string;\n env?: Record<string, string>;\n size?: string; // e.g. \"shared-cpu-2x\"\n memory?: number; // MB\n region?: string; // e.g. \"iad\"\n auto_destroy?: boolean;\n restart?: { policy: 'no' | 'always' | 'on-failure' };\n metadata?: Record<string, string>;\n}\n\nexport interface FlyMachine {\n id: string;\n name: string;\n state: string; // 'started', 'stopped', 'created', 'destroying', etc.\n region: string;\n image_ref?: { registry: string; repository: string; tag: string };\n instance_id?: string;\n private_ip?: string;\n created_at?: string;\n config?: {\n image: string;\n env?: Record<string, string>;\n guest?: { cpu_kind: string; cpus: number; memory_mb: number };\n };\n}\n\nexport interface FlyExecResult {\n stdout: string;\n stderr: string;\n exit_code: number;\n}\n\nexport class FlyApiError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly body: string\n ) {\n super(message);\n this.name = 'FlyApiError';\n }\n}\n\nconst BASE_URL = 'https://api.machines.dev/v1';\n\nexport class FlyApiClient {\n private readonly token: string;\n\n constructor(token: string) {\n this.token = token;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${BASE_URL}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new FlyApiError(\n `Fly API error ${response.status} for ${method} ${path}: ${text}`,\n response.status,\n text\n );\n }\n\n if (!text) return undefined as T;\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n }\n\n /** Create a machine in an app */\n async createMachine(\n appName: string,\n name: string,\n config: FlyMachineConfig\n ): Promise<FlyMachine> {\n return this.request<FlyMachine>('POST', `/apps/${appName}/machines`, {\n name,\n config: {\n image: config.image,\n env: config.env,\n guest: config.size\n ? { cpu_kind: 'shared', cpus: 2, memory_mb: config.memory ?? 1024 }\n : undefined,\n restart: config.restart ?? { policy: 'no' },\n auto_destroy: config.auto_destroy,\n metadata: config.metadata,\n },\n region: config.region,\n });\n }\n\n /** Destroy a machine (force=true for immediate) */\n async destroyMachine(appName: string, machineId: string): Promise<void> {\n await this.request<void>(\n 'DELETE',\n `/apps/${appName}/machines/${machineId}?force=true`\n );\n }\n\n /** Start a stopped machine */\n async startMachine(appName: string, machineId: string): Promise<void> {\n await this.request<void>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/start`\n );\n }\n\n /** Stop a running machine */\n async stopMachine(\n appName: string,\n machineId: string,\n signal?: string,\n timeout?: number\n ): Promise<void> {\n await this.request<void>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/stop`,\n signal || timeout ? { signal, timeout } : undefined\n );\n }\n\n /** Get a machine by ID */\n async getMachine(appName: string, machineId: string): Promise<FlyMachine> {\n return this.request<FlyMachine>(\n 'GET',\n `/apps/${appName}/machines/${machineId}`\n );\n }\n\n /** List all machines in an app */\n async listMachines(appName: string): Promise<FlyMachine[]> {\n const result = await this.request<FlyMachine[] | null>(\n 'GET',\n `/apps/${appName}/machines`\n );\n return result ?? [];\n }\n\n /** Execute a command inside a running machine */\n async execCommand(\n appName: string,\n machineId: string,\n command: string[],\n timeout: number = 30\n ): Promise<FlyExecResult> {\n return this.request<FlyExecResult>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/exec`,\n { command, timeout }\n );\n }\n\n /** Wait for a machine to reach a target state */\n async waitForState(\n appName: string,\n machineId: string,\n state: string,\n timeout: number = 60\n ): Promise<void> {\n await this.request<void>(\n 'GET',\n `/apps/${appName}/machines/${machineId}/wait?state=${state}&timeout=${timeout}`\n );\n }\n\n /** Create a Fly app if it doesn't exist */\n async ensureApp(appName: string, orgSlug: string): Promise<void> {\n try {\n await this.request<unknown>('GET', `/apps/${appName}`);\n } catch (err) {\n if (err instanceof FlyApiError && err.statusCode === 404) {\n await this.request<unknown>('POST', '/apps', {\n app_name: appName,\n org_slug: orgSlug,\n network: 'default',\n });\n } else {\n throw err;\n }\n }\n }\n}\n\n/** Create a FlyApiClient from env or explicit token */\nexport function createFlyApiClient(token?: string): FlyApiClient {\n const tok = token ?? process.env.FLY_API_TOKEN;\n if (!tok) {\n throw new Error(\n 'Fly API token not found. Set FLY_API_TOKEN environment variable or run: fly auth login'\n );\n }\n return new FlyApiClient(tok);\n}\n","/**\n * Fly.io Remote Provider\n *\n * Implements the RemoteProvider interface using Fly Machines API and Fly CLI.\n * VM lifecycle is managed via REST API; exec/SSH via Fly CLI.\n */\n\nimport { exec, spawn } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { parse } from 'yaml';\nimport { FlyApiClient, createFlyApiClient, FlyApiError } from './fly-api.js';\nimport type { RemoteProvider, VmInfo, VmStatus, ExecResult } from './interface.js';\n\nconst execAsync = promisify(exec);\n\nexport interface FlyProviderConfig {\n /** Fly.io app name for workspace machines (default: pan-workspaces) */\n app?: string;\n /** Fly.io org slug */\n org?: string;\n /** Default region (default: iad) */\n region?: string;\n /** Machine size (default: shared-cpu-2x) */\n vmSize?: string;\n /** Memory in MB (default: 1024) */\n vmMemory?: number;\n /** Docker image for workspace machines */\n image?: string;\n /** API token (falls back to FLY_API_TOKEN env var) */\n apiToken?: string;\n}\n\nfunction mapFlyStateToVmStatus(state: string): VmStatus {\n switch (state) {\n case 'started':\n return 'running';\n case 'stopped':\n case 'suspended':\n return 'stopped';\n case 'created':\n case 'replacing':\n return 'creating';\n case 'destroying':\n case 'destroyed':\n return 'deleting';\n default:\n return 'unknown';\n }\n}\n\nexport class FlyProvider implements RemoteProvider {\n readonly name = 'fly';\n\n private readonly config: Required<FlyProviderConfig>;\n private api: FlyApiClient | null = null;\n\n constructor(config: FlyProviderConfig = {}) {\n this.config = {\n app: config.app ?? 'pan-workspaces',\n org: config.org ?? 'personal',\n region: config.region ?? 'iad',\n vmSize: config.vmSize ?? 'shared-cpu-2x',\n vmMemory: config.vmMemory ?? 1024,\n image: config.image ?? 'registry.fly.io/pan-workspace:latest',\n apiToken: config.apiToken ?? process.env.FLY_API_TOKEN ?? '',\n };\n }\n\n private getApi(): FlyApiClient {\n if (!this.api) {\n this.api = createFlyApiClient(this.config.apiToken || undefined);\n }\n return this.api;\n }\n\n async isAuthenticated(): Promise<boolean> {\n // Check API token first\n if (this.config.apiToken || process.env.FLY_API_TOKEN) {\n try {\n await this.getApi().listMachines(this.config.app);\n return true;\n } catch {\n // Fall through to CLI check\n }\n }\n\n // Check fly CLI auth\n try {\n const result = await execAsync('fly auth whoami', { timeout: 10000 });\n return !result.stdout.includes('not logged in');\n } catch {\n return false;\n }\n }\n\n /**\n * Resolve vmName to {appName, machineId} by scanning workspace metadata.\n * Falls back to listing machines in the app.\n */\n async resolveVm(vmName: string): Promise<{ appName: string; machineId: string }> {\n const workspacesDir = join(homedir(), '.panopticon', 'workspaces');\n if (existsSync(workspacesDir)) {\n for (const file of readdirSync(workspacesDir)) {\n if (!file.endsWith('.yaml')) continue;\n try {\n const content = readFileSync(join(workspacesDir, file), 'utf-8');\n const metadata = parse(content) as { vmName?: string; machineId?: string; appName?: string };\n if (metadata.vmName === vmName && metadata.machineId && metadata.appName) {\n return { appName: metadata.appName, machineId: metadata.machineId };\n }\n } catch {\n // Skip invalid files\n }\n }\n }\n\n // Fallback: search by machine name via API\n const machines = await this.getApi().listMachines(this.config.app);\n const machine = machines.find(m => m.name === vmName);\n if (!machine) {\n throw new Error(`No Fly machine found for VM name: ${vmName}`);\n }\n return { appName: this.config.app, machineId: machine.id };\n }\n\n async createVm(name: string): Promise<VmInfo> {\n const api = this.getApi();\n\n // Ensure app exists\n await api.ensureApp(this.config.app, this.config.org);\n\n // Create machine\n const machine = await api.createMachine(this.config.app, name, {\n image: this.config.image,\n size: this.config.vmSize,\n memory: this.config.vmMemory,\n region: this.config.region,\n restart: { policy: 'no' },\n auto_destroy: false,\n });\n\n // Wait for machine to start\n try {\n await api.waitForState(this.config.app, machine.id, 'started', 120);\n } catch {\n // Non-fatal: machine may still be starting\n }\n\n return {\n name,\n status: mapFlyStateToVmStatus(machine.state),\n machineId: machine.id,\n ipAddress: machine.private_ip,\n created: machine.created_at ? new Date(machine.created_at) : undefined,\n };\n }\n\n async deleteVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().destroyMachine(appName, machineId);\n }\n\n async listVms(): Promise<VmInfo[]> {\n const machines = await this.getApi().listMachines(this.config.app);\n return machines.map(m => ({\n name: m.name,\n status: mapFlyStateToVmStatus(m.state),\n machineId: m.id,\n ipAddress: m.private_ip,\n created: m.created_at ? new Date(m.created_at) : undefined,\n }));\n }\n\n async getStatus(name: string): Promise<VmStatus> {\n try {\n const { appName, machineId } = await this.resolveVm(name);\n const machine = await this.getApi().getMachine(appName, machineId);\n return mapFlyStateToVmStatus(machine.state);\n } catch {\n return 'unknown';\n }\n }\n\n async getVmInfo(name: string): Promise<VmInfo | null> {\n try {\n const { appName, machineId } = await this.resolveVm(name);\n const machine = await this.getApi().getMachine(appName, machineId);\n return {\n name,\n status: mapFlyStateToVmStatus(machine.state),\n machineId: machine.id,\n ipAddress: machine.private_ip,\n created: machine.created_at ? new Date(machine.created_at) : undefined,\n memoryTotal: machine.config?.guest?.memory_mb,\n };\n } catch {\n return null;\n }\n }\n\n async startVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().startMachine(appName, machineId);\n await this.getApi().waitForState(appName, machineId, 'started', 60);\n }\n\n async stopVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().stopMachine(appName, machineId);\n }\n\n /** Execute a command on the VM via Fly Machines exec API */\n async ssh(vm: string, command: string): Promise<ExecResult> {\n const { appName, machineId } = await this.resolveVm(vm);\n try {\n const result = await this.getApi().execCommand(\n appName,\n machineId,\n ['/bin/sh', '-c', command],\n 60\n );\n return {\n stdout: result.stdout ?? '',\n stderr: result.stderr ?? '',\n exitCode: result.exit_code ?? 0,\n };\n } catch (err) {\n if (err instanceof FlyApiError) {\n return { stdout: '', stderr: err.message, exitCode: 1 };\n }\n throw err;\n }\n }\n\n /** Stream command output via fly SSH console */\n async *sshStream(vm: string, command: string): AsyncIterable<string> {\n const { appName } = await this.resolveVm(vm);\n const child = spawn('fly', ['ssh', 'console', '-a', appName, '-C', command], {\n env: { ...process.env },\n });\n\n for await (const chunk of child.stdout) {\n yield chunk.toString();\n }\n for await (const chunk of child.stderr) {\n yield chunk.toString();\n }\n\n await new Promise<void>((resolve, reject) => {\n child.on('close', resolve);\n child.on('error', reject);\n });\n }\n\n /** Copy a local file to VM using base64 encoding */\n async copyToVm(vm: string, localPath: string, remotePath: string): Promise<void> {\n const content = readFileSync(localPath);\n const b64 = content.toString('base64');\n const dirPath = remotePath.substring(0, remotePath.lastIndexOf('/'));\n if (dirPath) {\n await this.ssh(vm, `mkdir -p ${JSON.stringify(dirPath)}`);\n }\n await this.ssh(vm, `echo '${b64}' | base64 -d > ${JSON.stringify(remotePath)}`);\n }\n\n /** Copy a file from VM to local path */\n async copyFromVm(vm: string, remotePath: string, localPath: string): Promise<void> {\n const { appName } = await this.resolveVm(vm);\n await execAsync(\n `fly ssh sftp get -a ${JSON.stringify(appName)} ${JSON.stringify(remotePath)} ${JSON.stringify(localPath)}`,\n { timeout: 60000 }\n );\n }\n\n /** Expose a port — not supported by Fly.io provider */\n async exposePort(_vm: string, _port: number): Promise<string> {\n throw new Error(\n 'exposePort is not supported by the Fly.io provider. ' +\n 'Configure services in fly.toml or via the Fly Machines API config.'\n );\n }\n\n /** Create a fly proxy tunnel to the machine */\n async tunnel(\n vm: string,\n remotePort: number,\n localPort: number\n ): Promise<{ close: () => void }> {\n const { appName } = await this.resolveVm(vm);\n const child = spawn('fly', ['proxy', `${localPort}:${remotePort}`, '-a', appName], {\n env: { ...process.env },\n });\n\n return {\n close: () => {\n child.kill();\n },\n };\n }\n\n // ============================================================================\n // Credential Sync & Configuration (ported from ExeProvider)\n // ============================================================================\n\n /** Sync Claude Code credentials from local macOS Keychain to remote VM */\n async syncClaudeCredentials(vmName: string): Promise<boolean> {\n try {\n const { stdout: credentials } = await execAsync(\n 'security find-generic-password -s \"Claude Code-credentials\" -w 2>/dev/null',\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (!credentials?.trim()) return false;\n\n const b64 = Buffer.from(credentials.trim()).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.claude && echo '${b64}' | base64 -d > ~/.claude/.credentials.json`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync GitHub CLI authentication to the remote VM */\n async syncGitHubAuth(vmName: string): Promise<boolean> {\n const ghConfigPath = join(homedir(), '.config', 'gh', 'hosts.yml');\n if (!existsSync(ghConfigPath)) return false;\n\n try {\n const content = readFileSync(ghConfigPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.config/gh && echo '${b64}' | base64 -d > ~/.config/gh/hosts.yml`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync GitLab CLI (glab) authentication to the remote VM */\n async syncGitLabAuth(vmName: string): Promise<boolean> {\n const glabConfigPath = join(homedir(), '.config', 'glab-cli', 'config.yml');\n if (!existsSync(glabConfigPath)) return false;\n\n try {\n const content = readFileSync(glabConfigPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.config/glab-cli && echo '${b64}' | base64 -d > ~/.config/glab-cli/config.yml`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync all credentials needed for remote workspace operation */\n async syncAllCredentials(vmName: string): Promise<{ claude: boolean; github: boolean }> {\n const [claude, github] = await Promise.all([\n this.syncClaudeCredentials(vmName),\n this.syncGitHubAuth(vmName),\n ]);\n return { claude, github };\n }\n\n /** Install beads CLI (bd) on a remote VM */\n async installBeads(vmName: string): Promise<boolean> {\n // Check if already installed\n const check = await this.ssh(vmName, 'which bd 2>/dev/null');\n if (check.exitCode === 0 && check.stdout.trim()) return true;\n\n // Install via npm\n const result = await this.ssh(vmName, 'npm install -g @beads-dev/beads 2>&1');\n if (result.exitCode !== 0) {\n // Try alternative install\n const alt = await this.ssh(\n vmName,\n 'curl -fsSL https://raw.githubusercontent.com/beads-dev/beads/main/install.sh | bash 2>&1'\n );\n return alt.exitCode === 0;\n }\n return true;\n }\n\n /** Initialize beads in a workspace on a remote VM */\n async initBeads(vmName: string, workspacePath: string = '/workspace'): Promise<boolean> {\n const result = await this.ssh(\n vmName,\n `cd ${workspacePath} && (bd init --prefix PAN 2>&1 || bd init 2>&1) && git config beads.role contributor`\n );\n return result.exitCode === 0;\n }\n\n /** Configure Claude Code on a VM for autonomous operation */\n async configureClaudeCode(vmName: string): Promise<void> {\n await this.ssh(vmName, 'mkdir -p ~/.claude');\n\n // Set onboarding complete\n const onboardingScript = `\nimport json, os\npath = os.path.expanduser(\"~/.claude.json\")\ndata = {}\nif os.path.exists(path):\n with open(path) as f:\n data = json.load(f)\ndata[\"hasCompletedOnboarding\"] = True\ndata[\"lastOnboardingVersion\"] = \"2.0.50\"\nwith open(path, \"w\") as f:\n json.dump(data, f, indent=2)\n`;\n const scriptB64 = Buffer.from(onboardingScript).toString('base64');\n await this.ssh(vmName, `echo '${scriptB64}' | base64 -d | python3`);\n\n // Write settings.json with bypass permissions\n const settings = JSON.stringify({\n theme: 'dark',\n permissions: { defaultMode: 'bypassPermissions' },\n });\n const settingsB64 = Buffer.from(settings).toString('base64');\n await this.ssh(vmName, `echo '${settingsB64}' | base64 -d > ~/.claude/settings.json`);\n }\n\n /** Copy essential skills from local ~/.panopticon/skills/ to remote VM */\n async copySkillsToVm(vmName: string): Promise<void> {\n const skillsDir = join(homedir(), '.panopticon', 'skills');\n if (!existsSync(skillsDir)) return;\n\n await this.ssh(vmName, 'mkdir -p ~/.claude/skills');\n\n try {\n const entries = readdirSync(skillsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.md')) continue;\n const localPath = join(skillsDir, entry.name);\n const content = readFileSync(localPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `echo '${b64}' | base64 -d > ~/.claude/skills/${entry.name}`);\n }\n } catch {\n // Non-fatal: skills are optional\n }\n }\n\n /** Sync beads from remote VM to git: exports JSONL, commits, and pushes */\n async syncBeadsToGit(\n vmName: string,\n workspacePath: string = '/workspace',\n commitMessage?: string\n ): Promise<boolean> {\n const msg = commitMessage ?? 'chore: sync beads from remote';\n\n // Export beads to JSONL\n const exportResult = await this.ssh(\n vmName,\n `cd ${workspacePath} && bd export --output .beads/issues.jsonl 2>&1`\n );\n if (exportResult.exitCode !== 0) {\n return false;\n }\n\n // Commit and push\n const gitResult = await this.ssh(\n vmName,\n `cd ${workspacePath} && git diff --cached --quiet || (git commit -m ${JSON.stringify(msg)} && git push origin HEAD) 2>&1`\n );\n return gitResult.exitCode === 0;\n }\n\n /** Query beads on a remote VM via bd search */\n async queryBeads(\n vmName: string,\n searchTerm: string,\n workspacePath: string = '/workspace'\n ): Promise<unknown[]> {\n const result = await this.ssh(\n vmName,\n `cd ${workspacePath} && bd search ${JSON.stringify(searchTerm)} --json 2>/dev/null || echo '[]'`\n );\n try {\n return JSON.parse(result.stdout.trim() || '[]');\n } catch {\n return [];\n }\n }\n\n /** Get the configured app name */\n getAppName(): string {\n return this.config.app;\n }\n}\n\nexport function createFlyProvider(config?: FlyProviderConfig): FlyProvider {\n return new FlyProvider(config);\n}\n","/**\n * Remote Agent Management\n *\n * Spawn and manage Claude agents on remote Fly.io machines.\n * Agents run in tmux sessions for persistence and monitoring.\n */\n\nimport { createFlyProvider } from './fly-provider.js';\nimport type { RemoteProvider, RemoteWorkspaceMetadata } from './interface.js';\nimport { join } from 'path';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\n\nconst AGENTS_DIR = join(homedir(), '.panopticon', 'agents');\n\nexport interface RemoteAgentState {\n id: string;\n issueId: string;\n vmName: string;\n model: string;\n status: 'starting' | 'running' | 'stopped' | 'error';\n startedAt: string;\n lastActivity?: string;\n location: 'remote';\n}\n\n/**\n * Get agent state file path\n */\nfunction getRemoteAgentStateFile(agentId: string): string {\n return join(AGENTS_DIR, agentId, 'remote-state.json');\n}\n\n/**\n * Save remote agent state\n */\nfunction saveRemoteAgentState(state: RemoteAgentState): void {\n const dir = join(AGENTS_DIR, state.id);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(getRemoteAgentStateFile(state.id), JSON.stringify(state, null, 2));\n}\n\n/**\n * Load remote agent state\n */\nexport function loadRemoteAgentState(agentId: string): RemoteAgentState | null {\n const file = getRemoteAgentStateFile(agentId);\n if (!existsSync(file)) return null;\n\n try {\n return JSON.parse(readFileSync(file, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Check if remote agent session exists\n */\nasync function remoteSessionExists(\n provider: RemoteProvider,\n vmName: string,\n sessionName: string\n): Promise<boolean> {\n const result = await provider.ssh(vmName, `tmux has-session -t ${sessionName} 2>/dev/null && echo exists || echo not-found`);\n return result.stdout.trim() === 'exists';\n}\n\nexport interface SpawnRemoteAgentOptions {\n issueId: string;\n workspace: RemoteWorkspaceMetadata;\n model?: string;\n prompt?: string;\n phase?: string;\n}\n\n/**\n * Spawn a Claude agent on a remote VM\n */\nexport async function spawnRemoteAgent(options: SpawnRemoteAgentOptions): Promise<RemoteAgentState> {\n const { issueId, workspace, model = 'claude-sonnet-4-6', prompt } = options;\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const vmName = workspace.vmName;\n\n const fly = createFlyProvider();\n\n // Check if VM is running\n const vmStatus = await fly.getStatus(vmName);\n if (vmStatus !== 'running') {\n throw new Error(`VM ${vmName} is not running. Start it with: pan workspace start ${issueId}`);\n }\n\n // Check if agent already exists\n if (await remoteSessionExists(fly, vmName, agentId)) {\n throw new Error(`Agent ${agentId} already running on ${vmName}. Use 'pan work tell' to message it.`);\n }\n\n // Create agent state\n const state: RemoteAgentState = {\n id: agentId,\n issueId,\n vmName,\n model,\n status: 'starting',\n startedAt: new Date().toISOString(),\n location: 'remote',\n };\n\n saveRemoteAgentState(state);\n\n // Write prompt to file on remote VM if provided\n let claudeCmd: string;\n\n if (prompt) {\n // Write prompt to file on VM using base64 to avoid escaping issues\n const promptFile = `/workspace/.pan/prompts/${agentId}.md`;\n await fly.ssh(vmName, `mkdir -p /workspace/.pan/prompts`);\n const promptBase64 = Buffer.from(prompt).toString('base64');\n await fly.ssh(vmName, `echo '${promptBase64}' | base64 -d > ${promptFile}`);\n\n // Create launcher script using base64 to avoid shell interpretation\n const launcherScript = `/workspace/.pan/prompts/${agentId}-launcher.sh`;\n const launcherContent = `#!/bin/bash\nexport PATH=\"/usr/local/bin:\\$PATH\"\nprompt=\\$(cat \"${promptFile}\")\nexec claude --dangerously-skip-permissions --model ${model} \"\\$prompt\"\n`;\n const launcherBase64 = Buffer.from(launcherContent).toString('base64');\n await fly.ssh(vmName, `echo '${launcherBase64}' | base64 -d > ${launcherScript} && chmod +x ${launcherScript}`);\n\n claudeCmd = `bash ${launcherScript}`;\n } else {\n claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;\n }\n\n // Create tmux session on remote VM\n const tmuxCmd = `tmux new-session -d -s ${agentId} -c /workspace '${claudeCmd}'`;\n const result = await fly.ssh(vmName, tmuxCmd);\n\n if (result.exitCode !== 0) {\n state.status = 'error';\n saveRemoteAgentState(state);\n throw new Error(`Failed to start agent: ${result.stderr}`);\n }\n\n // Update status\n state.status = 'running';\n saveRemoteAgentState(state);\n\n return state;\n}\n\n/**\n * Get remote agent output from tmux session\n */\nexport async function getRemoteAgentOutput(\n agentId: string,\n vmName: string,\n lines: number = 100\n): Promise<string> {\n const fly = createFlyProvider();\n\n const result = await fly.ssh(vmName, `tmux capture-pane -t ${agentId} -p -S -${lines}`);\n return result.stdout;\n}\n\n/**\n * Send message to remote agent\n */\nexport async function sendToRemoteAgent(\n agentId: string,\n vmName: string,\n message: string\n): Promise<void> {\n const fly = createFlyProvider();\n\n // Escape message for shell\n const escapedMessage = message.replace(/'/g, \"'\\\\''\");\n\n // Send keys to tmux session (send message then Enter)\n await fly.ssh(vmName, `tmux send-keys -t ${agentId} '${escapedMessage}'`);\n await fly.ssh(vmName, `tmux send-keys -t ${agentId} C-m`);\n}\n\n/**\n * Check if remote agent is still running\n */\nexport async function isRemoteAgentRunning(\n agentId: string,\n vmName: string\n): Promise<boolean> {\n const fly = createFlyProvider();\n return remoteSessionExists(fly, vmName, agentId);\n}\n\n/**\n * Kill remote agent session\n */\nexport async function killRemoteAgent(\n agentId: string,\n vmName: string\n): Promise<void> {\n const fly = createFlyProvider();\n await fly.ssh(vmName, `tmux kill-session -t ${agentId} 2>/dev/null || true`);\n\n // Update state\n const state = loadRemoteAgentState(agentId);\n if (state) {\n state.status = 'stopped';\n saveRemoteAgentState(state);\n }\n}\n\n/**\n * Get list of running remote agents on a VM\n */\nexport async function listRemoteAgents(vmName: string): Promise<string[]> {\n const fly = createFlyProvider();\n\n const result = await fly.ssh(vmName, `tmux list-sessions -F \"#{session_name}\" 2>/dev/null | grep \"^agent-\" || true`);\n if (!result.stdout.trim()) {\n return [];\n }\n\n return result.stdout.trim().split('\\n').filter(Boolean);\n}\n\n/**\n * Poll remote agent for status updates\n * Returns parsed events from the agent output\n */\nexport async function pollRemoteAgentStatus(\n agentId: string,\n vmName: string\n): Promise<{\n isRunning: boolean;\n lastOutput: string;\n toolUses: string[];\n}> {\n const fly = createFlyProvider();\n\n // Check if session exists\n const isRunning = await remoteSessionExists(fly, vmName, agentId);\n\n if (!isRunning) {\n return { isRunning: false, lastOutput: '', toolUses: [] };\n }\n\n // Get recent output\n const output = await getRemoteAgentOutput(agentId, vmName, 50);\n\n // Parse tool uses from output (simple pattern matching)\n const toolUses: string[] = [];\n const toolPattern = /(?:Using|Calling|Running)\\s+(\\w+)\\s+tool/gi;\n let match;\n while ((match = toolPattern.exec(output)) !== null) {\n toolUses.push(match[1]);\n }\n\n return {\n isRunning,\n lastOutput: output,\n toolUses,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remote-agents-CZXrUF4f.js","names":["parse"],"sources":["../src/lib/remote/fly-api.ts","../src/lib/remote/fly-provider.ts","../src/lib/remote/remote-agents.ts"],"sourcesContent":["/**\n * Fly Machines REST API Client\n *\n * Wraps the Fly Machines API (flaps) for machine lifecycle management.\n * Base URL: https://api.machines.dev/v1\n * Auth: FLY_API_TOKEN environment variable\n */\n\nexport interface FlyMachineConfig {\n image: string;\n env?: Record<string, string>;\n size?: string; // e.g. \"shared-cpu-2x\"\n memory?: number; // MB\n region?: string; // e.g. \"iad\"\n auto_destroy?: boolean;\n restart?: { policy: 'no' | 'always' | 'on-failure' };\n metadata?: Record<string, string>;\n}\n\nexport interface FlyMachine {\n id: string;\n name: string;\n state: string; // 'started', 'stopped', 'created', 'destroying', etc.\n region: string;\n image_ref?: { registry: string; repository: string; tag: string };\n instance_id?: string;\n private_ip?: string;\n created_at?: string;\n config?: {\n image: string;\n env?: Record<string, string>;\n guest?: { cpu_kind: string; cpus: number; memory_mb: number };\n };\n}\n\nexport interface FlyExecResult {\n stdout: string;\n stderr: string;\n exit_code: number;\n}\n\nexport class FlyApiError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly body: string\n ) {\n super(message);\n this.name = 'FlyApiError';\n }\n}\n\nconst BASE_URL = 'https://api.machines.dev/v1';\n\nexport class FlyApiClient {\n private readonly token: string;\n\n constructor(token: string) {\n this.token = token;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n const url = `${BASE_URL}${path}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new FlyApiError(\n `Fly API error ${response.status} for ${method} ${path}: ${text}`,\n response.status,\n text\n );\n }\n\n if (!text) return undefined as T;\n\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n }\n\n /** Create a machine in an app */\n async createMachine(\n appName: string,\n name: string,\n config: FlyMachineConfig\n ): Promise<FlyMachine> {\n return this.request<FlyMachine>('POST', `/apps/${appName}/machines`, {\n name,\n config: {\n image: config.image,\n env: config.env,\n guest: config.size\n ? { cpu_kind: 'shared', cpus: 2, memory_mb: config.memory ?? 1024 }\n : undefined,\n restart: config.restart ?? { policy: 'no' },\n auto_destroy: config.auto_destroy,\n metadata: config.metadata,\n },\n region: config.region,\n });\n }\n\n /** Destroy a machine (force=true for immediate) */\n async destroyMachine(appName: string, machineId: string): Promise<void> {\n await this.request<void>(\n 'DELETE',\n `/apps/${appName}/machines/${machineId}?force=true`\n );\n }\n\n /** Start a stopped machine */\n async startMachine(appName: string, machineId: string): Promise<void> {\n await this.request<void>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/start`\n );\n }\n\n /** Stop a running machine */\n async stopMachine(\n appName: string,\n machineId: string,\n signal?: string,\n timeout?: number\n ): Promise<void> {\n await this.request<void>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/stop`,\n signal || timeout ? { signal, timeout } : undefined\n );\n }\n\n /** Get a machine by ID */\n async getMachine(appName: string, machineId: string): Promise<FlyMachine> {\n return this.request<FlyMachine>(\n 'GET',\n `/apps/${appName}/machines/${machineId}`\n );\n }\n\n /** List all machines in an app */\n async listMachines(appName: string): Promise<FlyMachine[]> {\n const result = await this.request<FlyMachine[] | null>(\n 'GET',\n `/apps/${appName}/machines`\n );\n return result ?? [];\n }\n\n /** Execute a command inside a running machine */\n async execCommand(\n appName: string,\n machineId: string,\n command: string[],\n timeout: number = 30\n ): Promise<FlyExecResult> {\n return this.request<FlyExecResult>(\n 'POST',\n `/apps/${appName}/machines/${machineId}/exec`,\n { command, timeout }\n );\n }\n\n /** Wait for a machine to reach a target state */\n async waitForState(\n appName: string,\n machineId: string,\n state: string,\n timeout: number = 60\n ): Promise<void> {\n await this.request<void>(\n 'GET',\n `/apps/${appName}/machines/${machineId}/wait?state=${state}&timeout=${timeout}`\n );\n }\n\n /** Create a Fly app if it doesn't exist */\n async ensureApp(appName: string, orgSlug: string): Promise<void> {\n try {\n await this.request<unknown>('GET', `/apps/${appName}`);\n } catch (err) {\n if (err instanceof FlyApiError && err.statusCode === 404) {\n await this.request<unknown>('POST', '/apps', {\n app_name: appName,\n org_slug: orgSlug,\n network: 'default',\n });\n } else {\n throw err;\n }\n }\n }\n}\n\n/** Create a FlyApiClient from env or explicit token */\nexport function createFlyApiClient(token?: string): FlyApiClient {\n const tok = token ?? process.env.FLY_API_TOKEN;\n if (!tok) {\n throw new Error(\n 'Fly API token not found. Set FLY_API_TOKEN environment variable or run: fly auth login'\n );\n }\n return new FlyApiClient(tok);\n}\n","/**\n * Fly.io Remote Provider\n *\n * Implements the RemoteProvider interface using Fly Machines API and Fly CLI.\n * VM lifecycle is managed via REST API; exec/SSH via Fly CLI.\n */\n\nimport { exec, spawn } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { parse } from 'yaml';\nimport { FlyApiClient, createFlyApiClient, FlyApiError } from './fly-api.js';\nimport type { RemoteProvider, VmInfo, VmStatus, ExecResult } from './interface.js';\n\nconst execAsync = promisify(exec);\n\nexport interface FlyProviderConfig {\n /** Fly.io app name for workspace machines (default: pan-workspaces) */\n app?: string;\n /** Fly.io org slug */\n org?: string;\n /** Default region (default: iad) */\n region?: string;\n /** Machine size (default: shared-cpu-2x) */\n vmSize?: string;\n /** Memory in MB (default: 1024) */\n vmMemory?: number;\n /** Docker image for workspace machines */\n image?: string;\n /** API token (falls back to FLY_API_TOKEN env var) */\n apiToken?: string;\n}\n\nfunction mapFlyStateToVmStatus(state: string): VmStatus {\n switch (state) {\n case 'started':\n return 'running';\n case 'stopped':\n case 'suspended':\n return 'stopped';\n case 'created':\n case 'replacing':\n return 'creating';\n case 'destroying':\n case 'destroyed':\n return 'deleting';\n default:\n return 'unknown';\n }\n}\n\nexport class FlyProvider implements RemoteProvider {\n readonly name = 'fly';\n\n private readonly config: Required<FlyProviderConfig>;\n private api: FlyApiClient | null = null;\n\n constructor(config: FlyProviderConfig = {}) {\n this.config = {\n app: config.app ?? 'pan-workspaces',\n org: config.org ?? 'personal',\n region: config.region ?? 'iad',\n vmSize: config.vmSize ?? 'shared-cpu-2x',\n vmMemory: config.vmMemory ?? 1024,\n image: config.image ?? 'registry.fly.io/pan-workspace:latest',\n apiToken: config.apiToken ?? process.env.FLY_API_TOKEN ?? '',\n };\n }\n\n private getApi(): FlyApiClient {\n if (!this.api) {\n this.api = createFlyApiClient(this.config.apiToken || undefined);\n }\n return this.api;\n }\n\n async isAuthenticated(): Promise<boolean> {\n // Check API token first\n if (this.config.apiToken || process.env.FLY_API_TOKEN) {\n try {\n await this.getApi().listMachines(this.config.app);\n return true;\n } catch {\n // Fall through to CLI check\n }\n }\n\n // Check fly CLI auth\n try {\n const result = await execAsync('fly auth whoami', { timeout: 10000 });\n return !result.stdout.includes('not logged in');\n } catch {\n return false;\n }\n }\n\n /**\n * Resolve vmName to {appName, machineId} by scanning workspace metadata.\n * Falls back to listing machines in the app.\n */\n async resolveVm(vmName: string): Promise<{ appName: string; machineId: string }> {\n const workspacesDir = join(homedir(), '.panopticon', 'workspaces');\n if (existsSync(workspacesDir)) {\n for (const file of readdirSync(workspacesDir)) {\n if (!file.endsWith('.yaml')) continue;\n try {\n const content = readFileSync(join(workspacesDir, file), 'utf-8');\n const metadata = parse(content) as { vmName?: string; machineId?: string; appName?: string };\n if (metadata.vmName === vmName && metadata.machineId && metadata.appName) {\n return { appName: metadata.appName, machineId: metadata.machineId };\n }\n } catch {\n // Skip invalid files\n }\n }\n }\n\n // Fallback: search by machine name via API\n const machines = await this.getApi().listMachines(this.config.app);\n const machine = machines.find(m => m.name === vmName);\n if (!machine) {\n throw new Error(`No Fly machine found for VM name: ${vmName}`);\n }\n return { appName: this.config.app, machineId: machine.id };\n }\n\n async createVm(name: string): Promise<VmInfo> {\n const api = this.getApi();\n\n // Ensure app exists\n await api.ensureApp(this.config.app, this.config.org);\n\n // Create machine\n const machine = await api.createMachine(this.config.app, name, {\n image: this.config.image,\n size: this.config.vmSize,\n memory: this.config.vmMemory,\n region: this.config.region,\n restart: { policy: 'no' },\n auto_destroy: false,\n });\n\n // Wait for machine to start\n try {\n await api.waitForState(this.config.app, machine.id, 'started', 120);\n } catch {\n // Non-fatal: machine may still be starting\n }\n\n return {\n name,\n status: mapFlyStateToVmStatus(machine.state),\n machineId: machine.id,\n ipAddress: machine.private_ip,\n created: machine.created_at ? new Date(machine.created_at) : undefined,\n };\n }\n\n async deleteVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().destroyMachine(appName, machineId);\n }\n\n async listVms(): Promise<VmInfo[]> {\n const machines = await this.getApi().listMachines(this.config.app);\n return machines.map(m => ({\n name: m.name,\n status: mapFlyStateToVmStatus(m.state),\n machineId: m.id,\n ipAddress: m.private_ip,\n created: m.created_at ? new Date(m.created_at) : undefined,\n }));\n }\n\n async getStatus(name: string): Promise<VmStatus> {\n try {\n const { appName, machineId } = await this.resolveVm(name);\n const machine = await this.getApi().getMachine(appName, machineId);\n return mapFlyStateToVmStatus(machine.state);\n } catch {\n return 'unknown';\n }\n }\n\n async getVmInfo(name: string): Promise<VmInfo | null> {\n try {\n const { appName, machineId } = await this.resolveVm(name);\n const machine = await this.getApi().getMachine(appName, machineId);\n return {\n name,\n status: mapFlyStateToVmStatus(machine.state),\n machineId: machine.id,\n ipAddress: machine.private_ip,\n created: machine.created_at ? new Date(machine.created_at) : undefined,\n memoryTotal: machine.config?.guest?.memory_mb,\n };\n } catch {\n return null;\n }\n }\n\n async startVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().startMachine(appName, machineId);\n await this.getApi().waitForState(appName, machineId, 'started', 60);\n }\n\n async stopVm(name: string): Promise<void> {\n const { appName, machineId } = await this.resolveVm(name);\n await this.getApi().stopMachine(appName, machineId);\n }\n\n /** Execute a command on the VM via Fly Machines exec API */\n async ssh(vm: string, command: string): Promise<ExecResult> {\n const { appName, machineId } = await this.resolveVm(vm);\n try {\n const result = await this.getApi().execCommand(\n appName,\n machineId,\n ['/bin/sh', '-c', command],\n 60\n );\n return {\n stdout: result.stdout ?? '',\n stderr: result.stderr ?? '',\n exitCode: result.exit_code ?? 0,\n };\n } catch (err) {\n if (err instanceof FlyApiError) {\n return { stdout: '', stderr: err.message, exitCode: 1 };\n }\n throw err;\n }\n }\n\n /** Stream command output via fly SSH console */\n async *sshStream(vm: string, command: string): AsyncIterable<string> {\n const { appName } = await this.resolveVm(vm);\n const child = spawn('fly', ['ssh', 'console', '-a', appName, '-C', command], {\n env: { ...process.env },\n });\n\n for await (const chunk of child.stdout) {\n yield chunk.toString();\n }\n for await (const chunk of child.stderr) {\n yield chunk.toString();\n }\n\n await new Promise<void>((resolve, reject) => {\n child.on('close', resolve);\n child.on('error', reject);\n });\n }\n\n /** Copy a local file to VM using base64 encoding */\n async copyToVm(vm: string, localPath: string, remotePath: string): Promise<void> {\n const content = readFileSync(localPath);\n const b64 = content.toString('base64');\n const dirPath = remotePath.substring(0, remotePath.lastIndexOf('/'));\n if (dirPath) {\n await this.ssh(vm, `mkdir -p ${JSON.stringify(dirPath)}`);\n }\n await this.ssh(vm, `echo '${b64}' | base64 -d > ${JSON.stringify(remotePath)}`);\n }\n\n /** Copy a file from VM to local path */\n async copyFromVm(vm: string, remotePath: string, localPath: string): Promise<void> {\n const { appName } = await this.resolveVm(vm);\n await execAsync(\n `fly ssh sftp get -a ${JSON.stringify(appName)} ${JSON.stringify(remotePath)} ${JSON.stringify(localPath)}`,\n { timeout: 60000 }\n );\n }\n\n /** Expose a port — not supported by Fly.io provider */\n async exposePort(_vm: string, _port: number): Promise<string> {\n throw new Error(\n 'exposePort is not supported by the Fly.io provider. ' +\n 'Configure services in fly.toml or via the Fly Machines API config.'\n );\n }\n\n /** Create a fly proxy tunnel to the machine */\n async tunnel(\n vm: string,\n remotePort: number,\n localPort: number\n ): Promise<{ close: () => void }> {\n const { appName } = await this.resolveVm(vm);\n const child = spawn('fly', ['proxy', `${localPort}:${remotePort}`, '-a', appName], {\n env: { ...process.env },\n });\n\n return {\n close: () => {\n child.kill();\n },\n };\n }\n\n // ============================================================================\n // Credential Sync & Configuration (ported from ExeProvider)\n // ============================================================================\n\n /** Sync Claude Code credentials from local macOS Keychain to remote VM */\n async syncClaudeCredentials(vmName: string): Promise<boolean> {\n try {\n const { stdout: credentials } = await execAsync(\n 'security find-generic-password -s \"Claude Code-credentials\" -w 2>/dev/null',\n { encoding: 'utf-8', timeout: 10000 }\n );\n if (!credentials?.trim()) return false;\n\n const b64 = Buffer.from(credentials.trim()).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.claude && echo '${b64}' | base64 -d > ~/.claude/.credentials.json`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync GitHub CLI authentication to the remote VM */\n async syncGitHubAuth(vmName: string): Promise<boolean> {\n const ghConfigPath = join(homedir(), '.config', 'gh', 'hosts.yml');\n if (!existsSync(ghConfigPath)) return false;\n\n try {\n const content = readFileSync(ghConfigPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.config/gh && echo '${b64}' | base64 -d > ~/.config/gh/hosts.yml`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync GitLab CLI (glab) authentication to the remote VM */\n async syncGitLabAuth(vmName: string): Promise<boolean> {\n const glabConfigPath = join(homedir(), '.config', 'glab-cli', 'config.yml');\n if (!existsSync(glabConfigPath)) return false;\n\n try {\n const content = readFileSync(glabConfigPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `mkdir -p ~/.config/glab-cli && echo '${b64}' | base64 -d > ~/.config/glab-cli/config.yml`);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Sync all credentials needed for remote workspace operation */\n async syncAllCredentials(vmName: string): Promise<{ claude: boolean; github: boolean }> {\n const [claude, github] = await Promise.all([\n this.syncClaudeCredentials(vmName),\n this.syncGitHubAuth(vmName),\n ]);\n return { claude, github };\n }\n\n /** Install beads CLI (bd) on a remote VM */\n async installBeads(vmName: string): Promise<boolean> {\n // Check if already installed\n const check = await this.ssh(vmName, 'which bd 2>/dev/null');\n if (check.exitCode === 0 && check.stdout.trim()) return true;\n\n // Install via npm\n const result = await this.ssh(vmName, 'npm install -g @beads-dev/beads 2>&1');\n if (result.exitCode !== 0) {\n // Try alternative install\n const alt = await this.ssh(\n vmName,\n 'curl -fsSL https://raw.githubusercontent.com/beads-dev/beads/main/install.sh | bash 2>&1'\n );\n return alt.exitCode === 0;\n }\n return true;\n }\n\n /** Initialize beads in a workspace on a remote VM */\n async initBeads(vmName: string, workspacePath: string = '/workspace'): Promise<boolean> {\n const result = await this.ssh(\n vmName,\n `cd ${workspacePath} && (bd init --prefix PAN 2>&1 || bd init 2>&1) && git config beads.role contributor`\n );\n return result.exitCode === 0;\n }\n\n /** Configure Claude Code on a VM for autonomous operation */\n async configureClaudeCode(vmName: string): Promise<void> {\n await this.ssh(vmName, 'mkdir -p ~/.claude');\n\n // Set onboarding complete\n const onboardingScript = `\nimport json, os\npath = os.path.expanduser(\"~/.claude.json\")\ndata = {}\nif os.path.exists(path):\n with open(path) as f:\n data = json.load(f)\ndata[\"hasCompletedOnboarding\"] = True\ndata[\"lastOnboardingVersion\"] = \"2.0.50\"\nwith open(path, \"w\") as f:\n json.dump(data, f, indent=2)\n`;\n const scriptB64 = Buffer.from(onboardingScript).toString('base64');\n await this.ssh(vmName, `echo '${scriptB64}' | base64 -d | python3`);\n\n // Write settings.json with bypass permissions\n const settings = JSON.stringify({\n theme: 'dark',\n permissions: { defaultMode: 'bypassPermissions' },\n });\n const settingsB64 = Buffer.from(settings).toString('base64');\n await this.ssh(vmName, `echo '${settingsB64}' | base64 -d > ~/.claude/settings.json`);\n }\n\n /** Copy essential skills from local ~/.panopticon/skills/ to remote VM */\n async copySkillsToVm(vmName: string): Promise<void> {\n const skillsDir = join(homedir(), '.panopticon', 'skills');\n if (!existsSync(skillsDir)) return;\n\n await this.ssh(vmName, 'mkdir -p ~/.claude/skills');\n\n try {\n const entries = readdirSync(skillsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.md')) continue;\n const localPath = join(skillsDir, entry.name);\n const content = readFileSync(localPath, 'utf-8');\n const b64 = Buffer.from(content).toString('base64');\n await this.ssh(vmName, `echo '${b64}' | base64 -d > ~/.claude/skills/${entry.name}`);\n }\n } catch {\n // Non-fatal: skills are optional\n }\n }\n\n /** Sync beads from remote VM to git: exports JSONL, commits, and pushes */\n async syncBeadsToGit(\n vmName: string,\n workspacePath: string = '/workspace',\n commitMessage?: string\n ): Promise<boolean> {\n const msg = commitMessage ?? 'chore: sync beads from remote';\n\n // Export beads to JSONL\n const exportResult = await this.ssh(\n vmName,\n `cd ${workspacePath} && bd export --output .beads/issues.jsonl 2>&1`\n );\n if (exportResult.exitCode !== 0) {\n return false;\n }\n\n // Commit and push\n const gitResult = await this.ssh(\n vmName,\n `cd ${workspacePath} && git diff --cached --quiet || (git commit -m ${JSON.stringify(msg)} && git push origin HEAD) 2>&1`\n );\n return gitResult.exitCode === 0;\n }\n\n /** Query beads on a remote VM via bd search */\n async queryBeads(\n vmName: string,\n searchTerm: string,\n workspacePath: string = '/workspace'\n ): Promise<unknown[]> {\n const result = await this.ssh(\n vmName,\n `cd ${workspacePath} && bd search ${JSON.stringify(searchTerm)} --json 2>/dev/null || echo '[]'`\n );\n try {\n return JSON.parse(result.stdout.trim() || '[]');\n } catch {\n return [];\n }\n }\n\n /** Get the configured app name */\n getAppName(): string {\n return this.config.app;\n }\n}\n\nexport function createFlyProvider(config?: FlyProviderConfig): FlyProvider {\n return new FlyProvider(config);\n}\n","/**\n * Remote Agent Management\n *\n * Spawn and manage Claude agents on remote Fly.io machines.\n * Agents run in tmux sessions for persistence and monitoring.\n */\n\nimport { createFlyProvider } from './fly-provider.js';\nimport type { RemoteProvider, RemoteWorkspaceMetadata } from './interface.js';\nimport { join } from 'path';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\n\nconst AGENTS_DIR = join(homedir(), '.panopticon', 'agents');\n\nexport interface RemoteAgentState {\n id: string;\n issueId: string;\n vmName: string;\n model: string;\n status: 'starting' | 'running' | 'stopped' | 'error';\n startedAt: string;\n lastActivity?: string;\n location: 'remote';\n}\n\n/**\n * Get agent state file path\n */\nfunction getRemoteAgentStateFile(agentId: string): string {\n return join(AGENTS_DIR, agentId, 'remote-state.json');\n}\n\n/**\n * Save remote agent state\n */\nfunction saveRemoteAgentState(state: RemoteAgentState): void {\n const dir = join(AGENTS_DIR, state.id);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(getRemoteAgentStateFile(state.id), JSON.stringify(state, null, 2));\n}\n\n/**\n * Load remote agent state\n */\nexport function loadRemoteAgentState(agentId: string): RemoteAgentState | null {\n const file = getRemoteAgentStateFile(agentId);\n if (!existsSync(file)) return null;\n\n try {\n return JSON.parse(readFileSync(file, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Check if remote agent session exists\n */\nasync function remoteSessionExists(\n provider: RemoteProvider,\n vmName: string,\n sessionName: string\n): Promise<boolean> {\n const result = await provider.ssh(vmName, `tmux has-session -t ${sessionName} 2>/dev/null && echo exists || echo not-found`);\n return result.stdout.trim() === 'exists';\n}\n\nexport interface SpawnRemoteAgentOptions {\n issueId: string;\n workspace: RemoteWorkspaceMetadata;\n model?: string;\n prompt?: string;\n phase?: string;\n}\n\n/**\n * Spawn a Claude agent on a remote VM\n */\nexport async function spawnRemoteAgent(options: SpawnRemoteAgentOptions): Promise<RemoteAgentState> {\n const { issueId, workspace, model = 'claude-sonnet-4-6', prompt } = options;\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const vmName = workspace.vmName;\n\n const fly = createFlyProvider();\n\n // Check if VM is running\n const vmStatus = await fly.getStatus(vmName);\n if (vmStatus !== 'running') {\n throw new Error(`VM ${vmName} is not running. Start it with: pan workspace start ${issueId}`);\n }\n\n // Check if agent already exists\n if (await remoteSessionExists(fly, vmName, agentId)) {\n throw new Error(`Agent ${agentId} already running on ${vmName}. Use 'pan work tell' to message it.`);\n }\n\n // Create agent state\n const state: RemoteAgentState = {\n id: agentId,\n issueId,\n vmName,\n model,\n status: 'starting',\n startedAt: new Date().toISOString(),\n location: 'remote',\n };\n\n saveRemoteAgentState(state);\n\n // Write prompt to file on remote VM if provided\n let claudeCmd: string;\n\n if (prompt) {\n // Write prompt to file on VM using base64 to avoid escaping issues\n const promptFile = `/workspace/.pan/prompts/${agentId}.md`;\n await fly.ssh(vmName, `mkdir -p /workspace/.pan/prompts`);\n const promptBase64 = Buffer.from(prompt).toString('base64');\n await fly.ssh(vmName, `echo '${promptBase64}' | base64 -d > ${promptFile}`);\n\n // Create launcher script using base64 to avoid shell interpretation\n const launcherScript = `/workspace/.pan/prompts/${agentId}-launcher.sh`;\n const launcherContent = `#!/bin/bash\nexport PATH=\"/usr/local/bin:\\$PATH\"\nprompt=\\$(cat \"${promptFile}\")\nexec claude --dangerously-skip-permissions --model ${model} \"\\$prompt\"\n`;\n const launcherBase64 = Buffer.from(launcherContent).toString('base64');\n await fly.ssh(vmName, `echo '${launcherBase64}' | base64 -d > ${launcherScript} && chmod +x ${launcherScript}`);\n\n claudeCmd = `bash ${launcherScript}`;\n } else {\n claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;\n }\n\n // Create tmux session on remote VM\n const tmuxCmd = `tmux new-session -d -s ${agentId} -c /workspace '${claudeCmd}'`;\n const result = await fly.ssh(vmName, tmuxCmd);\n\n if (result.exitCode !== 0) {\n state.status = 'error';\n saveRemoteAgentState(state);\n throw new Error(`Failed to start agent: ${result.stderr}`);\n }\n\n // Update status\n state.status = 'running';\n saveRemoteAgentState(state);\n\n return state;\n}\n\n/**\n * Get remote agent output from tmux session\n */\nexport async function getRemoteAgentOutput(\n agentId: string,\n vmName: string,\n lines: number = 100\n): Promise<string> {\n const fly = createFlyProvider();\n\n const result = await fly.ssh(vmName, `tmux capture-pane -t ${agentId} -p -S -${lines}`);\n return result.stdout;\n}\n\n/**\n * Send message to remote agent\n */\nexport async function sendToRemoteAgent(\n agentId: string,\n vmName: string,\n message: string\n): Promise<void> {\n const fly = createFlyProvider();\n\n // Escape message for shell\n const escapedMessage = message.replace(/'/g, \"'\\\\''\");\n\n // Send keys to tmux session (send message then Enter)\n await fly.ssh(vmName, `tmux send-keys -t ${agentId} '${escapedMessage}'`);\n await fly.ssh(vmName, `tmux send-keys -t ${agentId} C-m`);\n}\n\n/**\n * Check if remote agent is still running\n */\nexport async function isRemoteAgentRunning(\n agentId: string,\n vmName: string\n): Promise<boolean> {\n const fly = createFlyProvider();\n return remoteSessionExists(fly, vmName, agentId);\n}\n\n/**\n * Kill remote agent session\n */\nexport async function killRemoteAgent(\n agentId: string,\n vmName: string\n): Promise<void> {\n const fly = createFlyProvider();\n await fly.ssh(vmName, `tmux kill-session -t ${agentId} 2>/dev/null || true`);\n\n // Update state\n const state = loadRemoteAgentState(agentId);\n if (state) {\n state.status = 'stopped';\n saveRemoteAgentState(state);\n }\n}\n\n/**\n * Get list of running remote agents on a VM\n */\nexport async function listRemoteAgents(vmName: string): Promise<string[]> {\n const fly = createFlyProvider();\n\n const result = await fly.ssh(vmName, `tmux list-sessions -F \"#{session_name}\" 2>/dev/null | grep \"^agent-\" || true`);\n if (!result.stdout.trim()) {\n return [];\n }\n\n return result.stdout.trim().split('\\n').filter(Boolean);\n}\n\n/**\n * Poll remote agent for status updates\n * Returns parsed events from the agent output\n */\nexport async function pollRemoteAgentStatus(\n agentId: string,\n vmName: string\n): Promise<{\n isRunning: boolean;\n lastOutput: string;\n toolUses: string[];\n}> {\n const fly = createFlyProvider();\n\n // Check if session exists\n const isRunning = await remoteSessionExists(fly, vmName, agentId);\n\n if (!isRunning) {\n return { isRunning: false, lastOutput: '', toolUses: [] };\n }\n\n // Get recent output\n const output = await getRemoteAgentOutput(agentId, vmName, 50);\n\n // Parse tool uses from output (simple pattern matching)\n const toolUses: string[] = [];\n const toolPattern = /(?:Using|Calling|Running)\\s+(\\w+)\\s+tool/gi;\n let match;\n while ((match = toolPattern.exec(output)) !== null) {\n toolUses.push(match[1]);\n }\n\n return {\n isRunning,\n lastOutput: output,\n toolUses,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { i as sendToRemoteAgent, r as loadRemoteAgentState, t as init_remote_agents } from "./remote-agents-
|
|
1
|
+
import { i as sendToRemoteAgent, r as loadRemoteAgentState, t as init_remote_agents } from "./remote-agents-CZXrUF4f.js";
|
|
2
2
|
init_remote_agents();
|
|
3
3
|
export { loadRemoteAgentState, sendToRemoteAgent };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as init_config, o as loadConfig } from "./config-
|
|
2
|
-
import { a as findProjectByTeam, p as init_projects, r as extractTeamPrefix, s as getIssuePrefix } from "./projects-
|
|
3
|
-
import { s as saveWorkspaceMetadata, t as createFlyProviderFromConfig } from "./remote-
|
|
1
|
+
import { a as init_config, o as loadConfig } from "./config-BQNKsi9G.js";
|
|
2
|
+
import { a as findProjectByTeam, p as init_projects, r as extractTeamPrefix, s as getIssuePrefix } from "./projects-Bk-5QhFQ.js";
|
|
3
|
+
import { s as saveWorkspaceMetadata, t as createFlyProviderFromConfig } from "./remote-CYiOJg0q.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { exec } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
@@ -116,4 +116,4 @@ EOF`);
|
|
|
116
116
|
//#endregion
|
|
117
117
|
export { createRemoteWorkspace };
|
|
118
118
|
|
|
119
|
-
//# sourceMappingURL=remote-workspace-
|
|
119
|
+
//# sourceMappingURL=remote-workspace-CA33UuVI.js.map
|