@askexenow/exe-os 0.9.384 → 0.9.389
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/deploy/compose/.env.askexe-control-plane.example +6 -0
- package/deploy/compose/.env.customer.example +1 -1
- package/deploy/compose/.env.example +1 -1
- package/deploy/compose/docker-compose.yml +16 -0
- package/deploy/compose/generate-env.ts +1 -1
- package/deploy/compose/sso-edge/sso-redirect.conf +18 -3
- package/deploy/stack-manifests/v0.9.json +1160 -1
- package/dist/.dist-verified.json +3 -3
- package/dist/{active-agent-F5V4E4UL.js → active-agent-RZ24KSJN.js} +8 -6
- package/dist/{active-agent-YTBB4ZQM.js → active-agent-YWSWPIEA.js} +8 -6
- package/dist/{agent-defs-generator-7TFISIJQ.js → agent-defs-generator-GY3UBREJ.js} +8 -6
- package/dist/{agent-heartbeat-CQPGJIFF.js → agent-heartbeat-GUVUUQVM.js} +1 -1
- package/dist/{agent-loop-EZ7LDAGD.js → agent-loop-HPNCH3HK.js} +1 -1
- package/dist/{agentic-ontology-XVTUVV7W.js → agentic-ontology-RHYNXEKE.js} +1 -1
- package/dist/{artifact-backup-TF6YWQT2.js → artifact-backup-HGUFSIJH.js} +19 -6
- package/dist/{backfill-metadata-J6MXIDCW.js → backfill-metadata-XLFASEOD.js} +11 -9
- package/dist/{backup-verify-6DVBQMDP.js → backup-verify-TUQEICWA.js} +1 -1
- package/dist/{behavior-injection-XDA7ZCYN.js → behavior-injection-GXO7ERGN.js} +14 -12
- package/dist/{behavior-promotion-4VLQ7LXK.js → behavior-promotion-V63WIZ4N.js} +13 -11
- package/dist/{behaviors-SCCZP7RZ.js → behaviors-RMLDPLOF.js} +18 -11
- package/dist/bin/agentic-ontology-backfill.js +12 -10
- package/dist/bin/agentic-reflection-backfill.js +13 -11
- package/dist/bin/agentic-semantic-label.js +12 -10
- package/dist/bin/backfill-conversations.js +11 -9
- package/dist/bin/backfill-responses.js +11 -9
- package/dist/bin/backfill-vectors.js +13 -11
- package/dist/bin/bulk-sync-postgres.js +20 -18
- package/dist/bin/cc-doctor.js +15 -13
- package/dist/bin/cleanup-stale-review-tasks.js +44 -32
- package/dist/bin/cli.js +109 -30
- package/dist/bin/customer-readiness.js +55 -25
- package/dist/bin/deferred-daemon-restart.js +111 -108
- package/dist/bin/exe-actions.js +7 -1
- package/dist/bin/exe-agent-config.js +8 -6
- package/dist/bin/exe-agent.js +18 -16
- package/dist/bin/exe-assign.js +13 -11
- package/dist/bin/exe-boot.js +49 -47
- package/dist/bin/exe-call.js +9 -7
- package/dist/bin/exe-cicd.js +19 -8
- package/dist/bin/exe-cloud.js +59 -25
- package/dist/bin/exe-config-dump.js +8 -6
- package/dist/bin/exe-dispatch.js +23 -21
- package/dist/bin/exe-doctor.js +11 -8
- package/dist/bin/exe-export-behaviors.js +16 -14
- package/dist/bin/exe-forget.js +14 -12
- package/dist/bin/exe-gateway.js +12 -10
- package/dist/bin/exe-grant.js +6 -6
- package/dist/bin/exe-healthcheck.js +15 -13
- package/dist/bin/exe-heartbeat.js +28 -26
- package/dist/bin/exe-kill.js +28 -26
- package/dist/bin/exe-launch-agent.js +35 -33
- package/dist/bin/exe-mcp.js +9 -7
- package/dist/bin/exe-memory-reads.js +11 -9
- package/dist/bin/exe-new-employee.js +13 -11
- package/dist/bin/exe-os-mcp-proxy.js +9 -7
- package/dist/bin/exe-pending-messages.js +23 -21
- package/dist/bin/exe-pending-notifications.js +22 -20
- package/dist/bin/exe-pending-reviews.js +23 -21
- package/dist/bin/exe-preflight.js +1 -1
- package/dist/bin/exe-rename.js +9 -7
- package/dist/bin/exe-review.js +59 -55
- package/dist/bin/exe-search-quality.js +16 -3
- package/dist/bin/exe-search.js +13 -11
- package/dist/bin/exe-session-cleanup.js +29 -27
- package/dist/bin/exe-settings.js +22 -20
- package/dist/bin/exe-start-codex.js +23 -21
- package/dist/bin/exe-start-opencode.js +21 -19
- package/dist/bin/exe-status.js +24 -22
- package/dist/bin/exe-support.js +3 -3
- package/dist/bin/exe-team.js +8 -6
- package/dist/bin/exe-verify-running-images.js +644 -17
- package/dist/bin/git-sweep.js +24 -22
- package/dist/bin/graph-backfill.js +12 -10
- package/dist/bin/graph-export.js +12 -10
- package/dist/bin/hooks-cli.js +4 -4
- package/dist/bin/import-history.js +16 -14
- package/dist/bin/install.js +113 -110
- package/dist/bin/intercom-check.js +5 -5
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +8 -6
- package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
- package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
- package/dist/bin/pre-build-guard.js +55 -16
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/prune-orphan-chunks.js +154 -28
- package/dist/bin/reconcile-backlog.js +5 -3
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/repair-document-chunks.js +11 -9
- package/dist/bin/scan-tasks.js +23 -21
- package/dist/bin/setup.js +3 -3
- package/dist/bin/shard-migrate.js +12 -10
- package/dist/bin/stack-update.js +622 -43
- package/dist/bin/verify-stack.js +2 -2
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{boot-generation-Q7LGQ3AO.js → boot-generation-VCQXVYGT.js} +7 -5
- package/dist/{boot-review-sections-G5774FR5.js → boot-review-sections-3QO6Q7MP.js} +21 -19
- package/dist/{branch-hygiene-44WLSOZR.js → branch-hygiene-DDXXI6ZA.js} +59 -21
- package/dist/bug-landed-reconciler-TQVNOUCU.js +353 -0
- package/dist/build-manifest.json +1269 -1194
- package/dist/{capability-cards-I3RHCF4F.js → capability-cards-XPDLT6LU.js} +6 -4
- package/dist/{capacity-monitor-7I7GDONU.js → capacity-monitor-N3AW242L.js} +23 -21
- package/dist/{catchup-brief-3T5PARIR.js → catchup-brief-3PKX7FUP.js} +29 -27
- package/dist/{chunk-C5ISP5FO.js → chunk-2BKJAOO4.js} +11 -1
- package/dist/{chunk-JJTPPAZY.js → chunk-2CUQHG6H.js} +1609 -1356
- package/dist/{chunk-HU3VD4MQ.js → chunk-2Q2GGYBX.js} +1 -1
- package/dist/{chunk-CHIUS5QB.js → chunk-36LMIOVU.js} +9 -9
- package/dist/{chunk-46VSBDUW.js → chunk-3DCBDG4T.js} +2 -2
- package/dist/{chunk-W3KPQE7H.js → chunk-3GL3E4TD.js} +3 -3
- package/dist/{chunk-GL47M7NY.js → chunk-3UD3OF3X.js} +202 -117
- package/dist/{chunk-WNBZJSQT.js → chunk-3VW5ZP2F.js} +1 -1
- package/dist/{chunk-JDDM6C57.js → chunk-3X74IQBU.js} +1 -1
- package/dist/{chunk-NVFNADTX.js → chunk-3ZL4MDQ6.js} +18 -4
- package/dist/{chunk-R5E7WNAC.js → chunk-4ESNDGMB.js} +3 -3
- package/dist/{chunk-CYCIX7AW.js → chunk-4FLLIQRQ.js} +2 -2
- package/dist/{chunk-3RGTLZNO.js → chunk-4GAKF7KR.js} +99 -74
- package/dist/{chunk-RVO5O5SR.js → chunk-4S3A52AV.js} +38 -10
- package/dist/{chunk-7J2ZQ4XN.js → chunk-4VQ3QR6B.js} +29 -28
- package/dist/{chunk-MROSARM5.js → chunk-5BJBTFFQ.js} +7 -1
- package/dist/chunk-5LQZABW5.js +6 -0
- package/dist/chunk-5ONMHTNI.js +236 -0
- package/dist/{chunk-APL7HUE3.js → chunk-5QQJUO3F.js} +200 -31
- package/dist/{chunk-UOPCVFVR.js → chunk-5RK75SWH.js} +205 -278
- package/dist/{chunk-T5FEAQES.js → chunk-5TYJYGS5.js} +1 -1
- package/dist/{chunk-P3QQ52QK.js → chunk-5U6EOTZV.js} +3 -3
- package/dist/{chunk-ALRHGEAJ.js → chunk-637FL62O.js} +1 -1
- package/dist/{chunk-PUBA5B4M.js → chunk-6L4VIYLZ.js} +33 -19
- package/dist/{chunk-VF2KRROX.js → chunk-754P2UXM.js} +1 -1
- package/dist/{chunk-CM7N5MBY.js → chunk-7FZG7WP4.js} +5 -5
- package/dist/{chunk-NYLJQ2M2.js → chunk-7NEBFB3L.js} +4 -4
- package/dist/{chunk-E7C2WNR5.js → chunk-AEIOFFFX.js} +5 -5
- package/dist/{chunk-VOVFUKEB.js → chunk-AIB2EWO2.js} +5 -5
- package/dist/{chunk-EGOEB42T.js → chunk-AP2C4OGS.js} +3 -3
- package/dist/{chunk-MPZ2AVNS.js → chunk-ATNV3BVZ.js} +1 -1
- package/dist/{chunk-F4BBMIPG.js → chunk-B4XJQBQB.js} +2 -2
- package/dist/{chunk-BPVFZ6Q4.js → chunk-B5EGDNG6.js} +2 -2
- package/dist/{chunk-AGC2V7JS.js → chunk-B6CBN6EF.js} +1 -1
- package/dist/{chunk-KTKIFXIP.js → chunk-B7FDRN7G.js} +1 -1
- package/dist/{chunk-C5HUMAA7.js → chunk-B7WVYLAQ.js} +67 -15
- package/dist/chunk-BGVP5RMT.js +185 -0
- package/dist/{chunk-FVLNNNH6.js → chunk-BNI4LIU5.js} +35 -4
- package/dist/{chunk-P4XLIPB7.js → chunk-BU5JRLZT.js} +1 -1
- package/dist/{chunk-PHUHIVD5.js → chunk-C6U7XWOC.js} +34 -5
- package/dist/chunk-C7GHNJPW.js +8 -0
- package/dist/{chunk-4AR5XKCH.js → chunk-CL2MU3OX.js} +1 -1
- package/dist/{chunk-ZPP63JSQ.js → chunk-CL3AB7Z2.js} +1 -1
- package/dist/{chunk-FHIFTSPL.js → chunk-CP7XUSOF.js} +8 -3
- package/dist/{chunk-3OX34OC3.js → chunk-CUDNRHHT.js} +30 -10
- package/dist/chunk-CYUUNIEL.js +21 -0
- package/dist/{chunk-P2UY34XE.js → chunk-D4C57S2H.js} +5 -5
- package/dist/{chunk-XSUALA32.js → chunk-DDYFNBYV.js} +3 -3
- package/dist/{chunk-LSNBTGW4.js → chunk-DPYZW42H.js} +1 -1
- package/dist/{chunk-FMIYFHLE.js → chunk-E2PRXEZK.js} +9 -3
- package/dist/{chunk-L63V25HS.js → chunk-E54SPUYJ.js} +3 -3
- package/dist/{chunk-DMUOTCDN.js → chunk-E5CKRHIZ.js} +1 -1
- package/dist/{chunk-BV2IET6B.js → chunk-EVMOKRBC.js} +15 -2
- package/dist/{chunk-YLFCH3VA.js → chunk-FDWI3T6I.js} +133 -31
- package/dist/{chunk-PWC3C5ME.js → chunk-FNJTCJGY.js} +4 -4
- package/dist/{chunk-TNS63ATU.js → chunk-FWRPDSO3.js} +350 -50
- package/dist/{chunk-JA3Y5TV6.js → chunk-GDLB6BPM.js} +141 -74
- package/dist/{chunk-IGJMS7C5.js → chunk-GOVGG7HU.js} +2 -2
- package/dist/{chunk-MB47DRF5.js → chunk-GP7CTIUK.js} +2 -2
- package/dist/{chunk-L5EPGGDP.js → chunk-GWAN2BDB.js} +4 -4
- package/dist/{chunk-YRQ26MNP.js → chunk-H3YF7X4J.js} +12 -5
- package/dist/{chunk-VLBHZ6TF.js → chunk-H6PLIFUE.js} +2 -2
- package/dist/{chunk-GSLFH4AI.js → chunk-HRNM3N4Y.js} +6 -15
- package/dist/chunk-IG2ISRTF.js +48 -0
- package/dist/{chunk-MWEIBVBS.js → chunk-IJCWBYA5.js} +14 -1
- package/dist/{chunk-NKEFYE4R.js → chunk-IPTUKX3N.js} +1 -1
- package/dist/{chunk-RB3J2WYQ.js → chunk-IVE7H6KA.js} +1 -1
- package/dist/{chunk-HA63SH5W.js → chunk-IWY2OGQG.js} +1 -1
- package/dist/{chunk-BQEZPRUC.js → chunk-IZWMWJZT.js} +64 -46
- package/dist/{chunk-AHESD3KW.js → chunk-J4UVI652.js} +1 -1
- package/dist/{chunk-PLII76NJ.js → chunk-JBUCA4OX.js} +33 -6
- package/dist/{chunk-2PZTMSMG.js → chunk-JOF4HQDF.js} +5 -5
- package/dist/{chunk-OBBO6EJR.js → chunk-JQN3QGSF.js} +1 -1
- package/dist/{chunk-P4PIZOL2.js → chunk-JWYIH5DR.js} +3 -3
- package/dist/{chunk-3XY57PKT.js → chunk-KZLKE7I3.js} +210 -48
- package/dist/{chunk-DYVLEMKS.js → chunk-L3GXMYBK.js} +1 -1
- package/dist/{chunk-72V3S44T.js → chunk-LUA4SLJY.js} +46 -19
- package/dist/{chunk-ZT4LFTUJ.js → chunk-LW3JCBXP.js} +20 -3
- package/dist/{chunk-YLB365YX.js → chunk-M5XHN4OM.js} +3 -3
- package/dist/{chunk-VHHX4UCP.js → chunk-MDTWARSF.js} +22 -93
- package/dist/{chunk-JFJP6ATJ.js → chunk-MWKXQSF6.js} +3 -2
- package/dist/{chunk-VJEJ7JNB.js → chunk-N32DVIKY.js} +1 -1
- package/dist/{chunk-MUCNYSJP.js → chunk-NRLJOV7M.js} +32 -4
- package/dist/{chunk-PL4CFSQ7.js → chunk-NSXJOSH7.js} +1 -1
- package/dist/{chunk-NF5FOSLD.js → chunk-NVTG66CK.js} +1 -1
- package/dist/chunk-O45GBJEH.js +1500 -0
- package/dist/{chunk-4AAMFWJT.js → chunk-OHZSTZXN.js} +4 -2
- package/dist/chunk-OSIS7ZQW.js +344 -0
- package/dist/{chunk-E3ZM32LF.js → chunk-P22VFGFK.js} +14 -5
- package/dist/{chunk-KILR4FBR.js → chunk-PAFLR7WD.js} +3 -3
- package/dist/{chunk-R3CZWFFC.js → chunk-PEYQ6MWO.js} +15 -3
- package/dist/{chunk-RDWIKDMY.js → chunk-PZR54BEI.js} +8 -20
- package/dist/{chunk-2FKU4ZEO.js → chunk-QDBKMEYY.js} +9 -9
- package/dist/{chunk-7UHZBVP3.js → chunk-QDVLVLXP.js} +97 -31
- package/dist/{chunk-PRDDPQLC.js → chunk-QLXEQRHD.js} +5 -5
- package/dist/{chunk-MUAWCFW6.js → chunk-QNPDXV5V.js} +5 -2
- package/dist/{chunk-LR5O2XWZ.js → chunk-QUZYLDNJ.js} +5 -5
- package/dist/chunk-RBQUXHYK.js +48 -0
- package/dist/{chunk-3SUPEE6H.js → chunk-RDMBSHGG.js} +3 -3
- package/dist/{chunk-I5XIW2BP.js → chunk-RGUM474V.js} +7 -7
- package/dist/{chunk-OAFENIHN.js → chunk-RJ67XAJU.js} +76 -28
- package/dist/{chunk-LFUBEHFW.js → chunk-RT57D7C4.js} +39 -1
- package/dist/{chunk-MKM6D7JM.js → chunk-RWG7DLOG.js} +42 -5
- package/dist/{chunk-P3UX5EH4.js → chunk-RWKAHHIX.js} +3 -3
- package/dist/{chunk-XEBKIWI7.js → chunk-RYNT3HB7.js} +12 -12
- package/dist/{chunk-EUGEFBGV.js → chunk-S2V272KJ.js} +3 -3
- package/dist/{chunk-W6BZ4UGA.js → chunk-SGYEM2PD.js} +1 -1
- package/dist/{chunk-VROK4X2L.js → chunk-SJP57MQM.js} +1 -1
- package/dist/{chunk-5MVKTNZP.js → chunk-SL4JWAMQ.js} +54 -4
- package/dist/{chunk-MOANZQXF.js → chunk-SLMKLEU6.js} +1 -1
- package/dist/{chunk-RZN2LEAK.js → chunk-SQA7XCYI.js} +3 -3
- package/dist/{chunk-4YNA3CM6.js → chunk-SSFS4NCH.js} +262 -56
- package/dist/{chunk-6FIC57PH.js → chunk-T4P2G3PO.js} +1 -1
- package/dist/{chunk-5U2IJBFV.js → chunk-TA3B4N24.js} +1 -1
- package/dist/{chunk-ZOTTYCP5.js → chunk-TEWWBVQP.js} +40 -4
- package/dist/{chunk-SDYWZRPN.js → chunk-TLSFGXXE.js} +1 -1
- package/dist/{chunk-L3VUW5M4.js → chunk-TQS2YHLB.js} +1 -1
- package/dist/{chunk-KXYKSLW4.js → chunk-TU4M757W.js} +399 -116
- package/dist/{chunk-MC4IM2CC.js → chunk-TYD4F4XU.js} +6 -6
- package/dist/{chunk-TKS5GMYA.js → chunk-U44Y7RC7.js} +2 -0
- package/dist/{chunk-B7AMNZTC.js → chunk-UKBPEEEQ.js} +2 -2
- package/dist/{chunk-ZBPZDADW.js → chunk-UWVXQEQQ.js} +467 -78
- package/dist/{chunk-P2VVMGMI.js → chunk-V4WH3FV7.js} +35 -16
- package/dist/{worktree-ownership-guard-O6JQF2CL.js → chunk-V6F4I2XY.js} +13 -14
- package/dist/{chunk-JFTUAV4J.js → chunk-VBKUESIW.js} +1 -1
- package/dist/{chunk-AZSTIAVL.js → chunk-VBPYYJPC.js} +1 -1
- package/dist/{chunk-LKTN22L6.js → chunk-VIP4LKLO.js} +57 -24
- package/dist/{chunk-L5CGXEZC.js → chunk-WAAGVFFN.js} +63 -71
- package/dist/{chunk-D3LXVAIQ.js → chunk-WC7AE3PT.js} +244 -103
- package/dist/{chunk-EOTZSAQL.js → chunk-WUCUDHKR.js} +1 -1
- package/dist/{chunk-VF3BZOZK.js → chunk-X7HZHKY4.js} +50 -3
- package/dist/{chunk-LFBLQE2P.js → chunk-XCAKYVKI.js} +1 -1
- package/dist/{chunk-UXN4BICC.js → chunk-XMX5J52Z.js} +1 -1
- package/dist/{chunk-6YDY6ZTD.js → chunk-XTCXGWTV.js} +9 -4
- package/dist/{chunk-XUZK23V7.js → chunk-XW2FW6LS.js} +2 -2
- package/dist/{chunk-CH7BUBP3.js → chunk-XWVHUDEK.js} +5 -4
- package/dist/{chunk-S26CV7BE.js → chunk-YA5LK6KB.js} +45 -8
- package/dist/{chunk-ELCRJGCH.js → chunk-YMRQIENV.js} +2 -2
- package/dist/{chunk-QNZ4562V.js → chunk-Z3LXWCB7.js} +1 -1
- package/dist/{chunk-EYMFVITZ.js → chunk-Z7I6NQMA.js} +2 -2
- package/dist/{chunk-6BJCDWSM.js → chunk-ZB4FS2EG.js} +1 -1
- package/dist/{chunk-ZAEUEDTN.js → chunk-ZD6FZ3WL.js} +1 -1
- package/dist/{chunk-FEU6QEEL.js → chunk-ZM6EZAVY.js} +1 -0
- package/dist/{chunk-2KY7WZZF.js → chunk-ZPA4R2UI.js} +1 -1
- package/dist/chunk-ZPPPHVSU.js +285 -0
- package/dist/{chunk-ELEGH4DX.js → chunk-ZUGQPYUY.js} +51 -4
- package/dist/{chunk-UX2EO653.js → chunk-ZZWFZ46U.js} +66 -8
- package/dist/{cleanup-graph-garbage-VQAJ2RBT.js → cleanup-graph-garbage-HFUD5ZQT.js} +12 -10
- package/dist/{cloud-sync-command-J7NEFUY7.js → cloud-sync-command-3JHJJKPN.js} +25 -3
- package/dist/{cloud-sync-db-init-KWEMNMGA.js → cloud-sync-db-init-2LHIQOSF.js} +1 -1
- package/dist/{cloud-sync-shutdown-P63AUTUI.js → cloud-sync-shutdown-ZXAXA3Z7.js} +6 -4
- package/dist/{cloud-sync-state-QF3TMMJ4.js → cloud-sync-state-VSQFGPP6.js} +1 -1
- package/dist/{co-activation-7IHRPBZN.js → co-activation-NX4EH4AP.js} +6 -4
- package/dist/{co-occurrence-R5AG4OAU.js → co-occurrence-5LBYCW3U.js} +10 -8
- package/dist/{consent-passphrase-5ZBDJ5G7.js → consent-passphrase-65W3DJIJ.js} +2 -2
- package/dist/{content-extractor-ZLXC6GNR.js → content-extractor-OGXETBOX.js} +2 -2
- package/dist/{conversation-entity-extractor-7FRKLLBD.js → conversation-entity-extractor-FQCOAH4F.js} +5 -4
- package/dist/conversations-retention-XTBFJBUM.js +166 -0
- package/dist/{crdt-sync-IFRZOEIS.js → crdt-sync-7TDUBTLH.js} +4 -4
- package/dist/{crm-webhook-L2S63ASV.js → crm-webhook-776JTY55.js} +2 -2
- package/dist/{cto-delegation-gate-3WU7OHX7.js → cto-delegation-gate-ZNUWVYCN.js} +25 -23
- package/dist/{daemon-orchestration-3VQYKLOR.js → daemon-orchestration-76BOFDEP.js} +30 -24
- package/dist/{doc-graph-extractor-YIR5E7OF.js → doc-graph-extractor-F724KZJX.js} +10 -8
- package/dist/{documents-QEHPTYIY.js → documents-Q24V4GRD.js} +13 -11
- package/dist/{dreaming-LNFE7DTN.js → dreaming-IY6NQTSP.js} +27 -25
- package/dist/{employee-boot-brief-ZTXDYNWM.js → employee-boot-brief-CCTXO5DQ.js} +35 -29
- package/dist/{entity-boost-R5N6JWXD.js → entity-boost-XGOB7HDM.js} +1 -1
- package/dist/{exe-drift-WYSJVZFO.js → exe-drift-NEPXLNGP.js} +7 -5
- package/dist/{exe-export-GOLNAEAV.js → exe-export-ZHJQFKR2.js} +14 -12
- package/dist/{exe-import-U4BSI6OH.js → exe-import-7SVNI4FW.js} +14 -12
- package/dist/{exe-key-V42G3NGO.js → exe-key-QJKU56WZ.js} +119 -108
- package/dist/{exe-org-U4GLQ6OP.js → exe-org-SJXHLV6K.js} +76 -51
- package/dist/{exe-snapshot-U7ONBYNY.js → exe-snapshot-EFWPXTXE.js} +45 -36
- package/dist/{fast-db-init-W3NGKAEU.js → fast-db-init-BQZOQNT3.js} +1 -1
- package/dist/{founder-context-ZSIBRVDM.js → founder-context-KG2ZAOXY.js} +2 -2
- package/dist/{founder-monitor-envelope-L3IWZLFY.js → founder-monitor-envelope-NYB5LLU4.js} +4 -4
- package/dist/gateway/index.js +20 -18
- package/dist/{gateway-client-DJFCVIPT.js → gateway-client-ODMUXUL3.js} +1 -1
- package/dist/{git-staleness-OF24QXFZ.js → git-staleness-OPGEAJA7.js} +6 -4
- package/dist/{global-procedures-WSIOWO43.js → global-procedures-3KXY4LL6.js} +8 -6
- package/dist/{graph-auto-extract-IEVVN3L3.js → graph-auto-extract-I7H2F5NJ.js} +10 -8
- package/dist/{graph-query-JDZWAVCU.js → graph-query-E7JBSNY5.js} +1 -1
- package/dist/{graph-rag-BKAD2XQA.js → graph-rag-H24CMFP2.js} +5 -4
- package/dist/{hook-integrity-TPWDGX2M.js → hook-integrity-PGQ3OX4S.js} +1 -1
- package/dist/{hook-prefetch-FD6VGJJA.js → hook-prefetch-7AZJER6Z.js} +23 -21
- package/dist/hook-registration-liveness-NGQRFKAA.js +11 -0
- package/dist/hooks/bug-report-worker.js +26 -24
- package/dist/hooks/codex-stop-task-finalizer.js +28 -26
- package/dist/hooks/commit-complete.js +26 -24
- package/dist/hooks/error-recall.js +16 -14
- package/dist/hooks/exe-heartbeat-hook.js +9 -7
- package/dist/hooks/ingest-worker.js +12 -10
- package/dist/hooks/ingest.js +22 -17
- package/dist/hooks/instructions-loaded.js +11 -9
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +10 -8
- package/dist/hooks/post-compact.js +9 -7
- package/dist/hooks/post-tool-combined.js +14 -14
- package/dist/hooks/pre-compact.js +26 -24
- package/dist/hooks/pre-tool-use.js +2 -2
- package/dist/hooks/prompt-submit.js +47 -41
- package/dist/hooks/session-end.js +38 -42
- package/dist/hooks/session-start.js +26 -18
- package/dist/hooks/stop.js +29 -27
- package/dist/hooks/subagent-stop.js +29 -27
- package/dist/hooks/summary-worker.js +32 -30
- package/dist/index.js +37 -35
- package/dist/{installer-OTI5SA43.js → installer-2ZS3LHCU.js} +12 -9
- package/dist/{installer-NMOBSEZ2.js → installer-NH3A3HWC.js} +14 -9
- package/dist/{installer-SRJGE4GU.js → installer-UJ73PGRD.js} +21 -9
- package/dist/{intercom-queue-4FFXCBHO.js → intercom-queue-OE25GJF2.js} +21 -19
- package/dist/lib/agent-config.js +2 -2
- package/dist/lib/cloud-sync.js +24 -18
- package/dist/lib/consolidation.js +13 -11
- package/dist/lib/crypto.js +3 -3
- package/dist/lib/database.js +6 -4
- package/dist/lib/db-backup.js +1 -1
- package/dist/lib/db-write-worker-client.js +3 -1
- package/dist/lib/db-write-worker.js +65 -89
- package/dist/lib/db.js +6 -4
- package/dist/lib/delivery-truth.js +5 -1
- package/dist/lib/device-registry.js +1 -1
- package/dist/lib/embedder.js +2 -2
- package/dist/lib/employee-templates.js +9 -7
- package/dist/lib/employees.js +6 -4
- package/dist/lib/exe-daemon.js +425 -314
- package/dist/lib/file-grep.js +15 -21
- package/dist/lib/hybrid-search.js +19 -13
- package/dist/lib/identity-templates.js +1 -1
- package/dist/lib/identity.js +6 -4
- package/dist/lib/keychain.js +1 -1
- package/dist/lib/license.js +22 -6
- package/dist/lib/messaging.js +22 -20
- package/dist/lib/post-tool-memory.js +10 -8
- package/dist/lib/registry-proxy.js +1 -1
- package/dist/lib/reminders.js +7 -5
- package/dist/lib/runtime-table.js +1 -1
- package/dist/lib/schedules.js +12 -10
- package/dist/lib/session-registry.js +8 -6
- package/dist/lib/skill-learning.js +13 -11
- package/dist/lib/status-brief.js +2 -2
- package/dist/lib/store.js +17 -9
- package/dist/lib/task-router.js +7 -5
- package/dist/lib/tasks.js +23 -21
- package/dist/lib/tmux-routing.js +21 -19
- package/dist/lib/tmux-status.js +4 -1
- package/dist/lib/token-spend.js +7 -5
- package/dist/lib/ws-client.js +1 -1
- package/dist/{license-gate-R4JWTWM4.js → license-gate-XQ2KN5I5.js} +3 -3
- package/dist/{license-recheck-3MBJ4IJX.js → license-recheck-IIBKR2V5.js} +1 -1
- package/dist/{license-sync-BVSG3WMW.js → license-sync-76JVI2M5.js} +151 -8
- package/dist/{local-secret-inventory-JWNBHZZ4.js → local-secret-inventory-MJT7NJLY.js} +2 -2
- package/dist/{mailbox-inbox-MTAT4MXX.js → mailbox-inbox-BPZP4GIZ.js} +1 -1
- package/dist/maintenance-worker-54VZTFIK.js +371 -0
- package/dist/mcp/register-tools.js +110 -107
- package/dist/mcp/server.js +113 -110
- package/dist/mcp/tools/complete-reminder.js +8 -6
- package/dist/mcp/tools/create-reminder.js +8 -6
- package/dist/mcp/tools/create-task.js +26 -24
- package/dist/mcp/tools/deactivate-behavior.js +15 -13
- package/dist/mcp/tools/list-reminders.js +8 -6
- package/dist/mcp/tools/list-tasks.js +26 -24
- package/dist/mcp/tools/send-message.js +25 -23
- package/dist/mcp/tools/update-task.js +24 -22
- package/dist/{mcp-child-registry-TIZERM7O.js → mcp-child-registry-5VUG5ATZ.js} +1 -1
- package/dist/{mcp-http-config-VUMSFWFH.js → mcp-http-config-SKXESWHA.js} +9 -7
- package/dist/{mcp-session-identity-DZHWEJRV.js → mcp-session-identity-HYMLJXXA.js} +21 -19
- package/dist/{memory-cards-JVXTMSAT.js → memory-cards-PAZDUQ3B.js} +16 -32
- package/dist/{memory-graph-extractor-PPBJMYFJ.js → memory-graph-extractor-BC6QKBY6.js} +11 -9
- package/dist/{memory-poisoning-defense-K5Q74XQ4.js → memory-poisoning-defense-BRGIHF6J.js} +9 -7
- package/dist/{memory-pressure-watchdog-YMJH5CSA.js → memory-pressure-watchdog-HR6TPOMT.js} +22 -20
- package/dist/{memory-reflection-7BZWIHRE.js → memory-reflection-LO7VFBL3.js} +38 -59
- package/dist/memory-write-repository-J25USX63.js +16 -0
- package/dist/{monitor-grants-Q7X3MYQP.js → monitor-grants-YQTO6NWR.js} +3 -3
- package/dist/{monitor-identity-ZQB4IXEE.js → monitor-identity-POVTG76Y.js} +2 -2
- package/dist/{notification-ledger-XRAXL5OI.js → notification-ledger-KLD6GZTG.js} +22 -20
- package/dist/{notifications-JE3PX55X.js → notifications-3R6HNHDR.js} +21 -19
- package/dist/{orchestration-events-JCQ5F5IJ.js → orchestration-events-CHY3GOJD.js} +7 -5
- package/dist/{orchestrator-X65INDTR.js → orchestrator-GAL3HOAD.js} +23 -21
- package/dist/{org-brief-session-3PQ22UIX.js → org-brief-session-LQ2DBYVF.js} +17 -15
- package/dist/{org-consent-policy-VGELJ7K7.js → org-consent-policy-KFUP7QPG.js} +5 -5
- package/dist/{org-join-6TO5ZYUZ.js → org-join-MXZCIQWX.js} +8 -8
- package/dist/{org-key-admin-XHYVRRAZ.js → org-key-admin-4UK6QUT7.js} +9 -9
- package/dist/{org-key-pin-TTTYGTQL.js → org-key-pin-ZOX4OM3O.js} +3 -3
- package/dist/{org-membership-Y7T2BS57.js → org-membership-D5TPN45F.js} +4 -4
- package/dist/{org-partner-config-OP4NEUNF.js → org-partner-config-3FXSYPQA.js} +7 -7
- package/dist/{org-partner-runtime-YNVUNT2R.js → org-partner-runtime-TH3LMPII.js} +20 -16
- package/dist/{org-row-visibility-JBBXNYXD.js → org-row-visibility-Y5R33C5U.js} +1 -1
- package/dist/{pipeline-router-EIBB7QNG.js → pipeline-router-E2JZLDK6.js} +7 -5
- package/dist/{plan-limits-MEWXYLEN.js → plan-limits-NYI6R4IK.js} +9 -7
- package/dist/{platform-procedures-Z7ZGBJWN.js → platform-procedures-RTD5E3BD.js} +5 -3
- package/dist/{pre-tool-use-impl-C6KJY22W.js → pre-tool-use-impl-LT4FRXBP.js} +127 -83
- package/dist/{project-boot-VEYFCCSL.js → project-boot-LIMXQKAF.js} +25 -17
- package/dist/{project-name-UYORSNGE.js → project-name-V62GWALX.js} +7 -5
- package/dist/{projection-worker-WYFDWS57.js → projection-worker-KLE3IZQ2.js} +5 -2
- package/dist/{prospective-memory-E6XBQFJA.js → prospective-memory-HRN7JFT2.js} +6 -4
- package/dist/{push-notifications-M7YU63FY.js → push-notifications-XC6EQEXF.js} +22 -20
- package/dist/{reproject-worktree-memories-B5XMGJSD.js → reproject-worktree-memories-4CAVZSFE.js} +21 -19
- package/dist/{reranker-SRA4BR7N.js → reranker-2RU6IIX2.js} +1 -1
- package/dist/{retrieval-health-P2JPZUPV.js → retrieval-health-4Q5TOV2S.js} +1 -1
- package/dist/{review-polling-KQPN7IQE.js → review-polling-WOWIMBCL.js} +22 -20
- package/dist/runtime/index.js +30 -28
- package/dist/{session-events-6LDDKKY3.js → session-events-OPMS2C5F.js} +22 -20
- package/dist/{session-kill-telemetry-5DSRGWCL.js → session-kill-telemetry-3GWATSIX.js} +22 -20
- package/dist/{session-scope-XG5R3P3D.js → session-scope-TIRGR4MH.js} +21 -19
- package/dist/{setup-wizard-7QFLZIKT.js → setup-wizard-B2JBVGNH.js} +3 -3
- package/dist/{shard-manager-HHHHV2KZ.js → shard-manager-C7LYX6E4.js} +5 -1
- package/dist/{skill-refinement-I5RMAHSC.js → skill-refinement-FWQFYNBI.js} +7 -5
- package/dist/skill-retrieval-PPLFC5RY.js +166 -0
- package/dist/skill-usage-NDB67PU2.js +25 -0
- package/dist/{stack-generate-manifest-AKMZ3AUX.js → stack-generate-manifest-AXI55MEQ.js} +6 -6
- package/dist/{stack-promote-34CE2HHF.js → stack-promote-6N23OGZY.js} +6 -6
- package/dist/stack-release-2CXFYVWM.js +2770 -0
- package/dist/{stack-update-ZQQ5YYPJ.js → stack-update-U53FBVCQ.js} +13 -5
- package/dist/{steward-gate-AQJ6KT2C.js → steward-gate-TUA3QCN7.js} +7 -5
- package/dist/{subagent-brief-injection-3GU7YAWY.js → subagent-brief-injection-N52RWYFZ.js} +34 -32
- package/dist/{subagent-spawn-ledger-52I66CLW.js → subagent-spawn-ledger-OI23OT4I.js} +1 -1
- package/dist/support-bug-actions-XWCJI6S5.js +30 -0
- package/dist/support-inbox-MMVTCXRX.js +77 -0
- package/dist/{support-outbox-KNI3FR3G.js → support-outbox-2TG2UV6J.js} +2 -2
- package/dist/{task-enforcement-CAA6D2MH.js → task-enforcement-JDMBAAOY.js} +22 -20
- package/dist/{task-request-inbox-QBZVU66E.js → task-request-inbox-PKJCYKJV.js} +9 -5
- package/dist/{task-scope-NHXB4ZRN.js → task-scope-32HQEV6W.js} +27 -19
- package/dist/{tasks-crud-JHSWL5NM.js → tasks-crud-CYKUKS3F.js} +21 -19
- package/dist/{tasks-notify-7PBE4U7T.js → tasks-notify-YND5MKWJ.js} +22 -20
- package/dist/{tasks-review-PPPVEE62.js → tasks-review-CG2A5GM3.js} +23 -19
- package/dist/{telemetry-upload-YU2VRNHX.js → telemetry-upload-WLUMEFAB.js} +11 -9
- package/dist/{token-budget-QCMWKG3K.js → token-budget-3YHAY5LL.js} +7 -5
- package/dist/{tool-capability-index-7AWQAJRP.js → tool-capability-index-2ADOWH43.js} +2 -2
- package/dist/{tool-gates-5Q3RSQEG.js → tool-gates-7ENOOYMR.js} +5 -3
- package/dist/{tool-gates-QD26D2NE.js → tool-gates-EKJTLVGN.js} +1 -1
- package/dist/{tool-telemetry-LTOIC64I.js → tool-telemetry-VUP7S6HC.js} +1 -1
- package/dist/tui/App.js +59 -35
- package/dist/{tui-data-NDBXD6U7.js → tui-data-BOQTOW2G.js} +25 -23
- package/dist/{update-support-BMJM4NG7.js → update-support-WT3S3AQ4.js} +21 -21
- package/dist/{whatsapp-QQLDAHUX.js → whatsapp-OZGIF2EM.js} +2 -2
- package/dist/{wiki-acl-W6WDCVCP.js → wiki-acl-LTEQJVDA.js} +1 -1
- package/dist/{work-claims-6DM32MGO.js → work-claims-CMV2GWIW.js} +8 -6
- package/dist/{work-state-heartbeat-HG7D4AWJ.js → work-state-heartbeat-S45TXK4M.js} +1 -1
- package/dist/{worker-gate-FTMW7GBB.js → worker-gate-7Z6NMM3G.js} +1 -1
- package/dist/{workflow-engine-ZWC6CCXE.js → workflow-engine-ZSQDXZQ7.js} +2 -2
- package/dist/{worktree-EMQCPGFE.js → worktree-NMWDVT6N.js} +8 -6
- package/dist/worktree-ownership-guard-ZVIWJAOP.js +35 -0
- package/dist/{worktree-sweep-XVUJZBXV.js → worktree-sweep-Q62ZIT3U.js} +9 -7
- package/package.json +14 -8
- package/release-notes.json +7 -41
- package/src/commands/exe/afk.md +6 -0
- package/src/commands/exe/call.md +6 -0
- package/src/commands/exe/employee-heartbeat.md +6 -0
- package/src/commands/exe/intercom.md +6 -0
- package/src/commands/exe/kill.md +6 -0
- package/src/commands/exe/launch.md +6 -0
- package/src/commands/exe/logs.md +1 -1
- package/src/commands/exe/sessions.md +6 -0
- package/src/commands/exe/setup.md +7 -3
- package/src/commands/exe/status.md +6 -0
- package/src/commands/exe-reference.md +25 -36
- package/src/commands/exe.md +19 -28
- package/stack.release.json +11 -11
- package/dist/chunk-3B6FLNLV.js +0 -30
- package/dist/chunk-FXU7JOXK.js +0 -6
- package/dist/chunk-QFAFKEB5.js +0 -820
- package/dist/conversations-retention-C4BLKFEL.js +0 -54
- package/dist/stack-release-5PGWV5D4.js +0 -1607
- package/dist/support-inbox-NY5TB3FN.js +0 -51
- /package/dist/{chunk-U2D6PJ5T.js → chunk-65G7ZTGE.js} +0 -0
- /package/dist/{chunk-DN3ZR3UE.js → chunk-CP7Z35MJ.js} +0 -0
- /package/dist/{chunk-XDVOGDBC.js → chunk-KBBWALJV.js} +0 -0
- /package/dist/{chunk-JRIH6I7D.js → chunk-LZXYPTY4.js} +0 -0
- /package/dist/{chunk-GIRD7O5X.js → chunk-MCWEW2SG.js} +0 -0
- /package/dist/{chunk-TKAU7IJ2.js → chunk-MQHUJLCA.js} +0 -0
- /package/dist/{chunk-M4XY4JRK.js → chunk-O53LTOXK.js} +0 -0
- /package/dist/{chunk-DZN7SDY2.js → chunk-OSVTH3SG.js} +0 -0
- /package/dist/{chunk-AJK2PVQW.js → chunk-QXXEXGX3.js} +0 -0
- /package/dist/{chunk-AWWDKF22.js → chunk-SFWNTO2F.js} +0 -0
- /package/dist/{chunk-2Q2CD6CD.js → chunk-WC5HF2FN.js} +0 -0
- /package/dist/{core-memory-O6D5UC3T.js → core-memory-MMMW3TF3.js} +0 -0
- /package/dist/{memory-isolation-EBFFC6HF.js → memory-isolation-67ACKQ2M.js} +0 -0
- /package/dist/{oauth-server-ZKBUHDFI.js → oauth-server-OILIKR7D.js} +0 -0
|
@@ -0,0 +1,1500 @@
|
|
|
1
|
+
import {
|
|
2
|
+
persistSupportArtifact,
|
|
3
|
+
supportClusterClearableSchema,
|
|
4
|
+
supportClusterSchema,
|
|
5
|
+
supportCommitSchema,
|
|
6
|
+
supportIdSchema,
|
|
7
|
+
supportProductSchema,
|
|
8
|
+
supportTaskIdSchema,
|
|
9
|
+
supportVersionSchema
|
|
10
|
+
} from "./chunk-BNI4LIU5.js";
|
|
11
|
+
import {
|
|
12
|
+
queueSupportStatusUpdateSync
|
|
13
|
+
} from "./chunk-X7HZHKY4.js";
|
|
14
|
+
import {
|
|
15
|
+
assessAttribution,
|
|
16
|
+
assessDisposition,
|
|
17
|
+
buildVerificationArtifact,
|
|
18
|
+
isPlaceholderIdentity,
|
|
19
|
+
isUnresolvedIdentity
|
|
20
|
+
} from "./chunk-SSFS4NCH.js";
|
|
21
|
+
import {
|
|
22
|
+
SUPPORT_REPORT_SENT_MARKER
|
|
23
|
+
} from "./chunk-MMRUBN3I.js";
|
|
24
|
+
import {
|
|
25
|
+
resolveSupportAdminToken,
|
|
26
|
+
supportAdminHeaders
|
|
27
|
+
} from "./chunk-2XZ6X3PJ.js";
|
|
28
|
+
import {
|
|
29
|
+
getActiveAgent
|
|
30
|
+
} from "./chunk-B4XJQBQB.js";
|
|
31
|
+
import {
|
|
32
|
+
SUPPORT_PRODUCTS,
|
|
33
|
+
describeUpstreamFailure,
|
|
34
|
+
redactSecrets,
|
|
35
|
+
redactedToolResult,
|
|
36
|
+
sanitizeProductFilter,
|
|
37
|
+
sanitizeUpstreamId,
|
|
38
|
+
sanitizeVersionField,
|
|
39
|
+
sanitizedUpstreamBody
|
|
40
|
+
} from "./chunk-ZUGQPYUY.js";
|
|
41
|
+
import {
|
|
42
|
+
loadLicense,
|
|
43
|
+
readCachedLicenseToken
|
|
44
|
+
} from "./chunk-FWRPDSO3.js";
|
|
45
|
+
import {
|
|
46
|
+
EXE_AI_DIR,
|
|
47
|
+
loadConfig
|
|
48
|
+
} from "./chunk-MMS2HBRT.js";
|
|
49
|
+
|
|
50
|
+
// src/mcp/tools/support-inbox.ts
|
|
51
|
+
import { z as z3 } from "zod";
|
|
52
|
+
import { readFileSync } from "fs";
|
|
53
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
54
|
+
import { join } from "path";
|
|
55
|
+
import { homedir } from "os";
|
|
56
|
+
|
|
57
|
+
// src/mcp/tools/update-bug-report.ts
|
|
58
|
+
import { z as z2 } from "zod";
|
|
59
|
+
import { readdir, readFile } from "fs/promises";
|
|
60
|
+
import path from "path";
|
|
61
|
+
|
|
62
|
+
// src/lib/support-text-limits.ts
|
|
63
|
+
var SUPPORT_PROGRESS_NOTE_MAX = 2e3;
|
|
64
|
+
var SUPPORT_TRIAGE_NOTES_MAX = 12e3;
|
|
65
|
+
var SUPPORT_EVIDENCE_PREFIX = "Evidence: ";
|
|
66
|
+
var SUPPORT_REOPEN_PREFIX = "Reopen requested: ";
|
|
67
|
+
var SUPPORT_EVIDENCE_MAX = SUPPORT_PROGRESS_NOTE_MAX - SUPPORT_EVIDENCE_PREFIX.length;
|
|
68
|
+
var SUPPORT_REOPEN_REASON_MAX = SUPPORT_PROGRESS_NOTE_MAX - SUPPORT_REOPEN_PREFIX.length;
|
|
69
|
+
function upstreamTextLength(text) {
|
|
70
|
+
if (!text) return 0;
|
|
71
|
+
const trimmed = text.trim();
|
|
72
|
+
if (!trimmed) return 0;
|
|
73
|
+
return redactSecrets(trimmed).text.length;
|
|
74
|
+
}
|
|
75
|
+
function checkSupportTextLimits(entries) {
|
|
76
|
+
const oversize = [];
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const length = upstreamTextLength(entry.text);
|
|
79
|
+
if (length > entry.limit) {
|
|
80
|
+
oversize.push({ field: entry.field, length, limit: entry.limit });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return oversize;
|
|
84
|
+
}
|
|
85
|
+
function describeOversizeRefusal(oversize) {
|
|
86
|
+
const lines = oversize.map((o) => ` - ${o.field}: ${o.length} chars (max ${o.limit})`);
|
|
87
|
+
return `Refused: text too long for the support tracker, which truncates silently (bug 43b2a68c).
|
|
88
|
+
` + lines.join("\n") + `
|
|
89
|
+
Nothing was written \u2014 no local update, no memory, no upstream send. Shorten the field(s) above or split the text across multiple calls, then retry.`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/lib/support-verification-input.ts
|
|
93
|
+
import { z } from "zod";
|
|
94
|
+
|
|
95
|
+
// src/lib/attribution-resolve.ts
|
|
96
|
+
import { execFileSync } from "child_process";
|
|
97
|
+
function resolveModelFromEnvironment() {
|
|
98
|
+
const candidates = [
|
|
99
|
+
process.env.EXE_ATTRIBUTION_MODEL,
|
|
100
|
+
process.env.ANTHROPIC_MODEL,
|
|
101
|
+
process.env.CLAUDE_MODEL,
|
|
102
|
+
process.env.EXE_MODEL
|
|
103
|
+
];
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
const value = candidate?.trim();
|
|
106
|
+
if (value && !isPlaceholderIdentity(value)) return value;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function resolveRef(ref, cwd) {
|
|
111
|
+
try {
|
|
112
|
+
const out = execFileSync("git", ["rev-parse", "--verify", `${ref}^{commit}`], {
|
|
113
|
+
cwd,
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
timeout: 1e4
|
|
116
|
+
}).trim();
|
|
117
|
+
return /^[0-9a-f]{40}$/i.test(out) ? out : null;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var MODEL_HELP = "Set ANTHROPIC_MODEL (employee sessions get it automatically at launch) or pass attribution_model explicitly. It is NOT guessed: a wrong model on the record is worse than no record at all, because it points at the wrong suspect.";
|
|
123
|
+
function resolveAttribution(purpose, input = {}) {
|
|
124
|
+
const stated = input.agent?.trim() ?? "";
|
|
125
|
+
let observed = "";
|
|
126
|
+
try {
|
|
127
|
+
observed = getActiveAgent().agentId?.trim() ?? "";
|
|
128
|
+
} catch {
|
|
129
|
+
observed = "";
|
|
130
|
+
}
|
|
131
|
+
let agent;
|
|
132
|
+
if (purpose === "verdict") {
|
|
133
|
+
const observedIsReal = observed !== "" && !isUnresolvedIdentity(observed);
|
|
134
|
+
if (observedIsReal && stated && stated.toLowerCase() !== observed.toLowerCase()) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
code: "attribution_agent_conflict",
|
|
138
|
+
message: `Refused locally [attribution_agent_conflict]: attribution_agent was given as "${stated}", but this session resolves to "${observed}". A verdict is signed by whoever actually made it \u2014 attribution_agent is a fallback for a session that cannot observe its own identity (the MCP daemon), not a way to record the call under another employee's name. Omit attribution_agent to sign as "${observed}", or make the call from that employee's session.`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
agent = observedIsReal ? observed : stated;
|
|
142
|
+
} else {
|
|
143
|
+
agent = stated || observed;
|
|
144
|
+
}
|
|
145
|
+
if (purpose === "verdict" && isUnresolvedIdentity(agent)) {
|
|
146
|
+
agent = "";
|
|
147
|
+
}
|
|
148
|
+
if (!agent) {
|
|
149
|
+
const message = purpose === "verdict" ? "Refused locally [attribution_agent_missing]: could not resolve which agent is producing this verdict, and it will not be invented. The MCP tools run in the launchd daemon, which carries no active-agent marker, no tmux session and no AGENT_ID, so the session identity is not observable from there. Pass attribution_agent with your own employee name exactly as it appears in the roster \u2014 never a role like 'admin'." : `Refused locally [attribution_agent_missing]: could not resolve which agent is producing this ${purpose}, and it will not be invented. Restore the active-agent marker or set AGENT_ID to your real employee name.`;
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
code: "attribution_agent_missing",
|
|
153
|
+
// Every refusal out of this function names its machine-stable code in the message,
|
|
154
|
+
// including the ones below that come back from `assessAttribution`. A caller only
|
|
155
|
+
// ever sees the message, so a code that appears in some refusals and not others is
|
|
156
|
+
// a code the caller cannot rely on.
|
|
157
|
+
message
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const model = input.model?.trim() || resolveModelFromEnvironment();
|
|
161
|
+
if (!model) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
code: "attribution_model_missing",
|
|
165
|
+
message: `Refused locally [attribution_model_missing]: could not resolve which model is producing this record. ${MODEL_HELP}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const observedAt = input.observed_at?.trim() || (/* @__PURE__ */ new Date()).toISOString();
|
|
169
|
+
const attribution = {
|
|
170
|
+
agent,
|
|
171
|
+
model,
|
|
172
|
+
observed_at: observedAt
|
|
173
|
+
};
|
|
174
|
+
if (purpose === "verdict") {
|
|
175
|
+
const repo = input.repo_path?.trim() || process.cwd();
|
|
176
|
+
const requested = input.evidence_ref?.trim() || "HEAD";
|
|
177
|
+
const resolved = resolveRef(requested, repo);
|
|
178
|
+
if (!resolved) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
code: "attribution_evidence_ref_invalid",
|
|
182
|
+
message: `Refused locally [attribution_evidence_ref_invalid]: evidence_ref "${requested}" does not resolve to a commit in ${repo}. A verdict is only meaningful against a ref someone else can check out \u2014 name the commit you actually read; if the repository is wrong, pass repo_path for the repository that contains the commit.`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
attribution.evidence_ref = resolved;
|
|
186
|
+
}
|
|
187
|
+
if (input.second_opinion_model || input.second_opinion_verdict) {
|
|
188
|
+
attribution.second_opinion = {
|
|
189
|
+
model: input.second_opinion_model?.trim() ?? "",
|
|
190
|
+
verdict: input.second_opinion_verdict ?? "confirmed"
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const assessed = assessAttribution(attribution, purpose);
|
|
194
|
+
if (!assessed.ok) {
|
|
195
|
+
return { ok: false, code: assessed.code, message: `Refused locally [${assessed.code}]: ${assessed.message}` };
|
|
196
|
+
}
|
|
197
|
+
return { ok: true, attribution };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/lib/support-verification-input.ts
|
|
201
|
+
function isVerifiedStatus(status) {
|
|
202
|
+
return status === "fixed" || status === "closed";
|
|
203
|
+
}
|
|
204
|
+
var verificationArtifactInputSchema = {
|
|
205
|
+
verification_kind: z.enum(["regression_test", "manual", "ops_transcript"]).optional().describe(
|
|
206
|
+
"REQUIRED to set status=fixed or closed (unless closing with a non-'fixed' disposition). 'regression_test' when you actually RAN a test that fails at the parent commit and passes at the fix. 'manual' when a named person reproduced the original defect and confirmed it is gone. 'ops_transcript' when the defect was settled by running commands against a live system and there is no commit (CI state, a VPS config edit, an ops confirmation)."
|
|
207
|
+
),
|
|
208
|
+
defect_layer: z.enum(["production", "test_harness"]).optional().describe(
|
|
209
|
+
"Where the DEFECT lives. Omit for the normal case (production source). Declare 'test_harness' ONLY when the filed defect IS the test/harness \u2014 a flaky fixture, a leaked singleton, a test that spawns a hook wrong \u2014 so that a production diff would be the WRONG fix. This costs MORE evidence, not less: it additionally requires regression_test_id and reverted_result. It is NOT a way past a refused substance check; if your fix was supposed to change production code and did not, the honest answer is that it is not finished."
|
|
210
|
+
),
|
|
211
|
+
reverted_result: z.enum(["fail", "pass", "error"]).optional().describe(
|
|
212
|
+
"defect_layer=test_harness only: the result you OBSERVED re-running the named test with the fix reverted. Must be 'fail' \u2014 a test-only diff that does not go red when removed evidences nothing."
|
|
213
|
+
),
|
|
214
|
+
claim_type: z.enum(["point_in_time", "ongoing_state"]).optional().describe(
|
|
215
|
+
"ops_transcript only. 'point_in_time' for a fact true when observed (a config now routes correctly). 'ongoing_state' for a claim that a condition HOLDS (an agent is reporting, CI is green) \u2014 that requires at least two probes taken at two distinct times, because one sample cannot evidence a continuing condition."
|
|
216
|
+
),
|
|
217
|
+
ops_probes: z.string().optional().describe(
|
|
218
|
+
`ops_transcript only. JSON array of probes, each {"command","output","host","ran_at"}. VERBATIM: the command as typed, its real output, the host/system it ran against, and an ISO timestamp. Summaries are refused \u2014 'checked the VPS and it looks fine' is not a probe.`
|
|
219
|
+
),
|
|
220
|
+
fix_commit: supportCommitSchema.optional().describe(
|
|
221
|
+
"SHA of the merged commit containing the fix. Its diff is measured locally on EVERY kind that supplies one, ops_transcript included \u2014 a comment-only, test-only, docs-only or lockfile-only commit is refused. Optional only for ops_transcript, which may name no commit at all."
|
|
222
|
+
),
|
|
223
|
+
repo_path: z.string().optional().describe("Absolute path to the repository containing fix_commit. Required when the fix landed outside the current working repo (e.g. exe-gateway, exe-crm). Defaults to the current directory."),
|
|
224
|
+
regression_test_id: z.string().optional().describe("regression_test only: the test that detects the defect, e.g. 'tests/raw-event-backlog.test.ts::counts unprocessed events'."),
|
|
225
|
+
parent_result: z.enum(["fail", "pass", "error"]).optional().describe("regression_test only: the result you OBSERVED running that test at the parent commit. Must be 'fail' \u2014 a test that already passed does not detect the defect."),
|
|
226
|
+
fix_result: z.enum(["fail", "pass", "error"]).optional().describe("regression_test only: the result you OBSERVED running that test at the fix commit. Must be 'pass'."),
|
|
227
|
+
verifier: z.string().optional().describe("manual and ops_transcript: the identity of the person or employee who confirmed the defect is gone. Checked server-side against the bug's filer and assignee \u2014 it may not be either. Placeholders ('me', 'agent') are refused."),
|
|
228
|
+
disposition: z.enum(["fixed", "not_a_defect", "superseded", "wontfix"]).optional().describe(
|
|
229
|
+
"WHY the record is closing. Omit (or 'fixed') for the normal case: a defect was repaired, and a verification artifact is required. Use 'not_a_defect' when there was nothing to fix (a smoke-test record, a misfiled report), 'superseded' when another record covers it, 'wontfix' when it is a deliberate decline. Those three require NO fix_commit but DO require disposition_reason \u2014 and they are recorded as closed, not as fixed, because asserting a repair that never happened is its own kind of false record."
|
|
230
|
+
),
|
|
231
|
+
disposition_reason: z.string().optional().describe(
|
|
232
|
+
"REQUIRED for any disposition other than 'fixed': what makes this record a non-defect. Explain the call \u2014 a bare label or a hedge is refused."
|
|
233
|
+
),
|
|
234
|
+
superseded_by: z.string().optional().describe("REQUIRED for disposition='superseded': the id of the record that replaces this one, so the claim is checkable."),
|
|
235
|
+
superseded_coverage: z.string().optional().describe(
|
|
236
|
+
`REQUIRED for disposition='superseded': JSON array of {"id","evidence"} \u2014 one entry per declared symptom, each naming where the superseding record covers it. PARTIAL COVERAGE SUPERSEDES NOTHING: if the other record covers three of five symptoms, the honest outcome is to keep this record open with the two survivors as its remaining scope, not to close it. The refusal names the survivors.`
|
|
237
|
+
),
|
|
238
|
+
symptoms: z.string().optional().describe(
|
|
239
|
+
`JSON array of the distinct defects this record claims, as ids or {"id"} objects. Required to close as 'superseded' so coverage can be checked one by one.`
|
|
240
|
+
),
|
|
241
|
+
observed_at: z.string().optional().describe(
|
|
242
|
+
"ISO timestamp of when you actually READ the evidence. Defaults to now. Set it explicitly when the evidence was observed earlier than this call."
|
|
243
|
+
),
|
|
244
|
+
evidence_ref: z.string().optional().describe(
|
|
245
|
+
"The git ref you read the evidence at (defaults to HEAD of repo_path, resolved locally and refused if it does not exist). main moves several times a day, so a verdict against an unnamed ref cannot be reproduced."
|
|
246
|
+
),
|
|
247
|
+
attribution_agent: z.string().optional().describe(
|
|
248
|
+
"The employee identity producing this verdict \u2014 your own name as it appears in the roster. Normally OMITTED: it is resolved from the session's active-agent marker. Supply it when the call is made through the MCP daemon (which has no marker, no tmux and no AGENT_ID and would otherwise sign the record 'default') or from a harness. It is never guessed, and a role ('admin', 'agent', 'support') is refused \u2014 the whole point is that a verdict can be traced to someone."
|
|
249
|
+
),
|
|
250
|
+
attribution_model: z.string().optional().describe(
|
|
251
|
+
"The model producing this verdict, e.g. 'claude-opus-5'. Normally OMITTED: it is read from ANTHROPIC_MODEL, which employee launches set automatically. Supply it only when the session carries no such variable \u2014 a session launched outside the employee router, or a harness. It is still never guessed: omit it with no environment model and the close is refused, naming the variable."
|
|
252
|
+
),
|
|
253
|
+
second_opinion_model: z.string().optional().describe("Optional adversarial review: the OTHER model that checked this verdict. Must differ from the model producing it."),
|
|
254
|
+
second_opinion_verdict: z.enum(["confirmed", "refuted"]).optional().describe("Optional adversarial review: what the second model concluded. A 'refuted' verdict cannot be closed over."),
|
|
255
|
+
disposition_verifier: z.string().optional().describe(
|
|
256
|
+
"REQUIRED for disposition='not_a_defect' or 'superseded': a named identity, not the record's filer or assignee, who agrees that what was filed is not a defect. Same identity rule as a manual verification."
|
|
257
|
+
),
|
|
258
|
+
verification_method: z.string().optional().describe("manual only \u2014 genuinely manual-only, unlike verification_observation: how it was verified, in enough detail for someone else to repeat it."),
|
|
259
|
+
verification_observation: z.string().optional().describe(
|
|
260
|
+
"REQUIRED for manual AND for ops_transcript (it is listed under both in REQUIRED_ARGUMENTS_BY_KIND, and both run the same assessObservation predicate): what was observed that shows the DEFECT IS ABSENT \u2014 not what the change did, and not what you were doing. 'It is fixed' is refused; so is 'probing whether ...'. On an ops_transcript the observation must additionally report what the probes returned, in the output's own words."
|
|
261
|
+
),
|
|
262
|
+
delivery_proof: z.string().optional().describe('Optional JSON delivery declaration, e.g. {"channel":"npm","version":"0.9.364","tarball_shasum":"\u2026","fix_path_published":true}. Recorded, not required \u2014 delivery does not gate closing. A declaration that is present must still be true.')
|
|
263
|
+
};
|
|
264
|
+
function buildVerdictAttribution(input) {
|
|
265
|
+
return resolveAttribution("verdict", {
|
|
266
|
+
agent: input.attribution_agent,
|
|
267
|
+
model: input.attribution_model,
|
|
268
|
+
observed_at: input.observed_at,
|
|
269
|
+
evidence_ref: input.evidence_ref,
|
|
270
|
+
repo_path: input.repo_path,
|
|
271
|
+
second_opinion_model: input.second_opinion_model,
|
|
272
|
+
second_opinion_verdict: input.second_opinion_verdict
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
var NO_ARTIFACT_HELP = [
|
|
276
|
+
"Refused locally: setting status=fixed or closed requires evidence that the defect is ABSENT.",
|
|
277
|
+
"",
|
|
278
|
+
"Supply ONE of:",
|
|
279
|
+
" regression_test \u2014 verification_kind='regression_test', fix_commit, repo_path,",
|
|
280
|
+
" regression_test_id, parent_result (must be the result you observed at the parent),",
|
|
281
|
+
" fix_result. Use this when you actually ran the test at both commits.",
|
|
282
|
+
" manual \u2014 verification_kind='manual', fix_commit, repo_path, verifier (a real identity,",
|
|
283
|
+
" not the bug's filer or assignee), verification_method, verification_observation",
|
|
284
|
+
" describing what you saw that shows the defect is gone.",
|
|
285
|
+
" ops_transcript \u2014 verification_kind='ops_transcript', verifier, claim_type",
|
|
286
|
+
" ('point_in_time' for a fact true when observed, e.g. a config now routes",
|
|
287
|
+
" correctly; 'ongoing_state' for a claim that a condition HOLDS, e.g. an agent is",
|
|
288
|
+
" reporting or CI is green \u2014 that needs 2+ probes at distinct times, since one",
|
|
289
|
+
" sample cannot evidence a continuing condition), ops_probes (verbatim",
|
|
290
|
+
" {command, output, host, ran_at} entries), and verification_observation \u2014 which",
|
|
291
|
+
" is required for this kind too, and must report what the probes actually returned",
|
|
292
|
+
" in the output's own words. Use this when there is no commit to measure \u2014 the",
|
|
293
|
+
" defect was settled by running commands against a live system (CI state, a VPS",
|
|
294
|
+
" config edit, an ops confirmation). fix_commit stays OPTIONAL here, but a commit",
|
|
295
|
+
" you do name is measured exactly as it is on every other kind.",
|
|
296
|
+
"",
|
|
297
|
+
"The fix commit's diff is measured from the repository; substance is never taken from the caller.",
|
|
298
|
+
"If you have not verified the defect is absent, do not close it \u2014 record what you found with notes instead."
|
|
299
|
+
].join("\n");
|
|
300
|
+
var REQUIRED_ARGUMENTS_BY_KIND = {
|
|
301
|
+
regression_test: ["fix_commit", "regression_test_id", "parent_result", "fix_result"],
|
|
302
|
+
manual: ["fix_commit", "verifier", "verification_method", "verification_observation"],
|
|
303
|
+
ops_transcript: ["verifier", "claim_type", "ops_probes", "verification_observation"]
|
|
304
|
+
};
|
|
305
|
+
function argumentArrived(value) {
|
|
306
|
+
if (value === void 0 || value === null) return false;
|
|
307
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
function assertVerificationArgumentsArrived(kind, input) {
|
|
311
|
+
const missing = REQUIRED_ARGUMENTS_BY_KIND[kind].filter((field) => !argumentArrived(input[field]));
|
|
312
|
+
if (missing.length === 0) return { ok: true };
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
message: [
|
|
316
|
+
`Refused locally [verification_arguments_missing]: this close declared verification_kind='${kind}', but these required parameters did not arrive in the tool call:`,
|
|
317
|
+
...missing.map((field) => ` - ${String(field)}`),
|
|
318
|
+
"",
|
|
319
|
+
"They reached the tool as undefined or empty, so this is NOT a judgement on the evidence you wrote \u2014 that value was never seen here.",
|
|
320
|
+
"If you did pass them, the argument was dropped or truncated between your client and the tool. Resend the call with those parameters present; long evidence survives better as a separate add_comment followed by a close that references it.",
|
|
321
|
+
"",
|
|
322
|
+
"Nothing was written locally and nothing was sent upstream."
|
|
323
|
+
].join("\n")
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
var ATTRIBUTION_REQUIRED_HELP = [
|
|
327
|
+
"Refused locally [attribution_missing]: a close carries WHO made the call, with WHAT model, at WHICH commit \u2014 and it did not resolve here, so it will not be sent unattributed for the server to refuse with an opaque 422.",
|
|
328
|
+
"",
|
|
329
|
+
"Supply the parts that could not be resolved from this session:",
|
|
330
|
+
" attribution_agent \u2014 your real employee name (the MCP daemon has no session identity to read).",
|
|
331
|
+
" attribution_model \u2014 the model producing this verdict, e.g. 'claude-opus-5'.",
|
|
332
|
+
" evidence_ref / repo_path \u2014 the commit you actually read the evidence at, in the repo it lives in.",
|
|
333
|
+
"",
|
|
334
|
+
"None of these are guessed: a verdict signed with the wrong agent or model is a record pointing at the wrong suspect."
|
|
335
|
+
].join("\n");
|
|
336
|
+
function assertVerdictAttribution(status, resolved) {
|
|
337
|
+
if (!isVerifiedStatus(status) || !resolved.ok || resolved.attribution) return { ok: true };
|
|
338
|
+
return { ok: false, message: ATTRIBUTION_REQUIRED_HELP };
|
|
339
|
+
}
|
|
340
|
+
function resolveVerificationArtifact(status, input, context = {}) {
|
|
341
|
+
if (!isVerifiedStatus(status)) return { ok: true };
|
|
342
|
+
const disposition = input.disposition ?? "fixed";
|
|
343
|
+
if (disposition !== "fixed") {
|
|
344
|
+
const assessed = assessDisposition(disposition, {
|
|
345
|
+
status: typeof status === "string" ? status : "closed",
|
|
346
|
+
disposition,
|
|
347
|
+
disposition_reason: input.disposition_reason,
|
|
348
|
+
superseded_by: input.superseded_by,
|
|
349
|
+
disposition_verifier: input.disposition_verifier,
|
|
350
|
+
superseded_coverage: input.superseded_coverage,
|
|
351
|
+
symptoms: input.symptoms,
|
|
352
|
+
context
|
|
353
|
+
});
|
|
354
|
+
if (!assessed.ok) return { ok: false, message: `Refused locally [${assessed.code}]: ${assessed.message}` };
|
|
355
|
+
const dispositionAttribution = buildVerdictAttribution(input);
|
|
356
|
+
if (!dispositionAttribution.ok) return { ok: false, message: dispositionAttribution.message };
|
|
357
|
+
return { ok: true, attribution: dispositionAttribution.attribution };
|
|
358
|
+
}
|
|
359
|
+
if (!input.verification_kind && !input.fix_commit) {
|
|
360
|
+
return { ok: false, message: NO_ARTIFACT_HELP };
|
|
361
|
+
}
|
|
362
|
+
if (!input.verification_kind) {
|
|
363
|
+
return { ok: false, message: "verification_kind is required (regression_test, manual or ops_transcript) when setting status=fixed or closed." };
|
|
364
|
+
}
|
|
365
|
+
if (!input.fix_commit && input.verification_kind !== "ops_transcript") {
|
|
366
|
+
return { ok: false, message: "fix_commit is required when setting status=fixed or closed \u2014 the diff is measured, not described." };
|
|
367
|
+
}
|
|
368
|
+
const arrived = assertVerificationArgumentsArrived(input.verification_kind, input);
|
|
369
|
+
if (!arrived.ok) return { ok: false, message: arrived.message };
|
|
370
|
+
let probes;
|
|
371
|
+
if (input.verification_kind === "ops_transcript") {
|
|
372
|
+
const parsed = parseOpsProbes(input.ops_probes);
|
|
373
|
+
if (!parsed.ok) return { ok: false, message: parsed.message };
|
|
374
|
+
probes = parsed.probes;
|
|
375
|
+
}
|
|
376
|
+
const request = {
|
|
377
|
+
kind: input.verification_kind,
|
|
378
|
+
fix_commit: input.fix_commit ?? "",
|
|
379
|
+
repo_path: input.repo_path?.trim() || process.cwd(),
|
|
380
|
+
test_id: input.regression_test_id,
|
|
381
|
+
parent_result: input.parent_result,
|
|
382
|
+
fix_result: input.fix_result,
|
|
383
|
+
verifier: input.verifier,
|
|
384
|
+
method: input.verification_method,
|
|
385
|
+
observation: input.verification_observation,
|
|
386
|
+
defect_layer: input.defect_layer,
|
|
387
|
+
reverted_result: input.reverted_result,
|
|
388
|
+
claim_type: input.claim_type,
|
|
389
|
+
probes
|
|
390
|
+
};
|
|
391
|
+
const built = buildVerificationArtifact(request, context);
|
|
392
|
+
if (!built.ok) {
|
|
393
|
+
return { ok: false, message: `Refused locally [${built.code}]: ${built.message}` };
|
|
394
|
+
}
|
|
395
|
+
const attribution = buildVerdictAttribution(input);
|
|
396
|
+
if (!attribution.ok) return { ok: false, message: attribution.message };
|
|
397
|
+
return { ok: true, artifact: built.artifact, attribution: attribution.attribution };
|
|
398
|
+
}
|
|
399
|
+
function parseOpsProbes(raw) {
|
|
400
|
+
if (raw === void 0 || !raw.trim()) {
|
|
401
|
+
return {
|
|
402
|
+
ok: false,
|
|
403
|
+
message: "ops_probes is required for verification_kind='ops_transcript': a JSON array of {command, output, host, ran_at}, all verbatim."
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
let parsed;
|
|
407
|
+
try {
|
|
408
|
+
parsed = JSON.parse(raw);
|
|
409
|
+
} catch {
|
|
410
|
+
return { ok: false, message: "ops_probes must be valid JSON: an array of {command, output, host, ran_at}." };
|
|
411
|
+
}
|
|
412
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
413
|
+
return { ok: false, message: "ops_probes must be a non-empty JSON array of {command, output, host, ran_at}." };
|
|
414
|
+
}
|
|
415
|
+
return { ok: true, probes: parsed };
|
|
416
|
+
}
|
|
417
|
+
function parseDeliveryProof(raw) {
|
|
418
|
+
if (raw === void 0 || !raw.trim()) return { ok: true };
|
|
419
|
+
try {
|
|
420
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
421
|
+
} catch {
|
|
422
|
+
return { ok: false, message: "delivery_proof must be valid JSON (or omitted \u2014 delivery does not gate closing)." };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/mcp/tools/update-bug-report.ts
|
|
427
|
+
var STATUS = z2.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
|
|
428
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
429
|
+
var FRONTMATTER_ID_RE = /^id:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\s*$/m;
|
|
430
|
+
async function readReportId(filePath) {
|
|
431
|
+
try {
|
|
432
|
+
const content = await readFile(filePath, "utf-8");
|
|
433
|
+
return content.match(FRONTMATTER_ID_RE)?.[1] ?? null;
|
|
434
|
+
} catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async function findReportFile(id) {
|
|
439
|
+
const dir = path.join(EXE_AI_DIR, "bug-reports");
|
|
440
|
+
let files;
|
|
441
|
+
try {
|
|
442
|
+
files = (await readdir(dir)).filter((f) => f.endsWith(".md"));
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const requested = id.trim().toLowerCase();
|
|
447
|
+
if (UUID_RE.test(requested)) {
|
|
448
|
+
for (const f of files) {
|
|
449
|
+
const filePath = path.join(dir, f);
|
|
450
|
+
const fileId = await readReportId(filePath);
|
|
451
|
+
if (fileId && fileId.toLowerCase() === requested) {
|
|
452
|
+
return { filePath, fullId: fileId };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const prefix = requested.slice(0, 8);
|
|
458
|
+
const suffix = `-${prefix}.md`;
|
|
459
|
+
const candidates = files.filter((f) => f.toLowerCase().endsWith(suffix));
|
|
460
|
+
if (candidates.length === 0) return null;
|
|
461
|
+
for (const f of candidates) {
|
|
462
|
+
const filePath = path.join(dir, f);
|
|
463
|
+
const fileId = await readReportId(filePath);
|
|
464
|
+
if (fileId && fileId.toLowerCase().startsWith(prefix)) {
|
|
465
|
+
return { filePath, fullId: fileId };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (candidates.length === 1 && candidates[0]) {
|
|
469
|
+
const filePath = path.join(dir, candidates[0]);
|
|
470
|
+
const fileId = await readReportId(filePath);
|
|
471
|
+
return { filePath, fullId: fileId ?? requested };
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
async function buildUpdateSection(filePath, opts) {
|
|
476
|
+
let existing = "";
|
|
477
|
+
try {
|
|
478
|
+
existing = await readFile(filePath, "utf-8");
|
|
479
|
+
} catch {
|
|
480
|
+
existing = "";
|
|
481
|
+
}
|
|
482
|
+
const heading = opts.reopen ? "Reopen Request" : "Status Update";
|
|
483
|
+
const updateBlock = [
|
|
484
|
+
"",
|
|
485
|
+
`## ${heading} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
486
|
+
"",
|
|
487
|
+
...opts.status ? [`status: ${opts.status}`] : [],
|
|
488
|
+
...opts.fixVersion ? [`fix_version: ${opts.fixVersion}`] : [],
|
|
489
|
+
...opts.notes ? [``, opts.notes] : [],
|
|
490
|
+
...opts.evidence ? [``, `Evidence: ${opts.evidence}`] : []
|
|
491
|
+
].join("\n");
|
|
492
|
+
const titleMatch = existing.match(/^# Bug Report — (.+)$/m);
|
|
493
|
+
return { title: titleMatch?.[1] ?? "Unknown bug", updateBlock };
|
|
494
|
+
}
|
|
495
|
+
async function collectBugAuthorIdentities(id) {
|
|
496
|
+
const identities = [];
|
|
497
|
+
try {
|
|
498
|
+
const resolved = await findReportFile(id);
|
|
499
|
+
if (resolved) {
|
|
500
|
+
const content = await readFile(resolved.filePath, "utf-8");
|
|
501
|
+
const filedBy = content.match(/^filed_by:\s*(.+)$/m)?.[1]?.trim();
|
|
502
|
+
if (filedBy) identities.push(filedBy);
|
|
503
|
+
}
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
const config = await loadConfig();
|
|
508
|
+
const adminToken2 = resolveSupportAdminToken(config);
|
|
509
|
+
if (adminToken2) {
|
|
510
|
+
const support = config.support;
|
|
511
|
+
const base = (process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT || support?.adminEndpoint || support?.admin_endpoint || "https://api.askexe.com/admin/support/bug-reports").replace(/\/+$/, "");
|
|
512
|
+
const parsed = new URL(`${base}/${encodeURIComponent(sanitizeUpstreamId(id))}`);
|
|
513
|
+
if (parsed.protocol === "https:" || ["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
|
|
514
|
+
const resp = await fetch(parsed, {
|
|
515
|
+
headers: { authorization: `Bearer ${adminToken2}` },
|
|
516
|
+
signal: AbortSignal.timeout(5e3)
|
|
517
|
+
});
|
|
518
|
+
if (resp.ok) {
|
|
519
|
+
const row = await resp.json();
|
|
520
|
+
identities.push(row?.agent_id, row?.license_email);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
return identities;
|
|
527
|
+
}
|
|
528
|
+
async function sendUpstream(payload, isAdmin) {
|
|
529
|
+
const config = await loadConfig();
|
|
530
|
+
const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
|
|
531
|
+
const adminToken2 = resolveSupportAdminToken(config);
|
|
532
|
+
const support = config.support;
|
|
533
|
+
const baseEndpoint = isAdmin && adminToken2 ? process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT || support?.adminEndpoint || support?.admin_endpoint || "https://api.askexe.com/admin/support/bug-reports" : config.support?.bugReportEndpoint || process.env.EXE_BUG_REPORT_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/bug-reports` : "https://api.askexe.com/v1/support/bug-reports");
|
|
534
|
+
const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || config.cloud?.apiKey;
|
|
535
|
+
const licenseToken = readCachedLicenseToken();
|
|
536
|
+
if (!baseEndpoint) return "not_configured";
|
|
537
|
+
try {
|
|
538
|
+
const base = baseEndpoint.replace(/\/+$/, "");
|
|
539
|
+
const url = `${base}/${encodeURIComponent(sanitizeUpstreamId(payload.id))}`;
|
|
540
|
+
const parsed = new URL(url);
|
|
541
|
+
if (parsed.protocol !== "https:" && !["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
|
|
542
|
+
return "failed: insecure endpoint rejected";
|
|
543
|
+
}
|
|
544
|
+
const adminHeaders = isAdmin && adminToken2 ? { authorization: `Bearer ${adminToken2}`, ...supportAdminHeaders(config) } : {};
|
|
545
|
+
const response = await fetch(parsed, {
|
|
546
|
+
method: "PATCH",
|
|
547
|
+
headers: {
|
|
548
|
+
"content-type": "application/json",
|
|
549
|
+
...isAdmin && adminToken2 ? {} : {
|
|
550
|
+
...licenseKey ? { "x-exe-license-key": licenseKey, authorization: `Bearer ${licenseKey}` } : {},
|
|
551
|
+
...licenseToken ? { "x-exe-license-token": licenseToken } : {}
|
|
552
|
+
},
|
|
553
|
+
...adminHeaders
|
|
554
|
+
},
|
|
555
|
+
// Egress redaction gate (dc16fee4) — add_evidence ("logs, repro details,
|
|
556
|
+
// stack traces"), add_note, and triage_notes are the most secret-dense
|
|
557
|
+
// support fields; redact before the network send.
|
|
558
|
+
body: sanitizedUpstreamBody(payload, "update-bug-report"),
|
|
559
|
+
signal: AbortSignal.timeout(1e4)
|
|
560
|
+
});
|
|
561
|
+
if (!response.ok) return `failed: ${await describeUpstreamFailure(response)}`;
|
|
562
|
+
return "sent";
|
|
563
|
+
} catch (err) {
|
|
564
|
+
return `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
var UPSTREAM_BOOKKEEPING_FIELDS = /* @__PURE__ */ new Set(["id", "updated_at", "agent_id"]);
|
|
568
|
+
var MISSING_ID_HELP = [
|
|
569
|
+
"Refused locally [id_missing]: update_bug requires the parameter 'id' \u2014 the bug report UUID, or its 8-character prefix \u2014 and it did not arrive in this tool call.",
|
|
570
|
+
"",
|
|
571
|
+
"Nothing was written locally and nothing was sent upstream. Resend the call with 'id' set.",
|
|
572
|
+
"If you did pass it, the argument was dropped or truncated between your client and the tool."
|
|
573
|
+
].join("\n");
|
|
574
|
+
function describeEmptyUpstreamPayload(isAdmin) {
|
|
575
|
+
return [
|
|
576
|
+
'Refused locally [upstream_payload_empty]: this update carries no supported field the AskExe support API can write, so it would be sent as an empty PATCH, refused upstream with HTTP 400 "No supported fields provided", and queued as a no-op retry that looks like recovery.',
|
|
577
|
+
"",
|
|
578
|
+
isAdmin ? "An admin update must carry at least one of: status, fix_version/fixed_version, product, notes (triage notes), add_evidence, reopen_request, or a verification artifact / disposition." : "An owner update must carry at least one of: notes, add_evidence, or reopen_request.",
|
|
579
|
+
"",
|
|
580
|
+
"Nothing was written locally and nothing was sent upstream. If you did pass one of those, the argument was dropped or truncated between your client and the tool \u2014 resend the call."
|
|
581
|
+
].join("\n");
|
|
582
|
+
}
|
|
583
|
+
function registerUpdateBugReport(server) {
|
|
584
|
+
server.registerTool(
|
|
585
|
+
"update_bug_report",
|
|
586
|
+
{
|
|
587
|
+
title: "Update Bug Report",
|
|
588
|
+
description: "Update a previously filed bug report. As the filing customer (owner) you can add follow-up notes (notes/add_note), attach additional evidence (add_evidence), and request a reopen on a recurring fixed bug (reopen_request). AskExe support (with an admin token) can also move status: open \u2192 triaged \u2192 fixed \u2192 closed (or wontfix). Updates the local file, POSTs the change to AskExe support, and stores a memory.",
|
|
589
|
+
inputSchema: {
|
|
590
|
+
id: supportIdSchema.describe("Bug report UUID (full or 8-char prefix)"),
|
|
591
|
+
status: STATUS.optional().describe(
|
|
592
|
+
"New status (admin only): open, triaged, fixed, closed, wontfix. Owners cannot set this \u2014 use reopen_request instead."
|
|
593
|
+
),
|
|
594
|
+
fix_version: supportVersionSchema.optional().describe("Version that fixes this bug (admin only, e.g. '0.9.161')"),
|
|
595
|
+
product: supportProductSchema.optional().describe("Re-label this bug's product (admin only): exe-os, exe-create, exe-wiki, exe-gateway, exe-crm, exe-build, exe-chat, exe-auth, exe-monitor, exe-db, exe-erp"),
|
|
596
|
+
notes: z2.string().optional().describe("Follow-up notes about the bug or status change"),
|
|
597
|
+
add_note: z2.string().optional().describe("Owner follow-up note (alias of notes)"),
|
|
598
|
+
add_evidence: z2.string().optional().describe("Additional evidence: logs, repro details, stack traces"),
|
|
599
|
+
reopen_request: z2.boolean().optional().describe("Request a reopen on a recurring fixed/closed bug"),
|
|
600
|
+
reopen_reason: z2.string().optional().describe("Why the bug should be reopened (recurring symptoms, etc.)"),
|
|
601
|
+
send_upstream: z2.boolean().default(true).describe("POST the change to AskExe support endpoint"),
|
|
602
|
+
// Close gate (bug e9ba4b47) — this is the third client that can set a gated
|
|
603
|
+
// status, and it 422s exactly like the others without an artifact.
|
|
604
|
+
...verificationArtifactInputSchema
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
async ({ id, status, fix_version, product, notes, add_note, add_evidence, reopen_request, reopen_reason, send_upstream, ...verification }) => redactedToolResult(async () => {
|
|
608
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
609
|
+
return {
|
|
610
|
+
isError: true,
|
|
611
|
+
content: [{
|
|
612
|
+
type: "text",
|
|
613
|
+
text: MISSING_ID_HELP
|
|
614
|
+
}]
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const { agentId, agentRole } = getActiveAgent();
|
|
618
|
+
const config = await loadConfig();
|
|
619
|
+
const isAdmin = !!resolveSupportAdminToken(config);
|
|
620
|
+
const followUpNote = notes ?? add_note;
|
|
621
|
+
const reopen = reopen_request === true;
|
|
622
|
+
const effectiveStatus = reopen ? "open" : status;
|
|
623
|
+
const wireStatus = isAdmin ? effectiveStatus : void 0;
|
|
624
|
+
const gatedStatus = reopen ? void 0 : wireStatus;
|
|
625
|
+
const adminProgressNote = isAdmin ? [
|
|
626
|
+
add_evidence ? `${SUPPORT_EVIDENCE_PREFIX}${add_evidence}` : null,
|
|
627
|
+
reopen ? `${SUPPORT_REOPEN_PREFIX}${reopen_reason ?? "recurring"}` : null
|
|
628
|
+
].filter(Boolean).join("\n\n") : "";
|
|
629
|
+
const oversize = checkSupportTextLimits(
|
|
630
|
+
isAdmin ? [
|
|
631
|
+
// Admin notes land in the triage_notes column (12k budget).
|
|
632
|
+
{ field: notes !== void 0 ? "notes" : "add_note", text: followUpNote, limit: SUPPORT_TRIAGE_NOTES_MAX },
|
|
633
|
+
{ field: "add_evidence", text: add_evidence, limit: SUPPORT_EVIDENCE_MAX },
|
|
634
|
+
{ field: "reopen_reason", text: reopen ? reopen_reason : void 0, limit: SUPPORT_REOPEN_REASON_MAX },
|
|
635
|
+
// Evidence and reopen reason share ONE progress note upstream, so
|
|
636
|
+
// both fitting individually is not enough.
|
|
637
|
+
...add_evidence && reopen ? [{ field: "add_evidence + reopen_reason (one progress note)", text: adminProgressNote, limit: SUPPORT_PROGRESS_NOTE_MAX }] : []
|
|
638
|
+
] : [
|
|
639
|
+
{ field: notes !== void 0 ? "notes" : "add_note", text: followUpNote, limit: SUPPORT_PROGRESS_NOTE_MAX },
|
|
640
|
+
{ field: "add_evidence", text: add_evidence, limit: SUPPORT_EVIDENCE_MAX },
|
|
641
|
+
{ field: "reopen_reason", text: reopen ? reopen_reason : void 0, limit: SUPPORT_REOPEN_REASON_MAX }
|
|
642
|
+
]
|
|
643
|
+
);
|
|
644
|
+
if (oversize.length > 0) {
|
|
645
|
+
return {
|
|
646
|
+
isError: true,
|
|
647
|
+
content: [{ type: "text", text: describeOversizeRefusal(oversize) }]
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const attestsByIdentity = verification.verification_kind === "manual" || verification.verification_kind === "ops_transcript" || verification.disposition !== void 0 && verification.disposition !== "fixed";
|
|
651
|
+
let resolvedVerification = resolveVerificationArtifact(gatedStatus, verification);
|
|
652
|
+
if (resolvedVerification.ok && isVerifiedStatus(gatedStatus) && attestsByIdentity) {
|
|
653
|
+
const authorIdentities = await collectBugAuthorIdentities(id);
|
|
654
|
+
resolvedVerification = resolveVerificationArtifact(gatedStatus, verification, { authorIdentities });
|
|
655
|
+
if (!resolvedVerification.ok) {
|
|
656
|
+
const namedAuthors = authorIdentities.filter((a) => typeof a === "string" && a.trim().length > 0).map((a) => a.trim());
|
|
657
|
+
const authorNote = resolvedVerification.message.includes("manual_verifier_is_author") && namedAuthors.length > 0 ? `
|
|
658
|
+
This record's author/assignee identities: ${namedAuthors.join(", ")}. Name a DIFFERENT person or employee as the verifier \u2014 a manual close attested by its own author is exactly what the gate exists to refuse, locally and server-side.` : "";
|
|
659
|
+
return { isError: true, content: [{ type: "text", text: resolvedVerification.message + authorNote }] };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (!resolvedVerification.ok) {
|
|
663
|
+
return { isError: true, content: [{ type: "text", text: resolvedVerification.message }] };
|
|
664
|
+
}
|
|
665
|
+
const attribution = assertVerdictAttribution(gatedStatus, resolvedVerification);
|
|
666
|
+
if (!attribution.ok) {
|
|
667
|
+
return { isError: true, content: [{ type: "text", text: attribution.message }] };
|
|
668
|
+
}
|
|
669
|
+
const resolvedDelivery = parseDeliveryProof(verification.delivery_proof);
|
|
670
|
+
if (!resolvedDelivery.ok) {
|
|
671
|
+
return { isError: true, content: [{ type: "text", text: resolvedDelivery.message }] };
|
|
672
|
+
}
|
|
673
|
+
const resolved = await findReportFile(id);
|
|
674
|
+
const localFile = resolved?.filePath ?? null;
|
|
675
|
+
if (UUID_RE.test(id.trim().toLowerCase())) {
|
|
676
|
+
const requested = id.trim().toLowerCase();
|
|
677
|
+
if (resolved && resolved.fullId.toLowerCase() !== requested) {
|
|
678
|
+
return {
|
|
679
|
+
content: [
|
|
680
|
+
{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: `Refused: local bug report mismatch.
|
|
683
|
+
Requested id: ${id}
|
|
684
|
+
Resolved report id: ${resolved.fullId}
|
|
685
|
+
These do not match \u2014 not updating to avoid mutating the wrong bug. Verify the bug id and the local bug-reports/ files.`
|
|
686
|
+
}
|
|
687
|
+
]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const fullId = resolved?.fullId ?? id;
|
|
692
|
+
let localTitle = "Unknown bug";
|
|
693
|
+
let updateBlock = null;
|
|
694
|
+
if (localFile) {
|
|
695
|
+
try {
|
|
696
|
+
const built = await buildUpdateSection(localFile, {
|
|
697
|
+
status: effectiveStatus,
|
|
698
|
+
notes: followUpNote,
|
|
699
|
+
fixVersion: fix_version,
|
|
700
|
+
evidence: add_evidence,
|
|
701
|
+
reopen
|
|
702
|
+
});
|
|
703
|
+
localTitle = built.title;
|
|
704
|
+
updateBlock = built.updateBlock;
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const changeParts = [];
|
|
709
|
+
if (reopen) changeParts.push("reopen requested");
|
|
710
|
+
else if (status) changeParts.push(`marked ${status}`);
|
|
711
|
+
if (fix_version) changeParts.push(`fix v ${fix_version}`);
|
|
712
|
+
if (product) changeParts.push(`product \u2192 ${product}`);
|
|
713
|
+
if (add_evidence) changeParts.push("evidence added");
|
|
714
|
+
if (followUpNote && !reopen && !status) changeParts.push("note added");
|
|
715
|
+
const change = changeParts.length > 0 ? changeParts.join(", ") : "updated";
|
|
716
|
+
const memoryText = `Bug "${localTitle}" (${fullId.slice(0, 8)}) ${change}` + (reopen_reason ? `. Reopen reason: ${reopen_reason}` : "") + (followUpNote ? `. Notes: ${followUpNote}` : "");
|
|
717
|
+
const payload = {
|
|
718
|
+
// Body belt (round 9): even past ingress validation, sanitize the id so a
|
|
719
|
+
// value from a non-schema caller can't put a raw id in the JSON body.
|
|
720
|
+
id: sanitizeUpstreamId(fullId),
|
|
721
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
722
|
+
agent_id: agentId
|
|
723
|
+
};
|
|
724
|
+
if (isAdmin) {
|
|
725
|
+
if (wireStatus) payload.status = wireStatus;
|
|
726
|
+
const safeFixVersion = sanitizeVersionField(fix_version);
|
|
727
|
+
if (safeFixVersion) {
|
|
728
|
+
payload.fixed_version = safeFixVersion;
|
|
729
|
+
payload.fix_version = safeFixVersion;
|
|
730
|
+
}
|
|
731
|
+
const safeProduct = sanitizeProductFilter(product);
|
|
732
|
+
if (safeProduct) payload.product = safeProduct;
|
|
733
|
+
if (followUpNote) payload.triage_notes = followUpNote;
|
|
734
|
+
if (adminProgressNote) payload.add_note = adminProgressNote;
|
|
735
|
+
if (resolvedVerification.artifact) payload.verification_artifact = resolvedVerification.artifact;
|
|
736
|
+
if (resolvedDelivery.value !== void 0) payload.delivery_proof = resolvedDelivery.value;
|
|
737
|
+
if (verification.disposition) payload.disposition = verification.disposition;
|
|
738
|
+
if (verification.disposition_reason) payload.disposition_reason = verification.disposition_reason;
|
|
739
|
+
if (verification.superseded_by) payload.superseded_by = verification.superseded_by;
|
|
740
|
+
if (verification.disposition_verifier) payload.disposition_verifier = verification.disposition_verifier;
|
|
741
|
+
if (verification.superseded_coverage) payload.superseded_coverage = verification.superseded_coverage;
|
|
742
|
+
if (verification.symptoms) payload.symptoms = verification.symptoms;
|
|
743
|
+
if (resolvedVerification.attribution) payload.attribution = resolvedVerification.attribution;
|
|
744
|
+
} else {
|
|
745
|
+
if (followUpNote) payload.add_note = followUpNote;
|
|
746
|
+
if (add_evidence) payload.add_evidence = add_evidence;
|
|
747
|
+
if (reopen) {
|
|
748
|
+
payload.reopen_request = true;
|
|
749
|
+
if (reopen_reason) payload.reopen_reason = reopen_reason;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const actionableFields = Object.keys(payload).filter((k) => !UPSTREAM_BOOKKEEPING_FIELDS.has(k));
|
|
753
|
+
if (send_upstream && actionableFields.length === 0) {
|
|
754
|
+
return {
|
|
755
|
+
isError: true,
|
|
756
|
+
content: [{ type: "text", text: describeEmptyUpstreamPayload(isAdmin) }]
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
const memoryId = `${fullId}-update-${Date.now()}`;
|
|
760
|
+
await persistSupportArtifact({
|
|
761
|
+
memoryText,
|
|
762
|
+
file: localFile && updateBlock !== null ? { path: localFile, content: updateBlock, append: true } : void 0,
|
|
763
|
+
memory: {
|
|
764
|
+
id: memoryId,
|
|
765
|
+
agent_id: agentId,
|
|
766
|
+
agent_role: agentRole,
|
|
767
|
+
session_id: process.env.SESSION_ID ?? "manual",
|
|
768
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
769
|
+
tool_name: "update_bug_report",
|
|
770
|
+
project_name: "support",
|
|
771
|
+
has_error: false,
|
|
772
|
+
source_path: localFile ?? null,
|
|
773
|
+
source_type: "bug_report",
|
|
774
|
+
memory_type: "bug_report",
|
|
775
|
+
tier: 1,
|
|
776
|
+
importance: status === "fixed" || status === "closed" ? 8 : 6,
|
|
777
|
+
intent: "update",
|
|
778
|
+
domain: "support",
|
|
779
|
+
file_paths: null
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
const upstreamStatus = send_upstream ? await sendUpstream(payload, isAdmin) : "skipped";
|
|
783
|
+
const upstreamFailed = send_upstream && upstreamStatus.startsWith("failed:");
|
|
784
|
+
const queuedId = sanitizeUpstreamId(fullId);
|
|
785
|
+
const queuedPath = upstreamFailed ? queueSupportStatusUpdateSync({
|
|
786
|
+
id: queuedId,
|
|
787
|
+
kind: "bug",
|
|
788
|
+
endpointPath: ["bug-reports", queuedId].join("/"),
|
|
789
|
+
method: "PATCH",
|
|
790
|
+
payload,
|
|
791
|
+
lastError: upstreamStatus
|
|
792
|
+
}) : void 0;
|
|
793
|
+
if (upstreamFailed) {
|
|
794
|
+
return {
|
|
795
|
+
isError: true,
|
|
796
|
+
content: [
|
|
797
|
+
{
|
|
798
|
+
type: "text",
|
|
799
|
+
text: `Bug report update failed upstream.
|
|
800
|
+
ID: ${fullId}
|
|
801
|
+
` + (status && isAdmin ? `Requested status: ${status}
|
|
802
|
+
` : "") + (reopen ? `Reopen requested: yes
|
|
803
|
+
` : "") + (fix_version && isAdmin ? `Fix version: ${fix_version}
|
|
804
|
+
` : "") + (product && isAdmin ? `Product: ${product}
|
|
805
|
+
` : "") + (add_evidence ? `Evidence: added
|
|
806
|
+
` : "") + (followUpNote ? `Note: added
|
|
807
|
+
` : "") + `Mode: ${isAdmin ? "admin" : "owner"}
|
|
808
|
+
Local file: ${localFile ?? "not found"}
|
|
809
|
+
Memory stored: ${memoryId}
|
|
810
|
+
Upstream status: ${upstreamStatus}
|
|
811
|
+
Retry queued: ${queuedPath ? "yes" : "no"}`
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
const versionOnlyStatusNotice = isAdmin && fix_version && !status ? `Status unchanged: fix version ${fix_version} was recorded, but the bug was NOT closed. Pass status: "closed" explicitly to close it.
|
|
817
|
+
` : "";
|
|
818
|
+
return {
|
|
819
|
+
content: [
|
|
820
|
+
{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: `Bug report ${reopen ? "reopen requested" : "updated"}.
|
|
823
|
+
ID: ${fullId}
|
|
824
|
+
` + (status && isAdmin ? `New status: ${status}
|
|
825
|
+
` : "") + (reopen ? `Reopen requested: yes
|
|
826
|
+
` : "") + (fix_version && isAdmin ? `Fix version: ${fix_version}
|
|
827
|
+
` : "") + versionOnlyStatusNotice + (product && isAdmin ? `Product: ${product}
|
|
828
|
+
` : "") + (add_evidence ? `Evidence: added
|
|
829
|
+
` : "") + (followUpNote ? `Note: added
|
|
830
|
+
` : "") + `Mode: ${isAdmin ? "admin" : "owner"}
|
|
831
|
+
Local file: ${localFile ?? "not found"}
|
|
832
|
+
Memory stored: ${memoryId}
|
|
833
|
+
Upstream status: ${upstreamStatus}`
|
|
834
|
+
}
|
|
835
|
+
]
|
|
836
|
+
};
|
|
837
|
+
})
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/mcp/tools/support-inbox.ts
|
|
842
|
+
var DEFAULT_ENDPOINT = "https://api.askexe.com/admin/support/bug-reports";
|
|
843
|
+
var DEFAULT_OWNER_ENDPOINT = "https://api.askexe.com/v1/support/bug-reports";
|
|
844
|
+
var STATUS2 = z3.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
|
|
845
|
+
var SupportHttpError = class extends Error {
|
|
846
|
+
constructor(status, message) {
|
|
847
|
+
super(message);
|
|
848
|
+
this.status = status;
|
|
849
|
+
this.name = "SupportHttpError";
|
|
850
|
+
}
|
|
851
|
+
status;
|
|
852
|
+
};
|
|
853
|
+
function adminToken() {
|
|
854
|
+
if (process.env.ASKEXE_SUPPORT_ADMIN_TOKEN) return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN;
|
|
855
|
+
if (process.env.EXE_SUPPORT_ADMIN_TOKEN) return process.env.EXE_SUPPORT_ADMIN_TOKEN;
|
|
856
|
+
try {
|
|
857
|
+
const configPath = join(process.env.EXE_OS_DIR ?? join(homedir(), ".exe-os"), "config.json");
|
|
858
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
859
|
+
return config.support?.adminToken || config.support?.admin_token;
|
|
860
|
+
} catch {
|
|
861
|
+
return void 0;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function endpoint() {
|
|
865
|
+
return (process.env.ASKEXE_SUPPORT_ADMIN_ENDPOINT || DEFAULT_ENDPOINT).replace(/\/+$/, "");
|
|
866
|
+
}
|
|
867
|
+
function ownerEndpoint() {
|
|
868
|
+
const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
|
|
869
|
+
let configured;
|
|
870
|
+
try {
|
|
871
|
+
const configPath = join(process.env.EXE_OS_DIR ?? join(homedir(), ".exe-os"), "config.json");
|
|
872
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
873
|
+
configured = config.support?.bugReportEndpoint;
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
const base = configured || process.env.EXE_BUG_REPORT_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/bug-reports` : DEFAULT_OWNER_ENDPOINT);
|
|
877
|
+
return base.replace(/\/+$/, "");
|
|
878
|
+
}
|
|
879
|
+
function ownerAuthHeaders() {
|
|
880
|
+
let cloudApiKey;
|
|
881
|
+
try {
|
|
882
|
+
const configPath = join(process.env.EXE_OS_DIR ?? join(homedir(), ".exe-os"), "config.json");
|
|
883
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
884
|
+
cloudApiKey = config.cloud?.apiKey;
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || cloudApiKey;
|
|
888
|
+
const licenseToken = readCachedLicenseToken();
|
|
889
|
+
const headers = {};
|
|
890
|
+
if (licenseKey) {
|
|
891
|
+
headers["x-exe-license-key"] = licenseKey;
|
|
892
|
+
headers.authorization = `Bearer ${licenseKey}`;
|
|
893
|
+
}
|
|
894
|
+
if (licenseToken) headers["x-exe-license-token"] = licenseToken;
|
|
895
|
+
return headers;
|
|
896
|
+
}
|
|
897
|
+
function assertInternal() {
|
|
898
|
+
const token = adminToken();
|
|
899
|
+
if (!token) throw new Error("AskExe support admin token not configured");
|
|
900
|
+
return token;
|
|
901
|
+
}
|
|
902
|
+
async function requestJson(url, init) {
|
|
903
|
+
const token = assertInternal();
|
|
904
|
+
const res = await fetch(url, {
|
|
905
|
+
...init,
|
|
906
|
+
headers: {
|
|
907
|
+
"content-type": "application/json",
|
|
908
|
+
authorization: `Bearer ${token}`,
|
|
909
|
+
...init?.headers ?? {}
|
|
910
|
+
},
|
|
911
|
+
signal: AbortSignal.timeout(1e4)
|
|
912
|
+
});
|
|
913
|
+
const text = await res.text();
|
|
914
|
+
let data = text;
|
|
915
|
+
try {
|
|
916
|
+
data = text ? JSON.parse(text) : {};
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
920
|
+
return data;
|
|
921
|
+
}
|
|
922
|
+
async function fetchJson(url, headers, init) {
|
|
923
|
+
const res = await fetch(url, {
|
|
924
|
+
...init,
|
|
925
|
+
headers: { "content-type": "application/json", ...headers, ...init?.headers ?? {} },
|
|
926
|
+
signal: AbortSignal.timeout(1e4)
|
|
927
|
+
});
|
|
928
|
+
const text = await res.text();
|
|
929
|
+
let data = text;
|
|
930
|
+
try {
|
|
931
|
+
data = text ? JSON.parse(text) : {};
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
if (!res.ok) throw new SupportHttpError(res.status, `HTTP ${res.status}: ${typeof data === "string" ? data : JSON.stringify(data)}`);
|
|
935
|
+
return data;
|
|
936
|
+
}
|
|
937
|
+
async function commentRequestJson(bugId, init) {
|
|
938
|
+
const token = adminToken();
|
|
939
|
+
if (token) {
|
|
940
|
+
const safeId2 = encodeURIComponent(sanitizeUpstreamId(bugId));
|
|
941
|
+
const bugUrl2 = `${endpoint()}/${safeId2}/comments`;
|
|
942
|
+
try {
|
|
943
|
+
return await fetchJson(bugUrl2, { authorization: `Bearer ${token}` }, init);
|
|
944
|
+
} catch (err) {
|
|
945
|
+
if (!(err instanceof SupportHttpError) || err.status !== 404) throw err;
|
|
946
|
+
}
|
|
947
|
+
const featureUrl2 = `${featureEndpoint()}/${safeId2}/comments`;
|
|
948
|
+
return fetchJson(featureUrl2, { authorization: `Bearer ${token}` }, init);
|
|
949
|
+
}
|
|
950
|
+
const ownerHeaders = ownerAuthHeaders();
|
|
951
|
+
if (!ownerHeaders.authorization && !ownerHeaders["x-exe-license-token"]) {
|
|
952
|
+
throw new Error(
|
|
953
|
+
"Not authenticated for support comments. Set a license key (`exe-os setup`), or store a support-issued token with `exe-os login --token <jwt>`, or configure an AskExe admin token (ASKEXE_SUPPORT_ADMIN_TOKEN)."
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
const safeId = encodeURIComponent(sanitizeUpstreamId(bugId));
|
|
957
|
+
const bugUrl = `${ownerEndpoint()}/${safeId}/comments`;
|
|
958
|
+
try {
|
|
959
|
+
return await fetchJson(bugUrl, ownerHeaders, init);
|
|
960
|
+
} catch (err) {
|
|
961
|
+
if (!(err instanceof SupportHttpError) || err.status !== 404) throw err;
|
|
962
|
+
}
|
|
963
|
+
const featureUrl = `${featureOwnerEndpoint()}/${safeId}/comments`;
|
|
964
|
+
return fetchJson(featureUrl, ownerHeaders, init);
|
|
965
|
+
}
|
|
966
|
+
function withStatusNotice(data, notice) {
|
|
967
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
968
|
+
return { ...data, status_notice: notice };
|
|
969
|
+
}
|
|
970
|
+
return { status_notice: notice, response: data };
|
|
971
|
+
}
|
|
972
|
+
var AUTO_PAGE_SIZE = 500;
|
|
973
|
+
var AUTO_FETCH_MAX_PAGES = 40;
|
|
974
|
+
async function fetchAllOrPage(baseUrl, opts) {
|
|
975
|
+
const explicitLimit = typeof opts.limit === "number";
|
|
976
|
+
const pageSize = explicitLimit ? opts.limit : AUTO_PAGE_SIZE;
|
|
977
|
+
let items = [];
|
|
978
|
+
let total;
|
|
979
|
+
let curOffset = opts.offset ?? 0;
|
|
980
|
+
let hasMore = false;
|
|
981
|
+
let nextOffset = null;
|
|
982
|
+
let hitSafetyCap = false;
|
|
983
|
+
let pages = 0;
|
|
984
|
+
do {
|
|
985
|
+
const url = new URL(baseUrl.toString());
|
|
986
|
+
url.searchParams.set("limit", String(pageSize));
|
|
987
|
+
url.searchParams.set("offset", String(curOffset));
|
|
988
|
+
const data = await requestJson(url.toString());
|
|
989
|
+
const pageItems = Array.isArray(data.items) ? data.items : [];
|
|
990
|
+
items = items.concat(pageItems);
|
|
991
|
+
total = data.total;
|
|
992
|
+
hasMore = !!data.has_more;
|
|
993
|
+
nextOffset = typeof data.next_offset === "number" ? data.next_offset : curOffset + pageItems.length;
|
|
994
|
+
curOffset = nextOffset;
|
|
995
|
+
pages++;
|
|
996
|
+
if (explicitLimit) break;
|
|
997
|
+
if (hasMore && pages >= AUTO_FETCH_MAX_PAGES) {
|
|
998
|
+
hitSafetyCap = true;
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
} while (hasMore);
|
|
1002
|
+
return {
|
|
1003
|
+
items,
|
|
1004
|
+
count: items.length,
|
|
1005
|
+
total,
|
|
1006
|
+
has_more: explicitLimit ? hasMore : hitSafetyCap,
|
|
1007
|
+
next_offset: explicitLimit || hitSafetyCap ? nextOffset : null,
|
|
1008
|
+
hit_safety_cap: hitSafetyCap
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function computeCoverage(page, requestedOffset) {
|
|
1012
|
+
const skippedBefore = requestedOffset ?? 0;
|
|
1013
|
+
const totalKnown = typeof page.total === "number";
|
|
1014
|
+
const complete = skippedBefore === 0 && !page.has_more && (!totalKnown || page.count === page.total);
|
|
1015
|
+
const remainingAfter = totalKnown ? Math.max(page.total - skippedBefore - page.count, 0) : page.has_more ? null : 0;
|
|
1016
|
+
return { complete, skippedBefore, remainingAfter };
|
|
1017
|
+
}
|
|
1018
|
+
function buildTruncationNotice(page, statusFilter, requestedOffset) {
|
|
1019
|
+
const lines = [];
|
|
1020
|
+
const coverage = computeCoverage(page, requestedOffset);
|
|
1021
|
+
const capNote = page.hit_safety_cap ? ` [safety cap after ${AUTO_FETCH_MAX_PAGES} auto-fetched pages, not a limit you set]` : "";
|
|
1022
|
+
if (coverage.complete) {
|
|
1023
|
+
lines.push(
|
|
1024
|
+
typeof page.total === "number" ? `Showing all ${page.count} matching result(s) (total=${page.total}).` : `Showing ${page.count} matching result(s).`
|
|
1025
|
+
);
|
|
1026
|
+
} else if (coverage.skippedBefore === 0) {
|
|
1027
|
+
const notShownSuffix = typeof page.total === "number" ? ` OF ${page.total} \u2014 ${coverage.remainingAfter} NOT SHOWN.` : " \u2014 MORE RESULTS EXIST (exact total unknown).";
|
|
1028
|
+
const nextHint = page.has_more ? ` Pass offset=${page.next_offset} for the next page.` : "";
|
|
1029
|
+
lines.push(`SHOWING ${page.count}${notShownSuffix}${capNote}${nextHint}`);
|
|
1030
|
+
} else {
|
|
1031
|
+
const totalSuffix = typeof page.total === "number" ? ` OF ${page.total}` : " (total unknown)";
|
|
1032
|
+
const afterLabel = coverage.remainingAfter === null ? "MORE RESULTS MAY EXIST" : `${coverage.remainingAfter}`;
|
|
1033
|
+
const nextHint = page.has_more ? ` Pass offset=${page.next_offset} for the next page.` : "";
|
|
1034
|
+
lines.push(
|
|
1035
|
+
`SHOWING ${page.count}${totalSuffix} \u2014 ${coverage.skippedBefore} SKIPPED BEFORE THIS PAGE, ${afterLabel} AFTER.${capNote}${nextHint}`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
if (statusFilter !== "all") {
|
|
1039
|
+
lines.push(
|
|
1040
|
+
`SCOPE: status='${statusFilter}' only \u2014 other statuses are NOT included in this count or list. Use status='all' (or count_only:true) to see totals across every status.`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
lines.push("");
|
|
1044
|
+
return lines;
|
|
1045
|
+
}
|
|
1046
|
+
async function fetchStatusCounts(baseUrl, statuses) {
|
|
1047
|
+
const entries = await Promise.all(
|
|
1048
|
+
statuses.map(async (s) => {
|
|
1049
|
+
const url = new URL(baseUrl.toString());
|
|
1050
|
+
url.searchParams.set("status", s);
|
|
1051
|
+
url.searchParams.set("limit", "1");
|
|
1052
|
+
url.searchParams.set("offset", "0");
|
|
1053
|
+
const data = await requestJson(url.toString());
|
|
1054
|
+
const n = typeof data.total === "number" ? data.total : typeof data.count === "number" ? data.count : 0;
|
|
1055
|
+
return [s, n];
|
|
1056
|
+
})
|
|
1057
|
+
);
|
|
1058
|
+
return Object.fromEntries(entries);
|
|
1059
|
+
}
|
|
1060
|
+
function formatCounts(counts, label, unresolvedKeys) {
|
|
1061
|
+
const lines = [`${label} counts (per status):`, ""];
|
|
1062
|
+
for (const [status, n] of Object.entries(counts)) lines.push(` ${status}: ${n}`);
|
|
1063
|
+
const unresolvedTotal = unresolvedKeys.reduce((sum, k) => sum + (counts[k] ?? 0), 0);
|
|
1064
|
+
lines.push("");
|
|
1065
|
+
lines.push(` TOTAL unresolved (${unresolvedKeys.join("+")}): ${unresolvedTotal}`);
|
|
1066
|
+
return lines.join("\n");
|
|
1067
|
+
}
|
|
1068
|
+
var BUG_STATUSES = ["open", "triaged", "fixed", "closed", "wontfix"];
|
|
1069
|
+
function registerListBugReports(server) {
|
|
1070
|
+
server.registerTool(
|
|
1071
|
+
"list_bug_reports",
|
|
1072
|
+
{
|
|
1073
|
+
title: "List Bug Reports",
|
|
1074
|
+
description: "AskExe-internal only: list incoming customer bug reports from the support inbox. With no `limit`, returns the COMPLETE matching set (auto-paginated) \u2014 never a silent partial page. Pass `limit` explicitly for exactly one page (the response leads with a notice if more exist). Pass `count_only:true` for per-status totals (open/triaged/fixed/closed/wontfix) plus per-cluster totals (root-cause class) without fetching records \u2014 the cheapest way to answer 'how many bugs are open' or 'how many bugs are in this cluster'. Pass `cluster` to filter the row list to one root-cause class.",
|
|
1075
|
+
inputSchema: {
|
|
1076
|
+
status: z3.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
|
|
1077
|
+
severity: z3.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
1078
|
+
// Round 8: constrain to the known enum at the schema — a `product` filter is
|
|
1079
|
+
// NOT free text, and it was sent raw as a query param.
|
|
1080
|
+
product: z3.enum(SUPPORT_PRODUCTS).optional().describe("Filter by product"),
|
|
1081
|
+
cluster: supportClusterSchema.optional().describe("Filter by root-cause-class cluster slug (feature d0970342)"),
|
|
1082
|
+
limit: z3.number().int().min(1).max(500).optional().describe("Return exactly one page of this size. Omit to fetch the COMPLETE matching set instead of a 25-row default page."),
|
|
1083
|
+
offset: z3.number().int().min(0).default(0).describe("Number of results to skip. Only meaningful together with an explicit `limit`; use next_offset from a prior response."),
|
|
1084
|
+
count_only: z3.boolean().optional().describe("Skip fetching records; return counts per status AND per cluster. Cross-check: sum of open+triaged+fixed = total unresolved.")
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
async ({ status, severity, product, cluster, limit, offset, count_only }) => {
|
|
1088
|
+
const safeProduct = sanitizeProductFilter(product);
|
|
1089
|
+
const filteredUrl = new URL(endpoint());
|
|
1090
|
+
if (severity) filteredUrl.searchParams.set("severity", severity);
|
|
1091
|
+
if (safeProduct) filteredUrl.searchParams.set("product", safeProduct);
|
|
1092
|
+
if (cluster) filteredUrl.searchParams.set("cluster", cluster);
|
|
1093
|
+
if (count_only) {
|
|
1094
|
+
let counts;
|
|
1095
|
+
let by_cluster = {};
|
|
1096
|
+
try {
|
|
1097
|
+
const countOnlyUrl = new URL(filteredUrl.toString());
|
|
1098
|
+
countOnlyUrl.searchParams.set("count_only", "1");
|
|
1099
|
+
const data = await requestJson(countOnlyUrl.toString());
|
|
1100
|
+
if (data.by_status) {
|
|
1101
|
+
counts = Object.fromEntries(BUG_STATUSES.map((s) => [s, data.by_status[s] ?? 0]));
|
|
1102
|
+
by_cluster = data.by_cluster ?? {};
|
|
1103
|
+
}
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
if (!counts) {
|
|
1107
|
+
counts = await fetchStatusCounts(filteredUrl, BUG_STATUSES);
|
|
1108
|
+
}
|
|
1109
|
+
const text = formatCounts(counts, "Bug report", ["open", "triaged", "fixed"]);
|
|
1110
|
+
const clusterLines = Object.keys(by_cluster).length > 0 ? "\n\nBy cluster (root-cause class):\n" + Object.entries(by_cluster).map(([c, n]) => ` ${c}: ${n}`).join("\n") : "";
|
|
1111
|
+
return { content: [{ type: "text", text: text + clusterLines }], structuredContent: { counts, by_cluster } };
|
|
1112
|
+
}
|
|
1113
|
+
const url = new URL(filteredUrl.toString());
|
|
1114
|
+
url.searchParams.set("status", status);
|
|
1115
|
+
const page = await fetchAllOrPage(url, { limit, offset });
|
|
1116
|
+
const notice = buildTruncationNotice(page, status, offset);
|
|
1117
|
+
const body = JSON.stringify(
|
|
1118
|
+
{ items: page.items, count: page.count, total: page.total, has_more: page.has_more, next_offset: page.next_offset },
|
|
1119
|
+
null,
|
|
1120
|
+
2
|
|
1121
|
+
);
|
|
1122
|
+
return { content: [{ type: "text", text: notice.join("\n") + body }] };
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
function parseComments(row) {
|
|
1127
|
+
if (!row || typeof row !== "object") return [];
|
|
1128
|
+
const raw = row.progress_log;
|
|
1129
|
+
if (typeof raw !== "string" || !raw.trim()) return [];
|
|
1130
|
+
try {
|
|
1131
|
+
const parsed = JSON.parse(raw);
|
|
1132
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1133
|
+
} catch {
|
|
1134
|
+
return [];
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
var LOCAL_FALLBACK_STATUSES = /* @__PURE__ */ new Set([404, 500, 502, 503, 504]);
|
|
1138
|
+
function upstreamMissingOrUnavailable(error) {
|
|
1139
|
+
if (error instanceof SupportHttpError) return LOCAL_FALLBACK_STATUSES.has(error.status);
|
|
1140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1141
|
+
const match = /^HTTP (\d{3}):/.exec(message);
|
|
1142
|
+
if (match) return LOCAL_FALLBACK_STATUSES.has(Number(match[1]));
|
|
1143
|
+
return !(error instanceof SupportHttpError);
|
|
1144
|
+
}
|
|
1145
|
+
async function readLocalBugReport(id) {
|
|
1146
|
+
const found = await findReportFile(id);
|
|
1147
|
+
if (!found) return null;
|
|
1148
|
+
let markdown;
|
|
1149
|
+
try {
|
|
1150
|
+
markdown = await readFile2(found.filePath, "utf-8");
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
const synced = markdown.includes(SUPPORT_REPORT_SENT_MARKER);
|
|
1155
|
+
return {
|
|
1156
|
+
id: found.fullId,
|
|
1157
|
+
source: "local",
|
|
1158
|
+
sync_state: synced ? "synced" : "never_synced",
|
|
1159
|
+
upstream_reachable: false,
|
|
1160
|
+
markdown
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function registerGetBugReport(server) {
|
|
1164
|
+
server.registerTool(
|
|
1165
|
+
"get_bug_report",
|
|
1166
|
+
{
|
|
1167
|
+
title: "Get Bug Report",
|
|
1168
|
+
description: "AskExe-internal only: fetch one customer bug report with full markdown payload and comments thread.",
|
|
1169
|
+
inputSchema: { id: supportIdSchema }
|
|
1170
|
+
},
|
|
1171
|
+
async ({ id }) => {
|
|
1172
|
+
let data;
|
|
1173
|
+
let localOnly = false;
|
|
1174
|
+
try {
|
|
1175
|
+
data = await requestJson(`${endpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`);
|
|
1176
|
+
} catch (err) {
|
|
1177
|
+
if (!upstreamMissingOrUnavailable(err)) throw err;
|
|
1178
|
+
const local = await readLocalBugReport(id);
|
|
1179
|
+
if (!local) throw err;
|
|
1180
|
+
data = local;
|
|
1181
|
+
localOnly = true;
|
|
1182
|
+
}
|
|
1183
|
+
const comments = localOnly ? [] : parseComments(data);
|
|
1184
|
+
const enriched = data && typeof data === "object" ? { ...data, comments } : { id, comments };
|
|
1185
|
+
if (localOnly && enriched && typeof enriched === "object") {
|
|
1186
|
+
const synced = enriched.sync_state === "synced";
|
|
1187
|
+
enriched._note = synced ? "Upstream could not serve this record (not found or unreachable); returned from the local ~/.exe-os/bug-reports/ outbox. The local copy is marked upstream_sent, so this is an upstream read failure, not a lost send." : "Upstream has no record for this id (or is unreachable); returned from the local ~/.exe-os/bug-reports/ outbox. The upstream send has not succeeded yet.";
|
|
1188
|
+
}
|
|
1189
|
+
return { content: [{ type: "text", text: JSON.stringify(enriched, null, 2) }] };
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
function registerAddBugReportComment(server) {
|
|
1194
|
+
server.registerTool(
|
|
1195
|
+
"add_bug_report_comment",
|
|
1196
|
+
{
|
|
1197
|
+
title: "Add Bug Report Comment",
|
|
1198
|
+
description: "AskExe-internal only: append a comment to a bug report's thread (POST /comments). Non-destructive \u2014 does not touch triage_notes or status.",
|
|
1199
|
+
inputSchema: {
|
|
1200
|
+
id: supportIdSchema,
|
|
1201
|
+
comment: z3.string().min(1).describe("Comment text to append to the bug report thread")
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
async ({ id, comment }) => {
|
|
1205
|
+
const data = await commentRequestJson(id, {
|
|
1206
|
+
method: "POST",
|
|
1207
|
+
body: sanitizedUpstreamBody({ comment }, "support-inbox:add_comment")
|
|
1208
|
+
});
|
|
1209
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1210
|
+
}
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
function registerGetBugReportComments(server) {
|
|
1214
|
+
server.registerTool(
|
|
1215
|
+
"get_bug_report_comments",
|
|
1216
|
+
{
|
|
1217
|
+
title: "Get Bug Report Comments",
|
|
1218
|
+
description: "AskExe-internal only: list the comments thread on a bug report (GET /comments).",
|
|
1219
|
+
inputSchema: { id: supportIdSchema }
|
|
1220
|
+
},
|
|
1221
|
+
async ({ id }) => {
|
|
1222
|
+
const data = await commentRequestJson(id);
|
|
1223
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
function registerTriageBugReport(server) {
|
|
1228
|
+
server.registerTool(
|
|
1229
|
+
"triage_bug_report",
|
|
1230
|
+
{
|
|
1231
|
+
title: "Triage Bug Report",
|
|
1232
|
+
description: "AskExe-internal only: update bug report status and link task/commit/release metadata. Setting status=fixed or closed requires a verification artifact (verification_kind + fix_commit + either the regression-test fields or the manual verifier fields); the fix commit's diff is measured locally.",
|
|
1233
|
+
inputSchema: {
|
|
1234
|
+
id: supportIdSchema,
|
|
1235
|
+
status: STATUS2.optional(),
|
|
1236
|
+
triage_notes: z3.string().optional(),
|
|
1237
|
+
add_note: z3.string().optional().describe("Append a timestamped progress note without changing status or replacing triage_notes"),
|
|
1238
|
+
linked_task_id: supportTaskIdSchema.optional(),
|
|
1239
|
+
linked_commit: supportCommitSchema.optional(),
|
|
1240
|
+
fixed_version: supportVersionSchema.optional(),
|
|
1241
|
+
product: supportProductSchema.optional().describe("Re-label this bug's product: exe-os, exe-create, exe-wiki, exe-gateway, exe-crm, exe-build, exe-chat, exe-auth, exe-monitor, exe-db, exe-erp"),
|
|
1242
|
+
// Bug: this field was missing from the schema entirely, so a caller
|
|
1243
|
+
// (e.g. support(action='triage_bug', classification=...)) had its
|
|
1244
|
+
// value silently swallowed into the verification rest-spread below
|
|
1245
|
+
// and NEVER reached the PATCH body — triage_bug left classification
|
|
1246
|
+
// stuck at 'unclear' no matter what was passed. See test coverage in
|
|
1247
|
+
// support-inbox-triage-classification.test.ts.
|
|
1248
|
+
classification: z3.enum([
|
|
1249
|
+
"upstream_bug",
|
|
1250
|
+
"customer_customization",
|
|
1251
|
+
"emergency_hotfix",
|
|
1252
|
+
"unclear"
|
|
1253
|
+
]).optional().describe("Triage classification: upstream_bug, customer_customization, emergency_hotfix, unclear"),
|
|
1254
|
+
cluster: supportClusterClearableSchema.optional().describe("Root-cause class this bug belongs to \u2014 a kebab-case slug (feature d0970342). Fixes are dispatched per cluster; use list_bugs(cluster=...) to find every bug sharing one. Pass an empty string to CLEAR a mis-triaged cluster back to unset."),
|
|
1255
|
+
...verificationArtifactInputSchema
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
async ({ id, status, triage_notes, add_note, linked_task_id, linked_commit, fixed_version, product, classification, cluster, ...verification }) => {
|
|
1259
|
+
const bugUrl = `${endpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`;
|
|
1260
|
+
const attestsByIdentity = verification.verification_kind === "manual" || verification.verification_kind === "ops_transcript" || verification.disposition !== void 0 && verification.disposition !== "fixed";
|
|
1261
|
+
let authorIdentities = [];
|
|
1262
|
+
if (isVerifiedStatus(status) && attestsByIdentity) {
|
|
1263
|
+
try {
|
|
1264
|
+
const row = await requestJson(bugUrl);
|
|
1265
|
+
authorIdentities = [row?.agent_id, row?.license_email];
|
|
1266
|
+
} catch {
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const resolved = resolveVerificationArtifact(status, verification, { authorIdentities });
|
|
1270
|
+
if (!resolved.ok) {
|
|
1271
|
+
return { isError: true, content: [{ type: "text", text: resolved.message }] };
|
|
1272
|
+
}
|
|
1273
|
+
const attribution = assertVerdictAttribution(status, resolved);
|
|
1274
|
+
if (!attribution.ok) {
|
|
1275
|
+
return { isError: true, content: [{ type: "text", text: attribution.message }] };
|
|
1276
|
+
}
|
|
1277
|
+
const delivery = parseDeliveryProof(verification.delivery_proof);
|
|
1278
|
+
if (!delivery.ok) {
|
|
1279
|
+
return { isError: true, content: [{ type: "text", text: delivery.message }] };
|
|
1280
|
+
}
|
|
1281
|
+
const data = await requestJson(bugUrl, {
|
|
1282
|
+
method: "PATCH",
|
|
1283
|
+
body: sanitizedUpstreamBody({
|
|
1284
|
+
status,
|
|
1285
|
+
triage_notes,
|
|
1286
|
+
add_note,
|
|
1287
|
+
linked_task_id,
|
|
1288
|
+
linked_commit,
|
|
1289
|
+
fixed_version,
|
|
1290
|
+
product,
|
|
1291
|
+
classification,
|
|
1292
|
+
cluster,
|
|
1293
|
+
verification_artifact: resolved.artifact,
|
|
1294
|
+
delivery_proof: delivery.value,
|
|
1295
|
+
// The disposition travels with the close: the server gates on it, and stores
|
|
1296
|
+
// it so the record reads as "closed because there was nothing to fix" rather
|
|
1297
|
+
// than as an unexplained close.
|
|
1298
|
+
disposition: verification.disposition,
|
|
1299
|
+
disposition_reason: verification.disposition_reason,
|
|
1300
|
+
superseded_by: verification.superseded_by,
|
|
1301
|
+
disposition_verifier: verification.disposition_verifier,
|
|
1302
|
+
superseded_coverage: verification.superseded_coverage,
|
|
1303
|
+
symptoms: verification.symptoms,
|
|
1304
|
+
attribution: resolved.attribution
|
|
1305
|
+
}, "support-inbox:triage_bug")
|
|
1306
|
+
});
|
|
1307
|
+
const response = fixed_version && !status ? withStatusNotice(
|
|
1308
|
+
data,
|
|
1309
|
+
`fixed_version ${fixed_version} recorded. Status was not changed and the bug was NOT closed. To close it, pass status: "closed" explicitly.`
|
|
1310
|
+
) : data;
|
|
1311
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1312
|
+
}
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
var FEATURE_STATUS = z3.enum(["open", "planned", "in_progress", "shipped", "closed", "wontdo"]);
|
|
1316
|
+
function featureEndpoint() {
|
|
1317
|
+
return endpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
|
|
1318
|
+
}
|
|
1319
|
+
function featureOwnerEndpoint() {
|
|
1320
|
+
return ownerEndpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
|
|
1321
|
+
}
|
|
1322
|
+
var FEATURE_STATUSES = ["open", "planned", "in_progress", "shipped", "closed", "wontdo"];
|
|
1323
|
+
function registerListFeatureRequests(server) {
|
|
1324
|
+
server.registerTool(
|
|
1325
|
+
"list_feature_requests",
|
|
1326
|
+
{
|
|
1327
|
+
title: "List Feature Requests",
|
|
1328
|
+
description: "AskExe-internal only: list incoming customer feature requests from the support inbox. With no `limit`, returns the COMPLETE matching set (auto-paginated) \u2014 never a silent partial page. Pass `limit` explicitly for exactly one page (the response leads with a notice if more exist). Pass `count_only:true` for per-status totals without fetching records.",
|
|
1329
|
+
inputSchema: {
|
|
1330
|
+
status: z3.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("open"),
|
|
1331
|
+
priority: z3.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
1332
|
+
product: z3.string().optional().describe("Filter by product: exe-os, exe-create, exe-wiki, exe-gateway, exe-crm, exe-build, exe-chat, exe-auth, exe-monitor, exe-db, exe-erp"),
|
|
1333
|
+
limit: z3.number().int().min(1).max(500).optional().describe("Return exactly one page of this size. Omit to fetch the COMPLETE matching set instead of a 25-row default page."),
|
|
1334
|
+
offset: z3.number().int().min(0).default(0).describe("Number of results to skip. Only meaningful together with an explicit `limit`; use next_offset from a prior response."),
|
|
1335
|
+
count_only: z3.boolean().optional().describe("Skip fetching records; return counts per status only.")
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
async ({ status, priority, product, limit, offset, count_only }) => {
|
|
1339
|
+
const safeProduct = sanitizeProductFilter(product);
|
|
1340
|
+
const filteredUrl = new URL(featureEndpoint());
|
|
1341
|
+
if (priority) filteredUrl.searchParams.set("priority", priority);
|
|
1342
|
+
if (safeProduct) filteredUrl.searchParams.set("product", safeProduct);
|
|
1343
|
+
if (count_only) {
|
|
1344
|
+
const counts = await fetchStatusCounts(filteredUrl, FEATURE_STATUSES);
|
|
1345
|
+
const text = formatCounts(counts, "Feature request", ["open", "planned", "in_progress"]);
|
|
1346
|
+
return { content: [{ type: "text", text }], structuredContent: { counts } };
|
|
1347
|
+
}
|
|
1348
|
+
const url = new URL(filteredUrl.toString());
|
|
1349
|
+
url.searchParams.set("status", status);
|
|
1350
|
+
const page = await fetchAllOrPage(url, { limit, offset });
|
|
1351
|
+
const notice = buildTruncationNotice(page, status, offset);
|
|
1352
|
+
const body = JSON.stringify(
|
|
1353
|
+
{ items: page.items, count: page.count, total: page.total, has_more: page.has_more, next_offset: page.next_offset },
|
|
1354
|
+
null,
|
|
1355
|
+
2
|
|
1356
|
+
);
|
|
1357
|
+
return { content: [{ type: "text", text: notice.join("\n") + body }] };
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
function registerGetFeatureRequest(server) {
|
|
1362
|
+
server.registerTool(
|
|
1363
|
+
"get_feature_request",
|
|
1364
|
+
{
|
|
1365
|
+
title: "Get Feature Request",
|
|
1366
|
+
description: "AskExe-internal only: fetch one customer feature request with full payload.",
|
|
1367
|
+
inputSchema: { id: supportIdSchema }
|
|
1368
|
+
},
|
|
1369
|
+
async ({ id }) => {
|
|
1370
|
+
const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`);
|
|
1371
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1372
|
+
}
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
function registerTriageFeatureRequest(server) {
|
|
1376
|
+
server.registerTool(
|
|
1377
|
+
"triage_feature_request",
|
|
1378
|
+
{
|
|
1379
|
+
title: "Triage Feature Request",
|
|
1380
|
+
description: "AskExe-internal only: update feature request status, response notes, and version metadata.",
|
|
1381
|
+
inputSchema: {
|
|
1382
|
+
id: supportIdSchema,
|
|
1383
|
+
status: FEATURE_STATUS.optional(),
|
|
1384
|
+
response_notes: z3.string().optional(),
|
|
1385
|
+
add_note: z3.string().optional().describe("Append a timestamped progress note without changing status or replacing response_notes"),
|
|
1386
|
+
target_version: supportVersionSchema.optional(),
|
|
1387
|
+
shipped_version: supportVersionSchema.optional(),
|
|
1388
|
+
// bug b8e990ed: allow admin re-label of a feature's product. Typed with the
|
|
1389
|
+
// strict product schema (round 10 ingress validation), not a loose string.
|
|
1390
|
+
product: supportProductSchema.optional().describe("Re-label this feature's product: exe-os, exe-create, exe-wiki, exe-gateway, exe-crm, exe-build, exe-chat, exe-auth, exe-monitor, exe-db, exe-erp")
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
async ({ id, status, response_notes, add_note, target_version, shipped_version, product }) => {
|
|
1394
|
+
const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`, {
|
|
1395
|
+
method: "PATCH",
|
|
1396
|
+
body: sanitizedUpstreamBody({ status, response_notes, add_note, target_version, shipped_version, product }, "support-inbox:triage_feature")
|
|
1397
|
+
});
|
|
1398
|
+
const response = shipped_version && !status ? withStatusNotice(
|
|
1399
|
+
data,
|
|
1400
|
+
`shipped_version ${shipped_version} recorded. Status was not changed and the feature was NOT marked shipped or closed. To mark it shipped, pass status: "shipped" explicitly.`
|
|
1401
|
+
) : data;
|
|
1402
|
+
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
1403
|
+
}
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
var REVIEW_STATUS = z3.enum(["open", "in_review", "approved", "merged", "closed", "rejected"]);
|
|
1407
|
+
function reviewEndpoint() {
|
|
1408
|
+
return endpoint().replace(/\/bug-reports\/?$/, "/reviews");
|
|
1409
|
+
}
|
|
1410
|
+
var REVIEW_STATUSES = ["open", "in_review", "approved", "merged", "closed", "rejected"];
|
|
1411
|
+
function registerListReviews(server) {
|
|
1412
|
+
server.registerTool(
|
|
1413
|
+
"list_reviews",
|
|
1414
|
+
{
|
|
1415
|
+
title: "List Reviews",
|
|
1416
|
+
description: "AskExe-internal only: list incoming review requests from the support inbox. With no `limit`, returns the COMPLETE matching set (auto-paginated) \u2014 never a silent partial page. Pass `limit` explicitly for exactly one page (the response leads with a notice if more exist). Pass `count_only:true` for per-status totals without fetching records.",
|
|
1417
|
+
inputSchema: {
|
|
1418
|
+
status: z3.enum(["all", "open", "in_review", "approved", "merged", "closed", "rejected"]).default("open"),
|
|
1419
|
+
priority: z3.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
1420
|
+
limit: z3.number().int().min(1).max(500).optional().describe("Return exactly one page of this size. Omit to fetch the COMPLETE matching set instead of a 25-row default page."),
|
|
1421
|
+
offset: z3.number().int().min(0).default(0).describe("Number of results to skip. Only meaningful together with an explicit `limit`; use next_offset from a prior response."),
|
|
1422
|
+
count_only: z3.boolean().optional().describe("Skip fetching records; return counts per status only.")
|
|
1423
|
+
}
|
|
1424
|
+
},
|
|
1425
|
+
async ({ status, priority, limit, offset, count_only }) => {
|
|
1426
|
+
const filteredUrl = new URL(reviewEndpoint());
|
|
1427
|
+
if (priority) filteredUrl.searchParams.set("priority", priority);
|
|
1428
|
+
if (count_only) {
|
|
1429
|
+
const counts = await fetchStatusCounts(filteredUrl, REVIEW_STATUSES);
|
|
1430
|
+
const text = formatCounts(counts, "Review", ["open", "in_review"]);
|
|
1431
|
+
return { content: [{ type: "text", text }], structuredContent: { counts } };
|
|
1432
|
+
}
|
|
1433
|
+
const url = new URL(filteredUrl.toString());
|
|
1434
|
+
url.searchParams.set("status", status);
|
|
1435
|
+
const page = await fetchAllOrPage(url, { limit, offset });
|
|
1436
|
+
const notice = buildTruncationNotice(page, status, offset);
|
|
1437
|
+
const body = JSON.stringify(
|
|
1438
|
+
{ items: page.items, count: page.count, total: page.total, has_more: page.has_more, next_offset: page.next_offset },
|
|
1439
|
+
null,
|
|
1440
|
+
2
|
|
1441
|
+
);
|
|
1442
|
+
return { content: [{ type: "text", text: notice.join("\n") + body }] };
|
|
1443
|
+
}
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
function registerGetReview(server) {
|
|
1447
|
+
server.registerTool(
|
|
1448
|
+
"get_review",
|
|
1449
|
+
{
|
|
1450
|
+
title: "Get Review",
|
|
1451
|
+
description: "AskExe-internal only: fetch one review with full payload.",
|
|
1452
|
+
inputSchema: { id: supportIdSchema }
|
|
1453
|
+
},
|
|
1454
|
+
async ({ id }) => {
|
|
1455
|
+
const data = await requestJson(`${reviewEndpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`);
|
|
1456
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
function registerTriageReview(server) {
|
|
1461
|
+
server.registerTool(
|
|
1462
|
+
"triage_review",
|
|
1463
|
+
{
|
|
1464
|
+
title: "Triage Review",
|
|
1465
|
+
description: "AskExe-internal only: update review status, response notes, and resolution.",
|
|
1466
|
+
inputSchema: {
|
|
1467
|
+
id: supportIdSchema,
|
|
1468
|
+
status: REVIEW_STATUS.optional(),
|
|
1469
|
+
response_notes: z3.string().optional(),
|
|
1470
|
+
add_note: z3.string().optional().describe("Append a timestamped progress note without changing status or replacing response_notes"),
|
|
1471
|
+
resolution: z3.string().optional(),
|
|
1472
|
+
pr_url: z3.string().optional()
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
async ({ id, status, response_notes, add_note, resolution, pr_url }) => {
|
|
1476
|
+
const data = await requestJson(`${reviewEndpoint()}/${encodeURIComponent(sanitizeUpstreamId(id))}`, {
|
|
1477
|
+
method: "PATCH",
|
|
1478
|
+
body: sanitizedUpstreamBody({ status, response_notes, add_note, resolution, pr_url }, "support-inbox:triage_review")
|
|
1479
|
+
});
|
|
1480
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
export {
|
|
1486
|
+
resolveAttribution,
|
|
1487
|
+
verificationArtifactInputSchema,
|
|
1488
|
+
registerUpdateBugReport,
|
|
1489
|
+
registerListBugReports,
|
|
1490
|
+
registerGetBugReport,
|
|
1491
|
+
registerAddBugReportComment,
|
|
1492
|
+
registerGetBugReportComments,
|
|
1493
|
+
registerTriageBugReport,
|
|
1494
|
+
registerListFeatureRequests,
|
|
1495
|
+
registerGetFeatureRequest,
|
|
1496
|
+
registerTriageFeatureRequest,
|
|
1497
|
+
registerListReviews,
|
|
1498
|
+
registerGetReview,
|
|
1499
|
+
registerTriageReview
|
|
1500
|
+
};
|