@researai/deepscientist 1.5.6 → 1.5.8

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 (158) hide show
  1. package/README.md +32 -0
  2. package/bin/ds.js +274 -18
  3. package/docs/en/07_MEMORY_AND_MCP.md +40 -3
  4. package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
  5. package/docs/zh/07_MEMORY_AND_MCP.md +40 -3
  6. package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
  7. package/install.sh +34 -0
  8. package/package.json +1 -1
  9. package/pyproject.toml +1 -1
  10. package/src/deepscientist/__init__.py +1 -1
  11. package/src/deepscientist/acp/envelope.py +1 -0
  12. package/src/deepscientist/artifact/metrics.py +813 -80
  13. package/src/deepscientist/artifact/schemas.py +1 -0
  14. package/src/deepscientist/artifact/service.py +1101 -99
  15. package/src/deepscientist/bash_exec/monitor.py +1 -1
  16. package/src/deepscientist/bash_exec/service.py +17 -9
  17. package/src/deepscientist/channels/qq.py +17 -0
  18. package/src/deepscientist/channels/relay.py +16 -0
  19. package/src/deepscientist/cli.py +1 -1
  20. package/src/deepscientist/config/models.py +12 -6
  21. package/src/deepscientist/config/service.py +75 -2
  22. package/src/deepscientist/connector_profiles.py +34 -6
  23. package/src/deepscientist/daemon/api/handlers.py +290 -15
  24. package/src/deepscientist/daemon/api/router.py +2 -0
  25. package/src/deepscientist/daemon/app.py +521 -23
  26. package/src/deepscientist/gitops/diff.py +6 -10
  27. package/src/deepscientist/mcp/server.py +188 -39
  28. package/src/deepscientist/prompts/builder.py +71 -22
  29. package/src/deepscientist/qq_profiles.py +19 -9
  30. package/src/deepscientist/quest/layout.py +1 -0
  31. package/src/deepscientist/quest/service.py +83 -34
  32. package/src/deepscientist/quest/stage_views.py +74 -29
  33. package/src/deepscientist/runners/codex.py +32 -14
  34. package/src/deepscientist/runners/runtime_overrides.py +46 -0
  35. package/src/deepscientist/skills/installer.py +7 -0
  36. package/src/prompts/connectors/qq.md +1 -1
  37. package/src/prompts/contracts/shared_interaction.md +14 -0
  38. package/src/prompts/system.md +134 -30
  39. package/src/skills/analysis-campaign/SKILL.md +34 -8
  40. package/src/skills/analysis-campaign/references/campaign-checklist-template.md +41 -0
  41. package/src/skills/analysis-campaign/references/campaign-plan-template.md +68 -0
  42. package/src/skills/baseline/SKILL.md +145 -32
  43. package/src/skills/baseline/references/baseline-checklist-template.md +57 -0
  44. package/src/skills/baseline/references/baseline-plan-template.md +105 -0
  45. package/src/skills/decision/SKILL.md +12 -8
  46. package/src/skills/experiment/SKILL.md +51 -9
  47. package/src/skills/experiment/references/main-experiment-checklist-template.md +52 -0
  48. package/src/skills/experiment/references/main-experiment-plan-template.md +79 -0
  49. package/src/skills/figure-polish/SKILL.md +1 -0
  50. package/src/skills/finalize/SKILL.md +3 -8
  51. package/src/skills/idea/SKILL.md +2 -8
  52. package/src/skills/intake-audit/SKILL.md +2 -8
  53. package/src/skills/rebuttal/SKILL.md +2 -8
  54. package/src/skills/review/SKILL.md +2 -8
  55. package/src/skills/scout/SKILL.md +2 -8
  56. package/src/skills/write/SKILL.md +52 -16
  57. package/src/skills/write/templates/DEEPSCIENTIST_NOTES.md +21 -0
  58. package/src/skills/write/templates/README.md +408 -0
  59. package/src/skills/write/templates/UPSTREAM_LICENSE.txt +21 -0
  60. package/src/skills/write/templates/aaai2026/README.md +534 -0
  61. package/src/skills/write/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
  62. package/src/skills/write/templates/aaai2026/aaai2026-unified-template.tex +952 -0
  63. package/src/skills/write/templates/aaai2026/aaai2026.bib +111 -0
  64. package/src/skills/write/templates/aaai2026/aaai2026.bst +1493 -0
  65. package/src/skills/write/templates/aaai2026/aaai2026.sty +315 -0
  66. package/src/skills/write/templates/acl/README.md +50 -0
  67. package/src/skills/write/templates/acl/acl.sty +312 -0
  68. package/src/skills/write/templates/acl/acl_latex.tex +377 -0
  69. package/src/skills/write/templates/acl/acl_lualatex.tex +101 -0
  70. package/src/skills/write/templates/acl/acl_natbib.bst +1940 -0
  71. package/src/skills/write/templates/acl/anthology.bib.txt +26 -0
  72. package/src/skills/write/templates/acl/custom.bib +70 -0
  73. package/src/skills/write/templates/acl/formatting.md +326 -0
  74. package/src/skills/write/templates/asplos2027/main.tex +459 -0
  75. package/src/skills/write/templates/asplos2027/references.bib +135 -0
  76. package/src/skills/write/templates/colm2025/README.md +3 -0
  77. package/src/skills/write/templates/colm2025/colm2025_conference.bib +11 -0
  78. package/src/skills/write/templates/colm2025/colm2025_conference.bst +1440 -0
  79. package/src/skills/write/templates/colm2025/colm2025_conference.sty +218 -0
  80. package/src/skills/write/templates/colm2025/colm2025_conference.tex +305 -0
  81. package/src/skills/write/templates/colm2025/fancyhdr.sty +485 -0
  82. package/src/skills/write/templates/colm2025/math_commands.tex +508 -0
  83. package/src/skills/write/templates/colm2025/natbib.sty +1246 -0
  84. package/src/skills/write/templates/iclr2026/fancyhdr.sty +485 -0
  85. package/src/skills/write/templates/iclr2026/iclr2026_conference.bib +24 -0
  86. package/src/skills/write/templates/iclr2026/iclr2026_conference.bst +1440 -0
  87. package/src/skills/write/templates/iclr2026/iclr2026_conference.sty +246 -0
  88. package/src/skills/write/templates/iclr2026/iclr2026_conference.tex +414 -0
  89. package/src/skills/write/templates/iclr2026/math_commands.tex +508 -0
  90. package/src/skills/write/templates/iclr2026/natbib.sty +1246 -0
  91. package/src/skills/write/templates/icml2026/algorithm.sty +79 -0
  92. package/src/skills/write/templates/icml2026/algorithmic.sty +201 -0
  93. package/src/skills/write/templates/icml2026/example_paper.bib +75 -0
  94. package/src/skills/write/templates/icml2026/example_paper.tex +662 -0
  95. package/src/skills/write/templates/icml2026/fancyhdr.sty +864 -0
  96. package/src/skills/write/templates/icml2026/icml2026.bst +1443 -0
  97. package/src/skills/write/templates/icml2026/icml2026.sty +767 -0
  98. package/src/skills/write/templates/neurips2025/Makefile +36 -0
  99. package/src/skills/write/templates/neurips2025/extra_pkgs.tex +53 -0
  100. package/src/skills/write/templates/neurips2025/main.tex +38 -0
  101. package/src/skills/write/templates/neurips2025/neurips.sty +382 -0
  102. package/src/skills/write/templates/nsdi2027/main.tex +426 -0
  103. package/src/skills/write/templates/nsdi2027/references.bib +151 -0
  104. package/src/skills/write/templates/nsdi2027/usenix-2020-09.sty +83 -0
  105. package/src/skills/write/templates/osdi2026/main.tex +429 -0
  106. package/src/skills/write/templates/osdi2026/references.bib +150 -0
  107. package/src/skills/write/templates/osdi2026/usenix-2020-09.sty +83 -0
  108. package/src/skills/write/templates/sosp2026/main.tex +532 -0
  109. package/src/skills/write/templates/sosp2026/references.bib +148 -0
  110. package/src/tui/package.json +1 -1
  111. package/src/ui/dist/assets/{AiManusChatView-BGLArZRn.js → AiManusChatView-m2FNtwbn.js} +110 -14
  112. package/src/ui/dist/assets/{AnalysisPlugin-BgDGSigG.js → AnalysisPlugin-BMTF8EGL.js} +1 -1
  113. package/src/ui/dist/assets/{AutoFigurePlugin-B65HD7L4.js → AutoFigurePlugin-DxPdMUNb.js} +5 -5
  114. package/src/ui/dist/assets/{CliPlugin-CUqgsFHC.js → CliPlugin-BEOWgxCI.js} +9 -9
  115. package/src/ui/dist/assets/{CodeEditorPlugin-CF5EdvaS.js → CodeEditorPlugin-BCXvjqmb.js} +8 -8
  116. package/src/ui/dist/assets/{CodeViewerPlugin-DEeU063D.js → CodeViewerPlugin-DaJcy3nD.js} +5 -5
  117. package/src/ui/dist/assets/{DocViewerPlugin-Df-FuDlZ.js → DocViewerPlugin-ByfeIq4K.js} +3 -3
  118. package/src/ui/dist/assets/{GitDiffViewerPlugin-RAnNaRxM.js → GitDiffViewerPlugin-Cksf3VZ-.js} +830 -86
  119. package/src/ui/dist/assets/{ImageViewerPlugin-DXJ0ZJGg.js → ImageViewerPlugin-CFz-OsTS.js} +5 -5
  120. package/src/ui/dist/assets/{LabCopilotPanel-BlO-sKsj.js → LabCopilotPanel-CJ1cJzoX.js} +10 -10
  121. package/src/ui/dist/assets/{LabPlugin-BajPZW5v.js → LabPlugin-BF3dVJwa.js} +1 -1
  122. package/src/ui/dist/assets/{LatexPlugin-F1OEol8D.js → LatexPlugin-DDkwZ6Sj.js} +7 -7
  123. package/src/ui/dist/assets/{MarkdownViewerPlugin-MhUupqwT.js → MarkdownViewerPlugin-HAuvurcT.js} +4 -4
  124. package/src/ui/dist/assets/{MarketplacePlugin-DxhIEsv0.js → MarketplacePlugin-BtoTYy2C.js} +3 -3
  125. package/src/ui/dist/assets/{index-B-2scqCJ.js → NotebookEditor-CSJYx7b-.js} +12 -155
  126. package/src/ui/dist/assets/{NotebookEditor-q7TkhewC.js → NotebookEditor-DQgRezm_.js} +1 -1
  127. package/src/ui/dist/assets/{PdfLoader-B8ZOTKFc.js → PdfLoader-DPa_-fv6.js} +1 -1
  128. package/src/ui/dist/assets/{PdfMarkdownPlugin-xFPvzvWh.js → PdfMarkdownPlugin-BZpXOEjm.js} +3 -3
  129. package/src/ui/dist/assets/{PdfViewerPlugin-EjEcsIB8.js → PdfViewerPlugin-BT8a6wGR.js} +10 -10
  130. package/src/ui/dist/assets/{SearchPlugin-ixY-1lgW.js → SearchPlugin-D_blveZi.js} +1 -1
  131. package/src/ui/dist/assets/{Stepper-gYFK2Pgz.js → Stepper-DH2k75Vo.js} +1 -1
  132. package/src/ui/dist/assets/{TextViewerPlugin-Cym6pv_n.js → TextViewerPlugin-Btx0M3hX.js} +4 -4
  133. package/src/ui/dist/assets/{VNCViewer-BPmIHcmK.js → VNCViewer-DImJO4rO.js} +9 -9
  134. package/src/ui/dist/assets/{bibtex-Btv6Wi7f.js → bibtex-B-Hqu0Sg.js} +1 -1
  135. package/src/ui/dist/assets/{code-BlG7g85c.js → code-BUfXGJSl.js} +1 -1
  136. package/src/ui/dist/assets/{file-content-DBT5OfTZ.js → file-content-VqamwI3X.js} +1 -1
  137. package/src/ui/dist/assets/{file-diff-panel-BWXYzqHk.js → file-diff-panel-C_wOoS7a.js} +1 -1
  138. package/src/ui/dist/assets/{file-socket-wDlx6byM.js → file-socket-D2bTuMVP.js} +1 -1
  139. package/src/ui/dist/assets/{file-utils-Ba3nJmH0.js → file-utils--zJCPN1i.js} +1 -1
  140. package/src/ui/dist/assets/{image-BwtCyguk.js → image-BZkGJ4mM.js} +1 -1
  141. package/src/ui/dist/assets/{index-CfRpE209.js → index-CxkvSeKw.js} +2 -2
  142. package/src/ui/dist/assets/{index-DcqvKzeJ.js → index-D9QIGcmc.js} +1 -1
  143. package/src/ui/dist/assets/{index-DpMZw8aM.css → index-DXZ1daiJ.css} +163 -34
  144. package/src/ui/dist/assets/index-DdRW6RMJ.js +159 -0
  145. package/src/ui/dist/assets/{index-Bz5AaWL7.js → index-DjggJovS.js} +2948 -1565
  146. package/src/ui/dist/assets/{message-square-BnlyWVH0.js → message-square-FUIPIhU2.js} +1 -1
  147. package/src/ui/dist/assets/{monaco-CXe0pAVe.js → monaco-DHMc7kKM.js} +1 -1
  148. package/src/ui/dist/assets/{popover-BCHmVhHj.js → popover-B85oCgCS.js} +1 -1
  149. package/src/ui/dist/assets/{project-sync-Brk6kaOD.js → project-sync-DOMCcPac.js} +1 -1
  150. package/src/ui/dist/assets/{sigma-D72eSUep.js → sigma-BO2rQrl3.js} +1 -1
  151. package/src/ui/dist/assets/{tooltip-BMWd0dqX.js → tooltip-B1OspAkx.js} +1 -1
  152. package/src/ui/dist/assets/{trash-BIt_eWIS.js → trash-BsVEH_dV.js} +1 -1
  153. package/src/ui/dist/assets/{useCliAccess-N1hkTRrR.js → useCliAccess-b8L6JuZm.js} +1 -1
  154. package/src/ui/dist/assets/{useFileDiffOverlay-DPRPv6rv.js → useFileDiffOverlay-BY7uA9hV.js} +1 -1
  155. package/src/ui/dist/assets/{wrap-text-E5-UheyP.js → wrap-text-BwyVuUIK.js} +1 -1
  156. package/src/ui/dist/assets/{zoom-out-D4TR-ZZ_.js → zoom-out-RDpLugQP.js} +1 -1
  157. package/src/ui/dist/index.html +5 -2
  158. /package/src/ui/dist/assets/{index-CccQYZjX.css → NotebookEditor-CccQYZjX.css} +0 -0
