@researai/deepscientist 1.5.16 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +309 -130
- package/AISB/catalog/aisb.b1.agentic_coding.yaml +244 -0
- package/AISB/catalog/aisb.b10.climate_earth.yaml +235 -0
- package/AISB/catalog/aisb.b11.model_efficiency.yaml +231 -0
- package/AISB/catalog/aisb.b12.embodied_ai.yaml +238 -0
- package/AISB/catalog/aisb.b2.agent_systems.yaml +229 -0
- package/AISB/catalog/aisb.b3.self_evolving_rl.yaml +237 -0
- package/AISB/catalog/aisb.b4.lm_reasoning.yaml +240 -0
- package/AISB/catalog/aisb.b5.math_proof.yaml +235 -0
- package/AISB/catalog/aisb.b6.research_process.yaml +243 -0
- package/AISB/catalog/aisb.b7.multimodal_fusion.yaml +232 -0
- package/AISB/catalog/aisb.b8.lifesci_drug.yaml +275 -0
- package/AISB/catalog/aisb.b9.material_science.yaml +237 -0
- package/AISB/catalog/aisb.t3.001_savvy.yaml +159 -0
- package/AISB/catalog/aisb.t3.001_savvy.zh.yaml +121 -0
- package/AISB/catalog/aisb.t3.002_pinet.yaml +189 -0
- package/AISB/catalog/aisb.t3.002_pinet.zh.yaml +130 -0
- package/AISB/catalog/aisb.t3.004_decentralattn.yaml +184 -0
- package/AISB/catalog/aisb.t3.004_decentralattn.zh.yaml +153 -0
- package/AISB/catalog/aisb.t3.005_tsae.yaml +193 -0
- package/AISB/catalog/aisb.t3.005_tsae.zh.yaml +139 -0
- package/AISB/catalog/aisb.t3.006_physense.yaml +194 -0
- package/AISB/catalog/aisb.t3.006_physense.zh.yaml +118 -0
- package/AISB/catalog/aisb.t3.007_reasoningiqa.yaml +169 -0
- package/AISB/catalog/aisb.t3.007_reasoningiqa.zh.yaml +133 -0
- package/AISB/catalog/aisb.t3.008_meanflows.yaml +188 -0
- package/AISB/catalog/aisb.t3.008_meanflows.zh.yaml +140 -0
- package/AISB/catalog/aisb.t3.009_scoremissing.yaml +179 -0
- package/AISB/catalog/aisb.t3.009_scoremissing.zh.yaml +119 -0
- package/AISB/catalog/aisb.t3.010_suitabilityfilter.yaml +221 -0
- package/AISB/catalog/aisb.t3.010_suitabilityfilter.zh.yaml +141 -0
- package/AISB/catalog/aisb.t3.011_osd.yaml +206 -0
- package/AISB/catalog/aisb.t3.011_osd.zh.yaml +163 -0
- package/AISB/catalog/aisb.t3.012_efficientqat.yaml +206 -0
- package/AISB/catalog/aisb.t3.012_efficientqat.zh.yaml +159 -0
- package/AISB/catalog/aisb.t3.013_appl.yaml +152 -0
- package/AISB/catalog/aisb.t3.013_appl.zh.yaml +126 -0
- package/AISB/catalog/aisb.t3.014_piguard.yaml +207 -0
- package/AISB/catalog/aisb.t3.014_piguard.zh.yaml +164 -0
- package/AISB/catalog/aisb.t3.015_frspec.yaml +209 -0
- package/AISB/catalog/aisb.t3.015_frspec.zh.yaml +163 -0
- package/AISB/catalog/aisb.t3.016_mathfusion.yaml +166 -0
- package/AISB/catalog/aisb.t3.016_mathfusion.zh.yaml +145 -0
- package/AISB/catalog/aisb.t3.017_multimodalglp.yaml +171 -0
- package/AISB/catalog/aisb.t3.017_multimodalglp.zh.yaml +122 -0
- package/AISB/catalog/aisb.t3.018_cotsynth.yaml +206 -0
- package/AISB/catalog/aisb.t3.018_cotsynth.zh.yaml +162 -0
- package/AISB/catalog/aisb.t3.019_dyscaleut.yaml +211 -0
- package/AISB/catalog/aisb.t3.019_dyscaleut.zh.yaml +148 -0
- package/AISB/catalog/aisb.t3.020_aristotle.yaml +173 -0
- package/AISB/catalog/aisb.t3.020_aristotle.zh.yaml +119 -0
- package/AISB/catalog/aisb.t3.021_tokenrecycling.yaml +160 -0
- package/AISB/catalog/aisb.t3.021_tokenrecycling.zh.yaml +129 -0
- package/AISB/catalog/aisb.t3.022_chainofreasoning.yaml +204 -0
- package/AISB/catalog/aisb.t3.022_chainofreasoning.zh.yaml +161 -0
- package/AISB/catalog/aisb.t3.023_guidedembed.yaml +211 -0
- package/AISB/catalog/aisb.t3.023_guidedembed.zh.yaml +189 -0
- package/AISB/catalog/aisb.t3.024_outputcentric.yaml +148 -0
- package/AISB/catalog/aisb.t3.024_outputcentric.zh.yaml +131 -0
- package/AISB/catalog/aisb.t3.025_deeper.yaml +143 -0
- package/AISB/catalog/aisb.t3.025_deeper.zh.yaml +116 -0
- package/AISB/catalog/aisb.t3.026_gartkg.yaml +195 -0
- package/AISB/catalog/aisb.t3.026_gartkg.zh.yaml +127 -0
- package/AISB/catalog/aisb.t3.027_citeeval.yaml +182 -0
- package/AISB/catalog/aisb.t3.027_citeeval.zh.yaml +135 -0
- package/AISB/catalog/aisb.t3.028_sbam.yaml +206 -0
- package/AISB/catalog/aisb.t3.028_sbam.zh.yaml +166 -0
- package/AISB/catalog/aisb.t3.029_cdqgeoembed.yaml +224 -0
- package/AISB/catalog/aisb.t3.029_cdqgeoembed.zh.yaml +142 -0
- package/AISB/catalog/aisb.t3.030_processrm.yaml +211 -0
- package/AISB/catalog/aisb.t3.030_processrm.zh.yaml +166 -0
- package/AISB/catalog/aisb.t3.031_circuitstability.yaml +172 -0
- package/AISB/catalog/aisb.t3.031_circuitstability.zh.yaml +134 -0
- package/AISB/catalog/aisb.t3.032_ptsolver.yaml +169 -0
- package/AISB/catalog/aisb.t3.032_ptsolver.zh.yaml +135 -0
- package/AISB/catalog/aisb.t3.033_gcse.yaml +144 -0
- package/AISB/catalog/aisb.t3.033_gcse.zh.yaml +126 -0
- package/AISB/catalog/aisb.t3.034_ensemblewm.yaml +183 -0
- package/AISB/catalog/aisb.t3.034_ensemblewm.zh.yaml +146 -0
- package/AISB/catalog/aisb.t3.035_moralvalueswa.yaml +207 -0
- package/AISB/catalog/aisb.t3.035_moralvalueswa.zh.yaml +165 -0
- package/AISB/catalog/aisb.t3.036_weakstrongpref.yaml +210 -0
- package/AISB/catalog/aisb.t3.036_weakstrongpref.zh.yaml +194 -0
- package/AISB/catalog/aisb.t3.037_dementiamask.yaml +172 -0
- package/AISB/catalog/aisb.t3.037_dementiamask.zh.yaml +132 -0
- package/AISB/catalog/aisb.t3.038_tinysam.yaml +284 -0
- package/AISB/catalog/aisb.t3.038_tinysam.zh.yaml +240 -0
- package/AISB/catalog/aisb.t3.039_calf.yaml +224 -0
- package/AISB/catalog/aisb.t3.039_calf.zh.yaml +194 -0
- package/AISB/catalog/aisb.t3.040_graniteguardian.yaml +199 -0
- package/AISB/catalog/aisb.t3.040_graniteguardian.zh.yaml +174 -0
- package/AISB/catalog/aisb.t3.041_amdm.yaml +149 -0
- package/AISB/catalog/aisb.t3.041_amdm.zh.yaml +137 -0
- package/AISB/catalog/aisb.t3.042_xpatch.yaml +216 -0
- package/AISB/catalog/aisb.t3.042_xpatch.zh.yaml +182 -0
- package/AISB/catalog/aisb.t3.043_vhm.yaml +268 -0
- package/AISB/catalog/aisb.t3.043_vhm.zh.yaml +193 -0
- package/AISB/catalog/aisb.t3.044_rgvi.yaml +224 -0
- package/AISB/catalog/aisb.t3.044_rgvi.zh.yaml +176 -0
- package/AISB/catalog/aisb.t3.045_pslstm.yaml +203 -0
- package/AISB/catalog/aisb.t3.045_pslstm.zh.yaml +179 -0
- package/AISB/catalog/aisb.t3.046_nonstatts.yaml +208 -0
- package/AISB/catalog/aisb.t3.046_nonstatts.zh.yaml +194 -0
- package/AISB/catalog/aisb.t3.047_timepfn.yaml +156 -0
- package/AISB/catalog/aisb.t3.047_timepfn.zh.yaml +124 -0
- package/AISB/catalog/aisb.t3.048_proxyspex.yaml +148 -0
- package/AISB/catalog/aisb.t3.048_proxyspex.zh.yaml +125 -0
- package/AISB/catalog/aisb.t3.049_hogwildinference.yaml +183 -0
- package/AISB/catalog/aisb.t3.049_hogwildinference.zh.yaml +138 -0
- package/AISB/catalog/aisb.t3.050_causalpfn.yaml +214 -0
- package/AISB/catalog/aisb.t3.050_causalpfn.zh.yaml +190 -0
- package/AISB/catalog/aisb.t3.051_flashtp.yaml +169 -0
- package/AISB/catalog/aisb.t3.051_flashtp.zh.yaml +124 -0
- package/AISB/catalog/aisb.t3.052_nsdiff.yaml +155 -0
- package/AISB/catalog/aisb.t3.052_nsdiff.zh.yaml +138 -0
- package/AISB/catalog/aisb.t3.053_k2vae.yaml +158 -0
- package/AISB/catalog/aisb.t3.053_k2vae.zh.yaml +132 -0
- package/AISB/catalog/aisb.t3.054_timebase.yaml +178 -0
- package/AISB/catalog/aisb.t3.054_timebase.zh.yaml +158 -0
- package/AISB/catalog/aisb.t3.055_csbrain.yaml +238 -0
- package/AISB/catalog/aisb.t3.055_csbrain.zh.yaml +184 -0
- package/AISB/catalog/aisb.t3.056_infosam.yaml +224 -0
- package/AISB/catalog/aisb.t3.056_infosam.zh.yaml +189 -0
- package/AISB/catalog/aisb.t3.057_mdreid.yaml +129 -0
- package/AISB/catalog/aisb.t3.057_mdreid.zh.yaml +117 -0
- package/AISB/catalog/aisb.t3.058_mindglitch.yaml +171 -0
- package/AISB/catalog/aisb.t3.058_mindglitch.zh.yaml +145 -0
- package/AISB/catalog/aisb.t3.059_selfsupervised.yaml +154 -0
- package/AISB/catalog/aisb.t3.059_selfsupervised.zh.yaml +125 -0
- package/AISB/catalog/aisb.t3.060_iaggad.yaml +121 -0
- package/AISB/catalog/aisb.t3.060_iaggad.zh.yaml +100 -0
- package/AISB/catalog/aisb.t3.061_hsgkn.yaml +136 -0
- package/AISB/catalog/aisb.t3.061_hsgkn.zh.yaml +113 -0
- package/AISB/catalog/aisb.t3.062_visionts.yaml +237 -0
- package/AISB/catalog/aisb.t3.062_visionts.zh.yaml +216 -0
- package/AISB/catalog/aisb.t3.063_tsrag.yaml +162 -0
- package/AISB/catalog/aisb.t3.063_tsrag.zh.yaml +138 -0
- package/AISB/catalog/aisb.t3.064_pir.yaml +221 -0
- package/AISB/catalog/aisb.t3.064_pir.zh.yaml +197 -0
- package/AISB/catalog/aisb.t3.065_proteinbinding.yaml +234 -0
- package/AISB/catalog/aisb.t3.065_proteinbinding.zh.yaml +167 -0
- package/AISB/catalog/aisb.t3.066_tropicalattention.yaml +267 -0
- package/AISB/catalog/aisb.t3.066_tropicalattention.zh.yaml +229 -0
- package/AISB/catalog/aisb.t3.067_kanad.yaml +193 -0
- package/AISB/catalog/aisb.t3.067_kanad.zh.yaml +167 -0
- package/AISB/catalog/aisb.t3.068_sempo.yaml +187 -0
- package/AISB/catalog/aisb.t3.068_sempo.zh.yaml +148 -0
- package/AISB/catalog/aisb.t3.069_treehfd.yaml +129 -0
- package/AISB/catalog/aisb.t3.069_treehfd.zh.yaml +111 -0
- package/AISB/catalog/aisb.t3.070_certifiedunlearning.yaml +224 -0
- package/AISB/catalog/aisb.t3.070_certifiedunlearning.zh.yaml +171 -0
- package/AISB/catalog/aisb.t3.071_neuralmjd.yaml +142 -0
- package/AISB/catalog/aisb.t3.071_neuralmjd.zh.yaml +120 -0
- package/AISB/catalog/aisb.t3.072_fedgmt.yaml +181 -0
- package/AISB/catalog/aisb.t3.072_fedgmt.zh.yaml +158 -0
- package/AISB/catalog/aisb.t3.073_rld.yaml +161 -0
- package/AISB/catalog/aisb.t3.073_rld.zh.yaml +129 -0
- package/AISB/catalog/aisb.t3.074_lsvi.yaml +163 -0
- package/AISB/catalog/aisb.t3.074_lsvi.zh.yaml +129 -0
- package/AISB/catalog/aisb.t3.075_treeslicedentropy.yaml +201 -0
- package/AISB/catalog/aisb.t3.075_treeslicedentropy.zh.yaml +148 -0
- package/AISB/catalog/aisb.t3.076_aanet.yaml +169 -0
- package/AISB/catalog/aisb.t3.076_aanet.zh.yaml +129 -0
- package/AISB/catalog/aisb.t3.077_cmnn.yaml +199 -0
- package/AISB/catalog/aisb.t3.077_cmnn.zh.yaml +165 -0
- package/AISB/catalog/aisb.t3.078_conformalanomaly.yaml +146 -0
- package/AISB/catalog/aisb.t3.078_conformalanomaly.zh.yaml +117 -0
- package/AISB/catalog/aisb.t3.079_dpfkmeans.yaml +131 -0
- package/AISB/catalog/aisb.t3.079_dpfkmeans.zh.yaml +104 -0
- package/AISB/catalog/aisb.t3.080_latentscorereweight.yaml +169 -0
- package/AISB/catalog/aisb.t3.080_latentscorereweight.zh.yaml +123 -0
- package/AISB/catalog/aisb.t3.081_qmamba.yaml +150 -0
- package/AISB/catalog/aisb.t3.081_qmamba.zh.yaml +117 -0
- package/AISB/catalog/aisb.t3.082_onlinellmrouting.yaml +160 -0
- package/AISB/catalog/aisb.t3.082_onlinellmrouting.zh.yaml +133 -0
- package/AISB/catalog/aisb.t3.083_starformer.yaml +178 -0
- package/AISB/catalog/aisb.t3.083_starformer.zh.yaml +140 -0
- package/AISB/catalog/aisb.t3.084_ift.yaml +139 -0
- package/AISB/catalog/aisb.t3.084_ift.zh.yaml +111 -0
- package/AISB/catalog/aisb.t3.085_neuralsurv.yaml +183 -0
- package/AISB/catalog/aisb.t3.085_neuralsurv.zh.yaml +143 -0
- package/AISB/catalog/aisb.t3.086_stella.yaml +197 -0
- package/AISB/catalog/aisb.t3.086_stella.zh.yaml +142 -0
- package/AISB/catalog/aisb.t3.087_moses.yaml +167 -0
- package/AISB/catalog/aisb.t3.087_moses.zh.yaml +132 -0
- package/AISB/catalog/aisb.t3.088_channelnorm.yaml +140 -0
- package/AISB/catalog/aisb.t3.088_channelnorm.zh.yaml +109 -0
- package/AISB/catalog/aisb.t3.089_causalvelocity.yaml +730 -0
- package/AISB/catalog/aisb.t3.089_causalvelocity.zh.yaml +668 -0
- package/AISB/catalog/aisb.t3.090_rstib.yaml +144 -0
- package/AISB/catalog/aisb.t3.090_rstib.zh.yaml +109 -0
- package/AISB/catalog/aisb.t3.091_timeawarecausal.yaml +132 -0
- package/AISB/catalog/aisb.t3.091_timeawarecausal.zh.yaml +107 -0
- package/AISB/catalog/aisb.t3.092_kmeanslocalopt.yaml +138 -0
- package/AISB/catalog/aisb.t3.092_kmeanslocalopt.zh.yaml +110 -0
- package/AISB/catalog/aisb.t3.093_fedwmsam.yaml +134 -0
- package/AISB/catalog/aisb.t3.093_fedwmsam.zh.yaml +106 -0
- package/AISB/catalog/aisb.t3.094_boundre.yaml +147 -0
- package/AISB/catalog/aisb.t3.094_boundre.zh.yaml +114 -0
- package/AISB/catalog/aisb.t3.095_fastfeaturecp.yaml +153 -0
- package/AISB/catalog/aisb.t3.095_fastfeaturecp.zh.yaml +118 -0
- package/AISB/catalog/aisb.t3.096_m3svm.yaml +189 -0
- package/AISB/catalog/aisb.t3.096_m3svm.zh.yaml +149 -0
- package/AISB/catalog/aisb.t3.097_wassersteintl.yaml +212 -0
- package/AISB/catalog/aisb.t3.097_wassersteintl.zh.yaml +169 -0
- package/AISB/catalog/aisb.t3.098_xmahalanobis.yaml +171 -0
- package/AISB/catalog/aisb.t3.098_xmahalanobis.zh.yaml +127 -0
- package/AISB/catalog/aisb.t3.099_ollalanding.yaml +248 -0
- package/AISB/catalog/aisb.t3.099_ollalanding.zh.yaml +182 -0
- package/AISB/catalog/aisb.t3.100_invmissingdata.yaml +179 -0
- package/AISB/catalog/aisb.t3.100_invmissingdata.zh.yaml +150 -0
- package/AISB/catalog/aisb.t3.101_acia.yaml +164 -0
- package/AISB/catalog/aisb.t3.101_acia.zh.yaml +109 -0
- package/AISB/catalog/aisb.t3.102_stochasticff.yaml +178 -0
- package/AISB/catalog/aisb.t3.102_stochasticff.zh.yaml +130 -0
- package/AISB/catalog/aisb.t3.103_qdcp.yaml +150 -0
- package/AISB/catalog/aisb.t3.103_qdcp.zh.yaml +116 -0
- package/AISB/catalog/aisb.t3.104_balancedactiveinf.yaml +137 -0
- package/AISB/catalog/aisb.t3.104_balancedactiveinf.zh.yaml +104 -0
- package/AISB/catalog/aisb.t3.105_binaryclasseval.yaml +161 -0
- package/AISB/catalog/aisb.t3.105_binaryclasseval.zh.yaml +130 -0
- package/AISB/image/001_aisb.t3.001_savvy.jpg +0 -0
- package/AISB/image/002_aisb.t3.002_pinet.jpg +0 -0
- package/AISB/image/003_aisb.t3.003_dmsqd.jpg +0 -0
- package/AISB/image/004_aisb.t3.004_decentralattn.jpg +0 -0
- package/AISB/image/005_aisb.t3.005_tsae.jpg +0 -0
- package/AISB/image/006_aisb.t3.006_physense.jpg +0 -0
- package/AISB/image/007_aisb.t3.007_reasoningiqa.jpg +0 -0
- package/AISB/image/008_aisb.t3.008_meanflows.jpg +0 -0
- package/AISB/image/009_aisb.t3.009_scoremissing.jpg +0 -0
- package/AISB/image/010_aisb.t3.010_suitabilityfilter.jpg +0 -0
- package/AISB/image/011_aisb.t3.011_osd.jpg +0 -0
- package/AISB/image/012_aisb.t3.012_efficientqat.jpg +0 -0
- package/AISB/image/013_aisb.t3.013_appl.jpg +0 -0
- package/AISB/image/014_aisb.t3.014_piguard.jpg +0 -0
- package/AISB/image/015_aisb.t3.015_frspec.jpg +0 -0
- package/AISB/image/016_aisb.t3.016_mathfusion.jpg +0 -0
- package/AISB/image/017_aisb.t3.017_multimodalglp.jpg +0 -0
- package/AISB/image/018_aisb.t3.018_cotsynth.jpg +0 -0
- package/AISB/image/019_aisb.t3.019_dyscaleut.jpg +0 -0
- package/AISB/image/020_aisb.t3.020_aristotle.jpg +0 -0
- package/AISB/image/021_aisb.t3.021_tokenrecycling.jpg +0 -0
- package/AISB/image/022_aisb.t3.022_chainofreasoning.jpg +0 -0
- package/AISB/image/023_aisb.t3.023_guidedembed.jpg +0 -0
- package/AISB/image/024_aisb.t3.024_outputcentric.jpg +0 -0
- package/AISB/image/025_aisb.t3.025_deeper.jpg +0 -0
- package/AISB/image/026_aisb.t3.026_gartkg.jpg +0 -0
- package/AISB/image/027_aisb.t3.027_citeeval.jpg +0 -0
- package/AISB/image/028_aisb.t3.028_sbam.jpg +0 -0
- package/AISB/image/029_aisb.t3.029_cdqgeoembed.jpg +0 -0
- package/AISB/image/030_aisb.t3.030_processrm.jpg +0 -0
- package/AISB/image/031_aisb.t3.031_circuitstability.jpg +0 -0
- package/AISB/image/032_aisb.t3.032_ptsolver.jpg +0 -0
- package/AISB/image/033_aisb.t3.033_gcse.jpg +0 -0
- package/AISB/image/034_aisb.t3.034_ensemblewm.jpg +0 -0
- package/AISB/image/035_aisb.t3.035_moralvalueswa.jpg +0 -0
- package/AISB/image/036_aisb.t3.036_weakstrongpref.jpg +0 -0
- package/AISB/image/037_aisb.t3.037_dementiamask.jpg +0 -0
- package/AISB/image/038_aisb.t3.038_tinysam.jpg +0 -0
- package/AISB/image/039_aisb.t3.039_calf.jpg +0 -0
- package/AISB/image/040_aisb.t3.040_graniteguardian.jpg +0 -0
- package/AISB/image/041_aisb.t3.041_amdm.jpg +0 -0
- package/AISB/image/042_aisb.t3.042_xpatch.jpg +0 -0
- package/AISB/image/043_aisb.t3.043_vhm.jpg +0 -0
- package/AISB/image/044_aisb.t3.044_rgvi.jpg +0 -0
- package/AISB/image/045_aisb.t3.045_pslstm.jpg +0 -0
- package/AISB/image/046_aisb.t3.046_nonstatts.jpg +0 -0
- package/AISB/image/047_aisb.t3.047_timepfn.jpg +0 -0
- package/AISB/image/048_aisb.t3.048_proxyspex.jpg +0 -0
- package/AISB/image/049_aisb.t3.049_hogwildinference.jpg +0 -0
- package/AISB/image/050_aisb.t3.050_causalpfn.jpg +0 -0
- package/AISB/image/051_aisb.t3.051_flashtp.jpg +0 -0
- package/AISB/image/052_aisb.t3.052_nsdiff.jpg +0 -0
- package/AISB/image/053_aisb.t3.053_k2vae.jpg +0 -0
- package/AISB/image/054_aisb.t3.054_timebase.jpg +0 -0
- package/AISB/image/055_aisb.t3.055_csbrain.jpg +0 -0
- package/AISB/image/056_aisb.t3.056_infosam.jpg +0 -0
- package/AISB/image/057_aisb.t3.057_mdreid.jpg +0 -0
- package/AISB/image/058_aisb.t3.058_mindglitch.jpg +0 -0
- package/AISB/image/059_aisb.t3.059_selfsupervised.jpg +0 -0
- package/AISB/image/060_aisb.t3.060_iaggad.jpg +0 -0
- package/AISB/image/061_aisb.t3.061_hsgkn.jpg +0 -0
- package/AISB/image/062_aisb.t3.062_visionts.jpg +0 -0
- package/AISB/image/063_aisb.t3.063_tsrag.jpg +0 -0
- package/AISB/image/064_aisb.t3.064_pir.jpg +0 -0
- package/AISB/image/065_aisb.t3.065_proteinbinding.jpg +0 -0
- package/AISB/image/066_aisb.t3.066_tropicalattention.jpg +0 -0
- package/AISB/image/067_aisb.t3.067_kanad.jpg +0 -0
- package/AISB/image/068_aisb.t3.068_sempo.jpg +0 -0
- package/AISB/image/069_aisb.t3.069_treehfd.jpg +0 -0
- package/AISB/image/070_aisb.t3.070_certifiedunlearning.jpg +0 -0
- package/AISB/image/071_aisb.t3.071_neuralmjd.jpg +0 -0
- package/AISB/image/072_aisb.t3.072_fedgmt.jpg +0 -0
- package/AISB/image/073_aisb.t3.073_rld.jpg +0 -0
- package/AISB/image/074_aisb.t3.074_lsvi.jpg +0 -0
- package/AISB/image/075_aisb.t3.075_treeslicedentropy.jpg +0 -0
- package/AISB/image/076_aisb.t3.076_aanet.jpg +0 -0
- package/AISB/image/077_aisb.t3.077_cmnn.jpg +0 -0
- package/AISB/image/078_aisb.t3.078_conformalanomaly.jpg +0 -0
- package/AISB/image/079_aisb.t3.079_dpfkmeans.jpg +0 -0
- package/AISB/image/080_aisb.t3.080_latentscorereweight.jpg +0 -0
- package/AISB/image/081_aisb.t3.081_qmamba.jpg +0 -0
- package/AISB/image/082_aisb.t3.082_onlinellmrouting.jpg +0 -0
- package/AISB/image/083_aisb.t3.083_starformer.jpg +0 -0
- package/AISB/image/084_aisb.t3.084_ift.jpg +0 -0
- package/AISB/image/085_aisb.t3.085_neuralsurv.jpg +0 -0
- package/AISB/image/086_aisb.t3.086_stella.jpg +0 -0
- package/AISB/image/087_aisb.t3.087_moses.jpg +0 -0
- package/AISB/image/088_aisb.t3.088_channelnorm.jpg +0 -0
- package/AISB/image/089_aisb.t3.089_causalvelocity.jpg +0 -0
- package/AISB/image/090_aisb.t3.090_rstib.jpg +0 -0
- package/AISB/image/091_aisb.t3.091_timeawarecausal.jpg +0 -0
- package/AISB/image/092_aisb.t3.092_kmeanslocalopt.jpg +0 -0
- package/AISB/image/093_aisb.t3.093_fedwmsam.jpg +0 -0
- package/AISB/image/094_aisb.t3.094_boundre.jpg +0 -0
- package/AISB/image/095_aisb.t3.095_fastfeaturecp.jpg +0 -0
- package/AISB/image/096_aisb.t3.096_m3svm.jpg +0 -0
- package/AISB/image/097_aisb.t3.097_wassersteintl.jpg +0 -0
- package/AISB/image/098_aisb.t3.098_xmahalanobis.jpg +0 -0
- package/AISB/image/099_aisb.t3.099_ollalanding.jpg +0 -0
- package/AISB/image/100_aisb.t3.100_invmissingdata.jpg +0 -0
- package/AISB/image/101_aisb.t3.101_acia.jpg +0 -0
- package/AISB/image/102_aisb.t3.102_stochasticff.jpg +0 -0
- package/AISB/image/103_aisb.t3.103_qdcp.jpg +0 -0
- package/AISB/image/104_aisb.t3.104_balancedactiveinf.jpg +0 -0
- package/AISB/image/105_aisb.t3.105_binaryclasseval.jpg +0 -0
- package/AISB/image/106_aisb.t1.reasoning_lite.jpg +0 -0
- package/AISB/image/107_aisb.t2.paper_audit.jpg +0 -0
- package/AISB/image/108_aisb.t3.multi_gpu_search.jpg +0 -0
- package/AISB/image/109_aisb.t3.tdc_admet.jpg +0 -0
- package/AISB/image/aisb.b1.agentic_coding.svg +16 -0
- package/AISB/image/aisb.b10.climate_earth.svg +16 -0
- package/AISB/image/aisb.b11.model_efficiency.svg +16 -0
- package/AISB/image/aisb.b12.embodied_ai.svg +16 -0
- package/AISB/image/aisb.b2.agent_systems.svg +16 -0
- package/AISB/image/aisb.b3.self_evolving_rl.svg +16 -0
- package/AISB/image/aisb.b4.lm_reasoning.svg +16 -0
- package/AISB/image/aisb.b5.math_proof.svg +16 -0
- package/AISB/image/aisb.b6.research_process.svg +16 -0
- package/AISB/image/aisb.b7.multimodal_fusion.svg +16 -0
- package/AISB/image/aisb.b8.lifesci_drug.svg +16 -0
- package/AISB/image/aisb.b9.material_science.svg +16 -0
- package/README.md +196 -32
- package/bin/ds.js +924 -66
- package/docs/en/00_QUICK_START.md +195 -18
- package/docs/en/01_SETTINGS_REFERENCE.md +468 -96
- package/docs/en/02_START_RESEARCH_GUIDE.md +26 -5
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +14 -3
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +2 -0
- package/docs/en/05_TUI_GUIDE.md +171 -2
- package/docs/en/07_MEMORY_AND_MCP.md +38 -2
- package/docs/en/09_DOCTOR.md +78 -7
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +38 -1
- package/docs/en/11_LICENSE_AND_RISK.md +4 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +15 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +9 -0
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +624 -180
- package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +14 -0
- package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +14 -0
- package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +14 -0
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +386 -0
- package/docs/en/22_BENCHSTORE_YAML_REFERENCE.md +469 -0
- package/docs/en/23_BENCHSTORE_GITHUB_RELEASES_SPEC.md +316 -0
- package/docs/en/24_CLAUDE_CODE_PROVIDER_SETUP.md +469 -0
- package/docs/en/25_OPENCODE_PROVIDER_SETUP.md +653 -0
- package/docs/en/26_CITATION_AND_ATTRIBUTION.md +119 -0
- package/docs/en/27_KIMI_CODE_PROVIDER_SETUP.md +180 -0
- package/docs/en/28_DISCORD_CONNECTOR_GUIDE.md +61 -0
- package/docs/en/29_SLACK_CONNECTOR_GUIDE.md +60 -0
- package/docs/en/30_SETTINGS_CONTROL_CENTER_GUIDE.md +371 -0
- package/docs/en/{19_LOCAL_BROWSER_AUTH.md → 31_LOCAL_BROWSER_AUTH.md} +1 -1
- package/docs/en/32_WINDOWS_WSL2_DEPLOYMENT_GUIDE.md +273 -0
- package/docs/en/33_WORKSPACE_EXPLORER_QA.md +121 -0
- package/docs/en/91_DEVELOPMENT.md +266 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +24 -19
- package/docs/en/README.md +48 -7
- package/docs/images/admin/admin-connectors-health-en.png +0 -0
- package/docs/images/admin/admin-controllers-en.png +0 -0
- package/docs/images/admin/admin-diagnostics-en.png +0 -0
- package/docs/images/admin/admin-errors-en.png +0 -0
- package/docs/images/admin/admin-issues-en.png +0 -0
- package/docs/images/admin/admin-logs-en.png +0 -0
- package/docs/images/admin/admin-quest-detail-en.png +0 -0
- package/docs/images/admin/admin-quests-en.png +0 -0
- package/docs/images/admin/admin-repairs-en.png +0 -0
- package/docs/images/admin/admin-runtime-en.png +0 -0
- package/docs/images/admin/admin-search-en.png +0 -0
- package/docs/images/admin/admin-stats-en.png +0 -0
- package/docs/images/admin/admin-summary-en.png +0 -0
- package/docs/images/connectors/connector-discord-en.png +0 -0
- package/docs/images/connectors/connector-feishu-en.png +0 -0
- package/docs/images/connectors/connector-lingzhu-en.png +0 -0
- package/docs/images/connectors/connector-qq-en.png +0 -0
- package/docs/images/connectors/connector-slack-en.png +0 -0
- package/docs/images/connectors/connector-telegram-en.png +0 -0
- package/docs/images/connectors/connector-weixin-en.png +0 -0
- package/docs/images/connectors/connector-whatsapp-en.png +0 -0
- package/docs/images/settings/settings-baselines-en.png +0 -0
- package/docs/images/settings/settings-config-en.png +0 -0
- package/docs/images/settings/settings-connectors-overview-en.png +0 -0
- package/docs/images/settings/settings-deepxiv-en.png +0 -0
- package/docs/images/settings/settings-mcp-servers-en.png +0 -0
- package/docs/images/settings/settings-plugins-en.png +0 -0
- package/docs/images/settings/settings-runners-en.png +0 -0
- package/docs/zh/00_QUICK_START.md +142 -18
- package/docs/zh/01_SETTINGS_REFERENCE.md +219 -98
- package/docs/zh/02_START_RESEARCH_GUIDE.md +26 -5
- package/docs/zh/05_TUI_GUIDE.md +171 -2
- package/docs/zh/07_MEMORY_AND_MCP.md +29 -2
- package/docs/zh/09_DOCTOR.md +54 -8
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +24 -1
- package/docs/zh/11_LICENSE_AND_RISK.md +4 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +15 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +9 -0
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +552 -181
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +384 -0
- package/docs/zh/22_BENCHSTORE_YAML_REFERENCE.md +459 -0
- package/docs/zh/23_BENCHSTORE_GITHUB_RELEASES_SPEC.md +287 -0
- package/docs/zh/23_CLAUDE_RUNNER_GUIDE.md +103 -0
- package/docs/zh/24_CLAUDE_CODE_PROVIDER_SETUP.md +460 -0
- package/docs/zh/25_OPENCODE_PROVIDER_SETUP.md +660 -0
- package/docs/zh/26_CITATION_AND_ATTRIBUTION.md +102 -0
- package/docs/zh/27_KIMI_CODE_PROVIDER_SETUP.md +51 -0
- package/docs/zh/{19_LOCAL_BROWSER_AUTH.md → 31_LOCAL_BROWSER_AUTH.md} +1 -1
- package/docs/zh/32_WINDOWS_WSL2_DEPLOYMENT_GUIDE.md +264 -0
- package/docs/zh/33_WORKSPACE_EXPLORER_QA.md +127 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +23 -19
- package/docs/zh/README.md +33 -7
- package/install.sh +168 -20
- package/package.json +5 -1
- package/pyproject.toml +2 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +13 -0
- package/src/deepscientist/admin/__init__.py +3 -0
- package/src/deepscientist/admin/charts.py +681 -0
- package/src/deepscientist/admin/logs.py +119 -0
- package/src/deepscientist/admin/repairs.py +217 -0
- package/src/deepscientist/admin/service.py +1310 -0
- package/src/deepscientist/admin/system_info.py +700 -0
- package/src/deepscientist/admin/tasks.py +465 -0
- package/src/deepscientist/admin/tool_metrics.py +600 -0
- package/src/deepscientist/artifact/guidance.py +8 -4
- package/src/deepscientist/artifact/schemas.py +115 -0
- package/src/deepscientist/artifact/service.py +4268 -260
- package/src/deepscientist/bash_exec/monitor.py +30 -3
- package/src/deepscientist/bash_exec/service.py +134 -1
- package/src/deepscientist/benchstore/__init__.py +4 -0
- package/src/deepscientist/benchstore/prompt_builder.py +224 -0
- package/src/deepscientist/benchstore/service.py +1716 -0
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/channels/weixin_ilink.py +8 -1
- package/src/deepscientist/cli.py +92 -17
- package/src/deepscientist/codex_cli_compat.py +187 -74
- package/src/deepscientist/config/models.py +82 -11
- package/src/deepscientist/config/service.py +1077 -93
- package/src/deepscientist/connector/weixin_support.py +48 -17
- package/src/deepscientist/daemon/api/handlers.py +827 -235
- package/src/deepscientist/daemon/api/router.py +81 -1
- package/src/deepscientist/daemon/app.py +1512 -85
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +277 -0
- package/src/deepscientist/doctor.py +407 -56
- package/src/deepscientist/evidence_packets.py +590 -0
- package/src/deepscientist/home.py +52 -4
- package/src/deepscientist/kimi_cli_compat.py +50 -0
- package/src/deepscientist/latex_runtime.py +2 -2
- package/src/deepscientist/mcp/context.py +2 -0
- package/src/deepscientist/mcp/schemas.py +114 -0
- package/src/deepscientist/mcp/server.py +1566 -126
- package/src/deepscientist/memory/service.py +203 -16
- package/src/deepscientist/process_control.py +8 -1
- package/src/deepscientist/prompts/builder.py +850 -88
- package/src/deepscientist/quest/__init__.py +2 -2
- package/src/deepscientist/quest/layout.py +12 -1
- package/src/deepscientist/quest/node_traces.py +10 -0
- package/src/deepscientist/quest/service.py +1852 -161
- package/src/deepscientist/quest/stage_views.py +1 -1
- package/src/deepscientist/runners/__init__.py +18 -0
- package/src/deepscientist/runners/base.py +89 -1
- package/src/deepscientist/runners/builtins.py +13 -1
- package/src/deepscientist/runners/claude.py +391 -0
- package/src/deepscientist/runners/codex.py +480 -35
- package/src/deepscientist/runners/codex_telemetry.py +127 -0
- package/src/deepscientist/runners/kimi.py +334 -0
- package/src/deepscientist/runners/metadata.py +68 -0
- package/src/deepscientist/runners/opencode.py +414 -0
- package/src/deepscientist/runners/runtime_overrides.py +100 -0
- package/src/deepscientist/runners/simple_cli.py +538 -0
- package/src/deepscientist/runtime_storage.py +303 -0
- package/src/deepscientist/shared.py +80 -16
- package/src/deepscientist/skills/installer.py +37 -0
- package/src/deepscientist/skills/registry.py +2 -0
- package/src/deepscientist/tinytex.py +2 -2
- package/src/deepscientist/tui.py +10 -3
- package/src/prompts/benchstore/system.md +77 -0
- package/src/prompts/connectors/qq.md +33 -2
- package/src/prompts/connectors/weixin.md +208 -23
- package/src/prompts/contracts/admin_ops.md +74 -0
- package/src/prompts/contracts/admin_ops_knowledge.md +138 -0
- package/src/prompts/contracts/shared_interaction.md +5 -10
- package/src/prompts/start_setup/system.md +422 -0
- package/src/prompts/system.md +411 -304
- package/src/prompts/system_copilot.md +89 -0
- package/src/skills/analysis-campaign/SKILL.md +239 -578
- package/src/skills/analysis-campaign/references/artifact-flow-examples.md +102 -0
- package/src/skills/analysis-campaign/references/boundary-cases.md +98 -0
- package/src/skills/analysis-campaign/references/campaign-checklist-template.md +39 -24
- package/src/skills/analysis-campaign/references/campaign-design.md +26 -10
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +53 -54
- package/src/skills/analysis-campaign/references/operational-guidance.md +97 -0
- package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +10 -20
- package/src/skills/baseline/SKILL.md +183 -461
- package/src/skills/baseline/references/artifact-flow-examples.md +106 -0
- package/src/skills/baseline/references/artifact-payload-examples.md +1 -1
- package/src/skills/baseline/references/baseline-checklist-template.md +27 -35
- package/src/skills/baseline/references/baseline-plan-template.md +37 -76
- package/src/skills/baseline/references/boundary-cases.md +86 -0
- package/src/skills/baseline/references/codebase-audit-checklist.md +2 -6
- package/src/skills/baseline/references/comparability-contract.md +7 -12
- package/src/skills/baseline/references/operational-guidance.md +56 -0
- package/src/skills/baseline/references/route-selection.md +5 -25
- package/src/skills/decision/SKILL.md +113 -306
- package/src/skills/decision/references/checkpoint-memory-template.md +47 -0
- package/src/skills/decision/references/operational-guidance.md +94 -0
- package/src/skills/decision/references/research-route-criteria.md +7 -8
- package/src/skills/decision/references/strategic-decision-template.md +13 -26
- package/src/skills/experiment/SKILL.md +132 -670
- package/src/skills/experiment/references/execution-playbook.md +374 -0
- package/src/skills/experiment/references/main-experiment-checklist-template.md +26 -2
- package/src/skills/experiment/references/main-experiment-plan-template.md +28 -17
- package/src/skills/experiment/references/operational-guidance.md +108 -0
- package/src/skills/finalize/SKILL.md +62 -0
- package/src/skills/finalize/references/checkpoint-memory-template.md +49 -0
- package/src/skills/finalize/references/resume-packet-template.md +7 -0
- package/src/skills/idea/SKILL.md +228 -15
- package/src/skills/idea/references/controlled-brainstorming-playbook.md +78 -0
- package/src/skills/idea/references/current-board-packet-template.md +61 -0
- package/src/skills/idea/references/high-value-idea-sourcing.md +119 -0
- package/src/skills/idea/references/idea-generation-playbook.md +21 -0
- package/src/skills/idea/references/idea-thinking-flow.md +6 -0
- package/src/skills/idea/references/literature-survey-template.md +3 -0
- package/src/skills/idea/references/objective-contract-template.md +54 -0
- package/src/skills/idea/references/outline-seeding-example.md +56 -0
- package/src/skills/idea/references/pre-idea-draft-template.md +105 -0
- package/src/skills/idea/references/related-work-playbook.md +75 -2
- package/src/skills/idea/references/research-history-playbook.md +114 -0
- package/src/skills/idea/references/selection-gate.md +58 -6
- package/src/skills/intake-audit/SKILL.md +43 -2
- package/src/skills/intake-audit/references/state-audit-template.md +10 -0
- package/src/skills/nature-data/SKILL.md +128 -0
- package/src/skills/nature-data/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/nature-data/agents/openai.yaml +4 -0
- package/src/skills/nature-data/references/chinese-author-alignment.md +84 -0
- package/src/skills/nature-data/references/fair-metadata-checklist.md +105 -0
- package/src/skills/nature-data/references/policy-principles.md +103 -0
- package/src/skills/nature-data/references/repository-and-identifiers.md +96 -0
- package/src/skills/nature-data/references/source-basis.md +54 -0
- package/src/skills/nature-data/references/statement-patterns.md +153 -0
- package/src/skills/nature-figure/SKILL.md +197 -0
- package/src/skills/nature-figure/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/nature-figure/agents/openai.yaml +4 -0
- package/src/skills/nature-figure/evals/evals.json +37 -0
- package/src/skills/nature-figure/references/api.md +428 -0
- package/src/skills/nature-figure/references/backend-selection.md +100 -0
- package/src/skills/nature-figure/references/chart-types.md +281 -0
- package/src/skills/nature-figure/references/common-patterns.md +349 -0
- package/src/skills/nature-figure/references/design-theory.md +436 -0
- package/src/skills/nature-figure/references/figure-contract.md +93 -0
- package/src/skills/nature-figure/references/nature-2026-observations.md +112 -0
- package/src/skills/nature-figure/references/qa-contract.md +119 -0
- package/src/skills/nature-figure/references/r-template-index.md +66 -0
- package/src/skills/nature-figure/references/r-workflow.md +161 -0
- package/src/skills/nature-figure/references/tutorials.md +250 -0
- package/src/skills/nature-paper2ppt/SKILL.md +507 -0
- package/src/skills/nature-paper2ppt/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/nature-paper2ppt/agents/openai.yaml +4 -0
- package/src/skills/nature-polishing/SKILL.md +385 -0
- package/src/skills/nature-polishing/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/nature-polishing/agents/openai.yaml +4 -0
- package/src/skills/nature-polishing/references/phrasebank-playbook.md +162 -0
- package/src/skills/nature-polishing/references/section-moves.md +240 -0
- package/src/skills/nature-polishing/references/style-guardrails.md +94 -0
- package/src/skills/nature-polishing/references/writing-strategy.md +148 -0
- package/src/skills/optimize/SKILL.md +177 -1568
- package/src/skills/optimize/references/brief-shaping-playbook.md +95 -0
- package/src/skills/optimize/references/candidate-board-template.md +13 -0
- package/src/skills/optimize/references/candidate-ranking-template.md +51 -0
- package/src/skills/optimize/references/codegen-route-playbook.md +50 -0
- package/src/skills/optimize/references/debug-response-template.md +29 -0
- package/src/skills/optimize/references/frontier-review-template.md +32 -0
- package/src/skills/optimize/references/fusion-playbook.md +36 -0
- package/src/skills/optimize/references/method-brief-template.md +73 -0
- package/src/skills/optimize/references/operational-guidance.md +621 -0
- package/src/skills/optimize/references/optimization-memory-template.md +30 -0
- package/src/skills/optimize/references/optimize-checklist-template.md +18 -0
- package/src/skills/optimize/references/plateau-response-playbook.md +28 -0
- package/src/skills/optimize/references/prompt-patterns.md +49 -0
- package/src/skills/paper-outline/SKILL.md +227 -0
- package/src/skills/paper-outline/references/outline-patterns.md +87 -0
- package/src/skills/paper-plot/SKILL.md +79 -0
- package/src/skills/paper-plot/agents/openai.yaml +4 -0
- package/src/skills/paper-plot/references/bar_grouped_hatch.md +96 -0
- package/src/skills/paper-plot/references/bar_paired_delta.md +72 -0
- package/src/skills/paper-plot/references/line_confidence_band.md +75 -0
- package/src/skills/paper-plot/references/line_loss_with_inset.md +65 -0
- package/src/skills/paper-plot/references/line_training_curve.md +44 -0
- package/src/skills/paper-plot/references/radar_dual_series.md +59 -0
- package/src/skills/paper-plot/references/scatter_broken_axis.md +59 -0
- package/src/skills/paper-plot/references/scatter_tsne_cluster.md +72 -0
- package/src/skills/paper-plot/scripts/bar_memevolve.py +109 -0
- package/src/skills/paper-plot/scripts/bar_spice.py +166 -0
- package/src/skills/paper-plot/scripts/line_aime.py +94 -0
- package/src/skills/paper-plot/scripts/line_loss_inset.py +157 -0
- package/src/skills/paper-plot/scripts/line_selfdistill.py +168 -0
- package/src/skills/paper-plot/scripts/radar_dora.py +151 -0
- package/src/skills/paper-plot/scripts/scatter_break.py +169 -0
- package/src/skills/paper-plot/scripts/scatter_tsne.py +133 -0
- package/src/skills/rebuttal/SKILL.md +9 -0
- package/src/skills/references/tool-usage-by-stage.md +438 -0
- package/src/skills/review/SKILL.md +105 -7
- package/src/skills/science/PROVENANCE.md +44 -0
- package/src/skills/science/SKILL.md +137 -0
- package/src/skills/science/references/artifact-science-tool.md +110 -0
- package/src/skills/science/references/claim-type-discipline.md +56 -0
- package/src/skills/science/references/domain-index.md +422 -0
- package/src/skills/science/references/hpc-via-bash-exec.md +42 -0
- package/src/skills/science/references/package-check-playbook.md +64 -0
- package/src/skills/science/references/package-index.min.json +3616 -0
- package/src/skills/science/references/packages/abinit.md +80 -0
- package/src/skills/science/references/packages/acts.md +73 -0
- package/src/skills/science/references/packages/aiida-core.md +80 -0
- package/src/skills/science/references/packages/alamode.md +80 -0
- package/src/skills/science/references/packages/amuse.md +88 -0
- package/src/skills/science/references/packages/anndata.md +88 -0
- package/src/skills/science/references/packages/arbor.md +80 -0
- package/src/skills/science/references/packages/arc.md +73 -0
- package/src/skills/science/references/packages/astropy.md +88 -0
- package/src/skills/science/references/packages/astroquery.md +88 -0
- package/src/skills/science/references/packages/atomate2.md +80 -0
- package/src/skills/science/references/packages/atomsmltr.md +73 -0
- package/src/skills/science/references/packages/awkward.md +73 -0
- package/src/skills/science/references/packages/batman.md +88 -0
- package/src/skills/science/references/packages/biopython.md +88 -0
- package/src/skills/science/references/packages/bloqade.md +73 -0
- package/src/skills/science/references/packages/brian2.md +73 -0
- package/src/skills/science/references/packages/bullet3.md +73 -0
- package/src/skills/science/references/packages/calculix.md +80 -0
- package/src/skills/science/references/packages/cantera.md +73 -0
- package/src/skills/science/references/packages/cavity-md-ipi.md +80 -0
- package/src/skills/science/references/packages/ccdproc.md +88 -0
- package/src/skills/science/references/packages/celerite2.md +88 -0
- package/src/skills/science/references/packages/cellrank.md +73 -0
- package/src/skills/science/references/packages/cesm.md +80 -0
- package/src/skills/science/references/packages/chemicals.md +73 -0
- package/src/skills/science/references/packages/chempy.md +73 -0
- package/src/skills/science/references/packages/cirq.md +73 -0
- package/src/skills/science/references/packages/coffea.md +73 -0
- package/src/skills/science/references/packages/cp2k.md +88 -0
- package/src/skills/science/references/packages/custodian.md +80 -0
- package/src/skills/science/references/packages/dart.md +73 -0
- package/src/skills/science/references/packages/datamol.md +88 -0
- package/src/skills/science/references/packages/dd4hep.md +73 -0
- package/src/skills/science/references/packages/dealii.md +80 -0
- package/src/skills/science/references/packages/deepchem.md +88 -0
- package/src/skills/science/references/packages/delphes.md +73 -0
- package/src/skills/science/references/packages/devito.md +80 -0
- package/src/skills/science/references/packages/dftb.md +88 -0
- package/src/skills/science/references/packages/dftd4.md +88 -0
- package/src/skills/science/references/packages/dftk-jl.md +80 -0
- package/src/skills/science/references/packages/dolfinx.md +80 -0
- package/src/skills/science/references/packages/drake.md +73 -0
- package/src/skills/science/references/packages/dumux.md +73 -0
- package/src/skills/science/references/packages/elk.md +80 -0
- package/src/skills/science/references/packages/elmerfem.md +80 -0
- package/src/skills/science/references/packages/enzo-e.md +88 -0
- package/src/skills/science/references/packages/espresso.md +80 -0
- package/src/skills/science/references/packages/exoplanet.md +88 -0
- package/src/skills/science/references/packages/fairroot.md +73 -0
- package/src/skills/science/references/packages/fbpic.md +80 -0
- package/src/skills/science/references/packages/fdtdbath-meep.md +80 -0
- package/src/skills/science/references/packages/geant4.md +73 -0
- package/src/skills/science/references/packages/geosx.md +80 -0
- package/src/skills/science/references/packages/gprmax.md +80 -0
- package/src/skills/science/references/packages/gromacs.md +80 -0
- package/src/skills/science/references/packages/gwaslab.md +73 -0
- package/src/skills/science/references/packages/gz-sim.md +73 -0
- package/src/skills/science/references/packages/hail.md +88 -0
- package/src/skills/science/references/packages/hiphive.md +80 -0
- package/src/skills/science/references/packages/hoomd-blue.md +80 -0
- package/src/skills/science/references/packages/itensor.md +73 -0
- package/src/skills/science/references/packages/itensors-jl.md +73 -0
- package/src/skills/science/references/packages/jdftx.md +73 -0
- package/src/skills/science/references/packages/jobflow.md +80 -0
- package/src/skills/science/references/packages/kadanoffbaym-jl.md +73 -0
- package/src/skills/science/references/packages/kite.md +80 -0
- package/src/skills/science/references/packages/kratos.md +80 -0
- package/src/skills/science/references/packages/kwant.md +73 -0
- package/src/skills/science/references/packages/lammps.md +80 -0
- package/src/skills/science/references/packages/lightkurve.md +88 -0
- package/src/skills/science/references/packages/limix.md +73 -0
- package/src/skills/science/references/packages/maxwelllink.md +80 -0
- package/src/skills/science/references/packages/mcdc.md +73 -0
- package/src/skills/science/references/packages/meep.md +80 -0
- package/src/skills/science/references/packages/mfem.md +80 -0
- package/src/skills/science/references/packages/mitgcm.md +73 -0
- package/src/skills/science/references/packages/modflow6.md +73 -0
- package/src/skills/science/references/packages/molecool.md +73 -0
- package/src/skills/science/references/packages/mom6.md +73 -0
- package/src/skills/science/references/packages/moose.md +80 -0
- package/src/skills/science/references/packages/mpas-model.md +73 -0
- package/src/skills/science/references/packages/mujoco.md +73 -0
- package/src/skills/science/references/packages/mumax3.md +73 -0
- package/src/skills/science/references/packages/nekrs.md +80 -0
- package/src/skills/science/references/packages/nessi.md +73 -0
- package/src/skills/science/references/packages/nest-simulator.md +73 -0
- package/src/skills/science/references/packages/netket.md +73 -0
- package/src/skills/science/references/packages/neuron.md +73 -0
- package/src/skills/science/references/packages/nextflow.md +88 -0
- package/src/skills/science/references/packages/nwchem.md +88 -0
- package/src/skills/science/references/packages/openbabel.md +88 -0
- package/src/skills/science/references/packages/openems.md +80 -0
- package/src/skills/science/references/packages/openff-toolkit.md +88 -0
- package/src/skills/science/references/packages/openfoam-dev.md +80 -0
- package/src/skills/science/references/packages/openmc.md +73 -0
- package/src/skills/science/references/packages/openmm.md +80 -0
- package/src/skills/science/references/packages/openmoc.md +73 -0
- package/src/skills/science/references/packages/openmx.md +80 -0
- package/src/skills/science/references/packages/opensees.md +80 -0
- package/src/skills/science/references/packages/opensn.md +80 -0
- package/src/skills/science/references/packages/opm-simulators.md +73 -0
- package/src/skills/science/references/packages/oqupy.md +73 -0
- package/src/skills/science/references/packages/packmol.md +80 -0
- package/src/skills/science/references/packages/palabos.md +80 -0
- package/src/skills/science/references/packages/parflow.md +80 -0
- package/src/skills/science/references/packages/pennylane.md +88 -0
- package/src/skills/science/references/packages/perceval.md +73 -0
- package/src/skills/science/references/packages/phono3py.md +73 -0
- package/src/skills/science/references/packages/phonopy.md +73 -0
- package/src/skills/science/references/packages/photutils.md +88 -0
- package/src/skills/science/references/packages/picongpu.md +80 -0
- package/src/skills/science/references/packages/plink-ng.md +88 -0
- package/src/skills/science/references/packages/precice.md +73 -0
- package/src/skills/science/references/packages/psc.md +80 -0
- package/src/skills/science/references/packages/psi4.md +88 -0
- package/src/skills/science/references/packages/pybinding.md +73 -0
- package/src/skills/science/references/packages/pyfr.md +80 -0
- package/src/skills/science/references/packages/pyhf.md +73 -0
- package/src/skills/science/references/packages/pyiron_base.md +80 -0
- package/src/skills/science/references/packages/pylcp.md +73 -0
- package/src/skills/science/references/packages/pylith.md +80 -0
- package/src/skills/science/references/packages/pynbody.md +88 -0
- package/src/skills/science/references/packages/pysam.md +88 -0
- package/src/skills/science/references/packages/pyscf.md +88 -0
- package/src/skills/science/references/packages/q-e.md +73 -0
- package/src/skills/science/references/packages/qibo.md +73 -0
- package/src/skills/science/references/packages/qiskit.md +73 -0
- package/src/skills/science/references/packages/quantica-jl.md +73 -0
- package/src/skills/science/references/packages/quantumoptics-jl.md +73 -0
- package/src/skills/science/references/packages/quimb.md +73 -0
- package/src/skills/science/references/packages/qulacs.md +73 -0
- package/src/skills/science/references/packages/qutip.md +73 -0
- package/src/skills/science/references/packages/rdkit.md +88 -0
- package/src/skills/science/references/packages/rmg-py.md +73 -0
- package/src/skills/science/references/packages/root.md +73 -0
- package/src/skills/science/references/packages/scanpy.md +88 -0
- package/src/skills/science/references/packages/scikit-allel.md +88 -0
- package/src/skills/science/references/packages/scikit-bio.md +88 -0
- package/src/skills/science/references/packages/scqubits.md +73 -0
- package/src/skills/science/references/packages/scuff-em.md +80 -0
- package/src/skills/science/references/packages/scvi-tools.md +73 -0
- package/src/skills/science/references/packages/seissol.md +73 -0
- package/src/skills/science/references/packages/sfepy.md +80 -0
- package/src/skills/science/references/packages/sisl.md +73 -0
- package/src/skills/science/references/packages/smilei.md +80 -0
- package/src/skills/science/references/packages/snakemake.md +88 -0
- package/src/skills/science/references/packages/specfem3d-globe.md +80 -0
- package/src/skills/science/references/packages/specutils.md +88 -0
- package/src/skills/science/references/packages/spglib.md +80 -0
- package/src/skills/science/references/packages/squidpy.md +88 -0
- package/src/skills/science/references/packages/starry.md +88 -0
- package/src/skills/science/references/packages/strawberryfields.md +73 -0
- package/src/skills/science/references/packages/su2.md +80 -0
- package/src/skills/science/references/packages/sunny-jl.md +73 -0
- package/src/skills/science/references/packages/sw4.md +73 -0
- package/src/skills/science/references/packages/swift.md +88 -0
- package/src/skills/science/references/packages/tdnegf.md +73 -0
- package/src/skills/science/references/packages/tenpy.md +73 -0
- package/src/skills/science/references/packages/thermo.md +73 -0
- package/src/skills/science/references/packages/tkwant.md +73 -0
- package/src/skills/science/references/packages/tvb-root.md +73 -0
- package/src/skills/science/references/packages/uproot5.md +73 -0
- package/src/skills/science/references/packages/vampire.md +80 -0
- package/src/skills/science/references/packages/wannier_tools.md +73 -0
- package/src/skills/science/references/packages/warpx.md +80 -0
- package/src/skills/science/references/packages/wrf.md +73 -0
- package/src/skills/science/references/packages/xtb.md +88 -0
- package/src/skills/science/references/packages/yt.md +73 -0
- package/src/skills/science/references/science-task-brief-template.md +71 -0
- package/src/skills/scout/SKILL.md +83 -425
- package/src/skills/scout/references/literature-scout-template.md +5 -24
- package/src/skills/scout/references/operational-guidance.md +191 -0
- package/src/skills/scout/references/paper-triage-playbook.md +11 -35
- package/src/skills/write/SKILL.md +744 -1246
- package/src/skills/write/references/experiments_analysis_patterns.md +129 -0
- package/src/skills/write/references/oral_package_patterns.md +252 -0
- package/src/skills/write/references/oral_writing_principles.md +291 -0
- package/src/skills/write/references/section_rewrite_checklist.md +234 -0
- package/src/tui/dist/app/AppContainer.js +1314 -27
- package/src/tui/dist/components/Composer.js +26 -1
- package/src/tui/dist/components/ConfigScreen.js +2 -1
- package/src/tui/dist/components/InputPrompt.js +25 -9
- package/src/tui/dist/components/MainContent.js +18 -3
- package/src/tui/dist/components/QuestScreen.js +3 -2
- package/src/tui/dist/components/UtilityScreen.js +37 -0
- package/src/tui/dist/hooks/useSafeInput.js +10 -0
- package/src/tui/dist/index.js +13 -1
- package/src/tui/dist/layouts/DefaultAppLayout.js +11 -8
- package/src/tui/dist/lib/api.js +89 -1
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AnalysisPlugin-DnSm0GZn.js → AnalysisPlugin-CA94NGmI.js} +1 -1
- package/src/ui/dist/assets/CliPlugin-DHBzphZU.js +79 -0
- package/src/ui/dist/assets/CodeEditorPlugin-BOFwD2rn.js +2 -0
- package/src/ui/dist/assets/{CodeViewerPlugin-itb0tltR.js → CodeViewerPlugin-CqDpgjik.js} +4 -4
- package/src/ui/dist/assets/{DocViewerPlugin-DqKkiCI6.js → DocViewerPlugin-UDBgt8-4.js} +3 -3
- package/src/ui/dist/assets/GitCommitViewerPlugin-BmHtZ0bZ.js +6 -0
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DxL2ezFG.js → GitDiffViewerPlugin-CAxjNorQ.js} +2 -2
- package/src/ui/dist/assets/{GitSnapshotViewer-B_RQm1YZ.js → GitSnapshotViewer-CweA6VON.js} +2 -2
- package/src/ui/dist/assets/{ImageViewerPlugin-tHqlXY3n.js → ImageViewerPlugin-C8wHGvGN.js} +5 -5
- package/src/ui/dist/assets/LabPlugin-COyyLUol.js +32 -0
- package/src/ui/dist/assets/{LatexPlugin-B495DTXC.js → LatexPlugin-BQjAaA5J.js} +4 -4
- package/src/ui/dist/assets/{MarkdownViewerPlugin-DG28-61B.js → MarkdownViewerPlugin-Dy1NE2dI.js} +3 -3
- package/src/ui/dist/assets/{MarketplacePlugin-BiOGT-Kj.js → MarketplacePlugin-DMIZtEJ2.js} +2 -2
- package/src/ui/dist/assets/NotebookEditor-CFHMq_Qt.js +91 -0
- package/src/ui/dist/assets/{NotebookEditor-CVsj8h_T.js → NotebookEditor-WFyd8Ybt.js} +23 -23
- package/src/ui/dist/assets/{PdfLoader-CASDQmxJ.js → PdfLoader-CLE5u5TS.js} +3 -3
- package/src/ui/dist/assets/{PdfMarkdownPlugin-BFhwoKsY.js → PdfMarkdownPlugin-_iNK_H83.js} +1 -1
- package/src/ui/dist/assets/PdfViewerPlugin-DgWsbInT.js +22 -0
- package/src/ui/dist/assets/SearchPlugin-DrZmn5iw.js +11 -0
- package/src/ui/dist/assets/{TextViewerPlugin-CB4DYfWO.js → TextViewerPlugin-D1-T3aC7.js} +4 -4
- package/src/ui/dist/assets/branding/runner-claude.svg +107 -0
- package/src/ui/dist/assets/branding/runner-codex.svg +10 -0
- package/src/ui/dist/assets/branding/runner-kimi.svg +14 -0
- package/src/ui/dist/assets/branding/runner-opencode.svg +7 -0
- package/src/ui/dist/assets/cli-store-CoZ-x5Ip.js +1 -0
- package/src/ui/dist/assets/{code-DLC6G24T.js → code-DbsmSd3Y.js} +1 -1
- package/src/ui/dist/assets/file-diff-panel-DsvyRz47.js +1 -0
- package/src/ui/dist/assets/{wrap-text-CwMn-iqb.js → file-jump-queue-DeQBikaw.js} +3 -3
- package/src/ui/dist/assets/{file-socket-Cu4Qln7Y.js → file-socket-DA5XIx88.js} +1 -1
- package/src/ui/dist/assets/fonts/ds-fonts.css +50 -4
- package/src/ui/dist/assets/images/deepxiv/register-guide.png +0 -0
- package/src/ui/dist/assets/index-39vY9LmZ.js +1 -0
- package/src/ui/dist/assets/{index-wQ7RIIRd.js → index-BsO46tJA.js} +1 -1
- package/src/ui/dist/assets/index-CHzJ2xtB.js +3530 -0
- package/src/ui/dist/assets/index-DH-zxoZ3.css +33 -0
- package/src/ui/dist/assets/{plugin-notebook-HbW2K-1c.js → plugin-notebook-JRhysCqj.js} +2 -2
- package/src/ui/dist/assets/{project-sync-CsX08Qno.js → project-sync-DPmWKmKD.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-R-GWEhzS.js → zoom-out-DAukFWen.js} +3 -3
- package/src/ui/dist/index.html +3 -3
- package/src/skills/analysis-campaign/references/artifact-orchestration.md +0 -58
- package/src/skills/baseline/references/memory-playbook.md +0 -40
- package/src/skills/baseline/references/publishable-baseline-package.md +0 -30
- package/src/skills/write/references/outline-evidence-contract-example.md +0 -107
- package/src/skills/write/references/paper-experiment-matrix-template.md +0 -131
- package/src/skills/write/references/paper-section-playbook.md +0 -64
- package/src/skills/write/references/reviewer-first-writing.md +0 -64
- package/src/skills/write/references/revision-checklist.md +0 -70
- package/src/skills/write/references/section-contracts.md +0 -82
- package/src/skills/write/references/sentence-level-proofing.md +0 -49
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +0 -204
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +0 -109
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +0 -2
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +0 -1
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +0 -14
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +0 -22
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +0 -81
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +0 -17
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +0 -16
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +0 -11
- package/src/ui/dist/assets/bot-CFkZY-JP.js +0 -6
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +0 -6
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +0 -1
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +0 -1
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +0 -1
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +0 -6
- package/src/ui/dist/assets/image-B9HUUddG.js +0 -6
- package/src/ui/dist/assets/index-B2B1sg-M.js +0 -1
- package/src/ui/dist/assets/index-Cgla8biy.css +0 -33
- package/src/ui/dist/assets/index-DRyx7vAc.js +0 -1
- package/src/ui/dist/assets/index-Gbl53BNp.js +0 -2496
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +0 -6
- package/src/ui/dist/assets/popover-DL6h35vr.js +0 -1
- package/src/ui/dist/assets/select-DvmXt1yY.js +0 -11
- package/src/ui/dist/assets/sigma-7jpXazui.js +0 -6
- package/src/ui/dist/assets/trash-xA7kFt8i.js +0 -11
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +0 -1
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +0 -1
|
@@ -3,10 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
import base64
|
|
4
4
|
from collections import deque
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
import errno
|
|
6
7
|
import faulthandler
|
|
7
8
|
import hashlib
|
|
8
9
|
import hmac
|
|
9
10
|
import json
|
|
11
|
+
import logging
|
|
10
12
|
import mimetypes
|
|
11
13
|
import os
|
|
12
14
|
import re
|
|
@@ -28,8 +30,12 @@ from urllib.request import Request
|
|
|
28
30
|
|
|
29
31
|
from .. import __version__
|
|
30
32
|
from ..annotations import AnnotationService
|
|
33
|
+
from ..admin import AdminTaskService
|
|
34
|
+
from ..admin.repairs import AdminRepairService
|
|
35
|
+
from ..admin.service import AdminService
|
|
31
36
|
from ..acp import build_session_update
|
|
32
37
|
from ..artifact import ArtifactService
|
|
38
|
+
from ..benchstore import BenchStoreService
|
|
33
39
|
from ..bash_exec import BashExecService
|
|
34
40
|
from ..bash_exec.models import TerminalClient
|
|
35
41
|
from ..bash_exec.service import DEFAULT_TERMINAL_SESSION_ID
|
|
@@ -55,6 +61,7 @@ from ..connector.connector_profiles import (
|
|
|
55
61
|
from ..connector_runtime import conversation_identity_key, format_conversation_id, normalize_conversation_id, parse_conversation_id
|
|
56
62
|
from ..config import ConfigManager
|
|
57
63
|
from ..config.models import SYSTEM_CONNECTOR_NAMES
|
|
64
|
+
from ..diagnostics import FailureDiagnosis, diagnose_runner_failure
|
|
58
65
|
from ..home import repo_root
|
|
59
66
|
from ..memory import MemoryService
|
|
60
67
|
from ..network import urlopen_with_proxy as urlopen
|
|
@@ -77,10 +84,11 @@ from ..connector.lingzhu_support import (
|
|
|
77
84
|
from ..prompts import PromptBuilder
|
|
78
85
|
from ..prompts.builder import classify_turn_intent, current_standard_skills
|
|
79
86
|
from ..connector.qq_profiles import list_qq_profiles, merge_qq_profile_config, normalize_qq_connector_config
|
|
80
|
-
from ..quest import QuestService
|
|
81
|
-
from ..runners import CodexRunner, RunRequest, get_runner_factory, register_builtin_runners
|
|
87
|
+
from ..quest import AUTONOMOUS_BLOCKING_WAIT_REASONS, QuestService
|
|
88
|
+
from ..runners import ClaudeRunner, CodexRunner, KimiRunner, OpenCodeRunner, RunRequest, get_runner_factory, register_builtin_runners
|
|
89
|
+
from ..runners.metadata import get_runner_metadata
|
|
82
90
|
from ..runtime_logs import JsonlLogger
|
|
83
|
-
from ..shared import append_jsonl, ensure_dir, generate_id, iter_jsonl, read_json, read_jsonl, read_jsonl_tail, read_text, resolve_within, run_command, slugify, utc_now, which, write_json
|
|
91
|
+
from ..shared import append_jsonl, ensure_dir, generate_id, iter_jsonl, read_json, read_jsonl, read_jsonl_tail, read_text, resolve_within, run_command, slugify, utc_now, utf8_text_subprocess_kwargs, which, write_json
|
|
84
92
|
from ..skills import SkillInstaller
|
|
85
93
|
from ..team import SingleTeamService
|
|
86
94
|
from ..connector.weixin_support import (
|
|
@@ -105,7 +113,12 @@ TERMINAL_STREAM_IDLE_SLEEP_SECONDS = 0.02
|
|
|
105
113
|
_AUTO_CONTINUE_DELAY_SECONDS = 240.0
|
|
106
114
|
_AUTO_CONTINUE_ACTIVE_WORK_DELAY_SECONDS = 0.2
|
|
107
115
|
_TERMINAL_PREWARM_DEBOUNCE_SECONDS = 20.0
|
|
108
|
-
|
|
116
|
+
_STALLED_RUNNING_TURN_INACTIVITY_SECONDS = 30 * 60
|
|
117
|
+
_STALLED_RUNNING_TURN_INTERRUPT_TIMEOUT_SECONDS = 5.0
|
|
118
|
+
_IMMEDIATE_READ_INTERRUPT_TIMEOUT_SECONDS = 5.0
|
|
119
|
+
_RESUME_CONTINUE_TEXT = "Continue"
|
|
120
|
+
CODEX_RETRY_DEFAULT_MAX_ATTEMPTS = 7
|
|
121
|
+
PREVIOUS_CODEX_RETRY_DEFAULT_MAX_ATTEMPTS = 5
|
|
109
122
|
CODEX_RETRY_DEFAULT_INITIAL_BACKOFF_SEC = 10.0
|
|
110
123
|
CODEX_RETRY_DEFAULT_BACKOFF_MULTIPLIER = 6.0
|
|
111
124
|
CODEX_RETRY_DEFAULT_MAX_BACKOFF_SEC = 1800.0
|
|
@@ -199,6 +212,7 @@ class DaemonApp:
|
|
|
199
212
|
self.memory_service = MemoryService(home)
|
|
200
213
|
self.annotation_service = AnnotationService(home)
|
|
201
214
|
self.artifact_service = ArtifactService(home)
|
|
215
|
+
self.benchstore_service = BenchStoreService(home, repo_root=self.repo_root)
|
|
202
216
|
self.bash_exec_service = BashExecService(home)
|
|
203
217
|
self.team_service = SingleTeamService(home)
|
|
204
218
|
self.cloud_service = CloudLinkService(home)
|
|
@@ -210,6 +224,16 @@ class DaemonApp:
|
|
|
210
224
|
sync_existing_quests_enabled=bool(skill_config.get("sync_quest_on_open", True)),
|
|
211
225
|
)
|
|
212
226
|
self.logger = JsonlLogger(home / "logs", level=config.get("logging", {}).get("level", "info"))
|
|
227
|
+
self.admin_task_service = AdminTaskService(
|
|
228
|
+
home,
|
|
229
|
+
repo_root=self.repo_root,
|
|
230
|
+
logger=self.logger,
|
|
231
|
+
system_update_status_fn=self.system_update_status,
|
|
232
|
+
system_update_action_fn=self.request_system_update,
|
|
233
|
+
)
|
|
234
|
+
self.admin_task_service.logger = self.logger
|
|
235
|
+
self.admin_repair_service = AdminRepairService(self)
|
|
236
|
+
self.admin_service = AdminService(self)
|
|
213
237
|
self.reconciled_quests = self.quest_service.reconcile_runtime_state()
|
|
214
238
|
for item in self.reconciled_quests:
|
|
215
239
|
self.logger.log(
|
|
@@ -233,11 +257,41 @@ class DaemonApp:
|
|
|
233
257
|
prompt_builder=self.prompt_builder,
|
|
234
258
|
artifact_service=self.artifact_service,
|
|
235
259
|
)
|
|
236
|
-
|
|
260
|
+
self.claude_runner = ClaudeRunner(
|
|
261
|
+
home=home,
|
|
262
|
+
repo_root=self.repo_root,
|
|
263
|
+
binary=self.runners_config.get("claude", {}).get("binary", "claude"),
|
|
264
|
+
logger=self.logger,
|
|
265
|
+
prompt_builder=self.prompt_builder,
|
|
266
|
+
artifact_service=self.artifact_service,
|
|
267
|
+
)
|
|
268
|
+
self.kimi_runner = KimiRunner(
|
|
269
|
+
home=home,
|
|
270
|
+
repo_root=self.repo_root,
|
|
271
|
+
binary=self.runners_config.get("kimi", {}).get("binary", "kimi"),
|
|
272
|
+
logger=self.logger,
|
|
273
|
+
prompt_builder=self.prompt_builder,
|
|
274
|
+
artifact_service=self.artifact_service,
|
|
275
|
+
)
|
|
276
|
+
self.opencode_runner = OpenCodeRunner(
|
|
277
|
+
home=home,
|
|
278
|
+
repo_root=self.repo_root,
|
|
279
|
+
binary=self.runners_config.get("opencode", {}).get("binary", "opencode"),
|
|
280
|
+
logger=self.logger,
|
|
281
|
+
prompt_builder=self.prompt_builder,
|
|
282
|
+
artifact_service=self.artifact_service,
|
|
283
|
+
)
|
|
284
|
+
register_builtin_runners(
|
|
285
|
+
codex_runner=self.codex_runner,
|
|
286
|
+
claude_runner=self.claude_runner,
|
|
287
|
+
kimi_runner=self.kimi_runner,
|
|
288
|
+
opencode_runner=self.opencode_runner,
|
|
289
|
+
)
|
|
237
290
|
register_builtin_connector_bridges()
|
|
238
291
|
register_builtin_channels(home=home, connectors_config=self.connectors_config)
|
|
239
292
|
self.runners = {
|
|
240
|
-
|
|
293
|
+
name: self._create_runner(name)
|
|
294
|
+
for name in ("codex", "claude", "kimi", "opencode")
|
|
241
295
|
}
|
|
242
296
|
self.channels = {name: self._create_channel(name) for name in list_channel_names()}
|
|
243
297
|
self.sessions = SessionStore()
|
|
@@ -755,7 +809,23 @@ class DaemonApp:
|
|
|
755
809
|
if int(snapshot.get("pending_user_message_count") or 0) > 0
|
|
756
810
|
else "auto_continue"
|
|
757
811
|
)
|
|
758
|
-
|
|
812
|
+
retry_delay_seconds = self._recovery_retry_delay_seconds(snapshot) if reason == "auto_continue" else None
|
|
813
|
+
if retry_delay_seconds is not None and retry_delay_seconds > 0:
|
|
814
|
+
self._schedule_turn_later(
|
|
815
|
+
quest_id,
|
|
816
|
+
reason=reason,
|
|
817
|
+
delay_seconds=retry_delay_seconds,
|
|
818
|
+
)
|
|
819
|
+
scheduled = {
|
|
820
|
+
"scheduled": True,
|
|
821
|
+
"started": False,
|
|
822
|
+
"queued": True,
|
|
823
|
+
"reason": reason,
|
|
824
|
+
"delayed": True,
|
|
825
|
+
"delay_seconds": retry_delay_seconds,
|
|
826
|
+
}
|
|
827
|
+
else:
|
|
828
|
+
scheduled = self.schedule_turn(quest_id, reason=reason)
|
|
759
829
|
event = {
|
|
760
830
|
"event_id": generate_id("evt"),
|
|
761
831
|
"type": "quest.runtime_auto_resumed",
|
|
@@ -767,6 +837,8 @@ class DaemonApp:
|
|
|
767
837
|
"scheduled": bool(scheduled.get("scheduled")),
|
|
768
838
|
"started": bool(scheduled.get("started")),
|
|
769
839
|
"queued": bool(scheduled.get("queued")),
|
|
840
|
+
"delayed": bool(scheduled.get("delayed")),
|
|
841
|
+
"delay_seconds": scheduled.get("delay_seconds"),
|
|
770
842
|
"created_at": utc_now(),
|
|
771
843
|
}
|
|
772
844
|
append_jsonl(self.home / "quests" / quest_id / ".ds" / "events.jsonl", event)
|
|
@@ -781,6 +853,8 @@ class DaemonApp:
|
|
|
781
853
|
scheduled=bool(scheduled.get("scheduled")),
|
|
782
854
|
started=bool(scheduled.get("started")),
|
|
783
855
|
queued=bool(scheduled.get("queued")),
|
|
856
|
+
delayed=bool(scheduled.get("delayed")),
|
|
857
|
+
delay_seconds=scheduled.get("delay_seconds"),
|
|
784
858
|
)
|
|
785
859
|
self._recovered_quest_ids.add(quest_id)
|
|
786
860
|
resumed.append(
|
|
@@ -834,6 +908,63 @@ class DaemonApp:
|
|
|
834
908
|
count += 1
|
|
835
909
|
return count
|
|
836
910
|
|
|
911
|
+
def _recovery_retry_delay_seconds(self, snapshot: dict[str, Any]) -> float | None:
|
|
912
|
+
retry_state = snapshot.get("retry_state") if isinstance(snapshot.get("retry_state"), dict) else None
|
|
913
|
+
if not retry_state:
|
|
914
|
+
return None
|
|
915
|
+
next_retry_at = str(retry_state.get("next_retry_at") or "").strip()
|
|
916
|
+
if not next_retry_at:
|
|
917
|
+
return None
|
|
918
|
+
parsed = self._parse_event_timestamp(next_retry_at)
|
|
919
|
+
if parsed is None:
|
|
920
|
+
return None
|
|
921
|
+
return max((parsed - datetime.now(UTC)).total_seconds(), 0.0)
|
|
922
|
+
|
|
923
|
+
def _resume_retry_state(
|
|
924
|
+
self,
|
|
925
|
+
snapshot: dict[str, Any],
|
|
926
|
+
*,
|
|
927
|
+
max_attempts: int,
|
|
928
|
+
) -> tuple[int, str | None, dict[str, Any] | None]:
|
|
929
|
+
retry_state = snapshot.get("retry_state") if isinstance(snapshot.get("retry_state"), dict) else None
|
|
930
|
+
resume_source = str(snapshot.get("last_resume_source") or "").strip()
|
|
931
|
+
if not retry_state or not resume_source.startswith("auto:daemon-recovery"):
|
|
932
|
+
return 1, None, None
|
|
933
|
+
|
|
934
|
+
try:
|
|
935
|
+
recorded_attempt = int(retry_state.get("attempt_index") or 0)
|
|
936
|
+
except (TypeError, ValueError):
|
|
937
|
+
recorded_attempt = 0
|
|
938
|
+
if recorded_attempt <= 0:
|
|
939
|
+
return 1, None, None
|
|
940
|
+
|
|
941
|
+
next_retry_at = str(retry_state.get("next_retry_at") or "").strip()
|
|
942
|
+
start_attempt = recorded_attempt + 1 if next_retry_at else recorded_attempt
|
|
943
|
+
if start_attempt > max_attempts:
|
|
944
|
+
start_attempt = max_attempts
|
|
945
|
+
if start_attempt <= 1:
|
|
946
|
+
return 1, None, None
|
|
947
|
+
|
|
948
|
+
turn_id = str(retry_state.get("turn_id") or "").strip() or None
|
|
949
|
+
previous_run_id = str(retry_state.get("last_run_id") or "").strip() or None
|
|
950
|
+
failure_summary = str(retry_state.get("last_error") or "").strip() or None
|
|
951
|
+
retry_context = {
|
|
952
|
+
"turn_id": turn_id,
|
|
953
|
+
"attempt_index": recorded_attempt,
|
|
954
|
+
"max_attempts": max_attempts,
|
|
955
|
+
"previous_run_id": previous_run_id,
|
|
956
|
+
"failure_kind": "daemon_recovery",
|
|
957
|
+
"failure_summary": failure_summary or "Recovered retry state after daemon restart.",
|
|
958
|
+
"previous_exit_code": None,
|
|
959
|
+
"previous_output_text": "",
|
|
960
|
+
"stderr_tail": "",
|
|
961
|
+
"recent_messages": [],
|
|
962
|
+
"tool_progress": [],
|
|
963
|
+
"workspace_summary": {},
|
|
964
|
+
"recent_artifacts": [],
|
|
965
|
+
}
|
|
966
|
+
return start_attempt, turn_id, retry_context
|
|
967
|
+
|
|
837
968
|
def _record_auto_resume_suppressed(
|
|
838
969
|
self,
|
|
839
970
|
*,
|
|
@@ -948,10 +1079,10 @@ class DaemonApp:
|
|
|
948
1079
|
command,
|
|
949
1080
|
cwd=str(self.repo_root),
|
|
950
1081
|
capture_output=True,
|
|
951
|
-
text=True,
|
|
952
1082
|
timeout=8,
|
|
953
1083
|
check=False,
|
|
954
1084
|
env=os.environ.copy(),
|
|
1085
|
+
**utf8_text_subprocess_kwargs(),
|
|
955
1086
|
**_windows_hidden_subprocess_kwargs(),
|
|
956
1087
|
)
|
|
957
1088
|
except subprocess.TimeoutExpired as exc:
|
|
@@ -996,10 +1127,10 @@ class DaemonApp:
|
|
|
996
1127
|
command,
|
|
997
1128
|
cwd=str(self.repo_root),
|
|
998
1129
|
capture_output=True,
|
|
999
|
-
text=True,
|
|
1000
1130
|
timeout=8,
|
|
1001
1131
|
check=False,
|
|
1002
1132
|
env=os.environ.copy(),
|
|
1133
|
+
**utf8_text_subprocess_kwargs(),
|
|
1003
1134
|
**_windows_hidden_subprocess_kwargs(),
|
|
1004
1135
|
)
|
|
1005
1136
|
except subprocess.TimeoutExpired as exc:
|
|
@@ -1014,6 +1145,24 @@ class DaemonApp:
|
|
|
1014
1145
|
raise RuntimeError("DeepScientist update request returned an invalid payload.")
|
|
1015
1146
|
return payload
|
|
1016
1147
|
|
|
1148
|
+
def start_benchstore_install(self, entry_id: str) -> dict[str, Any]:
|
|
1149
|
+
entry_payload = self.benchstore_service.get_entry(entry_id)
|
|
1150
|
+
entry = entry_payload.get("entry") if isinstance(entry_payload.get("entry"), dict) else {}
|
|
1151
|
+
normalized_id = str(entry.get("id") or entry_id).strip() or str(entry_id).strip()
|
|
1152
|
+
entry_name = str(entry.get("name") or normalized_id).strip() or normalized_id
|
|
1153
|
+
return self.admin_task_service.start_task(
|
|
1154
|
+
"benchstore_install",
|
|
1155
|
+
metadata={
|
|
1156
|
+
"entry_id": normalized_id,
|
|
1157
|
+
"entry_name": entry_name,
|
|
1158
|
+
},
|
|
1159
|
+
target=lambda reporter, current_task: self.benchstore_service.run_install_task(
|
|
1160
|
+
entry_id=normalized_id,
|
|
1161
|
+
reporter=reporter,
|
|
1162
|
+
task_id=str(current_task.get("task_id") or ""),
|
|
1163
|
+
),
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1017
1166
|
def _process_terminal_attach_request(
|
|
1018
1167
|
self,
|
|
1019
1168
|
connection: ServerConnection,
|
|
@@ -1358,6 +1507,9 @@ class DaemonApp:
|
|
|
1358
1507
|
def _start_terminal_attach_server(self, host: str, port: int) -> None:
|
|
1359
1508
|
if self._terminal_attach_server is not None:
|
|
1360
1509
|
return
|
|
1510
|
+
terminal_ws_logger = logging.getLogger("deepscientist.terminal_attach.websocket")
|
|
1511
|
+
terminal_ws_logger.setLevel(logging.CRITICAL + 1)
|
|
1512
|
+
terminal_ws_logger.propagate = False
|
|
1361
1513
|
candidates: list[int] = []
|
|
1362
1514
|
if port > 0 and port < 65535:
|
|
1363
1515
|
candidates.append(port + 1)
|
|
@@ -1373,6 +1525,7 @@ class DaemonApp:
|
|
|
1373
1525
|
compression=None,
|
|
1374
1526
|
max_size=None,
|
|
1375
1527
|
max_queue=None,
|
|
1528
|
+
logger=terminal_ws_logger,
|
|
1376
1529
|
)
|
|
1377
1530
|
self._terminal_attach_server = server
|
|
1378
1531
|
self._terminal_attach_host = host
|
|
@@ -1486,19 +1639,31 @@ class DaemonApp:
|
|
|
1486
1639
|
|
|
1487
1640
|
def reload_runners_config(self) -> dict[str, object]:
|
|
1488
1641
|
self.runners_config = self.config_manager.load_runners_config()
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1642
|
+
runner_instances = {
|
|
1643
|
+
"codex": self.codex_runner,
|
|
1644
|
+
"claude": self.claude_runner,
|
|
1645
|
+
"kimi": self.kimi_runner,
|
|
1646
|
+
"opencode": self.opencode_runner,
|
|
1647
|
+
}
|
|
1648
|
+
payload: dict[str, object] = {
|
|
1493
1649
|
"ok": True,
|
|
1494
1650
|
"runners": sorted(name for name, config in self.runners_config.items() if isinstance(config, dict)),
|
|
1495
|
-
"codex": {
|
|
1496
|
-
"binary": self.codex_runner.binary,
|
|
1497
|
-
"approval_policy": codex_config.get("approval_policy") if isinstance(codex_config, dict) else None,
|
|
1498
|
-
"sandbox_mode": codex_config.get("sandbox_mode") if isinstance(codex_config, dict) else None,
|
|
1499
|
-
"mcp_tool_timeout_sec": codex_config.get("mcp_tool_timeout_sec") if isinstance(codex_config, dict) else None,
|
|
1500
|
-
},
|
|
1501
1651
|
}
|
|
1652
|
+
for runner_name, instance in runner_instances.items():
|
|
1653
|
+
runner_config = self.runners_config.get(runner_name, {})
|
|
1654
|
+
if isinstance(runner_config, dict):
|
|
1655
|
+
instance.binary = str(runner_config.get("binary") or getattr(instance, "binary", runner_name))
|
|
1656
|
+
payload[runner_name] = {
|
|
1657
|
+
"binary": getattr(instance, "binary", None),
|
|
1658
|
+
"enabled": runner_config.get("enabled") if isinstance(runner_config, dict) else None,
|
|
1659
|
+
"model": runner_config.get("model") if isinstance(runner_config, dict) else None,
|
|
1660
|
+
"config_dir": runner_config.get("config_dir") if isinstance(runner_config, dict) else None,
|
|
1661
|
+
}
|
|
1662
|
+
if runner_name == "codex" and isinstance(runner_config, dict):
|
|
1663
|
+
payload[runner_name]["approval_policy"] = runner_config.get("approval_policy")
|
|
1664
|
+
payload[runner_name]["sandbox_mode"] = runner_config.get("sandbox_mode")
|
|
1665
|
+
payload[runner_name]["mcp_tool_timeout_sec"] = runner_config.get("mcp_tool_timeout_sec")
|
|
1666
|
+
return payload
|
|
1502
1667
|
|
|
1503
1668
|
def _preferred_locale(self) -> str:
|
|
1504
1669
|
return str(self.runtime_config.get("default_locale") or "en-US").lower()
|
|
@@ -1506,6 +1671,14 @@ class DaemonApp:
|
|
|
1506
1671
|
def _polite_copy(self, *, zh: str, en: str) -> str:
|
|
1507
1672
|
return zh if self._preferred_locale().startswith("zh") else en
|
|
1508
1673
|
|
|
1674
|
+
@staticmethod
|
|
1675
|
+
def _resume_continue_source(source: str) -> str:
|
|
1676
|
+
normalized = normalize_conversation_id(source)
|
|
1677
|
+
parsed = parse_conversation_id(normalized)
|
|
1678
|
+
if parsed is not None and str(parsed.get("connector") or "").strip().lower() != "local":
|
|
1679
|
+
return normalized
|
|
1680
|
+
return "local"
|
|
1681
|
+
|
|
1509
1682
|
def submit_user_message(
|
|
1510
1683
|
self,
|
|
1511
1684
|
quest_id: str,
|
|
@@ -1543,7 +1716,36 @@ class DaemonApp:
|
|
|
1543
1716
|
)
|
|
1544
1717
|
turn_state = self._refresh_turn_worker_state(quest_id)
|
|
1545
1718
|
has_live_turn = bool(turn_state.get("running"))
|
|
1546
|
-
|
|
1719
|
+
retry_wait_details = self._retry_waiting_turn_details(
|
|
1720
|
+
quest_id,
|
|
1721
|
+
snapshot=snapshot,
|
|
1722
|
+
turn_state=turn_state,
|
|
1723
|
+
turn_reason="user_message",
|
|
1724
|
+
)
|
|
1725
|
+
stalled_details = self._stalled_running_turn_details(
|
|
1726
|
+
quest_id,
|
|
1727
|
+
snapshot=snapshot,
|
|
1728
|
+
turn_state=turn_state,
|
|
1729
|
+
turn_reason="user_message",
|
|
1730
|
+
)
|
|
1731
|
+
if retry_wait_details is not None:
|
|
1732
|
+
restart = self._interrupt_retry_wait_for_new_user_message(
|
|
1733
|
+
quest_id,
|
|
1734
|
+
snapshot=snapshot,
|
|
1735
|
+
turn_state=turn_state,
|
|
1736
|
+
details=retry_wait_details,
|
|
1737
|
+
source=source,
|
|
1738
|
+
)
|
|
1739
|
+
if restart is not None:
|
|
1740
|
+
scheduled = restart
|
|
1741
|
+
else:
|
|
1742
|
+
scheduled = {
|
|
1743
|
+
"scheduled": True,
|
|
1744
|
+
"started": False,
|
|
1745
|
+
"queued": True,
|
|
1746
|
+
"reason": "queued_for_artifact_interact",
|
|
1747
|
+
}
|
|
1748
|
+
elif runtime_status == "running" and has_live_turn and stalled_details is None:
|
|
1547
1749
|
scheduled = {
|
|
1548
1750
|
"scheduled": True,
|
|
1549
1751
|
"started": False,
|
|
@@ -1559,6 +1761,151 @@ class DaemonApp:
|
|
|
1559
1761
|
**scheduled,
|
|
1560
1762
|
}
|
|
1561
1763
|
|
|
1764
|
+
def _retry_waiting_turn_details(
|
|
1765
|
+
self,
|
|
1766
|
+
quest_id: str,
|
|
1767
|
+
*,
|
|
1768
|
+
snapshot: dict | None,
|
|
1769
|
+
turn_state: dict[str, object] | None,
|
|
1770
|
+
turn_reason: str,
|
|
1771
|
+
) -> dict[str, Any] | None:
|
|
1772
|
+
if str(turn_reason or "").strip() != "user_message":
|
|
1773
|
+
return None
|
|
1774
|
+
snapshot = dict(snapshot or self.quest_service.snapshot(quest_id))
|
|
1775
|
+
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip().lower()
|
|
1776
|
+
if runtime_status != "running":
|
|
1777
|
+
return None
|
|
1778
|
+
state = dict(turn_state or self._refresh_turn_worker_state(quest_id))
|
|
1779
|
+
if not state.get("running"):
|
|
1780
|
+
return None
|
|
1781
|
+
retry_state = snapshot.get("retry_state") if isinstance(snapshot.get("retry_state"), dict) else None
|
|
1782
|
+
if not retry_state:
|
|
1783
|
+
return None
|
|
1784
|
+
next_retry_at = str(retry_state.get("next_retry_at") or "").strip()
|
|
1785
|
+
if not next_retry_at:
|
|
1786
|
+
return None
|
|
1787
|
+
if str(snapshot.get("active_run_id") or "").strip():
|
|
1788
|
+
return None
|
|
1789
|
+
pending_user_count = int(snapshot.get("pending_user_message_count") or 0)
|
|
1790
|
+
if pending_user_count <= 0:
|
|
1791
|
+
return None
|
|
1792
|
+
try:
|
|
1793
|
+
attempt_index = int(retry_state.get("attempt_index") or 0)
|
|
1794
|
+
except (TypeError, ValueError):
|
|
1795
|
+
attempt_index = 0
|
|
1796
|
+
try:
|
|
1797
|
+
max_attempts = int(retry_state.get("max_attempts") or 0)
|
|
1798
|
+
except (TypeError, ValueError):
|
|
1799
|
+
max_attempts = 0
|
|
1800
|
+
delay_seconds = self._recovery_retry_delay_seconds(snapshot) or 0.0
|
|
1801
|
+
return {
|
|
1802
|
+
"retry_state": dict(retry_state),
|
|
1803
|
+
"attempt_index": attempt_index,
|
|
1804
|
+
"max_attempts": max_attempts,
|
|
1805
|
+
"pending_user_count": pending_user_count,
|
|
1806
|
+
"delay_seconds": max(0.0, float(delay_seconds)),
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
def _interrupt_retry_wait_for_new_user_message(
|
|
1810
|
+
self,
|
|
1811
|
+
quest_id: str,
|
|
1812
|
+
*,
|
|
1813
|
+
snapshot: dict[str, Any],
|
|
1814
|
+
turn_state: dict[str, object],
|
|
1815
|
+
details: dict[str, Any],
|
|
1816
|
+
source: str,
|
|
1817
|
+
) -> dict[str, Any] | None:
|
|
1818
|
+
try:
|
|
1819
|
+
restart = self._quiet_interrupt_for_turn_restart(quest_id, source=source)
|
|
1820
|
+
except RuntimeError as exc:
|
|
1821
|
+
self.logger.log(
|
|
1822
|
+
"warning",
|
|
1823
|
+
"runner.turn_retry_interrupt_failed",
|
|
1824
|
+
quest_id=quest_id,
|
|
1825
|
+
source=source,
|
|
1826
|
+
error=str(exc),
|
|
1827
|
+
pending_user_message_count=int(details.get("pending_user_count") or 0),
|
|
1828
|
+
)
|
|
1829
|
+
return None
|
|
1830
|
+
|
|
1831
|
+
retry_state = details.get("retry_state") if isinstance(details.get("retry_state"), dict) else {}
|
|
1832
|
+
turn_id = str(retry_state.get("turn_id") or "").strip() or None
|
|
1833
|
+
previous_run_id = str(retry_state.get("last_run_id") or "").strip() or None
|
|
1834
|
+
attempt_index = max(0, int(details.get("attempt_index") or 0))
|
|
1835
|
+
max_attempts = max(0, int(details.get("max_attempts") or 0))
|
|
1836
|
+
pending_user_count = max(0, int(details.get("pending_user_count") or 0))
|
|
1837
|
+
delay_seconds = max(0.0, float(details.get("delay_seconds") or 0.0))
|
|
1838
|
+
runner_name = self._runner_name_for(snapshot)
|
|
1839
|
+
skill_id = self._turn_skill_for(snapshot, None, turn_reason="user_message", turn_mode=self._turn_mode_for(snapshot, None, turn_reason="user_message"))
|
|
1840
|
+
model = str((self.runners_config.get(runner_name) or {}).get("model", "gpt-5.4"))
|
|
1841
|
+
summary = (
|
|
1842
|
+
f"Retry wait aborted after attempt {attempt_index}/{max_attempts or attempt_index or 1} "
|
|
1843
|
+
f"because a newer user message arrived. "
|
|
1844
|
+
f"Pending queued user messages: {pending_user_count}. "
|
|
1845
|
+
f"Skipped remaining backoff wait: {delay_seconds:.1f}s."
|
|
1846
|
+
)
|
|
1847
|
+
self.quest_service.update_runtime_state(
|
|
1848
|
+
quest_root=self.quest_service._quest_root(quest_id),
|
|
1849
|
+
retry_state=None,
|
|
1850
|
+
)
|
|
1851
|
+
self._append_retry_event(
|
|
1852
|
+
quest_id,
|
|
1853
|
+
event_type="runner.turn_retry_aborted",
|
|
1854
|
+
runner_name=runner_name,
|
|
1855
|
+
run_id=previous_run_id,
|
|
1856
|
+
turn_id=turn_id,
|
|
1857
|
+
skill_id=skill_id,
|
|
1858
|
+
model=model,
|
|
1859
|
+
attempt_index=attempt_index,
|
|
1860
|
+
max_attempts=max_attempts,
|
|
1861
|
+
summary=summary,
|
|
1862
|
+
failure_summary=str(retry_state.get("last_error") or "").strip() or None,
|
|
1863
|
+
previous_run_id=previous_run_id,
|
|
1864
|
+
)
|
|
1865
|
+
self._relay_quest_message_to_bound_connectors(
|
|
1866
|
+
quest_id,
|
|
1867
|
+
message=self._polite_copy(
|
|
1868
|
+
zh="我收到新的用户消息,已中断上一轮等待中的自动重试,先优先处理最新要求。",
|
|
1869
|
+
en="A new user message arrived, so I interrupted the pending retry wait and will handle the latest request first.",
|
|
1870
|
+
),
|
|
1871
|
+
kind="system",
|
|
1872
|
+
response_phase="update",
|
|
1873
|
+
importance="normal",
|
|
1874
|
+
attachments=[
|
|
1875
|
+
{
|
|
1876
|
+
"kind": "retry_interrupted",
|
|
1877
|
+
"run_id": previous_run_id,
|
|
1878
|
+
"runner": runner_name,
|
|
1879
|
+
"turn_id": turn_id,
|
|
1880
|
+
}
|
|
1881
|
+
],
|
|
1882
|
+
)
|
|
1883
|
+
return self.schedule_turn(quest_id, reason="user_message")
|
|
1884
|
+
|
|
1885
|
+
def submit_web_user_message(
|
|
1886
|
+
self,
|
|
1887
|
+
quest_id: str,
|
|
1888
|
+
*,
|
|
1889
|
+
text: str,
|
|
1890
|
+
source: str,
|
|
1891
|
+
attachment_draft_ids: list[str],
|
|
1892
|
+
reply_to_interaction_id: str | None = None,
|
|
1893
|
+
client_message_id: str | None = None,
|
|
1894
|
+
) -> dict:
|
|
1895
|
+
finalized_attachments = self.quest_service.finalize_chat_attachment_drafts(
|
|
1896
|
+
quest_id,
|
|
1897
|
+
draft_ids=attachment_draft_ids,
|
|
1898
|
+
client_message_id=client_message_id,
|
|
1899
|
+
)
|
|
1900
|
+
return self.submit_user_message(
|
|
1901
|
+
quest_id,
|
|
1902
|
+
text=text,
|
|
1903
|
+
source=source,
|
|
1904
|
+
attachments=finalized_attachments,
|
|
1905
|
+
reply_to_interaction_id=reply_to_interaction_id,
|
|
1906
|
+
client_message_id=client_message_id,
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1562
1909
|
def create_quest(
|
|
1563
1910
|
self,
|
|
1564
1911
|
*,
|
|
@@ -1578,10 +1925,13 @@ class DaemonApp:
|
|
|
1578
1925
|
normalized_requested_bindings = self._normalize_requested_connector_bindings(requested_connector_bindings)
|
|
1579
1926
|
if len(normalized_requested_bindings) > 1:
|
|
1580
1927
|
raise ValueError("A quest may bind at most one external connector target.")
|
|
1928
|
+
configured = self.config_manager.load_named("config")
|
|
1929
|
+
default_runner = str(configured.get("default_runner") or "codex").strip().lower() or "codex"
|
|
1581
1930
|
snapshot = self.quest_service.create(
|
|
1582
1931
|
goal=goal,
|
|
1583
1932
|
title=title,
|
|
1584
1933
|
quest_id=quest_id,
|
|
1934
|
+
runner=default_runner,
|
|
1585
1935
|
requested_baseline_ref=dict(requested_baseline_ref) if isinstance(requested_baseline_ref, dict) else None,
|
|
1586
1936
|
startup_contract=dict(startup_contract) if isinstance(startup_contract, dict) else None,
|
|
1587
1937
|
)
|
|
@@ -1712,18 +2062,30 @@ class DaemonApp:
|
|
|
1712
2062
|
return snapshot
|
|
1713
2063
|
|
|
1714
2064
|
def schedule_turn(self, quest_id: str, *, reason: str = "user_message") -> dict:
|
|
2065
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
2066
|
+
snapshot = self._reconcile_stale_active_turn(quest_id, snapshot=snapshot)
|
|
2067
|
+
recovery = self._recover_stalled_running_turn(quest_id, snapshot=snapshot, turn_reason=reason)
|
|
2068
|
+
snapshot = dict(recovery.get("snapshot") or snapshot)
|
|
2069
|
+
if recovery.get("blocked"):
|
|
2070
|
+
return {
|
|
2071
|
+
"scheduled": True,
|
|
2072
|
+
"started": False,
|
|
2073
|
+
"queued": True,
|
|
2074
|
+
"reason": "stalled_turn_recovery_pending",
|
|
2075
|
+
}
|
|
1715
2076
|
self._refresh_turn_worker_state(quest_id)
|
|
1716
2077
|
with self._turn_lock:
|
|
1717
2078
|
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
1718
2079
|
state["pending"] = True
|
|
1719
2080
|
state["stop_requested"] = False
|
|
2081
|
+
state.pop("recovery_pending", None)
|
|
1720
2082
|
state["reason"] = reason
|
|
1721
2083
|
if state.get("running"):
|
|
1722
2084
|
return {
|
|
1723
2085
|
"scheduled": True,
|
|
1724
2086
|
"started": False,
|
|
1725
2087
|
"queued": True,
|
|
1726
|
-
"reason": reason,
|
|
2088
|
+
"reason": "queued_for_artifact_interact" if reason == "user_message" else reason,
|
|
1727
2089
|
}
|
|
1728
2090
|
state["running"] = True
|
|
1729
2091
|
worker = threading.Thread(
|
|
@@ -1804,6 +2166,110 @@ class DaemonApp:
|
|
|
1804
2166
|
state.pop("worker", None)
|
|
1805
2167
|
return dict(state)
|
|
1806
2168
|
|
|
2169
|
+
def _wait_for_turn_worker_exit(self, quest_id: str, *, timeout_seconds: float) -> dict[str, object]:
|
|
2170
|
+
deadline = time.monotonic() + max(0.0, float(timeout_seconds))
|
|
2171
|
+
state = self._refresh_turn_worker_state(quest_id)
|
|
2172
|
+
while state.get("running") and time.monotonic() < deadline:
|
|
2173
|
+
time.sleep(0.05)
|
|
2174
|
+
state = self._refresh_turn_worker_state(quest_id)
|
|
2175
|
+
return state
|
|
2176
|
+
|
|
2177
|
+
def _ensure_recovery_resume_watch(self, quest_id: str, *, turn_reason: str) -> None:
|
|
2178
|
+
with self._turn_lock:
|
|
2179
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2180
|
+
if state.get("recovery_watch_active"):
|
|
2181
|
+
return
|
|
2182
|
+
state["recovery_watch_active"] = True
|
|
2183
|
+
watcher = threading.Thread(
|
|
2184
|
+
target=self._wait_and_resume_recovered_turn,
|
|
2185
|
+
args=(quest_id, turn_reason),
|
|
2186
|
+
daemon=True,
|
|
2187
|
+
name=f"deepscientist-recovery-watch-{quest_id}",
|
|
2188
|
+
)
|
|
2189
|
+
watcher.start()
|
|
2190
|
+
|
|
2191
|
+
def _wait_and_resume_recovered_turn(self, quest_id: str, turn_reason: str) -> None:
|
|
2192
|
+
try:
|
|
2193
|
+
while True:
|
|
2194
|
+
state = self._refresh_turn_worker_state(quest_id)
|
|
2195
|
+
if not state.get("recovery_pending"):
|
|
2196
|
+
return
|
|
2197
|
+
if not state.get("running"):
|
|
2198
|
+
break
|
|
2199
|
+
time.sleep(0.1)
|
|
2200
|
+
|
|
2201
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
2202
|
+
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip().lower()
|
|
2203
|
+
if runtime_status in {"paused", "stopped", "completed", "error"}:
|
|
2204
|
+
with self._turn_lock:
|
|
2205
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2206
|
+
state.pop("recovery_pending", None)
|
|
2207
|
+
state["stop_requested"] = runtime_status in {"paused", "stopped"}
|
|
2208
|
+
return
|
|
2209
|
+
pending_user_count = int(snapshot.get("pending_user_message_count") or 0)
|
|
2210
|
+
if pending_user_count > 0:
|
|
2211
|
+
with self._turn_lock:
|
|
2212
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2213
|
+
state.pop("recovery_pending", None)
|
|
2214
|
+
self.schedule_turn(quest_id, reason=turn_reason)
|
|
2215
|
+
return
|
|
2216
|
+
|
|
2217
|
+
with self._turn_lock:
|
|
2218
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2219
|
+
state.pop("recovery_pending", None)
|
|
2220
|
+
state["stop_requested"] = False
|
|
2221
|
+
except Exception as exc:
|
|
2222
|
+
self.logger.log(
|
|
2223
|
+
"warning",
|
|
2224
|
+
"quest.turn_state_recovery_watch_failed",
|
|
2225
|
+
quest_id=quest_id,
|
|
2226
|
+
reason=turn_reason,
|
|
2227
|
+
error=str(exc),
|
|
2228
|
+
)
|
|
2229
|
+
finally:
|
|
2230
|
+
with self._turn_lock:
|
|
2231
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2232
|
+
state.pop("recovery_watch_active", None)
|
|
2233
|
+
|
|
2234
|
+
def _stalled_running_turn_details(
|
|
2235
|
+
self,
|
|
2236
|
+
quest_id: str,
|
|
2237
|
+
*,
|
|
2238
|
+
snapshot: dict | None = None,
|
|
2239
|
+
turn_state: dict[str, object] | None = None,
|
|
2240
|
+
turn_reason: str,
|
|
2241
|
+
) -> dict[str, int] | None:
|
|
2242
|
+
if str(turn_reason or "").strip() not in {"user_message", "queued_user_messages"}:
|
|
2243
|
+
return None
|
|
2244
|
+
snapshot = dict(snapshot or self.quest_service.snapshot(quest_id))
|
|
2245
|
+
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip().lower()
|
|
2246
|
+
active_run_id = str(snapshot.get("active_run_id") or "").strip()
|
|
2247
|
+
if runtime_status != "running" or not active_run_id:
|
|
2248
|
+
return None
|
|
2249
|
+
state = dict(turn_state or self._refresh_turn_worker_state(quest_id))
|
|
2250
|
+
if not state.get("running"):
|
|
2251
|
+
return None
|
|
2252
|
+
pending_user_count = int(snapshot.get("pending_user_message_count") or 0)
|
|
2253
|
+
if pending_user_count <= 0:
|
|
2254
|
+
return None
|
|
2255
|
+
counts = snapshot.get("counts") if isinstance(snapshot.get("counts"), dict) else {}
|
|
2256
|
+
if int(counts.get("bash_running_count") or 0) > 0:
|
|
2257
|
+
return None
|
|
2258
|
+
silent_seconds = snapshot.get("seconds_since_last_tool_activity")
|
|
2259
|
+
if silent_seconds is None:
|
|
2260
|
+
watchdog = snapshot.get("interaction_watchdog") if isinstance(snapshot.get("interaction_watchdog"), dict) else {}
|
|
2261
|
+
silent_seconds = watchdog.get("seconds_since_last_tool_activity")
|
|
2262
|
+
try:
|
|
2263
|
+
silent_seconds_int = int(silent_seconds or 0)
|
|
2264
|
+
except (TypeError, ValueError):
|
|
2265
|
+
return None
|
|
2266
|
+
if silent_seconds_int < _STALLED_RUNNING_TURN_INACTIVITY_SECONDS:
|
|
2267
|
+
return None
|
|
2268
|
+
return {
|
|
2269
|
+
"pending_user_count": pending_user_count,
|
|
2270
|
+
"silent_seconds": silent_seconds_int,
|
|
2271
|
+
}
|
|
2272
|
+
|
|
1807
2273
|
def _reconcile_stale_active_turn(self, quest_id: str, *, snapshot: dict | None = None) -> dict:
|
|
1808
2274
|
snapshot = dict(snapshot or self.quest_service.snapshot(quest_id))
|
|
1809
2275
|
active_run_id = str(snapshot.get("active_run_id") or "").strip()
|
|
@@ -1855,6 +2321,139 @@ class DaemonApp:
|
|
|
1855
2321
|
)
|
|
1856
2322
|
return self.quest_service.mark_turn_finished(quest_id, status=normalized_status)
|
|
1857
2323
|
|
|
2324
|
+
def _recover_stalled_running_turn(
|
|
2325
|
+
self,
|
|
2326
|
+
quest_id: str,
|
|
2327
|
+
*,
|
|
2328
|
+
snapshot: dict | None = None,
|
|
2329
|
+
turn_reason: str,
|
|
2330
|
+
) -> dict[str, object]:
|
|
2331
|
+
snapshot = dict(snapshot or self.quest_service.snapshot(quest_id))
|
|
2332
|
+
turn_state = self._refresh_turn_worker_state(quest_id)
|
|
2333
|
+
with self._turn_lock:
|
|
2334
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2335
|
+
if state.get("recovery_pending"):
|
|
2336
|
+
return {
|
|
2337
|
+
"snapshot": snapshot,
|
|
2338
|
+
"blocked": True,
|
|
2339
|
+
}
|
|
2340
|
+
details = self._stalled_running_turn_details(
|
|
2341
|
+
quest_id,
|
|
2342
|
+
snapshot=snapshot,
|
|
2343
|
+
turn_state=turn_state,
|
|
2344
|
+
turn_reason=turn_reason,
|
|
2345
|
+
)
|
|
2346
|
+
if details is None:
|
|
2347
|
+
return {
|
|
2348
|
+
"snapshot": snapshot,
|
|
2349
|
+
"blocked": False,
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
active_run_id = str(snapshot.get("active_run_id") or "").strip()
|
|
2353
|
+
runner_name = self._runner_name_for(snapshot)
|
|
2354
|
+
with self._turn_lock:
|
|
2355
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2356
|
+
if state.get("recovery_pending"):
|
|
2357
|
+
return {
|
|
2358
|
+
"snapshot": snapshot,
|
|
2359
|
+
"blocked": True,
|
|
2360
|
+
}
|
|
2361
|
+
state["pending"] = False
|
|
2362
|
+
state["stop_requested"] = True
|
|
2363
|
+
state["recovery_pending"] = True
|
|
2364
|
+
interrupted = False
|
|
2365
|
+
try:
|
|
2366
|
+
try:
|
|
2367
|
+
runner = self.get_runner(runner_name)
|
|
2368
|
+
except KeyError:
|
|
2369
|
+
runner = None
|
|
2370
|
+
if runner is not None and hasattr(runner, "interrupt"):
|
|
2371
|
+
interrupted = bool(getattr(runner, "interrupt")(quest_id))
|
|
2372
|
+
stopped_bash_session_ids = self._stop_active_bash_exec_sessions(
|
|
2373
|
+
quest_id,
|
|
2374
|
+
run_id=active_run_id or None,
|
|
2375
|
+
reason="stalled_turn_recovery",
|
|
2376
|
+
user_id="auto:stalled-turn-recovery",
|
|
2377
|
+
)
|
|
2378
|
+
turn_state = self._wait_for_turn_worker_exit(
|
|
2379
|
+
quest_id,
|
|
2380
|
+
timeout_seconds=_STALLED_RUNNING_TURN_INTERRUPT_TIMEOUT_SECONDS,
|
|
2381
|
+
)
|
|
2382
|
+
if turn_state.get("running"):
|
|
2383
|
+
self._ensure_recovery_resume_watch(quest_id, turn_reason="queued_user_messages")
|
|
2384
|
+
self.logger.log(
|
|
2385
|
+
"warning",
|
|
2386
|
+
"quest.turn_state_recovery_pending",
|
|
2387
|
+
quest_id=quest_id,
|
|
2388
|
+
abandoned_run_id=active_run_id or None,
|
|
2389
|
+
reason=turn_reason,
|
|
2390
|
+
silent_seconds=int(details.get("silent_seconds") or 0),
|
|
2391
|
+
pending_user_message_count=int(details.get("pending_user_count") or 0),
|
|
2392
|
+
interrupted=interrupted,
|
|
2393
|
+
)
|
|
2394
|
+
return {
|
|
2395
|
+
"snapshot": snapshot,
|
|
2396
|
+
"blocked": True,
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
previous_status = (
|
|
2400
|
+
str(snapshot.get("runtime_status") or snapshot.get("status") or snapshot.get("display_status") or "running").strip()
|
|
2401
|
+
or "running"
|
|
2402
|
+
)
|
|
2403
|
+
normalized_status = "active" if previous_status == "running" else previous_status
|
|
2404
|
+
summary = (
|
|
2405
|
+
f"Recovered stalled running turn `{active_run_id}` after "
|
|
2406
|
+
f"{int(details.get('silent_seconds') or 0)} seconds without tool activity while "
|
|
2407
|
+
f"{int(details.get('pending_user_count') or 0)} queued user message(s) were waiting."
|
|
2408
|
+
)
|
|
2409
|
+
if interrupted:
|
|
2410
|
+
summary = f"{summary} The active runner process was interrupted."
|
|
2411
|
+
if stopped_bash_session_ids:
|
|
2412
|
+
summary = f"{summary} Stopped {len(stopped_bash_session_ids)} bash_exec session(s)."
|
|
2413
|
+
quest_root = self.quest_service._quest_root(quest_id)
|
|
2414
|
+
append_jsonl(
|
|
2415
|
+
quest_root / ".ds" / "events.jsonl",
|
|
2416
|
+
{
|
|
2417
|
+
"event_id": generate_id("evt"),
|
|
2418
|
+
"type": "quest.turn_state_reconciled",
|
|
2419
|
+
"quest_id": quest_id,
|
|
2420
|
+
"abandoned_run_id": active_run_id or None,
|
|
2421
|
+
"previous_status": previous_status,
|
|
2422
|
+
"status": normalized_status,
|
|
2423
|
+
"completed_at": None,
|
|
2424
|
+
"exit_code": None,
|
|
2425
|
+
"summary": summary,
|
|
2426
|
+
"recovery_kind": "stalled_live_turn",
|
|
2427
|
+
"interrupted": interrupted,
|
|
2428
|
+
"stopped_bash_session_ids": stopped_bash_session_ids,
|
|
2429
|
+
"created_at": utc_now(),
|
|
2430
|
+
},
|
|
2431
|
+
)
|
|
2432
|
+
self.logger.log(
|
|
2433
|
+
"warning",
|
|
2434
|
+
"quest.turn_state_reconciled",
|
|
2435
|
+
quest_id=quest_id,
|
|
2436
|
+
abandoned_run_id=active_run_id or None,
|
|
2437
|
+
previous_status=previous_status,
|
|
2438
|
+
status=normalized_status,
|
|
2439
|
+
recovery_kind="stalled_live_turn",
|
|
2440
|
+
interrupted=interrupted,
|
|
2441
|
+
stopped_bash_session_count=len(stopped_bash_session_ids),
|
|
2442
|
+
)
|
|
2443
|
+
snapshot = self.quest_service.mark_turn_finished(quest_id, status=normalized_status)
|
|
2444
|
+
with self._turn_lock:
|
|
2445
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2446
|
+
state.pop("recovery_pending", None)
|
|
2447
|
+
return {
|
|
2448
|
+
"snapshot": snapshot,
|
|
2449
|
+
"blocked": False,
|
|
2450
|
+
}
|
|
2451
|
+
except Exception:
|
|
2452
|
+
with self._turn_lock:
|
|
2453
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2454
|
+
state.pop("recovery_pending", None)
|
|
2455
|
+
raise
|
|
2456
|
+
|
|
1858
2457
|
def control_quest(self, quest_id: str, *, action: str, source: str = "local") -> dict:
|
|
1859
2458
|
normalized_action = str(action or "").strip().lower()
|
|
1860
2459
|
if normalized_action == "pause":
|
|
@@ -1927,25 +2526,319 @@ class DaemonApp:
|
|
|
1927
2526
|
)
|
|
1928
2527
|
notice = self._announce_control_state(
|
|
1929
2528
|
quest_id,
|
|
1930
|
-
action=action,
|
|
2529
|
+
action=action,
|
|
2530
|
+
source=source,
|
|
2531
|
+
snapshot=snapshot,
|
|
2532
|
+
interrupted=interrupted,
|
|
2533
|
+
cancelled_pending_user_message_count=cancelled_count,
|
|
2534
|
+
previous_snapshot=previous_snapshot,
|
|
2535
|
+
)
|
|
2536
|
+
return {
|
|
2537
|
+
"ok": True,
|
|
2538
|
+
"quest_id": quest_id,
|
|
2539
|
+
"action": action,
|
|
2540
|
+
"interrupted": interrupted,
|
|
2541
|
+
"cancelled_pending_user_message_count": cancelled_count,
|
|
2542
|
+
"stopped_bash_session_ids": stopped_bash_session_ids,
|
|
2543
|
+
"snapshot": snapshot,
|
|
2544
|
+
"message": summary,
|
|
2545
|
+
"event": event,
|
|
2546
|
+
"notice": notice,
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
def _quiet_interrupt_for_turn_restart(self, quest_id: str, *, source: str) -> dict[str, Any]:
|
|
2550
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
2551
|
+
snapshot = self._reconcile_stale_active_turn(quest_id, snapshot=snapshot)
|
|
2552
|
+
runner_name = self._runner_name_for(snapshot)
|
|
2553
|
+
active_run_id = str(snapshot.get("active_run_id") or "").strip() or None
|
|
2554
|
+
turn_state = self._refresh_turn_worker_state(quest_id)
|
|
2555
|
+
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip().lower()
|
|
2556
|
+
if not active_run_id and not turn_state.get("running"):
|
|
2557
|
+
if runtime_status in {"stopped", "paused", "completed", "error"}:
|
|
2558
|
+
snapshot = self.quest_service.set_status(quest_id, "active")
|
|
2559
|
+
return {
|
|
2560
|
+
"snapshot": snapshot,
|
|
2561
|
+
"interrupted": False,
|
|
2562
|
+
"stopped_bash_session_ids": [],
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
interrupted = False
|
|
2566
|
+
try:
|
|
2567
|
+
runner = self.get_runner(runner_name)
|
|
2568
|
+
except KeyError:
|
|
2569
|
+
runner = None
|
|
2570
|
+
with self._turn_lock:
|
|
2571
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2572
|
+
state["pending"] = False
|
|
2573
|
+
state["stop_requested"] = True
|
|
2574
|
+
runner_supports_interrupt = runner is not None and hasattr(runner, "interrupt")
|
|
2575
|
+
if runner_supports_interrupt:
|
|
2576
|
+
interrupted = bool(getattr(runner, "interrupt")(quest_id))
|
|
2577
|
+
stopped_bash_session_ids = self._stop_active_bash_exec_sessions(
|
|
2578
|
+
quest_id,
|
|
2579
|
+
run_id=active_run_id,
|
|
2580
|
+
reason="immediate_read",
|
|
2581
|
+
user_id=source,
|
|
2582
|
+
)
|
|
2583
|
+
turn_state = self._wait_for_turn_worker_exit(
|
|
2584
|
+
quest_id,
|
|
2585
|
+
timeout_seconds=_IMMEDIATE_READ_INTERRUPT_TIMEOUT_SECONDS,
|
|
2586
|
+
)
|
|
2587
|
+
if turn_state.get("running"):
|
|
2588
|
+
detail_parts = [
|
|
2589
|
+
f"runner={runner_name or 'unknown'}",
|
|
2590
|
+
f"interrupt_attempted={runner_supports_interrupt}",
|
|
2591
|
+
f"interrupt_returned={interrupted}",
|
|
2592
|
+
f"active_run_id={active_run_id or 'none'}",
|
|
2593
|
+
f"runtime_status={runtime_status or 'unknown'}",
|
|
2594
|
+
f"timeout_seconds={_IMMEDIATE_READ_INTERRUPT_TIMEOUT_SECONDS:g}",
|
|
2595
|
+
]
|
|
2596
|
+
worker = turn_state.get("worker")
|
|
2597
|
+
if isinstance(worker, threading.Thread):
|
|
2598
|
+
detail_parts.append(f"worker_alive={worker.is_alive()}")
|
|
2599
|
+
detail_parts.append(f"worker_name={worker.name}")
|
|
2600
|
+
stopped_count = len(stopped_bash_session_ids)
|
|
2601
|
+
if stopped_count > 0:
|
|
2602
|
+
detail_parts.append(f"stopped_bash_sessions={stopped_count}")
|
|
2603
|
+
if runner is None:
|
|
2604
|
+
detail_parts.append("reason=runner_not_available")
|
|
2605
|
+
elif not runner_supports_interrupt:
|
|
2606
|
+
detail_parts.append("reason=runner_has_no_interrupt")
|
|
2607
|
+
elif not interrupted:
|
|
2608
|
+
detail_parts.append("reason=runner_interrupt_returned_false")
|
|
2609
|
+
else:
|
|
2610
|
+
detail_parts.append("reason=worker_did_not_exit_before_timeout")
|
|
2611
|
+
raise RuntimeError(
|
|
2612
|
+
"The active agent turn could not be interrupted for immediate read. "
|
|
2613
|
+
+ "; ".join(detail_parts)
|
|
2614
|
+
)
|
|
2615
|
+
normalized_status = "active" if runtime_status == "running" else (runtime_status or "active")
|
|
2616
|
+
if normalized_status in {"stopped", "paused", "completed", "error"}:
|
|
2617
|
+
snapshot = self.quest_service.set_status(quest_id, "active")
|
|
2618
|
+
else:
|
|
2619
|
+
snapshot = self.quest_service.mark_turn_finished(quest_id, status=normalized_status)
|
|
2620
|
+
with self._turn_lock:
|
|
2621
|
+
state = self._turn_state.setdefault(quest_id, {"running": False, "pending": False})
|
|
2622
|
+
state["stop_requested"] = False
|
|
2623
|
+
state["running"] = False
|
|
2624
|
+
state.pop("worker", None)
|
|
2625
|
+
return {
|
|
2626
|
+
"snapshot": snapshot,
|
|
2627
|
+
"interrupted": interrupted,
|
|
2628
|
+
"stopped_bash_session_ids": stopped_bash_session_ids,
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
def read_queued_user_messages_now(
|
|
2632
|
+
self,
|
|
2633
|
+
quest_id: str,
|
|
2634
|
+
*,
|
|
2635
|
+
message_id: str | None = None,
|
|
2636
|
+
client_message_id: str | None = None,
|
|
2637
|
+
source: str = "local",
|
|
2638
|
+
) -> dict[str, Any]:
|
|
2639
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
2640
|
+
snapshot = self._reconcile_stale_active_turn(quest_id, snapshot=snapshot)
|
|
2641
|
+
quest_root = self.quest_service._quest_root(quest_id)
|
|
2642
|
+
target_message_id = str(message_id or "").strip() or None
|
|
2643
|
+
target_client_message_id = str(client_message_id or "").strip() or None
|
|
2644
|
+
if target_message_id or target_client_message_id:
|
|
2645
|
+
status_payload = self.quest_service.pending_user_message_status(
|
|
2646
|
+
quest_root,
|
|
2647
|
+
message_id=target_message_id,
|
|
2648
|
+
client_message_id=target_client_message_id,
|
|
2649
|
+
)
|
|
2650
|
+
queue_state = str(status_payload.get("queue_state") or "missing")
|
|
2651
|
+
current_message_state = (
|
|
2652
|
+
dict(status_payload.get("message_state") or {})
|
|
2653
|
+
if isinstance(status_payload.get("message_state"), dict)
|
|
2654
|
+
else None
|
|
2655
|
+
)
|
|
2656
|
+
if queue_state == "read":
|
|
2657
|
+
return {
|
|
2658
|
+
"ok": True,
|
|
2659
|
+
"status": "already_read",
|
|
2660
|
+
"quest_id": quest_id,
|
|
2661
|
+
"message": "This message was already sent to the agent.",
|
|
2662
|
+
"message_ids": [target_message_id] if target_message_id else [],
|
|
2663
|
+
"client_message_ids": [target_client_message_id] if target_client_message_id else [],
|
|
2664
|
+
"current_message_state": current_message_state,
|
|
2665
|
+
"snapshot": snapshot,
|
|
2666
|
+
}
|
|
2667
|
+
if queue_state == "withdrawn":
|
|
2668
|
+
return {
|
|
2669
|
+
"ok": False,
|
|
2670
|
+
"status": "withdrawn",
|
|
2671
|
+
"quest_id": quest_id,
|
|
2672
|
+
"message": "This message was already withdrawn from the waiting queue.",
|
|
2673
|
+
"message_ids": [target_message_id] if target_message_id else [],
|
|
2674
|
+
"client_message_ids": [target_client_message_id] if target_client_message_id else [],
|
|
2675
|
+
"current_message_state": current_message_state,
|
|
2676
|
+
"snapshot": snapshot,
|
|
2677
|
+
}
|
|
2678
|
+
if queue_state == "missing":
|
|
2679
|
+
target_label = target_message_id or f"client:{target_client_message_id}"
|
|
2680
|
+
return {
|
|
2681
|
+
"ok": False,
|
|
2682
|
+
"status": "missing",
|
|
2683
|
+
"quest_id": quest_id,
|
|
2684
|
+
"message": f"Message `{target_label}` is not waiting in the queue.",
|
|
2685
|
+
"message_ids": [target_message_id] if target_message_id else [],
|
|
2686
|
+
"client_message_ids": [target_client_message_id] if target_client_message_id else [],
|
|
2687
|
+
"current_message_state": current_message_state,
|
|
2688
|
+
"snapshot": snapshot,
|
|
2689
|
+
}
|
|
2690
|
+
queue_payload = self.quest_service._read_message_queue(quest_root)
|
|
2691
|
+
pending = [dict(item) for item in (queue_payload.get("pending") or [])]
|
|
2692
|
+
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip().lower()
|
|
2693
|
+
self.logger.log(
|
|
2694
|
+
"info",
|
|
2695
|
+
"quest.user_message.read_now.requested",
|
|
2696
|
+
quest_id=quest_id,
|
|
2697
|
+
source=source,
|
|
2698
|
+
message_id=target_message_id,
|
|
2699
|
+
client_message_id=target_client_message_id,
|
|
2700
|
+
pending_user_message_count=len(pending),
|
|
2701
|
+
runtime_status=runtime_status or None,
|
|
2702
|
+
)
|
|
2703
|
+
if not pending:
|
|
2704
|
+
return {
|
|
2705
|
+
"ok": False,
|
|
2706
|
+
"status": "empty",
|
|
2707
|
+
"quest_id": quest_id,
|
|
2708
|
+
"message": "No unread user message is waiting in the queue.",
|
|
2709
|
+
"snapshot": snapshot,
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
try:
|
|
2713
|
+
restart = self._quiet_interrupt_for_turn_restart(quest_id, source=source)
|
|
2714
|
+
except RuntimeError as exc:
|
|
2715
|
+
failed_message_ids = (
|
|
2716
|
+
[target_message_id]
|
|
2717
|
+
if target_message_id
|
|
2718
|
+
else [
|
|
2719
|
+
str(item.get("message_id") or "").strip()
|
|
2720
|
+
for item in pending
|
|
2721
|
+
if str(item.get("message_id") or "").strip()
|
|
2722
|
+
]
|
|
2723
|
+
)
|
|
2724
|
+
self.logger.log(
|
|
2725
|
+
"warning",
|
|
2726
|
+
"quest.user_message.read_now.interrupt_failed",
|
|
2727
|
+
quest_id=quest_id,
|
|
2728
|
+
source=source,
|
|
2729
|
+
message_id=target_message_id,
|
|
2730
|
+
client_message_id=target_client_message_id,
|
|
2731
|
+
pending_user_message_count=len(pending),
|
|
2732
|
+
error=str(exc),
|
|
2733
|
+
)
|
|
2734
|
+
return {
|
|
2735
|
+
"ok": False,
|
|
2736
|
+
"status": "interrupt_failed",
|
|
2737
|
+
"quest_id": quest_id,
|
|
2738
|
+
"message": str(exc),
|
|
2739
|
+
"message_ids": failed_message_ids,
|
|
2740
|
+
"snapshot": snapshot,
|
|
2741
|
+
}
|
|
2742
|
+
mailbox_payload = self.quest_service.consume_pending_user_messages(
|
|
2743
|
+
quest_root,
|
|
2744
|
+
interaction_id=None,
|
|
2745
|
+
delivery_reason="immediate_read",
|
|
2746
|
+
)
|
|
2747
|
+
recent_inbound_messages = [
|
|
2748
|
+
dict(item)
|
|
2749
|
+
for item in (mailbox_payload.get("recent_inbound_messages") or [])
|
|
2750
|
+
if isinstance(item, dict)
|
|
2751
|
+
]
|
|
2752
|
+
if not recent_inbound_messages:
|
|
2753
|
+
return {
|
|
2754
|
+
"ok": False,
|
|
2755
|
+
"quest_id": quest_id,
|
|
2756
|
+
"message": "No unread user message remained after the restart.",
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
self.quest_service.set_turn_message_override(
|
|
2760
|
+
quest_root,
|
|
2761
|
+
turn_reason="immediate_read",
|
|
2762
|
+
message=str(mailbox_payload.get("agent_instruction") or "").strip(),
|
|
2763
|
+
message_ids=[
|
|
2764
|
+
str(item.get("message_id") or "").strip()
|
|
2765
|
+
for item in recent_inbound_messages
|
|
2766
|
+
if str(item.get("message_id") or "").strip()
|
|
2767
|
+
],
|
|
2768
|
+
delivery_batch_id=str(((mailbox_payload.get("delivery_batch") or {}).get("batch_id") or "")).strip() or None,
|
|
2769
|
+
source=source,
|
|
2770
|
+
)
|
|
2771
|
+
post_snapshot = self.quest_service.snapshot(quest_id)
|
|
2772
|
+
runtime_status = str(post_snapshot.get("runtime_status") or post_snapshot.get("status") or "").strip().lower()
|
|
2773
|
+
if runtime_status in {"stopped", "paused", "completed", "error"}:
|
|
2774
|
+
post_snapshot = self.quest_service.set_status(quest_id, "active")
|
|
2775
|
+
scheduling = self.schedule_turn(quest_id, reason="immediate_read")
|
|
2776
|
+
message_states = [
|
|
2777
|
+
dict(item)
|
|
2778
|
+
for item in (mailbox_payload.get("message_states") or [])
|
|
2779
|
+
if isinstance(item, dict)
|
|
2780
|
+
]
|
|
2781
|
+
self.logger.log(
|
|
2782
|
+
"info",
|
|
2783
|
+
"quest.user_message.read_now.scheduled",
|
|
2784
|
+
quest_id=quest_id,
|
|
2785
|
+
source=source,
|
|
2786
|
+
message_ids=[
|
|
2787
|
+
str(item.get("message_id") or "").strip()
|
|
2788
|
+
for item in recent_inbound_messages
|
|
2789
|
+
if str(item.get("message_id") or "").strip()
|
|
2790
|
+
],
|
|
2791
|
+
client_message_ids=[
|
|
2792
|
+
str(item.get("client_message_id") or "").strip()
|
|
2793
|
+
for item in recent_inbound_messages
|
|
2794
|
+
if str(item.get("client_message_id") or "").strip()
|
|
2795
|
+
],
|
|
2796
|
+
scheduled=bool(scheduling.get("scheduled")),
|
|
2797
|
+
started=bool(scheduling.get("started")),
|
|
2798
|
+
queued=bool(scheduling.get("queued")),
|
|
2799
|
+
interrupted=bool(restart.get("interrupted")),
|
|
2800
|
+
)
|
|
2801
|
+
return {
|
|
2802
|
+
"ok": True,
|
|
2803
|
+
"status": "scheduled",
|
|
2804
|
+
"quest_id": quest_id,
|
|
2805
|
+
"snapshot": self.quest_service.snapshot(quest_id),
|
|
2806
|
+
"message_ids": [
|
|
2807
|
+
str(item.get("message_id") or "").strip()
|
|
2808
|
+
for item in recent_inbound_messages
|
|
2809
|
+
if str(item.get("message_id") or "").strip()
|
|
2810
|
+
],
|
|
2811
|
+
"client_message_ids": [
|
|
2812
|
+
str(item.get("client_message_id") or "").strip()
|
|
2813
|
+
for item in recent_inbound_messages
|
|
2814
|
+
if str(item.get("client_message_id") or "").strip()
|
|
2815
|
+
],
|
|
2816
|
+
"message_states": message_states,
|
|
2817
|
+
"current_message_state": message_states[0] if message_states else None,
|
|
2818
|
+
"delivery_batch": mailbox_payload.get("delivery_batch"),
|
|
2819
|
+
"recent_inbound_messages": recent_inbound_messages,
|
|
2820
|
+
"interrupted": bool(restart.get("interrupted")),
|
|
2821
|
+
"stopped_bash_session_ids": list(restart.get("stopped_bash_session_ids") or []),
|
|
2822
|
+
"message": "Queued user messages were forced into an immediate-read turn.",
|
|
2823
|
+
**scheduling,
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
def withdraw_queued_user_message(
|
|
2827
|
+
self,
|
|
2828
|
+
quest_id: str,
|
|
2829
|
+
*,
|
|
2830
|
+
message_id: str,
|
|
2831
|
+
source: str = "local",
|
|
2832
|
+
) -> dict[str, Any]:
|
|
2833
|
+
quest_root = self.quest_service._quest_root(quest_id)
|
|
2834
|
+
result = self.quest_service.withdraw_pending_user_message(
|
|
2835
|
+
quest_id,
|
|
2836
|
+
message_id=message_id,
|
|
1931
2837
|
source=source,
|
|
1932
|
-
snapshot=snapshot,
|
|
1933
|
-
interrupted=interrupted,
|
|
1934
|
-
cancelled_pending_user_message_count=cancelled_count,
|
|
1935
|
-
previous_snapshot=previous_snapshot,
|
|
1936
2838
|
)
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
"action": action,
|
|
1941
|
-
"interrupted": interrupted,
|
|
1942
|
-
"cancelled_pending_user_message_count": cancelled_count,
|
|
1943
|
-
"stopped_bash_session_ids": stopped_bash_session_ids,
|
|
1944
|
-
"snapshot": snapshot,
|
|
1945
|
-
"message": summary,
|
|
1946
|
-
"event": event,
|
|
1947
|
-
"notice": notice,
|
|
1948
|
-
}
|
|
2839
|
+
result.setdefault("quest_id", quest_id)
|
|
2840
|
+
result["snapshot"] = self.quest_service.snapshot(quest_id)
|
|
2841
|
+
return result
|
|
1949
2842
|
|
|
1950
2843
|
def _stop_active_bash_exec_sessions(
|
|
1951
2844
|
self,
|
|
@@ -2010,6 +2903,23 @@ class DaemonApp:
|
|
|
2010
2903
|
last_recovery_abandoned_run_id=recovery_abandoned_run_id,
|
|
2011
2904
|
last_recovery_summary=recovery_summary,
|
|
2012
2905
|
)
|
|
2906
|
+
scheduling: dict[str, Any] | None = None
|
|
2907
|
+
if not source.startswith("auto:"):
|
|
2908
|
+
if int(snapshot.get("pending_user_message_count") or 0) > 0:
|
|
2909
|
+
scheduling = self.schedule_turn(quest_id, reason="queued_user_messages")
|
|
2910
|
+
else:
|
|
2911
|
+
continue_payload = self.submit_user_message(
|
|
2912
|
+
quest_id,
|
|
2913
|
+
text=_RESUME_CONTINUE_TEXT,
|
|
2914
|
+
source=self._resume_continue_source(source),
|
|
2915
|
+
)
|
|
2916
|
+
scheduling = {
|
|
2917
|
+
"scheduled": bool(continue_payload.get("scheduled")),
|
|
2918
|
+
"started": bool(continue_payload.get("started")),
|
|
2919
|
+
"queued": bool(continue_payload.get("queued")),
|
|
2920
|
+
"reason": str(continue_payload.get("reason") or "user_message"),
|
|
2921
|
+
"resume_trigger_message": continue_payload.get("message"),
|
|
2922
|
+
}
|
|
2013
2923
|
summary = f"Quest {quest_id} resumed."
|
|
2014
2924
|
event = self._append_control_event(
|
|
2015
2925
|
quest_id,
|
|
@@ -2038,6 +2948,7 @@ class DaemonApp:
|
|
|
2038
2948
|
"message": summary,
|
|
2039
2949
|
"event": event,
|
|
2040
2950
|
"notice": notice,
|
|
2951
|
+
**(dict(scheduling or {})),
|
|
2041
2952
|
}
|
|
2042
2953
|
|
|
2043
2954
|
def _append_control_event(
|
|
@@ -2249,8 +3160,17 @@ class DaemonApp:
|
|
|
2249
3160
|
runtime_status = str(snapshot.get("runtime_status") or snapshot.get("status") or "").strip()
|
|
2250
3161
|
if runtime_status in {"stopped", "paused", "completed", "error"} and not snapshot.get("active_run_id"):
|
|
2251
3162
|
return
|
|
2252
|
-
|
|
2253
|
-
|
|
3163
|
+
quest_root = Path(snapshot["quest_root"])
|
|
3164
|
+
turn_override = (
|
|
3165
|
+
self.quest_service.consume_turn_message_override(
|
|
3166
|
+
quest_root,
|
|
3167
|
+
expected_turn_reason=turn_reason,
|
|
3168
|
+
)
|
|
3169
|
+
if turn_reason == "immediate_read"
|
|
3170
|
+
else None
|
|
3171
|
+
)
|
|
3172
|
+
latest_user_message = self._latest_actionable_user_message(quest_id, turn_reason=turn_reason)
|
|
3173
|
+
if turn_reason not in {"auto_continue", "immediate_read"} and latest_user_message is None:
|
|
2254
3174
|
return
|
|
2255
3175
|
|
|
2256
3176
|
runner_name = self._runner_name_for(snapshot)
|
|
@@ -2262,7 +3182,11 @@ class DaemonApp:
|
|
|
2262
3182
|
model = str(runner_cfg.get("model", "gpt-5.4"))
|
|
2263
3183
|
run_message = ""
|
|
2264
3184
|
claimed_message_id: str | None = None
|
|
2265
|
-
if turn_reason
|
|
3185
|
+
if turn_reason == "immediate_read":
|
|
3186
|
+
run_message = str((turn_override or {}).get("message") or "").strip()
|
|
3187
|
+
if not run_message:
|
|
3188
|
+
return
|
|
3189
|
+
elif turn_reason != "auto_continue":
|
|
2266
3190
|
run_message = str((latest_user_message or {}).get("content") or "").strip()
|
|
2267
3191
|
claimed_message_id = str((latest_user_message or {}).get("id") or "").strip() or None
|
|
2268
3192
|
if not run_message:
|
|
@@ -2321,12 +3245,14 @@ class DaemonApp:
|
|
|
2321
3245
|
)
|
|
2322
3246
|
retry_policy = self._runner_retry_policy(runner_name, runner_cfg if isinstance(runner_cfg, dict) else {})
|
|
2323
3247
|
max_attempts = int(retry_policy.get("max_attempts") or 1)
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
3248
|
+
resumed_start_attempt, resumed_turn_id, retry_context = self._resume_retry_state(
|
|
3249
|
+
snapshot,
|
|
3250
|
+
max_attempts=max_attempts,
|
|
3251
|
+
)
|
|
3252
|
+
turn_id = resumed_turn_id or generate_id("turn")
|
|
2327
3253
|
worktree_root = Path(str(snapshot["current_workspace_root"])) if snapshot.get("current_workspace_root") else None
|
|
2328
3254
|
|
|
2329
|
-
for attempt_index in range(
|
|
3255
|
+
for attempt_index in range(resumed_start_attempt, max_attempts + 1):
|
|
2330
3256
|
current_run_id = run_id if attempt_index == 1 else generate_id("run")
|
|
2331
3257
|
if attempt_index > 1:
|
|
2332
3258
|
self._append_retry_event(
|
|
@@ -2395,6 +3321,31 @@ class DaemonApp:
|
|
|
2395
3321
|
previous_output_text="",
|
|
2396
3322
|
stderr_text=str(exc),
|
|
2397
3323
|
)
|
|
3324
|
+
diagnosis = self._non_retryable_failure_diagnosis(
|
|
3325
|
+
runner_name=runner_name,
|
|
3326
|
+
summary=failure_summary,
|
|
3327
|
+
stderr_text=str(exc),
|
|
3328
|
+
output_text="",
|
|
3329
|
+
)
|
|
3330
|
+
if diagnosis is not None:
|
|
3331
|
+
self.quest_service.update_runtime_state(
|
|
3332
|
+
quest_root=quest_root,
|
|
3333
|
+
continuation_policy="wait_for_user_or_resume",
|
|
3334
|
+
continuation_reason="non_retryable_runner_error",
|
|
3335
|
+
continuation_updated_at=utc_now(),
|
|
3336
|
+
)
|
|
3337
|
+
self._record_turn_error(
|
|
3338
|
+
quest_id=quest_id,
|
|
3339
|
+
runner_name=runner_name,
|
|
3340
|
+
run_id=current_run_id,
|
|
3341
|
+
skill_id=skill_id,
|
|
3342
|
+
model=model,
|
|
3343
|
+
summary=f"{diagnosis.problem} {failure_summary}".strip(),
|
|
3344
|
+
retry_state=None,
|
|
3345
|
+
diagnosis_code=diagnosis.code,
|
|
3346
|
+
guidance=list(diagnosis.guidance),
|
|
3347
|
+
)
|
|
3348
|
+
return
|
|
2398
3349
|
if bool(retry_policy.get("enabled")) and attempt_index < max_attempts:
|
|
2399
3350
|
delay_seconds = self._retry_delay_seconds(retry_policy, attempt_index=attempt_index + 1)
|
|
2400
3351
|
next_retry_at = self._retry_next_timestamp(delay_seconds)
|
|
@@ -2444,6 +3395,19 @@ class DaemonApp:
|
|
|
2444
3395
|
)
|
|
2445
3396
|
return
|
|
2446
3397
|
exhausted_summary = f"{failure_summary} Retry budget exhausted after {attempt_index} attempt(s)."
|
|
3398
|
+
diagnosis = self._runner_failure_diagnosis(
|
|
3399
|
+
runner_name=runner_name,
|
|
3400
|
+
summary=exhausted_summary,
|
|
3401
|
+
stderr_text=str(exc),
|
|
3402
|
+
output_text="",
|
|
3403
|
+
)
|
|
3404
|
+
if diagnosis is not None and diagnosis.retriable:
|
|
3405
|
+
self.quest_service.update_runtime_state(
|
|
3406
|
+
quest_root=quest_root,
|
|
3407
|
+
continuation_policy="wait_for_user_or_resume",
|
|
3408
|
+
continuation_reason=self._retry_exhausted_continuation_reason(diagnosis),
|
|
3409
|
+
continuation_updated_at=utc_now(),
|
|
3410
|
+
)
|
|
2447
3411
|
self._append_retry_event(
|
|
2448
3412
|
quest_id,
|
|
2449
3413
|
event_type="runner.turn_retry_exhausted",
|
|
@@ -2456,6 +3420,7 @@ class DaemonApp:
|
|
|
2456
3420
|
max_attempts=max_attempts,
|
|
2457
3421
|
summary=exhausted_summary,
|
|
2458
3422
|
failure_summary=failure_summary,
|
|
3423
|
+
diagnosis=diagnosis,
|
|
2459
3424
|
)
|
|
2460
3425
|
self._record_turn_error(
|
|
2461
3426
|
quest_id=quest_id,
|
|
@@ -2465,6 +3430,8 @@ class DaemonApp:
|
|
|
2465
3430
|
model=model,
|
|
2466
3431
|
summary=exhausted_summary,
|
|
2467
3432
|
retry_state=None,
|
|
3433
|
+
diagnosis_code=diagnosis.code if diagnosis is not None else None,
|
|
3434
|
+
guidance=list(diagnosis.guidance) if diagnosis is not None else None,
|
|
2468
3435
|
)
|
|
2469
3436
|
return
|
|
2470
3437
|
|
|
@@ -2543,6 +3510,31 @@ class DaemonApp:
|
|
|
2543
3510
|
previous_output_text=result.output_text,
|
|
2544
3511
|
stderr_text=result.stderr_text,
|
|
2545
3512
|
)
|
|
3513
|
+
diagnosis = self._non_retryable_failure_diagnosis(
|
|
3514
|
+
runner_name=runner_name,
|
|
3515
|
+
summary=failure_summary,
|
|
3516
|
+
stderr_text=result.stderr_text,
|
|
3517
|
+
output_text=result.output_text,
|
|
3518
|
+
)
|
|
3519
|
+
if diagnosis is not None:
|
|
3520
|
+
self.quest_service.update_runtime_state(
|
|
3521
|
+
quest_root=quest_root,
|
|
3522
|
+
continuation_policy="wait_for_user_or_resume",
|
|
3523
|
+
continuation_reason="non_retryable_runner_error",
|
|
3524
|
+
continuation_updated_at=utc_now(),
|
|
3525
|
+
)
|
|
3526
|
+
self._record_turn_error(
|
|
3527
|
+
quest_id=quest_id,
|
|
3528
|
+
runner_name=runner_name,
|
|
3529
|
+
run_id=result.run_id,
|
|
3530
|
+
skill_id=skill_id,
|
|
3531
|
+
model=model,
|
|
3532
|
+
summary=f"{diagnosis.problem} {failure_summary}".strip(),
|
|
3533
|
+
retry_state=None,
|
|
3534
|
+
diagnosis_code=diagnosis.code,
|
|
3535
|
+
guidance=list(diagnosis.guidance),
|
|
3536
|
+
)
|
|
3537
|
+
return
|
|
2546
3538
|
if bool(retry_policy.get("enabled")) and attempt_index < max_attempts:
|
|
2547
3539
|
delay_seconds = self._retry_delay_seconds(retry_policy, attempt_index=attempt_index + 1)
|
|
2548
3540
|
next_retry_at = self._retry_next_timestamp(delay_seconds)
|
|
@@ -2593,6 +3585,19 @@ class DaemonApp:
|
|
|
2593
3585
|
return
|
|
2594
3586
|
|
|
2595
3587
|
exhausted_summary = f"{failure_summary} Retry budget exhausted after {attempt_index} attempt(s)."
|
|
3588
|
+
diagnosis = self._runner_failure_diagnosis(
|
|
3589
|
+
runner_name=runner_name,
|
|
3590
|
+
summary=exhausted_summary,
|
|
3591
|
+
stderr_text=result.stderr_text,
|
|
3592
|
+
output_text=result.output_text,
|
|
3593
|
+
)
|
|
3594
|
+
if diagnosis is not None and diagnosis.retriable:
|
|
3595
|
+
self.quest_service.update_runtime_state(
|
|
3596
|
+
quest_root=quest_root,
|
|
3597
|
+
continuation_policy="wait_for_user_or_resume",
|
|
3598
|
+
continuation_reason=self._retry_exhausted_continuation_reason(diagnosis),
|
|
3599
|
+
continuation_updated_at=utc_now(),
|
|
3600
|
+
)
|
|
2596
3601
|
self._append_retry_event(
|
|
2597
3602
|
quest_id,
|
|
2598
3603
|
event_type="runner.turn_retry_exhausted",
|
|
@@ -2605,6 +3610,7 @@ class DaemonApp:
|
|
|
2605
3610
|
max_attempts=max_attempts,
|
|
2606
3611
|
summary=exhausted_summary,
|
|
2607
3612
|
failure_summary=failure_summary,
|
|
3613
|
+
diagnosis=diagnosis,
|
|
2608
3614
|
)
|
|
2609
3615
|
self._record_turn_error(
|
|
2610
3616
|
quest_id=quest_id,
|
|
@@ -2614,6 +3620,8 @@ class DaemonApp:
|
|
|
2614
3620
|
model=model,
|
|
2615
3621
|
summary=exhausted_summary,
|
|
2616
3622
|
retry_state=None,
|
|
3623
|
+
diagnosis_code=diagnosis.code if diagnosis is not None else None,
|
|
3624
|
+
guidance=list(diagnosis.guidance) if diagnosis is not None else None,
|
|
2617
3625
|
)
|
|
2618
3626
|
return
|
|
2619
3627
|
finally:
|
|
@@ -2624,8 +3632,37 @@ class DaemonApp:
|
|
|
2624
3632
|
)
|
|
2625
3633
|
|
|
2626
3634
|
def _runner_name_for(self, snapshot: dict) -> str:
|
|
3635
|
+
return self._resolve_enabled_runner_name(snapshot)
|
|
3636
|
+
|
|
3637
|
+
def _resolve_enabled_runner_name(self, snapshot: dict, requested_runner: str | None = None) -> str:
|
|
2627
3638
|
configured = self.config_manager.load_named("config")
|
|
2628
|
-
|
|
3639
|
+
candidates = [
|
|
3640
|
+
requested_runner,
|
|
3641
|
+
snapshot.get("runner"),
|
|
3642
|
+
snapshot.get("default_runner"),
|
|
3643
|
+
configured.get("default_runner", "codex"),
|
|
3644
|
+
"codex",
|
|
3645
|
+
]
|
|
3646
|
+
enabled: list[str] = []
|
|
3647
|
+
seen_enabled: set[str] = set()
|
|
3648
|
+
for name, cfg in self.runners_config.items():
|
|
3649
|
+
normalized = str(name or "").strip().lower()
|
|
3650
|
+
if not normalized or normalized in seen_enabled:
|
|
3651
|
+
continue
|
|
3652
|
+
seen_enabled.add(normalized)
|
|
3653
|
+
if isinstance(cfg, dict) and cfg.get("enabled") is not False:
|
|
3654
|
+
enabled.append(normalized)
|
|
3655
|
+
|
|
3656
|
+
checked: set[str] = set()
|
|
3657
|
+
for raw in candidates:
|
|
3658
|
+
normalized = str(raw or "").strip().lower()
|
|
3659
|
+
if not normalized or normalized in checked:
|
|
3660
|
+
continue
|
|
3661
|
+
checked.add(normalized)
|
|
3662
|
+
cfg = self.runners_config.get(normalized, {})
|
|
3663
|
+
if isinstance(cfg, dict) and cfg.get("enabled") is not False:
|
|
3664
|
+
return normalized
|
|
3665
|
+
return enabled[0] if enabled else "codex"
|
|
2629
3666
|
|
|
2630
3667
|
@staticmethod
|
|
2631
3668
|
def _stage_state_fingerprint(snapshot: dict) -> str:
|
|
@@ -2699,14 +3736,54 @@ class DaemonApp:
|
|
|
2699
3736
|
return value
|
|
2700
3737
|
return "autonomous"
|
|
2701
3738
|
|
|
3739
|
+
@staticmethod
|
|
3740
|
+
def _decision_policy_for(snapshot: dict) -> str:
|
|
3741
|
+
startup_contract = snapshot.get("startup_contract")
|
|
3742
|
+
if isinstance(startup_contract, dict):
|
|
3743
|
+
value = str(startup_contract.get("decision_policy") or "").strip().lower()
|
|
3744
|
+
if value in {"autonomous", "user_gated"}:
|
|
3745
|
+
return value
|
|
3746
|
+
return "user_gated"
|
|
3747
|
+
|
|
3748
|
+
def _auto_resume_wait_if_allowed(self, quest_id: str, snapshot: dict) -> tuple[dict, bool]:
|
|
3749
|
+
if str(snapshot.get("continuation_policy") or "").strip().lower() != "wait_for_user_or_resume":
|
|
3750
|
+
return snapshot, False
|
|
3751
|
+
if self._decision_policy_for(snapshot) != "autonomous":
|
|
3752
|
+
return snapshot, False
|
|
3753
|
+
reason = str(snapshot.get("continuation_reason") or "").strip() or "wait_for_user_or_resume"
|
|
3754
|
+
if reason in AUTONOMOUS_BLOCKING_WAIT_REASONS:
|
|
3755
|
+
return snapshot, False
|
|
3756
|
+
quest_root = self.quest_service._quest_root(quest_id)
|
|
3757
|
+
message = self.quest_service.localized_copy(
|
|
3758
|
+
quest_root=quest_root,
|
|
3759
|
+
zh=f"【自动继续】系统检测到当前为无需询问模式,本次“等待反馈”已自动转换为继续执行。原因:{reason}。",
|
|
3760
|
+
en=f"[Auto-resumed] This quest is in autonomous decision mode, so the waiting state was converted back to automatic continuation. Reason: {reason}.",
|
|
3761
|
+
)
|
|
3762
|
+
notice = {
|
|
3763
|
+
"status": "auto_resumed",
|
|
3764
|
+
"reason": reason,
|
|
3765
|
+
"message": message,
|
|
3766
|
+
"decision_policy": "autonomous",
|
|
3767
|
+
"label": self.quest_service.localized_copy(quest_root=quest_root, zh="自动继续", en="Auto-resumed"),
|
|
3768
|
+
"created_at": utc_now(),
|
|
3769
|
+
}
|
|
3770
|
+
self.quest_service.update_runtime_state(
|
|
3771
|
+
quest_root=quest_root,
|
|
3772
|
+
continuation_policy="auto",
|
|
3773
|
+
continuation_reason=f"{reason}_auto_resumed",
|
|
3774
|
+
waiting_notice=notice,
|
|
3775
|
+
)
|
|
3776
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
3777
|
+
return snapshot, True
|
|
3778
|
+
|
|
2702
3779
|
def _resolve_continuation_policy(self, snapshot: dict, *, current_policy: str) -> tuple[str, str]:
|
|
2703
3780
|
normalized = str(current_policy or "auto").strip().lower() or "auto"
|
|
2704
3781
|
if normalized != "auto":
|
|
2705
3782
|
return normalized, str(snapshot.get("continuation_reason") or "").strip() or "explicit_continuation_policy"
|
|
2706
|
-
if self._workspace_mode_for(snapshot) == "copilot":
|
|
2707
|
-
return "wait_for_user_or_resume", "copilot_mode"
|
|
2708
3783
|
if self._has_external_progress(snapshot):
|
|
2709
3784
|
return "when_external_progress", "background_external_progress_active"
|
|
3785
|
+
if self._workspace_mode_for(snapshot) == "copilot":
|
|
3786
|
+
return "wait_for_user_or_resume", "copilot_mode"
|
|
2710
3787
|
return "auto", "autonomous_prepare_or_launch_long_run"
|
|
2711
3788
|
|
|
2712
3789
|
@staticmethod
|
|
@@ -2736,6 +3813,29 @@ class DaemonApp:
|
|
|
2736
3813
|
|
|
2737
3814
|
return skill
|
|
2738
3815
|
|
|
3816
|
+
@staticmethod
|
|
3817
|
+
def _direct_user_turn_skill(snapshot: dict) -> str:
|
|
3818
|
+
available_stage_skills = current_standard_skills(repo_root())
|
|
3819
|
+
workspace_mode = DaemonApp._workspace_mode_for(snapshot)
|
|
3820
|
+
has_explicit_baseline_context = bool(
|
|
3821
|
+
isinstance(snapshot.get("confirmed_baseline_ref"), dict)
|
|
3822
|
+
or isinstance(snapshot.get("requested_baseline_ref"), dict)
|
|
3823
|
+
or str(snapshot.get("active_baseline_id") or "").strip()
|
|
3824
|
+
)
|
|
3825
|
+
for candidate in (
|
|
3826
|
+
str(snapshot.get("active_anchor") or "").strip(),
|
|
3827
|
+
str(snapshot.get("continuation_anchor") or "").strip(),
|
|
3828
|
+
):
|
|
3829
|
+
if candidate in available_stage_skills and candidate != "decision":
|
|
3830
|
+
if workspace_mode == "copilot" and candidate == "baseline" and not has_explicit_baseline_context:
|
|
3831
|
+
continue
|
|
3832
|
+
return DaemonApp._turn_skill_stage_gate(snapshot, candidate)
|
|
3833
|
+
if workspace_mode == "copilot" and "scout" in available_stage_skills:
|
|
3834
|
+
fallback = "scout"
|
|
3835
|
+
else:
|
|
3836
|
+
fallback = "baseline" if "baseline" in available_stage_skills else "scout"
|
|
3837
|
+
return DaemonApp._turn_skill_stage_gate(snapshot, fallback)
|
|
3838
|
+
|
|
2739
3839
|
@staticmethod
|
|
2740
3840
|
def _turn_skill_for(
|
|
2741
3841
|
snapshot: dict,
|
|
@@ -2747,16 +3847,6 @@ class DaemonApp:
|
|
|
2747
3847
|
available_stage_skills = current_standard_skills(repo_root())
|
|
2748
3848
|
workspace_mode = DaemonApp._workspace_mode_for(snapshot)
|
|
2749
3849
|
|
|
2750
|
-
def copilot_default_skill() -> str:
|
|
2751
|
-
active_anchor = str(snapshot.get("active_anchor") or "").strip()
|
|
2752
|
-
if active_anchor in available_stage_skills and active_anchor != "decision":
|
|
2753
|
-
return DaemonApp._turn_skill_stage_gate(snapshot, active_anchor)
|
|
2754
|
-
continuation_anchor = str(snapshot.get("continuation_anchor") or "").strip()
|
|
2755
|
-
if continuation_anchor in available_stage_skills and continuation_anchor != "decision":
|
|
2756
|
-
return DaemonApp._turn_skill_stage_gate(snapshot, continuation_anchor)
|
|
2757
|
-
fallback = "baseline" if "baseline" in available_stage_skills else "scout"
|
|
2758
|
-
return DaemonApp._turn_skill_stage_gate(snapshot, fallback)
|
|
2759
|
-
|
|
2760
3850
|
reply_target = str((latest_user_message or {}).get("reply_to_interaction_id") or "").strip()
|
|
2761
3851
|
if reply_target:
|
|
2762
3852
|
for item in (snapshot.get("active_interactions") or []):
|
|
@@ -2783,8 +3873,8 @@ class DaemonApp:
|
|
|
2783
3873
|
):
|
|
2784
3874
|
return "decision"
|
|
2785
3875
|
if str(item.get("reply_mode") or "") == "threaded":
|
|
2786
|
-
if workspace_mode == "copilot":
|
|
2787
|
-
return
|
|
3876
|
+
if workspace_mode == "copilot" or turn_mode in {"answering", "command_execution"}:
|
|
3877
|
+
return DaemonApp._direct_user_turn_skill(snapshot)
|
|
2788
3878
|
return DaemonApp._turn_skill_stage_gate(
|
|
2789
3879
|
snapshot,
|
|
2790
3880
|
DaemonApp._continuation_anchor_for(snapshot),
|
|
@@ -2792,9 +3882,9 @@ class DaemonApp:
|
|
|
2792
3882
|
if turn_mode == "recovering":
|
|
2793
3883
|
return "decision"
|
|
2794
3884
|
if workspace_mode == "copilot" and latest_user_message is not None:
|
|
2795
|
-
return
|
|
3885
|
+
return DaemonApp._direct_user_turn_skill(snapshot)
|
|
2796
3886
|
if turn_mode in {"answering", "command_execution"}:
|
|
2797
|
-
return
|
|
3887
|
+
return DaemonApp._direct_user_turn_skill(snapshot)
|
|
2798
3888
|
if str(turn_reason or "").strip() == "auto_continue" or latest_user_message is None:
|
|
2799
3889
|
return DaemonApp._turn_skill_stage_gate(
|
|
2800
3890
|
snapshot,
|
|
@@ -2818,6 +3908,14 @@ class DaemonApp:
|
|
|
2818
3908
|
return item
|
|
2819
3909
|
return None
|
|
2820
3910
|
|
|
3911
|
+
def _latest_actionable_user_message(self, quest_id: str, *, turn_reason: str) -> dict | None:
|
|
3912
|
+
normalized_reason = str(turn_reason or "").strip() or "user_message"
|
|
3913
|
+
if normalized_reason in {"user_message", "queued_user_messages"}:
|
|
3914
|
+
pending_message = self.quest_service.latest_pending_user_message(quest_id)
|
|
3915
|
+
if pending_message is not None:
|
|
3916
|
+
return pending_message
|
|
3917
|
+
return self._latest_user_message(quest_id)
|
|
3918
|
+
|
|
2821
3919
|
@staticmethod
|
|
2822
3920
|
def _runner_binary_issue(runner_name: str, runner: object) -> str | None:
|
|
2823
3921
|
binary = getattr(runner, "binary", None)
|
|
@@ -2896,6 +3994,14 @@ class DaemonApp:
|
|
|
2896
3994
|
initial_backoff = CODEX_RETRY_DEFAULT_INITIAL_BACKOFF_SEC
|
|
2897
3995
|
multiplier = CODEX_RETRY_DEFAULT_BACKOFF_MULTIPLIER
|
|
2898
3996
|
max_backoff = CODEX_RETRY_DEFAULT_MAX_BACKOFF_SEC
|
|
3997
|
+
if (
|
|
3998
|
+
runner_name == "codex"
|
|
3999
|
+
and max_attempts == PREVIOUS_CODEX_RETRY_DEFAULT_MAX_ATTEMPTS
|
|
4000
|
+
and self._float_matches(initial_backoff, CODEX_RETRY_DEFAULT_INITIAL_BACKOFF_SEC)
|
|
4001
|
+
and self._float_matches(multiplier, CODEX_RETRY_DEFAULT_BACKOFF_MULTIPLIER)
|
|
4002
|
+
and self._float_matches(max_backoff, CODEX_RETRY_DEFAULT_MAX_BACKOFF_SEC)
|
|
4003
|
+
):
|
|
4004
|
+
max_attempts = CODEX_RETRY_DEFAULT_MAX_ATTEMPTS
|
|
2899
4005
|
return {
|
|
2900
4006
|
"enabled": enabled,
|
|
2901
4007
|
"max_attempts": max_attempts,
|
|
@@ -2954,6 +4060,7 @@ class DaemonApp:
|
|
|
2954
4060
|
backoff_seconds: float | None = None,
|
|
2955
4061
|
next_attempt_index: int | None = None,
|
|
2956
4062
|
previous_run_id: str | None = None,
|
|
4063
|
+
diagnosis: FailureDiagnosis | None = None,
|
|
2957
4064
|
) -> dict[str, Any]:
|
|
2958
4065
|
payload = {
|
|
2959
4066
|
"event_id": generate_id("evt"),
|
|
@@ -2977,6 +4084,9 @@ class DaemonApp:
|
|
|
2977
4084
|
payload["next_attempt_index"] = next_attempt_index
|
|
2978
4085
|
if previous_run_id:
|
|
2979
4086
|
payload["previous_run_id"] = previous_run_id
|
|
4087
|
+
if diagnosis is not None:
|
|
4088
|
+
payload["diagnosis_code"] = diagnosis.code
|
|
4089
|
+
payload["diagnosis"] = self._failure_diagnosis_payload(diagnosis)
|
|
2980
4090
|
append_jsonl(self.home / "quests" / quest_id / ".ds" / "events.jsonl", payload)
|
|
2981
4091
|
self.logger.log(
|
|
2982
4092
|
"warning" if "scheduled" in event_type or "exhausted" in event_type else "info",
|
|
@@ -2990,6 +4100,7 @@ class DaemonApp:
|
|
|
2990
4100
|
backoff_seconds=backoff_seconds,
|
|
2991
4101
|
next_attempt_index=next_attempt_index,
|
|
2992
4102
|
previous_run_id=previous_run_id,
|
|
4103
|
+
diagnosis_code=diagnosis.code if diagnosis is not None else None,
|
|
2993
4104
|
)
|
|
2994
4105
|
return payload
|
|
2995
4106
|
|
|
@@ -3108,8 +4219,11 @@ class DaemonApp:
|
|
|
3108
4219
|
summary: str,
|
|
3109
4220
|
display_status: str = "error",
|
|
3110
4221
|
retry_state: dict[str, Any] | None = None,
|
|
4222
|
+
diagnosis_code: str | None = None,
|
|
4223
|
+
guidance: list[str] | None = None,
|
|
3111
4224
|
) -> None:
|
|
3112
4225
|
quest_root = self.home / "quests" / quest_id
|
|
4226
|
+
normalized_guidance = [str(line) for line in (guidance or []) if str(line).strip()]
|
|
3113
4227
|
append_jsonl(
|
|
3114
4228
|
quest_root / ".ds" / "events.jsonl",
|
|
3115
4229
|
{
|
|
@@ -3121,6 +4235,8 @@ class DaemonApp:
|
|
|
3121
4235
|
"skill_id": skill_id,
|
|
3122
4236
|
"model": model,
|
|
3123
4237
|
"summary": summary,
|
|
4238
|
+
"diagnosis_code": str(diagnosis_code or "").strip() or None,
|
|
4239
|
+
"guidance": normalized_guidance,
|
|
3124
4240
|
"created_at": utc_now(),
|
|
3125
4241
|
},
|
|
3126
4242
|
)
|
|
@@ -3131,6 +4247,23 @@ class DaemonApp:
|
|
|
3131
4247
|
active_run_id=None,
|
|
3132
4248
|
retry_state=retry_state,
|
|
3133
4249
|
)
|
|
4250
|
+
runner_label = self._connector_runner_label(runner_name)
|
|
4251
|
+
user_notice = self._polite_copy(
|
|
4252
|
+
zh=f"DeepScientist 意外停止运行,请检查你所绑定的{runner_label}是否能够顺利连接。",
|
|
4253
|
+
en=f"DeepScientist stopped unexpectedly. Please check whether the bound {runner_label} can connect normally.",
|
|
4254
|
+
)
|
|
4255
|
+
notice_message = "\n".join([user_notice, "", summary]).strip()
|
|
4256
|
+
if normalized_guidance:
|
|
4257
|
+
notice_message = "\n".join(
|
|
4258
|
+
[
|
|
4259
|
+
user_notice,
|
|
4260
|
+
"",
|
|
4261
|
+
summary,
|
|
4262
|
+
"",
|
|
4263
|
+
"Suggested fix:",
|
|
4264
|
+
*[f"- {line}" for line in normalized_guidance[:3]],
|
|
4265
|
+
]
|
|
4266
|
+
).strip()
|
|
3134
4267
|
self.logger.log(
|
|
3135
4268
|
"error",
|
|
3136
4269
|
"runner.turn_error",
|
|
@@ -3143,7 +4276,7 @@ class DaemonApp:
|
|
|
3143
4276
|
)
|
|
3144
4277
|
self._relay_quest_message_to_bound_connectors(
|
|
3145
4278
|
quest_id,
|
|
3146
|
-
message=
|
|
4279
|
+
message=notice_message,
|
|
3147
4280
|
kind="error",
|
|
3148
4281
|
response_phase="final",
|
|
3149
4282
|
importance="warning",
|
|
@@ -3154,10 +4287,63 @@ class DaemonApp:
|
|
|
3154
4287
|
"skill_id": skill_id,
|
|
3155
4288
|
"runner": runner_name,
|
|
3156
4289
|
"model": model,
|
|
4290
|
+
"diagnosis_code": str(diagnosis_code or "").strip() or None,
|
|
3157
4291
|
}
|
|
3158
4292
|
],
|
|
3159
4293
|
)
|
|
3160
4294
|
|
|
4295
|
+
@staticmethod
|
|
4296
|
+
def _failure_diagnosis_payload(diagnosis: FailureDiagnosis) -> dict[str, Any]:
|
|
4297
|
+
fix = [str(line) for line in diagnosis.guidance if str(line).strip()]
|
|
4298
|
+
return {
|
|
4299
|
+
"code": diagnosis.code,
|
|
4300
|
+
"problem": diagnosis.problem,
|
|
4301
|
+
"why": diagnosis.why,
|
|
4302
|
+
"fix": fix,
|
|
4303
|
+
"guidance": fix,
|
|
4304
|
+
"retriable": bool(diagnosis.retriable),
|
|
4305
|
+
"matched_text": diagnosis.matched_text,
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
@staticmethod
|
|
4309
|
+
def _runner_failure_diagnosis(
|
|
4310
|
+
*,
|
|
4311
|
+
runner_name: str,
|
|
4312
|
+
summary: str,
|
|
4313
|
+
stderr_text: str,
|
|
4314
|
+
output_text: str,
|
|
4315
|
+
) -> FailureDiagnosis | None:
|
|
4316
|
+
return diagnose_runner_failure(
|
|
4317
|
+
runner_name=runner_name,
|
|
4318
|
+
summary=summary,
|
|
4319
|
+
stderr_text=stderr_text,
|
|
4320
|
+
output_text=output_text,
|
|
4321
|
+
)
|
|
4322
|
+
|
|
4323
|
+
@staticmethod
|
|
4324
|
+
def _non_retryable_failure_diagnosis(
|
|
4325
|
+
*,
|
|
4326
|
+
runner_name: str,
|
|
4327
|
+
summary: str,
|
|
4328
|
+
stderr_text: str,
|
|
4329
|
+
output_text: str,
|
|
4330
|
+
) -> FailureDiagnosis | None:
|
|
4331
|
+
diagnosis = DaemonApp._runner_failure_diagnosis(
|
|
4332
|
+
runner_name=runner_name,
|
|
4333
|
+
summary=summary,
|
|
4334
|
+
stderr_text=stderr_text,
|
|
4335
|
+
output_text=output_text,
|
|
4336
|
+
)
|
|
4337
|
+
if diagnosis is None or diagnosis.retriable:
|
|
4338
|
+
return None
|
|
4339
|
+
return diagnosis
|
|
4340
|
+
|
|
4341
|
+
@staticmethod
|
|
4342
|
+
def _retry_exhausted_continuation_reason(diagnosis: FailureDiagnosis) -> str:
|
|
4343
|
+
if diagnosis.code == "codex_upstream_provider_error":
|
|
4344
|
+
return "external_codex_upstream_provider_error"
|
|
4345
|
+
return "runner_retry_budget_exhausted"
|
|
4346
|
+
|
|
3161
4347
|
def _record_turn_postprocess_warning(
|
|
3162
4348
|
self,
|
|
3163
4349
|
*,
|
|
@@ -3289,11 +4475,13 @@ class DaemonApp:
|
|
|
3289
4475
|
self.schedule_turn(quest_id, reason="queued_user_messages")
|
|
3290
4476
|
else:
|
|
3291
4477
|
continuation_policy = str(snapshot.get("continuation_policy") or "auto").strip().lower() or "auto"
|
|
4478
|
+
resolved_external_progress = False
|
|
3292
4479
|
if continuation_policy == "auto":
|
|
3293
4480
|
continuation_policy, continuation_reason = self._resolve_continuation_policy(
|
|
3294
4481
|
snapshot,
|
|
3295
4482
|
current_policy=continuation_policy,
|
|
3296
4483
|
)
|
|
4484
|
+
resolved_external_progress = continuation_policy == "when_external_progress"
|
|
3297
4485
|
self.quest_service.update_runtime_state(
|
|
3298
4486
|
quest_root=self.quest_service._quest_root(quest_id),
|
|
3299
4487
|
continuation_policy=continuation_policy,
|
|
@@ -3311,12 +4499,22 @@ class DaemonApp:
|
|
|
3311
4499
|
if int(snapshot.get("pending_user_message_count") or 0) > 0:
|
|
3312
4500
|
self.schedule_turn(quest_id, reason="queued_user_messages")
|
|
3313
4501
|
return
|
|
4502
|
+
snapshot, auto_resumed_wait = self._auto_resume_wait_if_allowed(quest_id, snapshot)
|
|
4503
|
+
if auto_resumed_wait:
|
|
4504
|
+
self._schedule_turn_later(
|
|
4505
|
+
quest_id,
|
|
4506
|
+
reason="auto_continue",
|
|
4507
|
+
delay_seconds=self._auto_continue_delay_for_policy("auto"),
|
|
4508
|
+
)
|
|
4509
|
+
return
|
|
3314
4510
|
continuation_policy = str(snapshot.get("continuation_policy") or "auto").strip().lower() or "auto"
|
|
4511
|
+
resolved_external_progress = False
|
|
3315
4512
|
if continuation_policy == "auto":
|
|
3316
4513
|
continuation_policy, continuation_reason = self._resolve_continuation_policy(
|
|
3317
4514
|
snapshot,
|
|
3318
4515
|
current_policy=continuation_policy,
|
|
3319
4516
|
)
|
|
4517
|
+
resolved_external_progress = continuation_policy == "when_external_progress"
|
|
3320
4518
|
self.quest_service.update_runtime_state(
|
|
3321
4519
|
quest_root=self.quest_service._quest_root(quest_id),
|
|
3322
4520
|
continuation_policy=continuation_policy,
|
|
@@ -3329,7 +4527,7 @@ class DaemonApp:
|
|
|
3329
4527
|
if continuation_policy == "wait_for_user_or_resume":
|
|
3330
4528
|
return
|
|
3331
4529
|
if continuation_policy == "when_external_progress":
|
|
3332
|
-
if not self._has_external_progress(snapshot):
|
|
4530
|
+
if not resolved_external_progress and not self._has_external_progress(snapshot):
|
|
3333
4531
|
next_policy = "wait_for_user_or_resume" if self._workspace_mode_for(snapshot) == "copilot" else "auto"
|
|
3334
4532
|
next_reason = "external_progress_finished" if next_policy == "wait_for_user_or_resume" else "external_progress_finished_continue_autonomous"
|
|
3335
4533
|
self.quest_service.update_runtime_state(
|
|
@@ -3361,11 +4559,13 @@ class DaemonApp:
|
|
|
3361
4559
|
if status in {"completed", "paused", "stopped", "error", "waiting_for_user"}:
|
|
3362
4560
|
return
|
|
3363
4561
|
continuation_policy = str(snapshot.get("continuation_policy") or "auto").strip().lower() or "auto"
|
|
4562
|
+
resolved_external_progress = False
|
|
3364
4563
|
if continuation_policy == "auto":
|
|
3365
4564
|
continuation_policy, continuation_reason = self._resolve_continuation_policy(
|
|
3366
4565
|
snapshot,
|
|
3367
4566
|
current_policy=continuation_policy,
|
|
3368
4567
|
)
|
|
4568
|
+
resolved_external_progress = continuation_policy == "when_external_progress"
|
|
3369
4569
|
self.quest_service.update_runtime_state(
|
|
3370
4570
|
quest_root=self.quest_service._quest_root(quest_id),
|
|
3371
4571
|
continuation_policy=continuation_policy,
|
|
@@ -3373,10 +4573,15 @@ class DaemonApp:
|
|
|
3373
4573
|
continuation_updated_at=utc_now(),
|
|
3374
4574
|
)
|
|
3375
4575
|
snapshot = self.quest_service.snapshot(quest_id)
|
|
4576
|
+
snapshot, auto_resumed_wait = self._auto_resume_wait_if_allowed(quest_id, snapshot)
|
|
4577
|
+
if auto_resumed_wait:
|
|
4578
|
+
self.schedule_turn(quest_id, reason=reason)
|
|
4579
|
+
return
|
|
4580
|
+
continuation_policy = str(snapshot.get("continuation_policy") or "auto").strip().lower() or "auto"
|
|
3376
4581
|
if continuation_policy in {"none", "wait_for_user_or_resume"}:
|
|
3377
4582
|
return
|
|
3378
4583
|
if continuation_policy == "when_external_progress":
|
|
3379
|
-
if not self._has_external_progress(snapshot):
|
|
4584
|
+
if not resolved_external_progress and not self._has_external_progress(snapshot):
|
|
3380
4585
|
next_policy = "wait_for_user_or_resume" if self._workspace_mode_for(snapshot) == "copilot" else "auto"
|
|
3381
4586
|
next_reason = "external_progress_finished" if next_policy == "wait_for_user_or_resume" else "external_progress_finished_continue_autonomous"
|
|
3382
4587
|
self.quest_service.update_runtime_state(
|
|
@@ -3400,16 +4605,18 @@ class DaemonApp:
|
|
|
3400
4605
|
quest_id = str(snapshot.get("quest_id") or "").strip()
|
|
3401
4606
|
if not quest_id:
|
|
3402
4607
|
return False
|
|
4608
|
+
counts = snapshot.get("counts") if isinstance(snapshot.get("counts"), dict) else {}
|
|
3403
4609
|
try:
|
|
3404
4610
|
quest_root = self.quest_service._quest_root(quest_id)
|
|
3405
4611
|
except FileNotFoundError:
|
|
3406
|
-
return
|
|
4612
|
+
return int(counts.get("bash_running_count") or 0) > 0
|
|
3407
4613
|
try:
|
|
3408
4614
|
sessions = self.bash_exec_service.list_sessions(quest_root, limit=200)
|
|
3409
|
-
|
|
4615
|
+
if any(str(item.get("status") or "").strip().lower() == "running" for item in sessions if isinstance(item, dict)):
|
|
4616
|
+
return True
|
|
3410
4617
|
except Exception:
|
|
3411
|
-
|
|
3412
|
-
|
|
4618
|
+
pass
|
|
4619
|
+
return int(counts.get("bash_running_count") or 0) > 0
|
|
3413
4620
|
|
|
3414
4621
|
def _relay_quest_message_to_bound_connectors(
|
|
3415
4622
|
self,
|
|
@@ -3467,6 +4674,7 @@ class DaemonApp:
|
|
|
3467
4674
|
self._shutdown_requested.set()
|
|
3468
4675
|
self._stop_background_connectors()
|
|
3469
4676
|
self._stop_terminal_attach_server()
|
|
4677
|
+
self.admin_service.system_monitor.stop()
|
|
3470
4678
|
self.bash_exec_service.shutdown()
|
|
3471
4679
|
self.logger.log(
|
|
3472
4680
|
"info",
|
|
@@ -4215,10 +5423,21 @@ class DaemonApp:
|
|
|
4215
5423
|
removed_conversations = self._unbind_external_bindings(quest_id)
|
|
4216
5424
|
self.sessions.forget(quest_id)
|
|
4217
5425
|
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
5426
|
+
last_delete_error: OSError | None = None
|
|
5427
|
+
for attempt in range(5):
|
|
5428
|
+
try:
|
|
5429
|
+
shutil.rmtree(quest_root)
|
|
5430
|
+
last_delete_error = None
|
|
5431
|
+
break
|
|
5432
|
+
except FileNotFoundError:
|
|
5433
|
+
return {"ok": True, "quest_id": quest_id, "deleted": False}
|
|
5434
|
+
except OSError as exc:
|
|
5435
|
+
last_delete_error = exc
|
|
5436
|
+
if exc.errno not in {errno.ENOTEMPTY, errno.EBUSY} or attempt == 4:
|
|
5437
|
+
raise
|
|
5438
|
+
time.sleep(0.05 * (attempt + 1))
|
|
5439
|
+
if last_delete_error is not None and quest_root.exists():
|
|
5440
|
+
raise last_delete_error
|
|
4222
5441
|
|
|
4223
5442
|
self.logger.log(
|
|
4224
5443
|
"info",
|
|
@@ -4333,11 +5552,17 @@ class DaemonApp:
|
|
|
4333
5552
|
exclude_conversation_id=conversation_id,
|
|
4334
5553
|
)
|
|
4335
5554
|
self.update_quest_binding(created["quest_id"], binding_conversation_id, force=True)
|
|
4336
|
-
self.submit_user_message(
|
|
5555
|
+
startup = self.submit_user_message(
|
|
4337
5556
|
created["quest_id"],
|
|
4338
5557
|
text=goal_text,
|
|
4339
5558
|
source=conversation_id,
|
|
4340
5559
|
)
|
|
5560
|
+
created_snapshot = self.quest_service.snapshot(created["quest_id"])
|
|
5561
|
+
startup_message = self._connector_turn_start_message(
|
|
5562
|
+
quest_id=created["quest_id"],
|
|
5563
|
+
startup=startup,
|
|
5564
|
+
snapshot=created_snapshot,
|
|
5565
|
+
)
|
|
4341
5566
|
return channel.send(
|
|
4342
5567
|
{
|
|
4343
5568
|
"conversation_id": conversation_id,
|
|
@@ -4350,7 +5575,9 @@ class DaemonApp:
|
|
|
4350
5575
|
quest_id=created["quest_id"],
|
|
4351
5576
|
goal=goal_text,
|
|
4352
5577
|
previous_quest_id=previous_quest_id,
|
|
4353
|
-
)
|
|
5578
|
+
)
|
|
5579
|
+
+ "\n"
|
|
5580
|
+
+ startup_message,
|
|
4354
5581
|
),
|
|
4355
5582
|
}
|
|
4356
5583
|
)
|
|
@@ -4759,7 +5986,7 @@ class DaemonApp:
|
|
|
4759
5986
|
message_id=str(message.get("message_id") or "").strip() or None,
|
|
4760
5987
|
attachments=[dict(item) for item in (message.get("attachments") or []) if isinstance(item, dict)],
|
|
4761
5988
|
)
|
|
4762
|
-
self.submit_user_message(
|
|
5989
|
+
startup = self.submit_user_message(
|
|
4763
5990
|
quest_id,
|
|
4764
5991
|
text=self._connector_message_text_with_attachment_notice(
|
|
4765
5992
|
original_text=text,
|
|
@@ -4768,18 +5995,18 @@ class DaemonApp:
|
|
|
4768
5995
|
source=conversation_id,
|
|
4769
5996
|
attachments=materialized_attachments,
|
|
4770
5997
|
)
|
|
5998
|
+
snapshot = self.quest_service.snapshot(quest_id)
|
|
5999
|
+
startup_message = self._connector_turn_start_message(
|
|
6000
|
+
quest_id=quest_id,
|
|
6001
|
+
startup=startup,
|
|
6002
|
+
snapshot=snapshot,
|
|
6003
|
+
)
|
|
4771
6004
|
return channel.send(
|
|
4772
6005
|
{
|
|
4773
6006
|
"conversation_id": conversation_id,
|
|
4774
6007
|
"quest_id": quest_id,
|
|
4775
6008
|
"kind": "ack",
|
|
4776
|
-
"message": self._with_qq_main_chat_notice(
|
|
4777
|
-
message,
|
|
4778
|
-
self._polite_copy(
|
|
4779
|
-
zh=f"老师,已收到您的消息,当前会话已绑定到 {quest_id}。我会继续推进,并通过后续消息同步进展。",
|
|
4780
|
-
en=f"Received. This conversation is bound to {quest_id}; I’ll continue the work and keep you updated with the next progress checkpoint.",
|
|
4781
|
-
),
|
|
4782
|
-
),
|
|
6009
|
+
"message": self._with_qq_main_chat_notice(message, startup_message),
|
|
4783
6010
|
}
|
|
4784
6011
|
)
|
|
4785
6012
|
|
|
@@ -5772,6 +6999,78 @@ class DaemonApp:
|
|
|
5772
6999
|
en="I will clarify the current task first, then keep moving. ✨",
|
|
5773
7000
|
)
|
|
5774
7001
|
|
|
7002
|
+
def _connector_runner_label(self, runner_name: str) -> str:
|
|
7003
|
+
normalized = str(runner_name or "codex").strip().lower() or "codex"
|
|
7004
|
+
if normalized == "claude":
|
|
7005
|
+
return "Claude Code"
|
|
7006
|
+
if normalized == "opencode":
|
|
7007
|
+
return "Open Code"
|
|
7008
|
+
try:
|
|
7009
|
+
metadata = get_runner_metadata(normalized)
|
|
7010
|
+
except Exception:
|
|
7011
|
+
return runner_name or "Codex"
|
|
7012
|
+
return str(metadata.label or runner_name or "Codex")
|
|
7013
|
+
|
|
7014
|
+
def _connector_runner_readiness(self, runner_name: str) -> dict[str, Any]:
|
|
7015
|
+
try:
|
|
7016
|
+
state = self.config_manager.runner_bootstrap_state(runner_name)
|
|
7017
|
+
except Exception:
|
|
7018
|
+
return {"runner": runner_name, "ready": True, "last_result": {}}
|
|
7019
|
+
if not isinstance(state, dict):
|
|
7020
|
+
return {"runner": runner_name, "ready": True, "last_result": {}}
|
|
7021
|
+
return state
|
|
7022
|
+
|
|
7023
|
+
def _connector_turn_start_message(
|
|
7024
|
+
self,
|
|
7025
|
+
*,
|
|
7026
|
+
quest_id: str,
|
|
7027
|
+
startup: dict[str, Any],
|
|
7028
|
+
snapshot: dict[str, Any],
|
|
7029
|
+
) -> str:
|
|
7030
|
+
runner_name = self._runner_name_for(snapshot)
|
|
7031
|
+
runner_label = self._connector_runner_label(runner_name)
|
|
7032
|
+
readiness = self._connector_runner_readiness(runner_name)
|
|
7033
|
+
readiness_result = readiness.get("last_result") if isinstance(readiness.get("last_result"), dict) else {}
|
|
7034
|
+
readiness_summary = str(readiness_result.get("summary") or "").strip()
|
|
7035
|
+
readiness_errors = [str(item).strip() for item in (readiness_result.get("errors") or []) if str(item).strip()]
|
|
7036
|
+
readiness_guidance = [str(item).strip() for item in (readiness_result.get("guidance") or []) if str(item).strip()]
|
|
7037
|
+
ready = bool(readiness.get("ready", True))
|
|
7038
|
+
reason = str(startup.get("reason") or "").strip().lower()
|
|
7039
|
+
started = bool(startup.get("started"))
|
|
7040
|
+
queued = bool(startup.get("queued"))
|
|
7041
|
+
auto_resumed = bool(startup.get("auto_resumed"))
|
|
7042
|
+
has_explicit_runner_failure = bool(readiness_summary or readiness_errors or readiness_guidance)
|
|
7043
|
+
if reason == "stalled_turn_recovery_pending":
|
|
7044
|
+
return self._polite_copy(
|
|
7045
|
+
zh=f"正在启动 DeepScientist,当前会先恢复 `{quest_id}` 里停滞的运行,再继续处理您的新消息。",
|
|
7046
|
+
en=f"DeepScientist is starting up. I’m recovering the stalled run in `{quest_id}` first, then I’ll continue with your new message.",
|
|
7047
|
+
)
|
|
7048
|
+
if not ready and has_explicit_runner_failure:
|
|
7049
|
+
status_line = f" 当前状态:{readiness_summary}" if readiness_summary else ""
|
|
7050
|
+
return self._polite_copy(
|
|
7051
|
+
zh=(
|
|
7052
|
+
f"DeepScientist 仍然不在线哟,请检查你所绑定的{runner_label}是否能够顺利连接。{status_line}"
|
|
7053
|
+
),
|
|
7054
|
+
en=(
|
|
7055
|
+
f"DeepScientist is still offline. Please check whether the bound {runner_label} can connect normally."
|
|
7056
|
+
f"{(' Current status: ' + readiness_summary) if readiness_summary else ''}"
|
|
7057
|
+
),
|
|
7058
|
+
)
|
|
7059
|
+
if started or auto_resumed:
|
|
7060
|
+
return self._polite_copy(
|
|
7061
|
+
zh=f"已经成功收到消息,DeepScientist 已经启动并开始处理 `{quest_id}` 啦。",
|
|
7062
|
+
en=f"Message received successfully. DeepScientist has started and is now processing `{quest_id}`.",
|
|
7063
|
+
)
|
|
7064
|
+
if queued:
|
|
7065
|
+
return self._polite_copy(
|
|
7066
|
+
zh=f"已经成功收到消息,`{quest_id}` 当前正在运行中,这条消息也已经排进队列了。",
|
|
7067
|
+
en=f"Message received successfully. `{quest_id}` is already running, and this message has been queued.",
|
|
7068
|
+
)
|
|
7069
|
+
return self._polite_copy(
|
|
7070
|
+
zh=f"已经成功收到消息,我会继续推进 `{quest_id}` 并同步后续进展。",
|
|
7071
|
+
en=f"Message received successfully. I’ll continue `{quest_id}` and keep you updated.",
|
|
7072
|
+
)
|
|
7073
|
+
|
|
5775
7074
|
def _quest_created_connector_message(
|
|
5776
7075
|
self,
|
|
5777
7076
|
connector_name: str,
|
|
@@ -6828,6 +8127,10 @@ class DaemonApp:
|
|
|
6828
8127
|
"event_id": event.get("event_id") or f"evt-{quest_id}-{current_cursor}",
|
|
6829
8128
|
**event,
|
|
6830
8129
|
}
|
|
8130
|
+
enriched_event = self.quest_service.enrich_conversation_message_event(
|
|
8131
|
+
quest_id,
|
|
8132
|
+
enriched_event,
|
|
8133
|
+
)
|
|
6831
8134
|
update = build_session_update(
|
|
6832
8135
|
enriched_event,
|
|
6833
8136
|
quest_id=quest_id,
|
|
@@ -6908,6 +8211,75 @@ class DaemonApp:
|
|
|
6908
8211
|
except (BrokenPipeError, ConnectionResetError, TimeoutError):
|
|
6909
8212
|
return
|
|
6910
8213
|
|
|
8214
|
+
def stream_admin_task(
|
|
8215
|
+
self,
|
|
8216
|
+
handler: BaseHTTPRequestHandler,
|
|
8217
|
+
*,
|
|
8218
|
+
task_id: str,
|
|
8219
|
+
headers: dict[str, str] | None = None,
|
|
8220
|
+
extra_headers: dict[str, str] | None = None,
|
|
8221
|
+
) -> None:
|
|
8222
|
+
try:
|
|
8223
|
+
task = self.admin_task_service.get_task(task_id)
|
|
8224
|
+
except FileNotFoundError:
|
|
8225
|
+
self._write_handler_response(
|
|
8226
|
+
handler,
|
|
8227
|
+
code=404,
|
|
8228
|
+
content=json.dumps(
|
|
8229
|
+
{"ok": False, "message": f"Unknown admin task `{task_id}`."},
|
|
8230
|
+
ensure_ascii=False,
|
|
8231
|
+
).encode("utf-8"),
|
|
8232
|
+
content_type="application/json; charset=utf-8",
|
|
8233
|
+
extra_headers=extra_headers,
|
|
8234
|
+
)
|
|
8235
|
+
return
|
|
8236
|
+
|
|
8237
|
+
last_event_raw = str((headers or {}).get("Last-Event-ID") or (headers or {}).get("last-event-id") or "").strip()
|
|
8238
|
+
cursor = int(last_event_raw) if last_event_raw.isdigit() else 0
|
|
8239
|
+
|
|
8240
|
+
handler.send_response(200)
|
|
8241
|
+
handler.send_header("Content-Type", "text/event-stream; charset=utf-8")
|
|
8242
|
+
handler.send_header("Cache-Control", "no-cache, no-transform")
|
|
8243
|
+
handler.send_header("Connection", "keep-alive")
|
|
8244
|
+
handler.send_header("X-Accel-Buffering", "no")
|
|
8245
|
+
for key, value in (extra_headers or {}).items():
|
|
8246
|
+
handler.send_header(key, value)
|
|
8247
|
+
handler.end_headers()
|
|
8248
|
+
handler.wfile.write(b"retry: 1000\n\n")
|
|
8249
|
+
handler.wfile.flush()
|
|
8250
|
+
|
|
8251
|
+
self._write_sse_event(
|
|
8252
|
+
handler,
|
|
8253
|
+
event="task.snapshot",
|
|
8254
|
+
data={"task": task},
|
|
8255
|
+
)
|
|
8256
|
+
heartbeat_at = time.monotonic()
|
|
8257
|
+
terminal_statuses = {"completed", "failed", "cancelled"}
|
|
8258
|
+
try:
|
|
8259
|
+
while True:
|
|
8260
|
+
current_task = self.admin_task_service.get_task(task_id)
|
|
8261
|
+
events = self.admin_task_service.read_events(task_id, after=cursor, limit=200)
|
|
8262
|
+
if events:
|
|
8263
|
+
for item in events:
|
|
8264
|
+
cursor = max(cursor, int(item.get("seq") or 0))
|
|
8265
|
+
self._write_sse_event(
|
|
8266
|
+
handler,
|
|
8267
|
+
event=str(item.get("event") or "task.log"),
|
|
8268
|
+
data=item,
|
|
8269
|
+
event_id=str(cursor),
|
|
8270
|
+
)
|
|
8271
|
+
heartbeat_at = time.monotonic()
|
|
8272
|
+
elif time.monotonic() - heartbeat_at >= 10:
|
|
8273
|
+
handler.wfile.write(b": keep-alive\n\n")
|
|
8274
|
+
handler.wfile.flush()
|
|
8275
|
+
heartbeat_at = time.monotonic()
|
|
8276
|
+
current_status = str(current_task.get("status") or "").strip().lower()
|
|
8277
|
+
if current_status in terminal_statuses and cursor >= int(current_task.get("last_event_seq") or 0):
|
|
8278
|
+
return
|
|
8279
|
+
time.sleep(0.2 if current_status == "running" else 0.6)
|
|
8280
|
+
except (BrokenPipeError, ConnectionResetError, TimeoutError):
|
|
8281
|
+
return
|
|
8282
|
+
|
|
6911
8283
|
def stream_bash_sessions(
|
|
6912
8284
|
self,
|
|
6913
8285
|
handler: BaseHTTPRequestHandler,
|
|
@@ -7227,6 +8599,13 @@ class DaemonApp:
|
|
|
7227
8599
|
app = self
|
|
7228
8600
|
|
|
7229
8601
|
class RequestHandler(BaseHTTPRequestHandler):
|
|
8602
|
+
def handle(self) -> None:
|
|
8603
|
+
try:
|
|
8604
|
+
super().handle()
|
|
8605
|
+
except (BrokenPipeError, ConnectionResetError, TimeoutError):
|
|
8606
|
+
self.close_connection = True
|
|
8607
|
+
return
|
|
8608
|
+
|
|
7230
8609
|
def log_message(self, format: str, *args) -> None:
|
|
7231
8610
|
return
|
|
7232
8611
|
|
|
@@ -7270,6 +8649,18 @@ class DaemonApp:
|
|
|
7270
8649
|
},
|
|
7271
8650
|
)
|
|
7272
8651
|
return
|
|
8652
|
+
if route_name in {"admin_task_stream", "system_task_stream"}:
|
|
8653
|
+
try:
|
|
8654
|
+
app.stream_admin_task(self, **params, headers=request_headers, extra_headers=auth_headers)
|
|
8655
|
+
except Exception as exc:
|
|
8656
|
+
app.logger.log(
|
|
8657
|
+
"error",
|
|
8658
|
+
"http.stream_admin_task_failed",
|
|
8659
|
+
path=self.path,
|
|
8660
|
+
error=str(exc),
|
|
8661
|
+
)
|
|
8662
|
+
self.close_connection = True
|
|
8663
|
+
return
|
|
7273
8664
|
if route_name == "quest_events" and app._wants_event_stream(self.path, request_headers):
|
|
7274
8665
|
try:
|
|
7275
8666
|
app.stream_quest_events(self, **params, path=self.path, headers=request_headers, extra_headers=auth_headers)
|
|
@@ -7341,6 +8732,7 @@ class DaemonApp:
|
|
|
7341
8732
|
|
|
7342
8733
|
try:
|
|
7343
8734
|
result = getattr(app.handlers, route_name)
|
|
8735
|
+
route_alias = route_name.removeprefix("admin_").removeprefix("system_")
|
|
7344
8736
|
if route_name == "asset":
|
|
7345
8737
|
status, headers, content = result(**params)
|
|
7346
8738
|
app._write_handler_response(
|
|
@@ -7350,7 +8742,17 @@ class DaemonApp:
|
|
|
7350
8742
|
extra_headers=app._merge_response_headers(headers, auth_headers),
|
|
7351
8743
|
)
|
|
7352
8744
|
return
|
|
7353
|
-
if
|
|
8745
|
+
if route_alias in {
|
|
8746
|
+
"quests",
|
|
8747
|
+
"runtime_sessions",
|
|
8748
|
+
"log_tail",
|
|
8749
|
+
"failures",
|
|
8750
|
+
"errors",
|
|
8751
|
+
"audit",
|
|
8752
|
+
"search",
|
|
8753
|
+
"repairs",
|
|
8754
|
+
"tasks",
|
|
8755
|
+
} or route_name in {
|
|
7354
8756
|
"quest_events",
|
|
7355
8757
|
"bash_sessions",
|
|
7356
8758
|
"bash_logs",
|
|
@@ -7373,9 +8775,28 @@ class DaemonApp:
|
|
|
7373
8775
|
"annotations_project",
|
|
7374
8776
|
}:
|
|
7375
8777
|
payload = result(**params, path=self.path)
|
|
8778
|
+
elif route_name in {
|
|
8779
|
+
"benchstore_entries",
|
|
8780
|
+
"benchstore_entry",
|
|
8781
|
+
"benchstore_entry_image",
|
|
8782
|
+
"benchstore_entry_setup_packet",
|
|
8783
|
+
}:
|
|
8784
|
+
payload = result(**params, path=self.path) if params else result(self.path)
|
|
7376
8785
|
elif method == "GET":
|
|
7377
8786
|
payload = result(**params) if params else result()
|
|
7378
|
-
elif
|
|
8787
|
+
elif route_alias in {
|
|
8788
|
+
"shutdown",
|
|
8789
|
+
"chart_query",
|
|
8790
|
+
"task_doctor_start",
|
|
8791
|
+
"task_system_update_check_start",
|
|
8792
|
+
"task_system_update_action_start",
|
|
8793
|
+
"issue_draft",
|
|
8794
|
+
"controller_run",
|
|
8795
|
+
"controller_toggle",
|
|
8796
|
+
"repair_create",
|
|
8797
|
+
"repair_close",
|
|
8798
|
+
"hardware_update",
|
|
8799
|
+
} or route_name in {"document_open", "document_asset_upload", "quest_file_create_folder", "quest_file_upload", "quest_file_rename", "quest_file_move", "quest_file_delete", "chat_upload_create", "chat_upload_delete", "chat", "command", "quest_control", "quest_message_read_now", "quest_message_withdraw", "config_save", "quest_create", "quest_baseline_binding", "run_create", "qq_inbound", "connector_inbound", "docs_open", "bash_stop", "quest_settings", "quest_bindings", "quest_delete", "quest_layout_update", "terminal_session_ensure", "terminal_attach", "terminal_input", "stage_view", "latex_init", "latex_compile", "system_update_action", "weixin_login_qr_start", "weixin_login_qr_wait", "arxiv_import", "annotation_create", "auth_login", "auth_rotate"}:
|
|
7379
8800
|
payload = result(**params, body=body)
|
|
7380
8801
|
elif route_name == "config_validate":
|
|
7381
8802
|
payload = result(body)
|
|
@@ -7385,6 +8806,8 @@ class DaemonApp:
|
|
|
7385
8806
|
payload = result(**params, body=body)
|
|
7386
8807
|
elif route_name == "memory":
|
|
7387
8808
|
payload = result(app.handlers.parse_query(self.path))
|
|
8809
|
+
elif route_name == "benchstore_entry_launch":
|
|
8810
|
+
payload = result(**params, path=self.path, body=body)
|
|
7388
8811
|
else:
|
|
7389
8812
|
payload = result(**params) if params else result()
|
|
7390
8813
|
except Exception as exc:
|
|
@@ -7432,6 +8855,8 @@ class DaemonApp:
|
|
|
7432
8855
|
self._serve_host = host
|
|
7433
8856
|
self._serve_port = port
|
|
7434
8857
|
self._shutdown_requested.clear()
|
|
8858
|
+
self.admin_service.system_monitor.start()
|
|
8859
|
+
self.admin_service.metrics_collector.start()
|
|
7435
8860
|
self._start_terminal_attach_server(host, port)
|
|
7436
8861
|
self._start_background_connectors()
|
|
7437
8862
|
self._resume_reconciled_quests()
|
|
@@ -7453,6 +8878,8 @@ class DaemonApp:
|
|
|
7453
8878
|
finally:
|
|
7454
8879
|
self._stop_background_connectors()
|
|
7455
8880
|
self._stop_terminal_attach_server()
|
|
8881
|
+
self.admin_service.metrics_collector.stop()
|
|
8882
|
+
self.admin_service.system_monitor.stop()
|
|
7456
8883
|
self.bash_exec_service.shutdown()
|
|
7457
8884
|
self._server = None
|
|
7458
8885
|
self._serve_host = None
|