@researai/deepscientist 1.5.14 → 1.5.16
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 +336 -90
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +816 -131
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +11 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/README.md +24 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/zh/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/zh/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/zh/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/README.md +24 -0
- package/install.sh +2 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/charts.py +567 -0
- package/src/deepscientist/artifact/guidance.py +50 -10
- package/src/deepscientist/artifact/metrics.py +228 -5
- package/src/deepscientist/artifact/schemas.py +3 -0
- package/src/deepscientist/artifact/service.py +4276 -308
- package/src/deepscientist/bash_exec/models.py +23 -0
- package/src/deepscientist/bash_exec/monitor.py +147 -67
- package/src/deepscientist/bash_exec/runtime.py +218 -156
- package/src/deepscientist/bash_exec/service.py +309 -69
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +8 -4
- package/src/deepscientist/config/service.py +38 -11
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +199 -9
- package/src/deepscientist/daemon/api/router.py +5 -0
- package/src/deepscientist/daemon/app.py +1458 -289
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +296 -1
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +212 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +501 -453
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +2539 -195
- package/src/deepscientist/quest/stage_views.py +177 -1
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +169 -31
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +24 -4
- package/src/prompts/system.md +921 -72
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +32 -2
- package/src/skills/analysis-campaign/references/artifact-orchestration.md +1 -1
- package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +65 -0
- package/src/skills/baseline/SKILL.md +10 -0
- package/src/skills/decision/SKILL.md +27 -2
- package/src/skills/experiment/SKILL.md +16 -2
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +19 -0
- package/src/skills/idea/SKILL.md +79 -0
- package/src/skills/idea/references/idea-generation-playbook.md +100 -0
- package/src/skills/idea/references/outline-seeding-example.md +60 -0
- package/src/skills/intake-audit/SKILL.md +9 -1
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1645 -0
- package/src/skills/rebuttal/SKILL.md +3 -1
- package/src/skills/review/SKILL.md +3 -1
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +81 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +22 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
- package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
- package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
- package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
- package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
- package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
- package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
- package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
- package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
- package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
- package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
- package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
- package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
- package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
- package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
- package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DaF9Nge_.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-BSVx6dXE.js +0 -123
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
- package/src/ui/dist/assets/CodeEditorPlugin-DU9G0Tox.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-DoX_fI9l.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-C4FWIXuU.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-BgfFMgtf.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-tcPkfY_x.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-_dKV60Bf.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-Bje0ayoC.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CVsBzAln.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-xjmrqv_8.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-mMM2A8wP.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-3kVDSOBo.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/NotebookEditor-SoJ8X-MO.js +0 -84873
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfLoader-DElVuHl9.js +0 -25468
- package/src/ui/dist/assets/PdfMarkdownPlugin-Bq88XT4G.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-CsCXMo9S.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/SearchPlugin-oUPvy19k.js +0 -741
- package/src/ui/dist/assets/TextViewerPlugin-CRkT9yNy.js +0 -472
- package/src/ui/dist/assets/VNCViewer-BgbuvWhR.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-v_RASACv.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-5hC9d0VH.js +0 -17
- package/src/ui/dist/assets/file-content-D1PxfOrp.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DG1oT_Hj.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-BmdFYQlk.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-Dqe2X2tW.js +0 -18
- package/src/ui/dist/assets/index-BQG-1s2o.css +0 -12553
- package/src/ui/dist/assets/index-DVsMKK_y.js +0 -25
- package/src/ui/dist/assets/index-Duvz8Ip0.js +0 -159
- package/src/ui/dist/assets/index-Nt9hS4ck.js +0 -244829
- package/src/ui/dist/assets/index-RDlNXXx1.js +0 -120
- package/src/ui/dist/assets/monaco-DIXge1CP.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BBTTQaO-.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-BWlolyxo.js +0 -476
- package/src/ui/dist/assets/project-sync-BM5PkFH4.js +0 -297
- package/src/ui/dist/assets/select-D4dAtrA8.js +0 -1690
- package/src/ui/dist/assets/sigma-CKbE5jJT.js +0 -22
- package/src/ui/dist/assets/square-check-big-CZNGMgiB.js +0 -17
- package/src/ui/dist/assets/trash-DaB37xAz.js +0 -32
- package/src/ui/dist/assets/useCliAccess-C2OmAcWe.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-Dowd1Ij4.js +0 -53
- package/src/ui/dist/assets/wrap-text-BGjAhAUq.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-dMZQMXzc.js +0 -34
package/bin/ds.js
CHANGED
|
@@ -36,15 +36,16 @@ 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', '--codex-profile', '--codex']);
|
|
39
|
+
const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile', '--codex', '--auth']);
|
|
40
40
|
|
|
41
|
-
function buildCodexOverrideEnv({ yolo =
|
|
41
|
+
function buildCodexOverrideEnv({ yolo = true, profile = null, binary = null } = {}) {
|
|
42
42
|
const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
43
43
|
const normalizedBinary = typeof binary === 'string' ? binary.trim() : '';
|
|
44
44
|
const overrides = {};
|
|
45
45
|
if (normalizedBinary) {
|
|
46
46
|
overrides.DEEPSCIENTIST_CODEX_BINARY = normalizedBinary;
|
|
47
47
|
}
|
|
48
|
+
overrides.DEEPSCIENTIST_CODEX_YOLO = yolo ? '1' : '0';
|
|
48
49
|
if (!yolo) {
|
|
49
50
|
if (normalizedProfile) {
|
|
50
51
|
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
@@ -52,7 +53,6 @@ function buildCodexOverrideEnv({ yolo = false, profile = null, binary = null } =
|
|
|
52
53
|
}
|
|
53
54
|
return overrides;
|
|
54
55
|
}
|
|
55
|
-
overrides.DEEPSCIENTIST_CODEX_YOLO = '1';
|
|
56
56
|
if (normalizedProfile) {
|
|
57
57
|
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
58
58
|
overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
|
|
@@ -69,6 +69,68 @@ function readOptionValue(argv, optionName) {
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function parseBooleanFlagValue(rawValue) {
|
|
73
|
+
const normalized = String(rawValue || '').trim().toLowerCase();
|
|
74
|
+
if (!normalized) return null;
|
|
75
|
+
if (['1', 'true', 'yes', 'on', 'y'].includes(normalized)) return true;
|
|
76
|
+
if (['0', 'false', 'no', 'off', 'n'].includes(normalized)) return false;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseCodexCliVersion(text) {
|
|
81
|
+
const match = String(text || '').match(/codex-cli\s+(\d+)\.(\d+)\.(\d+)/i);
|
|
82
|
+
if (!match) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatCodexCliVersion(version) {
|
|
89
|
+
if (!Array.isArray(version) || version.length !== 3) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
return version.join('.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareCodexCliVersion(left, right) {
|
|
96
|
+
const leftParts = Array.isArray(left) ? left : [0, 0, 0];
|
|
97
|
+
const rightParts = Array.isArray(right) ? right : [0, 0, 0];
|
|
98
|
+
for (let index = 0; index < 3; index += 1) {
|
|
99
|
+
const delta = Number(leftParts[index] || 0) - Number(rightParts[index] || 0);
|
|
100
|
+
if (delta !== 0) {
|
|
101
|
+
return delta;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseYoloArg(args, index, currentValue = true) {
|
|
108
|
+
const arg = args[index];
|
|
109
|
+
if (arg === '--yolo') {
|
|
110
|
+
const parsed = parseBooleanFlagValue(args[index + 1]);
|
|
111
|
+
if (parsed === null) {
|
|
112
|
+
return { matched: true, value: true, consumed: 1 };
|
|
113
|
+
}
|
|
114
|
+
return { matched: true, value: parsed, consumed: 2 };
|
|
115
|
+
}
|
|
116
|
+
if (typeof arg === 'string' && arg.startsWith('--yolo=')) {
|
|
117
|
+
const parsed = parseBooleanFlagValue(arg.slice('--yolo='.length));
|
|
118
|
+
return { matched: true, value: parsed === null ? true : parsed, consumed: 1 };
|
|
119
|
+
}
|
|
120
|
+
return { matched: false, value: currentValue, consumed: 0 };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveYoloFlag(args, defaultValue = true) {
|
|
124
|
+
let value = defaultValue;
|
|
125
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
126
|
+
const parsed = parseYoloArg(args, index, value);
|
|
127
|
+
if (!parsed.matched) continue;
|
|
128
|
+
value = parsed.value;
|
|
129
|
+
index += Math.max(0, parsed.consumed - 1);
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
72
134
|
function printLauncherHelp() {
|
|
73
135
|
console.log(`DeepScientist launcher
|
|
74
136
|
|
|
@@ -95,6 +157,7 @@ Usage:
|
|
|
95
157
|
Launcher flags:
|
|
96
158
|
--host <host> Bind host for the local web daemon
|
|
97
159
|
--port <port> Bind port for the local web daemon
|
|
160
|
+
--auth [true|false] Require a 16-character local browser password. Default is false
|
|
98
161
|
--tui Start the terminal workspace only
|
|
99
162
|
--both Start web + terminal workspace together
|
|
100
163
|
--no-browser Do not auto-open the browser
|
|
@@ -105,7 +168,7 @@ Launcher flags:
|
|
|
105
168
|
--home <path> Use a custom DeepScientist home
|
|
106
169
|
--here Create/use ./DeepScientist under the current working directory as home
|
|
107
170
|
--proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
|
|
108
|
-
--yolo
|
|
171
|
+
--yolo [true|false] Control Codex YOLO mode. Default is true; pass false to restore on-request + workspace-write
|
|
109
172
|
--codex-profile <id> Run DeepScientist with a specific Codex profile, for example \`m27\`
|
|
110
173
|
--codex <path> Run DeepScientist with a specific Codex executable path for this launch
|
|
111
174
|
--quest-id <id> Open the TUI on one quest directly
|
|
@@ -157,6 +220,47 @@ function normalizeProxyUrl(rawValue) {
|
|
|
157
220
|
return value || null;
|
|
158
221
|
}
|
|
159
222
|
|
|
223
|
+
function normalizeLegacyHostFlagArgs(argv) {
|
|
224
|
+
const args = [];
|
|
225
|
+
let warned = false;
|
|
226
|
+
let legacyValue = null;
|
|
227
|
+
|
|
228
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
229
|
+
const arg = argv[index];
|
|
230
|
+
if (arg === '--ip') {
|
|
231
|
+
warned = true;
|
|
232
|
+
legacyValue = argv[index + 1] || legacyValue;
|
|
233
|
+
args.push('--host');
|
|
234
|
+
if (argv[index + 1]) {
|
|
235
|
+
args.push(argv[index + 1]);
|
|
236
|
+
index += 1;
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (typeof arg === 'string' && arg.startsWith('--ip=')) {
|
|
241
|
+
warned = true;
|
|
242
|
+
legacyValue = arg.slice('--ip='.length) || legacyValue;
|
|
243
|
+
args.push('--host', arg.slice('--ip='.length));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
args.push(arg);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!warned) {
|
|
250
|
+
return { args, warnings: [] };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const normalizedValue = String(legacyValue || '').trim();
|
|
254
|
+
const bindHint =
|
|
255
|
+
normalizedValue && ['0.0.0.0', '::', '[::]'].includes(normalizedValue)
|
|
256
|
+
? ' Note: bind-all addresses such as 0.0.0.0 are valid for `--host`, but local browser access still uses 127.0.0.1.'
|
|
257
|
+
: '';
|
|
258
|
+
return {
|
|
259
|
+
args,
|
|
260
|
+
warnings: [`Launcher note: \`--ip\` is deprecated. Use \`--host\` instead.${bindHint}`],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
160
264
|
function applyLauncherProxy(proxyUrl) {
|
|
161
265
|
const normalized = normalizeProxyUrl(proxyUrl);
|
|
162
266
|
if (!normalized) {
|
|
@@ -314,11 +418,11 @@ function fetchLatestPublishedVersion({ npmBinary, timeoutMs = 3500 }) {
|
|
|
314
418
|
latestVersion: null,
|
|
315
419
|
};
|
|
316
420
|
}
|
|
317
|
-
const result = spawnSync(npmBinary, ['view', UPDATE_PACKAGE_NAME, 'version', '--json'], {
|
|
421
|
+
const result = spawnSync(npmBinary, ['view', UPDATE_PACKAGE_NAME, 'version', '--json'], syncSpawnOptions({
|
|
318
422
|
encoding: 'utf8',
|
|
319
423
|
env: process.env,
|
|
320
424
|
timeout: timeoutMs,
|
|
321
|
-
});
|
|
425
|
+
}));
|
|
322
426
|
if (result.error) {
|
|
323
427
|
return {
|
|
324
428
|
ok: false,
|
|
@@ -527,6 +631,20 @@ function bindUiUrl(host, port) {
|
|
|
527
631
|
return `http://${formatHttpHost(normalized)}:${port}`;
|
|
528
632
|
}
|
|
529
633
|
|
|
634
|
+
function generateBrowserAuthToken() {
|
|
635
|
+
return crypto.randomBytes(8).toString('hex');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function appendBrowserAuthToken(url, authToken) {
|
|
639
|
+
const normalized = typeof authToken === 'string' ? authToken.trim() : '';
|
|
640
|
+
if (!normalized) {
|
|
641
|
+
return url;
|
|
642
|
+
}
|
|
643
|
+
const target = new URL(url);
|
|
644
|
+
target.searchParams.set('token', normalized);
|
|
645
|
+
return target.toString();
|
|
646
|
+
}
|
|
647
|
+
|
|
530
648
|
function normalizeMode(value) {
|
|
531
649
|
const normalized = String(value || '')
|
|
532
650
|
.trim()
|
|
@@ -553,6 +671,66 @@ function parseBooleanSetting(rawValue, fallback = false) {
|
|
|
553
671
|
return fallback;
|
|
554
672
|
}
|
|
555
673
|
|
|
674
|
+
function shouldCompileRuntimeBytecode() {
|
|
675
|
+
return parseBooleanSetting(process.env.DEEPSCIENTIST_RUNTIME_COMPILE_BYTECODE, false);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function readRequiredOptionValue(args, index, optionName) {
|
|
679
|
+
const value = args[index + 1];
|
|
680
|
+
if (!value || String(value).startsWith('--')) {
|
|
681
|
+
return {
|
|
682
|
+
ok: false,
|
|
683
|
+
error: `Missing value for ${optionName}.`,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
ok: true,
|
|
688
|
+
value,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function parseStrictBooleanOption(rawValue, optionName) {
|
|
693
|
+
const parsed = parseBooleanFlagValue(rawValue);
|
|
694
|
+
if (parsed === null) {
|
|
695
|
+
return {
|
|
696
|
+
ok: false,
|
|
697
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Use true or false.`,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
ok: true,
|
|
702
|
+
value: parsed,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function parseStrictPortOption(rawValue, optionName) {
|
|
707
|
+
const port = Number(rawValue);
|
|
708
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
709
|
+
return {
|
|
710
|
+
ok: false,
|
|
711
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Expected an integer between 1 and 65535.`,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
ok: true,
|
|
716
|
+
value: port,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function parseStrictModeOption(rawValue, optionName) {
|
|
721
|
+
const normalized = String(rawValue || '').trim().toLowerCase();
|
|
722
|
+
if (!['web', 'tui', 'both'].includes(normalized)) {
|
|
723
|
+
return {
|
|
724
|
+
ok: false,
|
|
725
|
+
error: `Invalid value for ${optionName}: ${rawValue}. Expected one of: web, tui, both.`,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
ok: true,
|
|
730
|
+
value: normalized,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
556
734
|
function supportsAnsi() {
|
|
557
735
|
return Boolean(process.stdout.isTTY && process.env.TERM !== 'dumb');
|
|
558
736
|
}
|
|
@@ -588,6 +766,64 @@ function colorize(code, text) {
|
|
|
588
766
|
return `${code}${text}\u001B[0m`;
|
|
589
767
|
}
|
|
590
768
|
|
|
769
|
+
function readCodexProviderMetadata(configDir, profile) {
|
|
770
|
+
const normalizedProfile = String(profile || '').trim();
|
|
771
|
+
const expandedDir = expandUserPath(configDir || path.join(os.homedir(), '.codex'));
|
|
772
|
+
const configPath = path.join(expandedDir, 'config.toml');
|
|
773
|
+
if (!normalizedProfile || !fs.existsSync(configPath)) {
|
|
774
|
+
return {
|
|
775
|
+
provider: null,
|
|
776
|
+
model: null,
|
|
777
|
+
envKey: null,
|
|
778
|
+
baseUrl: null,
|
|
779
|
+
wireApi: null,
|
|
780
|
+
requiresOpenAiAuth: null,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
const text = fs.readFileSync(configPath, 'utf8');
|
|
784
|
+
const profileBlock = text.match(new RegExp(`\\[profiles\\.${normalizedProfile.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\]([\\s\\S]*?)(?:\\n\\[|$)`));
|
|
785
|
+
const provider = profileBlock?.[1]?.match(/^\s*model_provider\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || text.match(/^\s*model_provider\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
786
|
+
const model = profileBlock?.[1]?.match(/^\s*model\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || text.match(/^\s*model\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
787
|
+
if (!provider) {
|
|
788
|
+
return {
|
|
789
|
+
provider: null,
|
|
790
|
+
model,
|
|
791
|
+
envKey: null,
|
|
792
|
+
baseUrl: null,
|
|
793
|
+
wireApi: null,
|
|
794
|
+
requiresOpenAiAuth: null,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const providerBlock = text.match(new RegExp(`\\[model_providers\\.${provider.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\]([\\s\\S]*?)(?:\\n\\[|$)`));
|
|
798
|
+
const providerText = providerBlock?.[1] || '';
|
|
799
|
+
const envKey = providerText.match(/^\s*env_key\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
800
|
+
const baseUrl = providerText.match(/^\s*base_url\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
801
|
+
const wireApi = providerText.match(/^\s*wire_api\s*=\s*["']([^"']+)["']/m)?.[1]?.trim() || null;
|
|
802
|
+
const requiresOpenAiAuthRaw = providerText.match(/^\s*requires_openai_auth\s*=\s*(true|false)\s*$/m)?.[1] || null;
|
|
803
|
+
const requiresOpenAiAuth = requiresOpenAiAuthRaw === null ? null : requiresOpenAiAuthRaw === 'true';
|
|
804
|
+
return {
|
|
805
|
+
provider,
|
|
806
|
+
model,
|
|
807
|
+
envKey,
|
|
808
|
+
baseUrl,
|
|
809
|
+
wireApi,
|
|
810
|
+
requiresOpenAiAuth,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function installedCodexCliVersion(binaryPath) {
|
|
815
|
+
const resolved = resolveExecutableOnPath(binaryPath || 'codex') || binaryPath || 'codex';
|
|
816
|
+
try {
|
|
817
|
+
const result = spawnSync(resolved, ['--version'], syncSpawnOptions({ encoding: 'utf8' }));
|
|
818
|
+
if (result.status !== 0) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
return parseCodexCliVersion(`${result.stdout || ''}\n${result.stderr || ''}`);
|
|
822
|
+
} catch {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
591
827
|
const OFFICIAL_REPOSITORY_URL = 'https://github.com/ResearAI/DeepScientist';
|
|
592
828
|
|
|
593
829
|
function officialRepositoryLine() {
|
|
@@ -606,7 +842,7 @@ function renderBrandArtwork() {
|
|
|
606
842
|
const result = spawnSync(
|
|
607
843
|
chafa,
|
|
608
844
|
['--size', `${width}x${height}`, '--format', 'symbols', '--colors', '16', brandPath],
|
|
609
|
-
{ encoding: 'utf8' }
|
|
845
|
+
syncSpawnOptions({ encoding: 'utf8' })
|
|
610
846
|
);
|
|
611
847
|
if (result.status === 0 && result.stdout && result.stdout.trim()) {
|
|
612
848
|
return result.stdout.replace(/\s+$/, '').split(/\r?\n/);
|
|
@@ -650,7 +886,7 @@ function pythonVersionText(probe) {
|
|
|
650
886
|
return version;
|
|
651
887
|
}
|
|
652
888
|
|
|
653
|
-
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
889
|
+
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken }) {
|
|
654
890
|
const runtimeRows = [
|
|
655
891
|
['Version', packageJson.version],
|
|
656
892
|
['Home', truncateMiddle(home)],
|
|
@@ -659,6 +895,9 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
659
895
|
['Python', truncateMiddle(pythonVersionText(pythonSelection))],
|
|
660
896
|
['Codex mode', yolo ? 'YOLO (never + danger-full-access)' : 'Default (on-request + workspace-write)'],
|
|
661
897
|
];
|
|
898
|
+
if (authEnabled && authToken) {
|
|
899
|
+
runtimeRows.splice(4, 0, ['Auth token', authToken]);
|
|
900
|
+
}
|
|
662
901
|
if (pythonSelection && pythonSelection.sourceLabel) {
|
|
663
902
|
runtimeRows.push(['Python source', pythonSelection.sourceLabel]);
|
|
664
903
|
}
|
|
@@ -671,6 +910,7 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
671
910
|
['ds --yolo --port 20999 --here', 'Start in ./DeepScientist under the current directory with YOLO Codex access'],
|
|
672
911
|
['ds --port 21000', 'Change the web port'],
|
|
673
912
|
['ds --host 0.0.0.0 --port 21000', 'Bind on all interfaces'],
|
|
913
|
+
['ds --auth true', 'Enable the local browser password for this launch'],
|
|
674
914
|
['ds --here', 'Use ./DeepScientist under the current directory as home'],
|
|
675
915
|
['ds --both', 'Start web + TUI together'],
|
|
676
916
|
['ds --tui', 'Start the terminal workspace only'],
|
|
@@ -694,6 +934,8 @@ function printLaunchCard({
|
|
|
694
934
|
home,
|
|
695
935
|
pythonSelection,
|
|
696
936
|
yolo,
|
|
937
|
+
authEnabled,
|
|
938
|
+
authToken,
|
|
697
939
|
}) {
|
|
698
940
|
const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
|
|
699
941
|
const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
|
|
@@ -752,13 +994,18 @@ function printLaunchCard({
|
|
|
752
994
|
console.log(centerText(colorize('\u001B[1m', workspaceMode), width));
|
|
753
995
|
console.log(centerText(urlLabel, width));
|
|
754
996
|
console.log(centerText(divider, width));
|
|
997
|
+
if (authEnabled && authToken) {
|
|
998
|
+
console.log('');
|
|
999
|
+
console.log(centerText(colorize('\u001B[1;38;5;214m', authToken), width));
|
|
1000
|
+
console.log('');
|
|
1001
|
+
}
|
|
755
1002
|
console.log(centerText(browserLine, width));
|
|
756
1003
|
console.log(centerText(nextStep, width));
|
|
757
1004
|
console.log(centerText('Run ds --stop to stop the managed daemon.', width));
|
|
758
1005
|
console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
|
|
759
1006
|
console.log(centerText(officialRepositoryLine(), width));
|
|
760
1007
|
console.log('');
|
|
761
|
-
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
|
|
1008
|
+
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken });
|
|
762
1009
|
}
|
|
763
1010
|
|
|
764
1011
|
function escapeHtml(value) {
|
|
@@ -984,7 +1231,8 @@ function parseLauncherArgs(argv) {
|
|
|
984
1231
|
let status = false;
|
|
985
1232
|
let daemonOnly = false;
|
|
986
1233
|
let skipUpdateCheck = false;
|
|
987
|
-
let yolo =
|
|
1234
|
+
let yolo = true;
|
|
1235
|
+
let auth = null;
|
|
988
1236
|
let codexProfile = null;
|
|
989
1237
|
let codexBinary = null;
|
|
990
1238
|
|
|
@@ -1005,17 +1253,72 @@ function parseLauncherArgs(argv) {
|
|
|
1005
1253
|
else if (arg === '--open-browser') openBrowser = true;
|
|
1006
1254
|
else if (arg === '--daemon-only') daemonOnly = true;
|
|
1007
1255
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1008
|
-
else if (arg === '--
|
|
1009
|
-
else
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1256
|
+
else if (arg === '--here') continue;
|
|
1257
|
+
else {
|
|
1258
|
+
const parsedYolo = parseYoloArg(args, index, yolo);
|
|
1259
|
+
if (parsedYolo.matched) {
|
|
1260
|
+
yolo = parsedYolo.value;
|
|
1261
|
+
index += Math.max(0, parsedYolo.consumed - 1);
|
|
1262
|
+
} else if (arg === '--auth') {
|
|
1263
|
+
const next = readRequiredOptionValue(args, index, '--auth');
|
|
1264
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1265
|
+
const parsed = parseStrictBooleanOption(next.value, '--auth');
|
|
1266
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1267
|
+
auth = parsed.value;
|
|
1268
|
+
index += 1;
|
|
1269
|
+
} else if (typeof arg === 'string' && arg.startsWith('--auth=')) {
|
|
1270
|
+
const parsed = parseStrictBooleanOption(arg.slice('--auth='.length), '--auth');
|
|
1271
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1272
|
+
auth = parsed.value;
|
|
1273
|
+
} else if (arg === '--codex-profile') {
|
|
1274
|
+
const next = readRequiredOptionValue(args, index, '--codex-profile');
|
|
1275
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1276
|
+
codexProfile = next.value;
|
|
1277
|
+
index += 1;
|
|
1278
|
+
} else if (arg === '--codex') {
|
|
1279
|
+
const next = readRequiredOptionValue(args, index, '--codex');
|
|
1280
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1281
|
+
codexBinary = next.value;
|
|
1282
|
+
index += 1;
|
|
1283
|
+
} else if (arg === '--host') {
|
|
1284
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
1285
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1286
|
+
host = next.value;
|
|
1287
|
+
index += 1;
|
|
1288
|
+
} else if (arg === '--port') {
|
|
1289
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
1290
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1291
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
1292
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1293
|
+
port = parsed.value;
|
|
1294
|
+
index += 1;
|
|
1295
|
+
} else if (arg === '--home') {
|
|
1296
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1297
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1298
|
+
home = path.resolve(next.value);
|
|
1299
|
+
index += 1;
|
|
1300
|
+
} else if (arg === '--proxy') {
|
|
1301
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
1302
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1303
|
+
proxy = next.value;
|
|
1304
|
+
index += 1;
|
|
1305
|
+
} else if (arg === '--quest-id') {
|
|
1306
|
+
const next = readRequiredOptionValue(args, index, '--quest-id');
|
|
1307
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1308
|
+
questId = next.value;
|
|
1309
|
+
index += 1;
|
|
1310
|
+
} else if (arg === '--mode') {
|
|
1311
|
+
const next = readRequiredOptionValue(args, index, '--mode');
|
|
1312
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1313
|
+
const parsed = parseStrictModeOption(next.value, '--mode');
|
|
1314
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1315
|
+
mode = parsed.value;
|
|
1316
|
+
index += 1;
|
|
1317
|
+
}
|
|
1318
|
+
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1319
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown launcher flag: ${arg}` };
|
|
1320
|
+
else return { help: false, error: `Unexpected launcher argument: ${arg}` };
|
|
1321
|
+
}
|
|
1019
1322
|
}
|
|
1020
1323
|
|
|
1021
1324
|
return {
|
|
@@ -1033,8 +1336,10 @@ function parseLauncherArgs(argv) {
|
|
|
1033
1336
|
daemonOnly,
|
|
1034
1337
|
skipUpdateCheck,
|
|
1035
1338
|
yolo,
|
|
1339
|
+
auth,
|
|
1036
1340
|
codexProfile,
|
|
1037
1341
|
codexBinary,
|
|
1342
|
+
error: null,
|
|
1038
1343
|
};
|
|
1039
1344
|
}
|
|
1040
1345
|
|
|
@@ -1108,12 +1413,32 @@ function parseUpdateArgs(argv) {
|
|
|
1108
1413
|
else if (arg === '--worker') worker = true;
|
|
1109
1414
|
else if (arg === '--restart-daemon') restartDaemon = true;
|
|
1110
1415
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1111
|
-
else if (arg === '--home'
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1416
|
+
else if (arg === '--home') {
|
|
1417
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1418
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1419
|
+
home = path.resolve(next.value);
|
|
1420
|
+
index += 1;
|
|
1421
|
+
} else if (arg === '--host') {
|
|
1422
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
1423
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1424
|
+
host = next.value;
|
|
1425
|
+
index += 1;
|
|
1426
|
+
} else if (arg === '--port') {
|
|
1427
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
1428
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1429
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
1430
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
1431
|
+
port = parsed.value;
|
|
1432
|
+
index += 1;
|
|
1433
|
+
} else if (arg === '--proxy') {
|
|
1434
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
1435
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1436
|
+
proxy = next.value;
|
|
1437
|
+
index += 1;
|
|
1438
|
+
}
|
|
1115
1439
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1116
|
-
else if (
|
|
1440
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown update flag: ${arg}` };
|
|
1441
|
+
else return { help: false, error: `Unexpected update argument: ${arg}` };
|
|
1117
1442
|
}
|
|
1118
1443
|
|
|
1119
1444
|
return {
|
|
@@ -1132,6 +1457,7 @@ function parseUpdateArgs(argv) {
|
|
|
1132
1457
|
proxy,
|
|
1133
1458
|
restartDaemon,
|
|
1134
1459
|
skipUpdateCheck,
|
|
1460
|
+
error: null,
|
|
1135
1461
|
};
|
|
1136
1462
|
}
|
|
1137
1463
|
|
|
@@ -1149,15 +1475,23 @@ function parseMigrateArgs(argv) {
|
|
|
1149
1475
|
const arg = args[index];
|
|
1150
1476
|
if (arg === '--yes') yes = true;
|
|
1151
1477
|
else if (arg === '--restart') restart = true;
|
|
1152
|
-
else if (arg === '--home'
|
|
1478
|
+
else if (arg === '--home') {
|
|
1479
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
1480
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
1481
|
+
home = path.resolve(expandUserPath(next.value));
|
|
1482
|
+
index += 1;
|
|
1483
|
+
}
|
|
1153
1484
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1154
|
-
else if (arg.startsWith('--')) return
|
|
1485
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown migrate flag: ${arg}` };
|
|
1155
1486
|
else if (!target) target = path.resolve(expandUserPath(arg));
|
|
1156
|
-
else return
|
|
1487
|
+
else return { help: false, error: `Unexpected migrate argument: ${arg}` };
|
|
1157
1488
|
}
|
|
1158
1489
|
|
|
1159
1490
|
if (!target) {
|
|
1160
|
-
return
|
|
1491
|
+
return {
|
|
1492
|
+
help: false,
|
|
1493
|
+
error: 'Missing migration target path.',
|
|
1494
|
+
};
|
|
1161
1495
|
}
|
|
1162
1496
|
|
|
1163
1497
|
return {
|
|
@@ -1166,12 +1500,18 @@ function parseMigrateArgs(argv) {
|
|
|
1166
1500
|
target,
|
|
1167
1501
|
yes,
|
|
1168
1502
|
restart,
|
|
1503
|
+
error: null,
|
|
1169
1504
|
};
|
|
1170
1505
|
}
|
|
1171
1506
|
|
|
1172
1507
|
function findFirstPositionalArg(args) {
|
|
1173
1508
|
for (let index = 0; index < args.length; index += 1) {
|
|
1174
1509
|
const arg = args[index];
|
|
1510
|
+
const parsedYolo = parseYoloArg(args, index);
|
|
1511
|
+
if (parsedYolo.matched) {
|
|
1512
|
+
index += Math.max(0, parsedYolo.consumed - 1);
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1175
1515
|
if (optionsWithValues.has(arg)) {
|
|
1176
1516
|
index += 1;
|
|
1177
1517
|
continue;
|
|
@@ -1465,11 +1805,14 @@ function scheduleDeferredSourceCleanup({ sourceHome, targetHome }) {
|
|
|
1465
1805
|
' }',
|
|
1466
1806
|
'})();',
|
|
1467
1807
|
].join('\n');
|
|
1468
|
-
const child = spawn(
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1808
|
+
const child = spawn(
|
|
1809
|
+
process.execPath,
|
|
1810
|
+
['-e', helperScript, String(process.pid), sourceHome, logPath],
|
|
1811
|
+
detachedSpawnOptions({
|
|
1812
|
+
stdio: 'ignore',
|
|
1813
|
+
env: process.env,
|
|
1814
|
+
})
|
|
1815
|
+
);
|
|
1473
1816
|
child.unref();
|
|
1474
1817
|
}
|
|
1475
1818
|
|
|
@@ -1554,10 +1897,10 @@ function probePython(binary) {
|
|
|
1554
1897
|
' "patch": sys.version_info[2],',
|
|
1555
1898
|
'}, ensure_ascii=False))',
|
|
1556
1899
|
].join('\n');
|
|
1557
|
-
const result = spawnSync(binary, ['-c', snippet], {
|
|
1900
|
+
const result = spawnSync(binary, ['-c', snippet], syncSpawnOptions({
|
|
1558
1901
|
encoding: 'utf8',
|
|
1559
1902
|
env: process.env,
|
|
1560
|
-
});
|
|
1903
|
+
}));
|
|
1561
1904
|
if (result.error) {
|
|
1562
1905
|
return {
|
|
1563
1906
|
ok: false,
|
|
@@ -1830,6 +2173,7 @@ function runSync(binary, args, options = {}) {
|
|
|
1830
2173
|
env: options.env || process.env,
|
|
1831
2174
|
encoding: 'utf8',
|
|
1832
2175
|
input: options.input,
|
|
2176
|
+
windowsHide: process.platform === 'win32',
|
|
1833
2177
|
});
|
|
1834
2178
|
if (result.error) {
|
|
1835
2179
|
throw result.error;
|
|
@@ -1847,12 +2191,30 @@ function step(index, total, message) {
|
|
|
1847
2191
|
console.log(`[${index}/${total}] ${message}`);
|
|
1848
2192
|
}
|
|
1849
2193
|
|
|
2194
|
+
function detachedSpawnOptions(options = {}) {
|
|
2195
|
+
return {
|
|
2196
|
+
...options,
|
|
2197
|
+
detached: true,
|
|
2198
|
+
windowsHide: process.platform === 'win32',
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
function syncSpawnOptions(options = {}) {
|
|
2203
|
+
return {
|
|
2204
|
+
...options,
|
|
2205
|
+
windowsHide: process.platform === 'win32',
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
|
|
1850
2209
|
function verifyPythonRuntime(runtimePython) {
|
|
1851
2210
|
const result = runSync(
|
|
1852
2211
|
runtimePython,
|
|
1853
2212
|
['-c', 'import deepscientist.cli; import cryptography; import _cffi_backend; print("ok")'],
|
|
1854
2213
|
{ capture: true, allowFailure: true }
|
|
1855
2214
|
);
|
|
2215
|
+
if (result.status !== 0 && result.stderr) {
|
|
2216
|
+
process.stderr.write(result.stderr);
|
|
2217
|
+
}
|
|
1856
2218
|
return result.status === 0;
|
|
1857
2219
|
}
|
|
1858
2220
|
|
|
@@ -1999,11 +2361,11 @@ function downloadFileWithNode(url, destinationPath) {
|
|
|
1999
2361
|
' process.exit(1);',
|
|
2000
2362
|
'});',
|
|
2001
2363
|
].join('\n');
|
|
2002
|
-
const result = spawnSync(process.execPath, ['-e', downloader, url, destinationPath, '45000'], {
|
|
2364
|
+
const result = spawnSync(process.execPath, ['-e', downloader, url, destinationPath, '45000'], syncSpawnOptions({
|
|
2003
2365
|
cwd: repoRoot,
|
|
2004
2366
|
stdio: 'inherit',
|
|
2005
2367
|
env: process.env,
|
|
2006
|
-
});
|
|
2368
|
+
}));
|
|
2007
2369
|
if (result.error) {
|
|
2008
2370
|
throw result.error;
|
|
2009
2371
|
}
|
|
@@ -2054,11 +2416,11 @@ function installLocalUv(home) {
|
|
|
2054
2416
|
shellArgs = [installerPath];
|
|
2055
2417
|
}
|
|
2056
2418
|
|
|
2057
|
-
const installResult = spawnSync(shellBinary, shellArgs, {
|
|
2419
|
+
const installResult = spawnSync(shellBinary, shellArgs, syncSpawnOptions({
|
|
2058
2420
|
cwd: repoRoot,
|
|
2059
2421
|
stdio: 'inherit',
|
|
2060
2422
|
env: installEnv,
|
|
2061
|
-
});
|
|
2423
|
+
}));
|
|
2062
2424
|
if (installResult.error) {
|
|
2063
2425
|
throw installResult.error;
|
|
2064
2426
|
}
|
|
@@ -2085,13 +2447,17 @@ function ensureUvBinary(home) {
|
|
|
2085
2447
|
}
|
|
2086
2448
|
|
|
2087
2449
|
function buildUvRuntimeEnv(home, extraEnv = {}) {
|
|
2088
|
-
|
|
2450
|
+
const env = {
|
|
2089
2451
|
...process.env,
|
|
2090
2452
|
UV_CACHE_DIR: runtimeUvCachePath(home),
|
|
2091
2453
|
UV_PROJECT_ENVIRONMENT: runtimePythonEnvPath(home),
|
|
2092
2454
|
UV_PYTHON_INSTALL_DIR: runtimeUvPythonInstallPath(home),
|
|
2093
2455
|
...extraEnv,
|
|
2094
2456
|
};
|
|
2457
|
+
for (const key of ['PYTHONPATH', 'PYTHONHOME', 'VIRTUAL_ENV', '__PYVENV_LAUNCHER__']) {
|
|
2458
|
+
delete env[key];
|
|
2459
|
+
}
|
|
2460
|
+
return env;
|
|
2095
2461
|
}
|
|
2096
2462
|
|
|
2097
2463
|
function ensureUvLockPresent() {
|
|
@@ -2104,6 +2470,43 @@ function ensureUvLockPresent() {
|
|
|
2104
2470
|
process.exit(1);
|
|
2105
2471
|
}
|
|
2106
2472
|
|
|
2473
|
+
function buildUvSyncFailureGuidance({ installMode = detectInstallMode(repoRoot), env = process.env } = {}) {
|
|
2474
|
+
const guidance = [];
|
|
2475
|
+
if (installMode === 'source-checkout') {
|
|
2476
|
+
guidance.push('If you changed Python dependencies in a source checkout, run `uv lock` and try again.');
|
|
2477
|
+
} else {
|
|
2478
|
+
guidance.push('This npm install already includes a locked `uv.lock`, so this is usually a local Python or network environment issue rather than a missing lockfile.');
|
|
2479
|
+
guidance.push('Re-run `ds` in a clean shell first. If you have an active conda or virtualenv, try deactivating it before starting DeepScientist.');
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
const hasPythonEnv =
|
|
2483
|
+
Boolean(String(env.VIRTUAL_ENV || '').trim())
|
|
2484
|
+
|| Boolean(String(env.CONDA_PREFIX || '').trim())
|
|
2485
|
+
|| Boolean(String(env.PYTHONPATH || '').trim())
|
|
2486
|
+
|| Boolean(String(env.PYTHONHOME || '').trim());
|
|
2487
|
+
if (hasPythonEnv) {
|
|
2488
|
+
guidance.push('An active Python environment was detected. `VIRTUAL_ENV`, `CONDA_PREFIX`, `PYTHONPATH`, or `PYTHONHOME` can interfere with uv runtime bootstrap.');
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
const hasCustomIndex =
|
|
2492
|
+
Object.keys(env).some((key) => /^PIP_/i.test(key))
|
|
2493
|
+
|| Boolean(String(env.UV_INDEX_URL || '').trim())
|
|
2494
|
+
|| Boolean(String(env.UV_EXTRA_INDEX_URL || '').trim());
|
|
2495
|
+
if (hasCustomIndex) {
|
|
2496
|
+
guidance.push('Custom package index settings were detected. Check `PIP_*`, `UV_INDEX_URL`, or `UV_EXTRA_INDEX_URL` if uv could not download packages.');
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
const hasProxyOrCert =
|
|
2500
|
+
['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'http_proxy', 'https_proxy', 'all_proxy', 'SSL_CERT_FILE', 'REQUESTS_CA_BUNDLE']
|
|
2501
|
+
.some((key) => Boolean(String(env[key] || '').trim()));
|
|
2502
|
+
if (hasProxyOrCert) {
|
|
2503
|
+
guidance.push('Proxy or certificate overrides were detected. If uv reported TLS, certificate, or download errors above, verify those settings and try again.');
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
guidance.push('Look at the uv error printed above this message. That original uv output is the real failure reason.');
|
|
2507
|
+
return guidance;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2107
2510
|
function resolveUvVersion(uvBinary) {
|
|
2108
2511
|
const result = runSync(uvBinary, ['--version'], { capture: true, allowFailure: true });
|
|
2109
2512
|
if (result.status !== 0) {
|
|
@@ -2155,8 +2558,25 @@ function ensureUvManagedPython(home, uvBinary, minimumVersionRequest) {
|
|
|
2155
2558
|
return probe;
|
|
2156
2559
|
}
|
|
2157
2560
|
|
|
2561
|
+
function resolveBackgroundPythonExecutable(runtimePython) {
|
|
2562
|
+
const normalized = String(runtimePython || '').trim();
|
|
2563
|
+
if (process.platform !== 'win32' || !normalized) {
|
|
2564
|
+
return normalized;
|
|
2565
|
+
}
|
|
2566
|
+
const runtimePath = path.resolve(normalized);
|
|
2567
|
+
const runtimeDir = path.dirname(runtimePath);
|
|
2568
|
+
const pythonwCandidate = path.join(runtimeDir, 'pythonw.exe');
|
|
2569
|
+
if (fs.existsSync(pythonwCandidate)) {
|
|
2570
|
+
return pythonwCandidate;
|
|
2571
|
+
}
|
|
2572
|
+
return runtimePath;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2158
2575
|
function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
2159
|
-
const args = ['sync', '--frozen', '--no-dev', '--
|
|
2576
|
+
const args = ['sync', '--frozen', '--no-dev', '--python', pythonTarget];
|
|
2577
|
+
if (shouldCompileRuntimeBytecode()) {
|
|
2578
|
+
args.splice(3, 0, '--compile-bytecode');
|
|
2579
|
+
}
|
|
2160
2580
|
if (!editable) {
|
|
2161
2581
|
args.push('--no-editable');
|
|
2162
2582
|
}
|
|
@@ -2169,7 +2589,9 @@ function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
|
2169
2589
|
return;
|
|
2170
2590
|
}
|
|
2171
2591
|
console.error('DeepScientist could not sync the locked Python environment with uv.');
|
|
2172
|
-
|
|
2592
|
+
for (const line of buildUvSyncFailureGuidance()) {
|
|
2593
|
+
console.error(line);
|
|
2594
|
+
}
|
|
2173
2595
|
process.exit(result.status ?? 1);
|
|
2174
2596
|
}
|
|
2175
2597
|
|
|
@@ -2323,6 +2745,13 @@ function normalizePythonCliArgs(args, home) {
|
|
|
2323
2745
|
continue;
|
|
2324
2746
|
}
|
|
2325
2747
|
if (arg === '--yolo') {
|
|
2748
|
+
const parsed = parseBooleanFlagValue(args[index + 1]);
|
|
2749
|
+
if (parsed !== null) {
|
|
2750
|
+
index += 1;
|
|
2751
|
+
}
|
|
2752
|
+
continue;
|
|
2753
|
+
}
|
|
2754
|
+
if (typeof arg === 'string' && arg.startsWith('--yolo=')) {
|
|
2326
2755
|
continue;
|
|
2327
2756
|
}
|
|
2328
2757
|
if (arg === '--codex-profile') {
|
|
@@ -2590,16 +3019,33 @@ function decodeSupervisorEnvPayload(rawValue) {
|
|
|
2590
3019
|
}
|
|
2591
3020
|
}
|
|
2592
3021
|
|
|
2593
|
-
function spawnManagedDaemonProcess({
|
|
3022
|
+
function spawnManagedDaemonProcess({
|
|
3023
|
+
home,
|
|
3024
|
+
runtimePython,
|
|
3025
|
+
host,
|
|
3026
|
+
port,
|
|
3027
|
+
proxy = null,
|
|
3028
|
+
envOverrides = {},
|
|
3029
|
+
daemonId = null,
|
|
3030
|
+
authEnabled = false,
|
|
3031
|
+
authToken = null,
|
|
3032
|
+
}) {
|
|
2594
3033
|
const browserUrl = browserUiUrl(host, port);
|
|
2595
3034
|
const daemonBindUrl = bindUiUrl(host, port);
|
|
3035
|
+
const resolvedAuthEnabled = authEnabled !== false;
|
|
3036
|
+
const resolvedAuthToken = resolvedAuthEnabled
|
|
3037
|
+
? (typeof authToken === 'string' && authToken.trim() ? authToken.trim() : generateBrowserAuthToken())
|
|
3038
|
+
: null;
|
|
3039
|
+
const launchUrl = browserUrl;
|
|
3040
|
+
const bindLaunchUrl = daemonBindUrl;
|
|
2596
3041
|
const logPath = path.join(home, 'logs', 'daemon.log');
|
|
2597
3042
|
ensureDir(path.dirname(logPath));
|
|
2598
3043
|
const out = fs.openSync(logPath, 'a');
|
|
2599
3044
|
const resolvedDaemonId = String(daemonId || crypto.randomUUID()).trim();
|
|
2600
3045
|
const launcherPath = path.join(repoRoot, 'bin', 'ds.js');
|
|
3046
|
+
const backgroundPython = resolveBackgroundPythonExecutable(runtimePython);
|
|
2601
3047
|
const child = spawn(
|
|
2602
|
-
|
|
3048
|
+
backgroundPython,
|
|
2603
3049
|
[
|
|
2604
3050
|
'-m',
|
|
2605
3051
|
'deepscientist.cli',
|
|
@@ -2611,10 +3057,12 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2611
3057
|
host,
|
|
2612
3058
|
'--port',
|
|
2613
3059
|
String(port),
|
|
3060
|
+
'--auth',
|
|
3061
|
+
resolvedAuthEnabled ? 'true' : 'false',
|
|
3062
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? ['--auth-token', resolvedAuthToken] : []),
|
|
2614
3063
|
],
|
|
2615
|
-
{
|
|
3064
|
+
detachedSpawnOptions({
|
|
2616
3065
|
cwd: repoRoot,
|
|
2617
|
-
detached: true,
|
|
2618
3066
|
stdio: ['ignore', out, out],
|
|
2619
3067
|
env: {
|
|
2620
3068
|
...process.env,
|
|
@@ -2624,8 +3072,10 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2624
3072
|
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2625
3073
|
DS_DAEMON_ID: resolvedDaemonId,
|
|
2626
3074
|
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
3075
|
+
DS_UI_AUTH_ENABLED: resolvedAuthEnabled ? '1' : '0',
|
|
3076
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? { DS_UI_AUTH_TOKEN: resolvedAuthToken } : {}),
|
|
2627
3077
|
},
|
|
2628
|
-
}
|
|
3078
|
+
})
|
|
2629
3079
|
);
|
|
2630
3080
|
child.unref();
|
|
2631
3081
|
const statePayload = {
|
|
@@ -2634,10 +3084,14 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2634
3084
|
port,
|
|
2635
3085
|
url: browserUrl,
|
|
2636
3086
|
bind_url: daemonBindUrl,
|
|
3087
|
+
launch_url: launchUrl,
|
|
3088
|
+
bind_launch_url: bindLaunchUrl,
|
|
2637
3089
|
log_path: logPath,
|
|
2638
3090
|
started_at: new Date().toISOString(),
|
|
2639
3091
|
home: normalizeHomePath(home),
|
|
2640
3092
|
daemon_id: resolvedDaemonId,
|
|
3093
|
+
auth_enabled: resolvedAuthEnabled,
|
|
3094
|
+
auth_token: resolvedAuthToken,
|
|
2641
3095
|
};
|
|
2642
3096
|
writeDaemonState(home, statePayload);
|
|
2643
3097
|
return {
|
|
@@ -2645,6 +3099,8 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2645
3099
|
statePayload,
|
|
2646
3100
|
browserUrl,
|
|
2647
3101
|
bindUrl: daemonBindUrl,
|
|
3102
|
+
launchUrl,
|
|
3103
|
+
bindLaunchUrl,
|
|
2648
3104
|
logPath,
|
|
2649
3105
|
};
|
|
2650
3106
|
}
|
|
@@ -2673,9 +3129,8 @@ function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null,
|
|
|
2673
3129
|
if (envPayload) {
|
|
2674
3130
|
args.push('--env-json', envPayload);
|
|
2675
3131
|
}
|
|
2676
|
-
const child = spawn(process.execPath, args, {
|
|
3132
|
+
const child = spawn(process.execPath, args, detachedSpawnOptions({
|
|
2677
3133
|
cwd: repoRoot,
|
|
2678
|
-
detached: true,
|
|
2679
3134
|
stdio: 'ignore',
|
|
2680
3135
|
env: {
|
|
2681
3136
|
...process.env,
|
|
@@ -2683,7 +3138,7 @@ function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null,
|
|
|
2683
3138
|
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
2684
3139
|
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2685
3140
|
},
|
|
2686
|
-
});
|
|
3141
|
+
}));
|
|
2687
3142
|
child.unref();
|
|
2688
3143
|
return child.pid || null;
|
|
2689
3144
|
}
|
|
@@ -2700,19 +3155,54 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2700
3155
|
|
|
2701
3156
|
for (let index = 0; index < args.length; index += 1) {
|
|
2702
3157
|
const arg = args[index];
|
|
2703
|
-
if (arg === '--home'
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
else if (arg === '--
|
|
2709
|
-
|
|
3158
|
+
if (arg === '--home') {
|
|
3159
|
+
const next = readRequiredOptionValue(args, index, '--home');
|
|
3160
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3161
|
+
home = path.resolve(next.value);
|
|
3162
|
+
index += 1;
|
|
3163
|
+
} else if (arg === '--runtime-python') {
|
|
3164
|
+
const next = readRequiredOptionValue(args, index, '--runtime-python');
|
|
3165
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3166
|
+
runtimePython = next.value;
|
|
3167
|
+
index += 1;
|
|
3168
|
+
} else if (arg === '--host') {
|
|
3169
|
+
const next = readRequiredOptionValue(args, index, '--host');
|
|
3170
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3171
|
+
host = next.value;
|
|
3172
|
+
index += 1;
|
|
3173
|
+
} else if (arg === '--port') {
|
|
3174
|
+
const next = readRequiredOptionValue(args, index, '--port');
|
|
3175
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3176
|
+
const parsed = parseStrictPortOption(next.value, '--port');
|
|
3177
|
+
if (!parsed.ok) return { help: false, error: parsed.error };
|
|
3178
|
+
port = parsed.value;
|
|
3179
|
+
index += 1;
|
|
3180
|
+
} else if (arg === '--proxy') {
|
|
3181
|
+
const next = readRequiredOptionValue(args, index, '--proxy');
|
|
3182
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3183
|
+
proxy = next.value;
|
|
3184
|
+
index += 1;
|
|
3185
|
+
} else if (arg === '--daemon-id') {
|
|
3186
|
+
const next = readRequiredOptionValue(args, index, '--daemon-id');
|
|
3187
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3188
|
+
daemonId = next.value;
|
|
3189
|
+
index += 1;
|
|
3190
|
+
} else if (arg === '--env-json') {
|
|
3191
|
+
const next = readRequiredOptionValue(args, index, '--env-json');
|
|
3192
|
+
if (!next.ok) return { help: false, error: next.error };
|
|
3193
|
+
envJson = next.value;
|
|
3194
|
+
index += 1;
|
|
3195
|
+
}
|
|
2710
3196
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
2711
|
-
else return
|
|
3197
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown daemon supervisor flag: ${arg}` };
|
|
3198
|
+
else return { help: false, error: `Unexpected daemon supervisor argument: ${arg}` };
|
|
2712
3199
|
}
|
|
2713
3200
|
|
|
2714
3201
|
if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
|
|
2715
|
-
return
|
|
3202
|
+
return {
|
|
3203
|
+
help: false,
|
|
3204
|
+
error: 'Daemon supervisor requires --home, --runtime-python, --daemon-id, and a valid --port.',
|
|
3205
|
+
};
|
|
2716
3206
|
}
|
|
2717
3207
|
|
|
2718
3208
|
return {
|
|
@@ -2724,18 +3214,19 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2724
3214
|
proxy,
|
|
2725
3215
|
daemonId,
|
|
2726
3216
|
envOverrides: decodeSupervisorEnvPayload(envJson),
|
|
3217
|
+
error: null,
|
|
2727
3218
|
};
|
|
2728
3219
|
}
|
|
2729
3220
|
|
|
2730
3221
|
async function daemonSupervisorMain(rawArgs) {
|
|
2731
3222
|
const options = parseDaemonSupervisorArgs(rawArgs);
|
|
2732
|
-
if (!options) {
|
|
2733
|
-
console.error('Invalid daemon supervisor arguments.');
|
|
2734
|
-
process.exit(1);
|
|
2735
|
-
}
|
|
2736
3223
|
if (options.help) {
|
|
2737
3224
|
process.exit(0);
|
|
2738
3225
|
}
|
|
3226
|
+
if (options.error) {
|
|
3227
|
+
console.error(options.error);
|
|
3228
|
+
process.exit(1);
|
|
3229
|
+
}
|
|
2739
3230
|
|
|
2740
3231
|
const home = options.home;
|
|
2741
3232
|
let trackedDaemonId = String(options.daemonId || '').trim();
|
|
@@ -2762,7 +3253,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2762
3253
|
appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
|
|
2763
3254
|
return;
|
|
2764
3255
|
}
|
|
2765
|
-
const
|
|
3256
|
+
const authToken = typeof state.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
3257
|
+
const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port), authToken);
|
|
2766
3258
|
if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
|
|
2767
3259
|
restartBackoffMs = 1000;
|
|
2768
3260
|
await sleep(2500);
|
|
@@ -2785,6 +3277,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2785
3277
|
port: options.port,
|
|
2786
3278
|
proxy: options.proxy,
|
|
2787
3279
|
envOverrides: options.envOverrides,
|
|
3280
|
+
authEnabled: state.auth_enabled !== false,
|
|
3281
|
+
authToken,
|
|
2788
3282
|
});
|
|
2789
3283
|
trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
|
|
2790
3284
|
observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
|
|
@@ -2809,14 +3303,19 @@ function sleep(ms) {
|
|
|
2809
3303
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2810
3304
|
}
|
|
2811
3305
|
|
|
2812
|
-
async function isHealthy(url) {
|
|
2813
|
-
const payload = await fetchHealth(url);
|
|
3306
|
+
async function isHealthy(url, authToken = null) {
|
|
3307
|
+
const payload = await fetchHealth(url, authToken);
|
|
2814
3308
|
return Boolean(payload && payload.status === 'ok');
|
|
2815
3309
|
}
|
|
2816
3310
|
|
|
2817
|
-
async function fetchHealth(url) {
|
|
3311
|
+
async function fetchHealth(url, authToken = null) {
|
|
2818
3312
|
try {
|
|
2819
|
-
const
|
|
3313
|
+
const headers = {};
|
|
3314
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3315
|
+
if (normalizedAuthToken) {
|
|
3316
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3317
|
+
}
|
|
3318
|
+
const response = await fetch(`${url}/api/health`, { headers });
|
|
2820
3319
|
if (!response.ok) {
|
|
2821
3320
|
return null;
|
|
2822
3321
|
}
|
|
@@ -2866,13 +3365,18 @@ function daemonIdentityError({ url, home, health, state }) {
|
|
|
2866
3365
|
].join('\n');
|
|
2867
3366
|
}
|
|
2868
3367
|
|
|
2869
|
-
async function requestDaemonShutdown(url, daemonId) {
|
|
3368
|
+
async function requestDaemonShutdown(url, daemonId, authToken = null) {
|
|
2870
3369
|
try {
|
|
3370
|
+
const headers = {
|
|
3371
|
+
'Content-Type': 'application/json',
|
|
3372
|
+
};
|
|
3373
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3374
|
+
if (normalizedAuthToken) {
|
|
3375
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3376
|
+
}
|
|
2871
3377
|
const response = await fetch(`${url}/api/admin/shutdown`, {
|
|
2872
3378
|
method: 'POST',
|
|
2873
|
-
headers
|
|
2874
|
-
'Content-Type': 'application/json',
|
|
2875
|
-
},
|
|
3379
|
+
headers,
|
|
2876
3380
|
body: JSON.stringify({ source: 'ds-launcher', daemon_id: daemonId || null }),
|
|
2877
3381
|
});
|
|
2878
3382
|
if (!response.ok) {
|
|
@@ -2885,6 +3389,31 @@ async function requestDaemonShutdown(url, daemonId) {
|
|
|
2885
3389
|
}
|
|
2886
3390
|
}
|
|
2887
3391
|
|
|
3392
|
+
async function requestDaemonAuthRotate(url, authToken = null) {
|
|
3393
|
+
try {
|
|
3394
|
+
const headers = {
|
|
3395
|
+
'Content-Type': 'application/json',
|
|
3396
|
+
};
|
|
3397
|
+
const normalizedAuthToken = typeof authToken === 'string' ? authToken.trim() : '';
|
|
3398
|
+
if (normalizedAuthToken) {
|
|
3399
|
+
headers.Authorization = `Bearer ${normalizedAuthToken}`;
|
|
3400
|
+
}
|
|
3401
|
+
const response = await fetch(`${url}/api/auth/rotate`, {
|
|
3402
|
+
method: 'POST',
|
|
3403
|
+
headers,
|
|
3404
|
+
body: '{}',
|
|
3405
|
+
});
|
|
3406
|
+
if (!response.ok) {
|
|
3407
|
+
return null;
|
|
3408
|
+
}
|
|
3409
|
+
const payload = await response.json().catch(() => ({}));
|
|
3410
|
+
const token = typeof payload?.token === 'string' ? payload.token.trim() : '';
|
|
3411
|
+
return token || null;
|
|
3412
|
+
} catch {
|
|
3413
|
+
return null;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
|
|
2888
3417
|
function isPidAlive(pid) {
|
|
2889
3418
|
if (!pid) return false;
|
|
2890
3419
|
try {
|
|
@@ -2902,7 +3431,7 @@ function killManagedProcess(pid, signal) {
|
|
|
2902
3431
|
if (signal === 'SIGKILL') {
|
|
2903
3432
|
taskkillArgs.push('/T', '/F');
|
|
2904
3433
|
}
|
|
2905
|
-
const result = spawnSync('taskkill', taskkillArgs, { stdio: 'ignore' });
|
|
3434
|
+
const result = spawnSync('taskkill', taskkillArgs, syncSpawnOptions({ stdio: 'ignore' }));
|
|
2906
3435
|
return result.status === 0;
|
|
2907
3436
|
}
|
|
2908
3437
|
try {
|
|
@@ -2918,9 +3447,9 @@ function killManagedProcess(pid, signal) {
|
|
|
2918
3447
|
}
|
|
2919
3448
|
}
|
|
2920
3449
|
|
|
2921
|
-
async function waitForDaemonStop({ url, pid, attempts = 20, delayMs = 200 }) {
|
|
3450
|
+
async function waitForDaemonStop({ url, pid, authToken = null, attempts = 20, delayMs = 200 }) {
|
|
2922
3451
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2923
|
-
const healthy = url ? await isHealthy(url) : false;
|
|
3452
|
+
const healthy = url ? await isHealthy(url, authToken) : false;
|
|
2924
3453
|
const alive = pid ? isPidAlive(pid) : false;
|
|
2925
3454
|
if (!healthy && !alive) {
|
|
2926
3455
|
return true;
|
|
@@ -2945,7 +3474,8 @@ async function stopDaemon(home) {
|
|
|
2945
3474
|
const state = readDaemonState(home);
|
|
2946
3475
|
const configured = readConfiguredUiAddressFromFile(home);
|
|
2947
3476
|
const url = state?.url || browserUiUrl(state?.host || configured.host, state?.port || configured.port);
|
|
2948
|
-
const
|
|
3477
|
+
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
3478
|
+
const healthBefore = await fetchHealth(url, authToken);
|
|
2949
3479
|
const healthyBefore = Boolean(healthBefore && healthBefore.status === 'ok');
|
|
2950
3480
|
const sameHomeHealthy = healthMatchesHome({ health: healthBefore, home });
|
|
2951
3481
|
const pid = state?.pid || (sameHomeHealthy ? healthBefore?.pid : null);
|
|
@@ -2986,21 +3516,21 @@ async function stopDaemon(home) {
|
|
|
2986
3516
|
let stopped = false;
|
|
2987
3517
|
|
|
2988
3518
|
if (healthyBefore) {
|
|
2989
|
-
await requestDaemonShutdown(url, shutdownDaemonId || null);
|
|
2990
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 20, delayMs: 200 });
|
|
3519
|
+
await requestDaemonShutdown(url, shutdownDaemonId || null, authToken);
|
|
3520
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 20, delayMs: 200 });
|
|
2991
3521
|
}
|
|
2992
3522
|
|
|
2993
3523
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
2994
3524
|
killManagedProcess(pid, 'SIGTERM');
|
|
2995
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 30, delayMs: 200 });
|
|
3525
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 30, delayMs: 200 });
|
|
2996
3526
|
}
|
|
2997
3527
|
|
|
2998
3528
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
2999
3529
|
killManagedProcess(pid, 'SIGKILL');
|
|
3000
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 20, delayMs: 150 });
|
|
3530
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 20, delayMs: 150 });
|
|
3001
3531
|
}
|
|
3002
3532
|
|
|
3003
|
-
const stillHealthy = await isHealthy(url);
|
|
3533
|
+
const stillHealthy = await isHealthy(url, authToken);
|
|
3004
3534
|
if (!stopped && (stillHealthy || (pid && isPidAlive(pid)))) {
|
|
3005
3535
|
console.error('DeepScientist daemon is still running after shutdown attempts.');
|
|
3006
3536
|
process.exit(1);
|
|
@@ -3034,11 +3564,11 @@ function summarizeUpdateFailure(result) {
|
|
|
3034
3564
|
function runNpmInstallLatest(home, npmBinary) {
|
|
3035
3565
|
const args = ['install', '-g', `${UPDATE_PACKAGE_NAME}@latest`, '--no-audit', '--no-fund'];
|
|
3036
3566
|
const startedAt = new Date().toISOString();
|
|
3037
|
-
const result = spawnSync(npmBinary, args, {
|
|
3567
|
+
const result = spawnSync(npmBinary, args, syncSpawnOptions({
|
|
3038
3568
|
encoding: 'utf8',
|
|
3039
3569
|
env: process.env,
|
|
3040
3570
|
timeout: 15 * 60 * 1000,
|
|
3041
|
-
});
|
|
3571
|
+
}));
|
|
3042
3572
|
const finishedAt = new Date().toISOString();
|
|
3043
3573
|
const logPath = writeUpdateLog(
|
|
3044
3574
|
home,
|
|
@@ -3132,12 +3662,11 @@ async function promptYesNo(question, { defaultValue = false } = {}) {
|
|
|
3132
3662
|
|
|
3133
3663
|
function spawnDetachedNode(args, options = {}) {
|
|
3134
3664
|
const out = options.logPath ? fs.openSync(options.logPath, 'a') : 'ignore';
|
|
3135
|
-
const child = spawn(process.execPath, args, {
|
|
3665
|
+
const child = spawn(process.execPath, args, detachedSpawnOptions({
|
|
3136
3666
|
cwd: options.cwd || repoRoot,
|
|
3137
|
-
detached: true,
|
|
3138
3667
|
stdio: ['ignore', out, out],
|
|
3139
3668
|
env: options.env || process.env,
|
|
3140
|
-
});
|
|
3669
|
+
}));
|
|
3141
3670
|
child.unref();
|
|
3142
3671
|
return child;
|
|
3143
3672
|
}
|
|
@@ -3276,19 +3805,23 @@ async function performSelfUpdate(home, options = {}) {
|
|
|
3276
3805
|
log_path: installResult.logPath,
|
|
3277
3806
|
};
|
|
3278
3807
|
}
|
|
3808
|
+
const restartArgs = [
|
|
3809
|
+
launcherPath,
|
|
3810
|
+
'--home',
|
|
3811
|
+
home,
|
|
3812
|
+
'--host',
|
|
3813
|
+
String(host),
|
|
3814
|
+
'--port',
|
|
3815
|
+
String(port),
|
|
3816
|
+
'--daemon-only',
|
|
3817
|
+
'--no-browser',
|
|
3818
|
+
'--skip-update-check',
|
|
3819
|
+
];
|
|
3820
|
+
if (daemonState && daemonState.auth_enabled === false) {
|
|
3821
|
+
restartArgs.push('--auth', 'false');
|
|
3822
|
+
}
|
|
3279
3823
|
spawnDetachedNode(
|
|
3280
|
-
|
|
3281
|
-
launcherPath,
|
|
3282
|
-
'--home',
|
|
3283
|
-
home,
|
|
3284
|
-
'--host',
|
|
3285
|
-
String(host),
|
|
3286
|
-
'--port',
|
|
3287
|
-
String(port),
|
|
3288
|
-
'--daemon-only',
|
|
3289
|
-
'--no-browser',
|
|
3290
|
-
'--skip-update-check',
|
|
3291
|
-
],
|
|
3824
|
+
restartArgs,
|
|
3292
3825
|
{
|
|
3293
3826
|
cwd: repoRoot,
|
|
3294
3827
|
env: process.env,
|
|
@@ -3351,11 +3884,11 @@ function relaunchLauncherAfterUpdate(rawArgs, home) {
|
|
|
3351
3884
|
message: 'DeepScientist was updated, but the new launcher path could not be resolved for relaunch.',
|
|
3352
3885
|
};
|
|
3353
3886
|
}
|
|
3354
|
-
const result = spawnSync(process.execPath, [launcherPath, ...normalizeLauncherRelaunchArgs(rawArgs, home)], {
|
|
3887
|
+
const result = spawnSync(process.execPath, [launcherPath, ...normalizeLauncherRelaunchArgs(rawArgs, home)], syncSpawnOptions({
|
|
3355
3888
|
cwd: repoRoot,
|
|
3356
3889
|
stdio: 'inherit',
|
|
3357
3890
|
env: process.env,
|
|
3358
|
-
});
|
|
3891
|
+
}));
|
|
3359
3892
|
if (result.error) {
|
|
3360
3893
|
return {
|
|
3361
3894
|
ok: false,
|
|
@@ -3483,17 +4016,88 @@ async function startBackgroundUpdateWorker(home, options = {}) {
|
|
|
3483
4016
|
};
|
|
3484
4017
|
}
|
|
3485
4018
|
|
|
4019
|
+
async function maybeHandleMiniMaxCodexVersion(home, runtimePython, options = {}) {
|
|
4020
|
+
const configuredRunners = (() => {
|
|
4021
|
+
try {
|
|
4022
|
+
const result = runPythonCli(runtimePython, ['--home', home, 'config', 'show', 'runners'], {
|
|
4023
|
+
capture: true,
|
|
4024
|
+
allowFailure: true,
|
|
4025
|
+
});
|
|
4026
|
+
return String(result.stdout || '');
|
|
4027
|
+
} catch {
|
|
4028
|
+
return '';
|
|
4029
|
+
}
|
|
4030
|
+
})();
|
|
4031
|
+
const profileFromConfig =
|
|
4032
|
+
configuredRunners.match(/^\s*profile:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || '';
|
|
4033
|
+
const binaryFromConfig =
|
|
4034
|
+
configuredRunners.match(/^\s*binary:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || 'codex';
|
|
4035
|
+
const configDirFromConfig =
|
|
4036
|
+
configuredRunners.match(/^\s*config_dir:\s*["']?([^"'\n]+)["']?\s*$/m)?.[1]?.trim() || '~/.codex';
|
|
4037
|
+
|
|
4038
|
+
const effectiveProfile = String(options.codexProfile || profileFromConfig || '').trim();
|
|
4039
|
+
if (!effectiveProfile) {
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
const metadata = readCodexProviderMetadata(configDirFromConfig, effectiveProfile);
|
|
4043
|
+
if (String(metadata.provider || '').trim().toLowerCase() !== 'minimax') {
|
|
4044
|
+
return false;
|
|
4045
|
+
}
|
|
4046
|
+
const version = installedCodexCliVersion(options.codexBinary || binaryFromConfig || 'codex');
|
|
4047
|
+
const expected = [0, 57, 0];
|
|
4048
|
+
if (!version || compareCodexCliVersion(version, expected) === 0) {
|
|
4049
|
+
return false;
|
|
4050
|
+
}
|
|
4051
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4052
|
+
console.log(
|
|
4053
|
+
`MiniMax profile \`${effectiveProfile}\` is configured, but installed Codex CLI is ${formatCodexCliVersion(version)}. MiniMax currently requires Codex CLI 0.57.0 for the documented path.`
|
|
4054
|
+
);
|
|
4055
|
+
console.log('Install it manually with `npm install -g @openai/codex@0.57.0` before continuing.');
|
|
4056
|
+
return false;
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
console.log('');
|
|
4060
|
+
console.log(colorize('\u001B[1;38;5;214m', 'MiniMax compatibility check'));
|
|
4061
|
+
console.log(
|
|
4062
|
+
`DeepScientist detected MiniMax profile \`${effectiveProfile}\`, but installed Codex CLI is ${formatCodexCliVersion(version)}.`
|
|
4063
|
+
);
|
|
4064
|
+
console.log('MiniMax currently requires Codex CLI 0.57.0 for the documented DeepScientist path.');
|
|
4065
|
+
const confirmed = await promptYesNo('Reinstall Codex CLI to 0.57.0 now? [y/N]: ', {
|
|
4066
|
+
defaultValue: false,
|
|
4067
|
+
});
|
|
4068
|
+
if (!confirmed) {
|
|
4069
|
+
return false;
|
|
4070
|
+
}
|
|
4071
|
+
const npmBinary = resolveNpmBinary();
|
|
4072
|
+
if (!npmBinary) {
|
|
4073
|
+
console.error('`npm` is unavailable; cannot reinstall Codex CLI automatically.');
|
|
4074
|
+
process.exit(1);
|
|
4075
|
+
}
|
|
4076
|
+
const result = spawnSync(
|
|
4077
|
+
npmBinary,
|
|
4078
|
+
['install', '-g', '@openai/codex@0.57.0'],
|
|
4079
|
+
syncSpawnOptions({ stdio: 'inherit' })
|
|
4080
|
+
);
|
|
4081
|
+
if (result.status !== 0) {
|
|
4082
|
+
console.error('Failed to reinstall Codex CLI 0.57.0 automatically.');
|
|
4083
|
+
process.exit(result.status ?? 1);
|
|
4084
|
+
}
|
|
4085
|
+
return true;
|
|
4086
|
+
}
|
|
4087
|
+
|
|
3486
4088
|
async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallbackPort) {
|
|
3487
4089
|
try {
|
|
3488
4090
|
const result = runPythonCli(runtimePython, ['--home', home, 'config', 'show', 'config'], { capture: true, allowFailure: true });
|
|
3489
4091
|
const text = result.stdout || '';
|
|
3490
4092
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3491
4093
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4094
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3492
4095
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3493
4096
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3494
4097
|
return {
|
|
3495
4098
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3496
4099
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4100
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3497
4101
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3498
4102
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3499
4103
|
};
|
|
@@ -3501,6 +4105,7 @@ async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallba
|
|
|
3501
4105
|
return {
|
|
3502
4106
|
host: fallbackHost || '0.0.0.0',
|
|
3503
4107
|
port: fallbackPort || 20999,
|
|
4108
|
+
authEnabled: false,
|
|
3504
4109
|
defaultMode: 'web',
|
|
3505
4110
|
autoOpenBrowser: true,
|
|
3506
4111
|
};
|
|
@@ -3513,6 +4118,7 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3513
4118
|
return {
|
|
3514
4119
|
host: fallbackHost || '0.0.0.0',
|
|
3515
4120
|
port: fallbackPort || 20999,
|
|
4121
|
+
authEnabled: false,
|
|
3516
4122
|
defaultMode: 'web',
|
|
3517
4123
|
autoOpenBrowser: true,
|
|
3518
4124
|
};
|
|
@@ -3521,11 +4127,13 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3521
4127
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
3522
4128
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3523
4129
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4130
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3524
4131
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3525
4132
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3526
4133
|
return {
|
|
3527
4134
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3528
4135
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4136
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3529
4137
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3530
4138
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3531
4139
|
};
|
|
@@ -3533,20 +4141,51 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3533
4141
|
return {
|
|
3534
4142
|
host: fallbackHost || '0.0.0.0',
|
|
3535
4143
|
port: fallbackPort || 20999,
|
|
4144
|
+
authEnabled: false,
|
|
3536
4145
|
defaultMode: 'web',
|
|
3537
4146
|
autoOpenBrowser: true,
|
|
3538
4147
|
};
|
|
3539
4148
|
}
|
|
3540
4149
|
}
|
|
3541
4150
|
|
|
3542
|
-
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}) {
|
|
4151
|
+
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}, authEnabled = false) {
|
|
3543
4152
|
const browserUrl = browserUiUrl(host, port);
|
|
3544
4153
|
const daemonBindUrl = bindUiUrl(host, port);
|
|
3545
4154
|
const state = readDaemonState(home);
|
|
3546
|
-
const
|
|
4155
|
+
const desiredAuthToken = authEnabled ? generateBrowserAuthToken() : null;
|
|
4156
|
+
const launchUrl = browserUrl;
|
|
4157
|
+
const bindLaunchUrl = daemonBindUrl;
|
|
4158
|
+
const existingHealth = await fetchHealth(browserUrl, typeof state?.auth_token === 'string' ? state.auth_token.trim() : '');
|
|
3547
4159
|
if (existingHealth && existingHealth.status === 'ok') {
|
|
3548
4160
|
if (state && healthMatchesManagedState({ health: existingHealth, state, home })) {
|
|
3549
|
-
|
|
4161
|
+
const stateAuthEnabled = state.auth_enabled !== false;
|
|
4162
|
+
const stateAuthToken = typeof state.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
4163
|
+
let resolvedAuthToken = stateAuthToken || null;
|
|
4164
|
+
if (stateAuthEnabled) {
|
|
4165
|
+
const rotatedAuthToken = await requestDaemonAuthRotate(browserUrl, stateAuthToken);
|
|
4166
|
+
if (!rotatedAuthToken) {
|
|
4167
|
+
console.error('Managed daemon is healthy, but the browser auth token could not be rotated.');
|
|
4168
|
+
console.error('Restart the daemon with `ds --restart` if this keeps happening.');
|
|
4169
|
+
process.exit(1);
|
|
4170
|
+
}
|
|
4171
|
+
resolvedAuthToken = rotatedAuthToken;
|
|
4172
|
+
writeDaemonState(home, {
|
|
4173
|
+
...state,
|
|
4174
|
+
auth_enabled: true,
|
|
4175
|
+
auth_token: rotatedAuthToken,
|
|
4176
|
+
url: browserUrl,
|
|
4177
|
+
bind_url: daemonBindUrl,
|
|
4178
|
+
launch_url: launchUrl,
|
|
4179
|
+
bind_launch_url: bindLaunchUrl,
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
return {
|
|
4183
|
+
url: browserUrl,
|
|
4184
|
+
bindUrl: daemonBindUrl,
|
|
4185
|
+
reused: true,
|
|
4186
|
+
authEnabled: stateAuthEnabled,
|
|
4187
|
+
authToken: resolvedAuthToken,
|
|
4188
|
+
};
|
|
3550
4189
|
}
|
|
3551
4190
|
console.error(
|
|
3552
4191
|
state
|
|
@@ -3580,11 +4219,13 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3580
4219
|
port,
|
|
3581
4220
|
proxy,
|
|
3582
4221
|
envOverrides,
|
|
4222
|
+
authEnabled,
|
|
4223
|
+
authToken: desiredAuthToken,
|
|
3583
4224
|
});
|
|
3584
4225
|
const logPath = startedProcess.logPath;
|
|
3585
4226
|
|
|
3586
4227
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
3587
|
-
const health = await fetchHealth(browserUrl);
|
|
4228
|
+
const health = await fetchHealth(browserUrl, desiredAuthToken);
|
|
3588
4229
|
if (health && health.status === 'ok') {
|
|
3589
4230
|
const liveState = readDaemonState(home);
|
|
3590
4231
|
if (!healthMatchesManagedState({ health, state: liveState, home })) {
|
|
@@ -3603,12 +4244,26 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3603
4244
|
if (supervisorPid) {
|
|
3604
4245
|
appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
|
|
3605
4246
|
}
|
|
3606
|
-
return {
|
|
4247
|
+
return {
|
|
4248
|
+
url: launchUrl,
|
|
4249
|
+
bindUrl: bindLaunchUrl,
|
|
4250
|
+
reused: false,
|
|
4251
|
+
authEnabled,
|
|
4252
|
+
authToken: desiredAuthToken,
|
|
4253
|
+
};
|
|
3607
4254
|
}
|
|
3608
4255
|
await sleep(250);
|
|
3609
4256
|
}
|
|
3610
4257
|
|
|
3611
4258
|
console.error('DeepScientist daemon failed to become healthy.');
|
|
4259
|
+
console.error(`Expected local URL: ${launchUrl}`);
|
|
4260
|
+
console.error(`Daemon bind URL: ${bindLaunchUrl}`);
|
|
4261
|
+
if (authEnabled && desiredAuthToken) {
|
|
4262
|
+
console.error(`Auth token: ${desiredAuthToken}`);
|
|
4263
|
+
}
|
|
4264
|
+
if (['0.0.0.0', '::', '[::]'].includes(String(host || '').trim())) {
|
|
4265
|
+
console.error(`Hint: ${String(host || '').trim() || '0.0.0.0'} is a bind address. Local browser and health probes use ${browserUrl}.`);
|
|
4266
|
+
}
|
|
3612
4267
|
const logTail = tailLog(logPath);
|
|
3613
4268
|
if (logTail) {
|
|
3614
4269
|
console.error(logTail);
|
|
@@ -3619,7 +4274,7 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3619
4274
|
function openBrowser(url) {
|
|
3620
4275
|
const spawnDetached = (command, args) => {
|
|
3621
4276
|
try {
|
|
3622
|
-
const child = spawn(command, args, {
|
|
4277
|
+
const child = spawn(command, args, detachedSpawnOptions({ stdio: 'ignore' }));
|
|
3623
4278
|
child.unref();
|
|
3624
4279
|
return true;
|
|
3625
4280
|
} catch {
|
|
@@ -3692,12 +4347,15 @@ function handleCodexPreflightFailure(error) {
|
|
|
3692
4347
|
return true;
|
|
3693
4348
|
}
|
|
3694
4349
|
|
|
3695
|
-
function launchTui(url, questId, home, runtimePython) {
|
|
4350
|
+
function launchTui(url, questId, home, runtimePython, authToken = null) {
|
|
3696
4351
|
const entry = ensureNodeBundle('src/tui', 'dist/index.js');
|
|
3697
4352
|
const args = [entry, '--base-url', url];
|
|
3698
4353
|
if (questId) {
|
|
3699
4354
|
args.push('--quest-id', questId);
|
|
3700
4355
|
}
|
|
4356
|
+
if (typeof authToken === 'string' && authToken.trim()) {
|
|
4357
|
+
args.push('--auth-token', authToken.trim());
|
|
4358
|
+
}
|
|
3701
4359
|
const child = spawn(process.execPath, args, {
|
|
3702
4360
|
cwd: repoRoot,
|
|
3703
4361
|
stdio: 'inherit',
|
|
@@ -3715,14 +4373,15 @@ function launchTui(url, questId, home, runtimePython) {
|
|
|
3715
4373
|
|
|
3716
4374
|
async function updateMain(rawArgs) {
|
|
3717
4375
|
const options = parseUpdateArgs(rawArgs);
|
|
3718
|
-
if (!options) {
|
|
3719
|
-
printUpdateHelp();
|
|
3720
|
-
process.exit(1);
|
|
3721
|
-
}
|
|
3722
4376
|
if (options.help) {
|
|
3723
4377
|
printUpdateHelp();
|
|
3724
4378
|
process.exit(0);
|
|
3725
4379
|
}
|
|
4380
|
+
if (options.error) {
|
|
4381
|
+
console.error(options.error);
|
|
4382
|
+
console.error('Run `ds update --help` for update usage.');
|
|
4383
|
+
process.exit(1);
|
|
4384
|
+
}
|
|
3726
4385
|
|
|
3727
4386
|
const home = options.home || resolveHome(rawArgs);
|
|
3728
4387
|
applyLauncherProxy(options.proxy);
|
|
@@ -3837,14 +4496,15 @@ async function updateMain(rawArgs) {
|
|
|
3837
4496
|
|
|
3838
4497
|
async function migrateMain(rawArgs) {
|
|
3839
4498
|
const options = parseMigrateArgs(rawArgs);
|
|
3840
|
-
if (!options) {
|
|
3841
|
-
printMigrateHelp();
|
|
3842
|
-
process.exit(1);
|
|
3843
|
-
}
|
|
3844
4499
|
if (options.help) {
|
|
3845
4500
|
printMigrateHelp();
|
|
3846
4501
|
process.exit(0);
|
|
3847
4502
|
}
|
|
4503
|
+
if (options.error) {
|
|
4504
|
+
console.error(options.error);
|
|
4505
|
+
console.error('Run `ds migrate --help` for migration usage.');
|
|
4506
|
+
process.exit(1);
|
|
4507
|
+
}
|
|
3848
4508
|
|
|
3849
4509
|
const sourceHome = realpathOrSelf(options.home || resolveHome(rawArgs));
|
|
3850
4510
|
const targetHome = path.resolve(options.target);
|
|
@@ -3933,12 +4593,11 @@ async function migrateMain(rawArgs) {
|
|
|
3933
4593
|
const child = spawn(
|
|
3934
4594
|
process.execPath,
|
|
3935
4595
|
[migratedLauncher, '--home', targetHome, '--daemon-only', '--no-browser', '--skip-update-check'],
|
|
3936
|
-
{
|
|
4596
|
+
detachedSpawnOptions({
|
|
3937
4597
|
cwd: path.join(targetHome, 'cli'),
|
|
3938
|
-
detached: true,
|
|
3939
4598
|
stdio: 'ignore',
|
|
3940
4599
|
env: process.env,
|
|
3941
|
-
}
|
|
4600
|
+
})
|
|
3942
4601
|
);
|
|
3943
4602
|
child.unref();
|
|
3944
4603
|
restartMessage = 'Managed daemon restart scheduled from the migrated home.';
|
|
@@ -3966,13 +4625,15 @@ async function migrateMain(rawArgs) {
|
|
|
3966
4625
|
|
|
3967
4626
|
async function launcherMain(rawArgs) {
|
|
3968
4627
|
const options = parseLauncherArgs(rawArgs);
|
|
3969
|
-
if (!options) {
|
|
3970
|
-
return false;
|
|
3971
|
-
}
|
|
3972
4628
|
if (options.help) {
|
|
3973
4629
|
printLauncherHelp();
|
|
3974
4630
|
process.exit(0);
|
|
3975
4631
|
}
|
|
4632
|
+
if (options.error) {
|
|
4633
|
+
console.error(options.error);
|
|
4634
|
+
console.error('Run `ds --help` for launcher usage.');
|
|
4635
|
+
process.exit(1);
|
|
4636
|
+
}
|
|
3976
4637
|
|
|
3977
4638
|
const home = options.home || resolveHome(rawArgs);
|
|
3978
4639
|
applyLauncherProxy(options.proxy);
|
|
@@ -3990,8 +4651,10 @@ async function launcherMain(rawArgs) {
|
|
|
3990
4651
|
if (options.status) {
|
|
3991
4652
|
const state = readDaemonState(home);
|
|
3992
4653
|
const configured = readConfiguredUiAddressFromFile(home, options.host, options.port);
|
|
3993
|
-
const url = state?.url || browserUiUrl(configured.host, configured.port);
|
|
3994
|
-
const
|
|
4654
|
+
const url = state?.launch_url || state?.url || browserUiUrl(configured.host, configured.port);
|
|
4655
|
+
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
4656
|
+
const probeUrl = state?.url || browserUiUrl(configured.host, configured.port);
|
|
4657
|
+
const health = await fetchHealth(probeUrl, authToken);
|
|
3995
4658
|
const healthy = Boolean(health && health.status === 'ok');
|
|
3996
4659
|
const identityMatch = state ? healthMatchesManagedState({ health, state, home }) : false;
|
|
3997
4660
|
console.log(
|
|
@@ -4020,6 +4683,7 @@ async function launcherMain(rawArgs) {
|
|
|
4020
4683
|
binary: options.codexBinary,
|
|
4021
4684
|
});
|
|
4022
4685
|
ensureInitialized(home, runtimePython);
|
|
4686
|
+
await maybeHandleMiniMaxCodexVersion(home, runtimePython, options);
|
|
4023
4687
|
if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
|
|
4024
4688
|
return true;
|
|
4025
4689
|
}
|
|
@@ -4028,20 +4692,27 @@ async function launcherMain(rawArgs) {
|
|
|
4028
4692
|
const configuredUi = await readConfiguredUiAddress(home, runtimePython, options.host, options.port);
|
|
4029
4693
|
const host = configuredUi.host;
|
|
4030
4694
|
const port = configuredUi.port;
|
|
4695
|
+
const authEnabled = options.auth === null ? false : options.auth !== false;
|
|
4031
4696
|
const mode = normalizeMode(options.mode ?? 'web');
|
|
4032
4697
|
const shouldOpenBrowser = options.daemonOnly
|
|
4033
4698
|
? false
|
|
4034
4699
|
: options.openBrowser === null
|
|
4035
4700
|
? configuredUi.autoOpenBrowser !== false && mode !== 'tui'
|
|
4036
4701
|
: options.openBrowser;
|
|
4037
|
-
|
|
4702
|
+
const existingState = readDaemonState(home);
|
|
4703
|
+
const existingAuthEnabled = existingState ? existingState.auth_enabled !== false : null;
|
|
4704
|
+
const existingAuthToken = typeof existingState?.auth_token === 'string' ? existingState.auth_token.trim() : '';
|
|
4705
|
+
const authStateMismatch = existingState && (
|
|
4706
|
+
existingAuthEnabled !== authEnabled || (authEnabled && !existingAuthToken)
|
|
4707
|
+
);
|
|
4708
|
+
if (options.restart || authStateMismatch) {
|
|
4038
4709
|
await stopDaemon(home);
|
|
4039
4710
|
}
|
|
4040
4711
|
|
|
4041
4712
|
step(4, 4, 'Starting local daemon and UI surfaces');
|
|
4042
4713
|
let started;
|
|
4043
4714
|
try {
|
|
4044
|
-
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv);
|
|
4715
|
+
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv, authEnabled);
|
|
4045
4716
|
} catch (error) {
|
|
4046
4717
|
if (handleCodexPreflightFailure(error)) return true;
|
|
4047
4718
|
throw error;
|
|
@@ -4057,6 +4728,8 @@ async function launcherMain(rawArgs) {
|
|
|
4057
4728
|
home,
|
|
4058
4729
|
pythonSelection: pythonRuntime.runtimeProbe,
|
|
4059
4730
|
yolo: options.yolo,
|
|
4731
|
+
authEnabled: started.authEnabled,
|
|
4732
|
+
authToken: started.authToken,
|
|
4060
4733
|
});
|
|
4061
4734
|
|
|
4062
4735
|
if (options.daemonOnly) {
|
|
@@ -4065,12 +4738,16 @@ async function launcherMain(rawArgs) {
|
|
|
4065
4738
|
if (mode === 'web') {
|
|
4066
4739
|
process.exit(0);
|
|
4067
4740
|
}
|
|
4068
|
-
launchTui(
|
|
4741
|
+
launchTui(browserUiUrl(host, port), options.questId, home, runtimePython, started.authToken);
|
|
4069
4742
|
return true;
|
|
4070
4743
|
}
|
|
4071
4744
|
|
|
4072
4745
|
async function main() {
|
|
4073
|
-
const
|
|
4746
|
+
const normalizedArgState = normalizeLegacyHostFlagArgs(process.argv.slice(2));
|
|
4747
|
+
const args = normalizedArgState.args;
|
|
4748
|
+
for (const warning of normalizedArgState.warnings) {
|
|
4749
|
+
console.warn(warning);
|
|
4750
|
+
}
|
|
4074
4751
|
if (args[0] === '--daemon-supervisor') {
|
|
4075
4752
|
await daemonSupervisorMain(args.slice(1));
|
|
4076
4753
|
return;
|
|
@@ -4084,7 +4761,11 @@ async function main() {
|
|
|
4084
4761
|
await migrateMain(args);
|
|
4085
4762
|
return;
|
|
4086
4763
|
}
|
|
4087
|
-
if (
|
|
4764
|
+
if (
|
|
4765
|
+
args.length === 0
|
|
4766
|
+
|| args[0] === 'ui'
|
|
4767
|
+
|| (args[0]?.startsWith('--') && (!positional || !pythonCommands.has(positional.value)))
|
|
4768
|
+
) {
|
|
4088
4769
|
await launcherMain(args);
|
|
4089
4770
|
return;
|
|
4090
4771
|
}
|
|
@@ -4097,7 +4778,7 @@ async function main() {
|
|
|
4097
4778
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
4098
4779
|
const runtimePython = pythonRuntime.runtimePython;
|
|
4099
4780
|
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4100
|
-
yolo: args
|
|
4781
|
+
yolo: resolveYoloFlag(args, true),
|
|
4101
4782
|
profile: readOptionValue(args, '--codex-profile'),
|
|
4102
4783
|
binary: readOptionValue(args, '--codex'),
|
|
4103
4784
|
});
|
|
@@ -4140,6 +4821,8 @@ module.exports = {
|
|
|
4140
4821
|
resolveUvBinary,
|
|
4141
4822
|
resolveHome,
|
|
4142
4823
|
parseLauncherArgs,
|
|
4824
|
+
generateBrowserAuthToken,
|
|
4825
|
+
appendBrowserAuthToken,
|
|
4143
4826
|
normalizeProxyUrl,
|
|
4144
4827
|
parseMigrateArgs,
|
|
4145
4828
|
parseLegacyWrapperCandidate,
|
|
@@ -4147,12 +4830,14 @@ module.exports = {
|
|
|
4147
4830
|
useEditableProjectInstall,
|
|
4148
4831
|
compareVersions,
|
|
4149
4832
|
detectInstallMode,
|
|
4833
|
+
buildUvSyncFailureGuidance,
|
|
4150
4834
|
updateManualCommand,
|
|
4151
4835
|
buildUpdateStatus,
|
|
4152
4836
|
parseYesNoAnswer,
|
|
4153
4837
|
normalizeLauncherRelaunchArgs,
|
|
4154
4838
|
officialRepositoryLine,
|
|
4155
4839
|
stripAnsi,
|
|
4840
|
+
normalizeLegacyHostFlagArgs,
|
|
4156
4841
|
},
|
|
4157
4842
|
};
|
|
4158
4843
|
|