package/README.md CHANGED
@@ -17,6 +17,8 @@ Install DeepScientist:
17
17
  npm install -g @researai/deepscientist
18
18
  ```
19
19
 
20
+ For the best experience, we recommend using GPT-5.4 in `xhigh` mode (for example, via the $20 GPT Plus plan or the $200 GPT Pro plan); if you are in China, we recommend the MiniMax-M2.7 Token Plan (Max plan): https://platform.minimaxi.com/subscribe/token-plan
21
+
20
22
  ## Start
21
23
 
22
24
  ```bash
@@ -25,6 +27,20 @@ ds
25
27
 
26
28
  DeepScientist starts the local web workspace at `http://127.0.0.1:20999` by default.
27
29
 
30
+ By default, DeepScientist keeps Codex on the standard profile: `approval_policy=on-request` and `sandbox_mode=workspace-write`. Use `--yolo` only when you want explicit full-access execution.
31
+
32
+ Recommended command when you want the current directory as the home and Codex full-access execution:
33
+
34
+ ```bash
35
+ ds --yolo --port 20999 --here
36
+ ```
37
+
38
+ Parameter meanings:
39
+
40
+ - `--yolo`: run Codex in YOLO mode, which sets `approval_policy=never` and `sandbox_mode=danger-full-access`
41
+ - `--port 20999`: bind the local web workspace to port `20999`
42
+ - `--here`: use the current working directory as the DeepScientist home
43
+
28
44
  On first start, `ds` will:
