@researai/deepscientist 1.5.9 → 1.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +112 -99
  2. package/assets/branding/connector-qq.png +0 -0
  3. package/assets/branding/connector-rokid.png +0 -0
  4. package/assets/branding/connector-weixin.png +0 -0
  5. package/assets/branding/projects.png +0 -0
  6. package/bin/ds.js +519 -63
  7. package/docs/assets/branding/projects.png +0 -0
  8. package/docs/en/00_QUICK_START.md +338 -68
  9. package/docs/en/01_SETTINGS_REFERENCE.md +14 -0
  10. package/docs/en/02_START_RESEARCH_GUIDE.md +180 -4
  11. package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
  12. package/docs/en/09_DOCTOR.md +66 -5
  13. package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
  14. package/docs/en/11_LICENSE_AND_RISK.md +256 -0
  15. package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +446 -0
  16. package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
  17. package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
  18. package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
  19. package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
  20. package/docs/en/README.md +83 -0
  21. package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
  22. package/docs/images/weixin/weixin-plugin-entry.png +0 -0
  23. package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
  24. package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
  25. package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
  26. package/docs/images/weixin/weixin-settings-bind.svg +57 -0
  27. package/docs/zh/00_QUICK_START.md +345 -72
  28. package/docs/zh/01_SETTINGS_REFERENCE.md +14 -0
  29. package/docs/zh/02_START_RESEARCH_GUIDE.md +181 -3
  30. package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
  31. package/docs/zh/09_DOCTOR.md +68 -5
  32. package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
  33. package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
  34. package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +442 -0
  35. package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
  36. package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
  37. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
  38. package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
  39. package/docs/zh/README.md +129 -0
  40. package/install.sh +0 -34
  41. package/package.json +2 -2
  42. package/pyproject.toml +1 -1
  43. package/src/deepscientist/__init__.py +1 -1
  44. package/src/deepscientist/annotations.py +343 -0
  45. package/src/deepscientist/artifact/arxiv.py +484 -37
  46. package/src/deepscientist/artifact/service.py +574 -108
  47. package/src/deepscientist/arxiv_library.py +275 -0
  48. package/src/deepscientist/bash_exec/monitor.py +7 -5
  49. package/src/deepscientist/bash_exec/service.py +93 -21
  50. package/src/deepscientist/bridges/builtins.py +2 -0
  51. package/src/deepscientist/bridges/connectors.py +447 -0
  52. package/src/deepscientist/channels/__init__.py +2 -0
  53. package/src/deepscientist/channels/builtins.py +3 -1
  54. package/src/deepscientist/channels/local.py +3 -3
  55. package/src/deepscientist/channels/qq.py +8 -8
  56. package/src/deepscientist/channels/qq_gateway.py +1 -1
  57. package/src/deepscientist/channels/relay.py +14 -8
  58. package/src/deepscientist/channels/weixin.py +59 -0
  59. package/src/deepscientist/channels/weixin_ilink.py +388 -0
  60. package/src/deepscientist/config/models.py +23 -2
  61. package/src/deepscientist/config/service.py +539 -67
  62. package/src/deepscientist/connector/__init__.py +4 -0
  63. package/src/deepscientist/connector/connector_profiles.py +481 -0
  64. package/src/deepscientist/connector/lingzhu_support.py +668 -0
  65. package/src/deepscientist/connector/qq_profiles.py +206 -0
  66. package/src/deepscientist/connector/weixin_support.py +663 -0
  67. package/src/deepscientist/connector_profiles.py +1 -374
  68. package/src/deepscientist/connector_runtime.py +2 -0
  69. package/src/deepscientist/daemon/api/handlers.py +165 -5
  70. package/src/deepscientist/daemon/api/router.py +13 -1
  71. package/src/deepscientist/daemon/app.py +1444 -67
  72. package/src/deepscientist/doctor.py +4 -5
  73. package/src/deepscientist/gitops/diff.py +120 -29
  74. package/src/deepscientist/lingzhu_support.py +1 -182
  75. package/src/deepscientist/mcp/server.py +135 -7
  76. package/src/deepscientist/prompts/builder.py +128 -11
  77. package/src/deepscientist/qq_profiles.py +1 -196
  78. package/src/deepscientist/quest/node_traces.py +23 -0
  79. package/src/deepscientist/quest/service.py +359 -74
  80. package/src/deepscientist/quest/stage_views.py +71 -5
  81. package/src/deepscientist/runners/codex.py +170 -19
  82. package/src/deepscientist/runners/runtime_overrides.py +6 -0
  83. package/src/deepscientist/shared.py +33 -14
  84. package/src/deepscientist/weixin_support.py +1 -0
  85. package/src/prompts/connectors/lingzhu.md +3 -1
  86. package/src/prompts/connectors/qq.md +2 -1
  87. package/src/prompts/connectors/weixin.md +231 -0
  88. package/src/prompts/contracts/shared_interaction.md +4 -1
  89. package/src/prompts/system.md +61 -9
  90. package/src/skills/analysis-campaign/SKILL.md +46 -6
  91. package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
  92. package/src/skills/baseline/SKILL.md +1 -1
  93. package/src/skills/decision/SKILL.md +1 -1
  94. package/src/skills/experiment/SKILL.md +1 -1
  95. package/src/skills/finalize/SKILL.md +1 -1
  96. package/src/skills/idea/SKILL.md +1 -1
  97. package/src/skills/intake-audit/SKILL.md +1 -1
  98. package/src/skills/rebuttal/SKILL.md +74 -1
  99. package/src/skills/rebuttal/references/response-letter-template.md +55 -11
  100. package/src/skills/review/SKILL.md +118 -1
  101. package/src/skills/review/references/experiment-todo-template.md +23 -0
  102. package/src/skills/review/references/review-report-template.md +16 -0
  103. package/src/skills/review/references/revision-log-template.md +4 -0
  104. package/src/skills/scout/SKILL.md +1 -1
  105. package/src/skills/write/SKILL.md +168 -7
  106. package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
  107. package/src/tui/package.json +1 -1
  108. package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-CnJcXynW.js} +156 -48
  109. package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
  110. package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-CB1YODQn.js} +164 -9
  111. package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
  112. package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
  113. package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
  114. package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -21
  115. package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
  116. package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
  117. package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-Ciz1gDaX.js} +2 -1
  118. package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BhmjNQRC.js} +37 -11
  119. package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
  120. package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-DmyHspXt.js} +3 -3
  121. package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-BMXKrDRk.js} +1 -1
  122. package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BTVYRGkm.js} +12 -12
  123. package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-CvcjJHXv.js} +14 -7
  124. package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DW2ej8Vk.js} +73 -6
  125. package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-CmlDxbhU.js} +103 -34
  126. package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
  127. package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DAjQZPSv.js} +1 -1
  128. package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C-nVAZb_.js} +5 -4
  129. package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-D7-dIYon.js} +10 -10
  130. package/src/ui/dist/assets/bot-C_G4WtNI.js +21 -0
  131. package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
  132. package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
  133. package/src/ui/dist/assets/{code-BWAY76JP.js → code-Cd7WfiWq.js} +1 -1
  134. package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-B57zsL9y.js} +1 -1
  135. package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-DVoheLFq.js} +1 -1
  136. package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-B5kXFxZP.js} +1 -1
  137. package/src/ui/dist/assets/{image-D-NZM-6P.js → image-LLOjkMHF.js} +1 -1
  138. package/src/ui/dist/assets/{index-DGIYDuTv.css → index-BQG-1s2o.css} +40 -13
  139. package/src/ui/dist/assets/{index-DHZJ_0TI.js → index-C3r2iGrp.js} +12 -12
  140. package/src/ui/dist/assets/{index-7Chr1g9c.js → index-CLQauncb.js} +15050 -9561
  141. package/src/ui/dist/assets/index-Dxa2eYMY.js +25 -0
  142. package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-hOUOWbW2.js} +2 -2
  143. package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-BGGAEii3.js} +1 -1
  144. package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DlEr1_y5.js} +16 -1
  145. package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
  146. package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-CWJbJuYY.js} +1 -1
  147. package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-CRJiucYO.js} +18 -77
  148. package/src/ui/dist/assets/select-CoHB7pvH.js +1690 -0
  149. package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-D5aJWR8J.js} +1 -1
  150. package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-DUK_mnkS.js} +2 -13
  151. package/src/ui/dist/assets/{trash-BvTgE5__.js → trash-ChU3SEE3.js} +1 -1
  152. package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-BrJBV3tY.js} +1 -1
  153. package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
  154. package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-C7Qqh-om.js} +1 -1
  155. package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-rtX0FKya.js} +1 -1
  156. package/src/ui/dist/index.html +2 -2
  157. package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
  158. package/src/ui/dist/assets/AutoFigurePlugin-C_wWw4AP.js +0 -8149
  159. package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
  160. package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
  161. package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
  162. package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
  163. package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
  164. package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
  165. package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
