@researai/deepscientist 1.5.11 → 1.5.13
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 +8 -8
- package/bin/ds.js +375 -61
- package/docs/en/00_QUICK_START.md +55 -4
- package/docs/en/01_SETTINGS_REFERENCE.md +15 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +68 -4
- package/docs/en/09_DOCTOR.md +48 -4
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +21 -2
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +382 -0
- package/docs/en/README.md +4 -0
- package/docs/zh/00_QUICK_START.md +54 -3
- package/docs/zh/01_SETTINGS_REFERENCE.md +15 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +69 -3
- package/docs/zh/09_DOCTOR.md +48 -2
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +21 -2
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +383 -0
- package/docs/zh/README.md +4 -1
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/bash_exec/monitor.py +7 -5
- package/src/deepscientist/bash_exec/service.py +84 -21
- package/src/deepscientist/channels/local.py +3 -3
- package/src/deepscientist/channels/qq.py +7 -7
- package/src/deepscientist/channels/relay.py +7 -7
- package/src/deepscientist/channels/weixin_ilink.py +90 -19
- package/src/deepscientist/cli.py +3 -0
- package/src/deepscientist/codex_cli_compat.py +117 -0
- package/src/deepscientist/config/models.py +1 -0
- package/src/deepscientist/config/service.py +173 -25
- package/src/deepscientist/daemon/app.py +314 -6
- package/src/deepscientist/doctor.py +1 -5
- package/src/deepscientist/mcp/server.py +124 -3
- package/src/deepscientist/prompts/builder.py +113 -11
- package/src/deepscientist/quest/service.py +247 -31
- package/src/deepscientist/runners/codex.py +132 -24
- package/src/deepscientist/runners/runtime_overrides.py +9 -0
- package/src/deepscientist/shared.py +33 -14
- package/src/prompts/connectors/qq.md +2 -1
- package/src/prompts/connectors/weixin.md +2 -1
- package/src/prompts/contracts/shared_interaction.md +4 -1
- package/src/prompts/system.md +59 -9
- package/src/skills/analysis-campaign/SKILL.md +46 -6
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
- package/src/skills/baseline/SKILL.md +1 -1
- package/src/skills/baseline/references/artifact-payload-examples.md +39 -0
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +1 -1
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +1 -1
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +74 -1
- package/src/skills/rebuttal/references/response-letter-template.md +55 -11
- package/src/skills/review/SKILL.md +118 -1
- package/src/skills/review/references/experiment-todo-template.md +23 -0
- package/src/skills/review/references/review-report-template.md +16 -0
- package/src/skills/review/references/revision-log-template.md +4 -0
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +168 -7
- package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
- package/src/tui/dist/lib/connectorConfig.js +90 -0
- package/src/tui/dist/lib/qr.js +21 -0
- package/src/tui/package.json +2 -1
- package/src/ui/dist/assets/{AiManusChatView-D0mTXG4-.js → AiManusChatView-CnJcXynW.js} +12 -12
- package/src/ui/dist/assets/{AnalysisPlugin-Db0cTXxm.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-DrV8je02.js → CliPlugin-CB1YODQn.js} +9 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-QXMSCH71.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-7hhtWj_E.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-BWMSnRJe.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-7J9h9Vy_.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -20
- package/src/ui/dist/assets/{ImageViewerPlugin-CHJl_0lr.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-1qSow1es.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-eQpPPCEp.js → LabPlugin-Ciz1gDaX.js} +2 -2
- package/src/ui/dist/assets/{LatexPlugin-BwRfi89Z.js → LatexPlugin-BhmjNQRC.js} +37 -11
- package/src/ui/dist/assets/{MarkdownViewerPlugin-836PVQWV.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-C2y_556i.js → MarketplacePlugin-DmyHspXt.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-DIX7Mlzu.js → NotebookEditor-BMXKrDRk.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-BRzJbGsn.js → NotebookEditor-BTVYRGkm.js} +11 -11
- package/src/ui/dist/assets/{PdfLoader-DzRaTAlq.js → PdfLoader-CvcjJHXv.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-DZUfIUnp.js → PdfMarkdownPlugin-DW2ej8Vk.js} +2 -2
- package/src/ui/dist/assets/{PdfViewerPlugin-BwtICzue.js → PdfViewerPlugin-CmlDxbhU.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-DHeIAMsx.js → SearchPlugin-DAjQZPSv.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-C3tCmFox.js → TextViewerPlugin-C-nVAZb_.js} +5 -5
- package/src/ui/dist/assets/{VNCViewer-CQsKVm3t.js → VNCViewer-D7-dIYon.js} +10 -10
- package/src/ui/dist/assets/{bot-BEA2vWuK.js → bot-C_G4WtNI.js} +1 -1
- package/src/ui/dist/assets/{code-XfbSR8K2.js → code-Cd7WfiWq.js} +1 -1
- package/src/ui/dist/assets/{file-content-BjxNaIfy.js → file-content-B57zsL9y.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-D_lLVQk0.js → file-diff-panel-DVoheLFq.js} +1 -1
- package/src/ui/dist/assets/{file-socket-D9x_5vlY.js → file-socket-B5kXFxZP.js} +1 -1
- package/src/ui/dist/assets/{image-BhWT33W1.js → image-LLOjkMHF.js} +1 -1
- package/src/ui/dist/assets/{index-Dqj-Mjb4.css → index-BQG-1s2o.css} +40 -2
- package/src/ui/dist/assets/{index--c4iXtuy.js → index-C3r2iGrp.js} +12 -12
- package/src/ui/dist/assets/{index-DZTZ8mWP.js → index-CLQauncb.js} +911 -120
- package/src/ui/dist/assets/{index-PJbSbPTy.js → index-Dxa2eYMY.js} +1 -1
- package/src/ui/dist/assets/{index-BDxipwrC.js → index-hOUOWbW2.js} +2 -2
- package/src/ui/dist/assets/{monaco-K8izTGgo.js → monaco-BGGAEii3.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DfBors6y.js → pdf-effect-queue-DlEr1_y5.js} +1 -1
- package/src/ui/dist/assets/{popover-yFK1J4fL.js → popover-CWJbJuYY.js} +1 -1
- package/src/ui/dist/assets/{project-sync-PENr2zcz.js → project-sync-CRJiucYO.js} +18 -4
- package/src/ui/dist/assets/{select-CAbJDfYv.js → select-CoHB7pvH.js} +2 -2
- package/src/ui/dist/assets/{sigma-DEuYJqTl.js → sigma-D5aJWR8J.js} +1 -1
- package/src/ui/dist/assets/{square-check-big-omoSUmcd.js → square-check-big-DUK_mnkS.js} +1 -1
- package/src/ui/dist/assets/{trash--F119N47.js → trash-ChU3SEE3.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-D31UR23I.js → useCliAccess-BrJBV3tY.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-BH6KcMzq.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-CZ613PM5.js → wrap-text-C7Qqh-om.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgDLAv3z.js → zoom-out-rtX0FKya.js} +1 -1
- package/src/ui/dist/index.html +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="assets/branding/logo.svg" alt="DeepScientist logo" width="84" />
|
|
3
|
+
DeepScientist
|
|
4
|
+
</h1>
|
|
2
5
|
|
|
3
|
-
<p
|
|
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
|
|
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,37 @@ 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', '--codex']);
|
|
40
40
|
|
|
41
|
-
function buildCodexOverrideEnv({ yolo = false } = {}) {
|
|
41
|
+
function buildCodexOverrideEnv({ yolo = false, profile = null, binary = null } = {}) {
|
|
42
|
+
const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
|
|
43
|
+
const normalizedBinary = typeof binary === 'string' ? binary.trim() : '';
|
|
44
|
+
const overrides = {};
|
|
45
|
+
if (normalizedBinary) {
|
|
46
|
+
overrides.DEEPSCIENTIST_CODEX_BINARY = normalizedBinary;
|
|
47
|
+
}
|
|
42
48
|
if (!yolo) {
|
|
43
|
-
|
|
49
|
+
if (normalizedProfile) {
|
|
50
|
+
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
51
|
+
overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
|
|
52
|
+
}
|
|
53
|
+
return overrides;
|
|
44
54
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
overrides.DEEPSCIENTIST_CODEX_YOLO = '1';
|
|
56
|
+
if (normalizedProfile) {
|
|
57
|
+
overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
|
|
58
|
+
overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
|
|
59
|
+
}
|
|
60
|
+
return overrides;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readOptionValue(argv, optionName) {
|
|
64
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
65
|
+
if (argv[index] === optionName && argv[index + 1]) {
|
|
66
|
+
return argv[index + 1];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
48
70
|
}
|
|
49
71
|
|
|
50
72
|
function printLauncherHelp() {
|
|
@@ -84,6 +106,8 @@ Launcher flags:
|
|
|
84
106
|
--here Create/use ./DeepScientist under the current working directory as home
|
|
85
107
|
--proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
|
|
86
108
|
--yolo Run Codex in YOLO mode: approval_policy=never and sandbox_mode=danger-full-access
|
|
109
|
+
--codex-profile <id> Run DeepScientist with a specific Codex profile, for example \`m27\`
|
|
110
|
+
--codex <path> Run DeepScientist with a specific Codex executable path for this launch
|
|
87
111
|
--quest-id <id> Open the TUI on one quest directly
|
|
88
112
|
|
|
89
113
|
Update:
|
|
@@ -754,6 +778,13 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
754
778
|
const errors = Array.isArray(probe?.errors) ? probe.errors : [];
|
|
755
779
|
const guidance = Array.isArray(probe?.guidance) ? probe.guidance : [];
|
|
756
780
|
const details = probe && typeof probe.details === 'object' ? probe.details : {};
|
|
781
|
+
const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
|
|
782
|
+
const intro = profile
|
|
783
|
+
? `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.`
|
|
784
|
+
: '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.';
|
|
785
|
+
const introZh = profile
|
|
786
|
+
? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
|
|
787
|
+
: 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex --login`(或 `codex`)完成认证,再执行 `ds doctor`,最后重新启动 `ds`。';
|
|
757
788
|
const renderItems = (items, tone) =>
|
|
758
789
|
items
|
|
759
790
|
.map(
|
|
@@ -814,8 +845,8 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
814
845
|
<main class="page">
|
|
815
846
|
<section class="panel">
|
|
816
847
|
<h1>DeepScientist could not start Codex</h1>
|
|
817
|
-
<p class="meta"
|
|
818
|
-
<p class="meta"
|
|
848
|
+
<p class="meta">${escapeHtml(intro)}</p>
|
|
849
|
+
<p class="meta">${escapeHtml(introZh)}</p>
|
|
819
850
|
|
|
820
851
|
<h2>Summary</h2>
|
|
821
852
|
<p>${escapeHtml(probe?.summary || 'Codex startup probe failed.')}</p>
|
|
@@ -838,6 +869,10 @@ function writeCodexPreflightReport(home, probe) {
|
|
|
838
869
|
<dt>Model</dt>
|
|
839
870
|
<dd>${escapeHtml(details.model || '')}</dd>
|
|
840
871
|
</dl>
|
|
872
|
+
<dl class="kv">
|
|
873
|
+
<dt>Profile</dt>
|
|
874
|
+
<dd>${escapeHtml(details.profile || '')}</dd>
|
|
875
|
+
</dl>
|
|
841
876
|
<dl class="kv">
|
|
842
877
|
<dt>Exit code</dt>
|
|
843
878
|
<dd>${escapeHtml(details.exit_code ?? '')}</dd>
|
|
@@ -950,6 +985,8 @@ function parseLauncherArgs(argv) {
|
|
|
950
985
|
let daemonOnly = false;
|
|
951
986
|
let skipUpdateCheck = false;
|
|
952
987
|
let yolo = false;
|
|
988
|
+
let codexProfile = null;
|
|
989
|
+
let codexBinary = null;
|
|
953
990
|
|
|
954
991
|
if (args[0] === 'ui') {
|
|
955
992
|
args.shift();
|
|
@@ -969,6 +1006,8 @@ function parseLauncherArgs(argv) {
|
|
|
969
1006
|
else if (arg === '--daemon-only') daemonOnly = true;
|
|
970
1007
|
else if (arg === '--skip-update-check') skipUpdateCheck = true;
|
|
971
1008
|
else if (arg === '--yolo') yolo = true;
|
|
1009
|
+
else if (arg === '--codex-profile' && args[index + 1]) codexProfile = args[++index];
|
|
1010
|
+
else if (arg === '--codex' && args[index + 1]) codexBinary = args[++index];
|
|
972
1011
|
else if (arg === '--host' && args[index + 1]) host = args[++index];
|
|
973
1012
|
else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
|
|
974
1013
|
else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
|
|
@@ -994,6 +1033,8 @@ function parseLauncherArgs(argv) {
|
|
|
994
1033
|
daemonOnly,
|
|
995
1034
|
skipUpdateCheck,
|
|
996
1035
|
yolo,
|
|
1036
|
+
codexProfile,
|
|
1037
|
+
codexBinary,
|
|
997
1038
|
};
|
|
998
1039
|
}
|
|
999
1040
|
|
|
@@ -2284,6 +2325,14 @@ function normalizePythonCliArgs(args, home) {
|
|
|
2284
2325
|
if (arg === '--yolo') {
|
|
2285
2326
|
continue;
|
|
2286
2327
|
}
|
|
2328
|
+
if (arg === '--codex-profile') {
|
|
2329
|
+
index += 1;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (arg === '--codex') {
|
|
2333
|
+
index += 1;
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2287
2336
|
normalized.push(arg);
|
|
2288
2337
|
}
|
|
2289
2338
|
return ['--home', home, ...normalized];
|
|
@@ -2492,6 +2541,270 @@ function removeDaemonState(home) {
|
|
|
2492
2541
|
}
|
|
2493
2542
|
}
|
|
2494
2543
|
|
|
2544
|
+
function daemonSupervisorLogPath(home) {
|
|
2545
|
+
return path.join(home, 'logs', 'daemon-supervisor.log');
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function appendDaemonSupervisorLog(home, message) {
|
|
2549
|
+
try {
|
|
2550
|
+
const logPath = daemonSupervisorLogPath(home);
|
|
2551
|
+
ensureDir(path.dirname(logPath));
|
|
2552
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${String(message || '').trim()}\n`, 'utf8');
|
|
2553
|
+
} catch {}
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
function observeManagedDaemonChild(home, child, daemonId) {
|
|
2557
|
+
if (!child || typeof child.once !== 'function') {
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
const normalizedDaemonId = String(daemonId || '').trim() || 'unknown';
|
|
2561
|
+
child.once('exit', (code, signal) => {
|
|
2562
|
+
appendDaemonSupervisorLog(
|
|
2563
|
+
home,
|
|
2564
|
+
`daemon ${normalizedDaemonId} exited with code=${code === null ? 'null' : code} signal=${signal || 'null'}`
|
|
2565
|
+
);
|
|
2566
|
+
});
|
|
2567
|
+
child.once('error', (error) => {
|
|
2568
|
+
appendDaemonSupervisorLog(
|
|
2569
|
+
home,
|
|
2570
|
+
`daemon ${normalizedDaemonId} child process error: ${error instanceof Error ? error.message : String(error)}`
|
|
2571
|
+
);
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function encodeSupervisorEnvPayload(envOverrides) {
|
|
2576
|
+
const payload = envOverrides && typeof envOverrides === 'object' && !Array.isArray(envOverrides) ? envOverrides : {};
|
|
2577
|
+
return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
function decodeSupervisorEnvPayload(rawValue) {
|
|
2581
|
+
const normalized = String(rawValue || '').trim();
|
|
2582
|
+
if (!normalized) {
|
|
2583
|
+
return {};
|
|
2584
|
+
}
|
|
2585
|
+
try {
|
|
2586
|
+
const parsed = JSON.parse(Buffer.from(normalized, 'base64').toString('utf8'));
|
|
2587
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
2588
|
+
} catch {
|
|
2589
|
+
return {};
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId = null }) {
|
|
2594
|
+
const browserUrl = browserUiUrl(host, port);
|
|
2595
|
+
const daemonBindUrl = bindUiUrl(host, port);
|
|
2596
|
+
const logPath = path.join(home, 'logs', 'daemon.log');
|
|
2597
|
+
ensureDir(path.dirname(logPath));
|
|
2598
|
+
const out = fs.openSync(logPath, 'a');
|
|
2599
|
+
const resolvedDaemonId = String(daemonId || crypto.randomUUID()).trim();
|
|
2600
|
+
const launcherPath = path.join(repoRoot, 'bin', 'ds.js');
|
|
2601
|
+
const child = spawn(
|
|
2602
|
+
runtimePython,
|
|
2603
|
+
[
|
|
2604
|
+
'-m',
|
|
2605
|
+
'deepscientist.cli',
|
|
2606
|
+
'--home',
|
|
2607
|
+
home,
|
|
2608
|
+
...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
|
|
2609
|
+
'daemon',
|
|
2610
|
+
'--host',
|
|
2611
|
+
host,
|
|
2612
|
+
'--port',
|
|
2613
|
+
String(port),
|
|
2614
|
+
],
|
|
2615
|
+
{
|
|
2616
|
+
cwd: repoRoot,
|
|
2617
|
+
detached: true,
|
|
2618
|
+
stdio: ['ignore', out, out],
|
|
2619
|
+
env: {
|
|
2620
|
+
...process.env,
|
|
2621
|
+
...envOverrides,
|
|
2622
|
+
DEEPSCIENTIST_REPO_ROOT: repoRoot,
|
|
2623
|
+
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
2624
|
+
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2625
|
+
DS_DAEMON_ID: resolvedDaemonId,
|
|
2626
|
+
DS_DAEMON_MANAGED_BY: 'ds-launcher',
|
|
2627
|
+
},
|
|
2628
|
+
}
|
|
2629
|
+
);
|
|
2630
|
+
child.unref();
|
|
2631
|
+
const statePayload = {
|
|
2632
|
+
pid: child.pid,
|
|
2633
|
+
host,
|
|
2634
|
+
port,
|
|
2635
|
+
url: browserUrl,
|
|
2636
|
+
bind_url: daemonBindUrl,
|
|
2637
|
+
log_path: logPath,
|
|
2638
|
+
started_at: new Date().toISOString(),
|
|
2639
|
+
home: normalizeHomePath(home),
|
|
2640
|
+
daemon_id: resolvedDaemonId,
|
|
2641
|
+
};
|
|
2642
|
+
writeDaemonState(home, statePayload);
|
|
2643
|
+
return {
|
|
2644
|
+
child,
|
|
2645
|
+
statePayload,
|
|
2646
|
+
browserUrl,
|
|
2647
|
+
bindUrl: daemonBindUrl,
|
|
2648
|
+
logPath,
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId }) {
|
|
2653
|
+
const launcherPath = resolveLauncherPath() || path.join(repoRoot, 'bin', 'ds.js');
|
|
2654
|
+
const args = [
|
|
2655
|
+
launcherPath,
|
|
2656
|
+
'--daemon-supervisor',
|
|
2657
|
+
'--home',
|
|
2658
|
+
home,
|
|
2659
|
+
'--runtime-python',
|
|
2660
|
+
runtimePython,
|
|
2661
|
+
'--host',
|
|
2662
|
+
host,
|
|
2663
|
+
'--port',
|
|
2664
|
+
String(port),
|
|
2665
|
+
'--daemon-id',
|
|
2666
|
+
String(daemonId || '').trim(),
|
|
2667
|
+
];
|
|
2668
|
+
const normalizedProxy = normalizeProxyUrl(proxy);
|
|
2669
|
+
if (normalizedProxy) {
|
|
2670
|
+
args.push('--proxy', normalizedProxy);
|
|
2671
|
+
}
|
|
2672
|
+
const envPayload = encodeSupervisorEnvPayload(envOverrides);
|
|
2673
|
+
if (envPayload) {
|
|
2674
|
+
args.push('--env-json', envPayload);
|
|
2675
|
+
}
|
|
2676
|
+
const child = spawn(process.execPath, args, {
|
|
2677
|
+
cwd: repoRoot,
|
|
2678
|
+
detached: true,
|
|
2679
|
+
stdio: 'ignore',
|
|
2680
|
+
env: {
|
|
2681
|
+
...process.env,
|
|
2682
|
+
DEEPSCIENTIST_REPO_ROOT: repoRoot,
|
|
2683
|
+
DEEPSCIENTIST_NODE_BINARY: process.execPath,
|
|
2684
|
+
DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
|
|
2685
|
+
},
|
|
2686
|
+
});
|
|
2687
|
+
child.unref();
|
|
2688
|
+
return child.pid || null;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
function parseDaemonSupervisorArgs(argv) {
|
|
2692
|
+
const args = [...argv];
|
|
2693
|
+
let home = null;
|
|
2694
|
+
let runtimePython = null;
|
|
2695
|
+
let host = '0.0.0.0';
|
|
2696
|
+
let port = 20999;
|
|
2697
|
+
let proxy = null;
|
|
2698
|
+
let daemonId = null;
|
|
2699
|
+
let envJson = '';
|
|
2700
|
+
|
|
2701
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2702
|
+
const arg = args[index];
|
|
2703
|
+
if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
|
|
2704
|
+
else if (arg === '--runtime-python' && args[index + 1]) runtimePython = args[++index];
|
|
2705
|
+
else if (arg === '--host' && args[index + 1]) host = args[++index];
|
|
2706
|
+
else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
|
|
2707
|
+
else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
|
|
2708
|
+
else if (arg === '--daemon-id' && args[index + 1]) daemonId = args[++index];
|
|
2709
|
+
else if (arg === '--env-json' && args[index + 1]) envJson = args[++index];
|
|
2710
|
+
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
2711
|
+
else return null;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
|
|
2715
|
+
return null;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
return {
|
|
2719
|
+
help: false,
|
|
2720
|
+
home,
|
|
2721
|
+
runtimePython,
|
|
2722
|
+
host,
|
|
2723
|
+
port,
|
|
2724
|
+
proxy,
|
|
2725
|
+
daemonId,
|
|
2726
|
+
envOverrides: decodeSupervisorEnvPayload(envJson),
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
async function daemonSupervisorMain(rawArgs) {
|
|
2731
|
+
const options = parseDaemonSupervisorArgs(rawArgs);
|
|
2732
|
+
if (!options) {
|
|
2733
|
+
console.error('Invalid daemon supervisor arguments.');
|
|
2734
|
+
process.exit(1);
|
|
2735
|
+
}
|
|
2736
|
+
if (options.help) {
|
|
2737
|
+
process.exit(0);
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
const home = options.home;
|
|
2741
|
+
let trackedDaemonId = String(options.daemonId || '').trim();
|
|
2742
|
+
let restartBackoffMs = 1000;
|
|
2743
|
+
appendDaemonSupervisorLog(home, `supervisor started for daemon ${trackedDaemonId}`);
|
|
2744
|
+
|
|
2745
|
+
while (true) {
|
|
2746
|
+
const state = readDaemonState(home);
|
|
2747
|
+
if (!state) {
|
|
2748
|
+
appendDaemonSupervisorLog(home, 'daemon state removed; supervisor exiting');
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
if (state.shutdown_requested_at) {
|
|
2752
|
+
appendDaemonSupervisorLog(home, 'managed shutdown requested; supervisor exiting');
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
const stateHome = normalizeHomePath(state.home || home);
|
|
2756
|
+
if (stateHome !== normalizeHomePath(home)) {
|
|
2757
|
+
appendDaemonSupervisorLog(home, `daemon state home changed to ${stateHome}; supervisor exiting`);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const stateDaemonId = String(state.daemon_id || '').trim();
|
|
2761
|
+
if (trackedDaemonId && stateDaemonId && stateDaemonId !== trackedDaemonId) {
|
|
2762
|
+
appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port));
|
|
2766
|
+
if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
|
|
2767
|
+
restartBackoffMs = 1000;
|
|
2768
|
+
await sleep(2500);
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
if (state.pid && isPidAlive(state.pid)) {
|
|
2772
|
+
await sleep(2500);
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
appendDaemonSupervisorLog(
|
|
2777
|
+
home,
|
|
2778
|
+
`daemon ${stateDaemonId || trackedDaemonId || 'unknown'} is not healthy; attempting restart`
|
|
2779
|
+
);
|
|
2780
|
+
try {
|
|
2781
|
+
const restarted = spawnManagedDaemonProcess({
|
|
2782
|
+
home,
|
|
2783
|
+
runtimePython: options.runtimePython,
|
|
2784
|
+
host: options.host,
|
|
2785
|
+
port: options.port,
|
|
2786
|
+
proxy: options.proxy,
|
|
2787
|
+
envOverrides: options.envOverrides,
|
|
2788
|
+
});
|
|
2789
|
+
trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
|
|
2790
|
+
observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
|
|
2791
|
+
appendDaemonSupervisorLog(
|
|
2792
|
+
home,
|
|
2793
|
+
`restarted daemon ${trackedDaemonId} with pid ${restarted.statePayload.pid}`
|
|
2794
|
+
);
|
|
2795
|
+
restartBackoffMs = 1000;
|
|
2796
|
+
await sleep(2500);
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
appendDaemonSupervisorLog(
|
|
2799
|
+
home,
|
|
2800
|
+
`restart failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2801
|
+
);
|
|
2802
|
+
await sleep(restartBackoffMs);
|
|
2803
|
+
restartBackoffMs = Math.min(restartBackoffMs * 2, 30000);
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2495
2808
|
function sleep(ms) {
|
|
2496
2809
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2497
2810
|
}
|
|
@@ -2663,6 +2976,13 @@ async function stopDaemon(home) {
|
|
|
2663
2976
|
}
|
|
2664
2977
|
}
|
|
2665
2978
|
|
|
2979
|
+
if (state) {
|
|
2980
|
+
writeDaemonState(home, {
|
|
2981
|
+
...state,
|
|
2982
|
+
shutdown_requested_at: new Date().toISOString(),
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2666
2986
|
let stopped = false;
|
|
2667
2987
|
|
|
2668
2988
|
if (healthyBefore) {
|
|
@@ -3253,61 +3573,36 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
|
|
|
3253
3573
|
}
|
|
3254
3574
|
|
|
3255
3575
|
ensureNodeBundle('src/ui', 'dist/index.html');
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
ensureDir(path.dirname(logPath));
|
|
3259
|
-
const out = fs.openSync(logPath, 'a');
|
|
3260
|
-
const daemonId = crypto.randomUUID();
|
|
3261
|
-
const child = spawn(
|
|
3576
|
+
const startedProcess = spawnManagedDaemonProcess({
|
|
3577
|
+
home,
|
|
3262
3578
|
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
3579
|
host,
|
|
3294
3580
|
port,
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
home: normalizeHomePath(home),
|
|
3300
|
-
daemon_id: daemonId,
|
|
3301
|
-
};
|
|
3302
|
-
writeDaemonState(home, statePayload);
|
|
3581
|
+
proxy,
|
|
3582
|
+
envOverrides,
|
|
3583
|
+
});
|
|
3584
|
+
const logPath = startedProcess.logPath;
|
|
3303
3585
|
|
|
3304
3586
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
3305
3587
|
const health = await fetchHealth(browserUrl);
|
|
3306
3588
|
if (health && health.status === 'ok') {
|
|
3307
|
-
|
|
3308
|
-
|
|
3589
|
+
const liveState = readDaemonState(home);
|
|
3590
|
+
if (!healthMatchesManagedState({ health, state: liveState, home })) {
|
|
3591
|
+
console.error(daemonIdentityError({ url: browserUrl, home, health, state: liveState }));
|
|
3309
3592
|
process.exit(1);
|
|
3310
3593
|
}
|
|
3594
|
+
const supervisorPid = spawnDaemonSupervisor({
|
|
3595
|
+
home,
|
|
3596
|
+
runtimePython,
|
|
3597
|
+
host,
|
|
3598
|
+
port,
|
|
3599
|
+
proxy,
|
|
3600
|
+
envOverrides,
|
|
3601
|
+
daemonId: String((liveState || {}).daemon_id || ''),
|
|
3602
|
+
});
|
|
3603
|
+
if (supervisorPid) {
|
|
3604
|
+
appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
|
|
3605
|
+
}
|
|
3311
3606
|
return { url: browserUrl, bindUrl: daemonBindUrl, reused: false };
|
|
3312
3607
|
}
|
|
3313
3608
|
await sleep(250);
|
|
@@ -3380,11 +3675,18 @@ function handleCodexPreflightFailure(error) {
|
|
|
3380
3675
|
}
|
|
3381
3676
|
}
|
|
3382
3677
|
console.error(`${warningLabel} Recommended fix:`);
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3678
|
+
const guidance = Array.isArray(error.probe?.guidance) && error.probe.guidance.length > 0
|
|
3679
|
+
? error.probe.guidance
|
|
3680
|
+
: [
|
|
3681
|
+
'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
|
|
3682
|
+
'If `codex` is still missing, run `npm install -g @openai/codex`.',
|
|
3683
|
+
'Run `codex --login` (or `codex`) and finish authentication.',
|
|
3684
|
+
'Run `ds doctor` and confirm the Codex check passes.',
|
|
3685
|
+
'Run `ds` again.',
|
|
3686
|
+
];
|
|
3687
|
+
guidance.forEach((item, index) => {
|
|
3688
|
+
console.error(`${warningLabel} ${index + 1}. ${item}`);
|
|
3689
|
+
});
|
|
3388
3690
|
openBrowser(error.reportUrl);
|
|
3389
3691
|
process.exit(1);
|
|
3390
3692
|
return true;
|
|
@@ -3712,7 +4014,11 @@ async function launcherMain(rawArgs) {
|
|
|
3712
4014
|
|
|
3713
4015
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
3714
4016
|
const runtimePython = pythonRuntime.runtimePython;
|
|
3715
|
-
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4017
|
+
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4018
|
+
yolo: options.yolo,
|
|
4019
|
+
profile: options.codexProfile,
|
|
4020
|
+
binary: options.codexBinary,
|
|
4021
|
+
});
|
|
3716
4022
|
ensureInitialized(home, runtimePython);
|
|
3717
4023
|
if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
|
|
3718
4024
|
return true;
|
|
@@ -3765,6 +4071,10 @@ async function launcherMain(rawArgs) {
|
|
|
3765
4071
|
|
|
3766
4072
|
async function main() {
|
|
3767
4073
|
const args = process.argv.slice(2);
|
|
4074
|
+
if (args[0] === '--daemon-supervisor') {
|
|
4075
|
+
await daemonSupervisorMain(args.slice(1));
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
3768
4078
|
const positional = findFirstPositionalArg(args);
|
|
3769
4079
|
if (positional && positional.value === 'update') {
|
|
3770
4080
|
await updateMain(args);
|
|
@@ -3786,7 +4096,11 @@ async function main() {
|
|
|
3786
4096
|
const home = resolveHome(args);
|
|
3787
4097
|
const pythonRuntime = ensurePythonRuntime(home);
|
|
3788
4098
|
const runtimePython = pythonRuntime.runtimePython;
|
|
3789
|
-
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4099
|
+
const codexOverrideEnv = buildCodexOverrideEnv({
|
|
4100
|
+
yolo: args.includes('--yolo'),
|
|
4101
|
+
profile: readOptionValue(args, '--codex-profile'),
|
|
4102
|
+
binary: readOptionValue(args, '--codex'),
|
|
4103
|
+
});
|
|
3790
4104
|
if (positional.value === 'run' || positional.value === 'daemon') {
|
|
3791
4105
|
maybePrintOptionalLatexNotice(home);
|
|
3792
4106
|
}
|