@researai/deepscientist 1.5.15 → 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 -98
- package/bin/ds.js +691 -91
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +33 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -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/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- 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 +18 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +33 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- 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 +18 -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/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +31 -9
- package/src/deepscientist/daemon/api/handlers.py +125 -6
- package/src/deepscientist/daemon/api/router.py +4 -0
- package/src/deepscientist/daemon/app.py +715 -98
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +255 -32
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +295 -43
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +86 -31
- 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 +12 -1
- package/src/prompts/system.md +10 -5
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- 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 +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -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-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
package/bin/ds.js
CHANGED
|
@@ -36,7 +36,7 @@ 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
41
|
function buildCodexOverrideEnv({ yolo = true, profile = null, binary = null } = {}) {
|
|
42
42
|
const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
@@ -77,6 +77,33 @@ function parseBooleanFlagValue(rawValue) {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
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
|
+
|
|
80
107
|
function parseYoloArg(args, index, currentValue = true) {
|
|
81
108
|
const arg = args[index];
|
|
82
109
|
if (arg === '--yolo') {
|
|
@@ -130,6 +157,7 @@ Usage:
|
|
|
130
157
|
Launcher flags:
|
|
131
158
|
--host <host> Bind host for the local web daemon
|
|
132
159
|
--port <port> Bind port for the local web daemon
|
|
160
|
+
--auth [true|false] Require a 16-character local browser password. Default is false
|
|
133
161
|
--tui Start the terminal workspace only
|
|
134
162
|
--both Start web + terminal workspace together
|
|
135
163
|
--no-browser Do not auto-open the browser
|
|
@@ -192,6 +220,47 @@ function normalizeProxyUrl(rawValue) {
|
|
|
192
220
|
return value || null;
|
|
193
221
|
}
|
|
194
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
|
+
|
|
195
264
|
function applyLauncherProxy(proxyUrl) {
|
|
196
265
|
const normalized = normalizeProxyUrl(proxyUrl);
|
|
197
266
|
if (!normalized) {
|
|
@@ -562,6 +631,20 @@ function bindUiUrl(host, port) {
|
|
|
562
631
|
return `http://${formatHttpHost(normalized)}:${port}`;
|
|
563
632
|
}
|
|
564
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
|
+
|
|
565
648
|
function normalizeMode(value) {
|
|
566
649
|
const normalized = String(value || '')
|
|
567
650
|
.trim()
|
|
@@ -588,6 +671,66 @@ function parseBooleanSetting(rawValue, fallback = false) {
|
|
|
588
671
|
return fallback;
|
|
589
672
|
}
|
|
590
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
|
+
|
|
591
734
|
function supportsAnsi() {
|
|
592
735
|
return Boolean(process.stdout.isTTY && process.env.TERM !== 'dumb');
|
|
593
736
|
}
|
|
@@ -623,6 +766,64 @@ function colorize(code, text) {
|
|
|
623
766
|
return `${code}${text}\u001B[0m`;
|
|
624
767
|
}
|
|
625
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
|
+
|
|
626
827
|
const OFFICIAL_REPOSITORY_URL = 'https://github.com/ResearAI/DeepScientist';
|
|
627
828
|
|
|
628
829
|
function officialRepositoryLine() {
|
|
@@ -685,7 +886,7 @@ function pythonVersionText(probe) {
|
|
|
685
886
|
return version;
|
|
686
887
|
}
|
|
687
888
|
|
|
688
|
-
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
889
|
+
function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken }) {
|
|
689
890
|
const runtimeRows = [
|
|
690
891
|
['Version', packageJson.version],
|
|
691
892
|
['Home', truncateMiddle(home)],
|
|
@@ -694,6 +895,9 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
694
895
|
['Python', truncateMiddle(pythonVersionText(pythonSelection))],
|
|
695
896
|
['Codex mode', yolo ? 'YOLO (never + danger-full-access)' : 'Default (on-request + workspace-write)'],
|
|
696
897
|
];
|
|
898
|
+
if (authEnabled && authToken) {
|
|
899
|
+
runtimeRows.splice(4, 0, ['Auth token', authToken]);
|
|
900
|
+
}
|
|
697
901
|
if (pythonSelection && pythonSelection.sourceLabel) {
|
|
698
902
|
runtimeRows.push(['Python source', pythonSelection.sourceLabel]);
|
|
699
903
|
}
|
|
@@ -706,6 +910,7 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
|
|
|
706
910
|
['ds --yolo --port 20999 --here', 'Start in ./DeepScientist under the current directory with YOLO Codex access'],
|
|
707
911
|
['ds --port 21000', 'Change the web port'],
|
|
708
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'],
|
|
709
914
|
['ds --here', 'Use ./DeepScientist under the current directory as home'],
|
|
710
915
|
['ds --both', 'Start web + TUI together'],
|
|
711
916
|
['ds --tui', 'Start the terminal workspace only'],
|
|
@@ -729,6 +934,8 @@ function printLaunchCard({
|
|
|
729
934
|
home,
|
|
730
935
|
pythonSelection,
|
|
731
936
|
yolo,
|
|
937
|
+
authEnabled,
|
|
938
|
+
authToken,
|
|
732
939
|
}) {
|
|
733
940
|
const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
|
|
734
941
|
const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
|
|
@@ -787,13 +994,18 @@ function printLaunchCard({
|
|
|
787
994
|
console.log(centerText(colorize('\u001B[1m', workspaceMode), width));
|
|
788
995
|
console.log(centerText(urlLabel, width));
|
|
789
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
|
+
}
|
|
790
1002
|
console.log(centerText(browserLine, width));
|
|
791
1003
|
console.log(centerText(nextStep, width));
|
|
792
1004
|
console.log(centerText('Run ds --stop to stop the managed daemon.', width));
|
|
793
1005
|
console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
|
|
794
1006
|
console.log(centerText(officialRepositoryLine(), width));
|
|
795
1007
|
console.log('');
|
|
796
|
-
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
|
|
1008
|
+
renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo, authEnabled, authToken });
|
|
797
1009
|
}
|
|
798
1010
|
|
|
799
1011
|
function escapeHtml(value) {
|
|
@@ -1020,6 +1232,7 @@ function parseLauncherArgs(argv) {
|
|
|
1020
1232
|
let daemonOnly = false;
|
|
1021
1233
|
let skipUpdateCheck = false;
|
|
1022
1234
|
let yolo = true;
|
|
1235
|
+
let auth = null;
|
|
1023
1236
|
let codexProfile = null;
|
|
1024
1237
|
let codexBinary = null;
|
|
1025
1238
|
|
|
@@ -1040,21 +1253,71 @@ function parseLauncherArgs(argv) {
|
|
|
1040
1253
|
else if (arg === '--open-browser') openBrowser = true;
|
|
1041
1254
|
else if (arg === '--daemon-only') daemonOnly = true;
|
|
1042
1255
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1256
|
+
else if (arg === '--here') continue;
|
|
1043
1257
|
else {
|
|
1044
1258
|
const parsedYolo = parseYoloArg(args, index, yolo);
|
|
1045
1259
|
if (parsedYolo.matched) {
|
|
1046
1260
|
yolo = parsedYolo.value;
|
|
1047
1261
|
index += Math.max(0, parsedYolo.consumed - 1);
|
|
1048
|
-
} else if (arg === '--
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
else if (arg === '
|
|
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
|
+
}
|
|
1056
1318
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1057
|
-
else if (
|
|
1319
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown launcher flag: ${arg}` };
|
|
1320
|
+
else return { help: false, error: `Unexpected launcher argument: ${arg}` };
|
|
1058
1321
|
}
|
|
1059
1322
|
}
|
|
1060
1323
|
|
|
@@ -1073,8 +1336,10 @@ function parseLauncherArgs(argv) {
|
|
|
1073
1336
|
daemonOnly,
|
|
1074
1337
|
skipUpdateCheck,
|
|
1075
1338
|
yolo,
|
|
1339
|
+
auth,
|
|
1076
1340
|
codexProfile,
|
|
1077
1341
|
codexBinary,
|
|
1342
|
+
error: null,
|
|
1078
1343
|
};
|
|
1079
1344
|
}
|
|
1080
1345
|
|
|
@@ -1148,12 +1413,32 @@ function parseUpdateArgs(argv) {
|
|
|
1148
1413
|
else if (arg === '--worker') worker = true;
|
|
1149
1414
|
else if (arg === '--restart-daemon') restartDaemon = true;
|
|
1150
1415
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
1151
|
-
else if (arg === '--home'
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
+
}
|
|
1155
1439
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1156
|
-
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}` };
|
|
1157
1442
|
}
|
|
1158
1443
|
|
|
1159
1444
|
return {
|
|
@@ -1172,6 +1457,7 @@ function parseUpdateArgs(argv) {
|
|
|
1172
1457
|
proxy,
|
|
1173
1458
|
restartDaemon,
|
|
1174
1459
|
skipUpdateCheck,
|
|
1460
|
+
error: null,
|
|
1175
1461
|
};
|
|
1176
1462
|
}
|
|
1177
1463
|
|
|
@@ -1189,15 +1475,23 @@ function parseMigrateArgs(argv) {
|
|
|
1189
1475
|
const arg = args[index];
|
|
1190
1476
|
if (arg === '--yes') yes = true;
|
|
1191
1477
|
else if (arg === '--restart') restart = true;
|
|
1192
|
-
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
|
+
}
|
|
1193
1484
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1194
|
-
else if (arg.startsWith('--')) return
|
|
1485
|
+
else if (arg.startsWith('--')) return { help: false, error: `Unknown migrate flag: ${arg}` };
|
|
1195
1486
|
else if (!target) target = path.resolve(expandUserPath(arg));
|
|
1196
|
-
else return
|
|
1487
|
+
else return { help: false, error: `Unexpected migrate argument: ${arg}` };
|
|
1197
1488
|
}
|
|
1198
1489
|
|
|
1199
1490
|
if (!target) {
|
|
1200
|
-
return
|
|
1491
|
+
return {
|
|
1492
|
+
help: false,
|
|
1493
|
+
error: 'Missing migration target path.',
|
|
1494
|
+
};
|
|
1201
1495
|
}
|
|
1202
1496
|
|
|
1203
1497
|
return {
|
|
@@ -1206,6 +1500,7 @@ function parseMigrateArgs(argv) {
|
|
|
1206
1500
|
target,
|
|
1207
1501
|
yes,
|
|
1208
1502
|
restart,
|
|
1503
|
+
error: null,
|
|
1209
1504
|
};
|
|
1210
1505
|
}
|
|
1211
1506
|
|
|
@@ -2152,13 +2447,17 @@ function ensureUvBinary(home) {
|
|
|
2152
2447
|
}
|
|
2153
2448
|
|
|
2154
2449
|
function buildUvRuntimeEnv(home, extraEnv = {}) {
|
|
2155
|
-
|
|
2450
|
+
const env = {
|
|
2156
2451
|
...process.env,
|
|
2157
2452
|
UV_CACHE_DIR: runtimeUvCachePath(home),
|
|
2158
2453
|
UV_PROJECT_ENVIRONMENT: runtimePythonEnvPath(home),
|
|
2159
2454
|
UV_PYTHON_INSTALL_DIR: runtimeUvPythonInstallPath(home),
|
|
2160
2455
|
...extraEnv,
|
|
2161
2456
|
};
|
|
2457
|
+
for (const key of ['PYTHONPATH', 'PYTHONHOME', 'VIRTUAL_ENV', '__PYVENV_LAUNCHER__']) {
|
|
2458
|
+
delete env[key];
|
|
2459
|
+
}
|
|
2460
|
+
return env;
|
|
2162
2461
|
}
|
|
2163
2462
|
|
|
2164
2463
|
function ensureUvLockPresent() {
|
|
@@ -2171,6 +2470,43 @@ function ensureUvLockPresent() {
|
|
|
2171
2470
|
process.exit(1);
|
|
2172
2471
|
}
|
|
2173
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
|
+
|
|
2174
2510
|
function resolveUvVersion(uvBinary) {
|
|
2175
2511
|
const result = runSync(uvBinary, ['--version'], { capture: true, allowFailure: true });
|
|
2176
2512
|
if (result.status !== 0) {
|
|
@@ -2237,7 +2573,10 @@ function resolveBackgroundPythonExecutable(runtimePython) {
|
|
|
2237
2573
|
}
|
|
2238
2574
|
|
|
2239
2575
|
function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
2240
|
-
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
|
+
}
|
|
2241
2580
|
if (!editable) {
|
|
2242
2581
|
args.push('--no-editable');
|
|
2243
2582
|
}
|
|
@@ -2250,7 +2589,9 @@ function syncUvProjectEnvironment(home, uvBinary, pythonTarget, editable) {
|
|
|
2250
2589
|
return;
|
|
2251
2590
|
}
|
|
2252
2591
|
console.error('DeepScientist could not sync the locked Python environment with uv.');
|
|
2253
|
-
|
|
2592
|
+
for (const line of buildUvSyncFailureGuidance()) {
|
|
2593
|
+
console.error(line);
|
|
2594
|
+
}
|
|
2254
2595
|
process.exit(result.status ?? 1);
|
|
2255
2596
|
}
|
|
2256
2597
|
|
|
@@ -2678,9 +3019,25 @@ function decodeSupervisorEnvPayload(rawValue) {
|
|
|
2678
3019
|
}
|
|
2679
3020
|
}
|
|
2680
3021
|
|
|
2681
|
-
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
|
+
}) {
|
|
2682
3033
|
const browserUrl = browserUiUrl(host, port);
|
|
2683
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;
|
|
2684
3041
|
const logPath = path.join(home, 'logs', 'daemon.log');
|
|
2685
3042
|
ensureDir(path.dirname(logPath));
|
|
2686
3043
|
const out = fs.openSync(logPath, 'a');
|
|
@@ -2700,6 +3057,9 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2700
3057
|
host,
|
|
2701
3058
|
'--port',
|
|
2702
3059
|
String(port),
|
|
3060
|
+
'--auth',
|
|
3061
|
+
resolvedAuthEnabled ? 'true' : 'false',
|
|
3062
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? ['--auth-token', resolvedAuthToken] : []),
|
|
2703
3063
|
],
|
|
2704
3064
|
detachedSpawnOptions({
|
|
2705
3065
|
cwd: repoRoot,
|
|
@@ -2712,6 +3072,8 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2712
3072
|
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2713
3073
|
DS_DAEMON_ID: resolvedDaemonId,
|
|
2714
3074
|
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
3075
|
+
DS_UI_AUTH_ENABLED: resolvedAuthEnabled ? '1' : '0',
|
|
3076
|
+
...(resolvedAuthEnabled && resolvedAuthToken ? { DS_UI_AUTH_TOKEN: resolvedAuthToken } : {}),
|
|
2715
3077
|
},
|
|
2716
3078
|
})
|
|
2717
3079
|
);
|
|
@@ -2722,10 +3084,14 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2722
3084
|
port,
|
|
2723
3085
|
url: browserUrl,
|
|
2724
3086
|
bind_url: daemonBindUrl,
|
|
3087
|
+
launch_url: launchUrl,
|
|
3088
|
+
bind_launch_url: bindLaunchUrl,
|
|
2725
3089
|
log_path: logPath,
|
|
2726
3090
|
started_at: new Date().toISOString(),
|
|
2727
3091
|
home: normalizeHomePath(home),
|
|
2728
3092
|
daemon_id: resolvedDaemonId,
|
|
3093
|
+
auth_enabled: resolvedAuthEnabled,
|
|
3094
|
+
auth_token: resolvedAuthToken,
|
|
2729
3095
|
};
|
|
2730
3096
|
writeDaemonState(home, statePayload);
|
|
2731
3097
|
return {
|
|
@@ -2733,6 +3099,8 @@ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = nu
|
|
|
2733
3099
|
statePayload,
|
|
2734
3100
|
browserUrl,
|
|
2735
3101
|
bindUrl: daemonBindUrl,
|
|
3102
|
+
launchUrl,
|
|
3103
|
+
bindLaunchUrl,
|
|
2736
3104
|
logPath,
|
|
2737
3105
|
};
|
|
2738
3106
|
}
|
|
@@ -2787,19 +3155,54 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2787
3155
|
|
|
2788
3156
|
for (let index = 0; index < args.length; index += 1) {
|
|
2789
3157
|
const arg = args[index];
|
|
2790
|
-
if (arg === '--home'
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
else if (arg === '--
|
|
2796
|
-
|
|
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
|
+
}
|
|
2797
3196
|
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
2798
|
-
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}` };
|
|
2799
3199
|
}
|
|
2800
3200
|
|
|
2801
3201
|
if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
|
|
2802
|
-
return
|
|
3202
|
+
return {
|
|
3203
|
+
help: false,
|
|
3204
|
+
error: 'Daemon supervisor requires --home, --runtime-python, --daemon-id, and a valid --port.',
|
|
3205
|
+
};
|
|
2803
3206
|
}
|
|
2804
3207
|
|
|
2805
3208
|
return {
|
|
@@ -2811,18 +3214,19 @@ function parseDaemonSupervisorArgs(argv) {
|
|
|
2811
3214
|
proxy,
|
|
2812
3215
|
daemonId,
|
|
2813
3216
|
envOverrides: decodeSupervisorEnvPayload(envJson),
|
|
3217
|
+
error: null,
|
|
2814
3218
|
};
|
|
2815
3219
|
}
|
|
2816
3220
|
|
|
2817
3221
|
async function daemonSupervisorMain(rawArgs) {
|
|
2818
3222
|
const options = parseDaemonSupervisorArgs(rawArgs);
|
|
2819
|
-
if (!options) {
|
|
2820
|
-
console.error('Invalid daemon supervisor arguments.');
|
|
2821
|
-
process.exit(1);
|
|
2822
|
-
}
|
|
2823
3223
|
if (options.help) {
|
|
2824
3224
|
process.exit(0);
|
|
2825
3225
|
}
|
|
3226
|
+
if (options.error) {
|
|
3227
|
+
console.error(options.error);
|
|
3228
|
+
process.exit(1);
|
|
3229
|
+
}
|
|
2826
3230
|
|
|
2827
3231
|
const home = options.home;
|
|
2828
3232
|
let trackedDaemonId = String(options.daemonId || '').trim();
|
|
@@ -2849,7 +3253,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2849
3253
|
appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
|
|
2850
3254
|
return;
|
|
2851
3255
|
}
|
|
2852
|
-
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);
|
|
2853
3258
|
if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
|
|
2854
3259
|
restartBackoffMs = 1000;
|
|
2855
3260
|
await sleep(2500);
|
|
@@ -2872,6 +3277,8 @@ async function daemonSupervisorMain(rawArgs) {
|
|
|
2872
3277
|
port: options.port,
|
|
2873
3278
|
proxy: options.proxy,
|
|
2874
3279
|
envOverrides: options.envOverrides,
|
|
3280
|
+
authEnabled: state.auth_enabled !== false,
|
|
3281
|
+
authToken,
|
|
2875
3282
|
});
|
|
2876
3283
|
trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
|
|
2877
3284
|
observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
|
|
@@ -2896,14 +3303,19 @@ function sleep(ms) {
|
|
|
2896
3303
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2897
3304
|
}
|
|
2898
3305
|
|
|
2899
|
-
async function isHealthy(url) {
|
|
2900
|
-
const payload = await fetchHealth(url);
|
|
3306
|
+
async function isHealthy(url, authToken = null) {
|
|
3307
|
+
const payload = await fetchHealth(url, authToken);
|
|
2901
3308
|
return Boolean(payload && payload.status === 'ok');
|
|
2902
3309
|
}
|
|
2903
3310
|
|
|
2904
|
-
async function fetchHealth(url) {
|
|
3311
|
+
async function fetchHealth(url, authToken = null) {
|
|
2905
3312
|
try {
|
|
2906
|
-
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 });
|
|
2907
3319
|
if (!response.ok) {
|
|
2908
3320
|
return null;
|
|
2909
3321
|
}
|
|
@@ -2953,13 +3365,18 @@ function daemonIdentityError({ url, home, health, state }) {
|
|
|
2953
3365
|
].join('\n');
|
|
2954
3366
|
}
|
|
2955
3367
|
|
|
2956
|
-
async function requestDaemonShutdown(url, daemonId) {
|
|
3368
|
+
async function requestDaemonShutdown(url, daemonId, authToken = null) {
|
|
2957
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
|
+
}
|
|
2958
3377
|
const response = await fetch(`${url}/api/admin/shutdown`, {
|
|
2959
3378
|
method: 'POST',
|
|
2960
|
-
headers
|
|
2961
|
-
'Content-Type': 'application/json',
|
|
2962
|
-
},
|
|
3379
|
+
headers,
|
|
2963
3380
|
body: JSON.stringify({ source: 'ds-launcher', daemon_id: daemonId || null }),
|
|
2964
3381
|
});
|
|
2965
3382
|
if (!response.ok) {
|
|
@@ -2972,6 +3389,31 @@ async function requestDaemonShutdown(url, daemonId) {
|
|
|
2972
3389
|
}
|
|
2973
3390
|
}
|
|
2974
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
|
+
|
|
2975
3417
|
function isPidAlive(pid) {
|
|
2976
3418
|
if (!pid) return false;
|
|
2977
3419
|
try {
|
|
@@ -3005,9 +3447,9 @@ function killManagedProcess(pid, signal) {
|
|
|
3005
3447
|
}
|
|
3006
3448
|
}
|
|
3007
3449
|
|
|
3008
|
-
async function waitForDaemonStop({ url, pid, attempts = 20, delayMs = 200 }) {
|
|
3450
|
+
async function waitForDaemonStop({ url, pid, authToken = null, attempts = 20, delayMs = 200 }) {
|
|
3009
3451
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
3010
|
-
const healthy = url ? await isHealthy(url) : false;
|
|
3452
|
+
const healthy = url ? await isHealthy(url, authToken) : false;
|
|
3011
3453
|
const alive = pid ? isPidAlive(pid) : false;
|
|
3012
3454
|
if (!healthy && !alive) {
|
|
3013
3455
|
return true;
|
|
@@ -3032,7 +3474,8 @@ async function stopDaemon(home) {
|
|
|
3032
3474
|
const state = readDaemonState(home);
|
|
3033
3475
|
const configured = readConfiguredUiAddressFromFile(home);
|
|
3034
3476
|
const url = state?.url || browserUiUrl(state?.host || configured.host, state?.port || configured.port);
|
|
3035
|
-
const
|
|
3477
|
+
const authToken = typeof state?.auth_token === 'string' ? state.auth_token.trim() : '';
|
|
3478
|
+
const healthBefore = await fetchHealth(url, authToken);
|
|
3036
3479
|
const healthyBefore = Boolean(healthBefore && healthBefore.status === 'ok');
|
|
3037
3480
|
const sameHomeHealthy = healthMatchesHome({ health: healthBefore, home });
|
|
3038
3481
|
const pid = state?.pid || (sameHomeHealthy ? healthBefore?.pid : null);
|
|
@@ -3073,21 +3516,21 @@ async function stopDaemon(home) {
|
|
|
3073
3516
|
let stopped = false;
|
|
3074
3517
|
|
|
3075
3518
|
if (healthyBefore) {
|
|
3076
|
-
await requestDaemonShutdown(url, shutdownDaemonId || null);
|
|
3077
|
-
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 });
|
|
3078
3521
|
}
|
|
3079
3522
|
|
|
3080
3523
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
3081
3524
|
killManagedProcess(pid, 'SIGTERM');
|
|
3082
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 30, delayMs: 200 });
|
|
3525
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 30, delayMs: 200 });
|
|
3083
3526
|
}
|
|
3084
3527
|
|
|
3085
3528
|
if (!stopped && pid && isPidAlive(pid)) {
|
|
3086
3529
|
killManagedProcess(pid, 'SIGKILL');
|
|
3087
|
-
stopped = await waitForDaemonStop({ url, pid, attempts: 20, delayMs: 150 });
|
|
3530
|
+
stopped = await waitForDaemonStop({ url, pid, authToken, attempts: 20, delayMs: 150 });
|
|
3088
3531
|
}
|
|
3089
3532
|
|
|
3090
|
-
const stillHealthy = await isHealthy(url);
|
|
3533
|
+
const stillHealthy = await isHealthy(url, authToken);
|
|
3091
3534
|
if (!stopped && (stillHealthy || (pid && isPidAlive(pid)))) {
|
|
3092
3535
|
console.error('DeepScientist daemon is still running after shutdown attempts.');
|
|
3093
3536
|
process.exit(1);
|
|
@@ -3362,19 +3805,23 @@ async function performSelfUpdate(home, options = {}) {
|
|
|
3362
3805
|
log_path: installResult.logPath,
|
|
3363
3806
|
};
|
|
3364
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
|
+
}
|
|
3365
3823
|
spawnDetachedNode(
|
|
3366
|
-
|
|
3367
|
-
launcherPath,
|
|
3368
|
-
'--home',
|
|
3369
|
-
home,
|
|
3370
|
-
'--host',
|
|
3371
|
-
String(host),
|
|
3372
|
-
'--port',
|
|
3373
|
-
String(port),
|
|
3374
|
-
'--daemon-only',
|
|
3375
|
-
'--no-browser',
|
|
3376
|
-
'--skip-update-check',
|
|
3377
|
-
],
|
|
3824
|
+
restartArgs,
|
|
3378
3825
|
{
|
|
3379
3826
|
cwd: repoRoot,
|
|
3380
3827
|
env: process.env,
|
|
@@ -3569,17 +4016,88 @@ async function startBackgroundUpdateWorker(home, options = {}) {
|
|
|
3569
4016
|
};
|
|
3570
4017
|
}
|
|
3571
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
|
+
|
|
3572
4088
|
async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallbackPort) {
|
|
3573
4089
|
try {
|
|
3574
4090
|
const result = runPythonCli(runtimePython, ['--home', home, 'config', 'show', 'config'], { capture: true, allowFailure: true });
|
|
3575
4091
|
const text = result.stdout || '';
|
|
3576
4092
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3577
4093
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4094
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3578
4095
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3579
4096
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3580
4097
|
return {
|
|
3581
4098
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3582
4099
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4100
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3583
4101
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3584
4102
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3585
4103
|
};
|
|
@@ -3587,6 +4105,7 @@ async function readConfiguredUiAddress(home, runtimePython, fallbackHost, fallba
|
|
|
3587
4105
|
return {
|
|
3588
4106
|
host: fallbackHost || '0.0.0.0',
|
|
3589
4107
|
port: fallbackPort || 20999,
|
|
4108
|
+
authEnabled: false,
|
|
3590
4109
|
defaultMode: 'web',
|
|
3591
4110
|
autoOpenBrowser: true,
|
|
3592
4111
|
};
|
|
@@ -3599,6 +4118,7 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3599
4118
|
return {
|
|
3600
4119
|
host: fallbackHost || '0.0.0.0',
|
|
3601
4120
|
port: fallbackPort || 20999,
|
|
4121
|
+
authEnabled: false,
|
|
3602
4122
|
defaultMode: 'web',
|
|
3603
4123
|
autoOpenBrowser: true,
|
|
3604
4124
|
};
|
|
@@ -3607,11 +4127,13 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3607
4127
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
3608
4128
|
const hostMatch = text.match(/^\s*host:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3609
4129
|
const portMatch = text.match(/^\s*port:\s*(\d+)\s*$/m);
|
|
4130
|
+
const authMatch = text.match(/^\s*auth_enabled:\s*([^\n]+)\s*$/m);
|
|
3610
4131
|
const modeMatch = text.match(/^\s*default_mode:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
3611
4132
|
const autoOpenMatch = text.match(/^\s*auto_open_browser:\s*([^\n]+)\s*$/m);
|
|
3612
4133
|
return {
|
|
3613
4134
|
host: fallbackHost || (hostMatch ? hostMatch[1].trim() : '0.0.0.0'),
|
|
3614
4135
|
port: fallbackPort || (portMatch ? Number(portMatch[1]) : 20999),
|
|
4136
|
+
authEnabled: parseBooleanSetting(authMatch ? authMatch[1].trim() : false, false),
|
|
3615
4137
|
defaultMode: normalizeMode(modeMatch ? modeMatch[1].trim() : 'web'),
|
|
3616
4138
|
autoOpenBrowser: parseBooleanSetting(autoOpenMatch ? autoOpenMatch[1].trim() : true, true),
|
|
3617
4139
|
};
|
|
@@ -3619,20 +4141,51 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
|
|
|
3619
4141
|
return {
|
|
3620
4142
|
host: fallbackHost || '0.0.0.0',
|
|
3621
4143
|
port: fallbackPort || 20999,
|
|
4144
|
+
authEnabled: false,
|
|
3622
4145
|
defaultMode: 'web',
|
|
3623
4146
|
autoOpenBrowser: true,
|
|
3624
4147
|
};
|
|
3625
4148
|
}
|
|
3626
4149
|
}
|
|
3627
4150
|
|
|
3628
|
-
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}) {
|
|
4151
|
+
async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}, authEnabled = false) {
|
|
3629
4152
|
const browserUrl = browserUiUrl(host, port);
|
|
3630
4153
|
const daemonBindUrl = bindUiUrl(host, port);
|
|
3631
4154
|
const state = readDaemonState(home);
|
|
3632
|
-
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() : '');
|
|
3633
4159
|
if (existingHealth && existingHealth.status === 'ok') {
|
|
3634
4160
|
if (state && healthMatchesManagedState({ health: existingHealth, state, home })) {
|
|
3635
|
-
|
|
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
|
+
};
|
|
3636
4189
|
}
|
|
3637
4190
|
console.error(
|
|
3638
4191
|
state
|
|
@@ -3666,11 +4219,13 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3666
4219
|
port,
|
|
3667
4220
|
proxy,
|
|
3668
4221
|
envOverrides,
|
|
4222
|
+
authEnabled,
|
|
4223
|
+
authToken: desiredAuthToken,
|
|
3669
4224
|
});
|
|
3670
4225
|
const logPath = startedProcess.logPath;
|
|
3671
4226
|
|
|
3672
4227
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
3673
|
-
const health = await fetchHealth(browserUrl);
|
|
4228
|
+
const health = await fetchHealth(browserUrl, desiredAuthToken);
|
|
3674
4229
|
if (health && health.status === 'ok') {
|
|
3675
4230
|
const liveState = readDaemonState(home);
|
|
3676
4231
|
if (!healthMatchesManagedState({ health, state: liveState, home })) {
|
|
@@ -3689,12 +4244,26 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3689
4244
|
if (supervisorPid) {
|
|
3690
4245
|
appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
|
|
3691
4246
|
}
|
|
3692
|
-
return {
|
|
4247
|
+
return {
|
|
4248
|
+
url: launchUrl,
|
|
4249
|
+
bindUrl: bindLaunchUrl,
|
|
4250
|
+
reused: false,
|
|
4251
|
+
authEnabled,
|
|
4252
|
+
authToken: desiredAuthToken,
|
|
4253
|
+
};
|
|
3693
4254
|
}
|
|
3694
4255
|
await sleep(250);
|
|
3695
4256
|
}
|
|
3696
4257
|
|
|
3697
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
|
+
}
|
|
3698
4267
|
const logTail = tailLog(logPath);
|
|
3699
4268
|
if (logTail) {
|
|
3700
4269
|
console.error(logTail);
|
|
@@ -3778,12 +4347,15 @@ function handleCodexPreflightFailure(error) {
|
|
|
3778
4347
|
return true;
|
|
3779
4348
|
}
|
|
3780
4349
|
|
|
3781
|
-
function launchTui(url, questId, home, runtimePython) {
|
|
4350
|
+
function launchTui(url, questId, home, runtimePython, authToken = null) {
|
|
3782
4351
|
const entry = ensureNodeBundle('src/tui', 'dist/index.js');
|
|
3783
4352
|
const args = [entry, '--base-url', url];
|
|
3784
4353
|
if (questId) {
|
|
3785
4354
|
args.push('--quest-id', questId);
|
|
3786
4355
|
}
|
|
4356
|
+
if (typeof authToken === 'string' && authToken.trim()) {
|
|
4357
|
+
args.push('--auth-token', authToken.trim());
|
|
4358
|
+
}
|
|
3787
4359
|
const child = spawn(process.execPath, args, {
|
|
3788
4360
|
cwd: repoRoot,
|
|
3789
4361
|
stdio: 'inherit',
|
|
@@ -3801,14 +4373,15 @@ function launchTui(url, questId, home, runtimePython) {
|
|
|
3801
4373
|
|
|
3802
4374
|
async function updateMain(rawArgs) {
|
|
3803
4375
|
const options = parseUpdateArgs(rawArgs);
|
|
3804
|
-
if (!options) {
|
|
3805
|
-
printUpdateHelp();
|
|
3806
|
-
process.exit(1);
|
|
3807
|
-
}
|
|
3808
4376
|
if (options.help) {
|
|
3809
4377
|
printUpdateHelp();
|
|
3810
4378
|
process.exit(0);
|
|
3811
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
|
+
}
|
|
3812
4385
|
|
|
3813
4386
|
const home = options.home || resolveHome(rawArgs);
|
|
3814
4387
|
applyLauncherProxy(options.proxy);
|
|
@@ -3923,14 +4496,15 @@ async function updateMain(rawArgs) {
|
|
|
3923
4496
|
|
|
3924
4497
|
async function migrateMain(rawArgs) {
|
|
3925
4498
|
const options = parseMigrateArgs(rawArgs);
|
|
3926
|
-
if (!options) {
|
|
3927
|
-
printMigrateHelp();
|
|
3928
|
-
process.exit(1);
|
|
3929
|
-
}
|
|
3930
4499
|
if (options.help) {
|
|
3931
4500
|
printMigrateHelp();
|
|
3932
4501
|
process.exit(0);
|
|
3933
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
|
+
}
|
|
3934
4508
|
|
|
3935
4509
|
const sourceHome = realpathOrSelf(options.home || resolveHome(rawArgs));
|
|
3936
4510
|
const targetHome = path.resolve(options.target);
|
|
@@ -4051,13 +4625,15 @@ async function migrateMain(rawArgs) {
|
|
|
4051
4625
|
|
|
4052
4626
|
async function launcherMain(rawArgs) {
|
|
4053
4627
|
const options = parseLauncherArgs(rawArgs);
|
|
4054
|
-
if (!options) {
|
|
4055
|
-
return false;
|
|
4056
|
-
}
|
|
4057
4628
|
if (options.help) {
|
|
4058
4629
|
printLauncherHelp();
|
|
4059
4630
|
process.exit(0);
|
|
4060
4631
|
}
|
|
4632
|
+
if (options.error) {
|
|
4633
|
+
console.error(options.error);
|
|
4634
|
+
console.error('Run `ds --help` for launcher usage.');
|
|
4635
|
+
process.exit(1);
|
|
4636
|
+
}
|
|
4061
4637
|
|
|
4062
4638
|
const home = options.home || resolveHome(rawArgs);
|
|
4063
4639
|
applyLauncherProxy(options.proxy);
|
|
@@ -4075,8 +4651,10 @@ async function launcherMain(rawArgs) {
|
|
|
4075
4651
|
if (options.status) {
|
|
4076
4652
|
const state = readDaemonState(home);
|
|
4077
4653
|
const configured = readConfiguredUiAddressFromFile(home, options.host, options.port);
|
|
4078
|
-
const url = state?.url || browserUiUrl(configured.host, configured.port);
|
|
4079
|
-
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);
|
|
4080
4658
|
const healthy = Boolean(health && health.status === 'ok');
|
|
4081
4659
|
const identityMatch = state ? healthMatchesManagedState({ health, state, home }) : false;
|
|
4082
4660
|
console.log(
|
|
@@ -4105,6 +4683,7 @@ async function launcherMain(rawArgs) {
|
|
|
4105
4683
|
binary: options.codexBinary,
|
|
4106
4684
|
});
|
|
4107
4685
|
ensureInitialized(home, runtimePython);
|
|
4686
|
+
await maybeHandleMiniMaxCodexVersion(home, runtimePython, options);
|
|
4108
4687
|
if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
|
|
4109
4688
|
return true;
|
|
4110
4689
|
}
|
|
@@ -4113,20 +4692,27 @@ async function launcherMain(rawArgs) {
|
|
|
4113
4692
|
const configuredUi = await readConfiguredUiAddress(home, runtimePython, options.host, options.port);
|
|
4114
4693
|
const host = configuredUi.host;
|
|
4115
4694
|
const port = configuredUi.port;
|
|
4695
|
+
const authEnabled = options.auth === null ? false : options.auth !== false;
|
|
4116
4696
|
const mode = normalizeMode(options.mode ?? 'web');
|
|
4117
4697
|
const shouldOpenBrowser = options.daemonOnly
|
|
4118
4698
|
? false
|
|
4119
4699
|
: options.openBrowser === null
|
|
4120
4700
|
? configuredUi.autoOpenBrowser !== false && mode !== 'tui'
|
|
4121
4701
|
: options.openBrowser;
|
|
4122
|
-
|
|
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) {
|
|
4123
4709
|
await stopDaemon(home);
|
|
4124
4710
|
}
|
|
4125
4711
|
|
|
4126
4712
|
step(4, 4, 'Starting local daemon and UI surfaces');
|
|
4127
4713
|
let started;
|
|
4128
4714
|
try {
|
|
4129
|
-
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv);
|
|
4715
|
+
started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv, authEnabled);
|
|
4130
4716
|
} catch (error) {
|
|
4131
4717
|
if (handleCodexPreflightFailure(error)) return true;
|
|
4132
4718
|
throw error;
|
|
@@ -4142,6 +4728,8 @@ async function launcherMain(rawArgs) {
|
|
|
4142
4728
|
home,
|
|
4143
4729
|
pythonSelection: pythonRuntime.runtimeProbe,
|
|
4144
4730
|
yolo: options.yolo,
|
|
4731
|
+
authEnabled: started.authEnabled,
|
|
4732
|
+
authToken: started.authToken,
|
|
4145
4733
|
});
|
|
4146
4734
|
|
|
4147
4735
|
if (options.daemonOnly) {
|
|
@@ -4150,12 +4738,16 @@ async function launcherMain(rawArgs) {
|
|
|
4150
4738
|
if (mode === 'web') {
|
|
4151
4739
|
process.exit(0);
|
|
4152
4740
|
}
|
|
4153
|
-
launchTui(
|
|
4741
|
+
launchTui(browserUiUrl(host, port), options.questId, home, runtimePython, started.authToken);
|
|
4154
4742
|
return true;
|
|
4155
4743
|
}
|
|
4156
4744
|
|
|
4157
4745
|
async function main() {
|
|
4158
|
-
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
|
+
}
|
|
4159
4751
|
if (args[0] === '--daemon-supervisor') {
|
|
4160
4752
|
await daemonSupervisorMain(args.slice(1));
|
|
4161
4753
|
return;
|
|
@@ -4169,7 +4761,11 @@ async function main() {
|
|
|
4169
4761
|
await migrateMain(args);
|
|
4170
4762
|
return;
|
|
4171
4763
|
}
|
|
4172
|
-
if (
|
|
4764
|
+
if (
|
|
4765
|
+
args.length === 0
|
|
4766
|
+
|| args[0] === 'ui'
|
|
4767
|
+
|| (args[0]?.startsWith('--') && (!positional || !pythonCommands.has(positional.value)))
|
|
4768
|
+
) {
|
|
4173
4769
|
await launcherMain(args);
|
|
4174
4770
|
return;
|
|
4175
4771
|
}
|
|
@@ -4225,6 +4821,8 @@ module.exports = {
|
|
|
4225
4821
|
resolveUvBinary,
|
|
4226
4822
|
resolveHome,
|
|
4227
4823
|
parseLauncherArgs,
|
|
4824
|
+
generateBrowserAuthToken,
|
|
4825
|
+
appendBrowserAuthToken,
|
|
4228
4826
|
normalizeProxyUrl,
|
|
4229
4827
|
parseMigrateArgs,
|
|
4230
4828
|
parseLegacyWrapperCandidate,
|
|
@@ -4232,12 +4830,14 @@ module.exports = {
|
|
|
4232
4830
|
useEditableProjectInstall,
|
|
4233
4831
|
compareVersions,
|
|
4234
4832
|
detectInstallMode,
|
|
4833
|
+
buildUvSyncFailureGuidance,
|
|
4235
4834
|
updateManualCommand,
|
|
4236
4835
|
buildUpdateStatus,
|
|
4237
4836
|
parseYesNoAnswer,
|
|
4238
4837
|
normalizeLauncherRelaunchArgs,
|
|
4239
4838
|
officialRepositoryLine,
|
|
4240
4839
|
stripAnsi,
|
|
4840
|
+
normalizeLegacyHostFlagArgs,
|
|
4241
4841
|
},
|
|
4242
4842
|
};
|
|
4243
4843
|
|