@researai/deepscientist 1.5.11 → 1.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +8 -8
  2. package/bin/ds.js +358 -61
  3. package/docs/en/00_QUICK_START.md +35 -3
  4. package/docs/en/01_SETTINGS_REFERENCE.md +11 -0
  5. package/docs/en/02_START_RESEARCH_GUIDE.md +68 -4
  6. package/docs/en/09_DOCTOR.md +28 -3
  7. package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +21 -2
  8. package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
  9. package/docs/en/README.md +4 -0
  10. package/docs/zh/00_QUICK_START.md +34 -2
  11. package/docs/zh/01_SETTINGS_REFERENCE.md +11 -0
  12. package/docs/zh/02_START_RESEARCH_GUIDE.md +69 -3
  13. package/docs/zh/09_DOCTOR.md +28 -1
  14. package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +21 -2
  15. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
  16. package/docs/zh/README.md +4 -1
  17. package/package.json +1 -1
  18. package/pyproject.toml +1 -1
  19. package/src/deepscientist/__init__.py +1 -1
  20. package/src/deepscientist/bash_exec/monitor.py +7 -5
  21. package/src/deepscientist/bash_exec/service.py +84 -21
  22. package/src/deepscientist/channels/local.py +3 -3
  23. package/src/deepscientist/channels/qq.py +7 -7
  24. package/src/deepscientist/channels/relay.py +7 -7
  25. package/src/deepscientist/channels/weixin_ilink.py +90 -19
  26. package/src/deepscientist/config/models.py +1 -0
  27. package/src/deepscientist/config/service.py +121 -20
  28. package/src/deepscientist/daemon/app.py +314 -6
  29. package/src/deepscientist/doctor.py +1 -5
  30. package/src/deepscientist/mcp/server.py +124 -3
  31. package/src/deepscientist/prompts/builder.py +113 -11
  32. package/src/deepscientist/quest/service.py +247 -31
  33. package/src/deepscientist/runners/codex.py +121 -22
  34. package/src/deepscientist/runners/runtime_overrides.py +6 -0
  35. package/src/deepscientist/shared.py +33 -14
  36. package/src/prompts/connectors/qq.md +2 -1
  37. package/src/prompts/connectors/weixin.md +2 -1
  38. package/src/prompts/contracts/shared_interaction.md +4 -1
  39. package/src/prompts/system.md +59 -9
  40. package/src/skills/analysis-campaign/SKILL.md +46 -6
  41. package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
  42. package/src/skills/baseline/SKILL.md +1 -1
  43. package/src/skills/decision/SKILL.md +1 -1
  44. package/src/skills/experiment/SKILL.md +1 -1
  45. package/src/skills/finalize/SKILL.md +1 -1
  46. package/src/skills/idea/SKILL.md +1 -1
  47. package/src/skills/intake-audit/SKILL.md +1 -1
  48. package/src/skills/rebuttal/SKILL.md +74 -1
  49. package/src/skills/rebuttal/references/response-letter-template.md +55 -11
  50. package/src/skills/review/SKILL.md +118 -1
  51. package/src/skills/review/references/experiment-todo-template.md +23 -0
  52. package/src/skills/review/references/review-report-template.md +16 -0
  53. package/src/skills/review/references/revision-log-template.md +4 -0
  54. package/src/skills/scout/SKILL.md +1 -1
  55. package/src/skills/write/SKILL.md +168 -7
  56. package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
  57. package/src/tui/package.json +1 -1
  58. package/src/ui/dist/assets/{AiManusChatView-D0mTXG4-.js → AiManusChatView-CnJcXynW.js} +12 -12
  59. package/src/ui/dist/assets/{AnalysisPlugin-Db0cTXxm.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
  60. package/src/ui/dist/assets/{CliPlugin-DrV8je02.js → CliPlugin-CB1YODQn.js} +9 -9
  61. package/src/ui/dist/assets/{CodeEditorPlugin-QXMSCH71.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
  62. package/src/ui/dist/assets/{CodeViewerPlugin-7hhtWj_E.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
  63. package/src/ui/dist/assets/{DocViewerPlugin-BWMSnRJe.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
  64. package/src/ui/dist/assets/{GitDiffViewerPlugin-7J9h9Vy_.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -20
  65. package/src/ui/dist/assets/{ImageViewerPlugin-CHJl_0lr.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
  66. package/src/ui/dist/assets/{LabCopilotPanel-1qSow1es.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
  67. package/src/ui/dist/assets/{LabPlugin-eQpPPCEp.js → LabPlugin-Ciz1gDaX.js} +2 -2
  68. package/src/ui/dist/assets/{LatexPlugin-BwRfi89Z.js → LatexPlugin-BhmjNQRC.js} +37 -11
  69. package/src/ui/dist/assets/{MarkdownViewerPlugin-836PVQWV.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
  70. package/src/ui/dist/assets/{MarketplacePlugin-C2y_556i.js → MarketplacePlugin-DmyHspXt.js} +3 -3
  71. package/src/ui/dist/assets/{NotebookEditor-DIX7Mlzu.js → NotebookEditor-BMXKrDRk.js} +1 -1
  72. package/src/ui/dist/assets/{NotebookEditor-BRzJbGsn.js → NotebookEditor-BTVYRGkm.js} +11 -11
  73. package/src/ui/dist/assets/{PdfLoader-DzRaTAlq.js → PdfLoader-CvcjJHXv.js} +1 -1
  74. package/src/ui/dist/assets/{PdfMarkdownPlugin-DZUfIUnp.js → PdfMarkdownPlugin-DW2ej8Vk.js} +2 -2
  75. package/src/ui/dist/assets/{PdfViewerPlugin-BwtICzue.js → PdfViewerPlugin-CmlDxbhU.js} +10 -10
  76. package/src/ui/dist/assets/{SearchPlugin-DHeIAMsx.js → SearchPlugin-DAjQZPSv.js} +1 -1
  77. package/src/ui/dist/assets/{TextViewerPlugin-C3tCmFox.js → TextViewerPlugin-C-nVAZb_.js} +5 -5
  78. package/src/ui/dist/assets/{VNCViewer-CQsKVm3t.js → VNCViewer-D7-dIYon.js} +10 -10
  79. package/src/ui/dist/assets/{bot-BEA2vWuK.js → bot-C_G4WtNI.js} +1 -1
  80. package/src/ui/dist/assets/{code-XfbSR8K2.js → code-Cd7WfiWq.js} +1 -1
  81. package/src/ui/dist/assets/{file-content-BjxNaIfy.js → file-content-B57zsL9y.js} +1 -1
  82. package/src/ui/dist/assets/{file-diff-panel-D_lLVQk0.js → file-diff-panel-DVoheLFq.js} +1 -1
  83. package/src/ui/dist/assets/{file-socket-D9x_5vlY.js → file-socket-B5kXFxZP.js} +1 -1
  84. package/src/ui/dist/assets/{image-BhWT33W1.js → image-LLOjkMHF.js} +1 -1
  85. package/src/ui/dist/assets/{index-Dqj-Mjb4.css → index-BQG-1s2o.css} +40 -2
  86. package/src/ui/dist/assets/{index--c4iXtuy.js → index-C3r2iGrp.js} +12 -12
  87. package/src/ui/dist/assets/{index-DZTZ8mWP.js → index-CLQauncb.js} +911 -120
  88. package/src/ui/dist/assets/{index-PJbSbPTy.js → index-Dxa2eYMY.js} +1 -1
  89. package/src/ui/dist/assets/{index-BDxipwrC.js → index-hOUOWbW2.js} +2 -2
  90. package/src/ui/dist/assets/{monaco-K8izTGgo.js → monaco-BGGAEii3.js} +1 -1
  91. package/src/ui/dist/assets/{pdf-effect-queue-DfBors6y.js → pdf-effect-queue-DlEr1_y5.js} +1 -1
  92. package/src/ui/dist/assets/{popover-yFK1J4fL.js → popover-CWJbJuYY.js} +1 -1
  93. package/src/ui/dist/assets/{project-sync-PENr2zcz.js → project-sync-CRJiucYO.js} +18 -4
  94. package/src/ui/dist/assets/{select-CAbJDfYv.js → select-CoHB7pvH.js} +2 -2
  95. package/src/ui/dist/assets/{sigma-DEuYJqTl.js → sigma-D5aJWR8J.js} +1 -1
  96. package/src/ui/dist/assets/{square-check-big-omoSUmcd.js → square-check-big-DUK_mnkS.js} +1 -1
  97. package/src/ui/dist/assets/{trash--F119N47.js → trash-ChU3SEE3.js} +1 -1
  98. package/src/ui/dist/assets/{useCliAccess-D31UR23I.js → useCliAccess-BrJBV3tY.js} +1 -1
  99. package/src/ui/dist/assets/{useFileDiffOverlay-BH6KcMzq.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
  100. package/src/ui/dist/assets/{wrap-text-CZ613PM5.js → wrap-text-C7Qqh-om.js} +1 -1
  101. package/src/ui/dist/assets/{zoom-out-BgDLAv3z.js → zoom-out-rtX0FKya.js} +1 -1
  102. package/src/ui/dist/index.html +2 -2
package/README.md CHANGED
@@ -1,14 +1,13 @@
1
- # DeepScientist
1
+ <h1 align="center">
2
+ <img src="assets/branding/logo.svg" alt="DeepScientist logo" width="84" />
3
+ DeepScientist
4
+ </h1>
2
5
 
3
- <p align="center">
4
- <img src="assets/branding/logo.svg" alt="DeepScientist logo" width="120" />
5
- </p>
6
-
7
- <p align="center">
6
+ <p>
8
7
  <strong>DeepScientist is not just a long-running autonomous scientific discovery system. It is also a persistent research map that lives on your own machine.</strong>
9
8
  </p>
10
9
 
11
- <p align="center">
10
+ <p>
12
11
  Local-first. Open-source. Git-backed. Built for verifiable computational research.
13
12
  </p>
14
13
 
@@ -99,6 +98,7 @@ If `codex --login` is unavailable, run `codex` once and finish authentication th
99
98
  For detailed install, troubleshooting, PDF compile, and other launch modes, use:
100
99
 
101
100
  - [Quick Start](docs/en/00_QUICK_START.md)
101
+ - [Codex Provider Setup](docs/en/15_CODEX_PROVIDER_SETUP.md)
102
102
  - [Doctor](docs/en/09_DOCTOR.md)
103
103
 
104
104
  ## Documentation
@@ -112,6 +112,7 @@ For detailed install, troubleshooting, PDF compile, and other launch modes, use:
112
112
  - [Lingzhu / Rokid Guide (English)](docs/en/04_LINGZHU_CONNECTOR_GUIDE.md)
113
113
  - [Memory and MCP Guide (English)](docs/en/07_MEMORY_AND_MCP.md)
114
114
  - [Settings Reference (English)](docs/en/01_SETTINGS_REFERENCE.md)
115
+ - [Codex Provider Setup (English)](docs/en/15_CODEX_PROVIDER_SETUP.md)
115
116
 
116
117
  ## Maintainers
117
118
 
@@ -147,7 +148,6 @@ url={https://openreview.net/forum?id=cZFgsLq8Gs}
147
148
  | [Dr. Claw](https://github.com/OpenLAIR/dr-claw) | Open-source | ✓ | | ✓ | | ✓ | |
148
149
  | [FARS](https://analemma.ai/fars/) | Closed-source | ✓ | | | | | |
149
150
  | [EvoScientist](https://github.com/EvoScientist/EvoScientist) | Open-source | ✓ | | ✓ | ✓ | ✓ | |
150
- | [PaperClaw](https://github.com/meowscles69/PaperClaw) | Open-source | | | | | | ✓ |
151
151
  | [ScienceClaw](https://github.com/beita6969/ScienceClaw) | Open-source | | | | ✓ | ✓ | |
152
152
  | [claude-scholar](https://github.com/Galaxy-Dawn/claude-scholar) | Open-source | ✓ | | ✓ | ✓ | | |
153
153
  | [Research-Claw](https://github.com/wentorai/Research-Claw) | Open-source | ✓ | | ✓ | ✓ | ✓ | |
package/bin/ds.js CHANGED
@@ -36,15 +36,33 @@ const pythonCommands = new Set([
36
36
  const UPDATE_PACKAGE_NAME = String(packageJson.name || '@researai/deepscientist').trim() || '@researai/deepscientist';
37
37
  const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
38
38
 
39
- const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy']);
39
+ const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile']);
40
40
 
41
- function buildCodexOverrideEnv({ yolo = false } = {}) {
41
+ function buildCodexOverrideEnv({ yolo = false, profile = null } = {}) {
42
+ const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
43
+ const overrides = {};
42
44
  if (!yolo) {
43
- return {};
45
+ if (normalizedProfile) {
46
+ overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
47
+ overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
48
+ }
49
+ return overrides;
44
50
  }
45
- return {
46
- DEEPSCIENTIST_CODEX_YOLO: '1',
47
- };
51
+ overrides.DEEPSCIENTIST_CODEX_YOLO = '1';
52
+ if (normalizedProfile) {
53
+ overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
54
+ overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
55
+ }
56
+ return overrides;
57
+ }
58
+
59
+ function readOptionValue(argv, optionName) {
60
+ for (let index = 0; index < argv.length; index += 1) {
61
+ if (argv[index] === optionName && argv[index + 1]) {
62
+ return argv[index + 1];
63
+ }
64
+ }
65
+ return null;
48
66
  }
49
67
 
50
68
  function printLauncherHelp() {
@@ -84,6 +102,7 @@ Launcher flags:
84
102
  --here Create/use ./DeepScientist under the current working directory as home
85
103
  --proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
86
104
  --yolo Run Codex in YOLO mode: approval_policy=never and sandbox_mode=danger-full-access
105
+ --codex-profile <id> Run DeepScientist with a specific Codex profile, for example \`m27\`
87
106
  --quest-id <id> Open the TUI on one quest directly
88
107
 
89
108
  Update:
@@ -754,6 +773,13 @@ function writeCodexPreflightReport(home, probe) {
754
773
  const errors = Array.isArray(probe?.errors) ? probe.errors : [];
755
774
  const guidance = Array.isArray(probe?.guidance) ? probe.guidance : [];
756
775
  const details = probe && typeof probe.details === 'object' ? probe.details : {};
776
+ const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
777
+ const intro = profile
778
+ ? `DeepScientist blocked startup because the Codex hello probe did not pass for profile \`${profile}\`. Verify that \`codex --profile ${profile}\` works on this machine and that the profile's provider-specific API key, Base URL, and model configuration are already set up.`
779
+ : 'DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency. If `codex` is still missing, repair it with `npm install -g @openai/codex`. Then run `codex --login` (or `codex`), finish authentication, run `ds doctor`, and launch `ds` again.';
780
+ const introZh = profile
781
+ ? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
782
+ : 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex --login`(或 `codex`)完成认证,再执行 `ds doctor`,最后重新启动 `ds`。';
757
783
  const renderItems = (items, tone) =>
758
784
  items
759
785
  .map(
@@ -814,8 +840,8 @@ function writeCodexPreflightReport(home, probe) {
814
840
  <main class="page">
815
841
  <section class="panel">
816
842
  <h1>DeepScientist could not start Codex</h1>
817
- <p class="meta">DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, <code>npm install -g @researai/deepscientist</code> also installs the bundled Codex dependency. If <code>codex</code> is still missing, repair it with <code>npm install -g @openai/codex</code>. Then run <code>codex --login</code> (or <code>codex</code>), finish authentication, run <code>ds doctor</code>, and launch <code>ds</code> again.</p>
818
- <p class="meta">DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,<code>npm install -g @researai/deepscientist</code> 也会一并安装 bundled Codex 依赖;如果此后 <code>codex</code> 仍不可用,请再执行 <code>npm install -g @openai/codex</code> 修复。然后运行 <code>codex --login</code>(或 <code>codex</code>)完成认证,再执行 <code>ds doctor</code>,最后重新启动 <code>ds</code>。</p>
843
+ <p class="meta">${escapeHtml(intro)}</p>
844
+ <p class="meta">${escapeHtml(introZh)}</p>
819
845
 
820
846
  <h2>Summary</h2>
821
847
  <p>${escapeHtml(probe?.summary || 'Codex startup probe failed.')}</p>
@@ -838,6 +864,10 @@ function writeCodexPreflightReport(home, probe) {
838
864
  <dt>Model</dt>
839
865
  <dd>${escapeHtml(details.model || '')}</dd>
840
866
  </dl>
867
+ <dl class="kv">
868
+ <dt>Profile</dt>
869
+ <dd>${escapeHtml(details.profile || '')}</dd>
870
+ </dl>
841
871
  <dl class="kv">
842
872
  <dt>Exit code</dt>
843
873
  <dd>${escapeHtml(details.exit_code ?? '')}</dd>
@@ -950,6 +980,7 @@ function parseLauncherArgs(argv) {
950
980
  let daemonOnly = false;
951
981
  let skipUpdateCheck = false;
952
982
  let yolo = false;
983
+ let codexProfile = null;
953
984
 
954
985
  if (args[0] === 'ui') {
955
986
  args.shift();
@@ -969,6 +1000,7 @@ function parseLauncherArgs(argv) {
969
1000
  else if (arg === '--daemon-only') daemonOnly = true;
970
1001
  else if (arg === '--skip-update-check') skipUpdateCheck = true;
971
1002
  else if (arg === '--yolo') yolo = true;
1003
+ else if (arg === '--codex-profile' && args[index + 1]) codexProfile = args[++index];
972
1004
  else if (arg === '--host' && args[index + 1]) host = args[++index];
973
1005
  else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
974
1006
  else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
@@ -994,6 +1026,7 @@ function parseLauncherArgs(argv) {
994
1026
  daemonOnly,
995
1027
  skipUpdateCheck,
996
1028
  yolo,
1029
+ codexProfile,
997
1030
  };
998
1031
  }
999
1032
 
@@ -2284,6 +2317,10 @@ function normalizePythonCliArgs(args, home) {
2284
2317
  if (arg === '--yolo') {
2285
2318
  continue;
2286
2319
  }
2320
+ if (arg === '--codex-profile') {
2321
+ index += 1;
2322
+ continue;
2323
+ }
2287
2324
  normalized.push(arg);
2288
2325
  }
2289
2326
  return ['--home', home, ...normalized];
@@ -2492,6 +2529,270 @@ function removeDaemonState(home) {
2492
2529
  }
2493
2530
  }
2494
2531
 
2532
+ function daemonSupervisorLogPath(home) {
2533
+ return path.join(home, 'logs', 'daemon-supervisor.log');
2534
+ }
2535
+
2536
+ function appendDaemonSupervisorLog(home, message) {
2537
+ try {
2538
+ const logPath = daemonSupervisorLogPath(home);
2539
+ ensureDir(path.dirname(logPath));
2540
+ fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${String(message || '').trim()}\n`, 'utf8');
2541
+ } catch {}
2542
+ }
2543
+
2544
+ function observeManagedDaemonChild(home, child, daemonId) {
2545
+ if (!child || typeof child.once !== 'function') {
2546
+ return;
2547
+ }
2548
+ const normalizedDaemonId = String(daemonId || '').trim() || 'unknown';
2549
+ child.once('exit', (code, signal) => {
2550
+ appendDaemonSupervisorLog(
2551
+ home,
2552
+ `daemon ${normalizedDaemonId} exited with code=${code === null ? 'null' : code} signal=${signal || 'null'}`
2553
+ );
2554
+ });
2555
+ child.once('error', (error) => {
2556
+ appendDaemonSupervisorLog(
2557
+ home,
2558
+ `daemon ${normalizedDaemonId} child process error: ${error instanceof Error ? error.message : String(error)}`
2559
+ );
2560
+ });
2561
+ }
2562
+
2563
+ function encodeSupervisorEnvPayload(envOverrides) {
2564
+ const payload = envOverrides && typeof envOverrides === 'object' && !Array.isArray(envOverrides) ? envOverrides : {};
2565
+ return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
2566
+ }
2567
+
2568
+ function decodeSupervisorEnvPayload(rawValue) {
2569
+ const normalized = String(rawValue || '').trim();
2570
+ if (!normalized) {
2571
+ return {};
2572
+ }
2573
+ try {
2574
+ const parsed = JSON.parse(Buffer.from(normalized, 'base64').toString('utf8'));
2575
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
2576
+ } catch {
2577
+ return {};
2578
+ }
2579
+ }
2580
+
2581
+ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId = null }) {
2582
+ const browserUrl = browserUiUrl(host, port);
2583
+ const daemonBindUrl = bindUiUrl(host, port);
2584
+ const logPath = path.join(home, 'logs', 'daemon.log');
2585
+ ensureDir(path.dirname(logPath));
2586
+ const out = fs.openSync(logPath, 'a');
2587
+ const resolvedDaemonId = String(daemonId || crypto.randomUUID()).trim();
2588
+ const launcherPath = path.join(repoRoot, 'bin', 'ds.js');
2589
+ const child = spawn(
2590
+ runtimePython,
2591
+ [
2592
+ '-m',
2593
+ 'deepscientist.cli',
2594
+ '--home',
2595
+ home,
2596
+ ...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
2597
+ 'daemon',
2598
+ '--host',
2599
+ host,
2600
+ '--port',
2601
+ String(port),
2602
+ ],
2603
+ {
2604
+ cwd: repoRoot,
2605
+ detached: true,
2606
+ stdio: ['ignore', out, out],
2607
+ env: {
2608
+ ...process.env,
2609
+ ...envOverrides,
2610
+ DEEPSCIENTIST_REPO_ROOT: repoRoot,
2611
+ DEEPSCIENTIST_NODE_BINARY: process.execPath,
2612
+ DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
2613
+ DS_DAEMON_ID: resolvedDaemonId,
2614
+ DS_DAEMON_MANAGED_BY: 'ds-launcher',
2615
+ },
2616
+ }
2617
+ );
2618
+ child.unref();
2619
+ const statePayload = {
2620
+ pid: child.pid,
2621
+ host,
2622
+ port,
2623
+ url: browserUrl,
2624
+ bind_url: daemonBindUrl,
2625
+ log_path: logPath,
2626
+ started_at: new Date().toISOString(),
2627
+ home: normalizeHomePath(home),
2628
+ daemon_id: resolvedDaemonId,
2629
+ };
2630
+ writeDaemonState(home, statePayload);
2631
+ return {
2632
+ child,
2633
+ statePayload,
2634
+ browserUrl,
2635
+ bindUrl: daemonBindUrl,
2636
+ logPath,
2637
+ };
2638
+ }
2639
+
2640
+ function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId }) {
2641
+ const launcherPath = resolveLauncherPath() || path.join(repoRoot, 'bin', 'ds.js');
2642
+ const args = [
2643
+ launcherPath,
2644
+ '--daemon-supervisor',
2645
+ '--home',
2646
+ home,
2647
+ '--runtime-python',
2648
+ runtimePython,
2649
+ '--host',
2650
+ host,
2651
+ '--port',
2652
+ String(port),
2653
+ '--daemon-id',
2654
+ String(daemonId || '').trim(),
2655
+ ];
2656
+ const normalizedProxy = normalizeProxyUrl(proxy);
2657
+ if (normalizedProxy) {
2658
+ args.push('--proxy', normalizedProxy);
2659
+ }
2660
+ const envPayload = encodeSupervisorEnvPayload(envOverrides);
2661
+ if (envPayload) {
2662
+ args.push('--env-json', envPayload);
2663
+ }
2664
+ const child = spawn(process.execPath, args, {
2665
+ cwd: repoRoot,
2666
+ detached: true,
2667
+ stdio: 'ignore',
2668
+ env: {
2669
+ ...process.env,
2670
+ DEEPSCIENTIST_REPO_ROOT: repoRoot,
2671
+ DEEPSCIENTIST_NODE_BINARY: process.execPath,
2672
+ DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
2673
+ },
2674
+ });
2675
+ child.unref();
2676
+ return child.pid || null;
2677
+ }
2678
+
2679
+ function parseDaemonSupervisorArgs(argv) {
2680
+ const args = [...argv];
2681
+ let home = null;
2682
+ let runtimePython = null;
2683
+ let host = '0.0.0.0';
2684
+ let port = 20999;
2685
+ let proxy = null;
2686
+ let daemonId = null;
2687
+ let envJson = '';
2688
+
2689
+ for (let index = 0; index < args.length; index += 1) {
2690
+ const arg = args[index];
2691
+ if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
2692
+ else if (arg === '--runtime-python' && args[index + 1]) runtimePython = args[++index];
2693
+ else if (arg === '--host' && args[index + 1]) host = args[++index];
2694
+ else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
2695
+ else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
2696
+ else if (arg === '--daemon-id' && args[index + 1]) daemonId = args[++index];
2697
+ else if (arg === '--env-json' && args[index + 1]) envJson = args[++index];
2698
+ else if (arg === '--help' || arg === '-h') return { help: true };
2699
+ else return null;
2700
+ }
2701
+
2702
+ if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
2703
+ return null;
2704
+ }
2705
+
2706
+ return {
2707
+ help: false,
2708
+ home,
2709
+ runtimePython,
2710
+ host,
2711
+ port,
2712
+ proxy,
2713
+ daemonId,
2714
+ envOverrides: decodeSupervisorEnvPayload(envJson),
2715
+ };
2716
+ }
2717
+
2718
+ async function daemonSupervisorMain(rawArgs) {
2719
+ const options = parseDaemonSupervisorArgs(rawArgs);
2720
+ if (!options) {
2721
+ console.error('Invalid daemon supervisor arguments.');
2722
+ process.exit(1);
2723
+ }
2724
+ if (options.help) {
2725
+ process.exit(0);
2726
+ }
2727
+
2728
+ const home = options.home;
2729
+ let trackedDaemonId = String(options.daemonId || '').trim();
2730
+ let restartBackoffMs = 1000;
2731
+ appendDaemonSupervisorLog(home, `supervisor started for daemon ${trackedDaemonId}`);
2732
+
2733
+ while (true) {
2734
+ const state = readDaemonState(home);
2735
+ if (!state) {
2736
+ appendDaemonSupervisorLog(home, 'daemon state removed; supervisor exiting');
2737
+ return;
2738
+ }
2739
+ if (state.shutdown_requested_at) {
2740
+ appendDaemonSupervisorLog(home, 'managed shutdown requested; supervisor exiting');
2741
+ return;
2742
+ }
2743
+ const stateHome = normalizeHomePath(state.home || home);
2744
+ if (stateHome !== normalizeHomePath(home)) {
2745
+ appendDaemonSupervisorLog(home, `daemon state home changed to ${stateHome}; supervisor exiting`);
2746
+ return;
2747
+ }
2748
+ const stateDaemonId = String(state.daemon_id || '').trim();
2749
+ if (trackedDaemonId && stateDaemonId && stateDaemonId !== trackedDaemonId) {
2750
+ appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
2751
+ return;
2752
+ }
2753
+ const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port));
2754
+ if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
2755
+ restartBackoffMs = 1000;
2756
+ await sleep(2500);
2757
+ continue;
2758
+ }
2759
+ if (state.pid && isPidAlive(state.pid)) {
2760
+ await sleep(2500);
2761
+ continue;
2762
+ }
2763
+
2764
+ appendDaemonSupervisorLog(
2765
+ home,
2766
+ `daemon ${stateDaemonId || trackedDaemonId || 'unknown'} is not healthy; attempting restart`
2767
+ );
2768
+ try {
2769
+ const restarted = spawnManagedDaemonProcess({
2770
+ home,
2771
+ runtimePython: options.runtimePython,
2772
+ host: options.host,
2773
+ port: options.port,
2774
+ proxy: options.proxy,
2775
+ envOverrides: options.envOverrides,
2776
+ });
2777
+ trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
2778
+ observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
2779
+ appendDaemonSupervisorLog(
2780
+ home,
2781
+ `restarted daemon ${trackedDaemonId} with pid ${restarted.statePayload.pid}`
2782
+ );
2783
+ restartBackoffMs = 1000;
2784
+ await sleep(2500);
2785
+ } catch (error) {
2786
+ appendDaemonSupervisorLog(
2787
+ home,
2788
+ `restart failed: ${error instanceof Error ? error.message : String(error)}`
2789
+ );
2790
+ await sleep(restartBackoffMs);
2791
+ restartBackoffMs = Math.min(restartBackoffMs * 2, 30000);
2792
+ }
2793
+ }
2794
+ }
2795
+
2495
2796
  function sleep(ms) {
2496
2797
  return new Promise((resolve) => setTimeout(resolve, ms));
2497
2798
  }
@@ -2663,6 +2964,13 @@ async function stopDaemon(home) {
2663
2964
  }
2664
2965
  }
2665
2966
 
2967
+ if (state) {
2968
+ writeDaemonState(home, {
2969
+ ...state,
2970
+ shutdown_requested_at: new Date().toISOString(),
2971
+ });
2972
+ }
2973
+
2666
2974
  let stopped = false;
2667
2975
 
2668
2976
  if (healthyBefore) {
@@ -3253,61 +3561,36 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
3253
3561
  }
3254
3562
 
3255
3563
  ensureNodeBundle('src/ui', 'dist/index.html');
3256
-
3257
- const logPath = path.join(home, 'logs', 'daemon.log');
3258
- ensureDir(path.dirname(logPath));
3259
- const out = fs.openSync(logPath, 'a');
3260
- const daemonId = crypto.randomUUID();
3261
- const child = spawn(
3564
+ const startedProcess = spawnManagedDaemonProcess({
3565
+ home,
3262
3566
  runtimePython,
3263
- [
3264
- '-m',
3265
- 'deepscientist.cli',
3266
- '--home',
3267
- home,
3268
- ...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
3269
- 'daemon',
3270
- '--host',
3271
- host,
3272
- '--port',
3273
- String(port),
3274
- ],
3275
- {
3276
- cwd: repoRoot,
3277
- detached: true,
3278
- stdio: ['ignore', out, out],
3279
- env: {
3280
- ...process.env,
3281
- ...envOverrides,
3282
- DEEPSCIENTIST_REPO_ROOT: repoRoot,
3283
- DEEPSCIENTIST_NODE_BINARY: process.execPath,
3284
- DEEPSCIENTIST_LAUNCHER_PATH: path.join(repoRoot, 'bin', 'ds.js'),
3285
- DS_DAEMON_ID: daemonId,
3286
- DS_DAEMON_MANAGED_BY: 'ds-launcher',
3287
- },
3288
- }
3289
- );
3290
- child.unref();
3291
- const statePayload = {
3292
- pid: child.pid,
3293
3567
  host,
3294
3568
  port,
3295
- url: browserUrl,
3296
- bind_url: daemonBindUrl,
3297
- log_path: logPath,
3298
- started_at: new Date().toISOString(),
3299
- home: normalizeHomePath(home),
3300
- daemon_id: daemonId,
3301
- };
3302
- writeDaemonState(home, statePayload);
3569
+ proxy,
3570
+ envOverrides,
3571
+ });
3572
+ const logPath = startedProcess.logPath;
3303
3573
 
3304
3574
  for (let attempt = 0; attempt < 60; attempt += 1) {
3305
3575
  const health = await fetchHealth(browserUrl);
3306
3576
  if (health && health.status === 'ok') {
3307
- if (!healthMatchesManagedState({ health, state: readDaemonState(home), home })) {
3308
- console.error(daemonIdentityError({ url: browserUrl, home, health, state: readDaemonState(home) }));
3577
+ const liveState = readDaemonState(home);
3578
+ if (!healthMatchesManagedState({ health, state: liveState, home })) {
3579
+ console.error(daemonIdentityError({ url: browserUrl, home, health, state: liveState }));
3309
3580
  process.exit(1);
3310
3581
  }
3582
+ const supervisorPid = spawnDaemonSupervisor({
3583
+ home,
3584
+ runtimePython,
3585
+ host,
3586
+ port,
3587
+ proxy,
3588
+ envOverrides,
3589
+ daemonId: String((liveState || {}).daemon_id || ''),
3590
+ });
3591
+ if (supervisorPid) {
3592
+ appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
3593
+ }
3311
3594
  return { url: browserUrl, bindUrl: daemonBindUrl, reused: false };
3312
3595
  }
3313
3596
  await sleep(250);
@@ -3380,11 +3663,18 @@ function handleCodexPreflightFailure(error) {
3380
3663
  }
3381
3664
  }
3382
3665
  console.error(`${warningLabel} Recommended fix:`);
3383
- console.error(`${warningLabel} 1. In most installs, \`npm install -g @researai/deepscientist\` also installs the bundled Codex dependency.`);
3384
- console.error(`${warningLabel} 2. If \`codex\` is still missing, run \`npm install -g @openai/codex\`.`);
3385
- console.error(`${warningLabel} 3. Run \`codex --login\` (or \`codex\`) and finish authentication.`);
3386
- console.error(`${warningLabel} 4. Run \`ds doctor\` and confirm the Codex check passes.`);
3387
- console.error(`${warningLabel} 5. Run \`ds\` again.`);
3666
+ const guidance = Array.isArray(error.probe?.guidance) && error.probe.guidance.length > 0
3667
+ ? error.probe.guidance
3668
+ : [
3669
+ 'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
3670
+ 'If `codex` is still missing, run `npm install -g @openai/codex`.',
3671
+ 'Run `codex --login` (or `codex`) and finish authentication.',
3672
+ 'Run `ds doctor` and confirm the Codex check passes.',
3673
+ 'Run `ds` again.',
3674
+ ];
3675
+ guidance.forEach((item, index) => {
3676
+ console.error(`${warningLabel} ${index + 1}. ${item}`);
3677
+ });
3388
3678
  openBrowser(error.reportUrl);
3389
3679
  process.exit(1);
3390
3680
  return true;
@@ -3712,7 +4002,7 @@ async function launcherMain(rawArgs) {
3712
4002
 
3713
4003
  const pythonRuntime = ensurePythonRuntime(home);
3714
4004
  const runtimePython = pythonRuntime.runtimePython;
3715
- const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo });
4005
+ const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo, profile: options.codexProfile });
3716
4006
  ensureInitialized(home, runtimePython);
3717
4007
  if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
3718
4008
  return true;
@@ -3765,6 +4055,10 @@ async function launcherMain(rawArgs) {
3765
4055
 
3766
4056
  async function main() {
3767
4057
  const args = process.argv.slice(2);
4058
+ if (args[0] === '--daemon-supervisor') {
4059
+ await daemonSupervisorMain(args.slice(1));
4060
+ return;
4061
+ }
3768
4062
  const positional = findFirstPositionalArg(args);
3769
4063
  if (positional && positional.value === 'update') {
3770
4064
  await updateMain(args);
@@ -3786,7 +4080,10 @@ async function main() {
3786
4080
  const home = resolveHome(args);
3787
4081
  const pythonRuntime = ensurePythonRuntime(home);
3788
4082
  const runtimePython = pythonRuntime.runtimePython;
3789
- const codexOverrideEnv = buildCodexOverrideEnv({ yolo: args.includes('--yolo') });
4083
+ const codexOverrideEnv = buildCodexOverrideEnv({
4084
+ yolo: args.includes('--yolo'),
4085
+ profile: readOptionValue(args, '--codex-profile'),
4086
+ });
3790
4087
  if (positional.value === 'run' || positional.value === 'daemon') {
3791
4088
  maybePrintOptionalLatexNotice(home);
3792
4089
  }
@@ -36,7 +36,9 @@ See the full notice here:
36
36
  Prepare these first:
37
37
 
38
38
  - Node.js `>=18.18` and npm `>=9`; install them from the official download page: https://nodejs.org/en/download
39
- - a working Codex CLI setup; before the first `ds`, run `codex --login` (or `codex`) and finish authentication
39
+ - one working Codex path:
40
+ - default OpenAI login path: `codex --login` (or `codex`)
41
+ - provider-backed path: one working Codex profile such as `minimax`, `glm`, `ark`, or `bailian`
40
42
  - a model or API credential if your project needs external inference
41
43
  - GPU or server access if your experiments are compute-heavy
42
44
  - if you plan to run DeepScientist for real work, prepare Docker or another isolated environment and a dedicated non-root user
@@ -52,6 +54,10 @@ If you are still choosing a coding plan or subscription, these are practical sta
52
54
  - Alibaba Cloud Bailian Coding Plan: https://help.aliyun.com/zh/model-studio/coding-plan
53
55
  - Volcengine Ark Coding Plan: https://www.volcengine.com/docs/82379/1925115?lang=zh
54
56
 
57
+ If you plan to use a provider-backed Codex profile instead of the default OpenAI login flow, read this next:
58
+
59
+ - [15 Codex Provider Setup](./15_CODEX_PROVIDER_SETUP.md)
60
+
55
61
  ## 1. Install Node.js and DeepScientist
56
62
 
57
63
  DeepScientist currently supports Linux and macOS only.
@@ -89,6 +95,10 @@ This installs a lightweight TinyTeX runtime for local paper compilation.
89
95
 
90
96
  ## 2. Finish Codex Setup Before The First `ds`
91
97
 
98
+ Choose one of these two paths.
99
+
100
+ ### 2.1 Default OpenAI login path
101
+
92
102
  Run:
93
103
 
94
104
  ```bash
@@ -103,13 +113,35 @@ codex
103
113
 
104
114
  and finish the interactive authentication there.
105
115
 
106
- Then verify the environment before startup:
116
+ Then verify:
107
117
 
108
118
  ```bash
109
119
  ds doctor
110
120
  ```
111
121
 
112
- DeepScientist blocks startup until Codex can pass a real hello probe. In the current release, that probe first tries the runner model configured in `~/DeepScientist/config/runners.yaml`, which defaults to `gpt-5.4`. If that model is unavailable to your Codex setup, DeepScientist falls back to the current Codex default model and persists `model: inherit` for future runs.
122
+ ### 2.2 Provider-backed Codex profile path
123
+
124
+ If you already use a named Codex profile for MiniMax, GLM, Volcengine Ark, Alibaba Bailian, or another provider-backed path, verify that profile first in a terminal:
125
+
126
+ ```bash
127
+ codex --profile minimax
128
+ ```
129
+
130
+ Then run DeepScientist through the same profile:
131
+
132
+ ```bash
133
+ ds doctor --codex-profile minimax
134
+ ```
135
+
136
+ and later:
137
+
138
+ ```bash
139
+ ds --codex-profile minimax
140
+ ```
141
+
142
+ Replace `minimax` with the real profile name you created, such as `m27`, `glm`, `ark`, or `bailian`.
143
+
144
+ DeepScientist blocks startup until Codex can pass a real hello probe. By default, the runner model in `~/DeepScientist/config/runners.yaml` is `gpt-5.4`. If your profile expects the model to come from the profile itself, use `model: inherit` in `runners.yaml`, or simply launch with `--codex-profile <name>` and let that session inherit the profile-defined model.
113
145
 
114
146
  ## 3. Start the Local Runtime
115
147
 
@@ -392,6 +392,7 @@ codex:
392
392
  enabled: true
393
393
  binary: codex
394
394
  config_dir: ~/.codex
395
+ profile: ""
395
396
  model: gpt-5.4
396
397
  model_reasoning_effort: xhigh
397
398
  approval_policy: on-request
@@ -439,6 +440,15 @@ claude:
439
440
  - UI label: `Config directory`
440
441
  - Meaning: global runner home for auth and global config.
441
442
 
443
+ **`profile`**
444
+
445
+ - Type: `string`
446
+ - Default: `""`
447
+ - UI label: `Codex profile`
448
+ - Meaning: optional Codex profile name passed through as `codex --profile <name>`.
449
+ - Use this when your Codex CLI is already configured for a provider-backed setup such as MiniMax, GLM, Volcengine Ark, or Alibaba Bailian.
450
+ - One-off note: you can also leave this field empty and launch with `ds --codex-profile <name>`.
451
+
442
452
  **`model`**
443
453
 
444
454
  - Type: `string`
@@ -446,6 +456,7 @@ claude:
446
456
  - UI label: `Default model`
447
457
  - Meaning: default model used when a project does not override it.
448
458
  - Startup note: DeepScientist's Codex readiness probe uses this configured model first. If your Codex account cannot access it, DeepScientist falls back to the current Codex default model and persists `model: inherit`.
459
+ - Provider-profile note: when `profile` is set, `model: inherit` is usually the right choice so the Codex profile itself controls the provider model.
449
460
 
450
461
  **`model_reasoning_effort`**
451
462