# dsh-jev-ultrafast English | [中文](README-zh.md) | [Español](README-es.md) | [Português](README-pt.md) | **हिन्दी** > **एक लक्ष्य अंदर, हर चरण पर एक टूल कॉल।** DeepSeek Harness को एक प्राकृतिक-भाषा लक्ष्य दें, और **Jev** (TypeSafe) ब्राउज़र चलाए। > पेज को नियंत्रणों की एक क्रमांकित तालिका में बदला जाता है, और एक ही अनुरोध तय करता है > कि *कौन-सा ऑपरेशन* करना है और *किस तत्व* पर — इसलिए हर क्लिक पर एक मॉडल चक्र के बजाय > सत्र केवल एक टूल कॉल खर्च करता है। संक्षेप में इससे क्या मिलता है: - **हर चरण पर एक अनुरोध।** ऑपरेशन और उसका लक्ष्य साथ ही लौटते हैं, इसलिए मॉडल से तीन अलग चक्रों में देखना, सोचना और क्लिक करना नहीं माँगा जाता। - **लूप में कोई selector, निर्देशांक या कोड नहीं।** लक्ष्य उस तालिका के क्रमांक हैं जो प्लगइन जीवित पेज से बनाता है; और इनपुट से ठीक पहले ताज़गी, दृश्यता, ज्यामिति तथा अवरोध फिर जाँचे जाते हैं। - **अपना ब्राउज़र ख़ुद लेकर आता है।** जब कुछ भी पहुँच में न हो, तो चलना आपके चुने हुए Chrome या Edge को शुरू करके उससे जुड़ जाता है — अपनी डेटा डिरेक्टरी, अपना ख़ाली पोर्ट। - **वह उस टैब के पीछे चलता है जो क्लिक खोलता है**, और केवल उसी टैब को बंद करता है जो उसने ख़ुद खोला। - **`blocked` और `failed` एक बात नहीं।** ब्रेक लगने पर चलना `blocked` पर रुकता है और बताता है कि पेज पर अब भी क्या-क्या चलाया जा सकता था, इसलिए अटकने की जगह दिख जाती है। - **लंबे पेज भी पढ़ता है।** दूसरा टूल पेज को एक-एक स्क्रीन करके स्क्रॉल करता है और टेक्स्ट दोबारा जोड़ देता है, इसलिए एक स्क्रीन से लंबा दस्तावेज़ भी पूरा वापस आता है — और कोई निर्णय अनुरोध खर्च नहीं होता। - **`done` पर भरोसा नहीं, जाँच होती है।** पहले लिख दीजिए कि तैयार पेज पर क्या दिखना चाहिए, और प्लगइन उसे खोजने जाता है; बिना उसके «पूरा हो गया» कहने वाला चलना `blocked` लौटता है। - **हर चलन एक कच्चा रिकॉर्ड छोड़ता है।** हर चलन अपनी एक अस्थायी डिरेक्टरी में `trace.jsonl` लिखता है — हर निर्णय कॉल और हर टेक्स्ट-मॉडल कॉल का अनुरोध-निकाय तथा प्रतिक्रिया, कुंजी `***` कर दी जाती है और 20,000 अक्षरों से आगे का हिस्सा काट दिया जाता है — साथ ही, स्क्रीनशॉट चालू हों तो हर चरण पर एक `frames/NNNNNN.jpg` और एक `frames.json` जो हर फ़्रेम का नाम और समय दर्ज करता है। नतीजा उस डिरेक्टरी का नाम बताता है। - **गिरा हुआ टेक्स्ट-मॉडल कॉल दोबारा भेजा जाता है, टूटा हुआ कनेक्शन नहीं।** टेक्स्ट मॉडल से 429, 503 या 529 आने पर दो बार तक दोबारा कोशिश होती है, पहले 0.5 सेकंड और फिर 1 सेकंड रुककर; नेटवर्क टूटने की सूचना जैसी है वैसी दी जाती है, और ऊपरी Python संस्करण भी वहीं यही रेखा खींचता है। - **चलन को देखा भी जा सकता है, केवल पढ़ा नहीं।** host एक इंस्पेक्टर पेज परोसता है (नीचे «चलते चलन को देखना» देखें): हाथ से चलन शुरू कीजिए, जीवित स्क्रीन देखिए, हर चरण किस तत्व को चुनने वाला है और मॉडल कितना निश्चित है यह देखिए, और क्रिया निष्पादित होने से **पहले** उसे रोकिए, चरण-दर-चरण बढ़ाइए या बंद कीजिए — या पूरे हो चुके चलन को फ़्रेम-दर-फ़्रेम दोबारा देखिए। - **इसे अक्सर मॉडल चक्र के बिना भी शुरू किया जा सकता है।** कंपोज़र में सीधे `/jev-ultrafast` लिखिए और उसके बाद अपनी बात कहिए — URL ज़रूरी नहीं, वह वाक्य में कहीं भी हो सकता है और अकेला डोमेन भी चलता है। URL हो तो चलन तुरंत शुरू हो जाता है और कोई मॉडल कॉल नहीं होती; न हो तो सेटिंग्स पेज का टेक्स्ट मॉडल एक छोटी कॉल में पहला पता बता देता है, और वह भी न बता पाए तो जवाब में URL लिखने को कहा जाता है। कमांड पंक्ति और उसका नतीजा इंटरफ़ेस में रहते हैं; अकेला लिखने पर कमांड केवल अपनी व्याख्या और इंस्पेक्टर का पता देता है। यह [jev-ultrafast](https://github.com/browser-use/jev-ultrafast) (MIT, © 2026 Browser Use) का स्वतंत्र, अनौपचारिक TypeScript पोर्ट है, जो DeepSeek Harness bundle के रूप में पैक किया गया है। ऊपरी परियोजना Python में है जिसमें थोड़ा JavaScript भी है; यहाँ का लूप, पेज-स्नैपशॉट स्क्रिप्ट और प्रॉम्प्ट उसी से दोबारा लिखे गए हैं। > **अनौपचारिक।** यह परियोजना Browser Use या TypeSafe से संबद्ध, समर्थित या प्रायोजित > नहीं है। «Browser Use», «TypeSafe» और «Jev» अपने-अपने स्वामियों के ट्रेडमार्क हैं, > जो यहाँ केवल स्रोत और इस प्लगइन द्वारा कॉल की जाने वाली API बताने के लिए हैं। > कोई ट्रेडमार्क लाइसेंस नहीं दिया जाता। > > **कुंजी आपकी।** प्लगइन API पहुँच को शामिल, बंडल, प्रॉक्सी या पुनर्विक्रय नहीं करता। > यह कॉल के समय DeepSeek Harness के क्रेडेंशियल स्टोर से आपकी TypeSafe कुंजी लेता है, > और उस सेवा का उपयोग उसकी अपनी शर्तों के अधीन है। ## Compatibility | सतह | स्थिति | |---|---| | Harness | DeepSeek Harness `0.1.7-rc.2` और `0.2.0-rc.1` (दोनों यहाँ चलते हैं); प्लगइन `peerDependencies` में `>=0.1.7-rc.2 <0.3.0-0` घोषित करता है, इसलिए इस दायरे से बाहर का संस्करण लोड होने से पहले harness का compatibility gate अस्वीकार कर देता है, और कारण भी छाप देता है | | Node | `^22.19.0 || >=24.0.0` | | प्लेटफ़ॉर्म | Windows, macOS, Linux | | डेस्कटॉप ऐप | Electron डेस्कटॉप ऐप में चलता है; उसका profile ऐप ख़ुद संभालता है, इसलिए नीचे Install के अंतर्गत डेस्कटॉप वाला अनुच्छेद देखें | | ब्राउज़र | Chrome या Edge: सेटिंग्स पेज पर एक चुनकर 「启动并连接」 दबाइए और प्लगइन उसे आपके लिए शुरू कर देता है (अपनी profile डिरेक्टरी, अपना ख़ाली पोर्ट)। आप ख़ुद भी `--remote-debugging-port` के साथ एक शुरू कर सकते हैं और प्लगइन उसे खोज लेगा — और जब कुछ भी पहुँच में न हो, तो कोई काम वह ब्राउज़र ख़ुद शुरू कर देता है, इसलिए बटन पहले से ज़रूरी नहीं | | क्रेडेंशियल | `TYPESAFE_API_KEY`; साथ ही, किसी फ़ील्ड में लिखना पड़े तो टेक्स्ट मॉडल के लिए एक कुंजी — किसी प्रीसेट में वह डिफ़ॉल्ट नाम वही है जो विक्रेता की परंपरा रखती है (DeepSeek के मामले में `DEEPSEEK_API_KEY`), और DSH के अंतर्निहित रास्तों को कोई कुंजी नहीं चाहिए | ## What it does प्लगइन दो टूल पंजीकृत करता है। **`jev_browser_task`** एक पेज को एक लक्ष्य की ओर चलाता है और पूरा लूप एक ही कॉल के भीतर चलाता है। यह चार तर्क लेता है: | तर्क | ज़रूरी | अर्थ | |---|---|---| | `goal` | हाँ | पूरा काम एक वाक्य में, जिसमें लिखने वाला हर मान और लगाने वाला हर फ़िल्टर शामिल हो। लूप केवल यही वाक्य और वर्तमान पेज देखता है, आपकी बातचीत कभी नहीं। | | `url` | हाँ | सबसे पहले खोलने वाला पेज। क्रिया-क्षेत्र में «पते पर जाओ» जैसी कोई क्रिया नहीं है, इसलिए प्रवेश-बिंदु केवल यहीं से आ सकता है। | | `maxSteps` | नहीं | केवल इसी चलन के लिए चरण-बजट बदलता है, कॉन्फ़िगरेशन को छुए बिना। | | `expect` | नहीं | वे पाठ-खंड जो तैयार पेज पर दिखने चाहिए — चलन से पहले लिखे जाते हैं और बाद में प्लगइन जाँचता है। किसी एक के आगे `!` लगाइए तो वह दिखना **नहीं** चाहिए। यही चीज़ `done` को उस मॉडल का आख़िरी शब्द बनने से रोकती है जिसने काम किया। | यह `status` (`done` / `blocked` / `failed`), `reason`, `verification`, `url`, `title`, `text`, `steps`, `decisions`, `elapsedMs`, `actions`, `elements`, `omittedActions` और `textCalls` लौटाता है — साथ ही चलन की अपनी trace डिरेक्टरी का पथ। जब `status` `blocked` या `failed` हो, तो `elements` बताता है कि पेज पर अब भी क्या चलाया जा सकता था, इसलिए साफ़ रहता है कि चलन कहाँ अटका। `omittedActions` गिनता है कि पेज ने तालिका में समाने वाले 250 नियंत्रणों से आगे कितने और दिए थे; चरण-सूची उस चरण पर निशान लगाती है जिसके बारे में मॉडल ख़ुद अनिश्चित था (आधी से कम प्रायिकता); और स्क्रीनशॉट चालू हों तो यह अंतिम स्क्रीन की तस्वीर का पूरा पथ भी लौटाता है, जो सिस्टम की अस्थायी डिरेक्टरी में रहता है। **`jev_browser_read`** पेज पर क्रिया करने के बजाय उसे पढ़ता है। यह `url` लेता है, और वैकल्पिक रूप से `maxScreens` (डिफ़ॉल्ट 20) तथा `maxChars` (डिफ़ॉल्ट 60000)। यह दिखने वाला टेक्स्ट एक-एक स्क्रीन में इकट्ठा करता है, लगातार स्क्रीनों में दोहराई गई पंक्तियाँ हटाता है और पूरा पाठ लौटाता है, इसलिए एक स्क्रीन से लंबा दस्तावेज़ भी पूरा वापस आता है। यह किसी निर्णय मॉडल को नहीं बुलाता और कुछ क्लिक नहीं करता: यह सस्ता रास्ता है, और पढ़ना ही इसका एकमात्र काम है। यह चार कारणों में से एक पर रुकता है — पेज ख़त्म हो गया, स्क्रीन-बजट ख़त्म हो गया, अक्षर-बजट ख़त्म हो गया, या पेज ने स्क्रॉल करना बंद कर दिया — और बताता है कि कौन-सा कारण था; जो स्क्रीन एक ही स्क्रीन की 6000-अक्षर सीमा को ठीक-ठीक भर दे, वह गिनी जाती है और «कट सकती है» का निशान लगाती है। नई खुली टैब पहला स्क्रॉल इवेंट निगल जाती है (यह असली चलन में देखा गया), इसलिए जो स्क्रीन हिली नहीं उसे एक बार और धकेला जाता है। पढ़ने का अपना रास्ता क्यों ज़रूरी है: snapshot जान-बूझकर केवल दिखने वाले हिस्से का होता है। यह स्क्रीन से बाहर की हर पंक्ति हटा देता है और बचे हुए को 6000 अक्षरों पर रोक देता है, और वही टेक्स्ट *हर* निर्णय अनुरोध के साथ चलता है, इसलिए उसे चौड़ा करना हर चरण की क़ीमत बढ़ा देता। इसलिए काम वाला टूल एक स्क्रीन देखता है; पढ़ने वाला टूल पूरे पेज पर चलता है। **एक चलन पीछे क्या छोड़ता है।** हर चलन अपनी एक अस्थायी डिरेक्टरी में लिखता है: एक `trace.jsonl` जिसमें हर निर्णय कॉल और हर टेक्स्ट-मॉडल कॉल का अनुरोध-निकाय तथा प्रतिक्रिया रहती है, कुंजी `***` कर दी जाती है और 20,000 अक्षरों से आगे का हिस्सा काट दिया जाता है; साथ ही, स्क्रीनशॉट चालू हों तो हर चरण पर एक `frames/NNNNNN.jpg` और एक `frames.json` जो हर फ़्रेम का नाम और समय दर्ज करता है। नतीजा उस डिरेक्टरी की सूचना देता है, इसलिए बाद में वह कच्चा लेन-देन दोबारा पढ़ा जा सकता है। **चलते चलन को देखना।** host `http://127.0.0.1:3080/jev-ultrafast/inspector` पर एक इंटरैक्टिव इंस्पेक्टर भी परोसता है। वहाँ आप हाथ से चलन शुरू कर सकते हैं, वर्तमान स्क्रीन देख सकते हैं, यह देख सकते हैं कि हर चरण कौन-सा तत्व चुनने वाला है और मॉडल कितना निश्चित है, और क्रिया निष्पादित होने से **पहले** उसे रोक, चरण-दर-चरण बढ़ा या बंद कर सकते हैं। जो चलन पहले ही हो चुका है उसे दोबारा देखा जा सकता है: उसके फ़्रेम उसी रफ़्तार से चलते हैं जिस रफ़्तार से लिए गए थे, और हर अनुरोध की कच्ची JSON खोलकर देखी जा सकती है। यह पेज host का निकाला हुआ एक पूरा HTML फ़ाइल है, कोई client bundle नहीं, इसलिए इसे पाने के लिए कुछ भी दोबारा build नहीं करना पड़ता; केवल पेज ख़ुद बिना token का है, जबकि वह जिन-जिन endpoint को बुलाता है उन सब पर सेटिंग्स पेज जैसा ही token लगता है। भीतर का लूप चार चरणों का है: 1. **निरीक्षण** — इंजेक्ट की गई स्क्रिप्ट दिखने वाले नियंत्रणों को क्रमांकित तालिका में पढ़ती है (भूमिका, नाम, वर्तमान मान, चयनित स्थिति)। 2. **निर्णय** — एक TypeSafe अनुरोध ऑपरेशन (`CLICK`, `TYPE_TEXT`, `SELECT`, `SCROLL_UP`, `SCROLL_DOWN`, `WAIT`, `DONE`, `BLOCKED`) और उपलब्ध हर ऑपरेशन के लिए उम्मीदवार लक्ष्य लौटाता है; केवल चुने गए ऑपरेशन का लक्ष्य निष्पादित होता है। 3. **लेखन** — केवल जब ऑपरेशन `TYPE_TEXT` हो, एक छोटा OpenAI-संगत मॉडल फ़ील्ड का मान लिखता है। 4. **निष्पादन** — इनपुट से ठीक पहले ताज़गी, दृश्यता, ज्यामिति और अवरोध दोबारा जाँचे जाते हैं, और क्रिया उसके परिणाम देखने से *पहले* दर्ज होती है। मॉडल कभी भी selector, निर्देशांक या निष्पादन योग्य कोड नहीं देता: लक्ष्य प्लगइन द्वारा जारी तालिका के क्रमांक हैं। एक चलन पर तीन ब्रेक लगते हैं: क्रियाओं की गिनती `maxSteps` तक पहुँच जाए; निर्णय कॉल `2 × maxSteps` तक पहुँच जाएँ; या लगातार तीन चरण पेज को अपरिवर्तित छोड़ दें। इस तरह रुका चलन `blocked` पर ख़त्म होता है, असफलता पर नहीं — वह पहुँचा नहीं, टूटा नहीं। प्लगइन ब्राउज़र से अपना अलग कनेक्शन खोलता है और जान-बूझकर `ctx.browserUse` प्रोवाइडर स्लॉट का **इस्तेमाल नहीं** करता: उसे पेज को अपनी रफ़्तार से एक-एक चरण चलाना है, और उस स्लॉट का «पेज किसी प्रोवाइडर को सौंप दो» वाला करार इस काम का नहीं है। **अभी बाक़ी:** पैकेज npm पर नहीं है, इसलिए यह किसी स्थानीय tarball या डिरेक्टरी से इंस्टॉल होता है; टूल के नतीजे अभी सामान्य कार्ड इस्तेमाल करते हैं और उसके लिए कोई अपना समृद्ध कार्ड नहीं लिखा गया; कई टैब केवल उतनी ही संभाली जाती हैं कि क्लिक से खुले टैब के पीछे चला जाए (चलन पहले से खुली किसी टैब पर कभी नहीं जाता); फ़ाइल upload और drag-and-drop, तथा Shadow DOM, iframe या canvas के भीतर की कोई भी चीज़, ऊपरी परियोजना में भी और यहाँ भी, क्रिया-क्षेत्र में कभी नहीं रही। सबूत की दो सीमाएँ जानने लायक हैं। काम वाला टूल अंतिम पेज के अधिकतम 6000 अक्षर लौटाता है, और सफलता का चिह्न उसी अंतिम स्क्रीन पर खोजता है — बहुत लंबे पेज में नीचे कहीं रखा चिह्न वहाँ नहीं मिलता, इसीलिए असफल जाँच का अर्थ «पुष्टि नहीं हुई» है, «सच नहीं» नहीं। और आगे देखने का रास्ता पढ़ने वाला टूल है। ### या सीधे स्लैश कमांड लिखिए (URL हो तो कोई मॉडल चक्र नहीं) इनपुट बॉक्स में `/jev-ultrafast` लिखिए: - `/jev-ultrafast <अपनी बात>` — एक बार चलाता है; URL ज़रूरी नहीं, वह वाक्य में कहीं भी हो सकता है और `example.com` जैसा अकेला डोमेन भी चलता है। जैसे `/jev-ultrafast https://www.example.com क़ीमत खोजिए और बताइए कि कितनी है` या `/jev-ultrafast कल बीजिंग का मौसम देखिए और बताइए`। URL न दें तो सेटिंग्स पेज का टेक्स्ट मॉडल तय करता है कि पहले कौन-सी साइट खोलनी है — एक छोटी कॉल; वह भी न बता पाए तो जवाब में URL लिखने को कहा जाता है। कमांड तुरंत जवाब देता है; चलना पृष्ठभूमि में जारी रहता है (सत्र के कार्य पैनल में प्रगति देख सकते हैं और उसे रोक भी सकते हैं) और ख़त्म होने पर नतीजा लौटा देता है। - `/jev-ultrafast` अकेला — केवल यही व्याख्या और इंटरैक्टिव इंस्पेक्टर का पता लौटाता है। कमांड और उसका नतीजा इंटरफ़ेस में ही रहते हैं: वे कभी बातचीत का हिस्सा नहीं बनते। वाक्य में URL हो तो कोई मॉडल चक्र खर्च नहीं होता; न हो तो सेटिंग्स पेज का वही टेक्स्ट मॉडल एक छोटी कॉल में पहला पता तय करता है। नाम ASCII लोअरकेस ही हो सकता है (यह DSH का नियम है), इसीलिए यह `/jev-ultrafast` है, कोई चीनी नाम नहीं; और URL कोई ख़ास जगह नहीं माँगता — वह वाक्य में कहीं भी हो सकता है, या बिल्कुल न हो। हर चरण की तस्वीरें अब भी सेटिंग्स पेज के «हर चरण पर स्क्रीनशॉट» स्विच पर निर्भर हैं: वह बंद हो तो इस बार चलने का केवल कच्चा लेन-देन रिकॉर्ड बचता है। बिल्कुल नए सत्र में पहली बार स्क्रीन अब भी स्वागत पेज पर रह सकती है (लगे कि कुछ हुआ ही नहीं) — कोई और संदेश भेजिए, कमांड कार्ड बातचीत में मौजूद होगा। > **स्थिति।** संस्करण `0.1.0`, डेवलपर प्रीव्यू। प्लगइन अब लागू हो चुका है: यह दो टूल > पंजीकृत करता है — `jev_browser_task` (ऊपर का लूप वही है जो वास्तव में चलता है) और > `jev_browser_read`। npm पर अभी प्रकाशित नहीं है, इसलिए यह किसी स्थानीय tarball से > इंस्टॉल होता है (नीचे [Install](#install) देखें)। DeepSeek Harness ख़ुद तेज़ी से बदल रहा > है, इसलिए इस प्लगइन को उसके सामने दोबारा जाँचते रहना पड़ेगा। चरण योजना और > बाक़ी बचा काम [`tasks/todo.md`](tasks/todo.md) में है। ## Install ```sh pnpm pack dsh plugin --profile add ./dsh-jev-ultrafast-0.1.0.tgz dsh --profile --dump-config | grep 'dsh-jev-ultrafast' ``` **डेस्कटॉप ऐप में** profile ऐप खुद संभालता है और कमांड लाइन उसे अस्वीकार करती है (`profile "desktop" is managed exclusively by the Electron application`)। ऐप में **插件 → 添加插件** (प्लगइन → प्लगइन जोड़ें) खोलें, tarball का **पूरा पाथ** चिपकाएँ, 立即启用 (अभी सक्रिय करें) दबाएँ, और **ऐप को एक बार पुनः आरंभ करें**: boot payload हर बार ऐप शुरू होने पर केवल एक बार भेजा जाता है, इसलिए बिना पुनः आरंभ किए प्लगइन पेज को अपना token नहीं मिलता और वह चीनी में एक चेतावनी दिखाता है — टूल फिर भी काम करता रहता है। एक चलन से पहले दो चीज़ें मौजूद होनी चाहिए। पहली, ऐसा ब्राउज़र जिस तक प्लगइन पहुँच सके। सबसे छोटा रास्ता प्लगइन के अपने पेज से चुनना है: **सेटिंग्स → Jev 浏览器** खोलिए, ब्राउज़र वाले खंड के dropdown में Chrome या Edge चुनिए और 「启动并连接」 दबाइए। प्लगइन उसी ब्राउज़र की अपनी executable शुरू करता है, उसे **अपनी एक profile डिरेक्टरी** देता है (आपकी रोज़मर्रा की डिरेक्टरी से अलग — उसके भीतर एक बार लॉग इन कीजिए और वह वही हालत बनाए रखता है), पोर्ट ब्राउज़र को ख़ुद चुनने देता है, और वह पता उसी profile डिरेक्टरी में छोड़ देता है, इसलिए कोई पोर्ट नंबर लिखना नहीं पड़ता और DSH दोबारा शुरू होने पर भी वह मिल जाता है। Chrome और Edge ने संस्करण 136 से डिफ़ॉल्ट profile पर डिबगिंग पोर्ट देना बंद कर दिया है, इसीलिए «प्लगइन एक साफ़-सुथरी profile शुरू करता है» ही एक-बटन वाला एकमात्र रूप भी है। **वह बटन ज़रूरी नहीं, फिर भी**: जब कोई ब्राउज़र पहुँच में ही न हो, तो कोई काम चुने हुए ब्राउज़र को ख़ुद शुरू करके उससे जुड़ जाता है और अपने नतीजे में यह बताता है। बटन का दूसरा इस्तेमाल बना रहता है — जिस साइट पर लॉग इन चाहिए, वह लॉग इन उसी खिड़की में एक बार हाथ से कर लीजिए। आप ख़ुद भी एक अलग इंस्टेंस शुरू कर सकते हैं और बटन को छूने की ज़रूरत नहीं: ```sh # Chrome chrome --remote-debugging-port=9222 --user-data-dir=/tmp/jev-profile --no-first-run # Edge msedge --remote-debugging-port=9222 --user-data-dir=/tmp/jev-profile --no-first-run ``` Windows पर पाथ पूरा लिखिए: ```powershell & "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" ` --remote-debugging-port=9222 --user-data-dir="C:\Users\\jev-profile" ` --no-first-run --no-default-browser-check ``` `cdpUrl` ख़ाली छोड़ने पर प्लगइन इस क्रम में देखता है: कॉन्फ़िगर किया गया पता → `BU_CDP_URL` / `BU_CDP_WS` पर्यावरण चर → वह पोर्ट फ़ाइल जो ब्राउज़र ने ख़ुद लिखी (प्लगइन की शुरू की हुई दोनों profile डिरेक्टरियाँ समेत) → दो पारंपरिक पोर्ट 9222 और 9223। इनमें से कोई जवाब न दे, तो यह चीनी संदेश के साथ असफल होता है जो बताता है कि ब्राउज़र कैसे शुरू करें और उस बटन का ज़िक्र भी करता है। अगर आपका ब्राउज़र पहले से कहीं और सुन रहा है, तो `cdpUrl` सेट कीजिए। **दोनों ब्राउज़र चल रहे हों तो कोई काम वही चलाता है जिसे आपने dropdown में चुना** — वह दूसरे से आगे रहता है, इसके बजाय कि जो पहले शुरू हुआ वह जीत जाए। दूसरी, दोनों कुंजियाँ। सबसे छोटा रास्ता यह है कि उन्हें DSH की सेटिंग्स में प्लगइन के अपने पेज पर भर दें (अगला अनुभाग), जो उन्हें DSH की क्रेडेंशियल फ़ाइल में लिख देता है; इन्हें क्रेडेंशियल के रूप में भी रखा जा सकता है (निर्णय के लिए डिफ़ॉल्ट `TYPESAFE_API_KEY` है, और टेक्स्ट मॉडल के लिए चुने गए रास्ते का डिफ़ॉल्ट नाम) या DSH शुरू करने वाले पर्यावरण में export किया जा सकता है। इनमें से कोई एक न हो तो चीनी में त्रुटि आती है जो बताती है कि कौन-सी कुंजी नहीं है। कुंजियाँ कभी कॉन्फ़िगरेशन में नहीं लिखी जातीं — कॉन्फ़िगरेशन में **चर के नाम** रहते हैं। ## Configuration हर कुंजी `cordis.yml` में या प्लगइन के अपने सेटिंग्स पेज में बदली जा सकती है (अगला अनुभाग देखें); नाम सपाट हैं, कोई नेस्टिंग नहीं। डिफ़ॉल्ट कॉलम वही है जो बिना कुछ लिखे मिलता है। | कुंजी | प्रकार | डिफ़ॉल्ट | विवरण | |---|---|---|---| | `browserKind` | `chrome` \| `edge` | `chrome` | 「启动并连接」 किस ब्राउज़र को शुरू करे, और कुछ भी पहुँच में न हो तो कोई काम ख़ुद किसे शुरू करे। हर ब्राउज़र को अपनी profile डिरेक्टरी मिलती है, इसलिए यह केवल यह चुनता है कि कौन-सा। | | `browserPath` | string | ख़ाली | उस ब्राउज़र की executable कहाँ रहती है। केवल तब ज़रूरी जब वह आम जगहों से बाहर इंस्टॉल हो (जैसे portable प्रति)। | | `cdpUrl` | string | ख़ाली | ब्राउज़र डिबग पोर्ट, जैसे `http://127.0.0.1:9222`; ख़ाली = स्वतः खोज | | `userDataDir` | string | ख़ाली | ब्राउज़र डेटा डिरेक्टरी, केवल ग़ैर-डिफ़ॉल्ट डिरेक्टरी के लिए | | `decisionProvider` | `typesafe` \| `openrouter` | `typesafe` | निर्णय सेवा तक किस दरवाज़े से पहुँचा जाए: TypeSafe का अपना endpoint, या OpenRouter का alpha मार्ग; नीचे «दो रास्ते» देखें | | `decisionEndpoint` | string | ख़ाली | निर्णय सेवा का पूरा पता; ख़ाली = चुने गए प्रोवाइडर वाला, केवल पुनर्विक्रय वाले मार्ग के लिए भरें | | `decisionModel` | string | ख़ाली | निर्णय मॉडल का नाम; ख़ाली = चुने गए प्रोवाइडर वाला | | `decisionKeyRef` | string | ख़ाली | निर्णय कुंजी का *क्रेडेंशियल नाम* (पर्यावरण चर का नाम), कुंजी नहीं; ख़ाली = चुने गए प्रोवाइडर वाला | | `textProvider` | string | `deepseek` | टेक्स्ट मॉडल किस रास्ते से जाए: प्रीसेट नाम (`deepseek`, `openrouter`, `bailian`, `zhipu`, `moonshot`, `siliconflow`, `openai`), या `dsh:<प्रोवाइडर id>` (DSH में पहले से कॉन्फ़िगर किया गया मॉडल); नीचे «टेक्स्ट मॉडल किस रास्ते से जाए» देखें | | `textBaseUrl` | string | ख़ाली | टेक्स्ट मॉडल का OpenAI-संगत पता; ख़ाली = चुने गए रास्ते वाला | | `textModel` | string | ख़ाली | टेक्स्ट मॉडल का नाम; ख़ाली = चुने गए रास्ते का डिफ़ॉल्ट मॉडल; `dsh:` रास्ते का कोई डिफ़ॉल्ट नहीं है, एक चुनना ही पड़ता है | | `textKeyRef` | string | ख़ाली | टेक्स्ट मॉडल क्रेडेंशियल का *नाम*; ख़ाली = चुने गए रास्ते का नाम; `dsh:` रास्ते पर कुंजी DSH ख़ुद संभालता है | | `textReasoning` | `none` \| `auto` | `none` | `none` टेक्स्ट मॉडल को सोचने से रोकता है (फ़ील्ड में लिखना नक़ल है, तर्क नहीं); `auto` हर विक्रेता का डिफ़ॉल्ट इस्तेमाल करता है | | `maxSteps` | number | `60` | अधिकतम क्रियाएँ; निर्णयों की सीमा इससे दुगुनी | | `screenshots` | boolean | `false` | हर चरण पर स्क्रीनशॉट (काफ़ी धीमा) | कॉन्फ़िगरेशन `src/config.ts` में Schemastery `Config` स्कीमा से मान्य होता है, इसलिए ग़लत मान लोड होते समय ही असफल होता है, टूटा हुआ चलकर नहीं। दोनों `keyRef` फ़ील्ड किसी क्रेडेंशियल का *नाम* रखते हैं और उन पर `credential-ref` भूमिका लगी है। हर फ़ील्ड `volatile` है, और यही चीज़ सेटिंग्स पेज को तुरंत सहेजने और तुरंत असर करने देती है: DSH चलते समय बदला गया मान अगले काम में उठा लिया जाता है, बिना किसी रीस्टार्ट के। किसी नाम के पीछे का *मान* पेज के अपने खंड की 「密钥」 पंक्ति में डाला जाता है, जो DSH की अपनी क्रेडेंशियल फ़ाइल लिखता है — वह भी उतना ही तुरंत। ### टेक्स्ट मॉडल किस रास्ते से जाए टेक्स्ट मॉडल का उपयोग केवल «किसी फ़ील्ड में लिखने» के लिए होता है, और उसका अपना रास्ता चुनने का तरीक़ा भी है, जो दो वर्गों में बँटता है: | वर्ग | क्या है | पता और कुंजी | |---|---|---| | प्रीसेट (`deepseek`, `openrouter`, `bailian`, `zhipu`, `moonshot`, `siliconflow`, `openai`) | प्लगइन के साथ आने वाली तालिका | पता, डिफ़ॉल्ट मॉडल और डिफ़ॉल्ट क्रेडेंशियल नाम यही तालिका देती है; कुंजी का मान सेटिंग्स पेज का चिपकाने वाला बॉक्स DSH की क्रेडेंशियल फ़ाइल में रखता है | | DSH अंतर्निहित (`dsh:<प्रोवाइडर id>`, जैसे `dsh:deepseek-official`) | DSH में पहले से कॉन्फ़िगर किया गया मॉडल | पता और कुंजी दोनों DSH ख़ुद संभालता है; सेटिंग्स पेज केवल एक मॉडल चुनता है और कोई चिपकाने वाला बॉक्स नहीं बनाता | जो रास्ता चुना जाए वही चलेगा, और बाक़ी तीन फ़ील्ड ख़ाली रहने पर उसी के पीछे चलते हैं — नीचे निर्णय सेवा वाली व्यवस्था की तरह। `dsh:` उपसर्ग कोई शोभा नहीं है: DSH में भी `deepseek` नाम का प्रोवाइडर हो सकता है, और उपसर्ग के साथ «प्लगइन का प्रीसेट deepseek» तथा «DSH का deepseek» दो समानांतर विकल्प बनते हैं, जो एक-दूसरे को हटाते नहीं; और पहले से सहेजा गया मान भी केवल इस कारण दूसरा नहीं मान लिया जाएगा कि DSH ने बाद में उसी नाम का प्रोवाइडर पंजीकृत कर दिया। प्रीसेट केवल OpenAI प्रोटोकॉल वाले विक्रेताओं को लेते हैं, क्योंकि टेक्स्ट वाले आधे हिस्से को बस «एक वाक्य दो, एक JSON लो» से काम चलता है। Anthropic, Gemini जैसे दूसरे प्रोटोकॉल DSH के अंतर्निहित रास्ते से उतनी ही अच्छी तरह चलते हैं, क्षमता में कोई कमी नहीं आती। **क्षणिक गड़बड़ी दोबारा भेजी जाती है; टूटा हुआ कनेक्शन नहीं।** टेक्स्ट-मॉडल कॉल से 429, 503 या 529 आने पर दो बार तक दोबारा कोशिश होती है, पहले 0.5 सेकंड और फिर 1 सेकंड रुककर। नेटवर्क टूटने पर कोई दोबारा कोशिश नहीं होती और सूचना जैसी है वैसी दी जाती है — ऊपरी Python संस्करण भी वहीं यही रेखा खींचता है। **एक समझौता**: प्रीसेट में DeepSeek वाले रास्ते का डिफ़ॉल्ट क्रेडेंशियल नाम विक्रेता की परंपरा के अनुसार `DEEPSEEK_API_KEY` लिखा गया है, और DSH ख़ुद भी यही नाम इस्तेमाल करता है — इसलिए वह रास्ता ख़ाली हालत में ही «कॉन्फ़िगर» दिखता है। उसे अलग कुंजी देनी हो, तो सेटिंग्स पेज के «अग्रिम सेटिंग्स» में «कुंजी का नाम» बदलकर कुछ और भर दें। ### निर्णय सेवा के दो रास्ते दोनों दरवाज़े ठीक तीन मानों में भिन्न हैं — पता, मॉडल का नाम, और कुंजी किस क्रेडेंशियल नाम के नीचे रखी है। चूँकि ये तीनों साथ चलते हैं, कॉन्फ़िगरेशन में केवल दरवाज़ा चुना जाता है और बाक़ी तीन उसी के पीछे चलते हैं; पेज ख़ाली फ़ील्ड के लिए धूसर जगह-धारक (placeholder) में वही मान दिखाता है जो इस्तेमाल होगा, इसलिए प्रोवाइडर बदलने में कुछ कॉपी करना नहीं पड़ता और पीछे कुछ छूटता भी नहीं। | प्रोवाइडर | पता | मॉडल | क्रेडेंशियल नाम | |---|---|---|---| | TypeSafe, सीधा | `https://api.typesafe.ai/v1/systemone` | `jev-latest` | `TYPESAFE_API_KEY` | | OpenRouter | `https://openrouter.ai/api/alpha/decisions` | `~typesafe/jev-latest` | `OPENROUTER_API_KEY` | OpenRouter के मॉडल नाम के शुरू में लगी tilde कोई टाइपो नहीं है: उस मार्ग पर उसी मॉडल को यही तरह लिखा जाता है, और उसे हटाने पर ऐसा मॉडल माँगा जाता है जो मौजूद ही नहीं। यह भी देखा गया है कि उस मार्ग को अनुरोध का body `decisionsRequest` लिफ़ाफ़े में लपेटा हुआ चाहिए, इसलिए उस दरवाज़े पर प्लगइन पहले सपाट body भेजता है और केवल तभी एक बार लपेटकर दोबारा भेजता है जब body को 400 या 422 के साथ ठुकराया जाए — यह शक्ल का इनकार है, सामग्री का नहीं। TypeSafe का अपना endpoint कभी नहीं लपेटता। इस मशीन से किसी भी दरवाज़े को असली सेवा के सामने आज़माया नहीं गया है, क्योंकि यहाँ दोनों में से किसी की कुंजी नहीं है। **कुंजी कहाँ से आती है।** ऊपर के फ़ील्ड नाम रखते हैं, कुंजियाँ नहीं; DSH नाम को एक तय क्रम में हल करता है: पहले शुरुआत के समय विरासत में मिला प्रोसेस पर्यावरण, फिर उसकी अपनी क्रेडेंशियल फ़ाइल `~/.dsh/.credentials.yaml` का `refs:` भाग, फिर कार्य डिरेक्टरी का `.env`, और अंत में `~/.dsh/.env`। नाम वही पर्यावरण चर नाम होता है, बिना किसी उपसर्ग के। सबसे छोटा रास्ता प्लगइन का अपना पेज है (नीचे 「密钥」 अनुभाग): यह वह क्रेडेंशियल फ़ाइल आपके लिए लिख देता है, तुरंत असर करता है, और फ़ाइल कहाँ है यह जानने की ज़रूरत नहीं पड़ती। बाक़ी दो रास्ते अब भी काम करते हैं — `dsh web` शुरू करने से पहले उसी टर्मिनल में उसी नाम का पर्यावरण चर सेट करें, या `.credentials.yaml` में `refs:` के नीचे एक पंक्ति जोड़ें, जिसे DSH ख़ुद दोबारा पढ़ लेता है। `.env` भी काम करता है, पर उसमें `DSH_` से शुरू होने वाला कोई नाम नहीं होना चाहिए, वरना DSH शुरू ही नहीं होता। एक परत ऐसी है जिसे पेज बदल नहीं सकता: **शुरुआत के समय विरासत में मिला पर्यावरण जीतता है, और उसे उसी पल में एक ही बार पढ़ा जाता है।** जब किसी नाम का मान वहीं से आता है, तो पेज उस पंक्ति पर 「这一页改不了它」 लिख देता है और कोई इनपुट नहीं देता, साथ ही कारण भी बताता है। यह इनकार जान-बूझकर है: ऐसा लेखन जो सफल दिखे जबकि हल करने पर पुराना पर्यावरण मान ही मिलता रहे, उस साफ़-सुथरे «यह मेरी पहुँच से बाहर है» से बुरा होता। ## DSH सेटिंग्स में प्लगइन का पेज **सेटिंग्स → Jev 浏览器** खोलें। पेज चार खंडों का है — **ब्राउज़र**, **निर्णय सेवा**, **टेक्स्ट मॉडल**, **काम** — और हर खंड एक ही तरह बना है: पहले एक पंक्ति जो बताती है कि वह हिस्सा इस समय किस हाल में है, फिर उसे बदलने वाले नियंत्रण। ये हाल बताने वाली पंक्तियाँ अनुमान से नहीं, असली आने-जाने से आती हैं: «जुड़ा हुआ» का मतलब है कि डिबग endpoint ने जवाब दिया *और* एक असली टैब से snapshot लौटकर आया, और «कुंजी नहीं है» उसी बॉक्स के ठीक ऊपर बैठती है जो उसे ठीक करता है। पेज कभी आपको यह कहकर पेज के किसी दूसरे हिस्से में नहीं भेजता कि जो उसने अभी बताया उसे कहाँ ठीक करें। इस पेज पर दो कार्य ऐसे हैं जिनके बारे में टूल ख़ुद नहीं बता सकता। सबसे पहले एक स्वास्थ्य जाँच है, और वह असली जाँच है। यह ब्राउज़र के डिबग endpoint तक पहुँचता है और एक असली टैब से स्नैपशॉट का पूरा आना-जाना करता है, इसलिए «जुड़ा हुआ» का मतलब जुड़ा हुआ है, «पोर्ट ने जवाब दिया» नहीं। यह बताता है कि हर क्रेडेंशियल नाम हल होता है या नहीं — मान कभी नहीं — और यह एक असली निर्णय प्रश्न भेज सकता है, जो यह साबित करने का एकमात्र तरीका है कि पता, मॉडल नाम और कुंजी मिलकर काम करते हैं। उस पर एक कॉल खर्च होती है, इसलिए वह तभी चलता है जब आप उसे दबाते हैं। ब्राउज़र वाले खंड में एक चीज़ और है: एक 「用哪个浏览器」 dropdown (Chrome / Edge) और एक 「启动并连接」 बटन। उसे दबाइए तो प्लगइन वह ब्राउज़र शुरू करके जुड़ जाता है और नतीजा सीधे इसी पेज में लिख देता है। यह पहले खंड को सहेजता है — केवल उसी खंड को, इसलिए किसी दूसरे खंड में आधा लिखा हुआ इससे सहेजा नहीं जाता। जब executable न मिले तो यह गिनाता है कि कहाँ-कहाँ देखा; उस समय 高级设置 खोलकर **浏览器程序** में वह पाथ भर दीजिए जहाँ portable प्रति रखी है। इनके नीचे एक और बटन है, 「打开交互式检查器」, जो आपको सीधे ऊपर बताए गए उस पेज पर ले जाता है; वह पता उसी जगह से बनाता है जहाँ से आप यह पढ़ रहे हैं, इसलिए किसी भी पोर्ट पर वह ठीक रहता है। इसके बाद ऊपर की तालिका है, फ़ॉर्म के रूप में। सहेजना DSH की अपनी कॉन्फ़िग सेवा से जाता है, इसलिए मान्यता और «किसी और ने अभी यह बदल दिया» वाली जाँच harness की है, हमारी नहीं; बदलाव profile की patch लेयर में id-लक्षित override के रूप में पहुँचता है। **निर्णय सेवा** और **टेक्स्ट मॉडल** खंडों में से हर एक का अपना **保存** है: किसी एक को दबाने पर केवल उसी खंड के फ़ील्ड लिखे जाते हैं — अग्रिम सेटिंग्स में रखे उसके override भी — और दूसरे खंड में पड़े बिना सहेजे बदलाव छुए नहीं जाते। पेज के नीचे वाला बटन **保存全部改动** कहलाता है और पेज के सारे बदलाव एक ही बार में लिख देता है। दोनों हालतों में कॉन्फ़िगरेशन पहले जाता है और चिपकाई गई कुंजियाँ बाद में। यह क्रम सजावट नहीं है: जो क्रेडेंशियल नाम आपने अभी फ़ील्ड में लिखा है वह उसी समय से सहेजने योग्य होता है जब उसे नाम देने वाला कॉन्फ़िगरेशन सहेजा जा चुका हो, इसलिए एक ही बटन-दबाव नाम बदल भी सकता है *और* उसे मान भी दे सकता है। प्रोवाइडर एक dropdown है, और तीन फ़ील्ड में से अब केवल पता और क्रेडेंशियल नाम ख़ाली रह सकते हैं — वे पेज के अंत में खुलने वाले 「高级设置」 खंड में हैं —: तब धूसर जगह-धारक वही पता और क्रेडेंशियल नाम दिखाता है जो वह दरवाज़ा इस्तेमाल करेगा; **मॉडल** अब इसी खंड में दिखता है, उसका भी अपना धूसर जगह-धारक है। «टेक्स्ट मॉडल» वाला खंड भी वैसा ही है: उसके **प्रोवाइडर** dropdown में दो शीर्षकों वाले समूह दिखते हैं — पहला (「DSH 内置(由 DSH 管理地址和密钥)」) DSH में पहले से कॉन्फ़िगर किए गए मॉडल गिनाता है और दूसरा (「插件预设(本插件直连)」) प्लगइन के साथ आने वाले प्रीसेट —, और विकल्पों का पाठ सादे नाम हैं, बिना किसी उपसर्ग के; dropdown के नीचे अब एक और धूसर पंक्ति चुने गए विक्रेता का वर्णन करती है (अगर चुना गया रास्ता DSH का अंतर्निहित है तो वह पंक्ति नहीं बनती, क्योंकि फ़ील्ड का संकेत और कुंजी वाली पंक्ति पहले ही बताती है कि पता और क्रेडेंशियल DSH के पास हैं); **मॉडल** ख़ाली रहने पर उस रास्ते का डिफ़ॉल्ट मॉडल इस्तेमाल होता है, और उस बॉक्स की उम्मीदवार सूची खोलकर एक चुना भी जा सकता है और ख़ुद लिखा भी जा सकता है। प्रीसेट चुनने पर वह विक्रेता पता, मॉडल और क्रेडेंशियल नाम साथ ले आता है, और नीचे पहले की तरह चिपकाने वाला बॉक्स बनता है; DSH अंतर्निहित चुनने पर कुंजी वाली पंक्ति की जगह एक पंक्ति आ जाती है कि इसे DSH ख़ुद संभालता है, और कोई चिपकाने वाला बॉक्स नहीं बनता — उस रास्ते का पता और कुंजी DSH में हैं, इस पेज की पहुँच से बाहर। उसके नीचे 「密钥」 अनुभाग है। यह जो नाम गिनाता है, वे ठीक वही हैं जो यह कॉन्फ़िगरेशन इस्तेमाल करेगा: दोनों दरवाज़ों के डिफ़ॉल्ट नाम (ताकि प्रोवाइडर बदलने से पहले दूसरी कुंजी रखी जा सके) और साथ में कोई भी नाम जो आपने ख़ुद कॉन्फ़िगरेशन में लिखा हो। कोई मान चिपकाइए, 保存 दबाइए और वह DSH की क्रेडेंशियल फ़ाइल में लिख दिया जाता है, जिसे अगला काम बिना रीस्टार्ट इस्तेमाल करता है; 清除 उस फ़ाइल से वह प्रविष्टि हटा देता है। इसके बाद पेज केवल «कॉन्फ़िगर है» और यह दिखाता है कि मान कहाँ से आता है — DSH का क्रेडेंशियल इंटरफ़ेस बताता है कि नाम सेट है या नहीं, कौन-सी परत जीती और क्या वह लिखने योग्य है, और मान कभी किसी पेज को नहीं सौंपता, इसलिए यह पेज चाहे तो भी उसे दिखा नहीं सकता। पेज अब यह नहीं बताता कि कुंजी वाली फ़ाइल कहाँ रहती है और वह किस चीज़ से नहीं बचाती। पथ वहीं दिखता है जहाँ ज़रूरत पड़ती है: जब शुरुआती पर्यावरण किसी नाम को ढक देता है, तो वही पंक्ति उस फ़ाइल का नाम बताती है और बाहर निकलने के दोनों रास्ते भी। बाक़ी बातें यहाँ रहनी चाहिए, पेज पर नहीं: फ़ाइल केवल आपके अपने उपयोगकर्ता खाते के लिए खुली है, और DSH उसका पथ मॉडल को नहीं सौंपता — पर किसी AI की टूल प्रक्रियाएँ उसी उपयोगकर्ता के रूप में चलती हैं, इसलिए वे उसे पढ़ सकती हैं। DSH का अपना दस्तावेज़ हमसे ज़्यादा नरमी से कहता है: यह विवेक है, कोई सीमा नहीं। स्थानीय AI से बचाव के लिए ऑपरेटिंग सिस्टम का keychain चाहिए, जो अभी मौजूद नहीं है। ## Development ```sh pnpm install pnpm run typecheck pnpm test # 168 यूनिट टेस्ट (15 फ़ाइलें), न कुंजी न नेटवर्क चाहिए pnpm run build ``` ब्राउज़र वाली परत के अपने 14 इंटीग्रेशन टेस्ट हैं, जो डिफ़ॉल्ट रूप से छोड़ दिए जाते हैं और केवल असली ब्राउज़र के सामने चलाए जाते हैं: ```sh JEV_BROWSER=1 pnpm exec vitest run tests/browser.integration.test.ts ``` Windows पर यह `$env:JEV_BROWSER='1'; pnpm exec vitest run tests/browser.integration.test.ts` होता है। यह डिफ़ॉल्ट रूप से पोर्ट 9222 से जुड़ता है, या उससे जिसका नाम `JEV_CDP_URL` बताता है। पैकेज बनाए बिना भी इसे माउंट किया जा सकता है: `dsh web --patch ./scratch/cordis.yml`, जो पहले से इस रिपॉज़िटरी की बनी हुई `lib/index.mjs` की ओर इशारा करता है। अब तक जो सत्यापित हुआ है: 168 यूनिट टेस्ट पास होते हैं (15 फ़ाइलें); 14 ब्राउज़र इंटीग्रेशन टेस्ट पास होते हैं, जो यहाँ Edge के सामने चलाए गए (पिछले 11, Chrome 153.0.8010.53 और Edge 154.0.4258.37 के सामने, दोनों बार सब पास); `dsh-plugin-dev check` उस समय पास हुआ था, पर वह CLI प्लगइन-डेवलपमेंट skill के साथ आती है और इस मशीन के PATH पर अब नहीं है, इसलिए वह मद दोबारा नहीं चलाया गया; और `pnpm pack` का tarball एक साफ़-सुथरे अस्थायी profile में इंस्टॉल और लोड होता है। पेज के कुंजी अनुभाग को एक अस्थायी इंस्टेंस के सामने सोलह जाँचों में आज़माया गया: सहेजना, पंक्ति का «क्रेडेंशियल फ़ाइल से आता है» में बदल जाना, प्रोसेस रीस्टार्ट के बाद भी मान का कॉन्फ़िगर रहना, और 清除 से नाम का वापस अनकॉन्फ़िगर हो जाना; शुरुआती पर्यावरण से ढका हुआ नाम लिखने से इनकार करता है और कारण बताता है; पेज की सूची से बाहर का नाम (403), ख़ाली मान, और टोकन के बिना अनुरोध — तीनों अलग-अलग ठुकराए जाते हैं; और आठ प्रतिक्रिया बॉडी में वह मान एक बार भी नहीं दिखा। **असली कुंजी से अभी तक कोई असली निर्णय कॉल नहीं किया गया है** — इस मशीन पर दोनों में से कोई कुंजी नहीं है। चरण योजना और उसकी स्वीकृति शर्तें [`tasks/todo.md`](tasks/todo.md) में हैं। संस्करण 0.2.9 निर्णय-सेवा खंड और टेक्स्ट-मॉडल खंड को अपना-अपना «保存» बटन देता है: किसी एक को दबाने पर केवल उसी खंड के बदलाव लिखे जाते हैं — अग्रिम सेटिंग्स में रखे उसके override भी — और दूसरे खंड में पड़े बिना सहेजे बदलाव साथ नहीं चलते; पेज के नीचे वाला बटन अब «保存全部改动» कहलाता है और सब कुछ एक ही बार में लिख देता है। असली पेज पर यह इस तरह जाँचा गया कि दोनों खंडों में एक-एक बदलाव लिखकर निर्णय खंड का अपना «保存» दबाया गया: केवल निर्णय खंड का बदलाव ही सहेजा गया, जबकि टेक्स्ट खंड अब भी एक बिना सहेजा बदलाव बताता रहा, और उसका बॉक्स बिना सहेजा मान बनाए रखता रहा। संस्करण 0.2.10 ने सेटिंग्स पेज से फ़ुटनोट वाला वह खुलने वाला खंड हटा दिया: अब पेज 「保存全部改动」 पंक्ति पर समाप्त होता है, उसका बचा हुआ एकमात्र खुलासा अग्रिम सेटिंग्स वाला है, और दोनों सेवा खंड अब भी अपना-अपना 「保存」 रखते हैं। नए क्रम में लगाए गए पेज को फिर packaged tarball वाले एक अस्थायी इंस्टेंस पर चलाकर देखा गया: एक ही 「保存」 कॉन्फ़िगरेशन बदलता है और साथ ही कुंजी भी सहेज लेता है, और दोनों तुरंत असर करते हैं; फ़ॉर्म में लिखा क्रेडेंशियल नाम उसी सहेजने में मान सहेजने की अनुमति पाता है; 清除 फ़ाइल से वह प्रविष्टि हटा देता है; और पर्यावरण से दिया गया नाम बिना किसी इनपुट बॉक्स के अपना हाल दिखाता है। इसके बाद 0.2.0-rc.1 और डेस्कटॉप ऐप — दोनों को भी चलाकर देखा गया: प्लगइन उस `>=0.1.7-rc.2 <0.3.0-0` घोषणा के दम पर 0.2.0-rc.1 का compatibility gate पार कर जाता है (`--dump-config` में `disabling profile plugin` वाली कोई पंक्ति नहीं); वहाँ index injections अब भी हर अनुरोध पर दोबारा बनते हैं, इसलिए पेज को वही token मिलता है जिसे टूल का रास्ता स्वीकार करता है, और उसके बिना भेजा अनुरोध अब भी 403 से ठुकराया जाता है; डेस्कटॉप पर इंस्टॉल ऐप के अपने 添加插件 से tarball का पूरा पाथ देकर हुआ, और ऐप दोबारा शुरू करने पर boot payload में `global/__JEV_ULTRAFAST_TOKEN__` मौजूद रहता है तथा पेज बिना किसी token त्रुटि के जीवित हाल बताता है; और डेस्कटॉप पर 调试端口 बदलकर सहेजने से वह पंक्ति profile की patch लेयर में लिखी जाती है, साफ़ करने पर साफ़ किया हुआ मान लिखा जाता है, और दोनों सहेजनों के बीच पेज चलता रहता है — यही दिखता है जब कोई कॉन्फ़िगरेशन फ़ील्ड वाक़ई ऐसा हो जिसे रीलोड की ज़रूरत न हो। 0.2.8 ने मॉडल को वापस उसके अपने खंड में डाल दिया, और यह बदलाव असली पैकेज वाले इंस्टेंस में जाँचा गया: निर्णय मॉडल अपने विक्रेता के नीचे बैठता है और उसका जगह-धारक उसी के पीछे चलता है (TypeSafe `jev-latest` दिखाता है, OpenRouter `~typesafe/jev-latest`), 高级设置 में छह मदें रह जाती हैं, और हर विक्रेता के नीचे वाली टिप्पणी-पंक्ति दोनों हालतों में ठीक पढ़ी गई — OpenRouter वाली अपना alpha चैनल और tilde बताती है — जबकि DSH अंतर्निहित चुनने पर अब वह बात दोहराई नहीं जाती जो ऊपर का संकेत और नीचे की कुंजी-पंक्ति पहले ही कह चुके हैं। उसी संस्करण ने निर्णय खंड की «अब» वाली पंक्ति का शब्द चुनाव भी तय किया: अब वह सहेजा हुआ रास्ता, मॉडल **और क्रेडेंशियल नाम** एक साथ बताती है — नीचे का कुंजी-बॉक्स अब भी draft के पीछे चलता है, क्योंकि कोई मान सहेजने से पहले चिपकाया जाना ही पड़ता है, और जो वाक्य सहेजे हुए रास्ते को draft कुंजी-नाम से मिला देता था वह ऐसी हालत बताता था जो कभी अस्तित्व में नहीं रही (जीवित तौर पर देखा गया: विक्रेता बदला हुआ पर बिना सहेजा, पंक्ति अब भी `TypeSafe 官方直连 · jev-latest · 密钥 TYPESAFE_API_KEY 还没有值।` पढ़ती रही जबकि कुंजी-पंक्ति पहले ही `OPENROUTER_API_KEY` बन चुकी थी)। 0.2.12 के 「启动并连接」 को उसी tarball-इंस्टॉल इंस्टेंस के सामने असल में चलाया गया, दो बार: dropdown जो **Edge** पर खुला था उसने पिछली बार का समय सहेज रखा था (यानी चुनाव प्रोसेस रीस्टार्ट के बाद भी टिकता है); एक दबाव ने **Chrome** शुरू करके जोड़ा (पोर्ट 60856 — कोई random पोर्ट, क्योंकि पोर्ट ब्राउज़र ख़ुद चुनता है, 9222 सख़्त लिखा हुआ नहीं है); और Edge पर लौटकर दोबारा दबाने पर **Edge** से जुड़ा (`Edg/154.0.4258.37`, पोर्ट 60376) — *जबकि Chrome अब भी चल रहा था*। उसी दूसरे दौर में क्रम-सुधार दिखता है: पहले कार्यान्वयन में हाल बताने वाली पंक्ति अब भी Chrome की ओर इशारा करती थी, क्योंकि खोज ने चुने हुए ब्राउज़र को सबसे आगे नहीं रखा था। स्क्रीनशॉट: `scratch/review-0212-browser-block.png`; प्रोब: `scratch/probe-0212c.js`। **0.2.13 पहला शुरू-से-अंत चलन है** (2026-09-29, 「用插件搜 DeepSeek DSH 桌面版的下载页」 कहकर माँगा गया)। ब्राउज़र वाला आधा हिस्सा काम करता है: टूल ने सचमुच Bing खोला और पेज का टेक्स्ट तथा 20 चलाने योग्य तत्व वापस पढ़े। निर्णय वाला आधा हिस्सा **HTTP 401** पर रुक गया, और कारण प्लगइन में नहीं था: मशीन की दोनों निर्णय-क्रेडेंशियल (`OPENROUTER_API_KEY` और `TYPESAFE_API_KEY`) **एक ही 35-अक्षरों वाला मान** हैं (दोनों फ़ील्ड में चिपकाया गया), और कोई भी सेवा उसे स्वीकार नहीं करती — OpenRouter को भेजने पर वह «Missing Authentication header» कहती है (शक्ल भी नहीं पहचानती; ठीक-ठाक नक़ली कुंजी पर «User not found.» आता है, यानी वह कुंजी सचमुच पढ़ रही है), और TypeSafe के अपने endpoint को भेजने पर वह «Cannot authenticate with the server» कहती है। उसी जाँच से दो बातें निकलीं: `openrouter.ai` का सार्वजनिक endpoint बिना कुंजी के 200 देता है (यानी नेटवर्क का रास्ता ठीक है) और OpenRouter का alpha चैनल bearer कुंजी **लेता** है (यानी प्लगइन का OpenRouter दरवाज़ा चल सकता है — बस असली कुंजी चाहिए)। उसी चलन ने प्लगइन में एक असली ख़राबी भी उजागर की, जो इसी संस्करण में ठीक हुई: इनकार केवल «HTTP 401» कहता था, जिससे «यह कुंजी पहचानी नहीं गई» और «यह अनुरोध endpoint की पसंद का नहीं» में फ़र्क़ नहीं होता। अब सेवा के अपने शब्द भी एक पंक्ति में साथ आते हैं, 240 अक्षरों पर काटकर, और उससे पहले कुंजी `***` कर दी जाती है — इसे एक यूनिट टेस्ट पकड़े रहता है। रोज़मर्रा के web profile में इंस्टॉल किया गया, दोनों artifact रिपॉज़िटरी से बाइट-दर-बाइट वही, `--dump-config` का exit 0। **0.2.14 उस टैब के पीछे चलता है जो क्लिक खोलता है** (उसी दिन, बाद में)। 0.2.13 के चलन ने एक खुलासा करने वाला दृश्य छोड़ा: Bing के तीन नतीजा-टैब सचमुच खुले थे, जबकि चलन बता रहा था «पेज तीन चरणों तक नहीं बदला» — हर क्लिक काम कर चुका था, साइट हर नतीजे को नई टैब में खोल रही थी, और चलन केवल उसी टैब को देख रहा था जिससे वह जुड़ा था। दो चीज़ें बदलीं। पहली, ऐसा चरण जो इस टैब को उसी पते पर छोड़े मगर एक नया पेज अस्तित्व में ले आए, अब चलन को उस पेज पर ले जाता है और यह बताता है, इसलिए अगला निर्णय देखता है कि क्लिक ने क्या किया; जिस चरण में इस टैब का पता *बदल* जाए वह यथावत रहता है, और चरण-पंक्ति नए विंडो की सूचना देती है, यह दिखावा नहीं करती कि कुछ हुआ ही नहीं। कसौटी पूरा पेज नहीं, पता है, क्योंकि खोज-नतीजे का «देखा हुआ» लाल होना उस पेज को ही दोबारा खींचता है जिस पर वह बैठा है — और जिस fingerprint नियम से यह प्लगइन पहले चलता था, उसके तहत जीवित चलन ने Bing पर ठीक इसी कारण पीछे चलने से इनकार कर दिया था। दूसरी, अब कोई चलन केवल उसी टैब को बंद करता है जो उसने ख़ुद बनाई: जिस पेज पर वह गया था वह खुला रहता है, इसलिए काम जो खोजने गया था वह ख़त्म होने पर भी वहीं मौजूद रहता है। सात नए टेस्ट (लूप में तीन, उस एक पंक्ति के लिए दो जो उपयोगकर्ता पढ़ता है, और एक असली-ब्राउज़र इंटीग्रेशन टेस्ट जो `target="_blank"` वाली कड़ी पर क्लिक करके जाँचता है कि दूसरा पेज पढ़ा गया), साथ में असली निर्णय सेवा के सामने एक जीवित चलन — Bing → 冯时 → 百度百科 का लेख: एक चरण, पीछे चला, अंतिम पेज वही लेख, जहाँ सुधार से पहले इसी लक्ष्य में चार चरण लगे थे और अंत Baike के अपने खोज-पेज पर हुआ। रोज़मर्रा के web profile में इंस्टॉल, दोनों artifact बाइट-दर-बाइट वही, `--dump-config` का exit 0। **0.2.15 में कोई काम ख़ुद ब्राउज़र शुरू करता है** (उसी दिन, बाद में)। सवाल यह था कि बातचीत वाले पेज से इस प्लगइन को बुलाने वाला मुख्य मॉडल पहले पाठक को सेटिंग्स पेज भेजने के बजाय ख़ुद ब्राउज़र शुरू करके जुड़ सकता है या नहीं। वह कर सकता है: यहाँ ब्राउज़र शुरू करना पूरी तरह यांत्रिक है (executable खोजो, उसे profile डिरेक्टरी दो, पोर्ट उसे चुनने दो, पोर्ट के जवाब का इंतज़ार करो), और 0.2.12 का बटन वही कोड चलाता है। «मॉडल के हाथ नहीं होते» का मतलब था कि मॉडल ख़ुद से कोई प्रोसेस शुरू नहीं कर सकता — और टूल कॉल प्लगइन का अपना हाथ है, इसीलिए सीमा टूटने के बजाय खिसकती है। इसलिए कोई चलन अब पहले ब्राउज़र खोजता है, और जब कुछ भी पहुँच में न हो *और* कुछ pin भी न किया गया हो, तो वह सेटिंग्स पेज में बताया ब्राउज़र शुरू कर देता है — वही executable-खोज, वही profile डिरेक्टरी, वही पोर्ट-फ़ाइल तरकीब — और अपने नतीजे में यह बताता है। दो किनारे जान-बूझकर बनाए रखे गए हैं। pin की हुई `cdpUrl` या `userDataDir` संकेत नहीं, निर्देश है: जब उनमें से कोई सेट हो और मृत हो, तो चलन किसी और ब्राउज़र को शुरू करने के बजाय यही बताता है, क्योंकि ऐसा ब्राउज़र शुरू करना जिसे किसी ने माँगा ही नहीं, यह कहने से बुरा जवाब है कि वह पता जवाब नहीं देता। और मॉडल कभी executable या पोर्ट का नाम नहीं लेता: executable सेटिंग्स से आती है, पोर्ट ब्राउज़र से। जाँचा गया: 7 नए यूनिट टेस्ट (`ensureBrowser` का pin होना — जब ब्राउज़र शुरू हो तो कौन-सा, और जब कुछ न हो तो कुछ नहीं; और `launchNote` उस एक पंक्ति को पकड़ता है जो उपयोगकर्ता पढ़ता है), कुल 126 पास; दो जीवित चलन — एक में प्लगइन की profile डिरेक्टरी एक अस्थायी डिरेक्टरी पर लगाई गई थी ताकि कुछ भी पहुँच में न हो, और वहाँ सचमुच Edge (`Edg/154.0.4258.37`) शुरू हुआ और उससे जुड़ा, जिसके बाद उस इंस्टेंस को बंद होने को कहा गया ताकि कोई खिड़की पीछे न छूटे; और एक में ब्राउज़र पहले से चल रहा था, जिसमें कुछ भी शुरू नहीं हुआ। रोज़मर्रा के web profile में इंस्टॉल, दोनों artifact बाइट-दर-बाइट वही, `--dump-config` का exit 0। **ज्ञात सीमाएँ, साफ़ शब्दों में**: डेस्कटॉप profile अब भी 0.2.1 पर है और 3080 वाला रोज़मर्रा का `dsh web` रीस्टार्ट होने तक पुराने artifact ही चलाता है, इसलिए डेस्कटॉप ऐप को अपडेट करने का मतलब है वहाँ tarball दोबारा इंस्टॉल करना। इंस्पेक्टर पेज ख़ुद `dsh web` के एक रीस्टार्ट के बाद ही नया build उठाता है, और उसकी अपनी क्लिक-सतह असली ब्राउज़र में अभी क्लिक करके नहीं देखी गई — असली ब्राउज़र ने pause / step / stop के *अर्थ* की जाँच की है (रोकना सचमुच क्लिक रोकता है, छोड़ना सचमुच क्लिक करता है, हमेशा के लिए रोकना सचमुच क्लिक नहीं करता)। «रिकॉर्डिंग» का मतलब है फ़्रेम उसी रफ़्तार से दोबारा चलना जिस रफ़्तार से लिए गए थे; कोई वीडियो फ़ाइल नहीं बनती। trace सिस्टम की अस्थायी डिरेक्टरी में पड़ता है, उसमें पेज का टेक्स्ट रहता है, और उसे अपने आप साफ़ करने वाला कोई नहीं है। ## License MIT। कुछ भाग jev-ultrafast (MIT, © 2026 Browser Use) से लिए गए हैं; व्युत्पन्न फ़ाइलों की सूची [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) में है।