29
45
 
30
46
  - bootstrap a local `uv` runtime manager automatically if your machine does not already have one
@@ -37,6 +53,12 @@ If you want another port:
37
53
  ds --port 21000
38
54
  ```
39
55
 
56
+ If you want YOLO mode on another port:
57
+
58
+ ```bash
59
+ ds --yolo --port 21000
60
+ ```
61
+
40
62
  If you want to bind on all interfaces:
41
63
 
42
64
  ```bash
@@ -60,6 +82,14 @@ ds --here
60
82
 
61
83
  This is equivalent to launching with `ds --home "$PWD"`.
62
84
 
85
+ Useful launch examples:
86
+
87
+ ```bash
88
+ ds --yolo --port 20999 --here
89
+ ds --host 0.0.0.0 --port 21000
90
+ ds --yolo --host 0.0.0.0 --port 21000 --here
91
+ ```
92
+
63
93
  If you want to install the bundled CLI tree into another base path from a source checkout:
64
94
 
65
95
  ```bash
@@ -96,6 +126,8 @@ This installs a lightweight TinyTeX `pdflatex` runtime for local paper compilati
96
126
  - [快速开始(中文)](docs/zh/00_QUICK_START.md)
97
127
  - [QQ Connector Guide (English)](docs/en/03_QQ_CONNECTOR_GUIDE.md)
98
128
  - [QQ Connector Guide (中文)](docs/zh/03_QQ_CONNECTOR_GUIDE.md)
129
+ - [Memory and MCP Guide (English)](docs/en/07_MEMORY_AND_MCP.md)
130
+ - [Memory 与 MCP 指南(中文)](docs/zh/07_MEMORY_AND_MCP.md)
99
131
 
100
132
  ## Maintainers
101
133
 
package/bin/ds.js CHANGED
@@ -38,6 +38,15 @@ const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
38
38
 
39
39
  const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy']);
40
40
 
41
+ function buildCodexOverrideEnv({ yolo = false } = {}) {
42
+ if (!yolo) {
43
+ return {};
44
+ }
45
+ return {
46
+ DEEPSCIENTIST_CODEX_YOLO: '1',
47
+ };
48
+ }
49
+
41
50
  function printLauncherHelp() {
42
51
  console.log(`DeepScientist launcher
