@researai/deepscientist 1.5.9 → 1.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -99
- package/assets/branding/connector-qq.png +0 -0
- package/assets/branding/connector-rokid.png +0 -0
- package/assets/branding/connector-weixin.png +0 -0
- package/assets/branding/projects.png +0 -0
- package/bin/ds.js +519 -63
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +338 -68
- package/docs/en/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +180 -4
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +66 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
- package/docs/en/11_LICENSE_AND_RISK.md +256 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +446 -0
- package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +83 -0
- package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
- package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
- package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
- package/docs/images/weixin/weixin-settings-bind.svg +57 -0
- package/docs/zh/00_QUICK_START.md +345 -72
- package/docs/zh/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +181 -3
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +68 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
- package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +442 -0
- package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +129 -0
- package/install.sh +0 -34
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/service.py +574 -108
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/monitor.py +7 -5
- package/src/deepscientist/bash_exec/service.py +93 -21
- package/src/deepscientist/bridges/builtins.py +2 -0
- package/src/deepscientist/bridges/connectors.py +447 -0
- package/src/deepscientist/channels/__init__.py +2 -0
- package/src/deepscientist/channels/builtins.py +3 -1
- package/src/deepscientist/channels/local.py +3 -3
- package/src/deepscientist/channels/qq.py +8 -8
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +14 -8
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +388 -0
- package/src/deepscientist/config/models.py +23 -2
- package/src/deepscientist/config/service.py +539 -67
- package/src/deepscientist/connector/__init__.py +4 -0
- package/src/deepscientist/connector/connector_profiles.py +481 -0
- package/src/deepscientist/connector/lingzhu_support.py +668 -0
- package/src/deepscientist/connector/qq_profiles.py +206 -0
- package/src/deepscientist/connector/weixin_support.py +663 -0
- package/src/deepscientist/connector_profiles.py +1 -374
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +165 -5
- package/src/deepscientist/daemon/api/router.py +13 -1
- package/src/deepscientist/daemon/app.py +1444 -67
- package/src/deepscientist/doctor.py +4 -5
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +135 -7
- package/src/deepscientist/prompts/builder.py +128 -11
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +359 -74
- package/src/deepscientist/quest/stage_views.py +71 -5
- package/src/deepscientist/runners/codex.py +170 -19
- package/src/deepscientist/runners/runtime_overrides.py +6 -0
- package/src/deepscientist/shared.py +33 -14
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/qq.md +2 -1
- package/src/prompts/connectors/weixin.md +231 -0
- package/src/prompts/contracts/shared_interaction.md +4 -1
- package/src/prompts/system.md +61 -9
- package/src/skills/analysis-campaign/SKILL.md +46 -6
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
- package/src/skills/baseline/SKILL.md +1 -1
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +1 -1
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +1 -1
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +74 -1
- package/src/skills/rebuttal/references/response-letter-template.md +55 -11
- package/src/skills/review/SKILL.md +118 -1
- package/src/skills/review/references/experiment-todo-template.md +23 -0
- package/src/skills/review/references/review-report-template.md +16 -0
- package/src/skills/review/references/revision-log-template.md +4 -0
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +168 -7
- package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-CnJcXynW.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-CB1YODQn.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-Ciz1gDaX.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BhmjNQRC.js} +37 -11
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-DmyHspXt.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-BMXKrDRk.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BTVYRGkm.js} +12 -12
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-CvcjJHXv.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DW2ej8Vk.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-CmlDxbhU.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DAjQZPSv.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C-nVAZb_.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-D7-dIYon.js} +10 -10
- package/src/ui/dist/assets/bot-C_G4WtNI.js +21 -0
- package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
- package/src/ui/dist/assets/{code-BWAY76JP.js → code-Cd7WfiWq.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-B57zsL9y.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-DVoheLFq.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-B5kXFxZP.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-LLOjkMHF.js} +1 -1
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-BQG-1s2o.css} +40 -13
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index-C3r2iGrp.js} +12 -12
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-CLQauncb.js} +15050 -9561
- package/src/ui/dist/assets/index-Dxa2eYMY.js +25 -0
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-hOUOWbW2.js} +2 -2
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-BGGAEii3.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DlEr1_y5.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-CWJbJuYY.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-CRJiucYO.js} +18 -77
- package/src/ui/dist/assets/select-CoHB7pvH.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-D5aJWR8J.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-DUK_mnkS.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash-ChU3SEE3.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-BrJBV3tY.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-C7Qqh-om.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-rtX0FKya.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
- package/src/ui/dist/assets/AutoFigurePlugin-C_wWw4AP.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
- package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
- package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
- package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
package/bin/ds.js
CHANGED
|
@@ -36,15 +36,33 @@ const pythonCommands = new Set([
|
|
|
36
36
|
const UPDATE_PACKAGE_NAME = String(packageJson.name || '@researai/deepscientist').trim() || '@researai/deepscientist';
|
|
37
37
|
const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
|
|
38
38
|
|
|
39
|
-
const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy']);
|
|
39
|
+
const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile']);
|
|
40
40
|
|
|
41
|
-
function buildCodexOverrideEnv({ yolo = false } = {}) {
|
|
41
|
+
function buildCodexOverrideEnv({ yolo = false, profile = null } = {}) {
|
|
42
|
+
const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
43
|
+
const overrides = {};
|
|
42
44
|
if (!yolo) {
|
|
43
|
-
|
|
45
|
+
if (normalizedProfile) {
|
|
46
|
+
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
47
|
+
overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
|
|
48
|
+
}
|
|
49
|
+
return overrides;
|
|
44
50
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
overrides.DEEPSCIENTIST_CODEX_YOLO = '1';
|
|
52
|
+
if (normalizedProfile) {
|
|
53
|
+
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
54
|
+
overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
|
|
55
|
+
}
|
|
56
|
+
return overrides;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readOptionValue(argv, optionName) {
|
|
60
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
61
|
+
if (argv[index] === optionName && argv[index + 1]) {
|
|
62
|
+
return argv[index + 1];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
function printLauncherHelp() {
|
|
@@ -84,6 +102,7 @@ Launcher flags:
|
|
|
84
102
|
--here Create/use ./DeepScientist under the current working directory as home
|
|
85
103
|
--proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
|
|
86
104
|
--yolo Run Codex in YOLO mode: approval_policy=never and sandbox_mode=danger-full-access
|
|
105
|
+
--codex-profile <id> Run DeepScientist with a specific Codex profile, for example \`m27\`
|
|
87
106
|
--quest-id <id> Open the TUI on one quest directly
|
|
88
107
|
|
|
89
108
|
Update:
|
|
@@ -564,6 +583,12 @@ function colorize(code, text) {
|
|
|
564
583
|
return `${code}${text}\u001B[0m`;
|
|
565
584
|
}
|
|
566
585
|
|
|
586
|
+
const OFFICIAL_REPOSITORY_URL = 'https://github.com/ResearAI/DeepScientist';
|
|
587
|
+
|
|
588
|
+
function officialRepositoryLine() {
|
|
589
|
+
return `Official open-source repository: ${hyperlink(OFFICIAL_REPOSITORY_URL, OFFICIAL_REPOSITORY_URL)}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
567
592
|
function renderBrandArtwork() {
|
|
568
593
|
const brandPath = path.join(repoRoot, 'assets', 'branding', 'deepscientist-mark.png');
|
|
569
594
|
const chafa = resolveExecutableOnPath('chafa');
|
|
@@ -668,7 +693,10 @@ function printLaunchCard({
|
|
|
668
693
|
const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
|
|
669
694
|
const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
|
|
670
695
|
const title = colorize('\u001B[1;38;5;39m', 'ResearAI');
|
|
671
|
-
const
|
|
696
|
+
const subtitleLines = [
|
|
697
|
+
colorize('\u001B[38;5;110m', 'DeepScientist is not just a fully open-source autonomous scientific discovery system.'),
|
|
698
|
+
colorize('\u001B[38;5;110m', 'It is also a research map that keeps growing from every round.'),
|
|
699
|
+
];
|
|
672
700
|
const versionLine = colorize('\u001B[38;5;245m', `Version ${packageJson.version}`);
|
|
673
701
|
const urlLabel = colorize('\u001B[1;38;5;45m', hyperlink(url, url));
|
|
674
702
|
const workspaceMode =
|
|
@@ -711,7 +739,9 @@ function printLaunchCard({
|
|
|
711
739
|
for (const line of wordmark) {
|
|
712
740
|
console.log(centerText(colorize('\u001B[1;38;5;39m', line), width));
|
|
713
741
|
}
|
|
714
|
-
|
|
742
|
+
for (const line of subtitleLines) {
|
|
743
|
+
console.log(centerText(line, width));
|
|
744
|
+
}
|
|
715
745
|
console.log('');
|
|
716
746
|
console.log(centerText(divider, width));
|
|
717
747
|
console.log(centerText(colorize('\u001B[1m', workspaceMode), width));
|
|
@@ -721,6 +751,7 @@ function printLaunchCard({
|
|
|
721
751
|
console.log(centerText(nextStep, width));
|
|
722
752
|
console.log(centerText('Run ds --stop to stop the managed daemon.', width));
|
|
723
753
|
console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
|
|
754
|
+
console.log(centerText(officialRepositoryLine(), width));
|
|
724
755
|
console.log('');
|
|
725
756
|
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
|
|
726
757
|
}
|
|
@@ -742,6 +773,13 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
742
773
|
const errors = Array.isArray(probe?.errors) ? probe.errors : [];
|
|
743
774
|
const guidance = Array.isArray(probe?.guidance) ? probe.guidance : [];
|
|
744
775
|
const details = probe && typeof probe.details === 'object' ? probe.details : {};
|
|
776
|
+
const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
|
|
777
|
+
const intro = profile
|
|
778
|
+
? `DeepScientist blocked startup because the Codex hello probe did not pass for profile \`${profile}\`. Verify that \`codex --profile ${profile}\` works on this machine and that the profile's provider-specific API key, Base URL, and model configuration are already set up.`
|
|
779
|
+
: 'DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency. If `codex` is still missing, repair it with `npm install -g @openai/codex`. Then run `codex --login` (or `codex`), finish authentication, run `ds doctor`, and launch `ds` again.';
|
|
780
|
+
const introZh = profile
|
|
781
|
+
? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
|
|
782
|
+
: 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex --login`(或 `codex`)完成认证,再执行 `ds doctor`,最后重新启动 `ds`。';
|
|
745
783
|
const renderItems = (items, tone) =>
|
|
746
784
|
items
|
|
747
785
|
.map(
|
|
@@ -802,8 +840,8 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
802
840
|
<main class="page">
|
|
803
841
|
<section class="panel">
|
|
804
842
|
<h1>DeepScientist could not start Codex</h1>
|
|
805
|
-
<p class="meta"
|
|
806
|
-
<p class="meta"
|
|
843
|
+
<p class="meta">${escapeHtml(intro)}</p>
|
|
844
|
+
<p class="meta">${escapeHtml(introZh)}</p>
|
|
807
845
|
|
|
808
846
|
<h2>Summary</h2>
|
|
809
847
|
<p>${escapeHtml(probe?.summary || 'Codex startup probe failed.')}</p>
|
|
@@ -826,6 +864,10 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
826
864
|
<dt>Model</dt>
|
|
827
865
|
<dd>${escapeHtml(details.model || '')}</dd>
|
|
828
866
|
</dl>
|
|
867
|
+
<dl class="kv">
|
|
868
|
+
<dt>Profile</dt>
|
|
869
|
+
<dd>${escapeHtml(details.profile || '')}</dd>
|
|
870
|
+
</dl>
|
|
829
871
|
<dl class="kv">
|
|
830
872
|
<dt>Exit code</dt>
|
|
831
873
|
<dd>${escapeHtml(details.exit_code ?? '')}</dd>
|
|
@@ -938,6 +980,7 @@ function parseLauncherArgs(argv) {
|
|
|
938
980
|
let daemonOnly = false;
|
|
939
981
|
let skipUpdateCheck = false;
|
|
940
982
|
let yolo = false;
|
|
983
|
+
let codexProfile = null;
|
|
941
984
|
|
|
942
985
|
if (args[0] === 'ui') {
|
|
943
986
|
args.shift();
|
|
@@ -957,6 +1000,7 @@ function parseLauncherArgs(argv) {
|
|
|
957
1000
|
else if (arg === '--daemon-only') daemonOnly = true;
|
|
958
1001
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
959
1002
|
else if (arg === '--yolo') yolo = true;
|
|
1003
|
+
else if (arg === '--codex-profile' && args[index + 1]) codexProfile = args[++index];
|
|
960
1004
|
else if (arg === '--host' && args[index + 1]) host = args[++index];
|
|
961
1005
|
else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
|
|
962
1006
|
else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
|
|
@@ -982,6 +1026,7 @@ function parseLauncherArgs(argv) {
|
|
|
982
1026
|
daemonOnly,
|
|
983
1027
|
skipUpdateCheck,
|
|
984
1028
|
yolo,
|
|
1029
|
+
codexProfile,
|
|
985
1030
|
};
|
|
986
1031
|
}
|
|
987
1032
|
|
|
@@ -1002,6 +1047,8 @@ Flags:
|
|
|
1002
1047
|
--force-check Ignore the cached version probe
|
|
1003
1048
|
--remind-later Defer prompts for the current published version
|
|
1004
1049
|
--skip-version Skip reminders for the current published version
|
|
1050
|
+
|
|
1051
|
+
Without \`--yes\`, \`ds update\` will ask for a \`Y/N\` confirmation on interactive terminals.
|
|
1005
1052
|
`);
|
|
1006
1053
|
}
|
|
1007
1054
|
|
|
@@ -2270,6 +2317,10 @@ function normalizePythonCliArgs(args, home) {
|
|
|
2270
2317
|
if (arg === '--yolo') {
|
|
2271
2318
|
continue;
|
|
2272
2319
|
}
|
|
2320
|
+
if (arg === '--codex-profile') {
|
|
2321
|
+
index += 1;
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2273
2324
|
normalized.push(arg);
|
|
2274
2325
|
}
|
|
2275
2326
|
return ['--home', home, ...normalized];
|
|
@@ -2478,6 +2529,270 @@ function removeDaemonState(home) {
|
|
|
2478
2529
|
}
|
|
2479
2530
|
}
|
|
2480
2531
|
|
|
2532
|
+
function daemonSupervisorLogPath(home) {
|
|
2533
|
+
return path.join(home, 'logs', 'daemon-supervisor.log');
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
function appendDaemonSupervisorLog(home, message) {
|
|
2537
|
+
try {
|
|
2538
|
+
const logPath = daemonSupervisorLogPath(home);
|
|
2539
|
+
ensureDir(path.dirname(logPath));
|
|
2540
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${String(message || '').trim()}\n`, 'utf8');
|
|
2541
|
+
} catch {}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
function observeManagedDaemonChild(home, child, daemonId) {
|
|
2545
|
+
if (!child || typeof child.once !== 'function') {
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const normalizedDaemonId = String(daemonId || '').trim() || 'unknown';
|
|
2549
|
+
child.once('exit', (code, signal) => {
|
|
2550
|
+
appendDaemonSupervisorLog(
|
|
2551
|
+
home,
|
|
2552
|
+
`daemon ${normalizedDaemonId} exited with code=${code === null ? 'null' : code} signal=${signal || 'null'}`
|
|
2553
|
+
);
|
|
2554
|
+
});
|
|
2555
|
+
child.once('error', (error) => {
|
|
2556
|
+
appendDaemonSupervisorLog(
|
|
2557
|
+
home,
|
|
2558
|
+
`daemon ${normalizedDaemonId} child process error: ${error instanceof Error ? error.message : String(error)}`
|
|
2559
|
+
);
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
function encodeSupervisorEnvPayload(envOverrides) {
|
|
2564
|
+
const payload = envOverrides && typeof envOverrides === 'object' && !Array.isArray(envOverrides) ? envOverrides : {};
|
|
2565
|
+
return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
function decodeSupervisorEnvPayload(rawValue) {
|
|
2569
|
+
const normalized = String(rawValue || '').trim();
|
|
2570
|
+
if (!normalized) {
|
|
2571
|
+
return {};
|
|
2572
|
+
}
|
|
2573
|
+
try {
|
|
2574
|
+
const parsed = JSON.parse(Buffer.from(normalized, 'base64').toString('utf8'));
|
|
2575
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
2576
|
+
} catch {
|
|
2577
|
+
return {};
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId = null }) {
|
|
2582
|
+
const browserUrl = browserUiUrl(host, port);
|
|
2583
|
+
const daemonBindUrl = bindUiUrl(host, port);
|
|
2584
|
+
const logPath = path.join(home, 'logs', 'daemon.log');
|
|
2585
|
+
ensureDir(path.dirname(logPath));
|
|
2586
|
+
const out = fs.openSync(logPath, 'a');
|
|
2587
|
+
const resolvedDaemonId = String(daemonId || crypto.randomUUID()).trim();
|
|
2588
|
+
const launcherPath = path.join(repoRoot, 'bin', 'ds.js');
|
|
2589
|
+
const child = spawn(
|
|
2590
|
+
runtimePython,
|
|
2591
|
+
[
|
|
2592
|
+
'-m',
|
|
2593
|
+
'deepscientist.cli',
|
|
2594
|
+
'--home',
|
|
2595
|
+
home,
|
|
2596
|
+
...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
|
|
2597
|
+
'daemon',
|
|
2598
|
+
'--host',
|
|
2599
|
+
host,
|
|
2600
|
+
'--port',
|
|
2601
|
+
String(port),
|
|
2602
|
+
],
|
|
2603
|
+
{
|
|
2604
|
+
cwd: repoRoot,
|
|
2605
|
+
detached: true,
|
|
2606
|
+
stdio: ['ignore', out, out],
|
|
2607
|
+
env: {
|
|
2608
|
+
...process.env,
|
|
2609
|
+
...envOverrides,
|
|
2610
|
+
DEEPSCIENTIST_REPO_ROOT: repoRoot,
|
|
2611
|
+
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
2612
|
+
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2613
|
+
DS_DAEMON_ID: resolvedDaemonId,
|
|
2614
|
+
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
2615
|
+
},
|
|
2616
|
+
}
|
|
2617
|
+
);
|
|
2618
|
+
child.unref();
|
|
2619
|
+
const statePayload = {
|
|
2620
|
+
pid: child.pid,
|
|
2621
|
+
host,
|
|
2622
|
+
port,
|
|
2623
|
+
url: browserUrl,
|
|
2624
|
+
bind_url: daemonBindUrl,
|
|
2625
|
+
log_path: logPath,
|
|
2626
|
+
started_at: new Date().toISOString(),
|
|
2627
|
+
home: normalizeHomePath(home),
|
|
2628
|
+
daemon_id: resolvedDaemonId,
|
|
2629
|
+
};
|
|
2630
|
+
writeDaemonState(home, statePayload);
|
|
2631
|
+
return {
|
|
2632
|
+
child,
|
|
2633
|
+
statePayload,
|
|
2634
|
+
browserUrl,
|
|
2635
|
+
bindUrl: daemonBindUrl,
|
|
2636
|
+
logPath,
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId }) {
|
|
2641
|
+
const launcherPath = resolveLauncherPath() || path.join(repoRoot, 'bin', 'ds.js');
|
|
2642
|
+
const args = [
|
|
2643
|
+
launcherPath,
|
|
2644
|
+
'--daemon-supervisor',
|
|
2645
|
+
'--home',
|
|
2646
|
+
home,
|
|
2647
|
+
'--runtime-python',
|
|
2648
|
+
runtimePython,
|
|
2649
|
+
'--host',
|
|
2650
|
+
host,
|
|
2651
|
+
'--port',
|
|
2652
|
+
String(port),
|
|
2653
|
+
'--daemon-id',
|
|
2654
|
+
String(daemonId || '').trim(),
|
|
2655
|
+
];
|
|
2656
|
+
const normalizedProxy = normalizeProxyUrl(proxy);
|
|
2657
|
+
if (normalizedProxy) {
|
|
2658
|
+
args.push('--proxy', normalizedProxy);
|
|
2659
|
+
}
|
|
2660
|
+
const envPayload = encodeSupervisorEnvPayload(envOverrides);
|
|
2661
|
+
if (envPayload) {
|
|
2662
|
+
args.push('--env-json', envPayload);
|
|
2663
|
+
}
|
|
2664
|
+
const child = spawn(process.execPath, args, {
|
|
2665
|
+
cwd: repoRoot,
|
|
2666
|
+
detached: true,
|
|
2667
|
+
stdio: 'ignore',
|
|
2668
|
+
env: {
|
|
2669
|
+
...process.env,
|
|
2670
|
+
DEEPSCIENTIST_REPO_ROOT: repoRoot,
|
|
2671
|
+
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
2672
|
+
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2673
|
+
},
|
|
2674
|
+
});
|
|
2675
|
+
child.unref();
|
|
2676
|
+
return child.pid || null;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function parseDaemonSupervisorArgs(argv) {
|
|
2680
|
+
const args = [...argv];
|
|
2681
|
+
let home = null;
|
|
2682
|
+
let runtimePython = null;
|
|
2683
|
+
let host = '0.0.0.0';
|
|
2684
|
+
let port = 20999;
|
|
2685
|
+
let proxy = null;
|
|
2686
|
+
let daemonId = null;
|
|
2687
|
+
let envJson = '';
|
|
2688
|
+
|
|
2689
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2690
|
+
const arg = args[index];
|
|
2691
|
+
if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
|
|
2692
|
+
else if (arg === '--runtime-python' && args[index + 1]) runtimePython = args[++index];
|
|
2693
|
+
else if (arg === '--host' && args[index + 1]) host = args[++index];
|
|
2694
|
+
else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
|
|
2695
|
+
else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
|
|
2696
|
+
else if (arg === '--daemon-id' && args[index + 1]) daemonId = args[++index];
|
|
2697
|
+
else if (arg === '--env-json' && args[index + 1]) envJson = args[++index];
|
|
2698
|
+
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
2699
|
+
else return null;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
return {
|
|
2707
|
+
help: false,
|
|
2708
|
+
home,
|
|
2709
|
+
runtimePython,
|
|
2710
|
+
host,
|
|
2711
|
+
port,
|
|
2712
|
+
proxy,
|
|
2713
|
+
daemonId,
|
|
2714
|
+
envOverrides: decodeSupervisorEnvPayload(envJson),
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
async function daemonSupervisorMain(rawArgs) {
|
|
2719
|
+
const options = parseDaemonSupervisorArgs(rawArgs);
|
|
2720
|
+
if (!options) {
|
|
2721
|
+
console.error('Invalid daemon supervisor arguments.');
|
|
2722
|
+
process.exit(1);
|
|
2723
|
+
}
|
|
2724
|
+
if (options.help) {
|
|
2725
|
+
process.exit(0);
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
const home = options.home;
|
|
2729
|
+
let trackedDaemonId = String(options.daemonId || '').trim();
|
|
2730
|
+
let restartBackoffMs = 1000;
|
|
2731
|
+
appendDaemonSupervisorLog(home, `supervisor started for daemon ${trackedDaemonId}`);
|
|
2732
|
+
|
|
2733
|
+
while (true) {
|
|
2734
|
+
const state = readDaemonState(home);
|
|
2735
|
+
if (!state) {
|
|
2736
|
+
appendDaemonSupervisorLog(home, 'daemon state removed; supervisor exiting');
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (state.shutdown_requested_at) {
|
|
2740
|
+
appendDaemonSupervisorLog(home, 'managed shutdown requested; supervisor exiting');
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
const stateHome = normalizeHomePath(state.home || home);
|
|
2744
|
+
if (stateHome !== normalizeHomePath(home)) {
|
|
2745
|
+
appendDaemonSupervisorLog(home, `daemon state home changed to ${stateHome}; supervisor exiting`);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
const stateDaemonId = String(state.daemon_id || '').trim();
|
|
2749
|
+
if (trackedDaemonId && stateDaemonId && stateDaemonId !== trackedDaemonId) {
|
|
2750
|
+
appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port));
|
|
2754
|
+
if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
|
|
2755
|
+
restartBackoffMs = 1000;
|
|
2756
|
+
await sleep(2500);
|
|
2757
|
+
continue;
|
|
2758
|
+
}
|
|
2759
|
+
if (state.pid && isPidAlive(state.pid)) {
|
|
2760
|
+
await sleep(2500);
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
appendDaemonSupervisorLog(
|
|
2765
|
+
home,
|
|
2766
|
+
`daemon ${stateDaemonId || trackedDaemonId || 'unknown'} is not healthy; attempting restart`
|
|
2767
|
+
);
|
|
2768
|
+
try {
|
|
2769
|
+
const restarted = spawnManagedDaemonProcess({
|
|
2770
|
+
home,
|
|
2771
|
+
runtimePython: options.runtimePython,
|
|
2772
|
+
host: options.host,
|
|
2773
|
+
port: options.port,
|
|
2774
|
+
proxy: options.proxy,
|
|
2775
|
+
envOverrides: options.envOverrides,
|
|
2776
|
+
});
|
|
2777
|
+
trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
|
|
2778
|
+
observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
|
|
2779
|
+
appendDaemonSupervisorLog(
|
|
2780
|
+
home,
|
|
2781
|
+
`restarted daemon ${trackedDaemonId} with pid ${restarted.statePayload.pid}`
|
|
2782
|
+
);
|
|
2783
|
+
restartBackoffMs = 1000;
|
|
2784
|
+
await sleep(2500);
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
appendDaemonSupervisorLog(
|
|
2787
|
+
home,
|
|
2788
|
+
`restart failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2789
|
+
);
|
|
2790
|
+
await sleep(restartBackoffMs);
|
|
2791
|
+
restartBackoffMs = Math.min(restartBackoffMs * 2, 30000);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2481
2796
|
function sleep(ms) {
|
|
2482
2797
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2483
2798
|
}
|
|
@@ -2649,6 +2964,13 @@ async function stopDaemon(home) {
|
|
|
2649
2964
|
}
|
|
2650
2965
|
}
|
|
2651
2966
|
|
|
2967
|
+
if (state) {
|
|
2968
|
+
writeDaemonState(home, {
|
|
2969
|
+
...state,
|
|
2970
|
+
shutdown_requested_at: new Date().toISOString(),
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2652
2974
|
let stopped = false;
|
|
2653
2975
|
|
|
2654
2976
|
if (healthyBefore) {
|
|
@@ -2769,6 +3091,33 @@ function printUpdateStatus(status, { compact = false } = {}) {
|
|
|
2769
3091
|
}
|
|
2770
3092
|
}
|
|
2771
3093
|
|
|
3094
|
+
function parseYesNoAnswer(answer, defaultValue = false) {
|
|
3095
|
+
const normalized = String(answer || '').trim().toLowerCase();
|
|
3096
|
+
if (!normalized) {
|
|
3097
|
+
return defaultValue;
|
|
3098
|
+
}
|
|
3099
|
+
if (normalized === 'y' || normalized === 'yes') {
|
|
3100
|
+
return true;
|
|
3101
|
+
}
|
|
3102
|
+
if (normalized === 'n' || normalized === 'no') {
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
return defaultValue;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
async function promptYesNo(question, { defaultValue = false } = {}) {
|
|
3109
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3110
|
+
return defaultValue;
|
|
3111
|
+
}
|
|
3112
|
+
return new Promise((resolve) => {
|
|
3113
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3114
|
+
rl.question(question, (answer) => {
|
|
3115
|
+
rl.close();
|
|
3116
|
+
resolve(parseYesNoAnswer(answer, defaultValue));
|
|
3117
|
+
});
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
|
|
2772
3121
|
function spawnDetachedNode(args, options = {}) {
|
|
2773
3122
|
const out = options.logPath ? fs.openSync(options.logPath, 'a') : 'ignore';
|
|
2774
3123
|
const child = spawn(process.execPath, args, {
|
|
@@ -2965,6 +3314,50 @@ async function performSelfUpdate(home, options = {}) {
|
|
|
2965
3314
|
};
|
|
2966
3315
|
}
|
|
2967
3316
|
|
|
3317
|
+
function normalizeLauncherRelaunchArgs(rawArgs, home) {
|
|
3318
|
+
const normalized = [];
|
|
3319
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
3320
|
+
const arg = rawArgs[index];
|
|
3321
|
+
if (arg === '--home') {
|
|
3322
|
+
index += 1;
|
|
3323
|
+
continue;
|
|
3324
|
+
}
|
|
3325
|
+
if (arg === '--here' || arg === '--skip-update-check') {
|
|
3326
|
+
continue;
|
|
3327
|
+
}
|
|
3328
|
+
normalized.push(arg);
|
|
3329
|
+
}
|
|
3330
|
+
return ['--home', home, ...normalized, '--skip-update-check'];
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
function relaunchLauncherAfterUpdate(rawArgs, home) {
|
|
3334
|
+
const launcherPath = resolveLauncherPath();
|
|
3335
|
+
if (!launcherPath) {
|
|
3336
|
+
return {
|
|
3337
|
+
ok: false,
|
|
3338
|
+
exitCode: 1,
|
|
3339
|
+
message: 'DeepScientist was updated, but the new launcher path could not be resolved for relaunch.',
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
const result = spawnSync(process.execPath, [launcherPath, ...normalizeLauncherRelaunchArgs(rawArgs, home)], {
|
|
3343
|
+
cwd: repoRoot,
|
|
3344
|
+
stdio: 'inherit',
|
|
3345
|
+
env: process.env,
|
|
3346
|
+
});
|
|
3347
|
+
if (result.error) {
|
|
3348
|
+
return {
|
|
3349
|
+
ok: false,
|
|
3350
|
+
exitCode: 1,
|
|
3351
|
+
message: result.error.message,
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
return {
|
|
3355
|
+
ok: true,
|
|
3356
|
+
exitCode: result.status ?? 0,
|
|
3357
|
+
message: null,
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
|
|
2968
3361
|
async function maybeHandleStartupUpdate(home, rawArgs, options = {}) {
|
|
2969
3362
|
if (options.skipUpdateCheck || process.env.DS_SKIP_UPDATE_PROMPT === '1') {
|
|
2970
3363
|
return false;
|
|
@@ -2978,8 +3371,43 @@ async function maybeHandleStartupUpdate(home, rawArgs, options = {}) {
|
|
|
2978
3371
|
}
|
|
2979
3372
|
|
|
2980
3373
|
printUpdateStatus(status, { compact: true });
|
|
2981
|
-
|
|
2982
|
-
|
|
3374
|
+
if (!status.can_self_update || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3375
|
+
markUpdateDeferred(home, status.latest_version);
|
|
3376
|
+
return false;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
const confirmed = await promptYesNo(`Install DeepScientist ${status.latest_version} now? [y/N]: `, {
|
|
3380
|
+
defaultValue: false,
|
|
3381
|
+
});
|
|
3382
|
+
if (!confirmed) {
|
|
3383
|
+
markUpdateDeferred(home, status.latest_version);
|
|
3384
|
+
console.log(`DeepScientist will remind you later about ${status.latest_version || 'the next release'}.`);
|
|
3385
|
+
return false;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
console.log('Updating DeepScientist now...');
|
|
3389
|
+
const payload = await performSelfUpdate(home, {
|
|
3390
|
+
host: options.host,
|
|
3391
|
+
port: options.port,
|
|
3392
|
+
restartDaemon: false,
|
|
3393
|
+
});
|
|
3394
|
+
console.log(payload.message);
|
|
3395
|
+
if (payload.log_path) {
|
|
3396
|
+
console.log(`Update log: ${payload.log_path}`);
|
|
3397
|
+
}
|
|
3398
|
+
if (!payload.ok) {
|
|
3399
|
+
console.log('DeepScientist will continue launching with the current session.');
|
|
3400
|
+
return false;
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
console.log('Relaunching DeepScientist...');
|
|
3404
|
+
const relaunch = relaunchLauncherAfterUpdate(rawArgs, home);
|
|
3405
|
+
if (!relaunch.ok) {
|
|
3406
|
+
console.error(relaunch.message);
|
|
3407
|
+
process.exit(relaunch.exitCode || 1);
|
|
3408
|
+
}
|
|
3409
|
+
process.exit(relaunch.exitCode || 0);
|
|
3410
|
+
return true;
|
|
2983
3411
|
}
|
|
2984
3412
|
|
|
2985
3413
|
async function startBackgroundUpdateWorker(home, options = {}) {
|
|
@@ -3133,61 +3561,36 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3133
3561
|
}
|
|
3134
3562
|
|
|
3135
3563
|
ensureNodeBundle('src/ui', 'dist/index.html');
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
ensureDir(path.dirname(logPath));
|
|
3139
|
-
const out = fs.openSync(logPath, 'a');
|
|
3140
|
-
const daemonId = crypto.randomUUID();
|
|
3141
|
-
const child = spawn(
|
|
3564
|
+
const startedProcess = spawnManagedDaemonProcess({
|
|
3565
|
+
home,
|
|
3142
3566
|
runtimePython,
|
|
3143
|
-
[
|
|
3144
|
-
'-m',
|
|
3145
|
-
'deepscientist.cli',
|
|
3146
|
-
'--home',
|
|
3147
|
-
home,
|
|
3148
|
-
...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
|
|
3149
|
-
'daemon',
|
|
3150
|
-
'--host',
|
|
3151
|
-
host,
|
|
3152
|
-
'--port',
|
|
3153
|
-
String(port),
|
|
3154
|
-
],
|
|
3155
|
-
{
|
|
3156
|
-
cwd: repoRoot,
|
|
3157
|
-
detached: true,
|
|
3158
|
-
stdio: ['ignore', out, out],
|
|
3159
|
-
env: {
|
|
3160
|
-
...process.env,
|
|
3161
|
-
...envOverrides,
|
|
3162
|
-
DEEPSCIENTIST_REPO_ROOT: repoRoot,
|
|
3163
|
-
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
3164
|
-
DEEPSCIENTIST_LAUNCHER_PATH: path.join(repoRoot, 'bin', 'ds.js'),
|
|
3165
|
-
DS_DAEMON_ID: daemonId,
|
|
3166
|
-
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
3167
|
-
},
|
|
3168
|
-
}
|
|
3169
|
-
);
|
|
3170
|
-
child.unref();
|
|
3171
|
-
const statePayload = {
|
|
3172
|
-
pid: child.pid,
|
|
3173
3567
|
host,
|
|
3174
3568
|
port,
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
home: normalizeHomePath(home),
|
|
3180
|
-
daemon_id: daemonId,
|
|
3181
|
-
};
|
|
3182
|
-
writeDaemonState(home, statePayload);
|
|
3569
|
+
proxy,
|
|
3570
|
+
envOverrides,
|
|
3571
|
+
});
|
|
3572
|
+
const logPath = startedProcess.logPath;
|
|
3183
3573
|
|
|
3184
3574
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
3185
3575
|
const health = await fetchHealth(browserUrl);
|
|
3186
3576
|
if (health && health.status === 'ok') {
|
|
3187
|
-
|
|
3188
|
-
|
|
3577
|
+
const liveState = readDaemonState(home);
|
|
3578
|
+
if (!healthMatchesManagedState({ health, state: liveState, home })) {
|
|
3579
|
+
console.error(daemonIdentityError({ url: browserUrl, home, health, state: liveState }));
|
|
3189
3580
|
process.exit(1);
|
|
3190
3581
|
}
|
|
3582
|
+
const supervisorPid = spawnDaemonSupervisor({
|
|
3583
|
+
home,
|
|
3584
|
+
runtimePython,
|
|
3585
|
+
host,
|
|
3586
|
+
port,
|
|
3587
|
+
proxy,
|
|
3588
|
+
envOverrides,
|
|
3589
|
+
daemonId: String((liveState || {}).daemon_id || ''),
|
|
3590
|
+
});
|
|
3591
|
+
if (supervisorPid) {
|
|
3592
|
+
appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
|
|
3593
|
+
}
|
|
3191
3594
|
return { url: browserUrl, bindUrl: daemonBindUrl, reused: false };
|
|
3192
3595
|
}
|
|
3193
3596
|
await sleep(250);
|
|
@@ -3244,14 +3647,34 @@ function handleCodexPreflightFailure(error) {
|
|
|
3244
3647
|
if (!error || error.code !== 'DS_CODEX_PREFLIGHT') {
|
|
3245
3648
|
return false;
|
|
3246
3649
|
}
|
|
3650
|
+
const errorLabel = colorize('\u001B[1;38;5;196m', 'ERROR');
|
|
3651
|
+
const warningLabel = colorize('\u001B[1;38;5;214m', 'WARNING');
|
|
3247
3652
|
console.error('');
|
|
3248
|
-
console.error(
|
|
3653
|
+
console.error(`${errorLabel} DeepScientist could not start because Codex is not ready yet.`);
|
|
3249
3654
|
console.error(`Report: ${error.reportPath}`);
|
|
3250
3655
|
if (Array.isArray(error.probe?.errors)) {
|
|
3251
3656
|
for (const item of error.probe.errors) {
|
|
3252
|
-
console.error(
|
|
3657
|
+
console.error(`${errorLabel} ${item}`);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
if (Array.isArray(error.probe?.warnings)) {
|
|
3661
|
+
for (const item of error.probe.warnings) {
|
|
3662
|
+
console.error(`${warningLabel} ${item}`);
|
|
3253
3663
|
}
|
|
3254
3664
|
}
|
|
3665
|
+
console.error(`${warningLabel} Recommended fix:`);
|
|
3666
|
+
const guidance = Array.isArray(error.probe?.guidance) && error.probe.guidance.length > 0
|
|
3667
|
+
? error.probe.guidance
|
|
3668
|
+
: [
|
|
3669
|
+
'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
|
|
3670
|
+
'If `codex` is still missing, run `npm install -g @openai/codex`.',
|
|
3671
|
+
'Run `codex --login` (or `codex`) and finish authentication.',
|
|
3672
|
+
'Run `ds doctor` and confirm the Codex check passes.',
|
|
3673
|
+
'Run `ds` again.',
|
|
3674
|
+
];
|
|
3675
|
+
guidance.forEach((item, index) => {
|
|
3676
|
+
console.error(`${warningLabel} ${index + 1}. ${item}`);
|
|
3677
|
+
});
|
|
3255
3678
|
openBrowser(error.reportUrl);
|
|
3256
3679
|
process.exit(1);
|
|
3257
3680
|
return true;
|
|
@@ -3375,7 +3798,29 @@ async function updateMain(rawArgs) {
|
|
|
3375
3798
|
process.exit(0);
|
|
3376
3799
|
}
|
|
3377
3800
|
printUpdateStatus(status, { compact: true });
|
|
3378
|
-
|
|
3801
|
+
if (!status.can_self_update) {
|
|
3802
|
+
process.exit(0);
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
const confirmed = await promptYesNo(`Install DeepScientist ${status.latest_version} now? [y/N]: `, {
|
|
3806
|
+
defaultValue: false,
|
|
3807
|
+
});
|
|
3808
|
+
if (!confirmed) {
|
|
3809
|
+
const payload = markUpdateDeferred(home, status.latest_version);
|
|
3810
|
+
console.log(`DeepScientist will remind you later about ${payload.latest_version || 'the next release'}.`);
|
|
3811
|
+
process.exit(0);
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
const payload = await performSelfUpdate(home, {
|
|
3815
|
+
host: options.host,
|
|
3816
|
+
port: options.port,
|
|
3817
|
+
restartDaemon: options.restartDaemon,
|
|
3818
|
+
});
|
|
3819
|
+
console.log(payload.message);
|
|
3820
|
+
if (payload.log_path) {
|
|
3821
|
+
console.log(`Update log: ${payload.log_path}`);
|
|
3822
|
+
}
|
|
3823
|
+
process.exit(payload.ok ? 0 : 1);
|
|
3379
3824
|
}
|
|
3380
3825
|
|
|
3381
3826
|
async function migrateMain(rawArgs) {
|
|
@@ -3557,7 +4002,7 @@ async function launcherMain(rawArgs) {
|
|
|
3557
4002
|
|
|
3558
4003
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
3559
4004
|
const runtimePython = pythonRuntime.runtimePython;
|
|
3560
|
-
const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo });
|
|
4005
|
+
const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo, profile: options.codexProfile });
|
|
3561
4006
|
ensureInitialized(home, runtimePython);
|
|
3562
4007
|
if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
|
|
3563
4008
|
return true;
|
|
@@ -3610,6 +4055,10 @@ async function launcherMain(rawArgs) {
|
|
|
3610
4055
|
|
|
3611
4056
|
async function main() {
|
|
3612
4057
|
const args = process.argv.slice(2);
|
|
4058
|
+
if (args[0] === '--daemon-supervisor') {
|
|
4059
|
+
await daemonSupervisorMain(args.slice(1));
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
3613
4062
|
const positional = findFirstPositionalArg(args);
|
|
3614
4063
|
if (positional && positional.value === 'update') {
|
|
3615
4064
|
await updateMain(args);
|
|
@@ -3631,7 +4080,10 @@ async function main() {
|
|
|
3631
4080
|
const home = resolveHome(args);
|
|
3632
4081
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
3633
4082
|
const runtimePython = pythonRuntime.runtimePython;
|
|
3634
|
-
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4083
|
+
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4084
|
+
yolo: args.includes('--yolo'),
|
|
4085
|
+
profile: readOptionValue(args, '--codex-profile'),
|
|
4086
|
+
});
|
|
3635
4087
|
if (positional.value === 'run' || positional.value === 'daemon') {
|
|
3636
4088
|
maybePrintOptionalLatexNotice(home);
|
|
3637
4089
|
}
|
|
@@ -3680,6 +4132,10 @@ module.exports = {
|
|
|
3680
4132
|
detectInstallMode,
|
|
3681
4133
|
updateManualCommand,
|
|
3682
4134
|
buildUpdateStatus,
|
|
4135
|
+
parseYesNoAnswer,
|
|
4136
|
+
normalizeLauncherRelaunchArgs,
|
|
4137
|
+
officialRepositoryLine,
|
|
4138
|
+
stripAnsi,
|
|
3683
4139
|
},
|
|
3684
4140
|
};
|
|
3685
4141
|
|