package/bin/ds.js CHANGED
@@ -36,15 +36,33 @@ const pythonCommands = new Set([
36
36
  const UPDATE_PACKAGE_NAME = String(packageJson.name || '@researai/deepscientist').trim() || '@researai/deepscientist';
37
37
  const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
38
38
 
39
- const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy']);
39
+ const optionsWithValues = new Set(['--home', '--host', '--port', '--quest-id', '--mode', '--proxy', '--codex-profile']);
40
40
 
41
- function buildCodexOverrideEnv({ yolo = false } = {}) {
41
+ function buildCodexOverrideEnv({ yolo = false, profile = null } = {}) {
42
+ const normalizedProfile = typeof profile === 'string' ? profile.trim() : '';
43
+ const overrides = {};
42
44
  if (!yolo) {
43
- return {};
45
+ if (normalizedProfile) {
46
+ overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
47
+ overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
48
+ }
49
+ return overrides;
44
50
  }
45
- return {
46
- DEEPSCIENTIST_CODEX_YOLO: '1',
47
- };
51
+ overrides.DEEPSCIENTIST_CODEX_YOLO = '1';
52
+ if (normalizedProfile) {
53
+ overrides.DEEPSCIENTIST_CODEX_PROFILE = normalizedProfile;
54
+ overrides.DEEPSCIENTIST_CODEX_MODEL = 'inherit';
55
+ }
56
+ return overrides;
57
+ }
58
+
59
+ function readOptionValue(argv, optionName) {
60
+ for (let index = 0; index < argv.length; index += 1) {
61
+ if (argv[index] === optionName && argv[index + 1]) {
62
+ return argv[index + 1];
63
+ }
64
+ }
65
+ return null;
48
66
  }
49
67
 
50
68
  function printLauncherHelp() {
@@ -84,6 +102,7 @@ Launcher flags:
84
102
  --here Create/use ./DeepScientist under the current working directory as home
85
103
  --proxy <url> Use an outbound HTTP/WS proxy for npm and Python runtime traffic
86
104
  --yolo Run Codex in YOLO mode: approval_policy=never and sandbox_mode=danger-full-access
105
+ --codex-profile <id> Run DeepScientist with a specific Codex profile, for example \`m27\`
87
106
  --quest-id <id> Open the TUI on one quest directly
88
107
 
89
108
  Update:
@@ -564,6 +583,12 @@ function colorize(code, text) {
564
583
  return `${code}${text}\u001B[0m`;
565
584
  }
566
585
 
586
+ const OFFICIAL_REPOSITORY_URL = 'https://github.com/ResearAI/DeepScientist';
587
+
588
+ function officialRepositoryLine() {
589
+ return `Official open-source repository: ${hyperlink(OFFICIAL_REPOSITORY_URL, OFFICIAL_REPOSITORY_URL)}`;
590
+ }
591
+
567
592
  function renderBrandArtwork() {
568
593
  const brandPath = path.join(repoRoot, 'assets', 'branding', 'deepscientist-mark.png');
569
594
  const chafa = resolveExecutableOnPath('chafa');
@@ -668,7 +693,10 @@ function printLaunchCard({
668
693
  const width = Math.max(72, Math.min(process.stdout.columns || 100, 108));
669
694
  const divider = colorize('\u001B[38;5;245m', '─'.repeat(Math.max(36, width - 6)));
670
695
  const title = colorize('\u001B[1;38;5;39m', 'ResearAI');
671
- const subtitle = colorize('\u001B[38;5;110m', 'Local-first research operating system');
696
+ const subtitleLines = [
697
+ colorize('\u001B[38;5;110m', 'DeepScientist is not just a fully open-source autonomous scientific discovery system.'),
698
+ colorize('\u001B[38;5;110m', 'It is also a research map that keeps growing from every round.'),
699
+ ];
672
700
  const versionLine = colorize('\u001B[38;5;245m', `Version ${packageJson.version}`);
673
701
  const urlLabel = colorize('\u001B[1;38;5;45m', hyperlink(url, url));
674
702
  const workspaceMode =
@@ -711,7 +739,9 @@ function printLaunchCard({
711
739
  for (const line of wordmark) {
712
740
  console.log(centerText(colorize('\u001B[1;38;5;39m', line), width));
713
741
  }
714
- console.log(centerText(subtitle, width));
742
+ for (const line of subtitleLines) {
743
+ console.log(centerText(line, width));
744
+ }
715
745
  console.log('');
716
746
  console.log(centerText(divider, width));
717
747
  console.log(centerText(colorize('\u001B[1m', workspaceMode), width));
@@ -721,6 +751,7 @@ function printLaunchCard({
721
751
  console.log(centerText(nextStep, width));
722
752
  console.log(centerText('Run ds --stop to stop the managed daemon.', width));
723
753
  console.log(centerText('Need to move this installation later? Use ds migrate /new/path.', width));
754
+ console.log(centerText(officialRepositoryLine(), width));
724
755
  console.log('');
725
756
  renderLaunchHints({ home, url, bindUrl, pythonSelection, yolo });
726
757
  }
@@ -742,6 +773,13 @@ function writeCodexPreflightReport(home, probe) {
742
773
  const errors = Array.isArray(probe?.errors) ? probe.errors : [];
743
774
  const guidance = Array.isArray(probe?.guidance) ? probe.guidance : [];
744
775
  const details = probe && typeof probe.details === 'object' ? probe.details : {};
776
+ const profile = typeof details.profile === 'string' ? details.profile.trim() : '';
777
+ const intro = profile
778
+ ? `DeepScientist blocked startup because the Codex hello probe did not pass for profile \`${profile}\`. Verify that \`codex --profile ${profile}\` works on this machine and that the profile's provider-specific API key, Base URL, and model configuration are already set up.`
779
+ : 'DeepScientist blocked startup because the Codex hello probe did not pass. In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency. If `codex` is still missing, repair it with `npm install -g @openai/codex`. Then run `codex --login` (or `codex`), finish authentication, run `ds doctor`, and launch `ds` again.';
780
+ const introZh = profile
781
+ ? `DeepScientist 启动前进行了 Codex 可用性检查,但 profile \`${profile}\` 的 hello 探测没有通过。请先确认 \`codex --profile ${profile}\` 在当前机器上可以正常启动,并确保该 profile 依赖的 provider API Key、Base URL 和模型配置都已经在 Codex 中配置好。`
782
+ : 'DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。正常情况下,`npm install -g @researai/deepscientist` 也会一并安装 bundled Codex 依赖;如果此后 `codex` 仍不可用,请再执行 `npm install -g @openai/codex` 修复。然后运行 `codex --login`(或 `codex`)完成认证,再执行 `ds doctor`,最后重新启动 `ds`。';
745
783
  const renderItems = (items, tone) =>
746
784
  items
747
785
  .map(
@@ -802,8 +840,8 @@ function writeCodexPreflightReport(home, probe) {
802
840
  <main class="page">
803
841
  <section class="panel">
804
842
  <h1>DeepScientist could not start Codex</h1>
805
- <p class="meta">DeepScientist blocked startup because the Codex hello probe did not pass. Please run <code>codex</code>, complete login, then launch <code>ds</code> again.</p>
806
- <p class="meta">DeepScientist 启动前进行了 Codex 可用性检查,但 hello 探测没有通过。请先手动运行 <code>codex</code> 并完成登录,再重新启动 <code>ds</code>。</p>
843
+ <p class="meta">${escapeHtml(intro)}</p>
844
+ <p class="meta">${escapeHtml(introZh)}</p>
807
845
 
808
846
  <h2>Summary</h2>
809
847
  <p>${escapeHtml(probe?.summary || 'Codex startup probe failed.')}</p>
@@ -826,6 +864,10 @@ function writeCodexPreflightReport(home, probe) {
826
864
  <dt>Model</dt>
827
865
  <dd>${escapeHtml(details.model || '')}</dd>
828
866
  </dl>
867
+ <dl class="kv">
868
+ <dt>Profile</dt>
869
+ <dd>${escapeHtml(details.profile || '')}</dd>
870
+ </dl>
829
871
  <dl class="kv">
830
872
  <dt>Exit code</dt>
831
873
  <dd>${escapeHtml(details.exit_code ?? '')}</dd>
@@ -938,6 +980,7 @@ function parseLauncherArgs(argv) {
938
980
  let daemonOnly = false;
939
981
  let skipUpdateCheck = false;
940
982
  let yolo = false;
983
+ let codexProfile = null;
941
984
 
942
985
  if (args[0] === 'ui') {
943
986
  args.shift();
@@ -957,6 +1000,7 @@ function parseLauncherArgs(argv) {
957
1000
  else if (arg === '--daemon-only') daemonOnly = true;
958
1001
  else if (arg === '--skip-update-check') skipUpdateCheck = true;
959
1002
  else if (arg === '--yolo') yolo = true;
1003
+ else if (arg === '--codex-profile' && args[index + 1]) codexProfile = args[++index];
960
1004
  else if (arg === '--host' && args[index + 1]) host = args[++index];
961
1005
  else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
962
1006
  else if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
@@ -982,6 +1026,7 @@ function parseLauncherArgs(argv) {
982
1026
  daemonOnly,
983
1027
  skipUpdateCheck,
984
1028
  yolo,
1029
+ codexProfile,
985
1030
  };
986
1031
  }
987
1032
 
@@ -1002,6 +1047,8 @@ Flags:
1002
1047
  --force-check Ignore the cached version probe
1003
1048
  --remind-later Defer prompts for the current published version
1004
1049
  --skip-version Skip reminders for the current published version
1050
+
1051
+ Without \`--yes\`, \`ds update\` will ask for a \`Y/N\` confirmation on interactive terminals.
1005
1052
  `);
1006
1053
  }
1007
1054
 
@@ -2270,6 +2317,10 @@ function normalizePythonCliArgs(args, home) {
2270
2317
  if (arg === '--yolo') {
2271
2318
  continue;
2272
2319
  }
2320
+ if (arg === '--codex-profile') {
2321
+ index += 1;
2322
+ continue;
2323
+ }
2273
2324
  normalized.push(arg);
2274
2325
  }
2275
2326
  return ['--home', home, ...normalized];
@@ -2478,6 +2529,270 @@ function removeDaemonState(home) {
2478
2529
  }
2479
2530
  }
2480
2531
 
2532
+ function daemonSupervisorLogPath(home) {
2533
+ return path.join(home, 'logs', 'daemon-supervisor.log');
2534
+ }
2535
+
2536
+ function appendDaemonSupervisorLog(home, message) {
2537
+ try {
2538
+ const logPath = daemonSupervisorLogPath(home);
2539
+ ensureDir(path.dirname(logPath));
2540
+ fs.appendFileSync(logPath, `[${new Date().toISOString()}] ${String(message || '').trim()}\n`, 'utf8');
2541
+ } catch {}
2542
+ }
2543
+
2544
+ function observeManagedDaemonChild(home, child, daemonId) {
2545
+ if (!child || typeof child.once !== 'function') {
2546
+ return;
2547
+ }
2548
+ const normalizedDaemonId = String(daemonId || '').trim() || 'unknown';
2549
+ child.once('exit', (code, signal) => {
2550
+ appendDaemonSupervisorLog(
2551
+ home,
2552
+ `daemon ${normalizedDaemonId} exited with code=${code === null ? 'null' : code} signal=${signal || 'null'}`
2553
+ );
2554
+ });
2555
+ child.once('error', (error) => {
2556
+ appendDaemonSupervisorLog(
2557
+ home,
2558
+ `daemon ${normalizedDaemonId} child process error: ${error instanceof Error ? error.message : String(error)}`
2559
+ );
2560
+ });
2561
+ }
2562
+
2563
+ function encodeSupervisorEnvPayload(envOverrides) {
2564
+ const payload = envOverrides && typeof envOverrides === 'object' && !Array.isArray(envOverrides) ? envOverrides : {};
2565
+ return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
2566
+ }
2567
+
2568
+ function decodeSupervisorEnvPayload(rawValue) {
2569
+ const normalized = String(rawValue || '').trim();
2570
+ if (!normalized) {
2571
+ return {};
2572
+ }
2573
+ try {
2574
+ const parsed = JSON.parse(Buffer.from(normalized, 'base64').toString('utf8'));
2575
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
2576
+ } catch {
2577
+ return {};
2578
+ }
2579
+ }
2580
+
2581
+ function spawnManagedDaemonProcess({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId = null }) {
2582
+ const browserUrl = browserUiUrl(host, port);
2583
+ const daemonBindUrl = bindUiUrl(host, port);
2584
+ const logPath = path.join(home, 'logs', 'daemon.log');
2585
+ ensureDir(path.dirname(logPath));
2586
+ const out = fs.openSync(logPath, 'a');
2587
+ const resolvedDaemonId = String(daemonId || crypto.randomUUID()).trim();
2588
+ const launcherPath = path.join(repoRoot, 'bin', 'ds.js');
2589
+ const child = spawn(
2590
+ runtimePython,
2591
+ [
2592
+ '-m',
2593
+ 'deepscientist.cli',
2594
+ '--home',
2595
+ home,
2596
+ ...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
2597
+ 'daemon',
2598
+ '--host',
2599
+ host,
2600
+ '--port',
2601
+ String(port),
2602
+ ],
2603
+ {
2604
+ cwd: repoRoot,
2605
+ detached: true,
2606
+ stdio: ['ignore', out, out],
2607
+ env: {
2608
+ ...process.env,
2609
+ ...envOverrides,
2610
+ DEEPSCIENTIST_REPO_ROOT: repoRoot,
2611
+ DEEPSCIENTIST_NODE_BINARY: process.execPath,
2612
+ DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
2613
+ DS_DAEMON_ID: resolvedDaemonId,
2614
+ DS_DAEMON_MANAGED_BY: 'ds-launcher',
2615
+ },
2616
+ }
2617
+ );
2618
+ child.unref();
2619
+ const statePayload = {
2620
+ pid: child.pid,
2621
+ host,
2622
+ port,
2623
+ url: browserUrl,
2624
+ bind_url: daemonBindUrl,
2625
+ log_path: logPath,
2626
+ started_at: new Date().toISOString(),
2627
+ home: normalizeHomePath(home),
2628
+ daemon_id: resolvedDaemonId,
2629
+ };
2630
+ writeDaemonState(home, statePayload);
2631
+ return {
2632
+ child,
2633
+ statePayload,
2634
+ browserUrl,
2635
+ bindUrl: daemonBindUrl,
2636
+ logPath,
2637
+ };
2638
+ }
2639
+
2640
+ function spawnDaemonSupervisor({ home, runtimePython, host, port, proxy = null, envOverrides = {}, daemonId }) {
2641
+ const launcherPath = resolveLauncherPath() || path.join(repoRoot, 'bin', 'ds.js');
2642
+ const args = [
2643
+ launcherPath,
2644
+ '--daemon-supervisor',
2645
+ '--home',
2646
+ home,
2647
+ '--runtime-python',
2648
+ runtimePython,
2649
+ '--host',
2650
+ host,
2651
+ '--port',
2652
+ String(port),
2653
+ '--daemon-id',
2654
+ String(daemonId || '').trim(),
2655
+ ];
2656
+ const normalizedProxy = normalizeProxyUrl(proxy);
2657
+ if (normalizedProxy) {
2658
+ args.push('--proxy', normalizedProxy);
2659
+ }
2660
+ const envPayload = encodeSupervisorEnvPayload(envOverrides);
2661
+ if (envPayload) {
2662
+ args.push('--env-json', envPayload);
2663
+ }
2664
+ const child = spawn(process.execPath, args, {
2665
+ cwd: repoRoot,
2666
+ detached: true,
2667
+ stdio: 'ignore',
2668
+ env: {
2669
+ ...process.env,
2670
+ DEEPSCIENTIST_REPO_ROOT: repoRoot,
2671
+ DEEPSCIENTIST_NODE_BINARY: process.execPath,
2672
+ DEEPSCIENTIST_LAUNCHER_PATH: launcherPath,
2673
+ },
2674
+ });
2675
+ child.unref();
2676
+ return child.pid || null;
2677
+ }
2678
+
2679
+ function parseDaemonSupervisorArgs(argv) {
2680
+ const args = [...argv];
2681
+ let home = null;
2682
+ let runtimePython = null;
2683
+ let host = '0.0.0.0';
2684
+ let port = 20999;
2685
+ let proxy = null;
2686
+ let daemonId = null;
2687
+ let envJson = '';
2688
+
2689
+ for (let index = 0; index < args.length; index += 1) {
2690
+ const arg = args[index];
2691
+ if (arg === '--home' && args[index + 1]) home = path.resolve(args[++index]);
2692
+ else if (arg === '--runtime-python' && args[index + 1]) runtimePython = args[++index];
2693
+ else if (arg === '--host' && args[index + 1]) host = args[++index];
2694
+ else if (arg === '--port' && args[index + 1]) port = Number(args[++index]);
2695
+ else if (arg === '--proxy' && args[index + 1]) proxy = args[++index];
2696
+ else if (arg === '--daemon-id' && args[index + 1]) daemonId = args[++index];
2697
+ else if (arg === '--env-json' && args[index + 1]) envJson = args[++index];
2698
+ else if (arg === '--help' || arg === '-h') return { help: true };
2699
+ else return null;
2700
+ }
2701
+
2702
+ if (!home || !runtimePython || !daemonId || !Number.isFinite(port) || port <= 0) {
2703
+ return null;
2704
+ }
2705
+
2706
+ return {
2707
+ help: false,
2708
+ home,
2709
+ runtimePython,
2710
+ host,
2711
+ port,
2712
+ proxy,
2713
+ daemonId,
2714
+ envOverrides: decodeSupervisorEnvPayload(envJson),
2715
+ };
2716
+ }
2717
+
2718
+ async function daemonSupervisorMain(rawArgs) {
2719
+ const options = parseDaemonSupervisorArgs(rawArgs);
2720
+ if (!options) {
2721
+ console.error('Invalid daemon supervisor arguments.');
2722
+ process.exit(1);
2723
+ }
2724
+ if (options.help) {
2725
+ process.exit(0);
2726
+ }
2727
+
2728
+ const home = options.home;
2729
+ let trackedDaemonId = String(options.daemonId || '').trim();
2730
+ let restartBackoffMs = 1000;
2731
+ appendDaemonSupervisorLog(home, `supervisor started for daemon ${trackedDaemonId}`);
2732
+
2733
+ while (true) {
2734
+ const state = readDaemonState(home);
2735
+ if (!state) {
2736
+ appendDaemonSupervisorLog(home, 'daemon state removed; supervisor exiting');
2737
+ return;
2738
+ }
2739
+ if (state.shutdown_requested_at) {
2740
+ appendDaemonSupervisorLog(home, 'managed shutdown requested; supervisor exiting');
2741
+ return;
2742
+ }
2743
+ const stateHome = normalizeHomePath(state.home || home);
2744
+ if (stateHome !== normalizeHomePath(home)) {
2745
+ appendDaemonSupervisorLog(home, `daemon state home changed to ${stateHome}; supervisor exiting`);
2746
+ return;
2747
+ }
2748
+ const stateDaemonId = String(state.daemon_id || '').trim();
2749
+ if (trackedDaemonId && stateDaemonId && stateDaemonId !== trackedDaemonId) {
2750
+ appendDaemonSupervisorLog(home, `daemon id changed to ${stateDaemonId}; supervisor exiting`);
2751
+ return;
2752
+ }
2753
+ const health = await fetchHealth(state.url || browserUiUrl(options.host, options.port));
2754
+ if (health && health.status === 'ok' && healthMatchesManagedState({ health, state, home })) {
2755
+ restartBackoffMs = 1000;
2756
+ await sleep(2500);
2757
+ continue;
2758
+ }
2759
+ if (state.pid && isPidAlive(state.pid)) {
2760
+ await sleep(2500);
2761
+ continue;
2762
+ }
2763
+
2764
+ appendDaemonSupervisorLog(
2765
+ home,
2766
+ `daemon ${stateDaemonId || trackedDaemonId || 'unknown'} is not healthy; attempting restart`
2767
+ );
2768
+ try {
2769
+ const restarted = spawnManagedDaemonProcess({
2770
+ home,
2771
+ runtimePython: options.runtimePython,
2772
+ host: options.host,
2773
+ port: options.port,
2774
+ proxy: options.proxy,
2775
+ envOverrides: options.envOverrides,
2776
+ });
2777
+ trackedDaemonId = String(restarted.statePayload.daemon_id || '').trim();
2778
+ observeManagedDaemonChild(home, restarted.child, trackedDaemonId);
2779
+ appendDaemonSupervisorLog(
2780
+ home,
2781
+ `restarted daemon ${trackedDaemonId} with pid ${restarted.statePayload.pid}`
2782
+ );
2783
+ restartBackoffMs = 1000;
2784
+ await sleep(2500);
2785
+ } catch (error) {
2786
+ appendDaemonSupervisorLog(
2787
+ home,
2788
+ `restart failed: ${error instanceof Error ? error.message : String(error)}`
2789
+ );
2790
+ await sleep(restartBackoffMs);
2791
+ restartBackoffMs = Math.min(restartBackoffMs * 2, 30000);
2792
+ }
2793
+ }
2794
+ }
2795
+
2481
2796
  function sleep(ms) {
2482
2797
  return new Promise((resolve) => setTimeout(resolve, ms));
2483
2798
  }
@@ -2649,6 +2964,13 @@ async function stopDaemon(home) {
2649
2964
  }
2650
2965
  }
2651
2966
 
2967
+ if (state) {
2968
+ writeDaemonState(home, {
2969
+ ...state,
2970
+ shutdown_requested_at: new Date().toISOString(),
2971
+ });
2972
+ }
2973
+
2652
2974
  let stopped = false;
2653
2975
 
2654
2976
  if (healthyBefore) {
@@ -2769,6 +3091,33 @@ function printUpdateStatus(status, { compact = false } = {}) {
2769
3091
  }
2770
3092
  }
2771
3093
 
3094
+ function parseYesNoAnswer(answer, defaultValue = false) {
3095
+ const normalized = String(answer || '').trim().toLowerCase();
3096
+ if (!normalized) {
3097
+ return defaultValue;
3098
+ }
3099
+ if (normalized === 'y' || normalized === 'yes') {
3100
+ return true;
3101
+ }
3102
+ if (normalized === 'n' || normalized === 'no') {
3103
+ return false;
3104
+ }
3105
+ return defaultValue;
3106
+ }
3107
+
3108
+ async function promptYesNo(question, { defaultValue = false } = {}) {
3109
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3110
+ return defaultValue;
3111
+ }
3112
+ return new Promise((resolve) => {
3113
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3114
+ rl.question(question, (answer) => {
3115
+ rl.close();
3116
+ resolve(parseYesNoAnswer(answer, defaultValue));
3117
+ });
3118
+ });
3119
+ }
3120
+
2772
3121
  function spawnDetachedNode(args, options = {}) {
2773
3122
  const out = options.logPath ? fs.openSync(options.logPath, 'a') : 'ignore';
2774
3123
  const child = spawn(process.execPath, args, {
@@ -2965,6 +3314,50 @@ async function performSelfUpdate(home, options = {}) {
2965
3314
  };
2966
3315
  }
2967
3316
 
3317
+ function normalizeLauncherRelaunchArgs(rawArgs, home) {
3318
+ const normalized = [];
3319
+ for (let index = 0; index < rawArgs.length; index += 1) {
3320
+ const arg = rawArgs[index];
3321
+ if (arg === '--home') {
3322
+ index += 1;
3323
+ continue;
3324
+ }
3325
+ if (arg === '--here' || arg === '--skip-update-check') {
3326
+ continue;
3327
+ }
3328
+ normalized.push(arg);
3329
+ }
3330
+ return ['--home', home, ...normalized, '--skip-update-check'];
3331
+ }
3332
+
3333
+ function relaunchLauncherAfterUpdate(rawArgs, home) {
3334
+ const launcherPath = resolveLauncherPath();
3335
+ if (!launcherPath) {
3336
+ return {
3337
+ ok: false,
3338
+ exitCode: 1,
3339
+ message: 'DeepScientist was updated, but the new launcher path could not be resolved for relaunch.',
3340
+ };
3341
+ }
3342
+ const result = spawnSync(process.execPath, [launcherPath, ...normalizeLauncherRelaunchArgs(rawArgs, home)], {
3343
+ cwd: repoRoot,
3344
+ stdio: 'inherit',
3345
+ env: process.env,
3346
+ });
3347
+ if (result.error) {
3348
+ return {
3349
+ ok: false,
3350
+ exitCode: 1,
3351
+ message: result.error.message,
3352
+ };
3353
+ }
3354
+ return {
3355
+ ok: true,
3356
+ exitCode: result.status ?? 0,
3357
+ message: null,
3358
+ };
3359
+ }
3360
+
2968
3361
  async function maybeHandleStartupUpdate(home, rawArgs, options = {}) {
2969
3362
  if (options.skipUpdateCheck || process.env.DS_SKIP_UPDATE_PROMPT === '1') {
2970
3363
  return false;
@@ -2978,8 +3371,43 @@ async function maybeHandleStartupUpdate(home, rawArgs, options = {}) {
2978
3371
  }
2979
3372
 
2980
3373
  printUpdateStatus(status, { compact: true });
2981
- markUpdateDeferred(home, status.latest_version);
2982
- return false;
3374
+ if (!status.can_self_update || !process.stdin.isTTY || !process.stdout.isTTY) {
3375
+ markUpdateDeferred(home, status.latest_version);
3376
+ return false;
3377
+ }
3378
+
3379
+ const confirmed = await promptYesNo(`Install DeepScientist ${status.latest_version} now? [y/N]: `, {
3380
+ defaultValue: false,
3381
+ });
3382
+ if (!confirmed) {
3383
+ markUpdateDeferred(home, status.latest_version);
3384
+ console.log(`DeepScientist will remind you later about ${status.latest_version || 'the next release'}.`);
3385
+ return false;
3386
+ }
3387
+
3388
+ console.log('Updating DeepScientist now...');
3389
+ const payload = await performSelfUpdate(home, {
3390
+ host: options.host,
3391
+ port: options.port,
3392
+ restartDaemon: false,
3393
+ });
3394
+ console.log(payload.message);
3395
+ if (payload.log_path) {
3396
+ console.log(`Update log: ${payload.log_path}`);
3397
+ }
3398
+ if (!payload.ok) {
3399
+ console.log('DeepScientist will continue launching with the current session.');
3400
+ return false;
3401
+ }
3402
+
3403
+ console.log('Relaunching DeepScientist...');
3404
+ const relaunch = relaunchLauncherAfterUpdate(rawArgs, home);
3405
+ if (!relaunch.ok) {
3406
+ console.error(relaunch.message);
3407
+ process.exit(relaunch.exitCode || 1);
3408
+ }
3409
+ process.exit(relaunch.exitCode || 0);
3410
+ return true;
2983
3411
  }
2984
3412
 
2985
3413
  async function startBackgroundUpdateWorker(home, options = {}) {
@@ -3133,61 +3561,36 @@ async function startDaemon(home, runtimePython, host, port, proxy = null, envOve
3133
3561
  }
3134
3562
 
3135
3563
  ensureNodeBundle('src/ui', 'dist/index.html');
3136
-
3137
- const logPath = path.join(home, 'logs', 'daemon.log');
3138
- ensureDir(path.dirname(logPath));
3139
- const out = fs.openSync(logPath, 'a');
3140
- const daemonId = crypto.randomUUID();
3141
- const child = spawn(
3564
+ const startedProcess = spawnManagedDaemonProcess({
3565
+ home,
3142
3566
  runtimePython,
3143
- [
3144
- '-m',
3145
- 'deepscientist.cli',
3146
- '--home',
3147
- home,
3148
- ...(normalizeProxyUrl(proxy) ? ['--proxy', normalizeProxyUrl(proxy)] : []),
3149
- 'daemon',
3150
- '--host',
3151
- host,
3152
- '--port',
3153
- String(port),
3154
- ],
3155
- {
3156
- cwd: repoRoot,
3157
- detached: true,
3158
- stdio: ['ignore', out, out],
3159
- env: {
3160
- ...process.env,
3161
- ...envOverrides,
3162
- DEEPSCIENTIST_REPO_ROOT: repoRoot,
3163
- DEEPSCIENTIST_NODE_BINARY: process.execPath,
3164
- DEEPSCIENTIST_LAUNCHER_PATH: path.join(repoRoot, 'bin', 'ds.js'),
3165
- DS_DAEMON_ID: daemonId,
3166
- DS_DAEMON_MANAGED_BY: 'ds-launcher',
3167
- },
3168
- }
3169
- );
3170
- child.unref();
3171
- const statePayload = {
3172
- pid: child.pid,
3173
3567
  host,
3174
3568
  port,
3175
- url: browserUrl,
3176
- bind_url: daemonBindUrl,
3177
- log_path: logPath,
3178
- started_at: new Date().toISOString(),
3179
- home: normalizeHomePath(home),
3180
- daemon_id: daemonId,
3181
- };
3182
- writeDaemonState(home, statePayload);
3569
+ proxy,
3570
+ envOverrides,
3571
+ });
3572
+ const logPath = startedProcess.logPath;
3183
3573
 
3184
3574
  for (let attempt = 0; attempt < 60; attempt += 1) {
3185
3575
  const health = await fetchHealth(browserUrl);
3186
3576
  if (health && health.status === 'ok') {
3187
- if (!healthMatchesManagedState({ health, state: readDaemonState(home), home })) {
3188
- console.error(daemonIdentityError({ url: browserUrl, home, health, state: readDaemonState(home) }));
3577
+ const liveState = readDaemonState(home);
3578
+ if (!healthMatchesManagedState({ health, state: liveState, home })) {
3579
+ console.error(daemonIdentityError({ url: browserUrl, home, health, state: liveState }));
3189
3580
  process.exit(1);
3190
3581
  }
3582
+ const supervisorPid = spawnDaemonSupervisor({
3583
+ home,
3584
+ runtimePython,
3585
+ host,
3586
+ port,
3587
+ proxy,
3588
+ envOverrides,
3589
+ daemonId: String((liveState || {}).daemon_id || ''),
3590
+ });
3591
+ if (supervisorPid) {
3592
+ appendDaemonSupervisorLog(home, `supervisor started with pid ${supervisorPid}`);
3593
+ }
3191
3594
  return { url: browserUrl, bindUrl: daemonBindUrl, reused: false };
3192
3595
  }
3193
3596
  await sleep(250);
@@ -3244,14 +3647,34 @@ function handleCodexPreflightFailure(error) {
3244
3647
  if (!error || error.code !== 'DS_CODEX_PREFLIGHT') {
3245
3648
  return false;
3246
3649
  }
3650
+ const errorLabel = colorize('\u001B[1;38;5;196m', 'ERROR');
3651
+ const warningLabel = colorize('\u001B[1;38;5;214m', 'WARNING');
3247
3652
  console.error('');
3248
- console.error('DeepScientist could not start because Codex is not ready yet.');
3653
+ console.error(`${errorLabel} DeepScientist could not start because Codex is not ready yet.`);
3249
3654
  console.error(`Report: ${error.reportPath}`);
3250
3655
  if (Array.isArray(error.probe?.errors)) {
3251
3656
  for (const item of error.probe.errors) {
3252
- console.error(` - ${item}`);
3657
+ console.error(`${errorLabel} ${item}`);
3658
+ }
3659
+ }
3660
+ if (Array.isArray(error.probe?.warnings)) {
3661
+ for (const item of error.probe.warnings) {
3662
+ console.error(`${warningLabel} ${item}`);
3253
3663
  }
3254
3664
  }
3665
+ console.error(`${warningLabel} Recommended fix:`);
3666
+ const guidance = Array.isArray(error.probe?.guidance) && error.probe.guidance.length > 0
3667
+ ? error.probe.guidance
3668
+ : [
3669
+ 'In most installs, `npm install -g @researai/deepscientist` also installs the bundled Codex dependency.',
3670
+ 'If `codex` is still missing, run `npm install -g @openai/codex`.',
3671
+ 'Run `codex --login` (or `codex`) and finish authentication.',
3672
+ 'Run `ds doctor` and confirm the Codex check passes.',
3673
+ 'Run `ds` again.',
3674
+ ];
3675
+ guidance.forEach((item, index) => {
3676
+ console.error(`${warningLabel} ${index + 1}. ${item}`);
3677
+ });
3255
3678
  openBrowser(error.reportUrl);
3256
3679
  process.exit(1);
3257
3680
  return true;
@@ -3375,7 +3798,29 @@ async function updateMain(rawArgs) {
3375
3798
  process.exit(0);
3376
3799
  }
3377
3800
  printUpdateStatus(status, { compact: true });
3378
- process.exit(0);
3801
+ if (!status.can_self_update) {
3802
+ process.exit(0);
3803
+ }
3804
+
3805
+ const confirmed = await promptYesNo(`Install DeepScientist ${status.latest_version} now? [y/N]: `, {
3806
+ defaultValue: false,
3807
+ });
3808
+ if (!confirmed) {
3809
+ const payload = markUpdateDeferred(home, status.latest_version);
3810
+ console.log(`DeepScientist will remind you later about ${payload.latest_version || 'the next release'}.`);
3811
+ process.exit(0);
3812
+ }
3813
+
3814
+ const payload = await performSelfUpdate(home, {
3815
+ host: options.host,
3816
+ port: options.port,
3817
+ restartDaemon: options.restartDaemon,
3818
+ });
3819
+ console.log(payload.message);
3820
+ if (payload.log_path) {
3821
+ console.log(`Update log: ${payload.log_path}`);
3822
+ }
3823
+ process.exit(payload.ok ? 0 : 1);
3379
3824
  }
3380
3825
 
3381
3826
  async function migrateMain(rawArgs) {
@@ -3557,7 +4002,7 @@ async function launcherMain(rawArgs) {
3557
4002
 
3558
4003
  const pythonRuntime = ensurePythonRuntime(home);
3559
4004
  const runtimePython = pythonRuntime.runtimePython;
3560
- const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo });
4005
+ const codexOverrideEnv = buildCodexOverrideEnv({ yolo: options.yolo, profile: options.codexProfile });
3561
4006
  ensureInitialized(home, runtimePython);
3562
4007
  if (await maybeHandleStartupUpdate(home, rawArgs, options)) {
3563
4008
  return true;
@@ -3610,6 +4055,10 @@ async function launcherMain(rawArgs) {
3610
4055
 
3611
4056
  async function main() {
3612
4057
  const args = process.argv.slice(2);
4058
+ if (args[0] === '--daemon-supervisor') {
4059
+ await daemonSupervisorMain(args.slice(1));
4060
+ return;
4061
+ }
3613
4062
  const positional = findFirstPositionalArg(args);
3614
4063
  if (positional && positional.value === 'update') {
3615
4064
  await updateMain(args);
@@ -3631,7 +4080,10 @@ async function main() {
3631
4080
  const home = resolveHome(args);
3632
4081
  const pythonRuntime = ensurePythonRuntime(home);
3633
4082
  const runtimePython = pythonRuntime.runtimePython;
3634
- const codexOverrideEnv = buildCodexOverrideEnv({ yolo: args.includes('--yolo') });
4083
+ const codexOverrideEnv = buildCodexOverrideEnv({
4084
+ yolo: args.includes('--yolo'),
4085
+ profile: readOptionValue(args, '--codex-profile'),
4086
+ });
3635
4087
  if (positional.value === 'run' || positional.value === 'daemon') {
3636
4088
  maybePrintOptionalLatexNotice(home);
3637
4089
  }
@@ -3680,6 +4132,10 @@ module.exports = {
3680
4132
  detectInstallMode,
3681
4133
  updateManualCommand,
3682
4134
  buildUpdateStatus,
4135
+ parseYesNoAnswer,
4136
+ normalizeLauncherRelaunchArgs,
4137
+ officialRepositoryLine,
4138
+ stripAnsi,
3683
4139
  },
3684
4140
  };
3685
4141