43
52
 
@@ -48,6 +57,7 @@ Usage:
48
57
  ds update --yes
49
58
  ds migrate /data/DeepScientist
50
59
  ds --here
60
+ ds --yolo --port 20999 --here
51
61
  ds --here doctor
52
62
  ds --tui
53
63
  ds --both
@@ -73,6 +83,7 @@ Launcher flags:
73
83
  --home <path> Use a custom DeepScientist home
74
84
  --here Use the current working directory as DeepScientist home
75
85
  --proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
86
+ --yolo Run Codex in YOLO mode: approval_policy=never and sandbox_mode=danger-full-access
76
87
  --quest-id <id> Open the TUI on one quest directly
77
88
 
78
89
  Update:
@@ -209,6 +220,17 @@ function compareVersions(left, right) {
209
220
  return 0;
210
221
  }
211
222
 
223
+ function hasActiveBusyUpdate(state, currentVersion) {
224
+ if (!state || !state.busy) {
225
+ return false;
226
+ }
227
+ const targetVersion = normalizeVersion(state.target_version || state.latest_version || '');
228
+ if (!targetVersion) {
229
+ return false;
230
+ }
231
+ return compareVersions(targetVersion, currentVersion) > 0;
232
+ }
233
+
212
234
  function detectInstallMode(rootPath = repoRoot) {
213
235
  const normalized = String(rootPath || '');
214
236
  return normalized.includes(`${path.sep}node_modules${path.sep}`) ? 'npm-package' : 'source-checkout';
@@ -315,6 +337,8 @@ function buildUpdateStatus(home, statePatch = {}) {
315
337
  const support = updateSupportSummary(installMode, npmBinary, launcherPath);
316
338
  const currentVersion = normalizeVersion(state.current_version || packageJson.version);
317
339
  const latestVersion = normalizeVersion(state.latest_version || '');
340
+ const targetVersion = normalizeVersion(state.target_version || '');
341
+ const busy = hasActiveBusyUpdate(state, currentVersion);
318
342
  const promptedVersion = normalizeVersion(state.last_prompted_version || '');
319
343
  const updateAvailable = Boolean(latestVersion) && compareVersions(latestVersion, currentVersion) > 0;
320
344
  const skippedVersion = normalizeVersion(state.last_skipped_version || '');
@@ -322,7 +346,7 @@ function buildUpdateStatus(home, statePatch = {}) {
322
346
  const skippedCurrentTarget = Boolean(updateAvailable && skippedVersion && skippedVersion === latestVersion);
323
347
  const promptRecommended =
324
348
  Boolean(updateAvailable)
325
- && !Boolean(state.busy)
349
+ && !busy
326
350
  && !promptedCurrentTarget
327
351
  && !skippedCurrentTarget
328
352
  ;
@@ -337,7 +361,7 @@ function buildUpdateStatus(home, statePatch = {}) {
337
361
  latest_version: latestVersion || null,
338
362
  update_available: updateAvailable,
339
363
  prompt_recommended: promptRecommended,
340
- busy: Boolean(state.busy),
364
+ busy,
341
365
  last_checked_at: state.last_checked_at || null,
342
366
  last_check_error: state.last_check_error || null,
343
367
  last_prompted_at: state.last_prompted_at || null,
@@ -347,7 +371,7 @@ function buildUpdateStatus(home, statePatch = {}) {
347
371
  last_update_started_at: state.last_update_started_at || null,
348
372
  last_update_finished_at: state.last_update_finished_at || null,
349
373
  last_update_result: state.last_update_result || null,
350
- target_version: normalizeVersion(state.target_version || '') || null,
374
+ target_version: busy ? targetVersion || latestVersion || null : null,
351
375
  manual_update_command: updateManualCommand(installMode),
352
376
  reason: support.reason,
353
377
  };
@@ -360,8 +384,16 @@ function checkForUpdates(home, { force = false, timeoutMs = 3500 } = {}) {
360
384
  const npmBinary = resolveNpmBinary();
361
385
  const launcherPath = resolveLauncherPath();
362
386
  const support = updateSupportSummary(installMode, npmBinary, launcherPath);
387
+ const existingBusyIsStale = Boolean(existing.busy) && !hasActiveBusyUpdate(existing, currentVersion);
363
388
 
364
389
  if (!force && existing.current_version === currentVersion && !isExpired(existing.last_checked_at, UPDATE_CHECK_TTL_MS)) {
390
+ if (existingBusyIsStale) {
391
+ const repaired = mergeUpdateState(home, {
392
+ busy: false,
393
+ target_version: null,
394
+ });
395
+ return buildUpdateStatus(home, repaired);
396
+ }
365
397
  return buildUpdateStatus(home);
366
398
  }
367
399
 
@@ -381,6 +413,13 @@ function checkForUpdates(home, { force = false, timeoutMs = 3500 } = {}) {
381
413
  last_checked_at: new Date().toISOString(),
382
414
  last_check_error: probe.ok ? null : probe.error,
383
415
  });
416
+ if (Boolean(patched.busy) && !hasActiveBusyUpdate(patched, currentVersion)) {
417
+ const repaired = mergeUpdateState(home, {
418
+ busy: false,
419
+ target_version: null,
420
+ });
421
+ return buildUpdateStatus(home, repaired);
422
+ }
384
423
  return buildUpdateStatus(home, patched);
385
424
  }
386
425
 
@@ -581,13 +620,14 @@ function pythonVersionText(probe) {
581
620
  return version;
582
621
  }
583
622
 
584
- function renderLaunchHints({ home, url, bindUrl, pythonSelection }) {
623
+ function renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo }) {
585
624
  const runtimeRows = [
586
625
  ['Version', packageJson.version],
587
626
  ['Home', truncateMiddle(home)],
588
627
  ['Browser URL', url],
589
628
  ['Bind URL', bindUrl],
590
629
  ['Python', truncateMiddle(pythonVersionText(pythonSelection))],
630
+ ['Codex mode', yolo ? 'YOLO (never + danger-full-access)' : 'Default (on-request + workspace-write)'],
591
631
  ];
592
632
  if (pythonSelection && pythonSelection.sourceLabel) {
593
633
  runtimeRows.push(['Python source', pythonSelection.sourceLabel]);
@@ -598,6 +638,7 @@ function renderLaunchHints({ home, url, bindUrl, pythonSelection }) {
598
638
 
599
639
  console.log(colorize('\u001B[1;38;5;39m', 'Quick Flags'));
600
640
  renderKeyValueRows([
641
+ ['ds --yolo --port 20999 --here', 'Start in the current directory with YOLO Codex access'],
601
642
  ['ds --port 21000', 'Change the web port'],
602
643
  ['ds --host 0.0.0.0 --port 21000', 'Bind on all interfaces'],
603
644
  ['ds --here', 'Use the current directory as home'],
@@ -622,6 +663,7 @@ function printLaunchCard({
622
663
  daemonOnly,
623
664
  home,
624
665
  pythonSelection,
666
+ yolo,
625
667
  }) {
626
668
  const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
627
669
  const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
@@ -680,7 +722,7 @@ function printLaunchCard({
680
722
  console.log(centerText('Run ds --stop to stop the managed daemon.', width));
681
723
  console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
682
724
  console.log('');
683
- renderLaunchHints({ home, url, bindUrl, pythonSelection });
725
+ renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
684
726
  }
685
727
 
686
728
  function escapeHtml(value) {
@@ -803,7 +845,7 @@ function writeCodexPreflightReport(home, probe) {
803
845
  };
804
846
  }
805
847
 
806
- function readCodexBootstrapState(home, runtimePython) {
848
+ function readCodexBootstrapState(home, runtimePython, envOverrides = {}) {
807
849
  const snippet = [
808
850
  'import json, pathlib, sys',
809
851
  'from deepscientist.config import ConfigManager',
@@ -811,7 +853,14 @@ function readCodexBootstrapState(home, runtimePython) {
811
853
  'manager = ConfigManager(home)',
812
854
  'print(json.dumps(manager.codex_bootstrap_state(), ensure_ascii=False))',
813
855
  ].join('\n');
814
- const result = runSync(runtimePython, ['-c', snippet, home], { capture: true, allowFailure: true });
856
+ const result = runSync(runtimePython, ['-c', snippet, home], {
857
+ capture: true,
858
+ allowFailure: true,
859
+ env: {
860
+ ...process.env,
861
+ ...envOverrides,
862
+ },
863
+ });
815
864
  if (result.status !== 0) {
816
865
  return { codex_ready: false, codex_last_checked_at: null, codex_last_result: {} };
817
866
  }
@@ -822,7 +871,7 @@ function readCodexBootstrapState(home, runtimePython) {
822
871
  }
823
872
  }
824
873
 
825
- function probeCodexBootstrap(home, runtimePython) {
874
+ function probeCodexBootstrap(home, runtimePython, envOverrides = {}) {
826
875
  const snippet = [
827
876
  'import json, pathlib, sys',
828
877
  'from deepscientist.config import ConfigManager',
@@ -830,7 +879,14 @@ function probeCodexBootstrap(home, runtimePython) {
830
879
  'manager = ConfigManager(home)',
831
880
  'print(json.dumps(manager.probe_codex_bootstrap(persist=True), ensure_ascii=False))',
832
881
  ].join('\n');
833
- const result = runSync(runtimePython, ['-c', snippet, home], { capture: true, allowFailure: true });
882
+ const result = runSync(runtimePython, ['-c', snippet, home], {
883
+ capture: true,
884
+ allowFailure: true,
885
+ env: {
886
+ ...process.env,
887
+ ...envOverrides,
888
+ },
889
+ });
834
890
  let payload = null;
835
891
  try {
836
892
  payload = JSON.parse(result.stdout || '{}');
@@ -881,6 +937,7 @@ function parseLauncherArgs(argv) {
881
937
  let status = false;
882
938
  let daemonOnly = false;
883
939
  let skipUpdateCheck = false;
940
+ let yolo = false;
884
941
 
885
942
  if (args[0] === 'ui') {
886
943
  args.shift();
@@ -899,6 +956,7 @@ function parseLauncherArgs(argv) {
899
956
  else if (arg === '--open-browser') openBrowser = true;
900
957
  else if (arg === '--daemon-only') daemonOnly = true;
901
958
  else if (arg === '--skip-update-check') skipUpdateCheck = true;
959
+ else if (arg === '--yolo') yolo = true;
902
960
  else if (arg === '--host' && args[index + 1]) host = args[++index];
903
961
  else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
904
962
  else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
@@ -923,6 +981,7 @@ function parseLauncherArgs(argv) {
923
981
  questId,
924
982
  daemonOnly,
925
983
  skipUpdateCheck,
984
+ yolo,
926
985
  };
927
986
  }
928
987
 
@@ -1111,6 +1170,40 @@ function buildGlobalWrapperScript({ installDir, home, commandName }) {
1111
1170
  return [
1112
1171
  '#!/usr/bin/env bash',
1113
1172
  'set -euo pipefail',
1173
+ 'WRAPPER_PATH="${BASH_SOURCE[0]}"',
1174
+ 'WRAPPER_DIR="$(cd "$(dirname "$WRAPPER_PATH")" && pwd)"',
1175
+ `PREFERRED_COMMAND="${commandName}"`,
1176
+ 'LOOKUP_PATH=""',
1177
+ 'OLD_IFS="$IFS"',
1178
+ 'IFS=:',
1179
+ 'for ENTRY in $PATH; do',
1180
+ ' if [ -z "$ENTRY" ]; then',
1181
+ ' continue',
1182
+ ' fi',
1183
+ ' ENTRY_REAL="$ENTRY"',
1184
+ ' if ENTRY_CANONICAL="$(cd "$ENTRY" 2>/dev/null && pwd)"; then',
1185
+ ' ENTRY_REAL="$ENTRY_CANONICAL"',
1186
+ ' fi',
1187
+ ' if [ "$ENTRY_REAL" = "$WRAPPER_DIR" ]; then',
1188
+ ' continue',
1189
+ ' fi',
1190
+ ' if [ -z "$LOOKUP_PATH" ]; then',
1191
+ ' LOOKUP_PATH="$ENTRY"',
1192
+ ' else',
1193
+ ' LOOKUP_PATH="$LOOKUP_PATH:$ENTRY"',
1194
+ ' fi',
1195
+ 'done',
1196
+ 'IFS="$OLD_IFS"',
1197
+ 'if [ -n "$LOOKUP_PATH" ]; then',
1198
+ ' if RESOLVED_LAUNCHER="$(PATH="$LOOKUP_PATH" command -v "$PREFERRED_COMMAND" 2>/dev/null)"; then',
1199
+ ' if [ -n "$RESOLVED_LAUNCHER" ] && [ "$RESOLVED_LAUNCHER" != "$WRAPPER_PATH" ]; then',
1200
+ ' if [ -z "${DEEPSCIENTIST_HOME:-}" ]; then',
1201
+ ` export DEEPSCIENTIST_HOME="${home}"`,
1202
+ ' fi',
1203
+ ' exec "$RESOLVED_LAUNCHER" "$@"',
1204
+ ' fi',
1205
+ ' fi',
1206
+ 'fi',
1114
1207
  'if [ -z "${DEEPSCIENTIST_HOME:-}" ]; then',
1115
1208
  ` export DEEPSCIENTIST_HOME="${home}"`,
1116
1209
  'fi',
@@ -1119,6 +1212,18 @@ function buildGlobalWrapperScript({ installDir, home, commandName }) {
1119
1212
  ].join('\n');
1120
1213
  }
1121
1214
 
1215
+ function buildLauncherWrapperScript({ launcherPath, home }) {
1216
+ return [
1217
+ '#!/usr/bin/env bash',
1218
+ 'set -euo pipefail',
1219
+ 'if [ -z "${DEEPSCIENTIST_HOME:-}" ]; then',
1220
+ ` export DEEPSCIENTIST_HOME="${home}"`,
1221
+ 'fi',
1222
+ `exec "${launcherPath}" "$@"`,
1223
+ '',
1224
+ ].join('\n');
1225
+ }
1226
+
1122
1227
  function writeExecutableScript(targetPath, content) {
1123
1228
  ensureDir(path.dirname(targetPath));
1124
1229
  fs.writeFileSync(targetPath, content, { encoding: 'utf8', mode: 0o755 });
@@ -1155,6 +1260,94 @@ function candidateWrapperPathsForCommand(commandName) {
1155
1260
  return candidates;
1156
1261
  }
1157
1262
 
1263
+ function parseLegacyWrapperCandidate(candidatePath) {
1264
+ let stat = null;
1265
+ try {
1266
+ stat = fs.lstatSync(candidatePath);
1267
+ } catch {
1268
+ return null;
1269
+ }
1270
+
1271
+ if (stat.isSymbolicLink()) {
1272
+ let resolved = null;
1273
+ try {
1274
+ resolved = fs.realpathSync(candidatePath);
1275
+ } catch {
1276
+ return null;
1277
+ }
1278
+ if (!/[\\/]cli[\\/]bin[\\/](?:ds|ds-cli|research|resear)(?:\.cmd)?$/.test(resolved)) {
1279
+ return null;
1280
+ }
1281
+ return {
1282
+ source: 'symlink',
1283
+ execPath: resolved,
1284
+ home: path.dirname(path.dirname(path.dirname(resolved))),
1285
+ };
1286
+ }
1287
+
1288
+ if (!stat.isFile()) {
1289
+ return null;
1290
+ }
1291
+
1292
+ let text = '';
1293
+ try {
1294
+ text = fs.readFileSync(candidatePath, 'utf8');
1295
+ } catch {
1296
+ return null;
1297
+ }
1298
+
1299
+ const execMatch = text.match(/exec "([^"\n]+[\\/]bin[\\/](?:ds|ds-cli|research|resear))" "\$@"/);
1300
+ if (!execMatch) {
1301
+ return null;
1302
+ }
1303
+ const execPath = execMatch[1];
1304
+ if (!/[\\/]cli[\\/]bin[\\/](?:ds|ds-cli|research|resear)$/.test(execPath)) {
1305
+ return null;
1306
+ }
1307
+ const homeMatch = text.match(/export DEEPSCIENTIST_HOME="([^"\n]+)"/);
1308
+ return {
1309
+ source: 'script',
1310
+ execPath,
1311
+ home: homeMatch ? homeMatch[1] : path.dirname(path.dirname(path.dirname(execPath))),
1312
+ };
1313
+ }
1314
+
1315
+ function repairLegacyPathWrappers({ home, launcherPath, force = false }) {
1316
+ if (process.platform === 'win32') {
1317
+ return [];
1318
+ }
1319
+ if (!launcherPath || !fs.existsSync(launcherPath)) {
1320
+ return [];
1321
+ }
1322
+ if (!force && detectInstallMode(repoRoot) !== 'npm-package') {
1323
+ return [];
1324
+ }
1325
+
1326
+ const rewritten = [];
1327
+ const seen = new Set();
1328
+ for (const commandName of launcherWrapperCommands) {
1329
+ for (const candidate of candidateWrapperPathsForCommand(commandName)) {
1330
+ if (seen.has(candidate)) {
1331
+ continue;
1332
+ }
1333
+ seen.add(candidate);
1334
+ const legacy = parseLegacyWrapperCandidate(candidate);
1335
+ if (!legacy) {
1336
+ continue;
1337
+ }
1338
+ writeExecutableScript(
1339
+ candidate,
1340
+ buildLauncherWrapperScript({
1341
+ launcherPath,
1342
+ home: legacy.home || home,
1343
+ })
1344
+ );
1345
+ rewritten.push(candidate);
1346
+ }
1347
+ }
1348
+ return rewritten;
1349
+ }
1350
+
1158
1351
  function rewriteLauncherWrappersIfPointingAtSource({ sourceHome, targetHome }) {
1159
1352
  if (process.platform === 'win32') {
1160
1353
  return [];
@@ -2074,6 +2267,9 @@ function normalizePythonCliArgs(args, home) {
2074
2267
  if (arg === '--here') {
2075
2268
  continue;
2076
2269
  }
2270
+ if (arg === '--yolo') {
2271
+ continue;
2272
+ }
2077
2273
  normalized.push(arg);
2078
2274
  }
2079
2275
  return ['--home', home, ...normalized];
@@ -2588,19 +2784,45 @@ function spawnDetachedNode(args, options = {}) {
2588
2784
  async function performSelfUpdate(home, options = {}) {
2589
2785
  const status = checkForUpdates(home, { force: true });
2590
2786
  if (!status.update_available) {
2787
+ const message = `DeepScientist is already on the latest version (${status.current_version}).`;
2788
+ mergeUpdateState(home, {
2789
+ current_version: status.current_version,
2790
+ latest_version: status.latest_version,
2791
+ busy: false,
2792
+ target_version: null,
2793
+ last_update_finished_at: new Date().toISOString(),
2794
+ last_update_result: {
2795
+ ok: true,
2796
+ target_version: null,
2797
+ message,
2798
+ },
2799
+ });
2591
2800
  return {
2592
2801
  ok: true,
2593
2802
  updated: false,
2594
2803
  status,
2595
- message: `DeepScientist is already on the latest version (${status.current_version}).`,
2804
+ message,
2596
2805
  };
2597
2806
  }
2598
2807
  if (!status.can_self_update) {
2808
+ const message = status.reason || `Manual update required: ${status.manual_update_command}`;
2809
+ mergeUpdateState(home, {
2810
+ current_version: status.current_version,
2811
+ latest_version: status.latest_version,
2812
+ busy: false,
2813
+ target_version: null,
2814
+ last_update_finished_at: new Date().toISOString(),
2815
+ last_update_result: {
2816
+ ok: false,
2817
+ target_version: status.latest_version || null,
2818
+ message,
2819
+ },
2820
+ });
2599
2821
  return {
2600
2822
  ok: false,
2601
2823
  updated: false,
2602
2824
  status,
2603
- message: status.reason || `Manual update required: ${status.manual_update_command}`,
2825
+ message,
2604
2826
  };
2605
2827
  }
2606
2828
 
@@ -2663,6 +2885,11 @@ async function performSelfUpdate(home, options = {}) {
2663
2885
  };
2664
2886
  }
2665
2887
 
2888
+ repairLegacyPathWrappers({
2889
+ home,
2890
+ launcherPath: resolveLauncherPath(),
2891
+ });
2892
+
2666
2893
  const restartDaemon =
2667
2894
  options.restartDaemon === true
2668
2895
  || (options.restartDaemon !== false && Boolean(daemonState?.pid || daemonState?.daemon_id));
@@ -2765,6 +2992,22 @@ async function startBackgroundUpdateWorker(home, options = {}) {
2765
2992
  };
2766
2993
  }
2767
2994
  const status = checkForUpdates(home, { force: false });
2995
+ if (!status.update_available) {
2996
+ return {
2997
+ ok: true,
2998
+ started: false,
2999
+ message: `DeepScientist is already on the latest version (${status.current_version}).`,
3000
+ status,
3001
+ };
3002
+ }
3003
+ if (!status.can_self_update) {
3004
+ return {
3005
+ ok: false,
3006
+ started: false,
3007
+ message: status.reason || `Manual update required: ${status.manual_update_command}`,
3008
+ status,
3009
+ };
3010
+ }
2768
3011
  mergeUpdateState(home, {
2769
3012
  current_version: status.current_version,
2770
3013
  latest_version: status.latest_version,
@@ -2856,7 +3099,7 @@ function readConfiguredUiAddressFromFile(home, fallbackHost, fallbackPort) {
2856
3099
  }
2857
3100
  }
2858
3101
 
2859
- async function startDaemon(home, runtimePython, host, port, proxy = null) {
3102
+ async function startDaemon(home, runtimePython, host, port, proxy = null, envOverrides = {}) {
2860
3103
  const browserUrl = browserUiUrl(host, port);
2861
3104
  const daemonBindUrl = bindUiUrl(host, port);
2862
3105
  const state = readDaemonState(home);
@@ -2880,10 +3123,10 @@ async function startDaemon(home, runtimePython, host, port, proxy = null) {
2880
3123
  removeDaemonState(home);
2881
3124
  }
2882
3125
 
2883
- const bootstrapState = readCodexBootstrapState(home, runtimePython);
3126
+ const bootstrapState = readCodexBootstrapState(home, runtimePython, envOverrides);
2884
3127
  if (!bootstrapState.codex_ready) {
2885
3128
  console.log('Codex is not marked ready yet. Running startup probe...');
2886
- const probe = probeCodexBootstrap(home, runtimePython);
3129
+ const probe = probeCodexBootstrap(home, runtimePython, envOverrides);
2887
3130
  if (!probe || probe.ok !== true) {
2888
3131
  throw createCodexPreflightError(home, probe);
2889
3132
  }
@@ -2915,6 +3158,7 @@ async function startDaemon(home, runtimePython, host, port, proxy = null) {
2915
3158
  stdio: ['ignore', out, out],
2916
3159
  env: {
2917
3160
  ...process.env,
3161
+ ...envOverrides,
2918
3162
  DEEPSCIENTIST_REPO_ROOT: repoRoot,
2919
3163
  DEEPSCIENTIST_NODE_BINARY: process.execPath,
2920
3164
  DEEPSCIENTIST_LAUNCHER_PATH: path.join(repoRoot, 'bin', 'ds.js'),
@@ -3276,6 +3520,10 @@ async function launcherMain(rawArgs) {
3276
3520
  const home = options.home || resolveHome(rawArgs);
3277
3521
  applyLauncherProxy(options.proxy);
3278
3522
  ensureDir(home);
3523
+ repairLegacyPathWrappers({
3524
+ home,
3525
+ launcherPath: resolveLauncherPath(),
3526
+ });
3279
3527
 
3280
3528
  if (options.stop) {
3281
3529
  await stopDaemon(home);
@@ -3309,6 +3557,7 @@ async function launcherMain(rawArgs) {
3309
3557
 
3310
3558
  const pythonRuntime = ensurePythonRuntime(home);
3311
3559
  const runtimePython = pythonRuntime.runtimePython;
3560
+ const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo });
3312
3561
  ensureInitialized(home, runtimePython);
3313
3562
  if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
3314
3563
  return true;
@@ -3331,7 +3580,7 @@ async function launcherMain(rawArgs) {
3331
3580
  step(4, 4, 'Starting local daemon and UI surfaces');
3332
3581
  let started;
3333
3582
  try {
3334
- started = await startDaemon(home, runtimePython, host, port, options.proxy);
3583
+ started = await startDaemon(home, runtimePython, host, port, options.proxy, codexOverrideEnv);
3335
3584
  } catch (error) {
3336
3585
  if (handleCodexPreflightFailure(error)) return true;
3337
3586
  throw error;
@@ -3346,6 +3595,7 @@ async function launcherMain(rawArgs) {
3346
3595
  daemonOnly: options.daemonOnly,
3347
3596
  home,
3348
3597
  pythonSelection: pythonRuntime.runtimeProbe,
3598
+ yolo: options.yolo,
3349
3599
  });
3350
3600
 
3351
3601
  if (options.daemonOnly) {
@@ -3381,14 +3631,15 @@ async function main() {
3381
3631
  const home = resolveHome(args);
3382
3632
  const pythonRuntime = ensurePythonRuntime(home);
3383
3633
  const runtimePython = pythonRuntime.runtimePython;
3634
+ const codexOverrideEnv = buildCodexOverrideEnv({ yolo: args.includes('--yolo') });
3384
3635
  if (positional.value === 'run' || positional.value === 'daemon') {
3385
3636
  maybePrintOptionalLatexNotice(home);
3386
3637
  }
3387
3638
  if (positional.value === 'run' || positional.value === 'daemon') {
3388
- const bootstrapState = readCodexBootstrapState(home, runtimePython);
3639
+ const bootstrapState = readCodexBootstrapState(home, runtimePython, codexOverrideEnv);
3389
3640
  if (!bootstrapState.codex_ready) {
3390
3641
  try {
3391
- const probe = probeCodexBootstrap(home, runtimePython);
3642
+ const probe = probeCodexBootstrap(home, runtimePython, codexOverrideEnv);
3392
3643
  if (!probe || probe.ok !== true) {
3393
3644
  throw createCodexPreflightError(home, probe);
3394
3645
  }
@@ -3398,7 +3649,10 @@ async function main() {
3398
3649
  }
3399
3650
  }
3400
3651
  }
3401
- const result = runPythonCli(runtimePython, normalizePythonCliArgs(args, home), { allowFailure: true });
3652
+ const result = runPythonCli(runtimePython, normalizePythonCliArgs(args, home), {
3653
+ allowFailure: true,
3654
+ env: codexOverrideEnv,
3655
+ });
3402
3656
  process.exit(result.status ?? 0);
3403
3657
  return;
3404
3658
  }
@@ -3419,6 +3673,8 @@ module.exports = {
3419
3673
  parseLauncherArgs,
3420
3674
  normalizeProxyUrl,
3421
3675
  parseMigrateArgs,
3676
+ parseLegacyWrapperCandidate,
3677
+ repairLegacyPathWrappers,
3422
3678
  useEditableProjectInstall,
3423
3679
  compareVersions,
3424
3680
  detectInstallMode,
@@ -209,7 +209,44 @@ Example:
209
209
  Do not replace an experiment artifact with a memory card.
210
210
  Do not replace a reusable lesson with a progress artifact.
211
211
 
212
- ## 4. Bash exec usage
212
+ ## 4. Artifact metric-contract rules
213
+
214
+ Use `artifact` as the authoritative submission surface for baseline and main-experiment metrics.
215
+
216
+ ### `artifact.confirm_baseline(...)`
217
+
218
+ For a confirmed baseline:
219
+
220
+ - the canonical metric contract should live in `<baseline_root>/json/metric_contract.json`
221
+ - the canonical `metrics_summary` should be a flat top-level dictionary keyed by the paper-facing metric ids
222
+ - if the raw evaluator output is nested, map each required canonical metric through explicit `origin_path` fields inside `metric_contract.metrics`
223
+ - every canonical baseline metric entry should explain where the number came from:
224
+ - `description`
225
+ - either `derivation` or `origin_path`
226
+ - `source_ref`
227
+ - keep `primary_metric` as the headline metric only; do not use it to erase the rest of the accepted paper-facing comparison surface
228
+
229
+ ### `artifact.record_main_experiment(...)`
230
+
231
+ For a main experiment recorded against a confirmed baseline:
232
+
233
+ - use the confirmed baseline metric-contract JSON as the canonical comparison contract
234
+ - report every required baseline metric id in the main experiment submission
235
+ - extra metrics are allowed, but missing required baseline metrics are not
236
+ - keep the original evaluation code and metric definitions for the canonical baseline metrics
237
+ - if an extra evaluator is genuinely necessary, record it as supplementary evidence instead of replacing the canonical comparator
238
+
239
+ ### Validation and temporary notes
240
+
241
+ - when the MCP tool runs strict validation, contract failures return structured error payloads such as:
242
+ - `missing_metric_ids`
243
+ - `baseline_metric_ids`
244
+ - `baseline_metric_details`
245
+ - `evaluation_protocol_mismatch`
246
+ - `Result/metric.md` may be used as temporary scratch memory while working, but it is optional and not authoritative
247
+ - if `Result/metric.md` exists, reconcile it against the final baseline or main-experiment submission before calling the artifact tool
248
+
249
+ ## 5. Bash exec usage
213
250
 
214
251
  Use `bash_exec` for monitored commands:
215
252
 
@@ -226,7 +263,7 @@ bash_exec.bash_exec(mode="read", id="<bash_id>")
226
263
 
227
264
  Use `kill` only when the quest truly needs to stop the session.
228
265
 
229
- ## 5. Prompt-level expectations
266
+ ## 6. Prompt-level expectations
230
267
 
231
268
  The agent should normally follow this discipline:
232
269
 
@@ -237,7 +274,7 @@ The agent should normally follow this discipline:
237
274
  5. `bash_exec` for durable shell work
238
275
  6. `memory.write(...)` only after a real durable finding appears
239
276
 
240
- ## 6. UI expectation
277
+ ## 7. UI expectation
241
278
 
242
279
  In `/projects/{id}` Studio trace:
243
280