@researai/deepscientist 1.5.2 → 1.5.3
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 +22 -0
- package/bin/ds.js +384 -0
- package/docs/en/00_QUICK_START.md +22 -0
- package/docs/zh/00_QUICK_START.md +22 -0
- package/install.sh +120 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/artifact/service.py +1 -1
- package/src/deepscientist/bash_exec/monitor.py +23 -4
- package/src/deepscientist/bash_exec/runtime.py +3 -0
- package/src/deepscientist/bash_exec/service.py +132 -4
- package/src/deepscientist/bridges/base.py +10 -19
- package/src/deepscientist/channels/discord_gateway.py +25 -2
- package/src/deepscientist/channels/feishu_long_connection.py +41 -3
- package/src/deepscientist/channels/qq.py +524 -64
- package/src/deepscientist/channels/qq_gateway.py +22 -3
- package/src/deepscientist/channels/relay.py +429 -90
- package/src/deepscientist/channels/slack_socket.py +29 -5
- package/src/deepscientist/channels/telegram_polling.py +25 -2
- package/src/deepscientist/channels/whatsapp_local_session.py +32 -4
- package/src/deepscientist/cli.py +27 -0
- package/src/deepscientist/config/models.py +6 -40
- package/src/deepscientist/config/service.py +164 -155
- package/src/deepscientist/connector_profiles.py +346 -0
- package/src/deepscientist/connector_runtime.py +88 -43
- package/src/deepscientist/daemon/api/handlers.py +47 -10
- package/src/deepscientist/daemon/api/router.py +2 -2
- package/src/deepscientist/daemon/app.py +682 -218
- package/src/deepscientist/mcp/server.py +60 -7
- package/src/deepscientist/migration.py +114 -0
- package/src/deepscientist/prompts/builder.py +30 -3
- package/src/deepscientist/qq_profiles.py +186 -0
- package/src/prompts/connectors/qq.md +42 -2
- package/src/prompts/system.md +85 -5
- package/src/skills/analysis-campaign/SKILL.md +11 -5
- package/src/skills/baseline/SKILL.md +66 -31
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +11 -5
- 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 +1 -1
- package/src/skills/review/SKILL.md +1 -1
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +1 -1
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-CZpg376x.js → AiManusChatView-qzChi9uh.js} +14 -37
- package/src/ui/dist/assets/{AnalysisPlugin-CtHA22g3.js → AnalysisPlugin-CcC_-UqN.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-BSWmLMmF.js → AutoFigurePlugin-DD8LkJLe.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-CJ7jdm_s.js → CliPlugin-DJJFfVmW.js} +17 -110
- package/src/ui/dist/assets/{CodeEditorPlugin-DhInVGFf.js → CodeEditorPlugin-CrjkHNLh.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-D1n8S9r5.js → CodeViewerPlugin-obnD6G5R.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-C4XM_kqk.js → DocViewerPlugin-DB9SUQVd.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-W6kS9r6v.js → GitDiffViewerPlugin-DZLlNlD2.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-DPeUx_Oz.js → ImageViewerPlugin-BGwfDZ0Y.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-eAelUaub.js → LabCopilotPanel-dfLptQcR.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-BbOrBxKY.js → LabPlugin-CeGjAl3A.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-C-HhkVXY.js → LatexPlugin-BBJ7kd1V.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-BDIzIBfh.js → MarkdownViewerPlugin-DKZi7BcB.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DAOJphwr.js → MarketplacePlugin-C_k-9jD0.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-BsoMvDoU.js → NotebookEditor-4R88_BMO.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-fiC7RtHf.js → PdfLoader-DwEFQLrw.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-C5OxZBFK.js → PdfMarkdownPlugin-D-jdsqF8.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-CAbxQebk.js → PdfViewerPlugin-CmeBGDY0.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-SE33Lb9B.js → SearchPlugin-Dlz2WKJ4.js} +1 -1
- package/src/ui/dist/assets/{Stepper-0Av7GfV7.js → Stepper-ClOgzWM3.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Daf2gJDI.js → TextViewerPlugin-DDQWxibk.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BKrMUIOX.js → VNCViewer-CJXT0Nm8.js} +9 -9
- package/src/ui/dist/assets/{bibtex-JBdOEe45.js → bibtex-DLr4Rtk4.js} +1 -1
- package/src/ui/dist/assets/{code-B0TDFCZz.js → code-DgKK408Y.js} +1 -1
- package/src/ui/dist/assets/{file-content-3YtrSacz.js → file-content-6HBqQnvQ.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CJEg5OG1.js → file-diff-panel-Dhu0TbBM.js} +1 -1
- package/src/ui/dist/assets/{file-socket-CYQYdmB1.js → file-socket-CP3iwVZG.js} +1 -1
- package/src/ui/dist/assets/{file-utils-Cd1C9Ppl.js → file-utils-BsS-Aw68.js} +1 -1
- package/src/ui/dist/assets/{image-B33ctrvC.js → image-ByeK-Zcv.js} +1 -1
- package/src/ui/dist/assets/{index-BVXsmS7V.js → index-BLjo5--a.js} +9499 -8688
- package/src/ui/dist/assets/{index-BNQWqmJ2.js → index-BdsE0uRz.js} +11 -11
- package/src/ui/dist/assets/{index-9CLPVeZh.js → index-C-eX-N6A.js} +1 -1
- package/src/ui/dist/assets/{index-SwmFAld3.css → index-CuQhlrR-.css} +49 -2
- package/src/ui/dist/assets/{index-Buw_N1VQ.js → index-DyremSIv.js} +2 -2
- package/src/ui/dist/assets/{message-square-D0cUJ9yU.js → message-square-DnagiLnc.js} +1 -1
- package/src/ui/dist/assets/{monaco-UZLYkp2n.js → monaco-4kBFeprs.js} +1 -1
- package/src/ui/dist/assets/{popover-CTeiY-dK.js → popover-hRCXZzs2.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Dbs01Xky.js → project-sync-O_85YuP6.js} +1 -1
- package/src/ui/dist/assets/{sigma-CM08S-xT.js → sigma-DvKopSnL.js} +1 -1
- package/src/ui/dist/assets/{tooltip-pDtzvU9p.js → tooltip-BmlPc6kc.js} +1 -1
- package/src/ui/dist/assets/{trash-YvPCP-da.js → trash-n-UvdZFR.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-Bavi74Ac.js → useCliAccess-WDd3_wIh.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-CVXY6oeg.js → useFileDiffOverlay-rXLIL2NF.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-Cf4flRW7.js → wrap-text-qIYQ4a_W.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-Hb0Z1YpT.js → zoom-out-fZXCEFsy.js} +1 -1
- package/src/ui/dist/index.html +2 -2
package/README.md
CHANGED
|
@@ -52,6 +52,28 @@ The default DeepScientist home is:
|
|
|
52
52
|
|
|
53
53
|
Use `ds --home <path>` if you want to place the runtime somewhere else.
|
|
54
54
|
|
|
55
|
+
If you want to use the current working directory directly as the DeepScientist home, use:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ds --hero
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This is equivalent to launching with `ds --home "$PWD"`.
|
|
62
|
+
|
|
63
|
+
If you want to install the bundled CLI tree into another base path from a source checkout:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bash install.sh --dir /data/DeepScientist
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you already have a populated DeepScientist home and want to move it safely:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ds migrate /data/DeepScientist
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`ds migrate` stops the managed daemon first, shows the absolute source and target paths, asks for a double confirmation, verifies the copied tree, updates launcher wrappers, and only then removes the old path.
|
|
76
|
+
|
|
55
77
|
## Troubleshooting
|
|
56
78
|
|
|
57
79
|
```bash
|
package/bin/ds.js
CHANGED
|
@@ -13,6 +13,7 @@ const pyprojectToml = fs.readFileSync(path.join(repoRoot, 'pyproject.toml'), 'ut
|
|
|
13
13
|
const pythonCandidates = process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python'];
|
|
14
14
|
const requiredPythonSpec = parseRequiredPythonSpec(pyprojectToml);
|
|
15
15
|
const minimumPythonVersion = parseMinimumPythonVersion(requiredPythonSpec);
|
|
16
|
+
const launcherWrapperCommands = ['ds', 'ds-cli', 'research', 'resear'];
|
|
16
17
|
const pythonCommands = new Set([
|
|
17
18
|
'init',
|
|
18
19
|
'new',
|
|
@@ -46,6 +47,9 @@ Usage:
|
|
|
46
47
|
ds update
|
|
47
48
|
ds update --check
|
|
48
49
|
ds update --yes
|
|
50
|
+
ds migrate /data/DeepScientist
|
|
51
|
+
ds --hero
|
|
52
|
+
ds --hero doctor
|
|
49
53
|
ds --tui
|
|
50
54
|
ds --both
|
|
51
55
|
ds --host 0.0.0.0 --port 21000
|
|
@@ -67,6 +71,7 @@ Launcher flags:
|
|
|
67
71
|
--stop Stop the managed daemon
|
|
68
72
|
--restart Restart the managed daemon
|
|
69
73
|
--home <path> Use a custom DeepScientist home
|
|
74
|
+
--hero Use the current working directory as DeepScientist home
|
|
70
75
|
--quest-id <id> Open the TUI on one quest directly
|
|
71
76
|
|
|
72
77
|
Update:
|
|
@@ -74,6 +79,10 @@ Update:
|
|
|
74
79
|
ds update --check Print structured update status
|
|
75
80
|
ds update --yes Install the latest npm release immediately
|
|
76
81
|
|
|
82
|
+
Migration:
|
|
83
|
+
ds migrate <target> Move the DeepScientist home/install root to a new absolute path
|
|
84
|
+
ds migrate <target> --yes --restart
|
|
85
|
+
|
|
77
86
|
Runtime:
|
|
78
87
|
DeepScientist uses uv to manage a locked local Python runtime.
|
|
79
88
|
If uv is missing, ds bootstraps a local copy under the DeepScientist home automatically.
|
|
@@ -384,6 +393,9 @@ function resolveHome(args) {
|
|
|
384
393
|
if (index >= 0 && index + 1 < args.length) {
|
|
385
394
|
return path.resolve(args[index + 1]);
|
|
386
395
|
}
|
|
396
|
+
if (args.includes('--hero') || args.includes('--here')) {
|
|
397
|
+
return process.cwd();
|
|
398
|
+
}
|
|
387
399
|
if (process.env.DEEPSCIENTIST_HOME) {
|
|
388
400
|
return path.resolve(process.env.DEEPSCIENTIST_HOME);
|
|
389
401
|
}
|
|
@@ -554,12 +566,14 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection }) {
|
|
|
554
566
|
renderKeyValueRows([
|
|
555
567
|
['ds --port 21000', 'Change the web port'],
|
|
556
568
|
['ds --host 0.0.0.0 --port 21000', 'Bind on all interfaces'],
|
|
569
|
+
['ds --hero', 'Use the current directory as home'],
|
|
557
570
|
['ds --both', 'Start web + TUI together'],
|
|
558
571
|
['ds --tui', 'Start the terminal workspace only'],
|
|
559
572
|
['ds --no-browser', 'Do not auto-open the browser'],
|
|
560
573
|
['ds --status', 'Show daemon health as JSON'],
|
|
561
574
|
['ds --restart', 'Restart the managed daemon'],
|
|
562
575
|
['ds --stop', 'Stop the managed daemon'],
|
|
576
|
+
['ds migrate /data/DeepScientist', 'Move the full home/install root safely'],
|
|
563
577
|
['ds --help', 'Show the full launcher help'],
|
|
564
578
|
]);
|
|
565
579
|
console.log('');
|
|
@@ -630,6 +644,7 @@ function printLaunchCard({
|
|
|
630
644
|
console.log(centerText(browserLine, width));
|
|
631
645
|
console.log(centerText(nextStep, width));
|
|
632
646
|
console.log(centerText('Run ds --stop to stop the managed daemon.', width));
|
|
647
|
+
console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
|
|
633
648
|
console.log('');
|
|
634
649
|
renderLaunchHints({ home, url, bindUrl, pythonSelection });
|
|
635
650
|
}
|
|
@@ -894,6 +909,22 @@ Flags:
|
|
|
894
909
|
`);
|
|
895
910
|
}
|
|
896
911
|
|
|
912
|
+
function printMigrateHelp() {
|
|
913
|
+
console.log(`DeepScientist migrate
|
|
914
|
+
|
|
915
|
+
Usage:
|
|
916
|
+
ds migrate /absolute/target/path
|
|
917
|
+
ds migrate /absolute/target/path --yes
|
|
918
|
+
ds migrate /absolute/target/path --restart
|
|
919
|
+
ds migrate /absolute/target/path --home /current/source/path
|
|
920
|
+
|
|
921
|
+
Flags:
|
|
922
|
+
--yes Skip the interactive double-confirmation prompt
|
|
923
|
+
--restart Start the managed daemon again from the migrated home
|
|
924
|
+
--home <path> Override the current DeepScientist source home/root
|
|
925
|
+
`);
|
|
926
|
+
}
|
|
927
|
+
|
|
897
928
|
function parseUpdateArgs(argv) {
|
|
898
929
|
const args = [...argv];
|
|
899
930
|
if (args[0] === 'update') {
|
|
@@ -950,6 +981,40 @@ function parseUpdateArgs(argv) {
|
|
|
950
981
|
};
|
|
951
982
|
}
|
|
952
983
|
|
|
984
|
+
function parseMigrateArgs(argv) {
|
|
985
|
+
const args = [...argv];
|
|
986
|
+
if (args[0] === 'migrate') {
|
|
987
|
+
args.shift();
|
|
988
|
+
}
|
|
989
|
+
let home = null;
|
|
990
|
+
let target = null;
|
|
991
|
+
let yes = false;
|
|
992
|
+
let restart = false;
|
|
993
|
+
|
|
994
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
995
|
+
const arg = args[index];
|
|
996
|
+
if (arg === '--yes') yes = true;
|
|
997
|
+
else if (arg === '--restart') restart = true;
|
|
998
|
+
else if (arg === '--home' && args[index + 1]) home = path.resolve(expandUserPath(args[++index]));
|
|
999
|
+
else if (arg === '--help' || arg === '-h') return { help: true };
|
|
1000
|
+
else if (arg.startsWith('--')) return null;
|
|
1001
|
+
else if (!target) target = path.resolve(expandUserPath(arg));
|
|
1002
|
+
else return null;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (!target) {
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return {
|
|
1010
|
+
help: false,
|
|
1011
|
+
home,
|
|
1012
|
+
target,
|
|
1013
|
+
yes,
|
|
1014
|
+
restart,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
953
1018
|
function findFirstPositionalArg(args) {
|
|
954
1019
|
for (let index = 0; index < args.length; index += 1) {
|
|
955
1020
|
const arg = args[index];
|
|
@@ -973,6 +1038,188 @@ function realpathOrSelf(targetPath) {
|
|
|
973
1038
|
}
|
|
974
1039
|
}
|
|
975
1040
|
|
|
1041
|
+
function isPathEqual(left, right) {
|
|
1042
|
+
return realpathOrSelf(path.resolve(left)) === realpathOrSelf(path.resolve(right));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function isPathInside(candidatePath, parentPath) {
|
|
1046
|
+
const candidate = realpathOrSelf(path.resolve(candidatePath));
|
|
1047
|
+
const parent = realpathOrSelf(path.resolve(parentPath));
|
|
1048
|
+
if (candidate === parent) {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
const relative = path.relative(parent, candidate);
|
|
1052
|
+
return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function buildInstalledWrapperScript() {
|
|
1056
|
+
return [
|
|
1057
|
+
'#!/usr/bin/env bash',
|
|
1058
|
+
'set -euo pipefail',
|
|
1059
|
+
'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
1060
|
+
'HOME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"',
|
|
1061
|
+
'if [ -z "${DEEPSCIENTIST_HOME:-}" ]; then',
|
|
1062
|
+
' export DEEPSCIENTIST_HOME="$HOME_DIR"',
|
|
1063
|
+
'fi',
|
|
1064
|
+
'NODE_BIN="${DEEPSCIENTIST_NODE:-node}"',
|
|
1065
|
+
'exec "$NODE_BIN" "$SCRIPT_DIR/ds.js" "$@"',
|
|
1066
|
+
'',
|
|
1067
|
+
].join('\n');
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function buildGlobalWrapperScript({ installDir, home, commandName }) {
|
|
1071
|
+
return [
|
|
1072
|
+
'#!/usr/bin/env bash',
|
|
1073
|
+
'set -euo pipefail',
|
|
1074
|
+
'if [ -z "${DEEPSCIENTIST_HOME:-}" ]; then',
|
|
1075
|
+
` export DEEPSCIENTIST_HOME="${home}"`,
|
|
1076
|
+
'fi',
|
|
1077
|
+
`exec "${path.join(installDir, 'bin', commandName)}" "$@"`,
|
|
1078
|
+
'',
|
|
1079
|
+
].join('\n');
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function writeExecutableScript(targetPath, content) {
|
|
1083
|
+
ensureDir(path.dirname(targetPath));
|
|
1084
|
+
fs.writeFileSync(targetPath, content, { encoding: 'utf8', mode: 0o755 });
|
|
1085
|
+
fs.chmodSync(targetPath, 0o755);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function repairMigratedInstallWrappers(targetHome) {
|
|
1089
|
+
const installBinDir = path.join(targetHome, 'cli', 'bin');
|
|
1090
|
+
if (!fs.existsSync(installBinDir)) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const content = buildInstalledWrapperScript();
|
|
1094
|
+
for (const commandName of launcherWrapperCommands) {
|
|
1095
|
+
const wrapperPath = path.join(installBinDir, commandName);
|
|
1096
|
+
if (!fs.existsSync(wrapperPath)) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
writeExecutableScript(wrapperPath, content);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function candidateWrapperPathsForCommand(commandName) {
|
|
1104
|
+
const directories = String(process.env.PATH || '')
|
|
1105
|
+
.split(path.delimiter)
|
|
1106
|
+
.filter(Boolean);
|
|
1107
|
+
const candidates = [];
|
|
1108
|
+
for (const directory of directories) {
|
|
1109
|
+
candidates.push(path.join(directory, commandName));
|
|
1110
|
+
if (process.platform === 'win32') {
|
|
1111
|
+
candidates.push(path.join(directory, `${commandName}.cmd`));
|
|
1112
|
+
candidates.push(path.join(directory, `${commandName}.ps1`));
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return candidates;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function rewriteLauncherWrappersIfPointingAtSource({ sourceHome, targetHome }) {
|
|
1119
|
+
if (process.platform === 'win32') {
|
|
1120
|
+
return [];
|
|
1121
|
+
}
|
|
1122
|
+
const rewritten = [];
|
|
1123
|
+
const sourceInstallDir = path.join(sourceHome, 'cli');
|
|
1124
|
+
const targetInstallDir = path.join(targetHome, 'cli');
|
|
1125
|
+
for (const commandName of launcherWrapperCommands) {
|
|
1126
|
+
for (const candidate of candidateWrapperPathsForCommand(commandName)) {
|
|
1127
|
+
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) {
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
let text = '';
|
|
1131
|
+
try {
|
|
1132
|
+
text = fs.readFileSync(candidate, 'utf8');
|
|
1133
|
+
} catch {
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
if (!text.includes(sourceInstallDir) && !text.includes(sourceHome)) {
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
writeExecutableScript(
|
|
1140
|
+
candidate,
|
|
1141
|
+
buildGlobalWrapperScript({
|
|
1142
|
+
installDir: targetInstallDir,
|
|
1143
|
+
home: targetHome,
|
|
1144
|
+
commandName,
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
rewritten.push(candidate);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
return rewritten;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function scheduleDeferredSourceCleanup({ sourceHome, targetHome }) {
|
|
1154
|
+
const logPath = path.join(targetHome, 'logs', 'migrate-cleanup.log');
|
|
1155
|
+
ensureDir(path.dirname(logPath));
|
|
1156
|
+
const helperScript = [
|
|
1157
|
+
"const fs = require('node:fs');",
|
|
1158
|
+
"const { setTimeout: sleep } = require('node:timers/promises');",
|
|
1159
|
+
'const parentPid = Number(process.argv[1]);',
|
|
1160
|
+
'const sourceHome = process.argv[2];',
|
|
1161
|
+
'const logPath = process.argv[3];',
|
|
1162
|
+
'(async () => {',
|
|
1163
|
+
' for (let attempt = 0; attempt < 300; attempt += 1) {',
|
|
1164
|
+
' try {',
|
|
1165
|
+
' process.kill(parentPid, 0);',
|
|
1166
|
+
' await sleep(100);',
|
|
1167
|
+
' continue;',
|
|
1168
|
+
' } catch {',
|
|
1169
|
+
' break;',
|
|
1170
|
+
' }',
|
|
1171
|
+
' }',
|
|
1172
|
+
' try {',
|
|
1173
|
+
' fs.rmSync(sourceHome, { recursive: true, force: true });',
|
|
1174
|
+
' } catch (error) {',
|
|
1175
|
+
" fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${error instanceof Error ? error.message : String(error)}\\n`, 'utf8');",
|
|
1176
|
+
' process.exit(1);',
|
|
1177
|
+
' }',
|
|
1178
|
+
'})();',
|
|
1179
|
+
].join('\n');
|
|
1180
|
+
const child = spawn(process.execPath, ['-e', helperScript, String(process.pid), sourceHome, logPath], {
|
|
1181
|
+
detached: true,
|
|
1182
|
+
stdio: 'ignore',
|
|
1183
|
+
env: process.env,
|
|
1184
|
+
});
|
|
1185
|
+
child.unref();
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async function promptMigrationConfirmation({ sourceHome, targetHome }) {
|
|
1189
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1190
|
+
throw new Error('DeepScientist migration needs a TTY for confirmation. Re-run with `--yes` to continue non-interactively.');
|
|
1191
|
+
}
|
|
1192
|
+
console.log('');
|
|
1193
|
+
console.log('DeepScientist home migration');
|
|
1194
|
+
console.log('');
|
|
1195
|
+
console.log(`From: ${sourceHome}`);
|
|
1196
|
+
console.log(`To: ${targetHome}`);
|
|
1197
|
+
console.log('');
|
|
1198
|
+
console.log('This will stop the managed daemon, copy the full DeepScientist root, verify the copy, update launcher wrappers, and delete the old path after success.');
|
|
1199
|
+
const ask = (question) => new Promise((resolve) => {
|
|
1200
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1201
|
+
rl.question(question, (answer) => {
|
|
1202
|
+
rl.close();
|
|
1203
|
+
resolve(String(answer || '').trim());
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
const first = await ask('Type YES to continue: ');
|
|
1207
|
+
if (first !== 'YES') {
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
const second = await ask('Type MIGRATE to confirm old-path deletion after a successful copy: ');
|
|
1211
|
+
return second === 'MIGRATE';
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function printMigrationSummary({ sourceHome, targetHome, restart }) {
|
|
1215
|
+
console.log('');
|
|
1216
|
+
console.log('DeepScientist migrate');
|
|
1217
|
+
console.log('');
|
|
1218
|
+
console.log(`Source: ${sourceHome}`);
|
|
1219
|
+
console.log(`Target: ${targetHome}`);
|
|
1220
|
+
console.log(`Restart: ${restart ? 'yes' : 'no'}`);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
976
1223
|
function pythonMeetsMinimum(probe) {
|
|
977
1224
|
if (!probe || typeof probe.major !== 'number' || typeof probe.minor !== 'number') {
|
|
978
1225
|
return false;
|
|
@@ -1784,6 +2031,9 @@ function normalizePythonCliArgs(args, home) {
|
|
|
1784
2031
|
index += 1;
|
|
1785
2032
|
continue;
|
|
1786
2033
|
}
|
|
2034
|
+
if (arg === '--hero' || arg === '--here') {
|
|
2035
|
+
continue;
|
|
2036
|
+
}
|
|
1787
2037
|
normalized.push(arg);
|
|
1788
2038
|
}
|
|
1789
2039
|
return ['--home', home, ...normalized];
|
|
@@ -2996,6 +3246,135 @@ async function updateMain(rawArgs) {
|
|
|
2996
3246
|
process.exit(payload.ok ? 0 : 1);
|
|
2997
3247
|
}
|
|
2998
3248
|
|
|
3249
|
+
async function migrateMain(rawArgs) {
|
|
3250
|
+
const options = parseMigrateArgs(rawArgs);
|
|
3251
|
+
if (!options) {
|
|
3252
|
+
printMigrateHelp();
|
|
3253
|
+
process.exit(1);
|
|
3254
|
+
}
|
|
3255
|
+
if (options.help) {
|
|
3256
|
+
printMigrateHelp();
|
|
3257
|
+
process.exit(0);
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
const sourceHome = realpathOrSelf(options.home || resolveHome(rawArgs));
|
|
3261
|
+
const targetHome = path.resolve(options.target);
|
|
3262
|
+
if (!fs.existsSync(sourceHome)) {
|
|
3263
|
+
console.error(`DeepScientist source path does not exist: ${sourceHome}`);
|
|
3264
|
+
process.exit(1);
|
|
3265
|
+
}
|
|
3266
|
+
if (isPathEqual(sourceHome, targetHome)) {
|
|
3267
|
+
console.error('DeepScientist source and target paths are identical. Choose a different migration target.');
|
|
3268
|
+
process.exit(1);
|
|
3269
|
+
}
|
|
3270
|
+
if (isPathInside(targetHome, sourceHome) || isPathInside(sourceHome, targetHome)) {
|
|
3271
|
+
console.error('DeepScientist migration requires two separate sibling paths. Do not nest one path inside the other.');
|
|
3272
|
+
process.exit(1);
|
|
3273
|
+
}
|
|
3274
|
+
if (fs.existsSync(targetHome)) {
|
|
3275
|
+
console.error(`DeepScientist target path already exists: ${targetHome}`);
|
|
3276
|
+
process.exit(1);
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
printMigrationSummary({ sourceHome, targetHome, restart: options.restart });
|
|
3280
|
+
if (!options.yes) {
|
|
3281
|
+
const confirmed = await promptMigrationConfirmation({ sourceHome, targetHome });
|
|
3282
|
+
if (!confirmed) {
|
|
3283
|
+
console.log('DeepScientist migration cancelled.');
|
|
3284
|
+
process.exit(1);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
const state = readDaemonState(sourceHome);
|
|
3289
|
+
const configured = readConfiguredUiAddressFromFile(sourceHome);
|
|
3290
|
+
const url = state?.url || browserUiUrl(configured.host, configured.port);
|
|
3291
|
+
const health = await fetchHealth(url);
|
|
3292
|
+
if (state || healthMatchesHome({ health, home: sourceHome })) {
|
|
3293
|
+
await stopDaemon(sourceHome);
|
|
3294
|
+
} else if (health && health.status === 'ok') {
|
|
3295
|
+
console.log(`Skipping daemon stop because ${url} belongs to another DeepScientist home.`);
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
const pythonRuntime = ensurePythonRuntime(sourceHome);
|
|
3299
|
+
const runtimePython = pythonRuntime.runtimePython;
|
|
3300
|
+
const result = runPythonCli(
|
|
3301
|
+
runtimePython,
|
|
3302
|
+
['--home', sourceHome, 'migrate', targetHome],
|
|
3303
|
+
{ capture: true, allowFailure: true }
|
|
3304
|
+
);
|
|
3305
|
+
let payload = null;
|
|
3306
|
+
try {
|
|
3307
|
+
payload = JSON.parse(String(result.stdout || '{}'));
|
|
3308
|
+
} catch {
|
|
3309
|
+
payload = null;
|
|
3310
|
+
}
|
|
3311
|
+
if (result.status !== 0 || !payload || payload.ok !== true) {
|
|
3312
|
+
if (result.stdout) {
|
|
3313
|
+
process.stdout.write(result.stdout);
|
|
3314
|
+
if (!String(result.stdout).endsWith('\n')) {
|
|
3315
|
+
process.stdout.write('\n');
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (result.stderr) {
|
|
3319
|
+
process.stderr.write(result.stderr);
|
|
3320
|
+
if (!String(result.stderr).endsWith('\n')) {
|
|
3321
|
+
process.stderr.write('\n');
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
console.error('DeepScientist migration failed.');
|
|
3325
|
+
process.exit(result.status ?? 1);
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
repairMigratedInstallWrappers(targetHome);
|
|
3329
|
+
const rewrittenWrappers = rewriteLauncherWrappersIfPointingAtSource({ sourceHome, targetHome });
|
|
3330
|
+
|
|
3331
|
+
const sourceContainsCurrentInstall = isPathEqual(repoRoot, path.join(sourceHome, 'cli')) || isPathInside(repoRoot, sourceHome);
|
|
3332
|
+
if (sourceContainsCurrentInstall) {
|
|
3333
|
+
scheduleDeferredSourceCleanup({ sourceHome, targetHome });
|
|
3334
|
+
} else {
|
|
3335
|
+
fs.rmSync(sourceHome, { recursive: true, force: true });
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
let restartMessage = 'Restart skipped.';
|
|
3339
|
+
if (options.restart) {
|
|
3340
|
+
const migratedLauncher = path.join(targetHome, 'cli', 'bin', 'ds.js');
|
|
3341
|
+
if (!fs.existsSync(migratedLauncher)) {
|
|
3342
|
+
restartMessage = `Migration succeeded, but restart was skipped because the migrated launcher is missing: ${migratedLauncher}`;
|
|
3343
|
+
} else {
|
|
3344
|
+
const child = spawn(
|
|
3345
|
+
process.execPath,
|
|
3346
|
+
[migratedLauncher, '--home', targetHome, '--daemon-only', '--no-browser', '--skip-update-check'],
|
|
3347
|
+
{
|
|
3348
|
+
cwd: path.join(targetHome, 'cli'),
|
|
3349
|
+
detached: true,
|
|
3350
|
+
stdio: 'ignore',
|
|
3351
|
+
env: process.env,
|
|
3352
|
+
}
|
|
3353
|
+
);
|
|
3354
|
+
child.unref();
|
|
3355
|
+
restartMessage = 'Managed daemon restart scheduled from the migrated home.';
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
console.log('');
|
|
3360
|
+
console.log('DeepScientist migration completed.');
|
|
3361
|
+
console.log(`New home: ${targetHome}`);
|
|
3362
|
+
if (payload.summary) {
|
|
3363
|
+
console.log(payload.summary);
|
|
3364
|
+
}
|
|
3365
|
+
if (rewrittenWrappers.length > 0) {
|
|
3366
|
+
console.log(`Updated wrappers: ${rewrittenWrappers.join(', ')}`);
|
|
3367
|
+
}
|
|
3368
|
+
console.log(restartMessage);
|
|
3369
|
+
if (sourceContainsCurrentInstall) {
|
|
3370
|
+
console.log(`Old path cleanup has been scheduled: ${sourceHome}`);
|
|
3371
|
+
} else {
|
|
3372
|
+
console.log(`Old path removed: ${sourceHome}`);
|
|
3373
|
+
}
|
|
3374
|
+
console.log(`Use \`ds --home ${targetHome}\` if you want to override the default explicitly.`);
|
|
3375
|
+
process.exit(0);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
2999
3378
|
async function launcherMain(rawArgs) {
|
|
3000
3379
|
const options = parseLauncherArgs(rawArgs);
|
|
3001
3380
|
if (!options) {
|
|
@@ -3097,6 +3476,10 @@ async function main() {
|
|
|
3097
3476
|
await updateMain(args);
|
|
3098
3477
|
return;
|
|
3099
3478
|
}
|
|
3479
|
+
if (positional && positional.value === 'migrate') {
|
|
3480
|
+
await migrateMain(args);
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3100
3483
|
if (args.length === 0 || args[0] === 'ui' || (!positional && args[0]?.startsWith('--'))) {
|
|
3101
3484
|
await launcherMain(args);
|
|
3102
3485
|
return;
|
|
@@ -3144,6 +3527,7 @@ module.exports = {
|
|
|
3144
3527
|
legacyVenvRootPath,
|
|
3145
3528
|
resolveUvBinary,
|
|
3146
3529
|
resolveHome,
|
|
3530
|
+
parseMigrateArgs,
|
|
3147
3531
|
useEditableProjectInstall,
|
|
3148
3532
|
compareVersions,
|
|
3149
3533
|
detectInstallMode,
|
|
@@ -39,6 +39,28 @@ If `uv` is missing on your machine, `ds` will bootstrap a local copy automatical
|
|
|
39
39
|
|
|
40
40
|
By default, the DeepScientist home is `~/DeepScientist` on macOS and Linux, and `%USERPROFILE%\\DeepScientist` on Windows. You can override it with `ds --home <path>`.
|
|
41
41
|
|
|
42
|
+
If you want to use the current working directory as the DeepScientist home directly, run:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ds --hero
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This is equivalent to `ds --home "$PWD"`.
|
|
49
|
+
|
|
50
|
+
If you install from a source checkout and want another default base path for the bundled CLI tree, use:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bash install.sh --dir /data/DeepScientist
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you already have a populated DeepScientist home and need to move it to another path later, use:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ds migrate /data/DeepScientist
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The migration command prints the absolute source and target paths, stops the managed daemon, asks for a double confirmation, verifies the copied tree, updates launcher wrappers, and removes the old path only after the migration succeeds.
|
|
63
|
+
|
|
42
64
|
By default, the web UI is served at:
|
|
43
65
|
|
|
44
66
|
```text
|
|
@@ -39,6 +39,28 @@ DeepScientist 现在使用 `uv` 管理锁定的本地 Python 运行时。如果
|
|
|
39
39
|
|
|
40
40
|
默认情况下,DeepScientist home 在 macOS / Linux 上是 `~/DeepScientist`,在 Windows 上是 `%USERPROFILE%\\DeepScientist`。如果你希望放到别的路径,可以直接使用 `ds --home <path>`。
|
|
41
41
|
|
|
42
|
+
如果你希望直接把“当前工作目录”作为 DeepScientist home,可以运行:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ds --hero
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
它等价于 `ds --home "$PWD"`。
|
|
49
|
+
|
|
50
|
+
如果你是从源码仓库安装,并希望把默认的 CLI 安装基路径改到别的位置,可以使用:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bash install.sh --dir /data/DeepScientist
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
如果你已经有一个正在使用的 DeepScientist home,之后又想安全迁移到别的路径,可以使用:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ds migrate /data/DeepScientist
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`ds migrate` 会先显示当前绝对路径和目标绝对路径,停止托管 daemon,要求二次确认,校验复制结果,并在确认迁移成功后才删除旧路径。
|
|
63
|
+
|
|
42
64
|
默认情况下,网页会运行在:
|
|
43
65
|
|
|
44
66
|
```text
|